All of lore.kernel.org
 help / color / mirror / Atom feed
From: Sourav Poddar <sourav.poddar@ti.com>
To: Sourav Poddar <sourav.poddar@ti.com>, <artem.bityutskiy@linux.intel.com>
Cc: rnayak@ti.com, linux-kernel@vger.kernel.org, balbi@ti.com,
	broonie@kernel.org, linux-mtd@lists.infradead.org,
	tqnguyen@micron.com, grant.likely@linaro.org,
	spi-devel-general@lists.sourceforge.net,
	linux-omap@vger.kernel.org, dwmw2@infradead.org,
	manonuevo@micron.com
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.
Date: Mon, 1 Jul 2013 10:47:58 +0530	[thread overview]
Message-ID: <51D11106.3040701@ti.com> (raw)
In-Reply-To: <1372232472-2641-2-git-send-email-sourav.poddar@ti.com>

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> From: Mona Anonuevo<manonuevo@micron.com>
>
> This patch adds support for a generic spinand framework(spinand_mtd.c).
> This frameowrk can be used for other spi based flash devices also. The idea
> is to have a common model under drivers/mtd, as also present for other no spi
> devices(there is a generic framework and device part simply attaches itself to it.)
>
> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
> The patch also contains a micron driver attaching itself to generic framework.
>
> Signed-off-by: Mona Anonuevo<manonuevo@micron.com>
> Signed-off-by: Tuan Nguyen<tqnguyen@micron.com>
> Signed-off-by: Sourav Poddar<sourav.poddar@ti.com>
> ----
> [I picked this as a standalone patch, can split it into generic and device part
> based on community feedback.]
>
>   drivers/mtd/Kconfig               |    2 +
>   drivers/mtd/Makefile              |    2 +
>   drivers/mtd/spinand/Kconfig       |   24 ++
>   drivers/mtd/spinand/Makefile      |   10 +
>   drivers/mtd/spinand/spinand_lld.c |  776 +++++++++++++++++++++++++++++++++++++
>   drivers/mtd/spinand/spinand_mtd.c |  690 +++++++++++++++++++++++++++++++++
>   include/linux/mtd/spinand.h       |  155 ++++++++
>   7 files changed, 1659 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/mtd/spinand/Kconfig
>   create mode 100644 drivers/mtd/spinand/Makefile
>   create mode 100644 drivers/mtd/spinand/spinand_lld.c
>   create mode 100644 drivers/mtd/spinand/spinand_mtd.c
>   create mode 100644 include/linux/mtd/spinand.h
>
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 5fab4e6..c9e6c60 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -318,6 +318,8 @@ source "drivers/mtd/nand/Kconfig"
>
>   source "drivers/mtd/onenand/Kconfig"
>
> +source "drivers/mtd/spinand/Kconfig"
> +
>   source "drivers/mtd/lpddr/Kconfig"
>
>   source "drivers/mtd/ubi/Kconfig"
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 4cfb31e..cce68db 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -32,4 +32,6 @@ inftl-objs		:= inftlcore.o inftlmount.o
>
>   obj-y		+= chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
>
> +obj-y		+= spinand/
> +
>   obj-$(CONFIG_MTD_UBI)		+= ubi/
> diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig
> new file mode 100644
> index 0000000..38c739f
> --- /dev/null
> +++ b/drivers/mtd/spinand/Kconfig
> @@ -0,0 +1,24 @@
> +#
> +# 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 --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile
> new file mode 100644
> index 0000000..355e726
> --- /dev/null
> +++ b/drivers/mtd/spinand/Makefile
> @@ -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 --git a/drivers/mtd/spinand/spinand_lld.c b/drivers/mtd/spinand/spinand_lld.c
> new file mode 100644
> index 0000000..9f53737
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_lld.c
> @@ -0,0 +1,776 @@
> +/*
> +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>
> +#include<linux/spi/flash.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
> +*/
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC
> +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;
> +	}
> +}
> +#else
> +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;
> +	}
> +}
> +#endif
> +
> +/**
> + * 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);
> +			}
> +			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 spinand_probe(struct spi_device *spi_nand)
> +{
> +	ssize_t retval;
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +	struct spinand_info *info;
> +	struct flash_platform_data      *data;
> +	struct mtd_part_parser_data     ppdata;
> +	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) {
> +		pr_info(KERN_INFO "SPINAND: read id error! 0x%02x, 0x%02x!\n",
> +			id[0], id[1]);
> +		return 0;
> +	}
> +
> +	data = spi_nand->dev.platform_data;
> +	info  = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	retval = spinand_get_info(spi_nand, info, (u8 *)&id);
> +	pr_info(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n",
> +		id[0], id[1], info->name);
> +	pr_info(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
> +
> +	ppdata.of_node = spi_nand->dev.of_node;
> +
> +	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 mtd_device_parse_register(mtd, NULL,&ppdata,
> +			data ? data->parts : NULL,
> +			data ? data->nr_parts : 0);
> +}
> +
> +/**
> + * __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 spinand_remove(struct spi_device *spi)
> +{
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +
> +	pr_debug("%s: remove\n", dev_name(&spi->dev));
> +
> +	mtd = dev_get_drvdata(&spi->dev);
> +
> +	mtd_device_unregister(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		= 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@micron.com>");
> +MODULE_DESCRIPTION("SPI NAND driver code");
> diff --git a/drivers/mtd/spinand/spinand_mtd.c b/drivers/mtd/spinand/spinand_mtd.c
> new file mode 100644
> index 0000000..8bfff86
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_mtd.c
> @@ -0,0 +1,690 @@
> +/*
> +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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_info(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)
> +				pr_info(KERN_INFO
> +					"SWECC uncorrectable error! page=%x\n",
> +					page_id+count);
> +			else if (retval == 1)
> +				pr_info(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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_err(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;
> +
> +	pr_info("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)) {
> +		pr_err("spinand_erase: Unaligned address\n");
> +		return -EINVAL;
> +	}
> +
> +	if (instr->len&  (info->block_main_size - 1)) {
> +		pr_err("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) {
> +		pr_err("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) {
> +				pr_info(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)
> +{
> +	pr_debug("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
> +		pr_err(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->page_spare_size;
> +	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 0;
> +}
> +EXPORT_SYMBOL_GPL(spinand_mtd);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Henry Pan<hspan@micron.com>");
> diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
> new file mode 100644
> index 0000000..3b8802a
> --- /dev/null
> +++ b/include/linux/mtd/spinand.h
> @@ -0,0 +1,155 @@
> +/*
> + *  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.
> +/bin/bash: 4: command not found
> + *
> + *  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

WARNING: multiple messages have this Message-ID (diff)
From: Sourav Poddar <sourav.poddar@ti.com>
To: Sourav Poddar <sourav.poddar@ti.com>, artem.bityutskiy@linux.intel.com
Cc: rnayak@ti.com, linux-kernel@vger.kernel.org, balbi@ti.com,
	broonie@kernel.org, linux-mtd@lists.infradead.org,
	tqnguyen@micron.com, grant.likely@linaro.org,
	spi-devel-general@lists.sourceforge.net,
	linux-omap@vger.kernel.org, dwmw2@infradead.org,
	manonuevo@micron.com
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.
Date: Mon, 1 Jul 2013 10:47:58 +0530	[thread overview]
Message-ID: <51D11106.3040701@ti.com> (raw)
In-Reply-To: <1372232472-2641-2-git-send-email-sourav.poddar@ti.com>

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> From: Mona Anonuevo<manonuevo@micron.com>
>
> This patch adds support for a generic spinand framework(spinand_mtd.c).
> This frameowrk can be used for other spi based flash devices also. The idea
> is to have a common model under drivers/mtd, as also present for other no spi
> devices(there is a generic framework and device part simply attaches itself to it.)
>
> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
> The patch also contains a micron driver attaching itself to generic framework.
>
> Signed-off-by: Mona Anonuevo<manonuevo@micron.com>
> Signed-off-by: Tuan Nguyen<tqnguyen@micron.com>
> Signed-off-by: Sourav Poddar<sourav.poddar@ti.com>
> ----
> [I picked this as a standalone patch, can split it into generic and device part
> based on community feedback.]
>
>   drivers/mtd/Kconfig               |    2 +
>   drivers/mtd/Makefile              |    2 +
>   drivers/mtd/spinand/Kconfig       |   24 ++
>   drivers/mtd/spinand/Makefile      |   10 +
>   drivers/mtd/spinand/spinand_lld.c |  776 +++++++++++++++++++++++++++++++++++++
>   drivers/mtd/spinand/spinand_mtd.c |  690 +++++++++++++++++++++++++++++++++
>   include/linux/mtd/spinand.h       |  155 ++++++++
>   7 files changed, 1659 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/mtd/spinand/Kconfig
>   create mode 100644 drivers/mtd/spinand/Makefile
>   create mode 100644 drivers/mtd/spinand/spinand_lld.c
>   create mode 100644 drivers/mtd/spinand/spinand_mtd.c
>   create mode 100644 include/linux/mtd/spinand.h
>
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 5fab4e6..c9e6c60 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -318,6 +318,8 @@ source "drivers/mtd/nand/Kconfig"
>
>   source "drivers/mtd/onenand/Kconfig"
>
> +source "drivers/mtd/spinand/Kconfig"
> +
>   source "drivers/mtd/lpddr/Kconfig"
>
>   source "drivers/mtd/ubi/Kconfig"
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 4cfb31e..cce68db 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -32,4 +32,6 @@ inftl-objs		:= inftlcore.o inftlmount.o
>
>   obj-y		+= chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
>
> +obj-y		+= spinand/
> +
>   obj-$(CONFIG_MTD_UBI)		+= ubi/
> diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig
> new file mode 100644
> index 0000000..38c739f
> --- /dev/null
> +++ b/drivers/mtd/spinand/Kconfig
> @@ -0,0 +1,24 @@
> +#
> +# 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 --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile
> new file mode 100644
> index 0000000..355e726
> --- /dev/null
> +++ b/drivers/mtd/spinand/Makefile
> @@ -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 --git a/drivers/mtd/spinand/spinand_lld.c b/drivers/mtd/spinand/spinand_lld.c
> new file mode 100644
> index 0000000..9f53737
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_lld.c
> @@ -0,0 +1,776 @@
> +/*
> +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>
> +#include<linux/spi/flash.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
> +*/
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC
> +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;
> +	}
> +}
> +#else
> +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;
> +	}
> +}
> +#endif
> +
> +/**
> + * 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);
> +			}
> +			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 spinand_probe(struct spi_device *spi_nand)
> +{
> +	ssize_t retval;
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +	struct spinand_info *info;
> +	struct flash_platform_data      *data;
> +	struct mtd_part_parser_data     ppdata;
> +	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) {
> +		pr_info(KERN_INFO "SPINAND: read id error! 0x%02x, 0x%02x!\n",
> +			id[0], id[1]);
> +		return 0;
> +	}
> +
> +	data = spi_nand->dev.platform_data;
> +	info  = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	retval = spinand_get_info(spi_nand, info, (u8 *)&id);
> +	pr_info(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n",
> +		id[0], id[1], info->name);
> +	pr_info(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
> +
> +	ppdata.of_node = spi_nand->dev.of_node;
> +
> +	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 mtd_device_parse_register(mtd, NULL,&ppdata,
> +			data ? data->parts : NULL,
> +			data ? data->nr_parts : 0);
> +}
> +
> +/**
> + * __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 spinand_remove(struct spi_device *spi)
> +{
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +
> +	pr_debug("%s: remove\n", dev_name(&spi->dev));
> +
> +	mtd = dev_get_drvdata(&spi->dev);
> +
> +	mtd_device_unregister(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		= 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@micron.com>");
> +MODULE_DESCRIPTION("SPI NAND driver code");
> diff --git a/drivers/mtd/spinand/spinand_mtd.c b/drivers/mtd/spinand/spinand_mtd.c
> new file mode 100644
> index 0000000..8bfff86
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_mtd.c
> @@ -0,0 +1,690 @@
> +/*
> +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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_info(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)
> +				pr_info(KERN_INFO
> +					"SWECC uncorrectable error! page=%x\n",
> +					page_id+count);
> +			else if (retval == 1)
> +				pr_info(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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_err(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;
> +
> +	pr_info("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)) {
> +		pr_err("spinand_erase: Unaligned address\n");
> +		return -EINVAL;
> +	}
> +
> +	if (instr->len&  (info->block_main_size - 1)) {
> +		pr_err("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) {
> +		pr_err("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) {
> +				pr_info(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)
> +{
> +	pr_debug("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
> +		pr_err(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->page_spare_size;
> +	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 0;
> +}
> +EXPORT_SYMBOL_GPL(spinand_mtd);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Henry Pan<hspan@micron.com>");
> diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
> new file mode 100644
> index 0000000..3b8802a
> --- /dev/null
> +++ b/include/linux/mtd/spinand.h
> @@ -0,0 +1,155 @@
> +/*
> + *  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.
> +/bin/bash: 4: command not found
> + *
> + *  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 discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

WARNING: multiple messages have this Message-ID (diff)
From: Sourav Poddar <sourav.poddar@ti.com>
To: Sourav Poddar <sourav.poddar@ti.com>, <artem.bityutskiy@linux.intel.com>
Cc: rnayak@ti.com, linux-kernel@vger.kernel.org, balbi@ti.com,
	broonie@kernel.org, linux-mtd@lists.infradead.org,
	tqnguyen@micron.com, grant.likely@linaro.org,
	spi-devel-general@lists.sourceforge.net,
	linux-omap@vger.kernel.org, dwmw2@infradead.org,
	manonuevo@micron.com
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.
Date: Mon, 1 Jul 2013 10:47:58 +0530	[thread overview]
Message-ID: <51D11106.3040701@ti.com> (raw)
In-Reply-To: <1372232472-2641-2-git-send-email-sourav.poddar@ti.com>

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> From: Mona Anonuevo<manonuevo@micron.com>
>
> This patch adds support for a generic spinand framework(spinand_mtd.c).
> This frameowrk can be used for other spi based flash devices also. The idea
> is to have a common model under drivers/mtd, as also present for other no spi
> devices(there is a generic framework and device part simply attaches itself to it.)
>
> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
> The patch also contains a micron driver attaching itself to generic framework.
>
> Signed-off-by: Mona Anonuevo<manonuevo@micron.com>
> Signed-off-by: Tuan Nguyen<tqnguyen@micron.com>
> Signed-off-by: Sourav Poddar<sourav.poddar@ti.com>
> ----
> [I picked this as a standalone patch, can split it into generic and device part
> based on community feedback.]
>
>   drivers/mtd/Kconfig               |    2 +
>   drivers/mtd/Makefile              |    2 +
>   drivers/mtd/spinand/Kconfig       |   24 ++
>   drivers/mtd/spinand/Makefile      |   10 +
>   drivers/mtd/spinand/spinand_lld.c |  776 +++++++++++++++++++++++++++++++++++++
>   drivers/mtd/spinand/spinand_mtd.c |  690 +++++++++++++++++++++++++++++++++
>   include/linux/mtd/spinand.h       |  155 ++++++++
>   7 files changed, 1659 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/mtd/spinand/Kconfig
>   create mode 100644 drivers/mtd/spinand/Makefile
>   create mode 100644 drivers/mtd/spinand/spinand_lld.c
>   create mode 100644 drivers/mtd/spinand/spinand_mtd.c
>   create mode 100644 include/linux/mtd/spinand.h
>
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 5fab4e6..c9e6c60 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -318,6 +318,8 @@ source "drivers/mtd/nand/Kconfig"
>
>   source "drivers/mtd/onenand/Kconfig"
>
> +source "drivers/mtd/spinand/Kconfig"
> +
>   source "drivers/mtd/lpddr/Kconfig"
>
>   source "drivers/mtd/ubi/Kconfig"
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 4cfb31e..cce68db 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -32,4 +32,6 @@ inftl-objs		:= inftlcore.o inftlmount.o
>
>   obj-y		+= chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
>
> +obj-y		+= spinand/
> +
>   obj-$(CONFIG_MTD_UBI)		+= ubi/
> diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig
> new file mode 100644
> index 0000000..38c739f
> --- /dev/null
> +++ b/drivers/mtd/spinand/Kconfig
> @@ -0,0 +1,24 @@
> +#
> +# 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 --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile
> new file mode 100644
> index 0000000..355e726
> --- /dev/null
> +++ b/drivers/mtd/spinand/Makefile
> @@ -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 --git a/drivers/mtd/spinand/spinand_lld.c b/drivers/mtd/spinand/spinand_lld.c
> new file mode 100644
> index 0000000..9f53737
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_lld.c
> @@ -0,0 +1,776 @@
> +/*
> +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>
> +#include<linux/spi/flash.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
> +*/
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC
> +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;
> +	}
> +}
> +#else
> +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;
> +	}
> +}
> +#endif
> +
> +/**
> + * 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);
> +			}
> +			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 spinand_probe(struct spi_device *spi_nand)
> +{
> +	ssize_t retval;
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +	struct spinand_info *info;
> +	struct flash_platform_data      *data;
> +	struct mtd_part_parser_data     ppdata;
> +	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) {
> +		pr_info(KERN_INFO "SPINAND: read id error! 0x%02x, 0x%02x!\n",
> +			id[0], id[1]);
> +		return 0;
> +	}
> +
> +	data = spi_nand->dev.platform_data;
> +	info  = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	retval = spinand_get_info(spi_nand, info, (u8 *)&id);
> +	pr_info(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n",
> +		id[0], id[1], info->name);
> +	pr_info(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
> +
> +	ppdata.of_node = spi_nand->dev.of_node;
> +
> +	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 mtd_device_parse_register(mtd, NULL,&ppdata,
> +			data ? data->parts : NULL,
> +			data ? data->nr_parts : 0);
> +}
> +
> +/**
> + * __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 spinand_remove(struct spi_device *spi)
> +{
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +
> +	pr_debug("%s: remove\n", dev_name(&spi->dev));
> +
> +	mtd = dev_get_drvdata(&spi->dev);
> +
> +	mtd_device_unregister(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		= 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@micron.com>");
> +MODULE_DESCRIPTION("SPI NAND driver code");
> diff --git a/drivers/mtd/spinand/spinand_mtd.c b/drivers/mtd/spinand/spinand_mtd.c
> new file mode 100644
> index 0000000..8bfff86
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_mtd.c
> @@ -0,0 +1,690 @@
> +/*
> +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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_info(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)
> +				pr_info(KERN_INFO
> +					"SWECC uncorrectable error! page=%x\n",
> +					page_id+count);
> +			else if (retval == 1)
> +				pr_info(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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_err(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;
> +
> +	pr_info("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)) {
> +		pr_err("spinand_erase: Unaligned address\n");
> +		return -EINVAL;
> +	}
> +
> +	if (instr->len&  (info->block_main_size - 1)) {
> +		pr_err("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) {
> +		pr_err("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) {
> +				pr_info(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)
> +{
> +	pr_debug("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
> +		pr_err(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->page_spare_size;
> +	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 0;
> +}
> +EXPORT_SYMBOL_GPL(spinand_mtd);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Henry Pan<hspan@micron.com>");
> diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
> new file mode 100644
> index 0000000..3b8802a
> --- /dev/null
> +++ b/include/linux/mtd/spinand.h
> @@ -0,0 +1,155 @@
> +/*
> + *  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.
> +/bin/bash: 4: command not found
> + *
> + *  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 discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

WARNING: multiple messages have this Message-ID (diff)
From: Sourav Poddar <sourav.poddar@ti.com>
To: Sourav Poddar <sourav.poddar@ti.com>, <artem.bityutskiy@linux.intel.com>
Cc: <linux-mtd@lists.infradead.org>,
	<spi-devel-general@lists.sourceforge.net>, <broonie@kernel.org>,
	<dwmw2@infradead.org>, <manonuevo@micron.com>,
	<tqnguyen@micron.com>, <grant.likely@linaro.org>, <balbi@ti.com>,
	<rnayak@ti.com>, <linux-kernel@vger.kernel.org>,
	<linux-omap@vger.kernel.org>
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.
Date: Mon, 1 Jul 2013 10:47:58 +0530	[thread overview]
Message-ID: <51D11106.3040701@ti.com> (raw)
In-Reply-To: <1372232472-2641-2-git-send-email-sourav.poddar@ti.com>

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> From: Mona Anonuevo<manonuevo@micron.com>
>
> This patch adds support for a generic spinand framework(spinand_mtd.c).
> This frameowrk can be used for other spi based flash devices also. The idea
> is to have a common model under drivers/mtd, as also present for other no spi
> devices(there is a generic framework and device part simply attaches itself to it.)
>
> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
> The patch also contains a micron driver attaching itself to generic framework.
>
> Signed-off-by: Mona Anonuevo<manonuevo@micron.com>
> Signed-off-by: Tuan Nguyen<tqnguyen@micron.com>
> Signed-off-by: Sourav Poddar<sourav.poddar@ti.com>
> ----
> [I picked this as a standalone patch, can split it into generic and device part
> based on community feedback.]
>
>   drivers/mtd/Kconfig               |    2 +
>   drivers/mtd/Makefile              |    2 +
>   drivers/mtd/spinand/Kconfig       |   24 ++
>   drivers/mtd/spinand/Makefile      |   10 +
>   drivers/mtd/spinand/spinand_lld.c |  776 +++++++++++++++++++++++++++++++++++++
>   drivers/mtd/spinand/spinand_mtd.c |  690 +++++++++++++++++++++++++++++++++
>   include/linux/mtd/spinand.h       |  155 ++++++++
>   7 files changed, 1659 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/mtd/spinand/Kconfig
>   create mode 100644 drivers/mtd/spinand/Makefile
>   create mode 100644 drivers/mtd/spinand/spinand_lld.c
>   create mode 100644 drivers/mtd/spinand/spinand_mtd.c
>   create mode 100644 include/linux/mtd/spinand.h
>
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 5fab4e6..c9e6c60 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -318,6 +318,8 @@ source "drivers/mtd/nand/Kconfig"
>
>   source "drivers/mtd/onenand/Kconfig"
>
> +source "drivers/mtd/spinand/Kconfig"
> +
>   source "drivers/mtd/lpddr/Kconfig"
>
>   source "drivers/mtd/ubi/Kconfig"
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 4cfb31e..cce68db 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -32,4 +32,6 @@ inftl-objs		:= inftlcore.o inftlmount.o
>
>   obj-y		+= chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
>
> +obj-y		+= spinand/
> +
>   obj-$(CONFIG_MTD_UBI)		+= ubi/
> diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig
> new file mode 100644
> index 0000000..38c739f
> --- /dev/null
> +++ b/drivers/mtd/spinand/Kconfig
> @@ -0,0 +1,24 @@
> +#
> +# 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 --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile
> new file mode 100644
> index 0000000..355e726
> --- /dev/null
> +++ b/drivers/mtd/spinand/Makefile
> @@ -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 --git a/drivers/mtd/spinand/spinand_lld.c b/drivers/mtd/spinand/spinand_lld.c
> new file mode 100644
> index 0000000..9f53737
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_lld.c
> @@ -0,0 +1,776 @@
> +/*
> +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>
> +#include<linux/spi/flash.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
> +*/
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC
> +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;
> +	}
> +}
> +#else
> +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;
> +	}
> +}
> +#endif
> +
> +/**
> + * 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);
> +			}
> +			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 spinand_probe(struct spi_device *spi_nand)
> +{
> +	ssize_t retval;
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +	struct spinand_info *info;
> +	struct flash_platform_data      *data;
> +	struct mtd_part_parser_data     ppdata;
> +	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) {
> +		pr_info(KERN_INFO "SPINAND: read id error! 0x%02x, 0x%02x!\n",
> +			id[0], id[1]);
> +		return 0;
> +	}
> +
> +	data = spi_nand->dev.platform_data;
> +	info  = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	retval = spinand_get_info(spi_nand, info, (u8 *)&id);
> +	pr_info(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n",
> +		id[0], id[1], info->name);
> +	pr_info(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
> +
> +	ppdata.of_node = spi_nand->dev.of_node;
> +
> +	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 mtd_device_parse_register(mtd, NULL,&ppdata,
> +			data ? data->parts : NULL,
> +			data ? data->nr_parts : 0);
> +}
> +
> +/**
> + * __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 spinand_remove(struct spi_device *spi)
> +{
> +	struct mtd_info *mtd;
> +	struct spinand_chip *chip;
> +
> +	pr_debug("%s: remove\n", dev_name(&spi->dev));
> +
> +	mtd = dev_get_drvdata(&spi->dev);
> +
> +	mtd_device_unregister(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		= 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@micron.com>");
> +MODULE_DESCRIPTION("SPI NAND driver code");
> diff --git a/drivers/mtd/spinand/spinand_mtd.c b/drivers/mtd/spinand/spinand_mtd.c
> new file mode 100644
> index 0000000..8bfff86
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_mtd.c
> @@ -0,0 +1,690 @@
> +/*
> +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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_info(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)
> +				pr_info(KERN_INFO
> +					"SWECC uncorrectable error! page=%x\n",
> +					page_id+count);
> +			else if (retval == 1)
> +				pr_info(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 */
> +	if (info->ecclayout->oobavail)
> +		oob_num = (ops->ooblen +
> +			info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> +	else
> +		oob_num = 0;
> +
> +	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;
> +				pr_err(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;
> +
> +	pr_info("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)) {
> +		pr_err("spinand_erase: Unaligned address\n");
> +		return -EINVAL;
> +	}
> +
> +	if (instr->len&  (info->block_main_size - 1)) {
> +		pr_err("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) {
> +		pr_err("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) {
> +				pr_info(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)
> +{
> +	pr_debug("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
> +		pr_err(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->page_spare_size;
> +	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 0;
> +}
> +EXPORT_SYMBOL_GPL(spinand_mtd);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Henry Pan<hspan@micron.com>");
> diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
> new file mode 100644
> index 0000000..3b8802a
> --- /dev/null
> +++ b/include/linux/mtd/spinand.h
> @@ -0,0 +1,155 @@
> +/*
> + *  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.
> +/bin/bash: 4: command not found
> + *
> + *  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


  parent reply	other threads:[~2013-07-01  5:18 UTC|newest]

Thread overview: 46+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-06-26  7:41 [PATCH 0/3] spi/mtd generic framework, ti qspi controller and spansion driver Sourav Poddar
2013-06-26  7:41 ` [PATCH 0/3] spi/mtd generic framework,ti " Sourav Poddar
2013-06-26  7:41 ` [PATCH 0/3] spi/mtd generic framework, ti " Sourav Poddar
2013-06-26  7:41 ` Sourav Poddar
2013-06-26  7:41 ` [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-06-26 14:15   ` Florian Fainelli
2013-06-26 14:15     ` Florian Fainelli
2013-06-26 15:22   ` Kamlakant Patel
2013-06-26 15:22     ` Kamlakant Patel
2013-06-26 15:22     ` Kamlakant Patel
2013-06-27  4:51     ` Sourav Poddar
2013-06-27  4:51       ` Sourav Poddar
2013-06-27  4:51       ` Sourav Poddar
2013-07-01  5:17   ` Sourav Poddar [this message]
2013-07-01  5:17     ` Sourav Poddar
2013-07-01  5:17     ` Sourav Poddar
2013-07-01  5:17     ` Sourav Poddar
2013-06-26  7:41 ` [PATCH 2/3] drivers: spi: Add qspi flash controller Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-07-01  5:19   ` Sourav Poddar
2013-07-01  5:19     ` Sourav Poddar
2013-07-01  5:19     ` Sourav Poddar
2013-07-01  5:19     ` Sourav Poddar
2013-07-01 10:56   ` Mark Brown
2013-07-01 10:56     ` Mark Brown
2013-07-01 10:56     ` Mark Brown
2013-07-01 11:15     ` Sourav Poddar
2013-07-01 11:15       ` Sourav Poddar
2013-07-01 11:15       ` Sourav Poddar
2013-06-26  7:41 ` [PATCH 3/3] drivers: mtd: spinand: Add qspi spansion " Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-06-26  7:41   ` Sourav Poddar
2013-07-01  5:18   ` Sourav Poddar
2013-07-01  5:18     ` Sourav Poddar
2013-07-01  5:18     ` Sourav Poddar
2013-07-01  5:18     ` Sourav Poddar
2013-07-01  5:17 ` [PATCH 0/3] spi/mtd generic framework,ti qspi controller and spansion driver Sourav Poddar
2013-07-01  5:17   ` Sourav Poddar
2013-07-01  5:17   ` Sourav Poddar
2013-07-01  5:17   ` Sourav Poddar

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=51D11106.3040701@ti.com \
    --to=sourav.poddar@ti.com \
    --cc=artem.bityutskiy@linux.intel.com \
    --cc=balbi@ti.com \
    --cc=broonie@kernel.org \
    --cc=dwmw2@infradead.org \
    --cc=grant.likely@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=linux-omap@vger.kernel.org \
    --cc=manonuevo@micron.com \
    --cc=rnayak@ti.com \
    --cc=spi-devel-general@lists.sourceforge.net \
    --cc=tqnguyen@micron.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.