diff options
Diffstat (limited to 'target/linux/ubicom32/files/drivers/mtd/devices/nand-spi-er.c')
-rw-r--r-- | target/linux/ubicom32/files/drivers/mtd/devices/nand-spi-er.c | 1017 |
1 files changed, 1017 insertions, 0 deletions
diff --git a/target/linux/ubicom32/files/drivers/mtd/devices/nand-spi-er.c b/target/linux/ubicom32/files/drivers/mtd/devices/nand-spi-er.c new file mode 100644 index 0000000000..73938c8827 --- /dev/null +++ b/target/linux/ubicom32/files/drivers/mtd/devices/nand-spi-er.c @@ -0,0 +1,1017 @@ +/* + * 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"); |