[PATCH] MTD: Micron SPINAND Driver support
Mona Anonuevo (manonuevo)
manonuevo at micron.com
Mon May 24 17:54:00 EDT 2010
This patch adds support for Micron SPINAND via MTD.
Signed-off-by: Mona Anonuevo (manonuevo at micron.com)
Signed-off-by: Tuan Nguyen (tqnguyen at micron.com)
----
drivers/mtd/Kconfig | 2
drivers/mtd/Makefile | 2
drivers/mtd/spinand/Kconfig | 25 +
drivers/mtd/spinand/Makefile | 10
drivers/mtd/spinand/spinand_lld.c | 760
++++++++++++++++++++++++++++++++++++++
drivers/mtd/spinand/spinand_mtd.c | 728
++++++++++++++++++++++++++++++++++++
include/linux/mtd/spinand.h | 160 ++++++++
7 files changed, 1687 insertions(+)
PATCH FOLLOWS
Kernel Version: Linux 2.6.33.20
diff -urN linux-2.6.33.20-orig/drivers/mtd/Kconfig
linux-2.6.33.20/drivers/mtd/Kconfig
--- linux-2.6.33.20-orig/drivers/mtd/Kconfig 2010-04-01
16:02:33.000000000 -0700
+++ linux-2.6.33.20/drivers/mtd/Kconfig 2010-05-19 04:28:06.000000000
-0700
@@ -325,6 +325,8 @@
source "drivers/mtd/onenand/Kconfig"
+source "drivers/mtd/spinand/Kconfig"
+
source "drivers/mtd/lpddr/Kconfig"
source "drivers/mtd/ubi/Kconfig"
diff -urN linux-2.6.33.20-orig/drivers/mtd/Makefile
linux-2.6.33.20/drivers/mtd/Makefile
--- linux-2.6.33.20-orig/drivers/mtd/Makefile 2010-04-01
16:02:33.000000000 -0700
+++ linux-2.6.33.20/drivers/mtd/Makefile 2010-05-19
04:28:06.000000000 -0700
@@ -31,4 +31,6 @@
obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
+obj-y += spinand/
+
obj-$(CONFIG_MTD_UBI) += ubi/
diff -urN linux-2.6.33.20-orig/drivers/mtd/spinand/Kconfig
linux-2.6.33.20/drivers/mtd/spinand/Kconfig
--- linux-2.6.33.20-orig/drivers/mtd/spinand/Kconfig 1969-12-31
16:00:00.000000000 -0800
+++ linux-2.6.33.20/drivers/mtd/spinand/Kconfig 2010-05-19
04:28:06.000000000 -0700
@@ -0,0 +1,25 @@
+#
+# linux/drivers/mtd/spinand/Kconfig
+#
+
+menuconfig MTD_SPINAND
+ tristate "SPINAND Device Support"
+ depends on MTD
+ help
+ This enables support for accessing Micron SPI NAND flash
+ devices.
+
+if MTD_SPINAND
+
+config MTD_SPINAND_ONDIEECC
+ bool "Use SPINAND internal ECC"
+ help
+ Internel ECC
+
+config MTD_SPINAND_SWECC
+ bool "Use software ECC"
+ depends on MTD_NAND
+ help
+ software ECC
+
+endif
diff -urN linux-2.6.33.20-orig/drivers/mtd/spinand/Makefile
linux-2.6.33.20/drivers/mtd/spinand/Makefile
--- linux-2.6.33.20-orig/drivers/mtd/spinand/Makefile 1969-12-31
16:00:00.000000000 -0800
+++ linux-2.6.33.20/drivers/mtd/spinand/Makefile 2010-05-19
04:28:07.000000000 -0700
@@ -0,0 +1,10 @@
+#
+# Makefile for the SPI NAND MTD
+#
+
+# Core functionality.
+obj-$(CONFIG_MTD_SPINAND) += spinand.o
+
+spinand-objs := spinand_mtd.o spinand_lld.o
+
+
diff -urN linux-2.6.33.20-orig/drivers/mtd/spinand/spinand_lld.c
linux-2.6.33.20/drivers/mtd/spinand/spinand_lld.c
--- linux-2.6.33.20-orig/drivers/mtd/spinand/spinand_lld.c
1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.33.20/drivers/mtd/spinand/spinand_lld.c 2010-05-19
03:56:51.000000000 -0700
@@ -0,0 +1,760 @@
+/*
+spinand_lld.c
+
+Copyright (c) 2009-2010 Micron Technology, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/math64.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spinand.h>
+
+#include <linux/spi/spi.h>
+
+#define mu_spi_nand_driver_version
"Beagle-MTD_01.00_Linux2.6.33_20100507"
+#define SPI_NAND_MICRON_DRIVER_KEY 0x1233567
+
+/**********************************************************************
******/
+
+/**
+ OOB area specification layout: Total 32 available free bytes.
+*/
+static struct nand_ecclayout spinand_oob_64 = {
+ .eccbytes = 24,
+ .eccpos = {
+ 1, 2, 3, 4, 5, 6,
+ 17, 18, 19, 20, 21, 22,
+ 33, 34, 35, 36, 37, 38,
+ 49, 50, 51, 52, 53, 54, },
+ .oobavail = 32,
+ .oobfree = {
+ {.offset = 8,
+ .length = 8},
+ {.offset = 24,
+ .length = 8},
+ {.offset = 40,
+ .length = 8},
+ {.offset = 56,
+ .length = 8}, }
+};
+/**
+ * spinand_cmd - to process a command to send to the SPI Nand
+ *
+ * Description:
+ * Set up the command buffer to send to the SPI controller.
+ * The command buffer has to initized to 0
+ */
+int spinand_cmd(struct spi_device *spi, struct spinand_cmd *cmd)
+{
+ int ret;
+ struct spi_message message;
+ struct spi_transfer x[4];
+ u8 dummy = 0xff;
+
+
+ spi_message_init(&message);
+ memset(x, 0, sizeof x);
+
+ x[0].len = 1;
+ x[0].tx_buf = &cmd->cmd;
+ spi_message_add_tail(&x[0], &message);
+
+ if (cmd->n_addr)
+ {
+ x[1].len = cmd->n_addr;
+ x[1].tx_buf = cmd->addr;
+ spi_message_add_tail(&x[1], &message);
+ }
+
+ if (cmd->n_dummy)
+ {
+ x[2].len = cmd->n_dummy;
+ x[2].tx_buf = &dummy;
+ spi_message_add_tail(&x[2], &message);
+ }
+
+ if (cmd->n_tx)
+ {
+ x[3].len = cmd->n_tx;
+ x[3].tx_buf = cmd->tx_buf;
+ spi_message_add_tail(&x[3], &message);
+ }
+
+ if (cmd->n_rx)
+ {
+ x[3].len = cmd->n_rx;
+ x[3].rx_buf = cmd->rx_buf;
+ spi_message_add_tail(&x[3], &message);
+ }
+
+ ret = spi_sync(spi, &message);
+
+ return ret;
+}
+
+/**
+ * spinand_reset- send reset command "0xff" to the Nand device
+ *
+ * Description:
+ * Reset the SPI Nand with the reset command 0xff
+ */
+static int spinand_reset(struct spi_device *spi_nand)
+{
+ struct spinand_cmd cmd = {0};
+
+ cmd.cmd = CMD_RESET;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+/**
+ * spinand_read_id- Read SPI Nand ID
+ *
+ * Description:
+ * Read ID: read two ID bytes from the SPI Nand device
+ */
+static int spinand_read_id(struct spi_device *spi_nand, u8 *id)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+
+ cmd.cmd = CMD_READ_ID;
+ cmd.n_dummy = 1;
+ cmd.n_rx = 2;
+ cmd.rx_buf = id;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d reading id\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_lock_block- send write register 0x1f command to the Nand
device
+ *
+ * Description:
+ * After power up, all the Nand blocks are locked. This function
allows
+ * one to unlock the blocks, and so it can be wriiten or erased.
+ */
+static int spinand_lock_block(struct spi_device *spi_nand, struct
spinand_info *info, u8 lock)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_WRITE_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_BLOCK_LOCK;
+ cmd.n_tx = 1;
+ cmd.tx_buf = &lock;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d lock block\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_read_status- send command 0xf to the SPI Nand status
register
+ *
+ * Description:
+ * After read, write, or erase, the Nand device is expected to set
the busy status.
+ * This function is to allow reading the status of the command:
read, write, and erase.
+ * Once the status turns to be ready, the other status bits also are
valid status bits.
+ */
+static int spinand_read_status(struct spi_device *spi_nand, struct
spinand_info *info, u8 *status)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_READ_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_STATUS;
+ cmd.n_rx = 1;
+ cmd.rx_buf = status;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d reading status
register\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_get_otp- send command 0xf to read the SPI Nand OTP register
+ *
+ * Description:
+ * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
+ * Enable chip internal ECC, set the bit to 1
+ * Disable chip internal ECC, clear the bit to 0
+ */
+static int spinand_get_otp(struct spi_device *spi_nand, struct
spinand_info *info, u8* otp)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_READ_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_OTP;
+ cmd.n_rx = 1;
+ cmd.rx_buf = otp;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d get otp\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+
+/**
+ * spinand_set_otp- send command 0x1f to write the SPI Nand OTP
register
+ *
+ * Description:
+ * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
+ * Enable chip internal ECC, set the bit to 1
+ * Disable chip internal ECC, clear the bit to 0
+ */
+static int spinand_set_otp(struct spi_device *spi_nand, struct
spinand_info *info, u8* otp)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_WRITE_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_OTP;
+ cmd.n_tx = 1;
+ cmd.tx_buf = otp;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d set otp\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * sspinand_enable_ecc- send command 0x1f to write the SPI Nand OTP
register
+ *
+ * Description:
+ * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
+ * Enable chip internal ECC, set the bit to 1
+ * Disable chip internal ECC, clear the bit to 0
+ */
+static int spinand_enable_ecc(struct spi_device *spi_nand, struct
spinand_info *info)
+{
+ ssize_t retval;
+ u8 otp = 0;
+
+ retval = spinand_get_otp(spi_nand, info, &otp);
+
+ if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK)
+ {
+ return 0;
+ }
+ else
+ {
+ otp |= OTP_ECC_MASK;
+ retval = spinand_set_otp(spi_nand, info, &otp);
+ retval = spinand_get_otp(spi_nand, info, &otp);
+ return retval;
+ }
+}
+
+static int spinand_disable_ecc(struct spi_device *spi_nand, struct
spinand_info *info)
+{
+ ssize_t retval;
+ u8 otp = 0;
+
+ retval = spinand_get_otp(spi_nand, info, &otp);
+
+
+ if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK)
+ {
+ otp &= ~OTP_ECC_MASK;
+ retval = spinand_set_otp(spi_nand, info, &otp);
+ retval = spinand_get_otp(spi_nand, info, &otp);
+ return retval;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/**
+ * sspinand_write_enable- send command 0x06 to enable write or erase
the Nand cells
+ *
+ * Description:
+ * Before write and erase the Nand cells, the write enable has to be
set.
+ * After the write or erase, the write enable bit is automatically
cleared( status register bit 2 )
+ * Set the bit 2 of the status register has the same effect
+ */
+static int spinand_write_enable(struct spi_device *spi_nand, struct
spinand_info *info)
+{
+ struct spinand_cmd cmd = {0};
+
+ cmd.cmd = CMD_WR_ENABLE;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+static int spinand_read_page_to_cache(struct spi_device *spi_nand,
struct spinand_info *info, u16 page_id)
+{
+ struct spinand_cmd cmd = {0};
+ u16 row;
+
+ row = page_id;
+
+ cmd.cmd = CMD_READ;
+ cmd.n_addr = 3;
+ cmd.addr[1] = (u8)((row&0xff00)>>8);
+ cmd.addr[2] = (u8)(row&0x00ff);
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_read_from_cache- send command 0x03 to read out the data from
the cache register( 2112 bytes max )
+ *
+ * Description:
+ * The read can specify 1 to 2112 bytes of data read at the
coresponded locations.
+ * No tRd delay.
+ */
+static int spinand_read_from_cache(struct spi_device *spi_nand, struct
spinand_info *info, u16 byte_id, u16 len, u8* rbuf)
+{
+ struct spinand_cmd cmd = {0};
+ u16 column;
+
+ column = byte_id;
+
+ cmd.cmd = CMD_READ_RDM;
+ cmd.n_addr = 2;
+ cmd.addr[0] = (u8)((column&0xff00)>>8);
+ cmd.addr[1] = (u8)(column&0x00ff);
+ cmd.n_dummy = 1;
+ cmd.n_rx = len;
+ cmd.rx_buf = rbuf;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_read_page-to read a page with:
+ * @page_id: the physical page number
+ * @offset: the location from 0 to 2111
+ * @len: number of bytes to read
+ * @rbuf: read buffer to hold @len bytes
+ *
+ * Description:
+ * The read icludes two commands to the Nand: 0x13 and 0x03 commands
+ * Poll to read status to wait for tRD time.
+ */
+static int spinand_read_page(struct spi_device *spi_nand, struct
spinand_info *info, u16 page_id, u16 offset, u16 len, u8* rbuf)
+{
+ ssize_t retval;
+ u8 status = 0;
+
+ retval = spinand_read_page_to_cache(spi_nand, info, page_id);
+
+ while (1)
+ {
+ retval = spinand_read_status(spi_nand, info, &status);
+ if (retval<0) {
+ dev_err(&spi_nand->dev, "error %d reading status
register\n",
+ (int) retval);
+ return retval;
+ }
+
+ if ((status & STATUS_OIP_MASK) == STATUS_READY)
+ {
+ if ((status & STATUS_ECC_MASK) ==
STATUS_ECC_ERROR)
+ {
+ dev_err(&spi_nand->dev, "ecc error,
page=%d\n", page_id);
+ if (spi_nand ==
SPI_NAND_MICRON_DRIVER_KEY)
+ printk(KERN_INFO "Error:
reformat or erase your device. \n");
+ else
+ return -1;
+ }
+ break;
+ }
+ }
+
+ retval = spinand_read_from_cache(spi_nand, info, offset, len,
rbuf);
+ return 0;
+
+}
+
+/**
+ * spinand_program_data_to_cache--to write a page to cache with:
+ * @byte_id: the location to write to the cache
+ * @len: number of bytes to write
+ * @rbuf: read buffer to hold @len bytes
+ *
+ * Description:
+ * The write command used here is 0x84--indicating that the cache is
not cleared first.
+ * Since it is writing the data to cache, there is no tPROG time.
+ */
+static int spinand_program_data_to_cache(struct spi_device *spi_nand,
struct spinand_info *info, u16 byte_id, u16 len, u8* wbuf)
+{
+ struct spinand_cmd cmd = {0};
+ u16 column;
+
+ column = byte_id;
+
+ cmd.cmd = CMD_PROG_PAGE_CLRCACHE;
+ cmd.n_addr = 2;
+ cmd.addr[0] = (u8)((column&0xff00)>>8);
+ cmd.addr[1] = (u8)(column&0x00ff);
+ cmd.n_tx = len;
+ cmd.tx_buf = wbuf;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_program_execute--to write a page from cache to the Nand
array with:
+ * @page_id: the physical page location to write the page.
+ *
+ * Description:
+ * The write command used here is 0x10--indicating the cache is
writing to the Nand array.
+ * Need to wait for tPROG time to finish the transaction.
+ */
+static int spinand_program_execute(struct spi_device *spi_nand, struct
spinand_info *info, u16 page_id)
+{
+ struct spinand_cmd cmd = {0};
+ u16 row;
+
+ row = page_id;
+
+ cmd.cmd = CMD_PROG_PAGE_EXC;
+ cmd.n_addr = 3;
+ cmd.addr[1] = (u8)((row&0xff00)>>8);
+ cmd.addr[2] = (u8)(row&0x00ff);
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_program_page--to write a page with:
+ * @page_id: the physical page location to write the page.
+ * @offset: the location from the cache starting from 0 to 2111
+ * @len: the number of bytes to write
+ * @wbuf: the buffer to hold the number of bytes
+ *
+ * Description:
+ * The commands used here are 0x06, 0x84, and 0x10--indicating that
the write enable is first
+ * sent, the write cache command, and the write execute command
+ * Poll to wait for the tPROG time to finish the transaction.
+ */
+static int spinand_program_page(struct spi_device *spi_nand, struct
spinand_info *info, u16 page_id, u16 offset, u16 len, u8* wbuf)
+{
+ ssize_t retval;
+ u8 status = 0;
+
+ retval = spinand_write_enable(spi_nand, info);
+
+ retval = spinand_program_data_to_cache(spi_nand, info, offset,
len, wbuf);
+
+ retval = spinand_program_execute(spi_nand, info, page_id);
+
+ while (1)
+ {
+ retval = spinand_read_status(spi_nand, info, &status);
+ if (retval<0) {
+ dev_err(&spi_nand->dev, "error %d reading status
register\n",
+ (int) retval);
+ return retval;
+ }
+
+ if ((status & STATUS_OIP_MASK) == STATUS_READY)
+ {
+
+ if ((status & STATUS_P_FAIL_MASK) ==
STATUS_P_FAIL)
+ {
+ dev_err(&spi_nand->dev, "program error,
page=%d\n", page_id);
+ return -1;
+ }
+ else
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_erase_block_erase--to erase a page with:
+ * @block_id: the physical block location to erase.
+ *
+ * Description:
+ * The command used here is 0xd8--indicating an erase command to
erase one block--64 pages
+ * Need to wait for tERS.
+ */
+static int spinand_erase_block_erase(struct spi_device *spi_nand,
struct spinand_info *info, u16 block_id)
+{
+ struct spinand_cmd cmd = {0};
+ u16 row;
+
+ row = block_id << 6;
+ cmd.cmd = CMD_ERASE_BLK;
+ cmd.n_addr = 3;
+ cmd.addr[1] = (u8)((row&0xff00)>>8);
+ cmd.addr[2] = (u8)(row&0x00ff);
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_erase_block--to erase a page with:
+ * @block_id: the physical block location to erase.
+ *
+ * Description:
+ * The commands used here are 0x06 and 0xd8--indicating an erase
command to erase one block--64 pages
+ * It will first to enable the write enable bit ( 0x06 command ), and
then send the 0xd8 erase command
+ * Poll to wait for the tERS time to complete the tranaction.
+ */
+static int spinand_erase_block(struct spi_device *spi_nand, struct
spinand_info *info, u16 block_id)
+{
+ ssize_t retval;
+ u8 status= 0;
+
+ retval = spinand_write_enable(spi_nand, info);
+
+ retval = spinand_erase_block_erase(spi_nand, info, block_id);
+
+ while (1)
+ {
+ retval = spinand_read_status(spi_nand, info, &status);
+ if (retval<0) {
+ dev_err(&spi_nand->dev, "error %d reading status
register\n",
+ (int) retval);
+ return retval;
+ }
+
+ if ((status & STATUS_OIP_MASK) == STATUS_READY)
+ {
+ if ((status & STATUS_E_FAIL_MASK) ==
STATUS_E_FAIL)
+ {
+ dev_err(&spi_nand->dev, "erase error,
block=%d\n", block_id);
+ return -1;
+ }
+ else
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*
+* spinand_get_info: get NAND info, from read id or const value
+
+ * Description:
+ * To set up the device parameters.
+ */
+static int spinand_get_info(struct spi_device *spi_nand, struct
spinand_info *info, u8* id)
+{
+ if (id[0]==0x2C && (id[1]==0x11 || id[1]==0x12 || id[1]==0x13))
+ {
+ info->mid = id[0];
+ info->did = id[1];
+ info->name = "MT29F1G01ZAC";
+ info->nand_size = (1024 * 64 * 2112);
+ info->usable_size = (1024 * 64 * 2048);
+ info->block_size = (2112*64);
+ info->block_main_size = (2048*64);
+ info->block_num_per_chip = 1024;
+ info->page_size = 2112;
+ info->page_main_size = 2048;
+ info->page_spare_size = 64;
+ info->page_num_per_block = 64;
+
+ info->block_shift = 17;
+ info->block_mask = 0x1ffff;
+
+ info->page_shift = 11;
+ info->page_mask = 0x7ff;
+
+ info->ecclayout = &spinand_oob_64;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_probe - [spinand Interface]
+* @spi_nand: registered device driver.
+ *
+ * Description:
+ * To set up the device driver parameters to make the device
available.
+ */
+static int __devinit spinand_probe(struct spi_device *spi_nand)
+{
+ ssize_t retval;
+ struct mtd_info *mtd;
+ struct spinand_chip *chip;
+ struct spinand_info *info;
+ u8 id[2]= {0};
+
+ retval = spinand_reset(spi_nand);
+ retval = spinand_reset(spi_nand);
+ retval = spinand_read_id(spi_nand, (u8*)&id);
+ if (id[0]==0 && id[1]==0)
+ {
+ printk(KERN_INFO "SPINAND: read id error! 0x%02x,
0x%02x!\n", id[0], id[1]);
+ return 0;
+ }
+
+ info = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ retval = spinand_get_info(spi_nand, info, (u8*)&id);
+ printk(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n", id[0], id[1],
info->name);
+ printk(KERN_INFO "%s\n", mu_spi_nand_driver_version);
+ retval = spinand_lock_block(spi_nand, info, BL_ALL_UNLOCKED);
+
+#ifdef CONFIG_MTD_SPINAND_ONDIEECC
+ retval = spinand_enable_ecc(spi_nand, info);
+#else
+ retval = spinand_disable_ecc(spi_nand, info);
+#endif
+
+ chip = kzalloc(sizeof(struct spinand_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->spi_nand = spi_nand;
+ chip->info = info;
+ chip->reset = spinand_reset;
+ chip->read_id = spinand_read_id;
+ chip->read_page = spinand_read_page;
+ chip->program_page = spinand_program_page;
+ chip->erase_block = spinand_erase_block;
+
+ chip->buf = kzalloc(info->page_size, GFP_KERNEL);
+ if (!chip->buf)
+ return -ENOMEM;
+
+ chip->oobbuf = kzalloc(info->ecclayout->oobavail, GFP_KERNEL);
+ if (!chip->oobbuf)
+ return -ENOMEM;
+
+ mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ if (!mtd)
+ return -ENOMEM;
+
+ dev_set_drvdata(&spi_nand->dev, mtd);
+
+ mtd->priv = chip;
+
+ retval = spinand_mtd(mtd);
+
+ return retval;
+}
+
+/**
+ * __devexit spinand_remove--Remove the device driver
+ * @spi: the spi device.
+ *
+ * Description:
+ * To remove the device driver parameters and free up allocated
memories.
+ */
+static int __devexit spinand_remove(struct spi_device *spi)
+{
+ struct mtd_info *mtd;
+ struct spinand_chip *chip;
+
+ DEBUG(MTD_DEBUG_LEVEL1, "%s: remove\n", dev_name(&spi->dev));
+
+ mtd = dev_get_drvdata(&spi->dev);
+
+ spinand_mtd_release(mtd);
+
+ chip = mtd->priv;
+
+ kfree(chip->info);
+ kfree(chip->buf);
+ kfree(chip->oobbuf);
+ kfree(chip);
+ kfree(mtd);
+
+ return 0;
+}
+
+/**
+ * Device name structure description
+*/
+static struct spi_driver spinand_driver = {
+ .driver = {
+ .name = "spi_nand",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+
+ .probe = spinand_probe,
+ .remove = __devexit_p(spinand_remove),
+};
+
+/**
+ * Device driver registration
+*/
+static int __init spinand_init(void)
+{
+ return spi_register_driver(&spinand_driver);
+}
+
+/**
+ * unregister Device driver.
+*/
+static void __exit spinand_exit(void)
+{
+ spi_unregister_driver(&spinand_driver);
+}
+
+module_init(spinand_init);
+module_exit(spinand_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Henry Pan <hspan at micron.com>");
+MODULE_DESCRIPTION("SPI NAND driver code");
diff -urN linux-2.6.33.20-orig/drivers/mtd/spinand/spinand_mtd.c
linux-2.6.33.20/drivers/mtd/spinand/spinand_mtd.c
--- linux-2.6.33.20-orig/drivers/mtd/spinand/spinand_mtd.c
1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.33.20/drivers/mtd/spinand/spinand_mtd.c 2010-05-19
04:28:07.000000000 -0700
@@ -0,0 +1,728 @@
+/*
+spinand_mtd.c
+
+Copyright (c) 2009-2010 Micron Technology, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+*/
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spinand.h>
+#include <linux/mtd/nand_ecc.h>
+
+/**
+ * spinand_get_device - [GENERIC] Get chip for selected access
+ * @param mtd MTD device structure
+ * @param new_state the state which is requested
+ *
+ * Get the device and lock it for exclusive access
+ */
+#define mu_spi_nand_driver_version
"Beagle-MTD_01.00_Linux2.6.33_20100507"
+static int spinand_get_device(struct mtd_info *mtd, int new_state)
+{
+ struct spinand_chip *this = mtd->priv;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /*
+ * Grab the lock and see if the device is available
+ */
+ while (1) {
+ spin_lock(&this->chip_lock);
+ if (this->state == FL_READY) {
+ this->state = new_state;
+ spin_unlock(&this->chip_lock);
+ break;
+ }
+ if (new_state == FL_PM_SUSPENDED) {
+ spin_unlock(&this->chip_lock);
+ return (this->state == FL_PM_SUSPENDED) ? 0 :
-EAGAIN;
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&this->wq, &wait);
+ spin_unlock(&this->chip_lock);
+ schedule();
+ remove_wait_queue(&this->wq, &wait);
+ }
+ return 0;
+}
+
+/**
+ * spinand_release_device - [GENERIC] release chip
+ * @param mtd MTD device structure
+ *
+ * Deselect, release chip lock and wake up anyone waiting on the device
+ */
+static void spinand_release_device(struct mtd_info *mtd)
+{
+ struct spinand_chip *this = mtd->priv;
+
+ /* Release the chip */
+ spin_lock(&this->chip_lock);
+ this->state = FL_READY;
+ wake_up(&this->wq);
+ spin_unlock(&this->chip_lock);
+}
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+static void spinand_calculate_ecc(struct mtd_info *mtd)
+{
+ int i;
+ int eccsize = 512;
+ int eccbytes = 3;
+ int eccsteps = 4;
+ int ecctotal = 12;
+ struct spinand_chip *chip = mtd->priv;
+ struct spinand_info *info = chip->info;
+ unsigned char *p = chip->buf;
+
+ for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
+ __nand_calculate_ecc(p, eccsize, &chip->ecc_calc[i]);
+
+ for (i = 0; i < ecctotal; i++)
+ chip->buf[info->page_main_size +
info->ecclayout->eccpos[i]] = chip->ecc_calc[i];
+}
+
+static int spinand_correct_data(struct mtd_info *mtd)
+{
+ int i;
+ int eccsize = 512;
+ int eccbytes = 3;
+ int eccsteps = 4;
+ int ecctotal = 12;
+ struct spinand_chip *chip = mtd->priv;
+ struct spinand_info *info = chip->info;
+ unsigned char *p = chip->buf;
+ int errcode = 0;
+
+ for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
+ __nand_calculate_ecc(p, eccsize, &chip->ecc_calc[i]);
+
+ for (i = 0; i < ecctotal; i++)
+ chip->ecc_code[i] = chip->buf[info->page_main_size +
info->ecclayout->eccpos[i]];
+
+ for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
+ {
+ int stat;
+
+ stat = __nand_correct_data(p, &chip->ecc_code[i],
&chip->ecc_calc[i], eccsize);
+ if (stat < 0)
+ {
+ errcode = -1;
+ }
+ else if (stat == 1)
+ {
+ errcode = 1;
+ }
+ }
+
+ return errcode;
+}
+#endif
+
+static int spinand_read_ops(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ int page_id, page_offset, page_num, oob_num;
+
+ int count;
+ int main_ok, main_left, main_offset;
+ int oob_ok, oob_left;
+
+ signed int retval;
+ signed int errcode=0;
+
+ if (!chip->buf)
+ return -1;
+
+ page_id = from >> info->page_shift;
+
+ /* for main data */
+ page_offset = from & info->page_mask;
+ page_num = (page_offset + ops->len + info->page_main_size -1 ) /
info->page_main_size;
+
+ /* for oob */
+ oob_num = (ops->ooblen + info->ecclayout->oobavail -1) /
info->ecclayout->oobavail;
+
+ count = 0;
+
+ main_left = ops->len;
+ main_ok = 0;
+ main_offset = page_offset;
+
+ oob_left = ops->ooblen;
+ oob_ok = 0;
+
+ while (1)
+ {
+ if (count < page_num || count < oob_num)
+ {
+ memset(chip->buf, 0, info->page_size);
+ retval = chip->read_page(spi_nand, info, page_id
+ count, 0, info->page_size, chip->buf);
+ if (retval != 0)
+ {
+ errcode = -1;
+ printk(KERN_INFO "spinand_read_ops:
fail, page=%d!\n", page_id);
+
+ return errcode;
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ if (count < page_num && ops->datbuf)
+ {
+ int size;
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+ retval = spinand_correct_data(mtd);
+ if (retval == -1)
+ printk(KERN_INFO "SWECC uncorrectable
error! page=%x\n", page_id+count);
+ else if (retval == 1)
+ printk(KERN_INFO "SWECC 1 bit error,
corrected! page=%x\n", page_id+count);
+
+#endif
+
+ if ((main_offset + main_left) <
info->page_main_size)
+ {
+ size = main_left;
+ }
+ else
+ {
+ size = info->page_main_size -
main_offset;
+ }
+
+ memcpy (ops->datbuf + main_ok, chip->buf, size);
+
+ main_ok += size;
+ main_left -= size;
+ main_offset = 0;
+
+ ops->retlen = main_ok;
+ }
+
+ if (count < oob_num && ops->oobbuf && chip->oobbuf)
+ {
+ int size;
+ int offset, len, temp;
+
+ /* repack spare to oob */
+ memset(chip->oobbuf, 0,
info->ecclayout->oobavail);
+
+ temp = 0;
+ offset = info->ecclayout->oobfree[0].offset;
+ len = info->ecclayout->oobfree[0].length;
+ memcpy (chip->oobbuf + temp, chip->buf +
info->page_main_size + offset, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[1].offset;
+ len = info->ecclayout->oobfree[1].length;
+ memcpy (chip->oobbuf + temp, chip->buf +
info->page_main_size + offset, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[2].offset;
+ len = info->ecclayout->oobfree[2].length;
+ memcpy (chip->oobbuf + temp, chip->buf +
info->page_main_size + offset, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[3].offset;
+ len = info->ecclayout->oobfree[3].length;
+ memcpy (chip->oobbuf + temp, chip->buf +
info->page_main_size + offset, len);
+
+ /* copy oobbuf to ops oobbuf */
+ if (oob_left < info->ecclayout->oobavail)
+ {
+ size = oob_left;
+ }
+ else
+ {
+ size = info->ecclayout->oobavail;
+ }
+
+ memcpy (ops->oobbuf + oob_ok, chip->oobbuf,
size);
+
+ oob_ok += size;
+ oob_left -= size;
+
+ ops->oobretlen = oob_ok;
+ }
+
+ count++;
+ }
+ return errcode;
+}
+
+static int spinand_write_ops(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ int page_id, page_offset, page_num, oob_num;
+
+ int count;
+
+ int main_ok, main_left, main_offset;
+ int oob_ok, oob_left;
+
+ signed int retval;
+ signed int errcode=0;
+
+ if (!chip->buf)
+ return -1;
+
+ page_id = to >> info->page_shift;
+
+ /* for main data */
+ page_offset = to & info->page_mask;
+ page_num = (page_offset + ops->len + info->page_main_size -1 ) /
info->page_main_size;
+
+ /* for oob */
+ oob_num = (ops->ooblen + info->ecclayout->oobavail -1) /
info->ecclayout->oobavail;
+
+ count = 0;
+
+ main_left = ops->len;
+ main_ok = 0;
+ main_offset = page_offset;
+
+ oob_left = ops->ooblen;
+ oob_ok = 0;
+
+ while (1)
+ {
+ if (count < page_num || count < oob_num)
+ {
+ memset(chip->buf, 0xFF, info->page_size);
+ }
+ else
+ {
+ break;
+ }
+
+ if (count < page_num && ops->datbuf)
+ {
+ int size;
+
+ if ((main_offset + main_left) <
info->page_main_size)
+ {
+ size = main_left;
+ }
+ else
+ {
+ size = info->page_main_size -
main_offset;
+ }
+
+ memcpy (chip->buf, ops->datbuf + main_ok, size);
+
+ main_ok += size;
+ main_left -= size;
+ main_offset = 0;
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+ spinand_calculate_ecc(mtd);
+#endif
+ }
+
+ if (count < oob_num && ops->oobbuf && chip->oobbuf)
+ {
+ int size;
+ int offset, len, temp;
+
+ memset(chip->oobbuf, 0xFF,
info->ecclayout->oobavail);
+
+ if (oob_left < info->ecclayout->oobavail)
+ {
+ size = oob_left;
+ }
+ else
+ {
+ size = info->ecclayout->oobavail;
+ }
+
+ memcpy (chip->oobbuf, ops->oobbuf + oob_ok,
size);
+
+ oob_ok += size;
+ oob_left -= size;
+
+ /* repack oob to spare */
+ temp = 0;
+ offset = info->ecclayout->oobfree[0].offset;
+ len = info->ecclayout->oobfree[0].length;
+ memcpy (chip->buf + info->page_main_size +
offset, chip->oobbuf + temp, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[1].offset;
+ len = info->ecclayout->oobfree[1].length;
+ memcpy (chip->buf + info->page_main_size +
offset, chip->oobbuf + temp, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[2].offset;
+ len = info->ecclayout->oobfree[2].length;
+ memcpy (chip->buf + info->page_main_size +
offset, chip->oobbuf + temp, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[3].offset;
+ len = info->ecclayout->oobfree[3].length;
+ memcpy (chip->buf + info->page_main_size +
offset, chip->oobbuf + temp, len);
+
+ }
+
+ if (count < page_num || count < oob_num)
+ {
+
+ retval = chip->program_page(spi_nand, info,
page_id + count, 0, info->page_size, chip->buf);
+ if (retval != 0)
+ {
+ errcode = -1;
+ printk(KERN_INFO "spinand_write_ops:
fail, page=%d!\n", page_id);
+
+ return errcode;
+ }
+ }
+
+ if (count < page_num && ops->datbuf)
+ {
+ ops->retlen = main_ok;
+ }
+
+ if (count < oob_num && ops->oobbuf && chip->oobbuf)
+ {
+ ops->oobretlen = oob_ok;
+ }
+
+ count++;
+
+ }
+
+ return errcode;
+}
+
+static int spinand_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_oob_ops ops = {0};
+ int ret;
+
+ /* Do not allow reads past end of device */
+ if ((from + len) > mtd->size)
+ return -EINVAL;
+
+ if (!len)
+ return 0;
+
+ spinand_get_device(mtd, FL_READING);
+
+ ops.len = len;
+ ops.datbuf = buf;
+
+ ret = spinand_read_ops(mtd, from, &ops);
+
+ *retlen = ops.retlen;
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+static int spinand_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct mtd_oob_ops ops = {0};
+ int ret;
+
+ /* Do not allow reads past end of device */
+ if ((to + len) > mtd->size)
+ return -EINVAL;
+ if (!len)
+ return 0;
+
+ spinand_get_device(mtd, FL_WRITING);
+
+ ops.len = len;
+ ops.datbuf = (uint8_t *)buf;
+
+ ret = spinand_write_ops(mtd, to, &ops);
+
+ *retlen = ops.retlen;
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+static int spinand_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ int ret;
+
+ spinand_get_device(mtd, FL_READING);
+
+ ret = spinand_read_ops(mtd, from, ops);
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+static int spinand_write_oob(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ int ret;
+
+ spinand_get_device(mtd, FL_WRITING);
+
+ ret = spinand_write_ops(mtd, to, ops);
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+/**
+ * spinand_erase - [MTD Interface] erase block(s)
+ * @param mtd MTD device structure
+ * @param instr erase instruction
+ *
+ * Erase one ore more blocks
+ */
+static int spinand_erase(struct mtd_info *mtd, struct erase_info
*instr)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ u16 block_id, block_num, count;
+ signed int retval=0;
+ signed int errcode=0;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "spinand_erase: start = 0x%012llx, len =
%llu\n",
+ (unsigned long long)instr->addr, (unsigned long
long)instr->len);
+
+ /* check address align on block boundary */
+ if (instr->addr & (info->block_main_size - 1)) {
+ DEBUG(MTD_DEBUG_LEVEL0, "spinand_erase: Unaligned
address\n");
+ return -EINVAL;
+ }
+
+ if (instr->len & (info->block_main_size - 1)) {
+ DEBUG(MTD_DEBUG_LEVEL0, "spinand_erase: "
+ "Length not block aligned\n");
+ return -EINVAL;
+ }
+
+ /* Do not allow erase past end of device */
+ if ((instr->len + instr->addr) > info->usable_size) {
+ DEBUG(MTD_DEBUG_LEVEL0, "spinand_erase: "
+ "Erase past end of device\n");
+ return -EINVAL;
+ }
+
+ instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
+
+
+ /* Grab the lock and see if the device is available */
+ spinand_get_device(mtd, FL_ERASING);
+
+ block_id = instr->addr >> info->block_shift;
+ block_num = instr->len >> info->block_shift;
+ count = 0;
+
+ while (count < block_num )
+ {
+ retval = chip->erase_block(spi_nand, info,
block_id+count);
+
+ if (retval!=0)
+ {
+ retval = chip->erase_block(spi_nand, info,
block_id+count);
+ if (retval!=0)
+ {
+ printk(KERN_INFO "spinand_erase: fail,
block=%d!\n", block_id+count);
+ errcode = -1;
+ }
+ }
+ count++;
+ }
+
+ if (errcode == 0)
+ {
+ instr->state = MTD_ERASE_DONE;
+ }
+
+ /* Deselect and wake up anyone waiting on the device */
+ spinand_release_device(mtd);
+
+ /* Do call back function */
+ if(instr->callback)
+ instr->callback(instr);
+
+ return errcode;
+}
+
+/**
+ * spinand_sync - [MTD Interface] sync
+ * @param mtd MTD device structure
+ *
+ * Sync is actually a wait for chip ready function
+ */
+static void spinand_sync(struct mtd_info *mtd)
+{
+ DEBUG(MTD_DEBUG_LEVEL3, "spinand_sync: called\n");
+
+ /* Grab the lock and see if the device is available */
+ spinand_get_device(mtd, FL_SYNCING);
+
+ /* Release it and go back */
+ spinand_release_device(mtd);
+}
+
+static int spinand_block_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ u16 block_id;
+ u8 is_bad = 0x00;
+ u8 ret = 0;
+
+ spinand_get_device(mtd, FL_READING);
+
+ block_id = ofs >> info->block_shift;
+
+ chip->read_page(spi_nand, info,
block_id*info->page_num_per_block, info->page_main_size, 1, &is_bad);
+
+ if (is_bad != 0xFF)
+ {
+ ret = 1;
+ }
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+/**
+ * spinand_block_markbad - [MTD Interface] Mark bad block
+ * @param mtd MTD device structure
+ * @param ofs Bad block number
+ */
+static int spinand_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ u16 block_id;
+ u8 is_bad = 0x00;
+ u8 ret = 0;
+
+ spinand_get_device(mtd, FL_WRITING);
+
+ block_id = ofs >> info->block_shift;
+
+ chip->program_page(spi_nand, info,
block_id*info->page_num_per_block, info->page_main_size, 1, &is_bad);
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+
+/**
+ * spinand_suspend - [MTD Interface] Suspend the spinand flash
+ * @param mtd MTD device structure
+ */
+static int spinand_suspend(struct mtd_info *mtd)
+{
+ return spinand_get_device(mtd, FL_PM_SUSPENDED);
+}
+
+/**
+ * spinand_resume - [MTD Interface] Resume the spinand flash
+ * @param mtd MTD device structure
+ */
+static void spinand_resume(struct mtd_info *mtd)
+{
+ struct spinand_chip *this = mtd->priv;
+
+ if (this->state == FL_PM_SUSPENDED)
+ spinand_release_device(mtd);
+ else
+ printk(KERN_ERR "resume() called for the chip which is
not"
+ "in suspended state\n");
+}
+
+/**
+ * spinand_mtd - add MTD device with parameters
+ * @param mtd MTD device structure
+ *
+ * Add MTD device with parameters.
+ */
+int spinand_mtd(struct mtd_info *mtd)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spinand_info *info = chip->info;
+
+ chip->state = FL_READY;
+ init_waitqueue_head(&chip->wq);
+ spin_lock_init(&chip->chip_lock);
+
+ mtd->name = info->name;
+ mtd->size = info->usable_size;
+ mtd->erasesize = info->block_main_size;
+ mtd->writesize = info->page_main_size;
+ mtd->oobsize = info->ecclayout->oobavail;
+ mtd->owner = THIS_MODULE;
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+
+ mtd->ecclayout = info->ecclayout;
+
+ mtd->erase = spinand_erase;
+ mtd->point = NULL;
+ mtd->unpoint = NULL;
+ mtd->read = spinand_read;
+ mtd->write = spinand_write;
+ mtd->read_oob = spinand_read_oob;
+ mtd->write_oob = spinand_write_oob;
+ mtd->sync = spinand_sync;
+ mtd->lock = NULL;
+ mtd->unlock = NULL;
+ mtd->suspend = spinand_suspend;
+ mtd->resume = spinand_resume;
+ mtd->block_isbad = spinand_block_isbad;
+ mtd->block_markbad = spinand_block_markbad;
+
+ return add_mtd_device(mtd) == 1 ? -ENODEV : 0;
+}
+
+void spinand_mtd_release(struct mtd_info *mtd)
+{
+ del_mtd_device(mtd);
+}
+
+EXPORT_SYMBOL_GPL(spinand_mtd);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Henry Pan <hspan at micron.com>");
+MODULE_DESCRIPTION("SPI NAND driver code");
diff -urN linux-2.6.33.20-orig/include/linux/mtd/spinand.h
linux-2.6.33.20/include/linux/mtd/spinand.h
--- linux-2.6.33.20-orig/include/linux/mtd/spinand.h 1969-12-31
16:00:00.000000000 -0800
+++ linux-2.6.33.20/include/linux/mtd/spinand.h 2010-05-19
04:28:07.000000000 -0700
@@ -0,0 +1,160 @@
+/*
+ * linux/include/linux/mtd/spinand.h
+ *
+ Copyright (c) 2009-2010 Micron Technology, Inc.
+
+This software is licensed under the terms of the GNU General Public
+License version 2, as published by the Free Software Foundation, and
+may be copied, distributed, and modified under those terms.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+ * Henry Pan <hspan at micron.com>
+ *
+ * based on nand.h
+ */
+#ifndef __LINUX_MTD_SPI_NAND_H
+#define __LINUX_MTD_SPI_NAND_H
+
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/mtd/mtd.h>
+
+/* cmd */
+#define CMD_READ 0x13
+#define CMD_READ_RDM 0x03
+#define CMD_PROG_PAGE_CLRCACHE 0x02
+#define CMD_PROG_PAGE 0x84
+#define CMD_PROG_PAGE_EXC 0x10
+#define CMD_ERASE_BLK 0xd8
+#define CMD_WR_ENABLE 0x06
+#define CMD_WR_DISABLE 0x04
+#define CMD_READ_ID 0x9f
+#define CMD_RESET 0xff
+#define CMD_READ_REG 0x0f
+#define CMD_WRITE_REG 0x1f
+
+/* feature/ status reg */
+#define REG_BLOCK_LOCK 0xa0
+#define REG_OTP 0xb0
+#define REG_STATUS 0xc0/* timing */
+
+/* status */
+#define STATUS_OIP_MASK 0x01
+#define STATUS_READY 0 << 0
+#define STATUS_BUSY 1 << 0
+
+#define STATUS_E_FAIL_MASK 0x04
+#define STATUS_E_FAIL 1 << 2
+
+#define STATUS_P_FAIL_MASK 0x08
+#define STATUS_P_FAIL 1 << 3
+
+#define STATUS_ECC_MASK 0x30
+#define STATUS_ECC_1BIT_CORRECTED 1 << 4
+#define STATUS_ECC_ERROR 2 << 4
+#define STATUS_ECC_RESERVED 3 << 4
+
+
+/*ECC enable defines*/
+#define OTP_ECC_MASK 0x10
+#define OTP_ECC_OFF 0
+#define OTP_ECC_ON 1
+
+#define ECC_DISABLED
+#define ECC_IN_NAND
+#define ECC_SOFT
+
+/* block lock */
+#define BL_ALL_LOCKED 0x38
+#define BL_1_2_LOCKED 0x30
+#define BL_1_4_LOCKED 0x28
+#define BL_1_8_LOCKED 0x20
+#define BL_1_16_LOCKED 0x18
+#define BL_1_32_LOCKED 0x10
+#define BL_1_64_LOCKED 0x08
+#define BL_ALL_UNLOCKED 0
+
+/**********************************************************************
******/
+
+struct spinand_info {
+ u8 mid;
+ u8 did;
+ char *name;
+ u64 nand_size;
+ u64 usable_size;
+
+ u32 block_size;
+ u32 block_main_size;
+ /*u32 block_spare_size; */
+ u16 block_num_per_chip;
+
+ u16 page_size;
+ u16 page_main_size;
+ u16 page_spare_size;
+ u16 page_num_per_block;
+
+ u8 block_shift;
+ u32 block_mask;
+
+ u8 page_shift;
+ u16 page_mask;
+
+ struct nand_ecclayout *ecclayout;
+};
+
+typedef enum {
+ FL_READY,
+ FL_READING,
+ FL_WRITING,
+ FL_ERASING,
+ FL_SYNCING,
+ FL_LOCKING,
+ FL_RESETING,
+ FL_OTPING,
+ FL_PM_SUSPENDED,
+} spinand_state_t;
+
+struct spinand_chip { /* used for multi chip */
+ spinlock_t chip_lock;
+ wait_queue_head_t wq;
+ spinand_state_t state;
+ struct spi_device *spi_nand;
+ struct spinand_info *info;
+ /*struct mtd_info *mtd; */
+
+ int (*reset) (struct spi_device *spi_nand);
+ int (*read_id) (struct spi_device *spi_nand, u8* id);
+ int (*read_page) (struct spi_device *spi_nand, struct
spinand_info *info, u16 page_id, u16 offset, u16 len, u8* rbuf);
+ int (*program_page) (struct spi_device *spi_nand, struct
spinand_info *info, u16 page_id, u16 offset, u16 len, u8* wbuf);
+ int (*erase_block) (struct spi_device *spi_nand, struct
spinand_info *info, u16 block_id);
+
+ u8 *buf;
+ u8 *oobbuf; /* temp buffer */
+
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+ u8 ecc_calc[12];
+ u8 ecc_code[12];
+#endif
+};
+
+struct spinand_cmd {
+ u8 cmd;
+ unsigned n_addr;
+ u8 addr[3];
+ unsigned n_dummy;
+ unsigned n_tx;
+ u8 *tx_buf;
+ unsigned n_rx;
+ u8 *rx_buf;
+};
+
+extern int spinand_mtd(struct mtd_info *mtd);
+extern void spinand_mtd_release(struct mtd_info *mtd);
+
+
+#endif /* __LINUX_MTD_SPI_NAND_H */
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20100524/4672e2c1/attachment-0001.html>
More information about the linux-mtd
mailing list