mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 05:54:26 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			1018 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1018 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Micron SPI-ER NAND Flash Memory
 | |
|  *
 | |
|  * (C) Copyright 2009, Ubicom, Inc.
 | |
|  *
 | |
|  * This file is part of the Ubicom32 Linux Kernel Port.
 | |
|  *
 | |
|  * The Ubicom32 Linux Kernel Port 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.
 | |
|  *
 | |
|  * The Ubicom32 Linux Kernel Port 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.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with the Ubicom32 Linux Kernel Port.  If not,
 | |
|  * see <http://www.gnu.org/licenses/>.
 | |
| */
 | |
| #include <linux/module.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/mutex.h>
 | |
| #include <linux/err.h>
 | |
| 
 | |
| #include <linux/spi/spi.h>
 | |
| #include <linux/spi/flash.h>
 | |
| 
 | |
| #include <linux/mtd/mtd.h>
 | |
| #include <linux/mtd/partitions.h>
 | |
| 
 | |
| #define NAND_SPI_ER_BLOCK_FROM_ROW(row)		(row >> 6)
 | |
| 
 | |
| #define NAND_SPI_ER_STATUS_P_FAIL		(1 << 3)
 | |
| #define NAND_SPI_ER_STATUS_E_FAIL		(1 << 2)
 | |
| #define NAND_SPI_ER_STATUS_OIP			(1 << 0)
 | |
| 
 | |
| #define NAND_SPI_ER_LAST_ROW_INVALID		0xFFFFFFFF
 | |
| #define	NAND_SPI_ER_BAD_BLOCK_MARK_OFFSET	0x08
 | |
| 
 | |
| struct nand_spi_er_device {
 | |
| 	const char		*name;
 | |
| 
 | |
| 	uint8_t			id0;
 | |
| 	uint8_t			id1;
 | |
| 
 | |
| 	unsigned int		blocks;
 | |
| 	unsigned int		pages_per_block;
 | |
| 	unsigned int		page_size;
 | |
| 	unsigned int		write_size;
 | |
| 	unsigned int		erase_size;
 | |
| };
 | |
| 
 | |
| struct nand_spi_er {
 | |
| 	char				name[24];
 | |
| 
 | |
| 	const struct nand_spi_er_device	*device;
 | |
| 
 | |
| 	struct mutex			lock;
 | |
| 	struct spi_device		*spi;
 | |
| 
 | |
| 	struct mtd_info			mtd;
 | |
| 
 | |
| 	unsigned int			last_row;	/* the last row we fetched */
 | |
| 
 | |
| 	/*
 | |
| 	 * Bad block table (MUST be last in strcuture)
 | |
| 	 */
 | |
| 	unsigned long			nbb;
 | |
| 	unsigned long			bbt[0];
 | |
| };
 | |
| 
 | |
| const struct nand_spi_er_device nand_spi_er_devices[] = {
 | |
| 	{
 | |
| 		name:			"MT29F1G01ZDC",
 | |
| 		id0:			0x2C,
 | |
| 		id1:			0x12,
 | |
| 		blocks:			1024,
 | |
| 		pages_per_block:	64,
 | |
| 		page_size:		2048,
 | |
| 		write_size:		512,
 | |
| 		erase_size:		64 * 2048,
 | |
| 	},
 | |
| 	{
 | |
| 		name:			"MT29F1G01ZDC",
 | |
| 		id0:			0x2C,
 | |
| 		id1:			0x13,
 | |
| 		blocks:			1024,
 | |
| 		pages_per_block:	64,
 | |
| 		page_size:		2048,
 | |
| 		write_size:		512,
 | |
| 		erase_size:		64 * 2048,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static int read_only = 0;
 | |
| module_param(read_only, int, 0);
 | |
| MODULE_PARM_DESC(read_only, "Leave device locked");
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_get_feature
 | |
|  *	Get Feature register
 | |
|  */
 | |
| static int nand_spi_er_get_feature(struct nand_spi_er *chip, int reg, uint8_t *data)
 | |
| {
 | |
| 	uint8_t txbuf[2];
 | |
| 	uint8_t rxbuf[1];
 | |
| 	int res;
 | |
| 
 | |
| 	txbuf[0] = 0x0F;
 | |
| 	txbuf[1] = reg;
 | |
| 	res = spi_write_then_read(chip->spi, txbuf, 2, rxbuf, 1);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed get feature res=%d\n", chip->name, res);
 | |
| 		return res;
 | |
| 	}
 | |
| 	*data = rxbuf[0];
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_busywait
 | |
|  *	Wait until the chip is not busy
 | |
|  */
 | |
| static int nand_spi_er_busywait(struct nand_spi_er *chip, uint8_t *data)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < 100; i++) {
 | |
| 		int res = nand_spi_er_get_feature(chip, 0xC0, data);
 | |
| 		if (res) {
 | |
| 			return res;
 | |
| 		}
 | |
| 		if (!(*data & NAND_SPI_ER_STATUS_OIP)) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_erase
 | |
|  *	Erase a block, parameters must be block aligned
 | |
|  */
 | |
| static int nand_spi_er_erase(struct mtd_info *mtd, struct erase_info *instr)
 | |
| {
 | |
| 	struct nand_spi_er *chip = mtd->priv;
 | |
| 	struct spi_device *spi = chip->spi;
 | |
| 	uint8_t txbuf[4];
 | |
| 	int res;
 | |
| 
 | |
| 	DEBUG(MTD_DEBUG_LEVEL3, "%s: erase addr:%x len:%x\n", chip->name, instr->addr, instr->len);
 | |
| 
 | |
| 	if ((instr->addr + instr->len) > mtd->size) {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (instr->addr & (chip->device->erase_size - 1)) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: erase address is not aligned %x\n", chip->name, instr->addr);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (instr->len & (chip->device->erase_size - 1)) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: erase len is not aligned %x\n", chip->name, instr->len);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&chip->lock);
 | |
| 	chip->last_row = NAND_SPI_ER_LAST_ROW_INVALID;
 | |
| 
 | |
| 	while (instr->len) {
 | |
| 		uint32_t block = instr->addr >> 17;
 | |
| 		uint32_t row = block << 6;
 | |
| 		uint8_t stat;
 | |
| 		DEBUG(MTD_DEBUG_LEVEL3, "%s: block erase row:%x block:%x addr:%x rem:%x\n", chip->name, row, block, instr->addr, instr->len);
 | |
| 
 | |
| 		/*
 | |
| 		 * Write enable
 | |
| 		 */
 | |
| 		txbuf[0] = 0x06;
 | |
| 		res = spi_write(spi, txbuf, 1);
 | |
| 		if (res) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: failed write enable res=%d\n", chip->name, res);
 | |
| 			mutex_unlock(&chip->lock);
 | |
| 			return res;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Test for bad block
 | |
| 		 */
 | |
| 		if (test_bit(block, chip->bbt)) {
 | |
| 			instr->fail_addr = block << 17;
 | |
| 			instr->state = MTD_ERASE_FAILED;
 | |
| 			res = -EBADMSG;
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Block erase
 | |
| 		 */
 | |
| 		txbuf[0] = 0xD8;
 | |
| 		txbuf[1] = 0x00;
 | |
| 		txbuf[2] = row >> 8;
 | |
| 		txbuf[3] = row & 0xFF;
 | |
| 		res = spi_write(spi, txbuf, 4);
 | |
| 		if (res) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: failed block erase res=%d\n", chip->name, res);
 | |
| 			instr->fail_addr = block << 17;
 | |
| 			instr->state = MTD_ERASE_FAILED;
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Wait
 | |
| 		 */
 | |
| 		res = nand_spi_er_busywait(chip, &stat);
 | |
| 		if (res || (stat & NAND_SPI_ER_STATUS_OIP)) {
 | |
| 			instr->fail_addr = block << 17;
 | |
| 			instr->state = MTD_ERASE_FAILED;
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: chip is busy or nonresponsive res=%d stat=%02x\n", chip->name, res, stat);
 | |
| 			if (res) {
 | |
| 				goto done;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * Chip is stuck?
 | |
| 			 */
 | |
| 			res = -EIO;
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Check the status register
 | |
| 		 */
 | |
| 		if (stat & NAND_SPI_ER_STATUS_E_FAIL) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: E_FAIL signalled (%02x)\n", chip->name, stat);
 | |
| 			instr->fail_addr = block << 17;
 | |
| 			instr->state = MTD_ERASE_FAILED;
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Next
 | |
| 		 */
 | |
| 		block++;
 | |
| 		instr->len -= chip->device->erase_size;
 | |
| 		instr->addr += chip->device->erase_size;
 | |
| 	}
 | |
| 
 | |
| 	instr->state = MTD_ERASE_DONE;
 | |
| 
 | |
| 	mutex_unlock(&chip->lock);
 | |
| 	return 0;
 | |
| 
 | |
| done:
 | |
| 	/*
 | |
| 	 * Write disable
 | |
| 	 */
 | |
| 	txbuf[0] = 0x04;
 | |
| 	res = spi_write(spi, txbuf, 1);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed write disable res=%d\n", chip->name, res);
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&chip->lock);
 | |
| 
 | |
| 	mtd_erase_callback(instr);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_read
 | |
|  *
 | |
|  * return -EUCLEAN: ecc error recovered
 | |
|  * return -EBADMSG: ecc error not recovered
 | |
| */
 | |
| static int nand_spi_er_read(struct mtd_info *mtd, loff_t from, size_t len,
 | |
| 			       size_t *retlen, u_char *buf)
 | |
| {
 | |
| 	struct nand_spi_er *chip = mtd->priv;
 | |
| 	struct spi_device *spi = chip->spi;
 | |
| 
 | |
| 	uint32_t row;
 | |
| 	uint32_t column;
 | |
| 	int retval = 0;
 | |
| 
 | |
| 	*retlen = 0;
 | |
| 	DEBUG(MTD_DEBUG_LEVEL2, "%s: read block from %llx len %d into %p\n", chip->name, from, len, buf);
 | |
| 
 | |
| 	/*
 | |
| 	 * Zero length reads, nothing to do
 | |
| 	 */
 | |
| 	if (len == 0) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Reject reads which go over the end of the flash
 | |
| 	 */
 | |
| 	if ((from + len) > mtd->size) {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Get the row and column address to start at
 | |
| 	 */
 | |
| 	row = from >> 11;
 | |
| 	column = from & 0x7FF;
 | |
| 	DEBUG(MTD_DEBUG_LEVEL3, "%s: row=%x %d column=%x %d last_row=%x %d\n", chip->name, row, row, column, column, chip->last_row, chip->last_row);
 | |
| 
 | |
| 	/*
 | |
| 	 * Read the data from the chip
 | |
| 	 */
 | |
| 	mutex_lock(&chip->lock);
 | |
| 	while (len) {
 | |
| 		uint8_t stat;
 | |
| 		uint8_t txbuf[4];
 | |
| 		struct spi_message message;
 | |
| 		struct spi_transfer x[2];
 | |
| 		int res;
 | |
| 		size_t toread;
 | |
| 
 | |
| 		/*
 | |
| 		 * Figure out how much to read
 | |
| 		 *
 | |
| 		 * If we are reading from the middle of a page then the most we
 | |
| 		 * can read is to the end of the page
 | |
| 		 */
 | |
| 		toread = len;
 | |
| 		if (toread > (chip->device->page_size - column)) {
 | |
| 			toread = chip->device->page_size - column;
 | |
| 		}
 | |
| 
 | |
| 		DEBUG(MTD_DEBUG_LEVEL3, "%s: buf=%p toread=%x row=%x column=%x last_row=%x\n", chip->name, buf, toread, row, column, chip->last_row);
 | |
| 
 | |
| 		if (chip->last_row != row) {
 | |
| 			/*
 | |
| 			 * Check if the block is bad
 | |
| 			 */
 | |
| 			if (test_bit(NAND_SPI_ER_BLOCK_FROM_ROW(row), chip->bbt)) {
 | |
| 				mutex_unlock(&chip->lock);
 | |
| 				return -EBADMSG;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * Load the appropriate page
 | |
| 			 */
 | |
| 			txbuf[0] = 0x13;
 | |
| 			txbuf[1] = 0x00;
 | |
| 			txbuf[2] = row >> 8;
 | |
| 			txbuf[3] = row & 0xFF;
 | |
| 			res = spi_write(spi, txbuf, 4);
 | |
| 			if (res) {
 | |
| 				DEBUG(MTD_DEBUG_LEVEL1, "%s: failed page load res=%d\n", chip->name, res);
 | |
| 				mutex_unlock(&chip->lock);
 | |
| 				return res;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * Wait
 | |
| 			 */
 | |
| 			res = nand_spi_er_busywait(chip, &stat);
 | |
| 			if (res || (stat & NAND_SPI_ER_STATUS_OIP)) {
 | |
| 				DEBUG(MTD_DEBUG_LEVEL1, "%s: chip is busy or nonresponsive res=%d stat=%02x\n", chip->name, res, stat);
 | |
| 				if (res) {
 | |
| 					mutex_unlock(&chip->lock);
 | |
| 					return res;
 | |
| 				}
 | |
| 
 | |
| 				/*
 | |
| 				 * Chip is stuck?
 | |
| 				 */
 | |
| 				mutex_unlock(&chip->lock);
 | |
| 				return -EIO;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * Check the ECC bits
 | |
| 			 */
 | |
| 			stat >>= 4;
 | |
| 			if (stat == 1) {
 | |
| 				DEBUG(MTD_DEBUG_LEVEL1, "%s: ECC recovered, row=%x\n", chip->name, row);
 | |
| 				retval = -EUCLEAN;
 | |
| 			}
 | |
| 			if (stat == 2) {
 | |
| 				DEBUG(MTD_DEBUG_LEVEL0, "%s: failed ECC, row=%x\n", chip->name, row);
 | |
| 				chip->last_row = NAND_SPI_ER_LAST_ROW_INVALID;
 | |
| 				mutex_unlock(&chip->lock);
 | |
| 				return -EBADMSG;
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		chip->last_row = row;
 | |
| 
 | |
| 		/*
 | |
| 		 * Read out the data
 | |
| 		 */
 | |
| 		spi_message_init(&message);
 | |
| 		memset(x, 0, sizeof(x));
 | |
| 
 | |
| 		txbuf[0] = 0x03;
 | |
| 		txbuf[1] = column >> 8;
 | |
| 		txbuf[2] = column & 0xFF;
 | |
| 		txbuf[3] = 0;
 | |
| 		x[0].tx_buf = txbuf;
 | |
| 		x[0].len = 4;
 | |
| 		spi_message_add_tail(&x[0], &message);
 | |
| 
 | |
| 		x[1].rx_buf = buf;
 | |
| 		x[1].len = toread;
 | |
| 		spi_message_add_tail(&x[1], &message);
 | |
| 
 | |
| 		res = spi_sync(spi, &message);
 | |
| 		if (res) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: failed data read res=%d\n", chip->name, res);
 | |
| 			mutex_unlock(&chip->lock);
 | |
| 			return res;
 | |
| 		}
 | |
| 		buf += toread;
 | |
| 		len -= toread;
 | |
| 		*retlen += toread;
 | |
| 
 | |
| 		/*
 | |
| 		 * For the next page, increment the row and always start at column 0
 | |
| 		 */
 | |
| 		column = 0;
 | |
| 		row++;
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&chip->lock);
 | |
| 	return retval;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_write
 | |
|  */
 | |
| #define NOT_ALIGNED(x) ((x & (device->write_size - 1)) != 0)
 | |
| static int nand_spi_er_write(struct mtd_info *mtd, loff_t to, size_t len,
 | |
| 				size_t *retlen, const u_char *buf)
 | |
| {
 | |
| 	struct nand_spi_er *chip = mtd->priv;
 | |
| 	struct spi_device *spi = chip->spi;
 | |
| 	const struct nand_spi_er_device *device = chip->device;
 | |
| 	uint32_t row;
 | |
| 	uint32_t col;
 | |
| 	uint8_t txbuf[4];
 | |
| 	int res;
 | |
| 	size_t towrite;
 | |
| 
 | |
| 	DEBUG(MTD_DEBUG_LEVEL2, "%s: write block to %llx len %d from %p\n", chip->name, to, len, buf);
 | |
| 
 | |
| 	*retlen = 0;
 | |
| 
 | |
| 	/*
 | |
| 	 * nothing to write
 | |
| 	 */
 | |
| 	if (!len) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Reject writes which go over the end of the flash
 | |
| 	 */
 | |
| 	if ((to + len) > mtd->size) {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Check to see if everything is page aligned
 | |
| 	 */
 | |
| 	if (NOT_ALIGNED(to) || NOT_ALIGNED(len)) {
 | |
| 		printk(KERN_NOTICE "nand_spi_er_write: Attempt to write non page aligned data\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&chip->lock);
 | |
| 	chip->last_row = NAND_SPI_ER_LAST_ROW_INVALID;
 | |
| 
 | |
| 	/*
 | |
| 	 * If the first write is a partial write then write at most the number of
 | |
| 	 * bytes to get us page aligned and then the remainder will be
 | |
| 	 * page aligned.  The last bit may be a partial page as well.
 | |
| 	 */
 | |
| 	col = to & (device->page_size - 1);
 | |
| 	towrite = device->page_size - col;
 | |
| 	if (towrite > len) {
 | |
| 		towrite = len;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Write the data
 | |
| 	 */
 | |
| 	row = to >> 11;
 | |
| 	while (len) {
 | |
| 		struct spi_message message;
 | |
| 		struct spi_transfer x[2];
 | |
| 		uint8_t stat;
 | |
| 
 | |
| 		DEBUG(MTD_DEBUG_LEVEL3, "%s: write %p to row:%x col:%x len:%x rem:%x\n", chip->name, buf, row, col, towrite, len);
 | |
| 
 | |
| 		/*
 | |
| 		 * Write enable
 | |
| 		 */
 | |
| 		txbuf[0] = 0x06;
 | |
| 		res = spi_write(spi, txbuf, 1);
 | |
| 		if (res) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: failed write enable res=%d\n", chip->name, res);
 | |
| 			mutex_unlock(&chip->lock);
 | |
| 			return res;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Write the data into the cache
 | |
| 		 */
 | |
| 		spi_message_init(&message);
 | |
| 		memset(x, 0, sizeof(x));
 | |
| 		txbuf[0] = 0x02;
 | |
| 		txbuf[1] = col >> 8;
 | |
| 		txbuf[2] = col & 0xFF;
 | |
| 		x[0].tx_buf = txbuf;
 | |
| 		x[0].len = 3;
 | |
| 		spi_message_add_tail(&x[0], &message);
 | |
| 		x[1].tx_buf = buf;
 | |
| 		x[1].len = towrite;
 | |
| 		spi_message_add_tail(&x[1], &message);
 | |
| 		res = spi_sync(spi, &message);
 | |
| 		if (res) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: failed cache write res=%d\n", chip->name, res);
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Program execute
 | |
| 		 */
 | |
| 		txbuf[0] = 0x10;
 | |
| 		txbuf[1] = 0x00;
 | |
| 		txbuf[2] = row >> 8;
 | |
| 		txbuf[3] = row & 0xFF;
 | |
| 		res = spi_write(spi, txbuf, 4);
 | |
| 		if (res) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: failed prog execute res=%d\n", chip->name, res);
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Wait
 | |
| 		 */
 | |
| 		res = nand_spi_er_busywait(chip, &stat);
 | |
| 		if (res || (stat & NAND_SPI_ER_STATUS_OIP)) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: chip is busy or nonresponsive res=%d stat=%02x\n", chip->name, res, stat);
 | |
| 			if (res) {
 | |
| 				goto done;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * Chip is stuck?
 | |
| 			 */
 | |
| 			res = -EIO;
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		if (stat & (1 << 3)) {
 | |
| 			res = -EBADMSG;
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		row++;
 | |
| 		buf += towrite;
 | |
| 		len -= towrite;
 | |
| 		*retlen += towrite;
 | |
| 
 | |
| 		/*
 | |
| 		 * At this point, we are always page aligned so start at column 0.
 | |
| 		 * Note we may not have a full page to write at the end, hence the
 | |
| 		 * check if towrite > len.
 | |
| 		 */
 | |
| 		col = 0;
 | |
| 		towrite = device->page_size;
 | |
| 		if (towrite > len) {
 | |
| 			towrite = len;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&chip->lock);
 | |
| 	return res;
 | |
| 
 | |
| done:
 | |
| 	/*
 | |
| 	 * Write disable
 | |
| 	 */
 | |
| 	txbuf[0] = 0x04;
 | |
| 	res = spi_write(spi, txbuf, 1);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed write disable res=%d\n", chip->name, res);
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&chip->lock);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_isbad
 | |
|  */
 | |
| static int nand_spi_er_isbad(struct mtd_info *mtd, loff_t ofs)
 | |
| {
 | |
| 	struct nand_spi_er *chip = mtd->priv;
 | |
| 	uint32_t block;
 | |
| 
 | |
| 	if (ofs & (chip->device->erase_size - 1)) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: address not aligned %llx\n", chip->name, ofs);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	block = ofs >> 17;
 | |
| 
 | |
| 	return test_bit(block, chip->bbt);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_markbad
 | |
|  */
 | |
| static int nand_spi_er_markbad(struct mtd_info *mtd, loff_t ofs)
 | |
| {
 | |
| 	struct nand_spi_er *chip = mtd->priv;
 | |
| 	struct spi_device *spi = chip->spi;
 | |
| 	uint32_t block;
 | |
| 	uint32_t row;
 | |
| 	uint8_t txbuf[7];
 | |
| 	int res;
 | |
| 	uint8_t stat;
 | |
| 
 | |
| 	if (ofs & (chip->device->erase_size - 1)) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: address not aligned %llx\n", chip->name, ofs);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	block = ofs >> 17;
 | |
| 
 | |
| 	/*
 | |
| 	 * If it's already marked bad, no need to mark it
 | |
| 	 */
 | |
| 	if (test_bit(block, chip->bbt)) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Mark it in our cache
 | |
| 	 */
 | |
| 	__set_bit(block, chip->bbt);
 | |
| 
 | |
| 	/*
 | |
| 	 * Write the user bad block mark.  If it fails, then we really
 | |
| 	 * can't do anything about it.
 | |
| 	 */
 | |
| 	mutex_lock(&chip->lock);
 | |
| 	chip->last_row = NAND_SPI_ER_LAST_ROW_INVALID;
 | |
| 
 | |
| 	/*
 | |
| 	 * Write enable
 | |
| 	 */
 | |
| 	txbuf[0] = 0x06;
 | |
| 	res = spi_write(spi, txbuf, 1);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed write enable res=%d\n", chip->name, res);
 | |
| 		mutex_unlock(&chip->lock);
 | |
| 		return res;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Write the mark
 | |
| 	 */
 | |
| 	txbuf[0] = 0x84;
 | |
| 	txbuf[1] = 0x08;
 | |
| 	txbuf[2] = NAND_SPI_ER_BAD_BLOCK_MARK_OFFSET;
 | |
| 	txbuf[3] = 0xde;
 | |
| 	txbuf[4] = 0xad;
 | |
| 	txbuf[5] = 0xbe;
 | |
| 	txbuf[6] = 0xef;
 | |
| 	res = spi_write(spi, txbuf, 7);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed write mark res=%d\n", chip->name, res);
 | |
| 		goto done;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Program execute
 | |
| 	 */
 | |
| 	row = ofs >> 11;
 | |
| 	txbuf[0] = 0x10;
 | |
| 	txbuf[1] = 0x00;
 | |
| 	txbuf[2] = row >> 8;
 | |
| 	txbuf[3] = row & 0xFF;
 | |
| 	res = spi_write(spi, txbuf, 4);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed program execute res=%d\n", chip->name, res);
 | |
| 		goto done;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Wait
 | |
| 	 */
 | |
| 	res = nand_spi_er_busywait(chip, &stat);
 | |
| 	if (res || (stat & NAND_SPI_ER_STATUS_OIP)) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: chip is busy or nonresponsive res=%d stat=%02x\n", chip->name, res, stat);
 | |
| 		if (res) {
 | |
| 			goto done;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Chip is stuck?
 | |
| 		 */
 | |
| 		res = -EIO;
 | |
| 		goto done;
 | |
| 	}
 | |
| 
 | |
| 	if (stat & (1 << 3)) {
 | |
| 		res = -EBADMSG;
 | |
| 	}
 | |
| 
 | |
| done:
 | |
| 	/*
 | |
| 	 * Write disable
 | |
| 	 */
 | |
| 	txbuf[0] = 0x04;
 | |
| 	res = spi_write(spi, txbuf, 1);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed write disable res=%d\n", chip->name, res);
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&chip->lock);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_read_bbt
 | |
|  */
 | |
| static int nand_spi_er_read_bbt(struct nand_spi_er *chip)
 | |
| {
 | |
| 	int j;
 | |
| 	for (j = 0; j < chip->device->blocks; j++) {
 | |
| 		uint8_t txbuf[4];
 | |
| 		uint8_t rxbuf[16];
 | |
| 		uint32_t bbmark;
 | |
| 		int res;
 | |
| 		unsigned short row = j << 6;
 | |
| 		uint8_t stat;
 | |
| 
 | |
| 		/*
 | |
| 		 * Read Page
 | |
| 		 */
 | |
| 		txbuf[0] = 0x13;
 | |
| 		txbuf[1] = 0x00;
 | |
| 		txbuf[2] = row >> 8;
 | |
| 		txbuf[3] = row & 0xFF;
 | |
| 		res = spi_write(chip->spi, txbuf, 4);
 | |
| 		if (res) {
 | |
| 			return res;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Wait
 | |
| 		 */
 | |
| 		res = nand_spi_er_busywait(chip, &stat);
 | |
| 		if (res || (stat & NAND_SPI_ER_STATUS_OIP)) {
 | |
| 			DEBUG(MTD_DEBUG_LEVEL1, "%s: chip is busy or nonresponsive res=%d stat=%02x\n", chip->name, res, stat);
 | |
| 			if (res) {
 | |
| 				return res;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * Chip is stuck?
 | |
| 			 */
 | |
| 			return -EIO;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Check factory bad block mark
 | |
| 		 */
 | |
| 		txbuf[0] = 0x03;
 | |
| 		txbuf[1] = 0x08;
 | |
| 		txbuf[2] = 0x00;
 | |
| 		txbuf[3] = 0x00;
 | |
| 		res = spi_write_then_read(chip->spi, txbuf, 4, rxbuf, 16);
 | |
| 		if (res) {
 | |
| 			return res;
 | |
| 		}
 | |
| 		if (rxbuf[0] != 0xFF) {
 | |
| 			chip->nbb++;
 | |
| 			__set_bit(j, chip->bbt);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		memcpy(&bbmark, &rxbuf[8], 4);
 | |
| 		if (bbmark == 0xdeadbeef) {
 | |
| 			chip->nbb++;
 | |
| 			__set_bit(j, chip->bbt);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| #if defined(CONFIG_MTD_DEBUG) && (MTD_DEBUG_LEVEL3 <= CONFIG_MTD_DEBUG_VERBOSE)
 | |
| 	printk("%s: Bad Block Table:", chip->name);
 | |
| 	for (j = 0; j < chip->device->blocks; j++) {
 | |
| 		if ((j % 64) == 0) {
 | |
| 			printk("\n%s: block %03x: ", chip->name, j);
 | |
| 		}
 | |
| 		printk("%c", test_bit(j, chip->bbt) ? 'X' : '.');
 | |
| 	}
 | |
| 	printk("\n%s: Bad Block Numbers: ", chip->name);
 | |
| 	for (j = 0; j < chip->device->blocks; j++) {
 | |
| 		if (test_bit(j, chip->bbt)) {
 | |
| 			printk("%x ", j);
 | |
| 		}
 | |
| 	}
 | |
| 	printk("\n");
 | |
| #endif
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #ifndef MODULE
 | |
| /*
 | |
|  * Called at boot time:
 | |
|  *
 | |
|  * nand_spi_er=read_only
 | |
|  *	if read_only specified then do not unlock device
 | |
|  */
 | |
| static int __init nand_spi_er_setup(char *str)
 | |
| {
 | |
| 	if (str && (strncasecmp(str, "read_only", 9) == 0)) {
 | |
| 		read_only = 1;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| __setup("nand_spi_er=", nand_spi_er_setup);
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_probe
 | |
|  *	Detect and initialize nand_spi_er device.
 | |
|  */
 | |
| static int __devinit nand_spi_er_probe(struct spi_device *spi)
 | |
| {
 | |
| 	uint8_t txbuf[3];
 | |
| 	uint8_t rxbuf[2];
 | |
| 	int i;
 | |
| 	int res;
 | |
| 	size_t bbt_bytes;
 | |
| 	struct nand_spi_er *chip;
 | |
| 	const struct nand_spi_er_device *device;
 | |
| 
 | |
| 	res = spi_setup(spi);
 | |
| 	if (res) {
 | |
| 		return res;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Reset
 | |
| 	 */
 | |
| 	for (i = 0; i < 2; i++) {
 | |
| 		txbuf[0] = 0xFF;
 | |
| 		res = spi_write(spi, txbuf, 1);
 | |
| 		if (res) {
 | |
| 			return res;
 | |
| 		}
 | |
| 		udelay(250);
 | |
| 	}
 | |
| 	udelay(1000);
 | |
| 
 | |
| 	/*
 | |
| 	 * Read ID
 | |
| 	 */
 | |
| 	txbuf[0] = 0x9F;
 | |
| 	txbuf[1] = 0x00;
 | |
| 	res = spi_write_then_read(spi, txbuf, 2, rxbuf, 2);
 | |
| 	if (res) {
 | |
| 		return res;
 | |
| 	}
 | |
| 
 | |
| 	device = nand_spi_er_devices;
 | |
| 	for (i = 0; i < ARRAY_SIZE(nand_spi_er_devices); i++) {
 | |
| 		if ((device->id0 == rxbuf[0]) && (device->id1 == rxbuf[1])) {
 | |
| 			break;
 | |
| 		}
 | |
| 		device++;
 | |
| 	}
 | |
| 	if (i == ARRAY_SIZE(nand_spi_er_devices)) {
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Initialize our chip structure
 | |
| 	 */
 | |
| 	bbt_bytes = DIV_ROUND_UP(device->blocks, BITS_PER_BYTE);
 | |
| 	chip = kzalloc(sizeof(struct nand_spi_er) + bbt_bytes, GFP_KERNEL);
 | |
| 	if (!chip) {
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 	snprintf(chip->name, sizeof(chip->name), "%s.%d.%d", device->name, spi->master->bus_num, spi->chip_select);
 | |
| 
 | |
| 	chip->spi = spi;
 | |
| 	chip->device = device;
 | |
| 	chip->last_row = NAND_SPI_ER_LAST_ROW_INVALID;
 | |
| 
 | |
| 	mutex_init(&chip->lock);
 | |
| 
 | |
| 	chip->mtd.type = MTD_NANDFLASH;
 | |
| 	chip->mtd.flags = MTD_WRITEABLE;
 | |
| 
 | |
| 	/*
 | |
| 	 * #blocks * block size * n blocks
 | |
| 	 */
 | |
| 	chip->mtd.size = device->blocks * device->pages_per_block * device->page_size;
 | |
| 	chip->mtd.erasesize = device->erase_size;
 | |
| 
 | |
| 	/*
 | |
| 	 * 1 page, optionally we can support partial write (512)
 | |
| 	 */
 | |
| 	chip->mtd.writesize = device->write_size;
 | |
| 	chip->mtd.name = device->name;
 | |
| 	chip->mtd.erase = nand_spi_er_erase;
 | |
| 	chip->mtd.read = nand_spi_er_read;
 | |
| 	chip->mtd.write = nand_spi_er_write;
 | |
| 	chip->mtd.block_isbad = nand_spi_er_isbad;
 | |
| 	chip->mtd.block_markbad = nand_spi_er_markbad;
 | |
| 	chip->mtd.priv = chip;
 | |
| 
 | |
| 	/*
 | |
| 	 * Cache the bad block table
 | |
| 	 */
 | |
| 	res = nand_spi_er_read_bbt(chip);
 | |
| 	if (res) {
 | |
| 		kfree(chip);
 | |
| 		return res;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Un/lock the chip
 | |
| 	 */
 | |
| 	txbuf[0] = 0x1F;
 | |
| 	txbuf[1] = 0xA0;
 | |
| 	if (read_only) {
 | |
| 		txbuf[2] = 0x38;
 | |
| 	} else {
 | |
| 		txbuf[2] = 0x00;
 | |
| 	}
 | |
| 	res = spi_write(spi, txbuf, 3);
 | |
| 	if (res) {
 | |
| 		DEBUG(MTD_DEBUG_LEVEL1, "%s: failed lock operation res=%d\n", chip->name, res);
 | |
| 		mutex_unlock(&chip->lock);
 | |
| 		return res;
 | |
| 	}
 | |
| 
 | |
| 	spi_set_drvdata(spi, chip);
 | |
| 
 | |
| 	printk(KERN_INFO "%s: added device %s size: %u KBytes %u bad blocks %s\n", spi->dev.bus_id, chip->mtd.name, DIV_ROUND_UP(chip->mtd.size, 1024), chip->nbb, read_only ? "[read only]" : "");
 | |
| 	return add_mtd_device(&chip->mtd);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_remove
 | |
|  */
 | |
| static int __devexit nand_spi_er_remove(struct spi_device *spi)
 | |
| {
 | |
| 	struct nand_spi_er *chip = spi_get_drvdata(spi);
 | |
| 	int status = 0;
 | |
| 
 | |
| 	DEBUG(MTD_DEBUG_LEVEL1, "%s: remove\n", spi->dev.bus_id);
 | |
| 	status = del_mtd_device(&chip->mtd);
 | |
| 	if (status == 0)
 | |
| 		kfree(chip);
 | |
| 	return status;
 | |
| }
 | |
| 
 | |
| static struct spi_driver nand_spi_er_driver = {
 | |
| 	.driver = {
 | |
| 		.name		= "nand-spi-er",
 | |
| 		.bus		= &spi_bus_type,
 | |
| 		.owner		= THIS_MODULE,
 | |
| 	},
 | |
| 
 | |
| 	.probe		= nand_spi_er_probe,
 | |
| 	.remove		= __devexit_p(nand_spi_er_remove),
 | |
| 
 | |
| 	/* FIXME:  investigate suspend and resume... */
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_init
 | |
|  */
 | |
| static int __init nand_spi_er_init(void)
 | |
| {
 | |
| 	return spi_register_driver(&nand_spi_er_driver);
 | |
| }
 | |
| module_init(nand_spi_er_init);
 | |
| 
 | |
| /*
 | |
|  * nand_spi_er_exit
 | |
|  */
 | |
| static void __exit nand_spi_er_exit(void)
 | |
| {
 | |
| 	spi_unregister_driver(&nand_spi_er_driver);
 | |
| }
 | |
| module_exit(nand_spi_er_exit);
 | |
| 
 | |
| 
 | |
| MODULE_LICENSE("GPL");
 | |
| MODULE_AUTHOR("Patrick Tjin");
 | |
| MODULE_DESCRIPTION("MTD nand_spi_er driver");
 |