diff options
author | Rob Herring <r.herring@freescale.com> | 2009-10-19 14:43:19 -0500 |
---|---|---|
committer | Alejandro Gonzalez <alex.gonzalez@digi.com> | 2010-02-12 17:19:16 +0100 |
commit | cdde68e3a7d4cbf4701005ab6032366e76009419 (patch) | |
tree | 5690552665f0b7843e6552e4d5fe7b63cbc78f51 /drivers/mtd | |
parent | 57d1417ea543b83760b3fd76a46b9d29deb2e444 (diff) |
ENGR00117389 Port 5.0.0 release to 2.6.31
This is i.MX BSP 5.0.0 release ported to 2.6.31
Signed-off-by: Rob Herring <r.herring@freescale.com>
Signed-off-by: Alan Tull <r80115@freescale.com>
Signed-off-by: Xinyu Chen <xinyu.chen@freescale.com>
Diffstat (limited to 'drivers/mtd')
27 files changed, 19195 insertions, 0 deletions
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 0993d5cf3923..f008baccd796 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o obj-$(CONFIG_MTD_M25P80) += m25p80.o + diff --git a/drivers/mtd/devices/mxc_dataflash.c b/drivers/mtd/devices/mxc_dataflash.c new file mode 100644 index 000000000000..88c5788cb445 --- /dev/null +++ b/drivers/mtd/devices/mxc_dataflash.c @@ -0,0 +1,1031 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * (c) 2005 MontaVista Software, Inc. + * + * This code is based on mtd_dataflash.c by adding FSL spi access. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#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> + +/* + * DataFlash is a kind of SPI flash. Most AT45 chips have two buffers in + * each chip, which may be used for double buffered I/O; but this driver + * doesn't (yet) use these for any kind of i/o overlap or prefetching. + * + * Sometimes DataFlash is packaged in MMC-format cards, although the + * MMC stack can't (yet?) distinguish between MMC and DataFlash + * protocols during enumeration. + */ + +/* reads can bypass the buffers */ +#define OP_READ_CONTINUOUS 0xE8 +#define OP_READ_PAGE 0xD2 + +/* group B requests can run even while status reports "busy" */ +#define OP_READ_STATUS 0xD7 /* group B */ + +/* move data between host and buffer */ +#define OP_READ_BUFFER1 0xD4 /* group B */ +#define OP_READ_BUFFER2 0xD6 /* group B */ +#define OP_WRITE_BUFFER1 0x84 /* group B */ +#define OP_WRITE_BUFFER2 0x87 /* group B */ + +/* erasing flash */ +#define OP_ERASE_PAGE 0x81 +#define OP_ERASE_BLOCK 0x50 + +/* move data between buffer and flash */ +#define OP_TRANSFER_BUF1 0x53 +#define OP_TRANSFER_BUF2 0x55 +#define OP_MREAD_BUFFER1 0xD4 +#define OP_MREAD_BUFFER2 0xD6 +#define OP_MWERASE_BUFFER1 0x83 +#define OP_MWERASE_BUFFER2 0x86 +#define OP_MWRITE_BUFFER1 0x88 /* sector must be pre-erased */ +#define OP_MWRITE_BUFFER2 0x89 /* sector must be pre-erased */ + +/* write to buffer, then write-erase to flash */ +#define OP_PROGRAM_VIA_BUF1 0x82 +#define OP_PROGRAM_VIA_BUF2 0x85 + +/* compare buffer to flash */ +#define OP_COMPARE_BUF1 0x60 +#define OP_COMPARE_BUF2 0x61 + +/* read flash to buffer, then write-erase to flash */ +#define OP_REWRITE_VIA_BUF1 0x58 +#define OP_REWRITE_VIA_BUF2 0x59 + +/* newer chips report JEDEC manufacturer and device IDs; chip + * serial number and OTP bits; and per-sector writeprotect. + */ +#define OP_READ_ID 0x9F +#define OP_READ_SECURITY 0x77 +#define OP_WRITE_SECURITY_REVC 0x9A +#define OP_WRITE_SECURITY 0x9B /* revision D */ + +#define SPI_FIFOSIZE 24 /* Bust size in bytes */ +#define CMD_SIZE 4 +#define DUMY_SIZE 4 + +struct dataflash { + uint8_t command[4]; + char name[24]; + + unsigned partitioned:1; + + unsigned short page_offset; /* offset in flash address */ + unsigned int page_size; /* of bytes per page */ + + struct mutex lock; + struct spi_device *spi; + + struct mtd_info mtd; +}; + +#ifdef CONFIG_MTD_PARTITIONS +#define mtd_has_partitions() (1) +#else +#define mtd_has_partitions() (0) +#endif + +/* ......................................................................... */ + +/* + * This function initializes the SPI device parameters. + */ +static inline int spi_nor_setup(struct spi_device *spi, u8 bst_len) +{ + spi->bits_per_word = bst_len << 3; + + return spi_setup(spi); +} + +/* + * This function perform spi read/write transfer. + */ +static int spi_read_write(struct spi_device *spi, u8 * buf, u32 len) +{ + struct spi_message m; + struct spi_transfer t; + + if (len > SPI_FIFOSIZE || len <= 0) + return -1; + + spi_nor_setup(spi, len); + + spi_message_init(&m); + memset(&t, 0, sizeof t); + + t.tx_buf = buf; + t.rx_buf = buf; + t.len = ((len - 1) >> 2) + 1; + + spi_message_add_tail(&t, &m); + + if (spi_sync(spi, &m) != 0 || m.status != 0) { + printk(KERN_ERR "%s: error\n", __func__); + return -1; + } + + DEBUG(MTD_DEBUG_LEVEL2, "%s: len: 0x%x success\n", __func__, len); + + return 0; + +} + +/* + * Return the status of the DataFlash device. + */ +static inline int dataflash_status(struct spi_device *spi) +{ + /* NOTE: at45db321c over 25 MHz wants to write + * a dummy byte after the opcode... + */ + ssize_t retval; + + u16 val = OP_READ_STATUS << 8; + + retval = spi_read_write(spi, (u8 *)&val, 2); + + if (retval < 0) + return retval; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: status: 0x%x\n", __func__, val & 0xff); + + return val & 0xff; +} + +/* + * Poll the DataFlash device until it is READY. + * This usually takes 5-20 msec or so; more for sector erase. + */ +static int dataflash_waitready(struct spi_device *spi) +{ + int status; + + for (;;) { + status = dataflash_status(spi); + if (status < 0) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: status %d?\n", + dev_name(&spi->dev), status); + status = 0; + } + + if (status & (1 << 7)) /* RDY/nBSY */ + return status; + + msleep(3); + } +} + +/* ......................................................................... */ + +/* + * Erase pages of flash. + */ +static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + struct spi_device *spi = priv->spi; + unsigned blocksize = priv->page_size << 3; + uint8_t *command; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: erase addr=0x%x len 0x%x\n", + dev_name(&spi->dev), instr->addr, instr->len); + + /* Sanity checks */ + if ((instr->addr + instr->len) > mtd->size + || (instr->len % priv->page_size) != 0 + || (instr->addr % priv->page_size) != 0) + return -EINVAL; + + command = priv->command; + + mutex_lock(&priv->lock); + while (instr->len > 0) { + unsigned int pageaddr; + int status; + int do_block; + + /* Calculate flash page address; use block erase (for speed) if + * we're at a block boundary and need to erase the whole block. + */ + pageaddr = instr->addr / priv->page_size; + do_block = (pageaddr & 0x7) == 0 && instr->len >= blocksize; + pageaddr = pageaddr << priv->page_offset; + + command[3] = do_block ? OP_ERASE_BLOCK : OP_ERASE_PAGE; + command[2] = (uint8_t) (pageaddr >> 16); + command[1] = (uint8_t) (pageaddr >> 8); + command[0] = 0; + + DEBUG(MTD_DEBUG_LEVEL3, "ERASE %s: (%x) %x %x %x [%i]\n", + do_block ? "block" : "page", + command[0], command[1], command[2], command[3], pageaddr); + + status = spi_read_write(spi, command, 4); + (void)dataflash_waitready(spi); + + if (status < 0) { + printk(KERN_ERR "%s: erase %x, err %d\n", + dev_name(&spi->dev), pageaddr, status); + /* REVISIT: can retry instr->retries times; or + * giveup and instr->fail_addr = instr->addr; + */ + continue; + } + + if (do_block) { + instr->addr += blocksize; + instr->len -= blocksize; + } else { + instr->addr += priv->page_size; + instr->len -= priv->page_size; + } + } + mutex_unlock(&priv->lock); + + /* Inform MTD subsystem that erase is complete */ + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; +} + +/* + * Read from the DataFlash device. + * from : Start offset in flash device + * len : Amount to read + * retlen : About of data actually read + * buf : Buffer containing the data + */ +static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct dataflash *priv = mtd->priv; + struct spi_device *spi = priv->spi; + u32 addr; + int rx_len = 0, count = 0, i = 0; + u_char txer[SPI_FIFOSIZE]; + u_char *s = txer; + u_char *d = buf; + int cmd_len = CMD_SIZE + DUMY_SIZE; + int status = 0; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: read 0x%x..0x%x\n", + dev_name(&priv->spi->dev), (unsigned)from, (unsigned)(from + len)); + + *retlen = 0; + + /* Sanity checks */ + if (!len) + return 0; + + if (from + len > mtd->size) + return -EINVAL; + + /* Calculate flash page/byte address */ + addr = (((unsigned)from / priv->page_size) << priv->page_offset) + + ((unsigned)from % priv->page_size); + + mutex_unlock(&priv->lock); + + while (len > 0) { + + rx_len = len > (SPI_FIFOSIZE - cmd_len) ? + SPI_FIFOSIZE - cmd_len : len; + + txer[3] = OP_READ_CONTINUOUS; + txer[2] = (addr >> 16) & 0xff; + txer[1] = (addr >> 8) & 0xff; + txer[0] = addr & 0xff; + + status = spi_read_write(spi, txer, + roundup(rx_len, 4) + cmd_len); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + s = txer + cmd_len; + + for (i = rx_len; i >= 0; i -= 4, s += 4) { + if (i < 4) { + if (i == 1) { + *d = s[3]; + } else if (i == 2) { + *d++ = s[3]; + *d++ = s[2]; + } else if (i == 3) { + *d++ = s[3]; + *d++ = s[2]; + *d++ = s[1]; + } + + break; + } + + *d++ = s[3]; + *d++ = s[2]; + *d++ = s[1]; + *d++ = s[0]; + } + + /* updaate */ + len -= rx_len; + addr += rx_len; + count += rx_len; + + DEBUG(MTD_DEBUG_LEVEL2, + "%s: left:0x%x, from:0x%08x, to:0x%p, done: 0x%x\n", + __func__, len, (u32) addr, d, count); + } + + *retlen = count; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: %d bytes read\n", __func__, count); + + mutex_unlock(&priv->lock); + + return status; +} + +/* + * Write to the DataFlash device. + * to : Start offset in flash device + * len : Amount to write + * retlen : Amount of data actually written + * buf : Buffer containing the data + */ +static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct dataflash *priv = mtd->priv; + struct spi_device *spi = priv->spi; + u32 pageaddr, addr, offset, writelen; + size_t remaining = len; + u_char *writebuf = (u_char *) buf; + int status = -EINVAL; + u_char txer[SPI_FIFOSIZE] = { 0 }; + uint8_t *command = priv->command; + u_char *d = txer; + u_char *s = (u_char *) buf; + int delta = 0, l = 0, i = 0, count = 0; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: write 0x%x..0x%x\n", + dev_name(&spi->dev), (unsigned)to, (unsigned)(to + len)); + + *retlen = 0; + + /* Sanity checks */ + if (!len) + return 0; + + if ((to + len) > mtd->size) + return -EINVAL; + + pageaddr = ((unsigned)to / priv->page_size); + offset = ((unsigned)to % priv->page_size); + if (offset + len > priv->page_size) + writelen = priv->page_size - offset; + else + writelen = len; + + mutex_lock(&priv->lock); + + while (remaining > 0) { + DEBUG(MTD_DEBUG_LEVEL3, "write @ %i:%i len=%i\n", + pageaddr, offset, writelen); + + addr = pageaddr << priv->page_offset; + + /* (1) Maybe transfer partial page to Buffer1 */ + if (writelen != priv->page_size) { + command[3] = OP_TRANSFER_BUF1; + command[2] = (addr & 0x00FF0000) >> 16; + command[1] = (addr & 0x0000FF00) >> 8; + command[0] = 0; + + DEBUG(MTD_DEBUG_LEVEL3, "TRANSFER: (%x) %x %x %x\n", + command[3], command[2], command[1], command[0]); + + status = spi_read_write(spi, command, CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + + } + + (void)dataflash_waitready(spi); + } + + count = writelen; + while (count) { + d = txer; + l = count > (SPI_FIFOSIZE - CMD_SIZE) ? + SPI_FIFOSIZE - CMD_SIZE : count; + + delta = l % 4; + if (delta) { + switch (delta) { + case 1: + d[0] = OP_WRITE_BUFFER1; + d[6] = (offset >> 8) & 0xff; + d[5] = offset & 0xff; + d[4] = *s++; + break; + case 2: + d[1] = OP_WRITE_BUFFER1; + d[7] = (offset >> 8) & 0xff; + d[6] = offset & 0xff; + d[5] = *s++; + d[4] = *s++; + break; + case 3: + d[2] = OP_WRITE_BUFFER1; + d[0] = (offset >> 8) & 0xff; + d[7] = offset & 0xff; + d[6] = *s++; + d[5] = *s++; + d[4] = *s++; + break; + default: + break; + } + + DEBUG(MTD_DEBUG_LEVEL3, + "WRITEBUF: (%x) %x %x %x\n", + txer[3], txer[2], txer[1], txer[0]); + + status = spi_read_write(spi, txer, + delta + CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + /* update */ + count -= delta; + offset += delta; + l -= delta; + } + + d[3] = OP_WRITE_BUFFER1; + d[1] = (offset >> 8) & 0xff; + d[0] = offset & 0xff; + + for (i = 0, d += 4; i < l / 4; i++, d += 4) { + d[3] = *s++; + d[2] = *s++; + d[1] = *s++; + d[0] = *s++; + } + + DEBUG(MTD_DEBUG_LEVEL3, "WRITEBUF: (%x) %x %x %x\n", + txer[3], txer[2], txer[1], txer[0]); + + status = spi_read_write(spi, txer, l + CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + /* update */ + count -= l; + offset += l; + } + + /* (2) Program full page via Buffer1 */ + command[3] = OP_MWERASE_BUFFER1; + command[2] = (addr >> 16) & 0xff; + command[1] = (addr >> 8) & 0xff; + + DEBUG(MTD_DEBUG_LEVEL3, "PROGRAM: (%x) %x %x %x\n", + command[3], command[2], command[1], command[0]); + + status = spi_read_write(spi, command, CMD_SIZE); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + (void)dataflash_waitready(spi); + + remaining -= writelen; + pageaddr++; + offset = 0; + writebuf += writelen; + *retlen += writelen; + + if (remaining > priv->page_size) + writelen = priv->page_size; + else + writelen = remaining; + } + mutex_unlock(&priv->lock); + + return status; +} + +/* ......................................................................... */ + +#ifdef CONFIG_MTD_DATAFLASH_OTP + +static int dataflash_get_otp_info(struct mtd_info *mtd, + struct otp_info *info, size_t len) +{ + /* Report both blocks as identical: bytes 0..64, locked. + * Unless the user block changed from all-ones, we can't + * tell whether it's still writable; so we assume it isn't. + */ + info->start = 0; + info->length = 64; + info->locked = 1; + return sizeof(*info); +} + +static ssize_t otp_read(struct spi_device *spi, unsigned base, + uint8_t *buf, loff_t off, size_t len) +{ + struct dataflash *priv = mtd->priv; + struct spi_device *spi = priv->spi; + int rx_len = 0, count = 0, i = 0; + u_char txer[SPI_FIFOSIZE]; + u_char *s = txer; + u_char *d = NULL; + int cmd_len = CMD_SIZE; + int status; + + if (off > 64) + return -EINVAL; + + if ((off + len) > 64) + len = 64 - off; + if (len == 0) + return len; + + /* to make simple, we read 64 out */ + l = base + 64; + + d = kzalloc(l, GFP_KERNEL); + if (!d) + return -ENOMEM; + + while (l > 0) { + + rx_len = l > (SPI_FIFOSIZE - cmd_len) ? + SPI_FIFOSIZE - cmd_len : l; + + txer[3] = OP_READ_SECURITY; + + status = spi_read_write(spi, txer, rx_len + cmd_len); + if (status) { + mutex_unlock(&priv->lock); + return status; + } + + s = txer + cmd_len; + for (i = rx_len; i >= 0; i -= 4, s += 4) { + + *d++ = s[3]; + *d++ = s[2]; + *d++ = s[1]; + *d++ = s[0]; + } + + /* updaate */ + l -= rx_len; + addr += rx_len; + count += rx_len; + + DEBUG(MTD_DEBUG_LEVEL2, + "%s: left:0x%x, from:0x%08x, to:0x%p, done: 0x%x\n", + __func__, len, (u32) addr, d, count); + } + + d -= count; + memcpy(buf, d + base + off, len); + + mutex_unlock(&priv->lock); + + return len; +} + +static int dataflash_read_fact_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, + u_char *buf) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + int status; + + /* 64 bytes, from 0..63 ... start at 64 on-chip */ + mutex_lock(&priv->lock); + status = otp_read(priv->spi, 64, buf, from, len); + mutex_unlock(&priv->lock); + + if (status < 0) + return status; + *retlen = status; + return 0; +} + +static int dataflash_read_user_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, + u_char *buf) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + int status; + + /* 64 bytes, from 0..63 ... start at 0 on-chip */ + mutex_lock(&priv->lock); + status = otp_read(priv->spi, 0, buf, from, len); + mutex_unlock(&priv->lock); + + if (status < 0) + return status; + *retlen = status; + return 0; +} + +static int dataflash_write_user_otp(struct mtd_info *mtd, + loff_t from, size_t len, size_t *retlen, + u_char *buf) +{ + printk(KERN_ERR "%s not support!!\n", __func__); + return 0; +} + +static char *otp_setup(struct mtd_info *device, char revision) +{ + device->get_fact_prot_info = dataflash_get_otp_info; + device->read_fact_prot_reg = dataflash_read_fact_otp; + device->get_user_prot_info = dataflash_get_otp_info; + device->read_user_prot_reg = dataflash_read_user_otp; + + /* rev c parts (at45db321c and at45db1281 only!) use a + * different write procedure; not (yet?) implemented. + */ + if (revision > 'c') + device->write_user_prot_reg = dataflash_write_user_otp; + + return ", OTP"; +} + +#else + +static char *otp_setup(struct mtd_info *device, char revision) +{ + return " (OTP)"; +} + +#endif + +/* ......................................................................... */ + +/* + * Register DataFlash device with MTD subsystem. + */ +static int __devinit +add_dataflash_otp(struct spi_device *spi, char *name, + int nr_pages, int pagesize, int pageoffset, char revision) +{ + struct dataflash *priv; + struct mtd_info *device; + struct flash_platform_data *pdata = spi->dev.platform_data; + char *otp_tag = ""; + + priv = kzalloc(sizeof *priv, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + priv->spi = spi; + priv->page_size = pagesize; + priv->page_offset = pageoffset; + + /* name must be usable with cmdlinepart */ + sprintf(priv->name, "spi%d.%d-%s", + spi->master->bus_num, spi->chip_select, name); + + device = &priv->mtd; + device->name = (pdata && pdata->name) ? pdata->name : priv->name; + device->size = nr_pages * pagesize; + device->erasesize = pagesize; + device->writesize = pagesize; + device->owner = THIS_MODULE; + device->type = MTD_DATAFLASH; + device->flags = MTD_CAP_NORFLASH; + device->erase = dataflash_erase; + device->read = dataflash_read; + device->write = dataflash_write; + device->priv = priv; + + if (revision >= 'c') + otp_tag = otp_setup(device, revision); + + dev_info(&spi->dev, "%s (%d KBytes) pagesize %d bytes%s\n", + name, DIV_ROUND_UP(device->size, 1024), pagesize, otp_tag); + dev_set_drvdata(&spi->dev, priv); + + if (mtd_has_partitions()) { + struct mtd_partition *parts; + int nr_parts = 0; + +#ifdef CONFIG_MTD_CMDLINE_PARTS + static const char *part_probes[] = { "cmdlinepart", NULL, }; + + nr_parts = parse_mtd_partitions(device, part_probes, &parts, 0); +#endif + + if (nr_parts <= 0 && pdata && pdata->parts) { + parts = pdata->parts; + nr_parts = pdata->nr_parts; + } + + if (nr_parts > 0) { + priv->partitioned = 1; + return add_mtd_partitions(device, parts, nr_parts); + } + } else if (pdata && pdata->nr_parts) + dev_warn(&spi->dev, "ignoring %d default partitions on %s\n", + pdata->nr_parts, device->name); + + return add_mtd_device(device) == 1 ? -ENODEV : 0; +} + +static inline int __devinit +add_dataflash(struct spi_device *spi, char *name, + int nr_pages, int pagesize, int pageoffset) +{ + return add_dataflash_otp(spi, name, nr_pages, pagesize, pageoffset, 0); +} + +struct flash_info { + char *name; + + /* JEDEC id has a high byte of zero plus three data bytes: + * the manufacturer id, then a two byte device id. + */ + uint32_t jedec_id; + + /* The size listed here is what works with OP_ERASE_PAGE. */ + unsigned nr_pages; + uint16_t pagesize; + uint16_t pageoffset; + + uint16_t flags; +#define SUP_POW2PS 0x0002 /* supports 2^N byte pages */ +#define IS_POW2PS 0x0001 /* uses 2^N byte pages */ +}; + +static struct flash_info __devinitdata dataflash_data[] = { + + /* + * NOTE: chips with SUP_POW2PS (rev D and up) need two entries, + * one with IS_POW2PS and the other without. The entry with the + * non-2^N byte page size can't name exact chip revisions without + * losing backwards compatibility for cmdlinepart. + * + * These newer chips also support 128-byte security registers (with + * 64 bytes one-time-programmable) and software write-protection. + */ + {"AT45DB011B", 0x1f2200, 512, 264, 9, SUP_POW2PS}, + {"at45db011d", 0x1f2200, 512, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB021B", 0x1f2300, 1024, 264, 9, SUP_POW2PS}, + {"at45db021d", 0x1f2300, 1024, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB041x", 0x1f2400, 2048, 264, 9, SUP_POW2PS}, + {"at45db041d", 0x1f2400, 2048, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB081B", 0x1f2500, 4096, 264, 9, SUP_POW2PS}, + {"at45db081d", 0x1f2500, 4096, 256, 8, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB161x", 0x1f2600, 4096, 528, 10, SUP_POW2PS}, + {"at45db161d", 0x1f2600, 4096, 512, 9, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB321x", 0x1f2700, 8192, 528, 10, 0}, /* rev C */ + + {"AT45DB321x", 0x1f2701, 8192, 528, 10, SUP_POW2PS}, + {"at45db321d", 0x1f2701, 8192, 512, 9, SUP_POW2PS | IS_POW2PS}, + + {"AT45DB642x", 0x1f2800, 8192, 1056, 11, SUP_POW2PS}, + {"at45db642d", 0x1f2800, 8192, 1024, 10, SUP_POW2PS | IS_POW2PS}, +}; + +static struct flash_info *__devinit jedec_probe(struct spi_device *spi) +{ + int tmp; + u32 code = OP_READ_ID << 24; + u32 jedec; + struct flash_info *info; + int status; + + /* JEDEC also defines an optional "extended device information" + * string for after vendor-specific data, after the three bytes + * we use here. Supporting some chips might require using it. + * + * If the vendor ID isn't Atmel's (0x1f), assume this call failed. + * That's not an error; only rev C and newer chips handle it, and + * only Atmel sells these chips. + */ + + tmp = spi_read_write(spi, (u8 *)&code, 4); + if (tmp < 0) { + DEBUG(MTD_DEBUG_LEVEL0, "%s: error %d reading JEDEC ID\n", + dev_name(&spi->dev), tmp); + return NULL; + } + + jedec = code & 0xFFFFFF; + + for (tmp = 0, info = dataflash_data; + tmp < ARRAY_SIZE(dataflash_data); tmp++, info++) { + if (info->jedec_id == jedec) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: OTP, sector protect%s\n", + dev_name(&spi->dev), (info->flags & SUP_POW2PS) + ? ", binary pagesize" : ""); + if (info->flags & SUP_POW2PS) { + status = dataflash_status(spi); + if (status < 0) { + DEBUG(MTD_DEBUG_LEVEL1, + "%s: status error %d\n", + dev_name(&spi->dev), status); + return ERR_PTR(status); + } + if (status & 0x1) { + if (info->flags & IS_POW2PS) + return info; + } else { + if (!(info->flags & IS_POW2PS)) + return info; + } + } + } + } + + /* + * Treat other chips as errors ... we won't know the right page + * size (it might be binary) even when we can tell which density + * class is involved (legacy chip id scheme). + */ + dev_warn(&spi->dev, "JEDEC id %06x not handled\n", jedec); + return ERR_PTR(-ENODEV); +} + +/* + * Detect and initialize DataFlash device, using JEDEC IDs on newer chips + * or else the ID code embedded in the status bits: + * + * Device Density ID code #Pages PageSize Offset + * AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9 + * AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1024 264 9 + * AT45DB041B 4Mbit (512K) xx0111xx (0x1c) 2048 264 9 + * AT45DB081B 8Mbit (1M) xx1001xx (0x24) 4096 264 9 + * AT45DB0161B 16Mbit (2M) xx1011xx (0x2c) 4096 528 10 + * AT45DB0321B 32Mbit (4M) xx1101xx (0x34) 8192 528 10 + * AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11 + * AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11 + */ +static int __devinit dataflash_probe(struct spi_device *spi) +{ + int status; + struct flash_info *info; + + /* + * Try to detect dataflash by JEDEC ID. + * If it succeeds we know we have either a C or D part. + * D will support power of 2 pagesize option. + * Both support the security register, though with different + * write procedures. + */ + info = jedec_probe(spi); + if (IS_ERR(info)) + return PTR_ERR(info); + if (info != NULL) + return add_dataflash_otp(spi, info->name, info->nr_pages, + info->pagesize, info->pageoffset, + (info->flags & SUP_POW2PS) ? 'd' : + 'c'); + + /* + * Older chips support only legacy commands, identifing + * capacity using bits in the status byte. + */ + status = dataflash_status(spi); + if (status <= 0 || status == 0xff) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: status error %d\n", + dev_name(&spi->dev), status); + if (status == 0 || status == 0xff) + status = -ENODEV; + return status; + } + + /* if there's a device there, assume it's dataflash. + * board setup should have set spi->max_speed_max to + * match f(car) for continuous reads, mode 0 or 3. + */ + switch (status & 0x3c) { + case 0x0c: /* 0 0 1 1 x x */ + status = add_dataflash(spi, "AT45DB011B", 512, 264, 9); + break; + case 0x14: /* 0 1 0 1 x x */ + status = add_dataflash(spi, "AT45DB021B", 1024, 264, 9); + break; + case 0x1c: /* 0 1 1 1 x x */ + status = add_dataflash(spi, "AT45DB041x", 2048, 264, 9); + break; + case 0x24: /* 1 0 0 1 x x */ + status = add_dataflash(spi, "AT45DB081B", 4096, 264, 9); + break; + case 0x2c: /* 1 0 1 1 x x */ + status = add_dataflash(spi, "AT45DB161x", 4096, 528, 10); + break; + case 0x34: /* 1 1 0 1 x x */ + status = add_dataflash(spi, "AT45DB321x", 8192, 528, 10); + break; + case 0x38: /* 1 1 1 x x x */ + case 0x3c: + status = add_dataflash(spi, "AT45DB642x", 8192, 1056, 11); + break; + /* obsolete AT45DB1282 not (yet?) supported */ + default: + DEBUG(MTD_DEBUG_LEVEL1, "%s: unsupported device (%x)\n", + dev_name(&spi->dev), status & 0x3c); + status = -ENODEV; + } + + if (status < 0) + DEBUG(MTD_DEBUG_LEVEL1, "%s: add_dataflash --> %d\n", + dev_name(&spi->dev), status); + + return status; +} + +static int __devexit dataflash_remove(struct spi_device *spi) +{ + struct dataflash *flash = dev_get_drvdata(&spi->dev); + int status; + + DEBUG(MTD_DEBUG_LEVEL1, "%s: remove\n", dev_name(&spi->dev)); + + if (mtd_has_partitions() && flash->partitioned) + status = del_mtd_partitions(&flash->mtd); + else + status = del_mtd_device(&flash->mtd); + if (status == 0) + kfree(flash); + return status; +} + +static struct spi_driver dataflash_driver = { + .driver = { + .name = "mxc_dataflash", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = dataflash_probe, + .remove = __devexit_p(dataflash_remove), + + /* FIXME: investigate suspend and resume... */ +}; + +static int __init dataflash_init(void) +{ + return spi_register_driver(&dataflash_driver); +} + +module_init(dataflash_init); + +static void __exit dataflash_exit(void) +{ + spi_unregister_driver(&dataflash_driver); +} + +module_exit(dataflash_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MTD DataFlash driver"); diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 7a58bd5522fd..c90fcb79af03 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -546,4 +546,14 @@ config MTD_VMU To build this as a module select M here, the module will be called vmu-flash. +config MTD_MXC + bool "Map driver for Freescale MXC boards" + depends on MTD && ARCH_MXC + default y + select MTD_CFI + select MTD_PARTITIONS + help + This enables access to the flash chips on Freescale MXC based + platforms. If you have such a board, say 'Y'. + endmenu diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile index 5beb0662d724..01bf210825c9 100644 --- a/drivers/mtd/maps/Makefile +++ b/drivers/mtd/maps/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_MTD_INTEL_VR_NOR) += intel_vr_nor.o obj-$(CONFIG_MTD_BFIN_ASYNC) += bfin-async-flash.o obj-$(CONFIG_MTD_RBTX4939) += rbtx4939-flash.o obj-$(CONFIG_MTD_VMU) += vmu-flash.o +obj-$(CONFIG_MTD_MXC) += mxc_nor.o diff --git a/drivers/mtd/maps/mxc_nor.c b/drivers/mtd/maps/mxc_nor.c new file mode 100644 index 000000000000..69b55bcac847 --- /dev/null +++ b/drivers/mtd/maps/mxc_nor.c @@ -0,0 +1,184 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + * (c) 2005 MontaVista Software, Inc. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <linux/clocksource.h> +#include <asm/mach-types.h> +#include <asm/mach/flash.h> + +#define DVR_VER "2.0" + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +struct clocksource *mtd_xip_clksrc; + +struct mxcflash_info { + struct mtd_partition *parts; + struct mtd_info *mtd; + struct map_info map; +}; + +/*! + * @defgroup NOR_MTD NOR Flash MTD Driver + */ + +/*! + * @file mxc_nor.c + * + * @brief This file contains the MTD Mapping information on the MXC. + * + * @ingroup NOR_MTD + */ + +static int __devinit mxcflash_probe(struct platform_device *pdev) +{ + int err, nr_parts = 0; + struct mxcflash_info *info; + struct flash_platform_data *flash = pdev->dev.platform_data; + struct resource *res = pdev->resource; + unsigned long size = res->end - res->start + 1; + + info = kzalloc(sizeof(struct mxcflash_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (!request_mem_region(res->start, size, "flash")) { + err = -EBUSY; + goto out_free_info; + } + info->map.virt = ioremap(res->start, size); + if (!info->map.virt) { + err = -ENOMEM; + goto out_release_mem_region; + } + info->map.name = dev_name(&pdev->dev); + info->map.phys = res->start; + info->map.size = size; + info->map.bankwidth = flash->width; + + mtd_xip_clksrc = clocksource_get_next(); + + simple_map_init(&info->map); + info->mtd = do_map_probe(flash->map_name, &info->map); + if (!info->mtd) { + err = -EIO; + goto out_iounmap; + } + info->mtd->owner = THIS_MODULE; + +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0); + if (nr_parts > 0) { + add_mtd_partitions(info->mtd, info->parts, nr_parts); + } else if (flash->parts) { + add_mtd_partitions(info->mtd, flash->parts, flash->nr_parts); + } else +#endif + { + printk(KERN_NOTICE "MXC flash: no partition info " + "available, registering whole flash\n"); + add_mtd_device(info->mtd); + } + + platform_set_drvdata(pdev, info); + return 0; + + out_iounmap: + iounmap(info->map.virt); + out_release_mem_region: + release_mem_region(res->start, size); + out_free_info: + kfree(info); + + return err; +} + +static int __devexit mxcflash_remove(struct platform_device *pdev) +{ + + struct mxcflash_info *info = platform_get_drvdata(pdev); + struct flash_platform_data *flash = pdev->dev.platform_data; + + platform_set_drvdata(pdev, NULL); + + if (info) { + if (info->parts) { + del_mtd_partitions(info->mtd); + kfree(info->parts); + } else if (flash->parts) + del_mtd_partitions(info->mtd); + else + del_mtd_device(info->mtd); + + map_destroy(info->mtd); + release_mem_region(info->map.phys, info->map.size); + iounmap((void __iomem *)info->map.virt); + kfree(info); + } + return 0; +} + +static struct platform_driver mxcflash_driver = { + .driver = { + .name = "mxc_nor_flash", + }, + .probe = mxcflash_probe, + .remove = __devexit_p(mxcflash_remove), +}; + +/*! + * This is the module's entry function. It passes board specific + * config details into the MTD physmap driver which then does the + * real work for us. After this function runs, our job is done. + * + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_mtd_init(void) +{ + pr_info("MXC MTD nor Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcflash_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcflash_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * This function is the module's exit function. It's empty because the + * MTD physmap driver is doing the real work and our job was done after + * mxc_mtd_init() runs. + */ +static void __exit mxc_mtd_exit(void) +{ + platform_driver_unregister(&mxcflash_driver); +} + +module_init(mxc_mtd_init); +module_exit(mxc_mtd_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MTD map and partitions for Freescale MXC boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index ce96c091f01b..9653aa5e8628 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -378,6 +378,102 @@ config MTD_NAND_NANDSIM The simulator may simulate various NAND flash chips for the MTD nand layer. +config MTD_NAND_IMX_NFC + tristate "i.MX NAND Flash Controller driver" + depends on MTD_NAND + help + Enables the i.MX NAND Flash controller driver. + +config MTD_NAND_MXC + tristate "MXC NAND support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V1 + help + This enables the driver for the NAND flash controller on the + MXC processors. + +config MTD_NAND_MXC_V2 + tristate "MXC NAND Version 2 support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V2 + help + This enables the driver for the version 2 of NAND flash controller + on the MXC processors. + +config MTD_NAND_MXC_V3 + tristate "MXC NAND Version 3 support" + depends on MTD_NAND && ARCH_MXC_HAS_NFC_V3 + help + This enables the driver for the version 3 of NAND flash controller + on the MXC processors. + +config MTD_NAND_MXC_SWECC + bool "Software ECC support " + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2 || MTD_NAND_MXC_V3 + help + This enables the support for Software ECC handling. By + default MXC NAND controller Hardware ECC is supported. + + +config MTD_NAND_MXC_FORCE_CE + bool "NAND chip select operation support" + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2|| MTD_NAND_MXC_V3 + help + This enables the NAND chip select by using CE control line. By + default CE operation is disabled. + +config MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + bool "ECC correction in S/W" + depends on MTD_NAND_MXC + help + This enables the Option2 NFC ECC correction in software. By + default Option 1 is selected. Enable if you need option2 ECC correction. + +config MXC_NAND_LOW_LEVEL_ERASE + bool "Low level NAND erase" + depends on MTD_NAND_MXC || MTD_NAND_MXC_V2 || MTD_NAND_MXC_V3 + help + This enables the erase of whole NAND flash. By + default low level erase operation is disabled. + +config MTD_NAND_GPMI_LBA + tristate "GPMI LBA NAND driver" + depends on MTD_NAND && ARCH_STMP3XXX + help + Enables support of LBA devices on GPMI on 37xx/378x SigmaTel + boards + +config MTD_NAND_GPMI + tristate "GPMI NAND driver" + depends on MTD_NAND && ARCH_STMP3XXX && !MTD_NAND_GPMI_LBA + help + Enables support of NAND devices on GPMI on 37xx/378x SigmaTel + boards + +config MTD_NAND_GPMI_SYSFS_ENTRIES + bool "Create /sys entries for GPMI device" + depends on MTD_NAND_GPMI + help + Check this to enable /sys entries for GPMI devices + +config MTD_NAND_GPMI_BCH + bool "Enable BCH HWECC" + depends on MTD_NAND_GPMI + depends on ARCH_STMP378X + default y + help + Check this to enable /sys entries for GPMI devices + +config MTD_NAND_GPMI_TA1 + bool "Support for TA1 NCB format (Hamming code 22,16)" + depends on MTD_NAND_GPMI + depends on ARCH_STMP378X + default y + +config MTD_NAND_GPMI_TA3 + bool "Support for TA3 NCB format (Hamming code 13,8)" + depends on MTD_NAND_GPMI + depends on ARCH_STMP378X + default y + config MTD_NAND_PLATFORM tristate "Support for generic platform NAND driver" depends on MTD_NAND diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index f3a786b3cff3..f18e71f4a1eb 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -37,7 +37,12 @@ obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o +obj-$(CONFIG_MTD_NAND_IMX_NFC) += imx_nfc.o obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o +obj-$(CONFIG_MTD_NAND_MXC_V2) += mxc_nd2.o +obj-$(CONFIG_MTD_NAND_MXC_V3) += mxc_nd2.o +obj-$(CONFIG_MTD_NAND_GPMI) += gpmi/ +obj-$(CONFIG_MTD_NAND_GPMI_LBA) += lba/ obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o diff --git a/drivers/mtd/nand/gpmi/Makefile b/drivers/mtd/nand/gpmi/Makefile new file mode 100644 index 000000000000..4a4b50d294fa --- /dev/null +++ b/drivers/mtd/nand/gpmi/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_MTD_NAND_GPMI) += gpmi.o +gpmi-objs += gpmi-base.o gpmi-bbt.o +gpmi-objs += gpmi-hamming-22-16.o +gpmi-objs += gpmi-hamming-13-8.o +gpmi-objs += gpmi-bch.o +gpmi-objs += gpmi-ecc8.o diff --git a/drivers/mtd/nand/gpmi/gpmi-base.c b/drivers/mtd/nand/gpmi/gpmi-base.c new file mode 100644 index 000000000000..8bfb1c677a4e --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-base.c @@ -0,0 +1,1976 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/mtd/concat.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <asm/div64.h> + +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/regs-ecc8.h> +#include <mach/regs-gpmi.h> +#include <mach/dma.h> +#include "gpmi.h" + +static int debug; +static int copies; +static int map_buffers = true; +static int ff_writes; +static int raw_mode; +static int add_mtd_entire; +static int add_mtd_chip; +static int ignorebad; +static int max_chips = 4; +static long clk = -1; +static int bch /* = 0 */ ; + +static int gpmi_nand_init_hw(struct platform_device *pdev, int request_pins); +static void gpmi_nand_release_hw(struct platform_device *pdev); +static int gpmi_dma_exchange(struct gpmi_nand_data *g, + struct stmp3xxx_dma_descriptor *dma); +static void gpmi_read_buf(struct mtd_info *mtd, uint8_t * buf, int len); + +struct gpmi_nand_timing gpmi_safe_timing = { + .address_setup = 25, + .data_setup = 80, + .data_hold = 60, + .dsample_time = 6, +}; + +/* + * define OOB placement schemes for 4k and 2k page devices + */ +static struct nand_ecclayout gpmi_oob_128 = { + .oobfree = { + { + .offset = 2, + .length = 56, + }, { + .length = 0, + }, + }, +}; + +static struct nand_ecclayout gpmi_oob_64 = { + .oobfree = { + { + .offset = 2, + .length = 16, + }, { + .length = 0, + }, + }, +}; + +static inline u32 gpmi_cycles_ceil(u32 ntime, u32 period) +{ + int k; + + k = (ntime + period - 1) / period; + if (k == 0) + k++; + return k; +} + +/** + * gpmi_timer_expiry - timer expiry handling + */ +static void gpmi_timer_expiry(unsigned long d) +{ +#ifdef CONFIG_PM + struct gpmi_nand_data *g = (struct gpmi_nand_data *)d; + + pr_debug("%s: timer expired\n", __func__); + del_timer_sync(&g->timer); + + if (g->use_count || + __raw_readl(REGS_GPMI_BASE + HW_GPMI_CTRL0) & BM_GPMI_CTRL0_RUN) { + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); + } else { + stmp3xxx_setl(BM_GPMI_CTRL0_CLKGATE, + REGS_GPMI_BASE + HW_GPMI_CTRL0); + clk_disable(g->clk); + g->self_suspended = 1; + } +#endif +} + +/** + * gpmi_self_wakeup - wakeup from self-pm light suspend + */ +static void gpmi_self_wakeup(struct gpmi_nand_data *g) +{ +#ifdef CONFIG_PM + int i = 1000; + clk_enable(g->clk); + stmp3xxx_clearl(BM_GPMI_CTRL0_CLKGATE, REGS_GPMI_BASE + HW_GPMI_CTRL0); + while (i-- + && __raw_readl(REGS_GPMI_BASE + + HW_GPMI_CTRL0) & BM_GPMI_CTRL0_CLKGATE) ; + + pr_debug("%s: i stopped at %d, data %p\n", __func__, i, g); + g->self_suspended = 0; + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); +#endif +} + +/** + * gpmi_set_timings - set GPMI timings + * @pdev: pointer to GPMI platform device + * @tm: pointer to structure &gpmi_nand_timing with new timings + * + * During initialization, GPMI uses safe sub-optimal timings, which + * can be changed after reading boot control blocks + */ +void gpmi_set_timings(struct platform_device *pdev, struct gpmi_nand_timing *tm) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + u32 period_ns = 1000000 / clk_get_rate(g->clk) + 1; + u32 address_cycles, data_setup_cycles; + u32 data_hold_cycles, data_sample_cycles; + u32 busy_timeout; + u32 t0; + + if (g->self_suspended) + gpmi_self_wakeup(g); + g->use_count++; + + g->timing = *tm; + + address_cycles = gpmi_cycles_ceil(tm->address_setup, period_ns); + data_setup_cycles = gpmi_cycles_ceil(tm->data_setup, period_ns); + data_hold_cycles = gpmi_cycles_ceil(tm->data_hold, period_ns); + data_sample_cycles = gpmi_cycles_ceil(tm->dsample_time + period_ns / 4, + period_ns / 2); + busy_timeout = gpmi_cycles_ceil(10000000 / 4096, period_ns); + + dev_dbg(&pdev->dev, + "%s: ADDR %u, DSETUP %u, DH %u, DSAMPLE %u, BTO %u\n", + __func__, + address_cycles, data_setup_cycles, data_hold_cycles, + data_sample_cycles, busy_timeout); + + t0 = BF(address_cycles, GPMI_TIMING0_ADDRESS_SETUP) | + BF(data_setup_cycles, GPMI_TIMING0_DATA_SETUP) | + BF(data_hold_cycles, GPMI_TIMING0_DATA_HOLD); + __raw_writel(t0, REGS_GPMI_BASE + HW_GPMI_TIMING0); + + __raw_writel(BF(busy_timeout, GPMI_TIMING1_DEVICE_BUSY_TIMEOUT), + REGS_GPMI_BASE + HW_GPMI_TIMING1); + +#ifdef CONFIG_ARCH_STMP378X + stmp3xxx_clearl(BM_GPMI_CTRL1_RDN_DELAY, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + stmp3xxx_setl(BF(data_sample_cycles, GPMI_CTRL1_RDN_DELAY), + REGS_GPMI_BASE + HW_GPMI_CTRL1); +#else + stmp3xxx_clearl(BM_GPMI_CTRL1_DSAMPLE_TIME, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + stmp3xxx_setl(BF(data_sample_cycles, GPMI_CTRL1_DSAMPLE_TIME), + REGS_GPMI_BASE + HW_GPMI_CTRL1); +#endif + + g->use_count--; +} + +static inline u32 bch_mode(void) +{ + u32 c1 = 0; + +#ifdef CONFIG_MTD_NAND_GPMI_BCH + if (bch) + c1 |= BM_GPMI_CTRL1_BCH_MODE; +#endif + return c1; +} + +/** + * gpmi_nand_init_hw - initialize the hardware + * @pdev: pointer to platform device + * + * Initialize GPMI hardware and set default (safe) timings for NAND access. + * Returns error code or 0 on success + */ +static int gpmi_nand_init_hw(struct platform_device *pdev, int request_pins) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + int err = 0; + + g->clk = clk_get(NULL, "gpmi"); + if (IS_ERR(g->clk)) { + err = PTR_ERR(g->clk); + dev_err(&pdev->dev, "cannot set failsafe clockrate\n"); + goto out; + } + clk_enable(g->clk); + if (clk <= 0) + clk = 24000; /* safe setting, some chips do not work on + speeds >= 24kHz */ + clk_set_rate(g->clk, clk); + + clk = clk_get_rate(g->clk); + + if (request_pins) + gpd->pinmux(1); + + stmp3xxx_reset_block(HW_GPMI_CTRL0 + REGS_GPMI_BASE, 1); + + /* this CLEARS reset, despite of its name */ + stmp3xxx_setl(BM_GPMI_CTRL1_DEV_RESET, REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* IRQ polarity */ + stmp3xxx_setl(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* ...and ECC module */ + stmp3xxx_setl(bch_mode(), REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* choose NAND mode (1 means ATA, 0 - NAND */ + stmp3xxx_clearl(BM_GPMI_CTRL1_GPMI_MODE, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + +out: + return err; +} + +/** + * gpmi_nand_release_hw - free the hardware + * @pdev: pointer to platform device + * + * In opposite to gpmi_nand_init_hw, release all acquired resources + */ +static void gpmi_nand_release_hw(struct platform_device *pdev) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + + stmp3xxx_setl(BM_GPMI_CTRL0_SFTRST, REGS_GPMI_BASE + HW_GPMI_CTRL0); + + clk_disable(g->clk); + clk_put(g->clk); + gpd->pinmux(0); +} + +static int gpmi_dma_is_error(struct gpmi_nand_data *g) +{ + /* u32 n = __raw_readl(g->dma_ch); */ + + /* CURrent DMA command */ + u32 c = __raw_readl(REGS_APBH_BASE + + HW_APBH_CHn_NXTCMDAR(g->cchip->dma_ch)); + + if (c == g->cchip->error.handle) { + pr_debug("%s: dma chain has reached error terminator\n", + __func__); + return -EIO; + } + return 0; +} + +/** + * gpmi_dma_exchange - run DMA to exchange with NAND chip + * + * @g: structure associated with NAND chip + * + * Run DMA and wait for completion + */ +static int gpmi_dma_exchange(struct gpmi_nand_data *g, + struct stmp3xxx_dma_descriptor *d) +{ + struct platform_device *pdev = g->dev; + unsigned long timeout; + int err; + + if (g->self_suspended) + gpmi_self_wakeup(g); + g->use_count++; + + if (!g->regulator) { + g->regulator = regulator_get(&pdev->dev, "mmc_ssp-2"); + if (g->regulator && !IS_ERR(g->regulator)) + regulator_set_mode(g->regulator, REGULATOR_MODE_NORMAL); + else + g->regulator = NULL; + } + + if (g->regulator) + regulator_set_current_limit(g->regulator, g->reg_uA, g->reg_uA); + + init_completion(&g->done); + stmp3xxx_dma_enable_interrupt(g->cchip->dma_ch); + stmp3xxx_dma_go(g->cchip->dma_ch, d ? d : g->cchip->d, 1); + + timeout = wait_for_completion_timeout(&g->done, msecs_to_jiffies(1000)); + err = (timeout <= 0) ? -ETIMEDOUT : gpmi_dma_is_error(g); + + if (err) + printk(KERN_ERR "%s: error %d, CS = %d, channel %d\n", + __func__, err, g->cchip->cs, g->cchip->dma_ch); + + stmp3xxx_dma_reset_channel(g->cchip->dma_ch); + stmp3xxx_dma_clear_interrupt(g->cchip->dma_ch); + + if (g->regulator) + regulator_set_current_limit(g->regulator, 0, 0); + + mod_timer(&g->timer, jiffies + 4 * HZ); + g->use_count--; + + return err; +} + +/** + * gpmi_ecc_read_page - replacement for nand_read_page + * + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + */ +static int gpmi_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t * buf) +{ + struct gpmi_nand_data *g = chip->priv; + struct mtd_ecc_stats stats; + dma_addr_t bufphys, oobphys; + int err; + + bufphys = oobphys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + bufphys = dma_map_single(&g->dev->dev, buf, + mtd->writesize, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, bufphys)) + bufphys = g->data_buffer_handle; + + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) + oobphys = g->oob_buffer_handle; + + /* ECC read */ + (void)g->hc->read(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, bufphys, oobphys); + + err = gpmi_dma_exchange(g, NULL); + + g->hc->stat(g->hc, g->selected_chip, &stats); + + if (stats.failed || stats.corrected) { + + pr_debug("%s: ECC failed=%d, corrected=%d\n", + __func__, stats.failed, stats.corrected); + + g->mtd.ecc_stats.failed += stats.failed; + g->mtd.ecc_stats.corrected += stats.corrected; + } + + if (!dma_mapping_error(&g->dev->dev, oobphys)) { + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, + mtd->oobsize, DMA_FROM_DEVICE); + else { + memcpy(chip->oob_poi, g->oob_buffer, mtd->oobsize); + copies++; + } + } + + if (!dma_mapping_error(&g->dev->dev, bufphys)) { + if (bufphys != g->data_buffer_handle) + dma_unmap_single(&g->dev->dev, bufphys, + mtd->writesize, DMA_FROM_DEVICE); + else { + memcpy(buf, g->data_buffer, mtd->writesize); + copies++; + } + } + + /* always fill the (possible ECC bytes with FF) */ + memset(chip->oob_poi + g->oob_free, 0xff, mtd->oobsize - g->oob_free); + + return err; +} + +static inline int is_ff(const u8 * buffer, size_t size) +{ + while (size--) { + if (*buffer++ != 0xff) + return 0; + } + return 1; +} + +/** + * gpmi_ecc_write_page - replacement for nand_write_page + * + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + */ +static void gpmi_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *chip, const uint8_t * buf) +{ + struct gpmi_nand_data *g = chip->priv; + dma_addr_t bufphys, oobphys; + int err; + + /* if we can't map it, copy it */ + bufphys = oobphys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + bufphys = dma_map_single(&g->dev->dev, + (void *)buf, mtd->writesize, + DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, bufphys)) { + bufphys = g->data_buffer_handle; + memcpy(g->data_buffer, buf, mtd->writesize); + copies++; + } + + /* if OOB is all FF, leave it as such */ + if (!is_ff(chip->oob_poi, mtd->oobsize)) { + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) { + oobphys = g->oob_buffer_handle; + memcpy(g->oob_buffer, chip->oob_poi, mtd->oobsize); + copies++; + } + } else + ff_writes++; + + /* call ECC */ + g->hc->write(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, bufphys, oobphys); + + err = gpmi_dma_exchange(g, NULL); + if (err < 0) + printk(KERN_ERR "%s: dma error\n", __func__); + + if (!dma_mapping_error(&g->dev->dev, oobphys)) { + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize, + DMA_TO_DEVICE); + } + + if (bufphys != g->data_buffer_handle) + dma_unmap_single(&g->dev->dev, bufphys, mtd->writesize, + DMA_TO_DEVICE); +} + +/** + * gpmi_write_buf - replacement for nand_write_buf + * + * @mtd: MTD device + * @buf: data buffer + * @len: length of the data buffer + */ +static void gpmi_write_buf(struct mtd_info *mtd, const uint8_t * buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain = g->cchip->d; + dma_addr_t phys; + int err; + + BUG_ON(len > mtd->writesize); + + phys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + phys = dma_map_single(&g->dev->dev, + (void *)buf, len, DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, phys)) { + phys = g->write_buffer_handle; + memcpy(g->write_buffer, buf, len); + copies++; + } + + /* write plain data */ + chain->command->cmd = + BF(len, APBH_CHn_CMD_XFER_COUNT) | + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WRITE, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(g->selected_chip, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(len, GPMI_CTRL0_XFER_COUNT); + + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = phys; + + err = gpmi_dma_exchange(g, NULL); + if (err) + printk(KERN_ERR "%s: dma error\n", __func__); + + if (phys != g->write_buffer_handle) + dma_unmap_single(&g->dev->dev, phys, len, DMA_TO_DEVICE); + + if (debug >= 2) + print_hex_dump_bytes("WBUF ", DUMP_PREFIX_OFFSET, buf, len); +} + +/** + * gpmi_read_buf - replacement for nand_read_buf + * + * @mtd: MTD device + * @buf: pointer to the buffer + * @len: size of the buffer + */ +static void gpmi_read_buf(struct mtd_info *mtd, uint8_t * buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain; + dma_addr_t phys; + int err; + + phys = ~0; + + if (map_buffers && virt_addr_valid(buf)) + phys = dma_map_single(&g->dev->dev, buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, phys)) + phys = g->read_buffer_handle; + + chain = g->cchip->d; + + /* read data */ + chain->command->cmd = + BF(len, APBH_CHn_CMD_XFER_COUNT) | + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_WRITE, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__READ, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(g->selected_chip, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(len, GPMI_CTRL0_XFER_COUNT); + chain->command->buf_ptr = phys; + chain++; + + chain->command->cmd = + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BM_GPMI_CTRL0_LOCK_CS | BF(g->selected_chip, GPMI_CTRL0_CS); + chain->command->pio_words[1] = + chain->command->pio_words[2] = chain->command->pio_words[3] = 0; + chain->command->buf_ptr = 0; + + err = gpmi_dma_exchange(g, NULL); + if (err) + printk(KERN_ERR "%s: dma error\n", __func__); + + if (phys != g->read_buffer_handle) + dma_unmap_single(&g->dev->dev, phys, len, DMA_FROM_DEVICE); + else { + memcpy(buf, g->read_buffer, len); + copies++; + } + + if (debug >= 2) + print_hex_dump_bytes("RBUF ", DUMP_PREFIX_OFFSET, buf, len); +} + +/** + * gpmi_read_byte - replacement for nand_read_byte + * @mtd: MTD device + * + * Uses gpmi_read_buf to read 1 byte from device + */ +static u8 gpmi_read_byte(struct mtd_info *mtd) +{ + u8 b; + + gpmi_read_buf(mtd, (uint8_t *) & b, 1); + return b; +} + +/** + * gpmi_read_word - replacement for nand_read_word + * @mtd: MTD device + * + * Uses gpmi_read_buf to read 2 bytes from device + */ +static u16 gpmi_read_word(struct mtd_info *mtd) +{ + u16 w; + + gpmi_read_buf(mtd, (uint8_t *) & w, sizeof(u16)); + return w; +} + +/** + * gpmi_erase - erase a block and update BBT table + * + * @mtd: MTD device + * @instr: erase instruction + */ +int gpmi_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + int rc; + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct gpmi_nand_data *data = platform_get_drvdata(g->dev); + + if (g->self_suspended) + gpmi_self_wakeup(data); + g->use_count++; + + rc = nand_erase_nand(mtd, instr, 0); + + if (rc == -EIO) /* block cannot be erased */ + gpmi_block_mark_as(chip, + (instr->addr >> chip->bbt_erase_shift), + 0x01); + + mod_timer(&g->timer, jiffies + 4 * HZ); + g->use_count--; + return rc; +} + +/** + * gpmi_dev_ready - poll the RDY pin + * + * @mtd: MTD device + */ +static int gpmi_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain = g->cchip->d; + int ret; + + /* wait for ready */ + chain->command->cmd = + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, + GPMI_CTRL0_ADDRESS) | BF(g->selected_chip, GPMI_CTRL0_CS); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = 0; + chain++; + + ret = gpmi_dma_exchange(g, NULL); + if (ret != 0) + printk(KERN_ERR "gpmi: gpmi_dma_exchange() timeout!\n"); + return ret == 0; +} + +/** + * gpmi_hwcontrol - set command/address byte to the device + * + * @mtd: MTD device + * @cmd: command byte + * @ctrl: control flags + */ +static void gpmi_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + struct stmp3xxx_dma_descriptor *chain = g->cchip->d; + int ret; + + if ((ctrl & (NAND_ALE | NAND_CLE))) { + if (cmd != NAND_CMD_NONE) + g->cmd_buffer[g->cmd_buffer_sz++] = cmd; + return; + } + + if (g->cmd_buffer_sz == 0) + return; + + /* output command */ + chain->command->cmd = + BF(g->cmd_buffer_sz, APBH_CHn_CMD_XFER_COUNT) | + BF(3, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WRITE, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(g->selected_chip, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_CLE, GPMI_CTRL0_ADDRESS) | + BF(g->cmd_buffer_sz, GPMI_CTRL0_XFER_COUNT); + if (g->cmd_buffer_sz > 0) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->buf_ptr = g->cmd_buffer_handle; + chain++; + + /* emit IRQ */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND) | + BM_APBH_CHn_CMD_WAIT4ENDCMD; + chain++; + + /* last in chain get the irq bit set */ + chain[-1].command->cmd |= BM_APBH_CHn_CMD_IRQONCMPLT; + + if (debug >= 3) + print_hex_dump(KERN_INFO, "CMD ", DUMP_PREFIX_OFFSET, 16, 1, + g->cmd_buffer, g->cmd_buffer_sz, 1); + + ret = gpmi_dma_exchange(g, NULL); + if (ret != 0) { + printk(KERN_ERR "%s: chip %d, dma error %d on the command:\n", + __func__, g->selected_chip, ret); + print_hex_dump(KERN_INFO, "CMD ", DUMP_PREFIX_OFFSET, 16, 1, + g->cmd_buffer, g->cmd_buffer_sz, 1); + } + + gpmi_dev_ready(mtd); + + g->cmd_buffer_sz = 0; +} + +/** + * gpmi_alloc_buffers - allocate DMA buffers for one chip + * + * @pdev: GPMI platform device + * @g: pointer to structure associated with NAND chip + * + * Allocate buffer using dma_alloc_coherent + */ +static int gpmi_alloc_buffers(struct platform_device *pdev, + struct gpmi_nand_data *g) +{ + g->cmd_buffer = dma_alloc_coherent(&pdev->dev, + g->cmd_buffer_size, + &g->cmd_buffer_handle, GFP_DMA); + if (!g->cmd_buffer) + goto out1; + + g->write_buffer = dma_alloc_coherent(&pdev->dev, + g->write_buffer_size * 2, + &g->write_buffer_handle, GFP_DMA); + if (!g->write_buffer) + goto out2; + + g->read_buffer = g->write_buffer + g->write_buffer_size; + g->read_buffer_handle = g->write_buffer_handle + g->write_buffer_size; + + g->data_buffer = dma_alloc_coherent(&pdev->dev, + g->data_buffer_size, + &g->data_buffer_handle, GFP_DMA); + if (!g->data_buffer) + goto out3; + + g->oob_buffer = dma_alloc_coherent(&pdev->dev, + g->oob_buffer_size, + &g->oob_buffer_handle, GFP_DMA); + if (!g->oob_buffer) + goto out4; + + g->verify_buffer = kzalloc(2 * (g->data_buffer_size + + g->oob_buffer_size), GFP_KERNEL); + if (!g->verify_buffer) + goto out5; + + return 0; + +out5: + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); +out4: + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +out3: + dma_free_coherent(&pdev->dev, g->write_buffer_size * 2, + g->write_buffer, g->write_buffer_handle); +out2: + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); +out1: + return -ENOMEM; +} + +/** + * gpmi_free_buffers - free buffers allocated by gpmi_alloc_buffers + * + * @pdev: platform device + * @g: pointer to structure associated with NAND chip + * + * Deallocate buffers on exit + */ +static void gpmi_free_buffers(struct platform_device *pdev, + struct gpmi_nand_data *g) +{ + kfree(g->verify_buffer); + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); + dma_free_coherent(&pdev->dev, g->write_buffer_size * 2, + g->write_buffer, g->write_buffer_handle); + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +} + +/* only used in SW-ECC or NO-ECC cases */ +static int gpmi_verify_buf(struct mtd_info *mtd, const uint8_t * buf, int len) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + + chip->read_buf(mtd, g->verify_buffer, len); + + if (memcmp(buf, g->verify_buffer, len)) + return -EFAULT; + + return 0; +} + +/** + * gpmi_ecc_read_oob - replacement for nand_read_oob + * + * @mtd: MTD device + * @chip: mtd->priv + * @page: page address + * @sndcmd: flag indicates that command should be sent + */ +int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page, int sndcmd) +{ + struct gpmi_nand_data *g = chip->priv; + loff_t oob_offset; + struct mtd_ecc_stats stats; + dma_addr_t oobphys; + int ecc; + int ret; + + ecc = g->raw_oob_mode == 0 && raw_mode == 0; + + if (sndcmd) { + oob_offset = mtd->writesize; + if (likely(ecc)) + oob_offset += chip->ecc.bytes * chip->ecc.steps; + chip->cmdfunc(mtd, NAND_CMD_READ0, oob_offset, page); + sndcmd = 0; + } + + if (unlikely(!ecc)) { + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return 1; + } + + oobphys = ~0; + + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_FROM_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) + oobphys = g->oob_buffer_handle; + + /* ECC read */ + (void)g->hc->read(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, ~0, oobphys); + + ret = gpmi_dma_exchange(g, NULL); + + g->hc->stat(g->hc, g->selected_chip, &stats); + + if (stats.failed || stats.corrected) { + + printk(KERN_DEBUG "%s: ECC failed=%d, corrected=%d\n", + __func__, stats.failed, stats.corrected); + + g->mtd.ecc_stats.failed += stats.failed; + g->mtd.ecc_stats.corrected += stats.corrected; + } + + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize, + DMA_FROM_DEVICE); + else { + memcpy(chip->oob_poi, g->oob_buffer, mtd->oobsize); + copies++; + } + + /* fill rest with ff */ + memset(chip->oob_poi + g->oob_free, 0xff, mtd->oobsize - g->oob_free); + + return ret ? ret : 1; +} + +/** + * gpmi_ecc_write_oob - replacement for nand_write_oob + * + * @mtd: MTD device + * @chip: mtd->priv + * @page: page address + */ +static int gpmi_ecc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page) +{ + int status = 0; + struct gpmi_nand_data *g = chip->priv; + loff_t oob_offset; + dma_addr_t oobphys; + int ecc; + int err = 0; + + /* if OOB is all FF, leave it as such */ + if (is_ff(chip->oob_poi, mtd->oobsize)) { + ff_writes++; + + pr_debug("%s: Skipping an empty page 0x%x (0x%x)\n", + __func__, page, page << chip->page_shift); + return 0; + } + + ecc = g->raw_oob_mode == 0 && raw_mode == 0; + + /* Send command to start input data */ + oob_offset = mtd->writesize; + if (likely(ecc)) { + oob_offset += chip->ecc.bytes * chip->ecc.steps; + memset(chip->oob_poi + g->oob_free, 0xff, + mtd->oobsize - g->oob_free); + } + chip->cmdfunc(mtd, NAND_CMD_SEQIN, oob_offset, page); + + /* call ECC */ + if (likely(ecc)) { + + oobphys = ~0; + + if (map_buffers) + oobphys = dma_map_single(&g->dev->dev, chip->oob_poi, + mtd->oobsize, DMA_TO_DEVICE); + if (dma_mapping_error(&g->dev->dev, oobphys)) { + oobphys = g->oob_buffer_handle; + memcpy(g->oob_buffer, chip->oob_poi, mtd->oobsize); + copies++; + } + + g->hc->write(g->hc, g->selected_chip, g->cchip->d, + g->cchip->error.handle, ~0, oobphys); + + err = gpmi_dma_exchange(g, NULL); + } else + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); + + /* Send command to program the OOB data */ + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + /* ..and wait for result */ + status = chip->waitfunc(mtd, chip); + + if (likely(ecc)) { + if (oobphys != g->oob_buffer_handle) + dma_unmap_single(&g->dev->dev, oobphys, mtd->oobsize, + DMA_TO_DEVICE); + } + + if (status & NAND_STATUS_FAIL) { + pr_debug("%s: NAND_STATUS_FAIL\n", __func__); + return -EIO; + } + + return err; +} + +/** + * gpmi_irq - IRQ handler + * + * @irq: irq no + * @context: IRQ context, pointer to gpmi_nand_data + */ +static irqreturn_t gpmi_irq(int irq, void *context) +{ + struct gpmi_nand_data *g = context; + + if (stmp3xxx_dma_is_interrupt(g->cchip->dma_ch)) { + stmp3xxx_dma_clear_interrupt(g->cchip->dma_ch); + complete(&g->done); + } + stmp3xxx_clearl(BM_GPMI_CTRL1_DEV_IRQ | BM_GPMI_CTRL1_TIMEOUT_IRQ, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + return IRQ_HANDLED; +} + +static void gpmi_select_chip(struct mtd_info *mtd, int chipnr) +{ + struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + + if (chipnr == g->selected_chip) + return; + + g->selected_chip = chipnr; + g->cchip = NULL; + + if (chipnr == -1) + return; + + g->cchip = g->chips + chipnr; +} + +static void gpmi_command(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + + g->saved_command(mtd, command, column, page_addr); +} + +static int gpmi_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + int ret; + + g->raw_oob_mode = ops->mode == MTD_OOB_RAW; + ret = g->saved_read_oob(mtd, from, ops); + g->raw_oob_mode = 0; + return ret; +} + +static int gpmi_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + register struct nand_chip *chip = mtd->priv; + struct gpmi_nand_data *g = chip->priv; + int ret; + + g->raw_oob_mode = ops->mode == MTD_OOB_RAW; + ret = g->saved_write_oob(mtd, to, ops); + g->raw_oob_mode = 0; + return ret; +} + +/** + * perform the needed steps between nand_scan_ident and nand_scan_tail + */ +static int gpmi_scan_middle(struct gpmi_nand_data *g) +{ + int oobsize = 0; + + /* Limit to 2G size due to Kernel larger 4G space support */ + if (g->mtd.size == 0) { + g->mtd.size = 1 << 31; + g->chip.chipsize = do_div(g->mtd.size, g->chip.numchips); + } + + g->ecc_oob_bytes = 9; + switch (g->mtd.writesize) { + case 2048: /* 2K page */ + g->chip.ecc.layout = &gpmi_oob_64; + g->chip.ecc.bytes = 9; + g->oob_free = 19; + g->hwecc_type_read = GPMI_ECC4_RD; + g->hwecc_type_write = GPMI_ECC4_WR; + oobsize = 64; + break; + case 4096: + g->chip.ecc.layout = &gpmi_oob_128; + g->chip.ecc.bytes = 18; + g->oob_free = 65; + g->hwecc_type_read = GPMI_ECC8_RD; + g->hwecc_type_write = GPMI_ECC8_WR; + oobsize = 218; + break; + default: + printk(KERN_ERR "Unsupported write_size %d.", g->mtd.writesize); + break; + } + + g->mtd.ecclayout = g->chip.ecc.layout; + /* sanity check */ + if (oobsize > NAND_MAX_OOBSIZE || g->mtd.writesize > NAND_MAX_PAGESIZE) { + printk(KERN_ERR "Internal error. Either page size " + "(%d) > max (%d) " + "or oob size (%d) > max(%d). Sorry.\n", + oobsize, NAND_MAX_OOBSIZE, + g->mtd.writesize, NAND_MAX_PAGESIZE); + return -ERANGE; + } + + g->saved_command = g->chip.cmdfunc; + g->chip.cmdfunc = gpmi_command; + + if (oobsize > 0) { + g->mtd.oobsize = oobsize; + /* otherwise error; oobsize should be set + in valid cases */ + g->hc = gpmi_hwecc_chip_find("ecc8"); + g->hc->setup(g->hc, 0, g->mtd.writesize, g->mtd.oobsize); + return 0; + } + + return -ENXIO; +} + +/** + * gpmi_write_page - [REPLACEABLE] write one page + * @mtd: MTD device structure + * @chip: NAND chip descriptor + * @buf: the data to write + * @page: page number to write + * @cached: cached programming + * @raw: use _raw version of write_page + */ +static int gpmi_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t * buf, int page, int cached, int raw) +{ + struct gpmi_nand_data *g = chip->priv; + int status, empty_data, empty_oob; + int oobsz; +#if defined(CONFIG_MTD_NAND_VERIFY_WRITE) + void *vbuf, *obuf; +#if 0 + void *voob, *ooob; +#endif +#endif + + oobsz = likely(g->raw_oob_mode == 0 && raw_mode == 0) ? + g->oob_free : mtd->oobsize; + + empty_data = is_ff(buf, mtd->writesize); + empty_oob = is_ff(buf, oobsz); + + if (empty_data && empty_oob) { + ff_writes++; + + pr_debug("%s: Skipping an empty page 0x%x (0x%x)\n", + __func__, page, page << chip->page_shift); + return 0; + } + + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + + if (likely(raw == 0)) + chip->ecc.write_page(mtd, chip, buf); + else + chip->ecc.write_page_raw(mtd, chip, buf); + + /* + * Cached progamming disabled for now, Not sure if its worth the + * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s) + */ + cached = 0; + + if (!cached || !(chip->options & NAND_CACHEPRG)) { + + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = chip->waitfunc(mtd, chip); + + /* + * See if operation failed and additional status checks are + * available + */ + if ((status & NAND_STATUS_FAIL) && (chip->errstat)) + status = chip->errstat(mtd, chip, FL_WRITING, status, + page); + + if (status & NAND_STATUS_FAIL) { + pr_debug("%s: NAND_STATUS_FAIL\n", __func__); + return -EIO; + } + } else { + chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1); + status = chip->waitfunc(mtd, chip); + } + +#if defined(CONFIG_MTD_NAND_VERIFY_WRITE) + if (empty_data) + return 0; + + obuf = g->verify_buffer; +#if 1 /* make vbuf aligned by mtd->writesize */ + vbuf = obuf + mtd->writesize; +#else + ooob = obuf + mtd->writesize; + vbuf = ooob + mtd->oobsize; + voob = vbuf + mtd->writesize; +#endif + + /* keep data around */ + memcpy(obuf, buf, mtd->writesize); +#if 0 + memcpy(ooob, chip->oob_poi, oobsz); +#endif + /* Send command to read back the data */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + + if (likely(raw == 0)) + chip->ecc.read_page(mtd, chip, vbuf); + else + chip->ecc.read_page_raw(mtd, chip, vbuf); + +#if 0 + memcpy(voob, chip->oob_poi, oobsz); +#endif + + if (!empty_data && memcmp(obuf, vbuf, mtd->writesize) != 0) + return -EIO; +#endif + + return 0; +} + +/** + * gpmi_read_page_raw - [Intern] read raw page data without ecc + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + */ +static int gpmi_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t * buf) +{ + chip->read_buf(mtd, buf, mtd->writesize); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return 0; +} + +/** + * gpmi_write_page_raw - [Intern] raw page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + */ +static void gpmi_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t * buf) +{ + chip->write_buf(mtd, buf, mtd->writesize); + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); +} + +static int gpmi_init_chip(struct platform_device *pdev, + struct gpmi_nand_data *g, int n, unsigned dma_ch) +{ + int err; + + g->chips[n].dma_ch = dma_ch; + g->chips[n].cs = n; + + err = stmp3xxx_dma_request(dma_ch, NULL, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "can't request DMA channel 0x%x\n", dma_ch); + goto out_all; + } + + err = stmp3xxx_dma_make_chain(dma_ch, + &g->chips[n].chain, + g->chips[n].d, ARRAY_SIZE(g->chips[n].d)); + if (err) { + dev_err(&pdev->dev, "can't setup DMA chain\n"); + goto out_all; + } + + err = stmp3xxx_dma_allocate_command(dma_ch, &g->chips[n].error); + if (err) { + dev_err(&pdev->dev, "can't setup DMA chain\n"); + goto out_all; + } + + g->chips[n].error.command->cmd = + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); +out_all: + return err; +} + +static void gpmi_deinit_chip(struct platform_device *pdev, + struct gpmi_nand_data *g, int n) +{ + int dma_ch; + + if (n < 0) { + for (n = 0; n < ARRAY_SIZE(g->chips); n++) + gpmi_deinit_chip(pdev, g, n); + return; + } + + if (g->chips[n].dma_ch <= 0) + return; + + dma_ch = g->chips[n].dma_ch; + + stmp3xxx_dma_free_command(dma_ch, &g->chips[n].error); + stmp3xxx_dma_free_chain(&g->chips[n].chain); + stmp3xxx_dma_release(dma_ch); +} + +#if 0 +static int gpmi_to_concat(struct mtd_partition *part, char **list) +{ + while (list && *list) { + if (strcmp(part->name, *list) == 0) { + pr_debug("Partition '%s' will be concatenated\n", + part->name); + return true; + } + list++; + } + pr_debug("Partition '%s' is left as-is", part->name); + return false; +} +#endif + +static void gpmi_create_partitions(struct gpmi_nand_data *g, + struct gpmi_platform_data *gpd, + uint64_t chipsize) +{ +#ifdef CONFIG_MTD_PARTITIONS + int chip, p; + char chipname[20]; + + if (g->numchips == 1) + g->masters[0] = &g->mtd; + else { + for (chip = 0; chip < g->numchips; chip++) { + memset(g->chip_partitions + chip, + 0, sizeof(g->chip_partitions[chip])); + snprintf(chipname, sizeof(chipname), + "gpmi-chip-%d", chip); + g->chip_partitions[chip].name = + kstrdup(chipname, GFP_KERNEL); + g->chip_partitions[chip].size = chipsize; + g->chip_partitions[chip].offset = chipsize * chip; + g->chip_partitions[chip].mask_flags = 0; + } + add_mtd_partitions(&g->mtd, g->chip_partitions, g->numchips); + } + g->n_concat = 0; + memset(g->concat, 0, sizeof(g->concat)); + for (chip = 0; chip < g->numchips; chip++) { + if (add_mtd_chip) { + printk(KERN_NOTICE "Adding MTD for the chip %d\n", + chip); + add_mtd_device(g->masters[chip]); + } + if (chip >= gpd->items) + continue; + add_mtd_partitions(g->masters[chip], + gpd->parts[chip].partitions, + gpd->parts[chip].nr_partitions); + } + if (g->n_concat > 0) { +#ifdef CONFIG_MTD_CONCAT + if (g->n_concat == 1) +#endif + for (p = 0; p < g->n_concat; p++) + add_mtd_device(g->concat[p]); +#ifdef CONFIG_MTD_CONCAT + if (g->n_concat > 1) { + g->concat_mtd = mtd_concat_create(g->concat, + g->n_concat, + gpd->concat_name); + if (g->concat_mtd) + add_mtd_device(g->concat_mtd); + } +#endif + } + g->custom_partitions = true; +#endif +} + +static void gpmi_delete_partitions(struct gpmi_nand_data *g) +{ +#ifdef CONFIG_MTD_PARTITIONS + int chip, p; + + if (!g->custom_partitions) + return; +#ifdef CONFIG_MTD_CONCAT + if (g->concat_mtd) + del_mtd_device(g->concat_mtd); + if (g->n_concat == 1) +#endif + for (p = 0; p < g->n_concat; p++) + del_mtd_device(g->concat[p]); + + for (chip = 0; chip < g->numchips; chip++) { + del_mtd_partitions(g->masters[chip]); + if (add_mtd_chip) + del_mtd_device(g->masters[chip]); + kfree(g->chip_partitions[chip].name); + } +#endif +} + +/** + * gpmi_nand_probe - probe for the GPMI device + * + * Probe for GPMI device and discover NAND chips + */ +static int __init gpmi_nand_probe(struct platform_device *pdev) +{ + struct gpmi_nand_data *g; + struct gpmi_platform_data *gpd; + const char *part_type = 0; + int err = 0; + struct resource *r; + int dma; + unsigned long long chipsize; + + /* Allocate memory for the device structure (and zero it) */ + g = kzalloc(sizeof(*g), GFP_KERNEL); + if (!g) { + dev_err(&pdev->dev, "failed to allocate gpmi_nand_data\n"); + err = -ENOMEM; + goto out1; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get resource\n"); + err = -ENXIO; + goto out2; + } + g->io_base = ioremap(r->start, r->end - r->start + 1); + if (!g->io_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + err = -EIO; + goto out2; + } + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!r) { + err = -EIO; + dev_err(&pdev->dev, "can't get IRQ resource\n"); + goto out3; + } + + gpd = (struct gpmi_platform_data *)pdev->dev.platform_data; + platform_set_drvdata(pdev, g); + err = gpmi_nand_init_hw(pdev, 1); + if (err) + goto out3; + + init_timer(&g->timer); + g->timer.data = (unsigned long)g; + g->timer.function = gpmi_timer_expiry; + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); + dev_dbg(&pdev->dev, "%s: timer set to %ld\n", + __func__, jiffies + 4 * HZ); + + g->reg_uA = gpd->io_uA; + g->regulator = regulator_get(&pdev->dev, "mmc_ssp-2"); + if (g->regulator && !IS_ERR(g->regulator)) { + regulator_set_mode(g->regulator, REGULATOR_MODE_NORMAL); + } else + g->regulator = NULL; + + gpmi_set_timings(pdev, &gpmi_safe_timing); + + g->irq = r->start; + err = request_irq(g->irq, gpmi_irq, 0, dev_name(&pdev->dev), g); + if (err) { + dev_err(&pdev->dev, "can't request GPMI IRQ\n"); + goto out4; + } + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) { + dev_err(&pdev->dev, "can't get DMA resource\n"); + goto out5; + } + + if (r->end - r->start > GPMI_MAX_CHIPS) + dev_info(&pdev->dev, "too spread resource: max %d chips\n", + GPMI_MAX_CHIPS); + + for (dma = r->start; + dma < min_t(int, r->end, r->start + GPMI_MAX_CHIPS); dma++) { + err = gpmi_init_chip(pdev, g, dma - r->start, dma); + if (err) + goto out6; + } + + g->cmd_buffer_size = GPMI_CMD_BUF_SZ; + g->write_buffer_size = GPMI_WRITE_BUF_SZ; + g->data_buffer_size = GPMI_DATA_BUF_SZ; + g->oob_buffer_size = GPMI_OOB_BUF_SZ; + + err = gpmi_alloc_buffers(pdev, g); + if (err) { + dev_err(&pdev->dev, "can't setup buffers\n"); + goto out6; + } + + g->dev = pdev; + g->chip.priv = g; + g->timing = gpmi_safe_timing; + g->selected_chip = -1; + g->ignorebad = ignorebad; /* copy global setting */ + + g->mtd.priv = &g->chip; + g->mtd.name = dev_name(&pdev->dev); + g->mtd.owner = THIS_MODULE; + + g->chip.cmd_ctrl = gpmi_hwcontrol; + g->chip.read_word = gpmi_read_word; + g->chip.read_byte = gpmi_read_byte; + g->chip.read_buf = gpmi_read_buf; + g->chip.write_buf = gpmi_write_buf; + g->chip.select_chip = gpmi_select_chip; + g->chip.verify_buf = gpmi_verify_buf; + g->chip.dev_ready = gpmi_dev_ready; + + g->chip.ecc.mode = NAND_ECC_HW_SYNDROME; + g->chip.ecc.write_oob = gpmi_ecc_write_oob; + g->chip.ecc.read_oob = gpmi_ecc_read_oob; + g->chip.ecc.write_page = gpmi_ecc_write_page; + g->chip.ecc.read_page = gpmi_ecc_read_page; + g->chip.ecc.read_page_raw = gpmi_read_page_raw; + g->chip.ecc.write_page_raw = gpmi_write_page_raw; + g->chip.ecc.size = 512; + + g->chip.write_page = gpmi_write_page; + + g->chip.scan_bbt = gpmi_scan_bbt; + g->chip.block_bad = gpmi_block_bad; + + g->cmd_buffer_sz = 0; + + /* first scan to find the device and get the page size */ + if (nand_scan_ident(&g->mtd, max_chips) + || gpmi_scan_middle(g) + || nand_scan_tail(&g->mtd)) { + dev_err(&pdev->dev, "No NAND found\n"); + /* errors found on some step */ + goto out7; + } + + g->chip.options |= NAND_NO_SUBPAGE_WRITE; + g->chip.subpagesize = g->mtd.writesize; + g->mtd.subpage_sft = 0; + + g->mtd.erase = gpmi_erase; + + g->saved_read_oob = g->mtd.read_oob; + g->saved_write_oob = g->mtd.write_oob; + g->mtd.read_oob = gpmi_read_oob; + g->mtd.write_oob = gpmi_write_oob; + +#ifdef CONFIG_MTD_PARTITIONS + if (gpd == NULL) + goto out_all; + + if (gpd->parts[0].part_probe_types) { + g->nr_parts = parse_mtd_partitions(&g->mtd, + gpd->parts[0]. + part_probe_types, &g->parts, + 0); + if (g->nr_parts > 0) + part_type = "command line"; + else + g->nr_parts = 0; + } + + if (g->nr_parts == 0 && gpd->parts[0].partitions) { + g->parts = gpd->parts[0].partitions; + g->nr_parts = gpd->parts[0].nr_partitions; + part_type = "static"; + } + + if (g->nr_parts == 0) { + dev_err(&pdev->dev, "Neither part_probe_types nor " + "partitions was specified in platform_data"); + goto out_all; + } + + dev_info(&pdev->dev, "Using %s partition definition\n", part_type); + + g->numchips = g->chip.numchips; + chipsize = g->mtd.size; + do_div(chipsize, (unsigned long)g->numchips); + + if (!strcmp(part_type, "command line")) + add_mtd_partitions(&g->mtd, g->parts, g->nr_parts); + else + gpmi_create_partitions(g, gpd, chipsize); + + if (add_mtd_entire) { + printk(KERN_NOTICE "Adding MTD covering the whole flash\n"); + add_mtd_device(&g->mtd); + } +#else + add_mtd_device(&g->mtd); +#endif + gpmi_uid_init("nand", &g->mtd, gpd->uid_offset, gpd->uid_size); + +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES + gpmi_sysfs(pdev, true); +#endif + return 0; + +out_all: + ecc8_exit(); + bch_exit(); +out7: + nand_release(&g->mtd); + gpmi_free_buffers(pdev, g); +out6: + gpmi_deinit_chip(pdev, g, -1); +out5: + free_irq(g->irq, g); +out4: + del_timer_sync(&g->timer); + gpmi_nand_release_hw(pdev); +out3: + platform_set_drvdata(pdev, NULL); + iounmap(g->io_base); +out2: + kfree(g); +out1: + return err; +} + +/** + * gpmi_nand_remove - remove a GPMI device + * + */ +static int __devexit gpmi_nand_remove(struct platform_device *pdev) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + int i = 0; +#ifdef CONFIG_MTD_PARTITIONS + struct gpmi_platform_data *gpd = pdev->dev.platform_data; + struct mtd_partition *platf_parts; +#endif + + gpmi_delete_partitions(g); + del_timer_sync(&g->timer); + gpmi_uid_remove("nand"); +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES + gpmi_sysfs(pdev, false); +#endif + nand_release(&g->mtd); + gpmi_free_buffers(pdev, g); + gpmi_deinit_chip(pdev, g, -1); + gpmi_nand_release_hw(pdev); + free_irq(g->irq, g); + if (g->regulator) + regulator_put(g->regulator); + +#ifdef CONFIG_MTD_PARTITIONS + if (i < gpd->items && gpd->parts[i].partitions) + platf_parts = gpd->parts[i].partitions; + else + platf_parts = NULL; + if (g->parts && g->parts != platf_parts) + kfree(g->parts); +#endif + iounmap(g->io_base); + kfree(g); + + return 0; +} + +#ifdef CONFIG_PM +static int gpmi_nand_suspend(struct platform_device *pdev, pm_message_t pm) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + int r = 0; + + if (g->self_suspended) + gpmi_self_wakeup(g); + del_timer_sync(&g->timer); + + r = g->mtd.suspend(&g->mtd); + if (r == 0) + gpmi_nand_release_hw(pdev); + + return r; +} + +static int gpmi_nand_resume(struct platform_device *pdev) +{ + struct gpmi_nand_data *g = platform_get_drvdata(pdev); + int r; + + r = gpmi_nand_init_hw(pdev, 1); + gpmi_set_timings(pdev, &g->timing); + g->mtd.resume(&g->mtd); + g->timer.expires = jiffies + 4 * HZ; + add_timer(&g->timer); + return r; +} +#else +#define gpmi_nand_suspend NULL +#define gpmi_nand_resume NULL +#endif + +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES +static ssize_t show_timings(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct gpmi_nand_timing *ptm; + struct gpmi_nand_data *g = dev_get_drvdata(d); + + ptm = &g->timing; + return sprintf(buf, "DATA_SETUP %d, DATA_HOLD %d, " + "ADDR_SETUP %d, DSAMPLE_TIME %d\n", + ptm->data_setup, ptm->data_hold, + ptm->address_setup, ptm->dsample_time); +} + +static ssize_t store_timings(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + const char *p, *end; + struct gpmi_nand_timing t; + struct gpmi_nand_data *g = dev_get_drvdata(d); + char tmps[20]; + u8 *timings[] = { + &t.data_setup, + &t.data_hold, + &t.address_setup, + &t.dsample_time, + NULL, + }; + u8 **timing = timings; + + p = buf; + + /* parse values */ + while (*timing != NULL) { + unsigned long t_long; + + end = strchr(p, ','); + memset(tmps, 0, sizeof(tmps)); + if (end) + strncpy(tmps, p, min_t(int, sizeof(tmps) - 1, end - p)); + else + strncpy(tmps, p, sizeof(tmps) - 1); + + if (strict_strtoul(tmps, 0, &t_long) < 0) + return -EINVAL; + + if (t_long > 255) + return -EINVAL; + + **timing = (u8) t_long; + timing++; + + if (!end && *timing) + return -EINVAL; + p = end + 1; + } + + gpmi_set_timings(g->dev, &t); + + return size; +} + +static ssize_t show_stat(struct device *d, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "copies\t\t%dff pages\t%d\n", copies, ff_writes); +} + +static ssize_t show_chips(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct gpmi_nand_data *g = dev_get_drvdata(d); + return sprintf(buf, "%d\n", g->numchips); +} + +static ssize_t show_ignorebad(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct gpmi_nand_data *g = dev_get_drvdata(d); + + return sprintf(buf, "%d\n", g->ignorebad); +} + +static ssize_t store_ignorebad(struct device *d, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct gpmi_nand_data *g = dev_get_drvdata(d); + const char *p = buf; + unsigned long v; + + if (strict_strtoul(p, 0, &v) < 0) + return size; + if (v > 0) + v = 1; + if (v != g->ignorebad) { + if (v) { + g->bbt = g->chip.bbt; + g->chip.bbt = NULL; + g->ignorebad = 1; + } else { + g->chip.bbt = g->bbt; + g->ignorebad = 0; + } + } + return size; +} + +static DEVICE_ATTR(timings, 0644, show_timings, store_timings); +static DEVICE_ATTR(stat, 0444, show_stat, NULL); +static DEVICE_ATTR(ignorebad, 0644, show_ignorebad, store_ignorebad); +static DEVICE_ATTR(numchips, 0444, show_chips, NULL); + +static struct device_attribute *gpmi_attrs[] = { + &dev_attr_timings, + &dev_attr_stat, + &dev_attr_ignorebad, + &dev_attr_numchips, + NULL, +}; + +int gpmi_sysfs(struct platform_device *pdev, int create) +{ + int err = 0; + int i; + + if (create) { + for (i = 0; gpmi_attrs[i]; i++) { + err = device_create_file(&pdev->dev, gpmi_attrs[i]); + if (err) + break; + } + if (err) + while (--i >= 0) + device_remove_file(&pdev->dev, gpmi_attrs[i]); + } else { + for (i = 0; gpmi_attrs[i]; i++) + device_remove_file(&pdev->dev, gpmi_attrs[i]); + } + return err; +} +#endif + +static struct platform_driver gpmi_nand_driver = { + .probe = gpmi_nand_probe, + .remove = __devexit_p(gpmi_nand_remove), + .driver = { + .name = "gpmi", + .owner = THIS_MODULE, + }, + .suspend = gpmi_nand_suspend, + .resume = gpmi_nand_resume, +}; + +static LIST_HEAD(gpmi_hwecc_chips); + +void gpmi_hwecc_chip_add(struct gpmi_hwecc_chip *chip) +{ + list_add(&chip->list, &gpmi_hwecc_chips); +} + +EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_add); + +void gpmi_hwecc_chip_remove(struct gpmi_hwecc_chip *chip) +{ + list_del(&chip->list); +} + +EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_remove); + +struct gpmi_hwecc_chip *gpmi_hwecc_chip_find(char *name) +{ + struct gpmi_hwecc_chip *c; + + list_for_each_entry(c, &gpmi_hwecc_chips, list) + if (strncmp(c->name, name, sizeof(c->name)) == 0) + return c; + return NULL; +} + +EXPORT_SYMBOL_GPL(gpmi_hwecc_chip_find); + +static int __init gpmi_nand_init(void) +{ + bch_init(); + ecc8_init(); + return platform_driver_register(&gpmi_nand_driver); +} + +static void __exit gpmi_nand_exit(void) +{ + platform_driver_unregister(&gpmi_nand_driver); +} + +module_init(gpmi_nand_init); +module_exit(gpmi_nand_exit); +MODULE_AUTHOR("dmitry pervushin <dimka@embeddedalley.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPMI NAND driver"); +module_param(max_chips, int, 0400); +module_param(clk, long, 0400); +module_param(bch, int, 0600); +module_param(map_buffers, int, 0600); +module_param(raw_mode, int, 0600); +module_param(debug, int, 0600); +module_param(add_mtd_entire, int, 0400); +module_param(add_mtd_chip, int, 0400); +module_param(ignorebad, int, 0400); diff --git a/drivers/mtd/nand/gpmi/gpmi-bbt.c b/drivers/mtd/nand/gpmi/gpmi-bbt.c new file mode 100644 index 000000000000..54755f870440 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-bbt.c @@ -0,0 +1,565 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <mach/dma.h> +#include <mach/unique-id.h> +#include "gpmi.h" + +static int boot_search_count; +static int stride = 64; +static int ncb_version = 3; +module_param(boot_search_count, int, 0400); +module_param(ncb_version, int, 0400); + +void *gpmi_read_page(struct mtd_info *mtd, loff_t start, void *data, int raw) +{ + int ret; + struct mtd_oob_ops ops; + + if (!data) + data = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + if (!data) + return NULL; + + if (raw) + ops.mode = MTD_OOB_RAW; + else + ops.mode = MTD_OOB_PLACE; + ops.datbuf = data; + ops.len = mtd->writesize; + ops.oobbuf = data + mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + ret = nand_do_read_ops(mtd, start, &ops); + + if (ret) + return NULL; + return data; +} + +int gpmi_write_ncb(struct mtd_info *mtd, struct gpmi_bcb_info *b) +{ + struct gpmi_ncb *ncb = NULL, *unencoded_ncb = NULL; + struct nand_chip *chip = mtd->priv; + int err; + loff_t start = 0; + struct mtd_oob_ops ops; + struct erase_info instr; + int ncb_count; + + ncb = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + if (!ncb) { + err = -ENOMEM; + goto out; + } + unencoded_ncb = kzalloc(mtd->writesize, GFP_KERNEL); + if (!unencoded_ncb) { + err = -ENOMEM; + goto out; + } + ops.mode = -1; /* if the value is not set in switch below, + this will cause BUG. Take care. */ + if (b && b->pre_ncb) + memcpy(unencoded_ncb, b->pre_ncb, b->pre_ncb_size); + else { + memcpy(&unencoded_ncb->fingerprint1, SIG1, sizeof(u32)); + memcpy(&unencoded_ncb->fingerprint2, SIG_NCB, sizeof(u32)); + if (b) + unencoded_ncb->timing = b->timing; + } + + switch (ncb_version) { + case 0: + ops.mode = MTD_OOB_AUTO; + memcpy(ncb, unencoded_ncb, sizeof(*unencoded_ncb)); + break; +#ifdef CONFIG_MTD_NAND_GPMI_TA1 + case 1: + ops.mode = MTD_OOB_RAW; + gpmi_encode_hamming_ncb_22_16(unencoded_ncb, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ncb, mtd->writesize + mtd->oobsize); + break; +#endif +#ifdef CONFIG_MTD_NAND_GPMI_TA3 + case 3: + ops.mode = MTD_OOB_RAW; + gpmi_encode_hamming_ncb_13_8(unencoded_ncb, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ncb, mtd->writesize + mtd->oobsize); + break; +#endif + + default: + printk(KERN_ERR"Incorrect ncb_version = %d\n", ncb_version); + err = -EINVAL; + goto out; + } + + ops.datbuf = (u8 *)ncb; + ops.len = mtd->writesize; + ops.oobbuf = (u8 *)ncb + mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + + ncb_count = 0; + do { + printk(KERN_NOTICE"GPMI: Trying to store NCB at addr %lx\n", + (unsigned long)start); + memset(&instr, 0, sizeof(instr)); + instr.mtd = mtd; + instr.addr = start; + instr.len = (1 << chip->phys_erase_shift); + err = nand_erase_nand(mtd, &instr, 0); + if (err == 0) { + printk(KERN_NOTICE"GPMI: Erased, storing\n"); + err = nand_do_write_ops(mtd, start, &ops); + printk(KERN_NOTICE"GPMI: NCB update %s (%d).\n", + err ? "failed" : "succeeded", err); + } + start += (1 << chip->phys_erase_shift); + ncb_count++; + } while (err != 0 && ncb_count < 100); + + if (b) + b->ncbblock = start >> chip->bbt_erase_shift; + +out: + kfree(ncb); + kfree(unencoded_ncb); + + return 0; +} + +static int gpmi_redundancy_check_one(u8 *pg, int dsize, int esize, int offset, + int o1, int o2) +{ + int r; + + if (o1 == o2) + return 0; + + r = memcmp(pg + o1 * dsize, pg + o2 * dsize, dsize); + if (r) { + pr_debug("DATA copies %d and %d are different: %d\n", + o1, o2, r); + return r; + } + + r = memcmp(pg + o1 * esize + offset, + pg + o2 * esize + offset, esize); + if (r) { + pr_debug("ECC copies %d and %d are different: %d\n", o1, o2, r); + return r; + } + pr_debug("Both DATA and ECC copies %d and %d are identical\n", o1, o2); + return r; +} + +static int gpmi_redundancy_check(u8 *pg, int dsize, int esize, int ecc_offset) +{ + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 1) == 0) + return 0; + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 0, 2) == 0) + return 0; + if (gpmi_redundancy_check_one(pg, dsize, esize, ecc_offset, 1, 2) == 0) + return 1; + return -1; +} + +static inline int gpmi_ncb1_redundancy_check(u8 *pg) +{ + return gpmi_redundancy_check(pg, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES, + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY); +} + +static int gpmi_scan_sigmatel_bbt( + struct mtd_info *mtd, struct gpmi_bcb_info *nfo) +{ + int page, r; + u8 *pg; + struct gpmi_ncb *result = NULL; + + if (boot_search_count == 0) + boot_search_count = 1; + if (nfo == NULL) + return -EINVAL; + + pg = NULL; + printk(KERN_NOTICE"Scanning for NCB...\n"); + for (page = 0; page < (1<<boot_search_count); page += stride) { + pg = gpmi_read_page(mtd, page * mtd->writesize, + pg, ncb_version != 0); + + printk(KERN_NOTICE"GPMI: Checking page 0x%08X\n", page); + + if (ncb_version == 0) { + if (memcmp(pg, SIG1, SIG_SIZE) != 0) + continue; + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + result = (struct gpmi_ncb *)pg; + } + +#ifdef CONFIG_MTD_NAND_GPMI_TA1 + if (ncb_version == 1) { + void *dptr, *eccptr; + + if (memcmp(pg, SIG1, SIG_SIZE) != 0) + continue; + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + + r = gpmi_ncb1_redundancy_check(pg); + + if (r < 0) { + printk(KERN_ERR"GPMI: Oops. All three " + "copies of NCB are differrent!\n"); + continue; + } + + dptr = pg + r * NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES; + eccptr = pg + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY + + r * NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES; + + if (gpmi_verify_hamming_22_16(dptr, eccptr, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) { + printk(KERN_ERR"Verification failed.\n"); + continue; + } + result = (struct gpmi_ncb *)pg; + } +#endif + +#ifdef CONFIG_MTD_NAND_GPMI_TA3 + if (ncb_version == 3) { + + if (memcmp(pg + 12, SIG1, SIG_SIZE) != 0) + continue; + + printk(KERN_NOTICE"GPMI: Signature found at 0x%08X\n", + page); + + if (gpmi_verify_hamming_13_8( + pg + 12, + pg + 524, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) < 0) { + printk(KERN_ERR"Verification failed.\n"); + continue; + } + result = (struct gpmi_ncb *)(pg + 12); + } +#endif + if (result) { + printk(KERN_NOTICE"GPMI: Valid NCB found " + "at 0x%08x\n", page); + nfo->timing = result->timing; + nfo->ncbblock = page * mtd->writesize; + break; + } + } + kfree(pg); + + return result != NULL; +} + +int gpmi_scan_bbt(struct mtd_info *mtd) +{ + struct gpmi_bcb_info stmp_bbt; + struct nand_chip *this = mtd->priv; + struct gpmi_nand_data *g = this->priv; + int r; + int numblocks, from, i, ign; + + memset(&stmp_bbt, 0, sizeof(stmp_bbt)); + g->transcribe_bbmark = 0; + + /* + Since NCB uses the full page, including BB pattern bits, + driver has to ignore result of gpmi_block_bad when reading + these pages. + */ + ign = g->ignorebad; + + g->ignorebad = true; /* strictly speaking, I'd have to hide + * the BBT too. + * But we still scanning it :) */ + r = gpmi_scan_sigmatel_bbt(mtd, &stmp_bbt); + + /* and then, driver has to restore the setting */ + g->ignorebad = ign; + + if (r) { + printk(KERN_NOTICE"Setting discovered timings: %d:%d:%d:%d\n", + stmp_bbt.timing.data_setup, + stmp_bbt.timing.data_hold, + stmp_bbt.timing.address_setup, + stmp_bbt.timing.dsample_time); + + gpmi_set_timings(g->dev, &stmp_bbt.timing); + g->timing = stmp_bbt.timing; + + } else { + g->transcribe_bbmark = !0; + numblocks = this->chipsize >> this->bbt_erase_shift; + from = 0; + printk(KERN_NOTICE"Checking BB on common-formatted flash\n"); + for (i = stmp_bbt.ncbblock + 1; i < numblocks; i++) { + /* check the block and transcribe the bb if needed */ + gpmi_block_bad(mtd, from, 0); + from += (1 << this->bbt_erase_shift); + } + } + + r = nand_default_bbt(mtd); + + if (g->transcribe_bbmark) { + /* NCB has been not found, so create NCB now */ + g->transcribe_bbmark = 0; + + stmp_bbt.timing = gpmi_safe_timing; + r = gpmi_write_ncb(mtd, &stmp_bbt); + } else { + /* NCB found, and its block should be marked as "good" */ + gpmi_block_mark_as(this, stmp_bbt.ncbblock, 0x00); + } + + return r; +} + +int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) + +{ + int page, res = 0; + struct nand_chip *chip = mtd->priv; + u16 bad; + struct gpmi_nand_data *g = chip->priv; + int chipnr; + + /* badblockpos is an offset in OOB area */ + int badblockpos = chip->ecc.steps * chip->ecc.bytes; + + if (g->ignorebad) + return 0; + + page = (int)(ofs >> chip->page_shift) & chip->pagemask; + + chipnr = (int)(ofs >> chip->chip_shift); + chip->select_chip(mtd, chipnr); + + if (g->transcribe_bbmark) + /* bad block marks still are on first byte of OOB */ + badblockpos = 0; + + if (chip->options & NAND_BUSWIDTH_16) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos & 0xFE, + page); + bad = cpu_to_le16(chip->read_word(mtd)); + if (badblockpos & 0x1) + bad >>= 8; + if ((bad & 0xFF) != 0xff) + res = 1; + } else { + chip->cmdfunc(mtd, NAND_CMD_READOOB, badblockpos, page); + if (chip->read_byte(mtd) != 0xff) + res = 1; + } + + if (g->transcribe_bbmark && res) + chip->block_markbad(mtd, ofs); + + chip->select_chip(mtd, -1); + + return res; +} + +#if defined(CONFIG_STMP3XXX_UNIQUE_ID) +/* + * UID on NAND support + */ +const int uid_size = 256; + +struct gpmi_uid_context { + struct mtd_info *mtd; + struct nand_chip *nand; + u_int32_t start; + u_int32_t size; +}; + +static int gpmi_read_uid(struct gpmi_uid_context *ctx, void *result) +{ + void *pg = NULL; + int page, o; + int status = -ENOENT; + int h_size = gpmi_hamming_ecc_size_22_16(uid_size); + + for (page = ctx->start >> ctx->nand->page_shift; + page < (ctx->start + ctx->size) >> ctx->nand->page_shift;) { + pr_debug("%s: reading page 0x%x\n", __func__, page); + if (gpmi_block_bad(ctx->mtd, page * ctx->mtd->writesize, 0)) { + pr_debug("%s: bad block %x, skipping it\n", + __func__, page * ctx->mtd->writesize); + page += (1 << ctx->nand->phys_erase_shift) + >> ctx->nand->page_shift; + continue; + } + pg = gpmi_read_page(ctx->mtd, page * ctx->mtd->writesize, + pg, 0); + if (pg) + break; + page++; + } + + if (!pg) + return status; + + o = gpmi_redundancy_check(pg, uid_size, h_size, 3 * uid_size); + if (o >= 0) { + if (gpmi_verify_hamming_22_16( + pg + o * uid_size, + pg + 3 * uid_size + h_size, uid_size) >= 0) { + memcpy(result, pg + o * uid_size, uid_size); + status = 0; + } + } + kfree(pg); + return status; +} + +static int gpmi_write_uid(struct gpmi_uid_context *ctx, void *src) +{ + struct mtd_oob_ops ops; + struct erase_info instr; + u8 *data = kzalloc(ctx->mtd->writesize + ctx->mtd->oobsize, GFP_KERNEL); + int h_size = gpmi_hamming_ecc_size_22_16(uid_size); + char ecc[h_size]; + u_int32_t start; + int i; + int err; + + if (!data) + return -ENOMEM; + + gpmi_encode_hamming_22_16(src, uid_size, ecc, h_size); + for (i = 0; i < 3; i++) { + memcpy(data + i * uid_size, src, uid_size); + memcpy(data + 3 * uid_size + i * h_size, ecc, h_size); + } + + ops.mode = MTD_OOB_AUTO; + ops.datbuf = data; + ops.len = ctx->mtd->writesize; + ops.oobbuf = NULL; + ops.ooblen = ctx->mtd->oobsize; + ops.ooboffs = 0; + + start = ctx->start; + + do { + memset(&instr, 0, sizeof(instr)); + instr.mtd = ctx->mtd; + instr.addr = start; + instr.len = (1 << ctx->nand->phys_erase_shift); + err = nand_erase_nand(ctx->mtd, &instr, 0); + if (err == 0) + err = nand_do_write_ops(ctx->mtd, start, &ops); + start += (1 << ctx->nand->phys_erase_shift); + if (start > ctx->start + ctx->size) + break; + } while (err != 0); + + return err; +} + +static ssize_t gpmi_uid_store(void *context, const char *page, + size_t count, int ascii) +{ + u8 data[uid_size]; + + memset(data, 0, sizeof(data)); + memcpy(data, page, uid_size < count ? uid_size : count); + gpmi_write_uid(context, data); + return count; +} + +static ssize_t gpmi_uid_show(void *context, char *page, int ascii) +{ + u8 result[uid_size]; + int i; + char *p = page; + int r; + + r = gpmi_read_uid(context, result); + if (r < 0) + return r; + + if (ascii) { + for (i = 0; i < uid_size; i++) { + if (i % 16 == 0) { + if (i) + *p++ = '\n'; + sprintf(p, "%04X: ", i); + p += strlen(p); + } + sprintf(p, "%02X ", result[i]); + p += strlen(p); + } + *p++ = '\n'; + return p - page; + + } else { + memcpy(page, result, uid_size); + return uid_size; + } +} + +static struct uid_ops gpmi_uid_ops = { + .id_show = gpmi_uid_show, + .id_store = gpmi_uid_store, +}; + +static struct gpmi_uid_context gpmi_uid_context; + +int __init gpmi_uid_init(const char *name, struct mtd_info *mtd, + u_int32_t start, u_int32_t size) +{ + gpmi_uid_context.mtd = mtd; + gpmi_uid_context.nand = mtd->priv; + gpmi_uid_context.start = start; + gpmi_uid_context.size = size; + return uid_provider_init(name, &gpmi_uid_ops, &gpmi_uid_context) ? + 0 : -EFAULT; +} + +void gpmi_uid_remove(const char *name) +{ + uid_provider_remove(name); +} +#endif diff --git a/drivers/mtd/nand/gpmi/gpmi-bch.c b/drivers/mtd/nand/gpmi/gpmi-bch.c new file mode 100644 index 000000000000..d7b261778dcf --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-bch.c @@ -0,0 +1,293 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * STMP378X BCH hardware ECC engine + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/dma-mapping.h> + +#include <asm/dma.h> +#include <mach/stmp3xxx.h> +#include <mach/platform.h> +#include <mach/irqs.h> +#include <mach/regs-gpmi.h> +#include "gpmi.h" + +#define BCH_MAX_NANDS 4 + +static int bch_available(void *context); +static int bch_setup(void *context, int index, int writesize, int oobsize); +static int bch_read(void *context, + int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); +static int bch_stat(void *ctx, int index, struct mtd_ecc_stats *r); +static int bch_reset(void *context, int index); + +struct bch_state_t { + struct gpmi_hwecc_chip chip; + struct { + struct mtd_ecc_stats stat; + struct completion done; + u32 writesize, oobsize; + } nands[BCH_MAX_NANDS]; +}; + +static struct bch_state_t state = { + .chip = { + .name = "bch", + .setup = bch_setup, + .stat = bch_stat, + .read = bch_read, + .reset = bch_reset, + }, +}; + +static int bch_reset(void *context, int index) +{ + stmp3xxx_reset_block(REGS_BCH_BASE, true); + stmp3xxx_setl(BM_BCH_CTRL_COMPLETE_IRQ_EN, REGS_BCH_BASE + HW_BCH_CTRL); + return 0; +} + +static int bch_stat(void *context, int index, struct mtd_ecc_stats *r) +{ + struct bch_state_t *state = context; + + *r = state->nands[index].stat; + state->nands[index].stat.failed = 0; + state->nands[index].stat.corrected = 0; + return 0; +} + +static irqreturn_t bch_irq(int irq, void *context) +{ + u32 b0, s0; + struct mtd_ecc_stats stat; + int r; + struct bch_state_t *state = context; + + s0 = __raw_readl(REGS_BCH_BASE + HW_BCH_STATUS0); + r = (s0 & BM_BCH_STATUS0_COMPLETED_CE) >> 16; + + stat.corrected = stat.failed = 0; + + b0 = (s0 & BM_BCH_STATUS0_STATUS_BLK0) >> 8; + if (b0 <= 4) + stat.corrected += b0; + if (b0 == 0xFE) + stat.failed++; + + if (s0 & BM_BCH_STATUS0_CORRECTED) + stat.corrected += (s0 & BM_BCH_STATUS0_CORRECTED); + if (s0 & BM_BCH_STATUS0_UNCORRECTABLE) + stat.failed++; + + stmp3xxx_clearl(BM_BCH_CTRL_COMPLETE_IRQ, REGS_BCH_BASE + HW_BCH_CTRL); + + pr_debug("%s: chip %d, failed %d, corrected %d\n", + __func__, r, + state->nands[r].stat.failed, + state->nands[r].stat.corrected); + state->nands[r].stat.corrected += stat.corrected; + state->nands[r].stat.failed += stat.failed; + complete(&state->nands[r].done); + + return IRQ_HANDLED; +} + +static int bch_available(void *context) +{ + stmp3xxx_reset_block(REGS_BCH_BASE, 0); + return __raw_readl(REGS_BCH_BASE + HW_BCH_BLOCKNAME) == 0x20484342; +} + +static int bch_setup(void *context, int index, int writesize, int oobsize) +{ + struct bch_state_t *state = context; + u32 layout = (u32)REGS_BCH_BASE + 0x80 + index * 0x20; + u32 ecc0, eccN; + u32 reg; + int meta; + + switch (writesize) { + case 2048: + ecc0 = 4; + eccN = 4; + meta = 5; + break; + case 4096: + ecc0 = 16; + eccN = 14; + meta = 10; + break; + default: + printk(KERN_ERR"%s: cannot tune BCH for page size %d\n", + __func__, writesize); + return -EINVAL; + } + + state->nands[index].oobsize = oobsize; + state->nands[index].writesize = writesize; + + __raw_writel(BF(writesize/512, BCH_FLASH0LAYOUT0_NBLOCKS) | + BF(oobsize, BCH_FLASH0LAYOUT0_META_SIZE) | + BF(ecc0 >> 1, BCH_FLASH0LAYOUT0_ECC0) | /* for oob */ + BF(0x00, BCH_FLASH0LAYOUT0_DATA0_SIZE), layout); + __raw_writel(BF(writesize + oobsize, BCH_FLASH0LAYOUT1_PAGE_SIZE) | + BF(eccN >> 1, BCH_FLASH0LAYOUT1_ECCN) | /* for dblock */ + BF(512, BCH_FLASH0LAYOUT1_DATAN_SIZE), layout + 0x10); + + /* + * since driver only supports CS 0..3, layouts are mapped 1:1 : + * FLASHnLAYOUT[1,2] => LAYOUTSELECT[n*2:n2*+1] + */ + reg = __raw_readl(REGS_BCH_BASE + HW_BCH_LAYOUTSELECT); + reg &= ~(0x03 << (index * 2)); + reg |= index << (index * 2); + __raw_writel(reg, REGS_BCH_BASE + HW_BCH_LAYOUTSELECT); + + bch_reset(context, index); + + printk(KERN_DEBUG"%s: CS = %d, LAYOUT = 0x%08X, layout_reg = " + "0x%08x+0x%08x: 0x%08x+0x%08x\n", + __func__, + index, __raw_readl(REGS_BCH_BASE + HW_BCH_LAYOUTSELECT), + layout, layout + 0x10, + __raw_readl(layout), + __raw_readl(layout+0x10)); + return 0; +} + +static int bch_read(void *context, + int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob) +{ + unsigned long readsize = 0; + u32 bufmask = 0; + struct bch_state_t *state = context; + + if (!dma_mapping_error(NULL, oob)) { + bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY; + readsize += state->nands[index].oobsize; + } + if (!dma_mapping_error(NULL, page)) { + bufmask |= (BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE + & ~BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY); + readsize += state->nands[index].writesize; + } + + printk(KERN_DEBUG"readsize = %ld, bufmask = 0x%X\n", readsize, bufmask); + bch_reset(context, index); + + /* wait for ready */ + chain->command->cmd = + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(index, GPMI_CTRL0_CS); + chain->command->alternate = 0; + chain++; + + /* enable BCH and read NAND data */ + chain->command->cmd = + BF(6, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__READ, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(index, GPMI_CTRL0_CS) | + BF(readsize, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = + BM_GPMI_ECCCTRL_ENABLE_ECC | + BF(0x02, GPMI_ECCCTRL_ECC_CMD) | + BF(bufmask, GPMI_ECCCTRL_BUFFER_MASK); + chain->command->pio_words[3] = readsize; + chain->command->pio_words[4] = !dma_mapping_error(NULL, page) ? page : 0; + chain->command->pio_words[5] = !dma_mapping_error(NULL, oob) ? oob : 0; + chain->command->alternate = 0; + chain++; + + /* disable BCH block */ + chain->command->cmd = + BF(3, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(index, GPMI_CTRL0_CS) | + BF(readsize, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->alternate = 0; + chain++; + + /* and deassert nand lock */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->alternate = 0; + + return 0; +} + +int __init bch_init(void) +{ + int err; + + if (!bch_available(&state.chip)) + return -ENXIO; + gpmi_hwecc_chip_add(&state.chip); + err = request_irq(IRQ_BCH, bch_irq, 0, state.chip.name, &state); + if (err) + return err; + + printk(KERN_DEBUG"%s: initialized\n", __func__); + return 0; +} + +void bch_exit(void) +{ + free_irq(IRQ_BCH, &state); + gpmi_hwecc_chip_remove(&state.chip); +} diff --git a/drivers/mtd/nand/gpmi/gpmi-ecc8.c b/drivers/mtd/nand/gpmi/gpmi-ecc8.c new file mode 100644 index 000000000000..ead809cf0d54 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-ecc8.c @@ -0,0 +1,387 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * STMP37XX/STMP378X ECC8 hardware ECC engine + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/dma-mapping.h> + +#include <asm/dma.h> +#include <mach/stmp3xxx.h> +#include <mach/irqs.h> +#include <mach/regs-gpmi.h> +#include <mach/regs-apbx.h> +#include <mach/regs-apbh.h> +#include <mach/platform.h> +#include "gpmi.h" + +#define ECC8_MAX_NANDS 4 + +static int ecc8_available(void *context); +static int ecc8_setup(void *context, int index, int writesize, int oobsize); +static int ecc8_read(void *context, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); +static int ecc8_write(void *context, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); +static int ecc8_stat(void *ctx, int index, struct mtd_ecc_stats *r); +static int ecc8_reset(void *context, int index); + +struct ecc8_nand { +}; + +struct ecc8_state_t { + struct gpmi_hwecc_chip chip; + struct completion done; + struct mtd_ecc_stats stat; + u32 writesize, oobsize; + u32 ecc_page, ecc_oob, oob_free; + u32 r, w; + int bits; + u32 s0_mask, s1_mask; +}; + +static struct ecc8_state_t state = { + .chip = { + .name = "ecc8", + .setup = ecc8_setup, + .stat = ecc8_stat, + .read = ecc8_read, + .write = ecc8_write, + .reset = ecc8_reset, + }, +}; + +static int ecc8_reset(void *context, int index) +{ + stmp3xxx_reset_block(REGS_ECC8_BASE, false); + while (__raw_readl(REGS_ECC8_BASE + HW_ECC8_CTRL) & BM_ECC8_CTRL_AHBM_SFTRST) + stmp3xxx_clearl(BM_ECC8_CTRL_AHBM_SFTRST, REGS_ECC8_BASE + HW_ECC8_CTRL); + stmp3xxx_setl(BM_ECC8_CTRL_COMPLETE_IRQ_EN, REGS_ECC8_BASE + HW_ECC8_CTRL); + return 0; +} + +static int ecc8_stat(void *context, int index, struct mtd_ecc_stats *r) +{ + struct ecc8_state_t *state = context; + + wait_for_completion(&state->done); + + *r = state->stat; + state->stat.failed = 0; + state->stat.corrected = 0; + return 0; +} + +static irqreturn_t ecc8_irq(int irq, void *context) +{ + int r; + struct mtd_ecc_stats ecc_stats; + struct ecc8_state_t *state = context; + u32 corr; + u32 s0 = __raw_readl(REGS_ECC8_BASE + HW_ECC8_STATUS0), + s1 = __raw_readl(REGS_ECC8_BASE + HW_ECC8_STATUS1); + + r = (s0 & BM_ECC8_STATUS0_COMPLETED_CE) >> 16; + + if (s0 & BM_ECC8_STATUS0_CORRECTED || + s0 & BM_ECC8_STATUS0_UNCORRECTABLE) { + + ecc_stats.failed = 0; + ecc_stats.corrected = 0; + + s0 = (s0 & BM_ECC8_STATUS0_STATUS_AUX) >> 8; + if (s0 <= 4) + ecc_stats.corrected += s0; + if (s0 == 0xE) + ecc_stats.failed++; + + for ( ; s1 != 0; s1 >>= 4) { + corr = s1 & 0xF; + if (corr == 0x0C) + continue; + if (corr == 0xE) + ecc_stats.failed++; + if (corr <= 8) + ecc_stats.corrected += corr; + s1 >>= 4; + } + state->stat.corrected += ecc_stats.corrected; + state->stat.failed += ecc_stats.failed; + } + + complete(&state->done); + + stmp3xxx_clearl(BM_ECC8_CTRL_COMPLETE_IRQ, REGS_ECC8_BASE + HW_ECC8_CTRL); + return IRQ_HANDLED; +} + +static int ecc8_available(void *context) +{ + return 1; +} + +static int ecc8_setup(void *context, int index, int writesize, int oobsize) +{ + struct ecc8_state_t *state = context; + + switch (writesize) { + case 2048: + state->ecc_page = 9; + state->ecc_oob = 9; + state->bits = 4; + state->r = BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_4_BIT, + GPMI_ECCCTRL_ECC_CMD); + state->w = BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_4_BIT, + GPMI_ECCCTRL_ECC_CMD); + break; + case 4096: + state->ecc_page = 18; + state->ecc_oob = 9; + state->bits = 8; + state->r = BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_8_BIT, + GPMI_ECCCTRL_ECC_CMD); + state->w = BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_8_BIT, + GPMI_ECCCTRL_ECC_CMD); + break; + default: + return -ENOTSUPP; + } + + state->oob_free = oobsize - + (state->bits * state->ecc_page) - state->ecc_oob; + state->oobsize = oobsize; + state->writesize = writesize; + + ecc8_reset(context, index); + + return 0; +} + +/** + * ecc8_read - create dma chain to read nand page + * + * @context: context data, pointer to ecc8_state + * @index: CS of nand to read + * @chain: dma chain to be filled + * @page: (physical) address of page data to be read to + * @oob: (physical) address of OOB data to be read to + * + * Return: status of operation -- 0 on success + */ +static int ecc8_read(void *context, int cs, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob) +{ + unsigned long readsize = 0; + u32 bufmask = 0; + struct ecc8_state_t *state = context; + + ecc8_reset(context, cs); + + state->s0_mask = state->s1_mask = 0; + + if (!dma_mapping_error(NULL, page)) { + bufmask |= (1 << state->bits) - 1; + readsize += (state->bits * state->ecc_page); + readsize += state->writesize; + } + if (!dma_mapping_error(NULL, oob)) { + bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__AUXILIARY; + readsize += state->oob_free + state->ecc_oob; + state->s0_mask = BM_ECC8_STATUS0_STATUS_AUX; + if (dma_mapping_error(NULL, page)) + page = oob; + } + + + /* wait for ready */ + chain->command->cmd = + BF(1, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(cs, GPMI_CTRL0_CS); + chain->command->buf_ptr = 0; + chain++; + + /* enable ECC and read NAND data */ + chain->command->cmd = + BF(6, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__READ, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(cs, GPMI_CTRL0_CS) | + BF(readsize, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = + BM_GPMI_ECCCTRL_ENABLE_ECC | + state->r | + BF(bufmask, GPMI_ECCCTRL_BUFFER_MASK); + chain->command->pio_words[3] = readsize; + chain->command->pio_words[4] = !dma_mapping_error(NULL, page) ? page : 0; + chain->command->pio_words[5] = !dma_mapping_error(NULL, oob) ? oob : 0; + chain->command->buf_ptr = 0; + chain++; + + /* disable ECC block */ + chain->command->cmd = + BF(3, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY, + GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BF(cs, GPMI_CTRL0_CS); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->buf_ptr = 0; + chain++; + + /* and deassert nand lock */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, APBH_CHn_CMD_COMMAND); + chain->command->buf_ptr = 0; + + init_completion(&state->done); + + return 0; +} + +static int ecc8_write(void *context, int cs, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob) +{ + u32 bufmask = 0; + struct ecc8_state_t *state = context; + u32 data_w, data_w_ecc, + oob_w, oob_w_ecc; + + ecc8_reset(context, cs); + + data_w = data_w_ecc = oob_w = oob_w_ecc = 0; + + if (!dma_mapping_error(NULL, oob)) { + bufmask |= BV_GPMI_ECCCTRL_BUFFER_MASK__AUXILIARY; + oob_w = state->oob_free; + oob_w_ecc = oob_w + state->ecc_oob; + } + if (!dma_mapping_error(NULL, page)) { + bufmask |= (1 << state->bits) - 1; + data_w = state->bits * 512; + data_w_ecc = data_w + state->bits * state->ecc_page; + } + + /* enable ECC and send NAND data (page only) */ + chain->command->cmd = + BF(data_w ? data_w : oob_w, APBH_CHn_CMD_XFER_COUNT) | + BF(4, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + BF(BV_GPMI_CTRL0_COMMAND_MODE__WRITE, GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + BF(cs, GPMI_CTRL0_CS) | + BF(BV_GPMI_CTRL0_ADDRESS__NAND_DATA, GPMI_CTRL0_ADDRESS) | + BF(data_w + oob_w, GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = + BM_GPMI_ECCCTRL_ENABLE_ECC | + state->w | + BF(bufmask, GPMI_ECCCTRL_BUFFER_MASK); + chain->command->pio_words[3] = + data_w_ecc + oob_w_ecc; + if (!dma_mapping_error(NULL, page)) + chain->command->buf_ptr = page; + else + chain->command->buf_ptr = oob; + chain++; + + if (!dma_mapping_error(NULL, page) && !dma_mapping_error(NULL, oob)) { + /* send NAND data (OOB only) */ + chain->command->cmd = + BM_APBH_CHn_CMD_CHAIN | + BF(oob_w, APBH_CHn_CMD_XFER_COUNT) | + BF(0, APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BF(BV_APBH_CHn_CMD_COMMAND__DMA_READ, + APBH_CHn_CMD_COMMAND); + chain->command->buf_ptr = oob; /* never dma_mapping_error() */ + chain++; + } + /* emit IRQ */ + chain->command->cmd = + BF(0, APBH_CHn_CMD_CMDWORDS) | + BF(BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER, + APBH_CHn_CMD_COMMAND) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_IRQONCMPLT; + + return 0; +} + +int __init ecc8_init(void) +{ + int err; + + if (!ecc8_available(&state)) + return -ENXIO; + + gpmi_hwecc_chip_add(&state.chip); + err = request_irq(IRQ_ECC8_IRQ, ecc8_irq, 0, state.chip.name, &state); + if (err) + return err; + + printk(KERN_INFO"%s: initialized\n", __func__); + return 0; +} + +void ecc8_exit(void) +{ + free_irq(IRQ_ECC8_IRQ, &state); + gpmi_hwecc_chip_remove(&state.chip); +} diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c b/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c new file mode 100644 index 000000000000..256911050782 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-hamming-13-8.c @@ -0,0 +1,130 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * NCB software ECC Hamming code + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <mach/dma.h> +#include "gpmi.h" + +#define BIT_VAL(v, n) (((v) >> (n)) & 0x1) +#define B(n) (BIT_VAL(d, n)) + +static u8 calculate_parity(u8 d) +{ + u8 p = 0; + + if (d == 0 || d == 0xFF) + return 0; /* optimization :) */ + + p |= (B(6) ^ B(5) ^ B(3) ^ B(2)) << 0; + p |= (B(7) ^ B(5) ^ B(4) ^ B(2) ^ B(1)) << 1; + p |= (B(7) ^ B(6) ^ B(5) ^ B(1) ^ B(0)) << 2; + p |= (B(7) ^ B(4) ^ B(3) ^ B(0)) << 3; + p |= (B(6) ^ B(4) ^ B(3) ^ B(2) ^ B(1) ^ B(0)) << 4; + return p; +} + +static inline int even_number_of_1s(u8 byte) +{ + int even = 1; + + while (byte > 0) { + even ^= (byte & 0x1); + byte >>= 1; + } + return even; +} + +static int lookup_single_error(u8 syndrome) +{ + int i; + u8 syndrome_table[] = { + 0x1C, 0x16, 0x13, 0x19, + 0x1A, 0x07, 0x15, 0x0E, + 0x01, 0x02, 0x04, 0x08, + 0x10, + }; + + for (i = 0; i < ARRAY_SIZE(syndrome_table); i++) + if (syndrome_table[i] == syndrome) + return i; + return -ENOENT; +} + +int gpmi_verify_hamming_13_8(void *data, u8 *parity, size_t size) +{ + int i; + u8 *pdata = data; + int bit_to_flip; + u8 np, syndrome; + int errors = 0; + + for (i = 0; i < size; i ++, pdata++) { + np = calculate_parity(*pdata); + syndrome = np ^ parity[i]; + if (syndrome == 0) /* cool */ { + continue; + } + + if (even_number_of_1s(syndrome)) + return -i; /* can't recover */ + + bit_to_flip = lookup_single_error(syndrome); + if (bit_to_flip < 0) + return -i; /* can't fix the error */ + + if (bit_to_flip < 8) { + *pdata ^= (1 << bit_to_flip); + errors++; + } + } + return errors; +} + +void gpmi_encode_hamming_13_8(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size) +{ + int i; + u8 *src = source_block; + u8 *ecc = source_ecc; + + for (i = 0; i < src_size && i < ecc_size; i++) + ecc[i] = calculate_parity(src[i]); +} + +void gpmi_encode_hamming_ncb_13_8(void *source_block, size_t source_size, + void *target_block, size_t target_size) +{ + if (target_size < 12 + 2 * NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) + return; + memset(target_block, 0xFF, target_size); + memcpy((u8 *)target_block + 12, source_block, source_size); + gpmi_encode_hamming_13_8(source_block, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + (u8 *)target_block + 12 + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); +} + +unsigned gpmi_hamming_ecc_size_13_8(int block_size) +{ + return block_size; +} diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c new file mode 100644 index 000000000000..cceb35feab2e --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.c @@ -0,0 +1,195 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * NCB software ECC Hamming code + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/platform_device.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <mach/dma.h> +#include "gpmi.h" +#include "gpmi-hamming-22-16.h" + +#define BIT_VAL(v, n) (((v) >> (n)) & 0x1) +#define B(n) (BIT_VAL(d, n)) +#define BSEQ(a1, a2, a3, a4, a5, a6, a7, a8) \ + (B(a1) ^ B(a2) ^ B(a3) ^ B(a4) ^ B(a5) ^ B(a6) ^ B(a7) ^ B(a8)) +static u8 calculate_parity(u16 d) +{ + u8 p = 0; + + if (d == 0 || d == 0xFFFF) + return 0; /* optimization :) */ + + p |= BSEQ(15, 12, 11, 8, 5, 4, 3, 2) << 0; + p |= BSEQ(13, 12, 11, 10, 9, 7, 3, 1) << 1; + p |= BSEQ(15, 14, 13, 11, 10, 9, 6, 5) << 2; + p |= BSEQ(15, 14, 13, 8, 7, 6, 4, 0) << 3; + p |= BSEQ(12, 9, 8, 7, 6, 2, 1, 0) << 4; + p |= BSEQ(14, 10, 5, 4, 3, 2, 1, 0) << 5; + return p; +} + +static inline int even_number_of_1s(u8 byte) +{ + int even = 1; + + while (byte > 0) { + even ^= (byte & 0x1); + byte >>= 1; + } + return even; +} + +static int lookup_single_error(u8 syndrome) +{ + int i; + u8 syndrome_table[] = { + 0x38, 0x32, 0x31, 0x23, 0x29, 0x25, 0x1C, 0x1A, + 0x19, 0x16, 0x26, 0x07, 0x13, 0x0E, 0x2C, 0x0D, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, + }; + + for (i = 0; i < ARRAY_SIZE(syndrome_table); i++) + if (syndrome_table[i] == syndrome) + return i; + return -ENOENT; +} + +int gpmi_verify_hamming_22_16(void *data, u8 *parity, size_t size) +{ + int i, j; + int bit_index, bit_to_flip; + u16 *pdata = data; + u8 p = 0, np, syndrome; + int errors = 0; + + for (bit_index = i = j = 0; + i < size / sizeof(u16); + i ++, pdata++) { + + switch (bit_index) { + + case 0: + p = parity[j] & 0x3F; + break; + case 2: + p = (parity[j++] & 0xC0) >> 6; + p |= (parity[j] & 0x0F) << 2; + break; + case 4: + p = (parity[j++] & 0xF0) >> 4; + p |= (parity[j] & 0x03) << 4; + break; + case 6: + p = (parity[j++] & 0xFC) >> 2; + break; + default: + BUG(); /* how did you get this ?! */ + break; + } + bit_index = (bit_index + 2) % 8; + + np = calculate_parity(*pdata); + syndrome = np ^ p; + if (syndrome == 0) /* cool */ { + continue; + } + + if (even_number_of_1s(syndrome)) + return -i; /* can't recover */ + + bit_to_flip = lookup_single_error(syndrome); + if (bit_to_flip < 0) + return -i; /* can't fix the error */ + + if (bit_to_flip < 16) { + *pdata ^= (1 << bit_to_flip); + errors++; + } + } + return errors; +} + +void gpmi_encode_hamming_22_16(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size) +{ + int i, j, bit_index; + u16 *src = source_block; + u8 *ecc = source_ecc; + u8 np; + + for (bit_index = j = i = 0; + j < src_size/sizeof(u16) && i < ecc_size; + j++) { + + np = calculate_parity(src[j]); + + switch (bit_index) { + + case 0: + ecc[i] = np & 0x3F; + break; + case 2: + ecc[i++] |= (np & 0x03) << 6; + ecc[i] = (np & 0x3C) >> 2; + break; + case 4: + ecc[i++] |= (np & 0x0F) << 4; + ecc[i] = (np & 0x30) >> 4; + break; + case 6: + ecc[i++] |= (np & 0x3F) << 2; + break; + } + bit_index = (bit_index + 2) % 8; + } + +} + +void gpmi_encode_hamming_ncb_22_16(void *source_block, size_t source_size, + void *target_block, size_t target_size) +{ + u8 *dst = target_block; + u8 ecc[NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES]; + + gpmi_encode_hamming_22_16(source_block, + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES, + ecc, + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); + /* create THREE copies of source block */ + memcpy(dst + NAND_HC_ECC_OFFSET_FIRST_DATA_COPY, + source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_SECOND_DATA_COPY, + source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_THIRD_DATA_COPY, + source_block, NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES); + /* ..and three copies of ECC block */ + memcpy(dst + NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY, + ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY, + ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); + memcpy(dst + NAND_HC_ECC_OFFSET_THIRD_PARITY_COPY, + ecc, NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES); +} + +unsigned gpmi_hamming_ecc_size_22_16(int block_size) +{ + return (((block_size * 8) / 16) * 6) / 8; +} diff --git a/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h new file mode 100644 index 000000000000..6959bf3c599e --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi-hamming-22-16.h @@ -0,0 +1,43 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * NCB software ECC Hamming code + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __LINUX_GPMI_H +#define __LINUX_GPMI_H + +#define NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES (512) +#define NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES \ + ((((NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES*8)/16)*6)/8) +#define NAND_HC_ECC_OFFSET_FIRST_DATA_COPY (0) +#define NAND_HC_ECC_OFFSET_SECOND_DATA_COPY \ + (NAND_HC_ECC_OFFSET_FIRST_DATA_COPY + \ + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_THIRD_DATA_COPY \ + (NAND_HC_ECC_OFFSET_SECOND_DATA_COPY + \ + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY \ + (NAND_HC_ECC_OFFSET_THIRD_DATA_COPY + \ + NAND_HC_ECC_SIZEOF_DATA_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY \ + (NAND_HC_ECC_OFFSET_FIRST_PARITY_COPY + \ + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES) +#define NAND_HC_ECC_OFFSET_THIRD_PARITY_COPY \ + (NAND_HC_ECC_OFFSET_SECOND_PARITY_COPY + \ + NAND_HC_ECC_SIZEOF_PARITY_BLOCK_IN_BYTES) + +#endif diff --git a/drivers/mtd/nand/gpmi/gpmi.h b/drivers/mtd/nand/gpmi/gpmi.h new file mode 100644 index 000000000000..65de2e1a1c78 --- /dev/null +++ b/drivers/mtd/nand/gpmi/gpmi.h @@ -0,0 +1,293 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __DRIVERS_GPMI_H +#define __DRIVERS_GPMI_H + +#include <linux/mtd/partitions.h> +#include <linux/timer.h> +#include <mach/gpmi.h> +#include <mach/regs-gpmi.h> +#include <mach/regs-apbh.h> +#include <mach/dma.h> +#ifdef CONFIG_MTD_NAND_GPMI_BCH +#include <mach/regs-bch.h> +#endif +#include <mach/regs-ecc8.h> + +#include "gpmi-hamming-22-16.h" + +#define GPMI_ECC4_WR \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_4_BIT, GPMI_ECCCTRL_ECC_CMD)) +#define GPMI_ECC4_RD \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_4_BIT, GPMI_ECCCTRL_ECC_CMD)) +#define GPMI_ECC8_WR \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__ENCODE_8_BIT, GPMI_ECCCTRL_ECC_CMD)) +#define GPMI_ECC8_RD \ + (BM_GPMI_ECCCTRL_ENABLE_ECC | \ + BF(BV_GPMI_ECCCTRL_ECC_CMD__DECODE_8_BIT, GPMI_ECCCTRL_ECC_CMD)) + +/* fingerprints of BCB that can be found on STMP-formatted flash */ +#define SIG1 "STMP" +#define SIG_NCB "NCB " +#define SIG_LDLB "LDLB" +#define SIG_DBBT "DBBT" +#define SIG_SIZE 4 + +struct gpmi_nand_timing { + u8 data_setup; + u8 data_hold; + u8 address_setup; + u8 dsample_time; +}; + +struct gpmi_bcb_info { + struct gpmi_nand_timing timing; + loff_t ncbblock; + const void *pre_ncb; + size_t pre_ncb_size; +}; + +struct gpmi_ncb; + +int gpmi_erase(struct mtd_info *mtd, struct erase_info *instr); +int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs); +int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip); +int gpmi_scan_bbt(struct mtd_info *mtd); +int gpmi_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip); +#ifdef CONFIG_MTD_NAND_GPMI_SYSFS_ENTRIES +int gpmi_sysfs(struct platform_device *p, int create); +#endif +int gpmi_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, + int page, int sndcmd); +void gpmi_set_timings(struct platform_device *pdev, + struct gpmi_nand_timing *tm); +int gpmi_write_ncb(struct mtd_info *mtd, struct gpmi_bcb_info *b); + +unsigned gpmi_hamming_ecc_size_22_16(int block_size); +void gpmi_encode_hamming_ncb_22_16(void *source_block, size_t source_size, + void *target_block, size_t target_size); +void gpmi_encode_hamming_22_16(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size); +int gpmi_verify_hamming_22_16(void *data, u8 *parity, size_t size); + +unsigned gpmi_hamming_ecc_size_13_8(int block_size); +void gpmi_encode_hamming_ncb_13_8(void *source_block, size_t source_size, + void *target_block, size_t target_size); +void gpmi_encode_hamming_13_8(void *source_block, size_t src_size, + void *source_ecc, size_t ecc_size); +int gpmi_verify_hamming_13_8(void *data, u8 *parity, size_t size); + +#define GPMI_DMA_MAX_CHAIN 20 /* max DMA commands in chain */ + +/* + * Sizes of data buffers to exchange commands/data with NAND chip + * Default values cover 4K NAND page (4096 data bytes + 218 bytes OOB) + */ +#define GPMI_CMD_BUF_SZ 10 +#define GPMI_DATA_BUF_SZ NAND_MAX_PAGESIZE +#define GPMI_WRITE_BUF_SZ NAND_MAX_PAGESIZE +#define GPMI_OOB_BUF_SZ NAND_MAX_OOBSIZE + +#define GPMI_MAX_CHIPS 10 + +struct gpmi_hwecc_chip { + char name[40]; + struct list_head list; + int (*setup)(void *ctx, int index, int writesize, int oobsize); + int (*reset)(void *ctx, int index); + int (*read)(void *ctx, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); + int (*write)(void *ctx, int index, + struct stmp3xxx_dma_descriptor *chain, + dma_addr_t error, + dma_addr_t page, dma_addr_t oob); + int (*stat)(void *ctx, int index, struct mtd_ecc_stats *r); +}; + +/* HWECC chips */ +struct gpmi_hwecc_chip *gpmi_hwecc_chip_find(char *name); +void gpmi_hwecc_chip_add(struct gpmi_hwecc_chip *chip); +void gpmi_hwecc_chip_remove(struct gpmi_hwecc_chip *chip); +int bch_init(void); +int ecc8_init(void); +void bch_exit(void); +void ecc8_exit(void); + + +struct gpmi_nand_data { + void __iomem *io_base; + struct clk *clk; + int irq; + struct timer_list timer; + int self_suspended; + int use_count; + struct regulator *regulator; + int reg_uA; + + int ignorebad; + void *bbt; + + struct nand_chip chip; + struct mtd_info mtd; + struct platform_device *dev; + +#ifdef CONFIG_MTD_PARTITIONS + int nr_parts; + struct mtd_partition + *parts; +#endif + + struct completion done; + + u8 *cmd_buffer; + dma_addr_t cmd_buffer_handle; + int cmd_buffer_size, cmd_buffer_sz; + + u8 *write_buffer; + dma_addr_t write_buffer_handle; + int write_buffer_size; + u8 *read_buffer; /* point in write_buffer */ + dma_addr_t read_buffer_handle; + + u8 *data_buffer; + dma_addr_t data_buffer_handle; + int data_buffer_size; + + u8 *oob_buffer; + dma_addr_t oob_buffer_handle; + int oob_buffer_size; + + void *verify_buffer; + + struct nchip { + unsigned dma_ch; + struct stmp37xx_circ_dma_chain chain; + struct stmp3xxx_dma_descriptor d[GPMI_DMA_MAX_CHAIN]; + struct stmp3xxx_dma_descriptor error; + int cs; + } chips[GPMI_MAX_CHIPS]; + struct nchip *cchip; + int selected_chip; + + unsigned hwecc_type_read, hwecc_type_write; + int hwecc; + + int ecc_oob_bytes, oob_free; + + int transcribe_bbmark; + struct gpmi_nand_timing timing; + + void (*saved_command)(struct mtd_info *mtd, unsigned int command, + int column, int page_addr); + + int raw_oob_mode; + int (*saved_read_oob)(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops); + int (*saved_write_oob)(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops); + + struct gpmi_hwecc_chip *hc; + + int numchips; + int custom_partitions; + struct mtd_info *masters[GPMI_MAX_CHIPS]; + struct mtd_partition chip_partitions[GPMI_MAX_CHIPS]; + int n_concat; + struct mtd_info *concat[GPMI_MAX_CHIPS]; + struct mtd_info *concat_mtd; +}; + +extern struct gpmi_nand_timing gpmi_safe_timing; + +struct gpmi_ncb { + u32 fingerprint1; + struct gpmi_nand_timing timing; + u32 pagesize; + u32 page_plus_oob_size; + u32 sectors_per_block; + u32 sector_in_page_mask; + u32 sector_to_page_shift; + u32 num_nands; + u32 reserved[3]; + u32 fingerprint2; /* offset 0x2C */ +}; + +struct gpmi_ldlb { + u32 fingerprint1; + u16 major, minor, sub, reserved; + u32 nand_bitmap; + u32 reserved1[7]; + u32 fingerprint2; + struct { + u32 fw_starting_nand; + u32 fw_starting_sector; + u32 fw_sector_stride; + u32 fw_sectors_total; + } fw[2]; + u16 fw_major, fw_minor, fw_sub, fw_reserved; + u32 bbt_blk; + u32 bbt_blk_backup; +}; + +static inline void gpmi_block_mark_as(struct nand_chip *chip, + int block, int mark) +{ + u32 o; + int shift = (block & 0x03) << 1, + index = block >> 2; + + if (chip->bbt) { + mark &= 0x03; + + o = chip->bbt[index]; + o &= ~(0x03 << shift); + o |= (mark << shift); + chip->bbt[index] = o; + } +} + +static inline int gpmi_block_badness(struct nand_chip *chip, + int block) +{ + u32 o; + int shift = (block & 0x03) << 1, + index = block >> 2; + + if (chip->bbt) { + o = (chip->bbt[index] >> shift) & 0x03; + pr_debug("%s: block = %d, o = %d\n", __func__, block, o); + return o; + } + return -1; +} + +#ifdef CONFIG_STMP3XXX_UNIQUE_ID +int __init gpmi_uid_init(const char *name, struct mtd_info *mtd, + u_int32_t start, u_int32_t size); +void gpmi_uid_remove(const char *name); +#else +#define gpmi_uid_init(name, mtd, start, size) +#define gpmi_uid_remove(name) +#endif + +#endif diff --git a/drivers/mtd/nand/imx_nfc.c b/drivers/mtd/nand/imx_nfc.c new file mode 100644 index 000000000000..65f72b4e468a --- /dev/null +++ b/drivers/mtd/nand/imx_nfc.c @@ -0,0 +1,8286 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mtd/partitions.h> +#include <linux/io.h> + +#define DRIVER_VERSION "1.0" + +/* Define this macro to enable event reporting. */ + +#define EVENT_REPORTING + +/* + * For detailed information that will be helpful in understanding this driver, + * see: + * + * Documentation/imx_nfc.txt + */ + +/* + * Macros that describe NFC hardware have names of the form: + * + * NFC_* + * + * Macros that apply only to specific versions of the NFC have names of the + * following form: + * + * NFC_<M>_<N>_* + * + * where: + * + * <M> is the major version of the NFC hardware. + * <N> is the minor version of the NFC hardware. + * + * The minor version can be 'X', which means that the macro applies to *all* + * NFCs of the same major version. + * + * For NFC versions with only one set of registers, macros that give offsets + * against the base address have names of the form: + * + * *<RegisterName>_REG_OFF + * + * Macros that give the position of a field's LSB within a given register have + * names of the form: + * + * *<RegisterName>_<FieldName>_POS + * + * Macros that mask a field within a given register have names of the form: + * + * *<RegisterName>_<FieldName>_MSK + */ + +/* + * Macro definitions for ALL NFC versions. + */ + +#define NFC_MAIN_BUF_SIZE (512) + +/* + * Macro definitions for version 1.0 NFCs. + */ + +#define NFC_1_0_BUF_SIZE_REG_OFF (0x00) +#define NFC_1_0_BUF_ADDR_REG_OFF (0x04) +#define NFC_1_0_FLASH_ADDR_REG_OFF (0x06) +#define NFC_1_0_FLASH_CMD_REG_OFF (0x08) +#define NFC_1_0_CONFIG_REG_OFF (0x0A) +#define NFC_1_0_ECC_STATUS_RESULT_REG_OFF (0x0C) +#define NFC_1_0_RSLTMAIN_AREA_REG_OFF (0x0E) +#define NFC_1_0_RSLTSPARE_AREA_REG_OFF (0x10) +#define NFC_1_0_WRPROT_REG_OFF (0x12) +#define NFC_1_0_UNLOCKSTART_BLKADDR_REG_OFF (0x14) +#define NFC_1_0_UNLOCKEND_BLKADDR_REG_OFF (0x16) +#define NFC_1_0_NF_WRPRST_REG_OFF (0x18) + +#define NFC_1_0_CONFIG1_REG_OFF (0x1A) +#define NFC_1_0_CONFIG1_NF_CE_POS (7) +#define NFC_1_0_CONFIG1_NF_CE_MSK (0x1 << 7) + +#define NFC_1_0_CONFIG2_REG_OFF (0x1C) + +/* +* Macro definitions for version 2.X NFCs. +*/ + +#define NFC_2_X_FLASH_ADDR_REG_OFF (0x06) +#define NFC_2_X_FLASH_CMD_REG_OFF (0x08) +#define NFC_2_X_CONFIG_REG_OFF (0x0A) + +#define NFC_2_X_WR_PROT_REG_OFF (0x12) + +#define NFC_2_X_NF_WR_PR_ST_REG_OFF (0x18) + +#define NFC_2_X_CONFIG2_REG_OFF (0x1C) +#define NFC_2_X_CONFIG2_FCMD_POS (0) +#define NFC_2_X_CONFIG2_FCMD_MSK (0x1 << 0) +#define NFC_2_X_CONFIG2_FADD_POS (1) +#define NFC_2_X_CONFIG2_FADD_MSK (0x1 << 1) +#define NFC_2_X_CONFIG2_FDI_POS (2) +#define NFC_2_X_CONFIG2_FDI_MSK (0x1 << 2) +#define NFC_2_X_CONFIG2_FDO_POS (3) +#define NFC_2_X_CONFIG2_FDO_MSK (0x7 << 3) +#define NFC_2_X_CONFIG2_INT_POS (15) +#define NFC_2_X_CONFIG2_INT_MSK (0x1 << 15) + +/* +* Macro definitions for version 2.0 NFCs. +*/ + +#define NFC_2_0_BUF_ADDR_REG_OFF (0x04) +#define NFC_2_0_BUF_ADDR_RBA_POS (0) +#define NFC_2_0_BUF_ADDR_RBA_MSK (0xf << 0) + +#define NFC_2_0_ECC_STATUS_REG_OFF (0x0C) +#define NFC_2_0_ECC_STATUS_NOSER1_POS (0) +#define NFC_2_0_ECC_STATUS_NOSER1_MSK (0xF << 0) +#define NFC_2_0_ECC_STATUS_NOSER2_POS (4) +#define NFC_2_0_ECC_STATUS_NOSER2_MSK (0xF << 4) +#define NFC_2_0_ECC_STATUS_NOSER3_POS (8) +#define NFC_2_0_ECC_STATUS_NOSER3_MSK (0xF << 8) +#define NFC_2_0_ECC_STATUS_NOSER4_POS (12) +#define NFC_2_0_ECC_STATUS_NOSER4_MSK (0xF << 12) + +#define NFC_2_0_CONFIG1_REG_OFF (0x1A) +#define NFC_2_0_CONFIG1_SP_EN_POS (2) +#define NFC_2_0_CONFIG1_SP_EN_MSK (0x1 << 2) +#define NFC_2_0_CONFIG1_ECC_EN_POS (3) +#define NFC_2_0_CONFIG1_ECC_EN_MSK (0x1 << 3) +#define NFC_2_0_CONFIG1_INT_MSK_POS (4) +#define NFC_2_0_CONFIG1_INT_MSK_MSK (0x1 << 4) +#define NFC_2_0_CONFIG1_NF_BIG_POS (5) +#define NFC_2_0_CONFIG1_NF_BIG_MSK (0x1 << 5) +#define NFC_2_0_CONFIG1_NFC_RST_POS (6) +#define NFC_2_0_CONFIG1_NFC_RST_MSK (0x1 << 6) +#define NFC_2_0_CONFIG1_NF_CE_POS (7) +#define NFC_2_0_CONFIG1_NF_CE_MSK (0x1 << 7) +#define NFC_2_0_CONFIG1_ONE_CYLE_POS (8) +#define NFC_2_0_CONFIG1_ONE_CYLE_MSK (0x1 << 8) +#define NFC_2_0_CONFIG1_MLC_POS (9) +#define NFC_2_0_CONFIG1_MLC_MSK (0x1 << 9) + +#define NFC_2_0_UNLOCK_START_REG_OFF (0x14) +#define NFC_2_0_UNLOCK_END_REG_OFF (0x16) + +/* +* Macro definitions for version 2.1 NFCs. +*/ + +#define NFC_2_1_BUF_ADDR_REG_OFF (0x04) +#define NFC_2_1_BUF_ADDR_RBA_POS (0) +#define NFC_2_1_BUF_ADDR_RBA_MSK (0x7 << 0) +#define NFC_2_1_BUF_ADDR_CS_POS (4) +#define NFC_2_1_BUF_ADDR_CS_MSK (0x3 << 4) + +#define NFC_2_1_ECC_STATUS_REG_OFF (0x0C) +#define NFC_2_1_ECC_STATUS_NOSER1_POS (0) +#define NFC_2_1_ECC_STATUS_NOSER1_MSK (0xF << 0) +#define NFC_2_1_ECC_STATUS_NOSER2_POS (4) +#define NFC_2_1_ECC_STATUS_NOSER2_MSK (0xF << 4) +#define NFC_2_1_ECC_STATUS_NOSER3_POS (8) +#define NFC_2_1_ECC_STATUS_NOSER3_MSK (0xF << 8) +#define NFC_2_1_ECC_STATUS_NOSER4_POS (12) +#define NFC_2_1_ECC_STATUS_NOSER4_MSK (0xF << 12) + +#define NFC_2_1_CONFIG1_REG_OFF (0x1A) +#define NFC_2_1_CONFIG1_ECC_MODE_POS (0) +#define NFC_2_1_CONFIG1_ECC_MODE_MSK (0x1 << 0) +#define NFC_2_1_CONFIG1_DMA_MODE_POS (1) +#define NFC_2_1_CONFIG1_DMA_MODE_MSK (0x1 << 1) +#define NFC_2_1_CONFIG1_SP_EN_POS (2) +#define NFC_2_1_CONFIG1_SP_EN_MSK (0x1 << 2) +#define NFC_2_1_CONFIG1_ECC_EN_POS (3) +#define NFC_2_1_CONFIG1_ECC_EN_MSK (0x1 << 3) +#define NFC_2_1_CONFIG1_INT_MSK_POS (4) +#define NFC_2_1_CONFIG1_INT_MSK_MSK (0x1 << 4) +#define NFC_2_1_CONFIG1_NF_BIG_POS (5) +#define NFC_2_1_CONFIG1_NF_BIG_MSK (0x1 << 5) +#define NFC_2_1_CONFIG1_NFC_RST_POS (6) +#define NFC_2_1_CONFIG1_NFC_RST_MSK (0x1 << 6) +#define NFC_2_1_CONFIG1_NF_CE_POS (7) +#define NFC_2_1_CONFIG1_NF_CE_MSK (0x1 << 7) +#define NFC_2_1_CONFIG1_SYM_POS (8) +#define NFC_2_1_CONFIG1_SYM_MSK (0x1 << 8) +#define NFC_2_1_CONFIG1_PPB_POS (9) +#define NFC_2_1_CONFIG1_PPB_MSK (0x3 << 9) +#define NFC_2_1_CONFIG1_FP_INT_POS (11) +#define NFC_2_1_CONFIG1_FP_INT_MSK (0x1 << 11) + +#define NFC_2_1_UNLOCK_START_0_REG_OFF (0x20) +#define NFC_2_1_UNLOCK_END_0_REG_OFF (0x22) +#define NFC_2_1_UNLOCK_START_1_REG_OFF (0x24) +#define NFC_2_1_UNLOCK_END_1_REG_OFF (0x26) +#define NFC_2_1_UNLOCK_START_2_REG_OFF (0x28) +#define NFC_2_1_UNLOCK_END_2_REG_OFF (0x2A) +#define NFC_2_1_UNLOCK_START_3_REG_OFF (0x2C) +#define NFC_2_1_UNLOCK_END_3_REG_OFF (0x2E) + +/* +* Macro definitions for version 3.X NFCs. +*/ + +/* +* Macro definitions for version 3.1 NFCs. +*/ + +#define NFC_3_1_FLASH_ADDR_CMD_REG_OFF (0x00) +#define NFC_3_1_CONFIG1_REG_OFF (0x04) +#define NFC_3_1_ECC_STATUS_RESULT_REG_OFF (0x08) +#define NFC_3_1_LAUNCH_NFC_REG_OFF (0x0C) + +#define NFC_3_1_WRPROT_REG_OFF (0x00) +#define NFC_3_1_WRPROT_UNLOCK_BLK_ADD0_REG_OFF (0x04) +#define NFC_3_1_CONFIG2_REG_OFF (0x14) +#define NFC_3_1_IPC_REG_OFF (0x18) + +/* +* Macro definitions for version 3.2 NFCs. +*/ + +#define NFC_3_2_CMD_REG_OFF (0x00) + +#define NFC_3_2_ADD0_REG_OFF (0x04) +#define NFC_3_2_ADD1_REG_OFF (0x08) +#define NFC_3_2_ADD2_REG_OFF (0x0C) +#define NFC_3_2_ADD3_REG_OFF (0x10) +#define NFC_3_2_ADD4_REG_OFF (0x14) +#define NFC_3_2_ADD5_REG_OFF (0x18) +#define NFC_3_2_ADD6_REG_OFF (0x1C) +#define NFC_3_2_ADD7_REG_OFF (0x20) +#define NFC_3_2_ADD8_REG_OFF (0x24) +#define NFC_3_2_ADD9_REG_OFF (0x28) +#define NFC_3_2_ADD10_REG_OFF (0x2C) +#define NFC_3_2_ADD11_REG_OFF (0x30) + +#define NFC_3_2_CONFIG1_REG_OFF (0x34) +#define NFC_3_2_CONFIG1_SP_EN_POS (0) +#define NFC_3_2_CONFIG1_SP_EN_MSK (0x1 << 0) +#define NFC_3_2_CONFIG1_NF_CE_POS (1) +#define NFC_3_2_CONFIG1_NF_CE_MSK (0x1 << 1) +#define NFC_3_2_CONFIG1_RST_POS (2) +#define NFC_3_2_CONFIG1_RST_MSK (0x1 << 2) +#define NFC_3_2_CONFIG1_RBA_POS (4) +#define NFC_3_2_CONFIG1_RBA_MSK (0x7 << 4) +#define NFC_3_2_CONFIG1_ITER_POS (8) +#define NFC_3_2_CONFIG1_ITER_MSK (0xf << 8) +#define NFC_3_2_CONFIG1_CS_POS (12) +#define NFC_3_2_CONFIG1_CS_MSK (0x7 << 12) +#define NFC_3_2_CONFIG1_STATUS_POS (16) +#define NFC_3_2_CONFIG1_STATUS_MSK (0xFFFF<<16) + +#define NFC_3_2_ECC_STATUS_REG_OFF (0x38) +#define NFC_3_2_ECC_STATUS_NOBER1_POS (0) +#define NFC_3_2_ECC_STATUS_NOBER1_MSK (0xF << 0) +#define NFC_3_2_ECC_STATUS_NOBER2_POS (4) +#define NFC_3_2_ECC_STATUS_NOBER2_MSK (0xF << 4) +#define NFC_3_2_ECC_STATUS_NOBER3_POS (8) +#define NFC_3_2_ECC_STATUS_NOBER3_MSK (0xF << 8) +#define NFC_3_2_ECC_STATUS_NOBER4_POS (12) +#define NFC_3_2_ECC_STATUS_NOBER4_MSK (0xF << 12) +#define NFC_3_2_ECC_STATUS_NOBER5_POS (16) +#define NFC_3_2_ECC_STATUS_NOBER5_MSK (0xF << 16) +#define NFC_3_2_ECC_STATUS_NOBER6_POS (20) +#define NFC_3_2_ECC_STATUS_NOBER6_MSK (0xF << 20) +#define NFC_3_2_ECC_STATUS_NOBER7_POS (24) +#define NFC_3_2_ECC_STATUS_NOBER7_MSK (0xF << 24) +#define NFC_3_2_ECC_STATUS_NOBER8_POS (28) +#define NFC_3_2_ECC_STATUS_NOBER8_MSK (0xF << 28) + + +#define NFC_3_2_STATUS_SUM_REG_OFF (0x3C) +#define NFC_3_2_STATUS_SUM_NAND_SUM_POS (0x0) +#define NFC_3_2_STATUS_SUM_NAND_SUM_MSK (0xFF << 0) +#define NFC_3_2_STATUS_SUM_ECC_SUM_POS (8) +#define NFC_3_2_STATUS_SUM_ECC_SUM_MSK (0xFF << 8) + +#define NFC_3_2_LAUNCH_REG_OFF (0x40) +#define NFC_3_2_LAUNCH_FCMD_POS (0) +#define NFC_3_2_LAUNCH_FCMD_MSK (0x1 << 0) +#define NFC_3_2_LAUNCH_FADD_POS (1) +#define NFC_3_2_LAUNCH_FADD_MSK (0x1 << 1) +#define NFC_3_2_LAUNCH_FDI_POS (2) +#define NFC_3_2_LAUNCH_FDI_MSK (0x1 << 2) +#define NFC_3_2_LAUNCH_FDO_POS (3) +#define NFC_3_2_LAUNCH_FDO_MSK (0x7 << 3) +#define NFC_3_2_LAUNCH_AUTO_PROG_POS (6) +#define NFC_3_2_LAUNCH_AUTO_PROG_MSK (0x1 << 6) +#define NFC_3_2_LAUNCH_AUTO_READ_POS (7) +#define NFC_3_2_LAUNCH_AUTO_READ_MSK (0x1 << 7) +#define NFC_3_2_LAUNCH_AUTO_ERASE_POS (9) +#define NFC_3_2_LAUNCH_AUTO_ERASE_MSK (0x1 << 9) +#define NFC_3_2_LAUNCH_COPY_BACK0_POS (10) +#define NFC_3_2_LAUNCH_COPY_BACK0_MSK (0x1 << 10) +#define NFC_3_2_LAUNCH_COPY_BACK1_POS (11) +#define NFC_3_2_LAUNCH_COPY_BACK1_MSK (0x1 << 11) +#define NFC_3_2_LAUNCH_AUTO_STATUS_POS (12) +#define NFC_3_2_LAUNCH_AUTO_STATUS_MSK (0x1 << 12) + +#define NFC_3_2_WRPROT_REG_OFF (0x00) +#define NFC_3_2_WRPROT_WPC_POS (0) +#define NFC_3_2_WRPROT_WPC_MSK (0x7 << 0) +#define NFC_3_2_WRPROT_CS2L_POS (3) +#define NFC_3_2_WRPROT_CS2L_MSK (0x7 << 3) +#define NFC_3_2_WRPROT_BLS_POS (6) +#define NFC_3_2_WRPROT_BLS_MSK (0x3 << 6) +#define NFC_3_2_WRPROT_LTS0_POS (8) +#define NFC_3_2_WRPROT_LTS0_MSK (0x1 << 8) +#define NFC_3_2_WRPROT_LS0_POS (9) +#define NFC_3_2_WRPROT_LS0_MSK (0x1 << 9) +#define NFC_3_2_WRPROT_US0_POS (10) +#define NFC_3_2_WRPROT_US0_MSK (0x1 << 10) +#define NFC_3_2_WRPROT_LTS1_POS (11) +#define NFC_3_2_WRPROT_LTS1_MSK (0x1 << 11) +#define NFC_3_2_WRPROT_LS1_POS (12) +#define NFC_3_2_WRPROT_LS1_MSK (0x1 << 12) +#define NFC_3_2_WRPROT_US1_POS (13) +#define NFC_3_2_WRPROT_US1_MSK (0x1 << 13) +#define NFC_3_2_WRPROT_LTS2_POS (14) +#define NFC_3_2_WRPROT_LTS2_MSK (0x1 << 14) +#define NFC_3_2_WRPROT_LS2_POS (15) +#define NFC_3_2_WRPROT_LS2_MSK (0x1 << 15) +#define NFC_3_2_WRPROT_US2_POS (16) +#define NFC_3_2_WRPROT_US2_MSK (0x1 << 16) +#define NFC_3_2_WRPROT_LTS3_POS (17) +#define NFC_3_2_WRPROT_LTS3_MSK (0x1 << 17) +#define NFC_3_2_WRPROT_LS3_POS (18) +#define NFC_3_2_WRPROT_LS3_MSK (0x1 << 18) +#define NFC_3_2_WRPROT_US3_POS (19) +#define NFC_3_2_WRPROT_US3_MSK (0x1 << 19) +#define NFC_3_2_WRPROT_LTS4_POS (20) +#define NFC_3_2_WRPROT_LTS4_MSK (0x1 << 20) +#define NFC_3_2_WRPROT_LS4_POS (21) +#define NFC_3_2_WRPROT_LS4_MSK (0x1 << 21) +#define NFC_3_2_WRPROT_US4_POS (22) +#define NFC_3_2_WRPROT_US4_MSK (0x1 << 22) +#define NFC_3_2_WRPROT_LTS5_POS (23) +#define NFC_3_2_WRPROT_LTS5_MSK (0x1 << 23) +#define NFC_3_2_WRPROT_LS5_POS (24) +#define NFC_3_2_WRPROT_LS5_MSK (0x1 << 24) +#define NFC_3_2_WRPROT_US5_POS (25) +#define NFC_3_2_WRPROT_US5_MSK (0x1 << 25) +#define NFC_3_2_WRPROT_LTS6_POS (26) +#define NFC_3_2_WRPROT_LTS6_MSK (0x1 << 26) +#define NFC_3_2_WRPROT_LS6_POS (27) +#define NFC_3_2_WRPROT_LS6_MSK (0x1 << 27) +#define NFC_3_2_WRPROT_US6_POS (28) +#define NFC_3_2_WRPROT_US6_MSK (0x1 << 28) +#define NFC_3_2_WRPROT_LTS7_POS (29) +#define NFC_3_2_WRPROT_LTS7_MSK (0x1 << 29) +#define NFC_3_2_WRPROT_LS7_POS (30) +#define NFC_3_2_WRPROT_LS7_MSK (0x1 << 30) +#define NFC_3_2_WRPROT_US7_POS (31) +#define NFC_3_2_WRPROT_US7_MSK (0x1 << 31) + +#define NFC_3_2_UNLOCK_BLK_ADD0_REG_OFF (0x04) +#define NFC_3_2_UNLOCK_BLK_ADD0_USBA0_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD0_USBA0_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD0_UEBA0_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD0_UEBA0_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD1_REG_OFF (0x08) +#define NFC_3_2_UNLOCK_BLK_ADD1_USBA1_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD1_USBA1_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD1_UEBA1_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD1_UEBA1_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD2_REG_OFF (0x0C) +#define NFC_3_2_UNLOCK_BLK_ADD2_USBA2_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD2_USBA2_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD2_UEBA2_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD2_UEBA2_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD3_REG_OFF (0x10) +#define NFC_3_2_UNLOCK_BLK_ADD3_USBA3_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD3_USBA3_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD3_UEBA3_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD3_UEBA3_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD4_REG_OFF (0x14) +#define NFC_3_2_UNLOCK_BLK_ADD4_USBA4_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD4_USBA4_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD4_UEBA4_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD4_UEBA4_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD5_REG_OFF (0x18) +#define NFC_3_2_UNLOCK_BLK_ADD5_USBA5_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD5_USBA5_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD5_UEBA5_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD5_UEBA5_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD6_REG_OFF (0x1C) +#define NFC_3_2_UNLOCK_BLK_ADD6_USBA6_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD6_USBA6_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD6_UEBA6_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD6_UEBA6_MSK (0xFFFF<<16) + +#define NFC_3_2_UNLOCK_BLK_ADD7_REG_OFF (0x20) +#define NFC_3_2_UNLOCK_BLK_ADD7_USBA7_POS (0) +#define NFC_3_2_UNLOCK_BLK_ADD7_USBA7_MSK (0xFFFF << 0) +#define NFC_3_2_UNLOCK_BLK_ADD7_UEBA7_POS (16) +#define NFC_3_2_UNLOCK_BLK_ADD7_UEBA7_MSK (0xFFFF<<16) + +#define NFC_3_2_CONFIG2_REG_OFF (0x24) +#define NFC_3_2_CONFIG2_PS_POS (0) +#define NFC_3_2_CONFIG2_PS_MSK (0x3 << 0) +#define NFC_3_2_CONFIG2_SYM_POS (2) +#define NFC_3_2_CONFIG2_SYM_MSK (0x1 << 2) +#define NFC_3_2_CONFIG2_ECC_EN_POS (3) +#define NFC_3_2_CONFIG2_ECC_EN_MSK (0x1 << 3) +#define NFC_3_2_CONFIG2_CMD_PHASES_POS (4) +#define NFC_3_2_CONFIG2_CMD_PHASES_MSK (0x1 << 4) +#define NFC_3_2_CONFIG2_ADDR_PHASES0_POS (5) +#define NFC_3_2_CONFIG2_ADDR_PHASES0_MSK (0x1 << 5) +#define NFC_3_2_CONFIG2_ECC_MODE_POS (6) +#define NFC_3_2_CONFIG2_ECC_MODE_MSK (0x1 << 6) +#define NFC_3_2_CONFIG2_PPB_POS (7) +#define NFC_3_2_CONFIG2_PPB_MSK (0x3 << 7) +#define NFC_3_2_CONFIG2_EDC_POS (9) +#define NFC_3_2_CONFIG2_EDC_MSK (0x7 << 9) +#define NFC_3_2_CONFIG2_ADDR_PHASES1_POS (12) +#define NFC_3_2_CONFIG2_ADDR_PHASES1_MSK (0x3 << 12) +#define NFC_3_2_CONFIG2_AUTO_DONE_MSK_POS (14) +#define NFC_3_2_CONFIG2_AUTO_DONE_MSK_MSK (0x1 << 14) +#define NFC_3_2_CONFIG2_INT_MSK_POS (15) +#define NFC_3_2_CONFIG2_INT_MSK_MSK (0x1 << 15) +#define NFC_3_2_CONFIG2_SPAS_POS (16) +#define NFC_3_2_CONFIG2_SPAS_MSK (0xFF << 16) +#define NFC_3_2_CONFIG2_ST_CMD_POS (24) +#define NFC_3_2_CONFIG2_ST_CMD_MSK (0xFF << 24) + +#define NFC_3_2_CONFIG3_REG_OFF (0x28) +#define NFC_3_2_CONFIG3_ADD_OP_POS (0) +#define NFC_3_2_CONFIG3_ADD_OP_MSK (0x3 << 0) +#define NFC_3_2_CONFIG3_TOO_POS (2) +#define NFC_3_2_CONFIG3_TOO_MSK (0x1 << 2) +#define NFC_3_2_CONFIG3_FW_POS (3) +#define NFC_3_2_CONFIG3_FW_MSK (0x1 << 3) +#define NFC_3_2_CONFIG3_SB2R_POS (4) +#define NFC_3_2_CONFIG3_SB2R_MSK (0x7 << 4) +#define NFC_3_2_CONFIG3_NF_BIG_POS (7) +#define NFC_3_2_CONFIG3_NF_BIG_MSK (0x1 << 7) +#define NFC_3_2_CONFIG3_SBB_POS (8) +#define NFC_3_2_CONFIG3_SBB_MSK (0x7 << 8) +#define NFC_3_2_CONFIG3_DMA_MODE_POS (11) +#define NFC_3_2_CONFIG3_DMA_MODE_MSK (0x1 << 11) +#define NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS (12) +#define NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK (0x7 << 12) +#define NFC_3_2_CONFIG3_RBB_MODE_POS (15) +#define NFC_3_2_CONFIG3_RBB_MODE_MSK (0x1 << 15) +#define NFC_3_2_CONFIG3_FMP_POS (16) +#define NFC_3_2_CONFIG3_FMP_MSK (0xF << 16) +#define NFC_3_2_CONFIG3_NO_SDMA_POS (20) +#define NFC_3_2_CONFIG3_NO_SDMA_MSK (0x1 << 20) + +#define NFC_3_2_IPC_REG_OFF (0x2C) +#define NFC_3_2_IPC_CREQ_POS (0) +#define NFC_3_2_IPC_CREQ_MSK (0x1 << 0) +#define NFC_3_2_IPC_CACK_POS (1) +#define NFC_3_2_IPC_CACK_MSK (0x1 << 1) +#define NFC_3_2_IPC_DMA_STATUS_POS (26) +#define NFC_3_2_IPC_DMA_STATUS_MSK (0x3 << 26) +#define NFC_3_2_IPC_RB_B_POS (28) +#define NFC_3_2_IPC_RB_B_MSK (0x1 << 28) +#define NFC_3_2_IPC_LPS_POS (29) +#define NFC_3_2_IPC_LPS_MSK (0x1 << 29) +#define NFC_3_2_IPC_AUTO_PROG_DONE_POS (30) +#define NFC_3_2_IPC_AUTO_PROG_DONE_MSK (0x1 << 30) +#define NFC_3_2_IPC_INT_POS (31) +#define NFC_3_2_IPC_INT_MSK (0x1 << 31) + +#define NFC_3_2_AXI_ERR_ADD_REG_OFF (0x30) + +/** + * enum override - Choices for overrides. + * + * Some functions of this driver can be overriden at run time. This is a + * convenient enumerated type for all such options. + */ + +enum override { + NEVER = -1, + DRIVER_CHOICE = 0, + ALWAYS = 1, +}; + +/** + * struct physical_geometry - Physical geometry description. + * + * This structure describes the physical geometry of the medium. + * + * @chip_count: The number of chips in the medium. + * @chip_size: The size, in bytes, of a single chip (excluding the + * out-of-band bytes). + * @block_size: The size, in bytes, of a single block (excluding the + * out-of-band bytes). + * @page_data_size: The size, in bytes, of the data area in a page (excluding + * the out-of-band bytes). + * @page_oob_size: The size, in bytes, of the out-of-band area in a page. + */ + +struct physical_geometry { + unsigned int chip_count; + uint64_t chip_size; + unsigned int block_size; + unsigned int page_data_size; + unsigned int page_oob_size; +}; + +/** + * struct nfc_geometry - NFC geometry description. + * + * This structure describes the NFC's view of the medium geometry, which may be + * different from the physical geometry (for example, we treat pages that are + * physically 2K+112 as if they are 2K+64). + * + * @page_data_size: The size of the data area in a page (excluding the + * out-of-band bytes). This is almost certain to be the same + * as the physical data size. + * @page_oob_size: The size of the out-of-band area in a page. This may be + * different from the physical OOB size. + * @ecc_algorithm: The name of the ECC algorithm (e.g., "Reed-Solomon" or + * "BCH"). + * @ecc_strength: A number that describes the strength of the ECC algorithm. + * For example, various i.MX SoC's support ECC-1, ECC-4 or + * ECC-8 of the Reed-Solomon ECC algorithm. + * @buffer_count: The number of main/spare buffers used with this geometry. + * @spare_buf_size: The number of bytes held in each spare buffer. + * @spare_buf_spill: The number of extra bytes held in the last spare buffer. + * @mtd_layout: The MTD layout that best matches the geometry described by + * the rest of this structure. The logical layer might not use + * this structure, especially when interleaving. + */ + +struct nfc_geometry { + const unsigned int page_data_size; + const unsigned int page_oob_size; + const char *ecc_algorithm; + const int ecc_strength; + const unsigned int buffer_count; + const unsigned int spare_buf_size; + const unsigned int spare_buf_spill; + struct nand_ecclayout mtd_layout; +}; + +/** + * struct logical_geometry - Logical geometry description. + * + * This structure describes the logical geometry we expose to MTD. This geometry + * may be different from the physical or NFC geometries, especially when + * interleaving. + * + * @chip_count: The number of chips in the medium. + * @chip_size: The size, in bytes, of a single chip (excluding the + * out-of-band bytes). + * @usable_size: The size, in bytes, of the medium that MTD can actually + * use. This may be less than the chip size multiplied by the + * number of chips. + * @block_size: The size, in bytes, of a single block (excluding the + * out-of-band bytes). + * @page_data_size: The size of the data area in a page (excluding the + * out-of-band bytes). + * @page_oob_size: The size of the out-of-band area in a page. + */ + +struct logical_geometry { + unsigned int chip_count; + uint32_t chip_size; + uint32_t usable_size; + unsigned int block_size; + unsigned int page_data_size; + unsigned int page_oob_size; + struct nand_ecclayout *mtd_layout; +}; + +/** + * struct imx_nfc_data - i.MX NFC per-device data. + * + * Note that the "device" managed by this driver represents the NAND Flash + * controller *and* the NAND Flash medium behind it. Thus, the per-device data + * structure has information about the controller, the chips to which it is + * connected, and properties of the medium as a whole. + * + * @dev: A pointer to the owning struct device. + * @pdev: A pointer to the owning struct platform_device. + * @pdata: A pointer to the device's platform data. + * @buffers: A pointer to the NFC buffers. + * @primary_regs: A pointer to the NFC primary registers. + * @secondary_regs: A pointer to the NFC secondary registers. + * @clock: A pointer to the NFC struct clk. + * @interrupt: The NFC interrupt number. + * @physical_geometry: A description of the medium's physical geometry. + * @nfc: A pointer to the NFC HAL. + * @nfc_geometry: A description of the medium geometry as viewed by the + * NFC. + * @done: The struct completion we use to handle interrupts. + * @logical_geometry: A description of the logical geometry exposed to MTD. + * @interrupt_override: Can override how the driver uses interrupts when + * waiting for the NFC. + * @auto_op_override: Can override whether the driver uses automatic NFC + * operations. + * @inject_ecc_error: Indicates that the driver should inject an ECC error in + * the next read operation that uses ECC. User space + * programs can set this value through the sysfs node of + * the same name. If this value is less than zero, the + * driver will inject an uncorrectable ECC error. If this + * value is greater than zero, the driver will inject that + * number of correctable errors, capped at whatever + * possible maximum currently applies. + * @current_chip: The chip currently selected by the NAND Fash MTD HIL. + * A negative value indicates that no chip is selected. + * We use this field to detect when the HIL begins and + * ends essential transactions. This helps us to know when + * we should turn the NFC clock on or off. + * @command: The last command the HIL tried to send by calling + * cmdfunc(). Later functions use this information to + * adjust their behavior. The sentinel value ~0 indicates + * no command. + * @command_is_new: Indicates the command has just come down to cmdfunc() + * from the HIL and hasn't yet been handled. Other + * functions use this to adjust their behavior. + * @page_address: The current page address of interest. For reads, this + * information arrives in calls to cmdfunc(), but we don't + * actually use it until later. + * @nand: The data structure that represents this NAND Flash + * medium to the MTD NAND Flash system. + * @mtd: The data structure that represents this NAND Flash + * medium to MTD. + * @partitions: A pointer to a set of partitions collected from one of + * several possible sources (e.g., the boot loader, the + * kernel command line, etc.). See the global variable + * partition_source_types for the list of partition + * sources we examine. If this member is NULL, then no + * partitions were discovered. + * @partition_count: The number of discovered partitions. + */ + +struct imx_nfc_data { + + /* System Interface */ + struct device *dev; + struct platform_device *pdev; + + /* Platform Configuration */ + struct imx_nfc_platform_data *pdata; + void *buffers; + void *primary_regs; + void *secondary_regs; + struct clk *clock; + unsigned int interrupt; + + /* Flash Hardware */ + struct physical_geometry physical_geometry; + + /* NFC HAL and NFC Utilities */ + struct nfc_hal *nfc; + struct nfc_geometry *nfc_geometry; + struct completion done; + + /* Medium Abstraction Layer */ + struct logical_geometry logical_geometry; + enum override interrupt_override; + enum override auto_op_override; + int inject_ecc_error; + + /* MTD Interface Layer */ + int current_chip; + unsigned int command; + int command_is_new; + int page_address; + + /* NAND Flash MTD */ + struct nand_chip nand; + + /* MTD */ + struct mtd_info mtd; + struct mtd_partition *partitions; + unsigned int partition_count; + +}; + +/** + * struct nfc_hal - i.MX NFC HAL + * + * This structure embodies an abstract interface to the underlying NFC hardware. + * + * @major_version: The major version number of the NFC to which this + * structure applies. + * @minor_version: The minor version number of the NFC to which this + * structure applies. + * @max_chip_count: The maximum number of chips the NFC can possibly + * support. This may *not* be the actual number of chips + * currently connected. This value is constant for NFC's + * of a given version. + * @max_buffer_count: The number of main/spare buffers available in the NFC's + * memory. This value is constant for NFC's of a given + * version. + * @spare_buf_stride: The stride, in bytes, from the beginning of one spare + * buffer to the beginning of the next one. This value is + * constant for NFC's of a given version. + * @has_secondary_regs: Indicates if the NFC has a secondary register set that + * must be mapped in. + * @can_be_symmetric: Indicates if the NFC supports a "symmetric" clock. When + * the clock is "symmetric," the hardware waits one NFC + * clock for every read/write cycle. When the clock is + * "asymmetric," the hardware waits two NFC clocks for + * every read/write cycle. + * @init: Initializes the NFC and any version-specific data + * structures. This function will be called after + * everything has been set up for communication with the + * NFC itself, but before the platform has set up off-chip + * communication. Thus, this function must not attempt to + * communicate with the NAND Flash hardware. A non-zero + * return value indicates failure. + * @set_geometry: Based on the physical geometry, this function selects + * an NFC geometry structure and configures the NFC + * hardware to match. A non-zero return value indicates + * failure. + * @exit: Shuts down the NFC and cleans up version-specific data + * structures. This function will be called after the + * platform has shut down off-chip communication but while + * communication with the NFC still works. + * @set_closest_cycle: Configures the hardware to make the NAND Flash bus + * cycle period as close as possible to the given cycle + * period. This function is called during boot up and may + * assume that, at the time it's called, the parent clock + * is running at the highest rate it will ever run. Thus, + * this function need never worry that the NAND Flash bus + * will run faster and potentially make it impossible to + * communicate with the NAND Flash device -- it will only + * run slower. + * @mask_interrupt: Masks the NFC's interrupt. + * @unmask_interrupt: Unmasks the NFC's interrupt. + * @clear_interrupt: Clears the NFC's interrupt. + * @is_interrupting: Returns true if the NFC is interrupting. + * @is_ready: Returns true if all the chips in the medium are ready. + * This member may be set to NULL, which indicates that + * the underlying NFC hardware doesn't expose ready/busy + * signals. + * @set_force_ce: If passed true, forces the hardware chip enable signal + * for the current chip to be asserted always. If passed + * false, causes the chip enable signal to be asserted + * only during I/O. + * @set_ecc: Sets ECC on or off. + * @get_ecc_status: Examines the hardware ECC status and returns: + * == 0 => No errors. + * > 0 => The number of corrected errors. + * < 0 => There were uncorrectable errors. + * @get_symmetric: Gets the current symmetric clock setting. For versions + * that don't support symmetric clocks, this function + * always returns false. + * @set_symmetric: For versions that support symmetric clocks, sets + * whether or not the clock is symmetric. + * @select_chip: Selects the current chip. + * @command_cycle: Sends a command code and then returns immediately + * *without* waiting for the NFC to finish. + * @write_cycle: Applies a single write cycle to the current chip, + * sending the given byte, and waiting for the NFC to + * finish. + * @read_cycle: Applies a single read cycle to the current chip and + * returns the result, necessarily waiting for the NFC to + * finish. The width of the result is the same as the + * width of the Flash bus. + * @read_page: Applies read cycles to the current chip to read an + * entire page into the NFC. Note that ECC is enabled or + * disabled with the set_ecc function pointer (see above). + * This function waits for the NFC to finish before + * returning. + * @send_page: Applies write cycles to send an entire page from the + * NFC to the current chip. Note that ECC is enabled or + * disabled with the set_ecc function pointer (see above). + * This function waits for the NFC to finish before + * returning. + * @start_auto_read: Starts an automatic read operation. A NULL pointer + * indicates automatic read operations aren't available + * with this NFC version. + * @wait_for_auto_read: Blocks until an automatic read operation is ready for + * the CPU to copy a page out of the NFC. + * @resume_auto_read: Resumes an automatic read operation after the CPU has + * copied a page out. + * @start_auto_write: Starts an automatic write operation. A NULL pointer + * indicates automatic write operations aren't available + * with this NFC version. + * @wait_for_auto_write: Blocks until an automatic write operation is ready for + * the CPU to copy a page into the NFC. + * @start_auto_erase: Starts an automatic erase operation. A NULL pointer + * indicates automatic erase operations aren't available + * with this NFC version. + */ + +struct nfc_hal { + const unsigned int major_version; + const unsigned int minor_version; + const unsigned int max_chip_count; + const unsigned int max_buffer_count; + const unsigned int spare_buf_stride; + const int has_secondary_regs; + const int can_be_symmetric; + int (*init) (struct imx_nfc_data *); + int (*set_geometry) (struct imx_nfc_data *); + void (*exit) (struct imx_nfc_data *); + int (*set_closest_cycle) (struct imx_nfc_data *, unsigned ns); + void (*mask_interrupt) (struct imx_nfc_data *); + void (*unmask_interrupt) (struct imx_nfc_data *); + void (*clear_interrupt) (struct imx_nfc_data *); + int (*is_interrupting) (struct imx_nfc_data *); + int (*is_ready) (struct imx_nfc_data *); + void (*set_force_ce) (struct imx_nfc_data *, int on); + void (*set_ecc) (struct imx_nfc_data *, int on); + int (*get_ecc_status) (struct imx_nfc_data *); + int (*get_symmetric) (struct imx_nfc_data *); + void (*set_symmetric) (struct imx_nfc_data *, int on); + void (*select_chip) (struct imx_nfc_data *, int chip); + void (*command_cycle) (struct imx_nfc_data *, unsigned cmd); + void (*write_cycle) (struct imx_nfc_data *, unsigned byte); + unsigned (*read_cycle) (struct imx_nfc_data *); + void (*read_page) (struct imx_nfc_data *); + void (*send_page) (struct imx_nfc_data *); + int (*start_auto_read) (struct imx_nfc_data *, + unsigned start, unsigned count, unsigned column, unsigned page); + int (*wait_for_auto_read) (struct imx_nfc_data *); + int (*resume_auto_read) (struct imx_nfc_data *); + int (*start_auto_write) (struct imx_nfc_data *, + unsigned start, unsigned count, unsigned column, unsigned page); + int (*wait_for_auto_write)(struct imx_nfc_data *); + int (*start_auto_erase) (struct imx_nfc_data *, + unsigned start, unsigned count, unsigned page); +}; + +/* + * This variable controls whether or not probing is enabled. If false, then + * the driver will refuse to probe. The "enable" module parameter controls the + * value of this variable. + */ + +static int imx_nfc_module_enable = true; + +#ifdef EVENT_REPORTING + +/* + * This variable controls whether the driver reports event information by + * printing to the console. The "report_events" module parameter controls the + * value of this variable. + */ + +static int imx_nfc_module_report_events; /* implicitly initialized false*/ + +#endif + +/* + * This variable potentially overrides the driver's choice to interleave. The + * "interleave_override" module parameter controls the value of this variable. + */ + +static enum override imx_nfc_module_interleave_override = DRIVER_CHOICE; + +/* + * When set, this variable forces the driver to use the bytewise copy functions + * to get data in and out of the NFC. This is intended for testing, not typical + * use. + */ + +static int imx_nfc_module_force_bytewise_copy; /* implicitly initialized false*/ + +/* + * The list of algorithms we use to get partition information from the + * environment. + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *partition_source_types[] = { "cmdlinepart", NULL }; +#endif + +/* + * The following structures describe the NFC geometries we support. + * + * Notice that pieces of some structure definitions are commented out and edited + * because various parts of the MTD system can't handle the way our hardware + * formats the out-of-band area. + * + * Here are the problems: + * + * - struct nand_ecclayout expects no more than 64 ECC bytes. + * + * The eccpos member of struct nand_ecclayout can't hold more than 64 ECC + * byte positions. Some of our formats have more so, unedited, they won't + * even compile. We comment out all ECC byte positions after the 64th one. + * + * - struct nand_ecclayout expects no more than 8 free spans. + * + * The oobfree member of struct nand_ecclayout can't hold more than 8 free + * spans. Some of our formats have more so, unedited, they won't even + * compile. We comment out all free spans after the eighth one. + * + * - The MEMGETOOBSEL command in the mtdchar driver. + * + * The mtdchar ioctl command MEMGETOOBSEL checks the number of ECC bytes + * against the length of the eccpos array in struct nand_oobinfo + * (see include/mtd/mtd-abi.h). This array can handle up to 32 ECC bytes, + * but some of our formats have more. + * + * To make this work, we cap the value assigned to eccbytes at 32. + * + * Notice that struct nand_oobinfo, used by mtdchar, is *different* from the + * struct nand_ecclayout that MTD uses internally. The latter structure + * can accomodate up to 64 ECC byte positions. Thus, we declare up to 64 + * ECC byte positions here, even if the advertised number of ECC bytes is + * less. + * + * This command is obsolete and, if no one used it, we wouldn't care about + * this problem. Unfortunately The nandwrite program uses it, and everyone + * expects nandwrite to work (it's how everyone usually lays down their + * JFFS2 file systems). + */ + +static struct nfc_geometry nfc_geometry_512_16_RS_ECC1 = { + .page_data_size = 512, + .page_oob_size = 16, + .ecc_algorithm = "Reed-Solomon", + .ecc_strength = 1, + .buffer_count = 1, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobavail = 11, + .oobfree = { {0, 6}, {11, 5} }, + } +}; + +static struct nfc_geometry nfc_geometry_512_16_RS_ECC4 = { + .page_data_size = 512, + .page_oob_size = 16, + .ecc_algorithm = "Reed-Solomon", + .ecc_strength = 4, + .buffer_count = 1, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 7, + .oobfree = { {0, 7} }, + } +}; + +static struct nfc_geometry nfc_geometry_512_16_BCH_ECC4 = { + .page_data_size = 512, + .page_oob_size = 16, + .ecc_algorithm = "BCH", + .ecc_strength = 4, + .buffer_count = 1, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 8, + .eccpos = {8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 8, + .oobfree = { {0, 8} }, + } +}; + +static struct nfc_geometry nfc_geometry_2K_64_RS_ECC4 = { + .page_data_size = 2048, + .page_oob_size = 64, + .ecc_algorithm = "Reed-Solomon", + .ecc_strength = 4, + .buffer_count = 4, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 32 /*9 * 4*/, /* See notes above. */ + .eccpos = { + + (0*16)+7 , (0*16)+8 , (0*16)+9 , (0*16)+10, (0*16)+11, + (0*16)+12, (0*16)+13, (0*16)+14, (0*16)+15, + + (1*16)+7 , (1*16)+8 , (1*16)+9 , (1*16)+10, (1*16)+11, + (1*16)+12, (1*16)+13, (1*16)+14, (1*16)+15, + + (2*16)+7 , (2*16)+8 , (2*16)+9 , (2*16)+10, (2*16)+11, + (2*16)+12, (2*16)+13, (2*16)+14, (2*16)+15, + + (3*16)+7 , (3*16)+8 , (3*16)+9 , (3*16)+10, (3*16)+11, + (3*16)+12, (3*16)+13, (3*16)+14, (3*16)+15, + + }, + .oobavail = 7 * 4, + .oobfree = { + {(0*16)+0, 7}, + {(1*16)+0, 7}, + {(2*16)+0, 7}, + {(3*16)+0, 7}, + }, + } +}; + +static struct nfc_geometry nfc_geometry_2K_64_BCH_ECC4 = { + .page_data_size = 2048, + .page_oob_size = 64, + .ecc_algorithm = "BCH", + .ecc_strength = 4, + .buffer_count = 4, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 8 * 4, + .eccpos = { + + (0*16)+8 , (0*16)+9 , (0*16)+10, (0*16)+11, + (0*16)+12, (0*16)+13, (0*16)+14, (0*16)+15, + + (1*16)+8 , (1*16)+9 , (1*16)+10, (1*16)+11, + (1*16)+12, (1*16)+13, (1*16)+14, (1*16)+15, + + (2*16)+8 , (2*16)+9 , (2*16)+10, (2*16)+11, + (2*16)+12, (2*16)+13, (2*16)+14, (2*16)+15, + + (3*16)+8 , (3*16)+9 , (3*16)+10, (3*16)+11, + (3*16)+12, (3*16)+13, (3*16)+14, (3*16)+15, + + }, + .oobavail = 8 * 4, + .oobfree = { + {(0*16)+0, 8}, + {(1*16)+0, 8}, + {(2*16)+0, 8}, + {(3*16)+0, 8}, + }, + } +}; + +static struct nfc_geometry nfc_geometry_4K_128_BCH_ECC4 = { + .page_data_size = 4096, + .page_oob_size = 128, + .ecc_algorithm = "BCH", + .ecc_strength = 4, + .buffer_count = 8, + .spare_buf_size = 16, + .spare_buf_spill = 0, + .mtd_layout = { + .eccbytes = 8 * 8, + .eccpos = { + + (0*16)+8 , (0*16)+9 , (0*16)+10, (0*16)+11, + (0*16)+12, (0*16)+13, (0*16)+14, (0*16)+15, + + (1*16)+8 , (1*16)+9 , (1*16)+10, (1*16)+11, + (1*16)+12, (1*16)+13, (1*16)+14, (1*16)+15, + + (2*16)+8 , (2*16)+9 , (2*16)+10, (2*16)+11, + (2*16)+12, (2*16)+13, (2*16)+14, (2*16)+15, + + (3*16)+8 , (3*16)+9 , (3*16)+10, (3*16)+11, + (3*16)+12, (3*16)+13, (3*16)+14, (3*16)+15, + + (4*16)+8 , (4*16)+9 , (4*16)+10, (4*16)+11, + (4*16)+12, (4*16)+13, (4*16)+14, (4*16)+15, + + (5*16)+8 , (5*16)+9 , (5*16)+10, (5*16)+11, + (5*16)+12, (5*16)+13, (5*16)+14, (5*16)+15, + + (6*16)+8 , (6*16)+9 , (6*16)+10, (6*16)+11, + (6*16)+12, (6*16)+13, (6*16)+14, (6*16)+15, + + (7*16)+8 , (7*16)+9 , (7*16)+10, (7*16)+11, + (7*16)+12, (7*16)+13, (7*16)+14, (7*16)+15, + + }, + .oobavail = 8 * 8, + .oobfree = { + {(0*16)+0, 8}, + {(1*16)+0, 8}, + {(2*16)+0, 8}, + {(3*16)+0, 8}, + {(4*16)+0, 8}, + {(5*16)+0, 8}, + {(6*16)+0, 8}, + {(7*16)+0, 8}, + }, + } +}; + +static struct nfc_geometry nfc_geometry_4K_218_BCH_ECC8 = { + .page_data_size = 4096, + .page_oob_size = 218, + .ecc_algorithm = "BCH", + .ecc_strength = 8, + .buffer_count = 8, + .spare_buf_size = 26, + .spare_buf_spill = 10, + .mtd_layout = { + .eccbytes = 32 /*10 * 8*/, /* See notes above. */ + .eccpos = { + + (0*26)+12, (0*26)+13, (0*26)+14, (0*26)+15, (0*26)+16, + (0*26)+17, (0*26)+18, (0*26)+19, (0*26)+20, (0*26)+21, + + (1*26)+12, (1*26)+13, (1*26)+14, (1*26)+15, (1*26)+16, + (1*26)+17, (1*26)+18, (1*26)+19, (1*26)+20, (1*26)+21, + + (2*26)+12, (2*26)+13, (2*26)+14, (2*26)+15, (2*26)+16, + (2*26)+17, (2*26)+18, (2*26)+19, (2*26)+20, (2*26)+21, + + (3*26)+12, (3*26)+13, (3*26)+14, (3*26)+15, (3*26)+16, + (3*26)+17, (3*26)+18, (3*26)+19, (3*26)+20, (3*26)+21, + + (4*26)+12, (4*26)+13, (4*26)+14, (4*26)+15, (4*26)+16, + (4*26)+17, (4*26)+18, (4*26)+19, (4*26)+20, (4*26)+21, + + (5*26)+12, (5*26)+13, (5*26)+14, (5*26)+15, (5*26)+16, + (5*26)+17, (5*26)+18, (5*26)+19, (5*26)+20, (5*26)+21, + + (6*26)+12, (6*26)+13, (6*26)+14, (6*26)+15, + /* See notes above. + (6*26)+16, + (6*26)+17, (6*26)+18, (6*26)+19, (6*26)+20, (6*26)+21, + + (7*26)+12, (7*26)+13, (7*26)+14, (7*26)+15, (7*26)+16, + (7*26)+17, (7*26)+18, (7*26)+19, (7*26)+20, (7*26)+21, + */ + + }, + .oobavail = 96 /*(16 * 8) + 10*/, /* See notes above. */ + .oobfree = { + {(0*26)+0, 12}, {(0*26)+22, 4}, + {(1*26)+0, 12}, {(1*26)+22, 4}, + {(2*26)+0, 12}, {(2*26)+22, 4}, + {(3*26)+0, 48}, /* See notes above. */ + /* See notes above. + {(3*26)+0, 12}, {(3*26)+22, 4}, + {(4*26)+0, 12}, {(4*26)+22, 4}, + {(5*26)+0, 12}, {(5*26)+22, 4}, + {(6*26)+0, 12}, {(6*26)+22, 4}, + {(7*26)+0, 12}, {(7*26)+22, 4 + 10}, + */ + }, + } +}; + +/* + * When the logical geometry differs from the NFC geometry (e.g., + * interleaving), we synthesize a layout rather than use the one that comes with + * the NFC geometry. See mal_set_logical_geometry(). + */ + +static struct nand_ecclayout synthetic_layout; + +/* + * These structures describe how the BBT code will find block marks in the OOB + * area of a page. Don't be confused by the fact that this is the same type used + * to describe bad block tables. Some of the same information is needed, so the + * designers use the same structure for two conceptually distinct functions. + */ + +static uint8_t block_mark_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr small_page_block_mark_descriptor = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = block_mark_pattern, +}; + +static struct nand_bbt_descr large_page_block_mark_descriptor = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 0, + .len = 2, + .pattern = block_mark_pattern, +}; + +/* + * Bad block table descriptors for the main and mirror tables. + */ + +static uint8_t bbt_main_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t bbt_mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descriptor = { + .options = + NAND_BBT_LASTBLOCK | + NAND_BBT_CREATE | + NAND_BBT_WRITE | + NAND_BBT_2BIT | + NAND_BBT_VERSION | + NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_main_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descriptor = { + .options = + NAND_BBT_LASTBLOCK | + NAND_BBT_CREATE | + NAND_BBT_WRITE | + NAND_BBT_2BIT | + NAND_BBT_VERSION | + NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_mirror_pattern, +}; + +#ifdef EVENT_REPORTING + +/** + * struct event - A single record in the event trace. + * + * @time: The time at which the event occurred. + * @nesting: Indicates function call nesting. + * @description: A description of the event. + */ + +struct event { + ktime_t time; + unsigned int nesting; + char *description; +}; + +/** + * The event trace. + * + * @overhead: The delay to take a time stamp and nothing else. + * @nesting: The current nesting level. + * @overflow: Indicates the trace overflowed. + * @next: Index of the next event to write. + * @events: The array of events. + */ + +#define MAX_EVENT_COUNT (200) + +static struct { + ktime_t overhead; + int nesting; + int overflow; + unsigned int next; + struct event events[MAX_EVENT_COUNT]; +} event_trace; + +/** + * reset_event_trace() - Resets the event trace. + */ +static void reset_event_trace(void) +{ + event_trace.nesting = 0; + event_trace.overflow = false; + event_trace.next = 0; +} + +/** + * add_event() - Adds an event to the event trace. + * + * @description: A description of the event. + * @delta: A delta to the nesting level for this event [-1, 0, 1]. + */ +static inline void add_event(char *description, int delta) +{ + struct event *event; + + if (!imx_nfc_module_report_events) + return; + + if (event_trace.overflow) + return; + + if (event_trace.next >= MAX_EVENT_COUNT) { + event_trace.overflow = true; + return; + } + + event = event_trace.events + event_trace.next; + + event->time = ktime_get(); + + event->description = description; + + if (!delta) + event->nesting = event_trace.nesting; + else if (delta < 0) { + event->nesting = event_trace.nesting - 1; + event_trace.nesting -= 2; + } else { + event->nesting = event_trace.nesting + 1; + event_trace.nesting += 2; + } + + if (event_trace.nesting < 0) + event_trace.nesting = 0; + + event_trace.next++; + +} + +/** + * add_state_event_l() - Adds an event to display some state. + * + * @address: The address to examine. + * @mask: A mask to apply to the contents of the given address. + * @clear: An event message to add if the result is zero. + * @not_zero: An event message to add if the result is not zero. + */ +static void add_state_event_l(void *address, uint32_t mask, + char *zero, char *not_zero) +{ + int state; + state = !!(__raw_readl(address) & mask); + if (state) + add_event(not_zero, 0); + else + add_event(zero, 0); +} + +/** + * start_event_trace() - Starts an event trace and adds the first event. + * + * @description: A description of the first event. + */ +static void start_event_trace(char *description) +{ + + ktime_t t0; + ktime_t t1; + + if (!imx_nfc_module_report_events) + return; + + reset_event_trace(); + + t0 = ktime_get(); + t1 = ktime_get(); + + event_trace.overhead = ktime_sub(t1, t0); + + add_event(description, 1); + +} + +/** + * dump_event_trace() - Dumps the event trace. + */ +static void dump_event_trace(void) +{ + unsigned int i; + time_t seconds; + long nanoseconds; + char line[100]; + int o; + struct event *first_event; + struct event *last_event; + struct event *matching_event; + struct event *event; + ktime_t delta; + + /* Check if event reporting is turned off. */ + + if (!imx_nfc_module_report_events) + return; + + /* Print important facts about this event trace. */ + + printk(KERN_DEBUG "\n+--------------\n"); + + printk(KERN_DEBUG "| Overhead : [%d:%d]\n", + event_trace.overhead.tv.sec, + event_trace.overhead.tv.nsec); + + if (!event_trace.next) { + printk(KERN_DEBUG "| No Events\n"); + return; + } + + first_event = event_trace.events; + last_event = event_trace.events + (event_trace.next - 1); + + delta = ktime_sub(last_event->time, first_event->time); + printk(KERN_DEBUG "| Elapsed Time: [%d:%d]\n", + delta.tv.sec, delta.tv.nsec); + + if (event_trace.overflow) + printk(KERN_DEBUG "| Overflow!\n"); + + /* Print the events in this history. */ + + for (i = 0, event = event_trace.events; + i < event_trace.next; i++, event++) { + + /* Get the delta between this event and the previous event. */ + + if (!i) { + seconds = 0; + nanoseconds = 0; + } else { + delta = ktime_sub(event[0].time, event[-1].time); + seconds = delta.tv.sec; + nanoseconds = delta.tv.nsec; + } + + /* Print the current event. */ + + o = 0; + + o = snprintf(line, sizeof(line) - o, "| [%ld:% 10ld]%*s %s", + seconds, nanoseconds, + event->nesting, "", + event->description); + /* Check if this is the last event in a nested series. */ + + if (i && (event[0].nesting < event[-1].nesting)) { + + for (matching_event = event - 1;; matching_event--) { + + if (matching_event < event_trace.events) { + matching_event = 0; + break; + } + + if (matching_event->nesting == event->nesting) + break; + + } + + if (matching_event) { + delta = ktime_sub(event->time, + matching_event->time); + o += snprintf(line + o, sizeof(line) - o, + " <%d:%d]", delta.tv.sec, + delta.tv.nsec); + } + + } + + /* Check if this is the first event in a nested series. */ + + if ((i < event_trace.next - 1) && + (event[0].nesting < event[1].nesting)) { + + for (matching_event = event + 1;; matching_event++) { + + if (matching_event >= + (event_trace.events+event_trace.next)) { + matching_event = 0; + break; + } + + if (matching_event->nesting == event->nesting) + break; + + } + + if (matching_event) { + delta = ktime_sub(matching_event->time, + event->time); + o += snprintf(line + o, sizeof(line) - o, + " [%d:%d>", delta.tv.sec, + delta.tv.nsec); + } + + } + + printk(KERN_DEBUG "%s\n", line); + + } + + printk(KERN_DEBUG "+--------------\n"); + +} + +/** + * stop_event_trace() - Stops an event trace. + * + * @description: A description of the last event. + */ +static void stop_event_trace(char *description) +{ + struct event *event; + + if (!imx_nfc_module_report_events) + return; + + /* + * We want the end of the trace, no matter what happens. If the trace + * has already overflowed, or is about to, just jam this event into the + * last spot. Otherwise, add this event like any other. + */ + + if (event_trace.overflow || (event_trace.next >= MAX_EVENT_COUNT)) { + event = event_trace.events + (MAX_EVENT_COUNT - 1); + event->time = ktime_get(); + event->description = description; + event->nesting = 0; + } else { + add_event(description, -1); + } + + dump_event_trace(); + reset_event_trace(); + +} + +#else /* EVENT_REPORTING */ + +#define start_event_trace(description) do {} while (0) +#define add_event(description, delta) do {} while (0) +#define add_state_event_l(address, mask, zero, not_zero) do {} while (0) +#define stop_event_trace(description) do {} while (0) +#define dump_event_trace() do {} while (0) + +#endif /* EVENT_REPORTING */ + +/** + * unimplemented() - Announces intentionally unimplemented features. + * + * @this: Per-device data. + * @msg: A message about the unimplemented feature. + */ +static inline void unimplemented(struct imx_nfc_data *this, const char * msg) +{ + dev_err(this->dev, "Intentionally unimplemented: %s", msg); +} + +/** + * raw_read_mask_w() - Reads masked bits in a 16-bit hardware register. + */ +static inline uint16_t raw_read_mask_w(uint16_t mask, void *address) +{ + return __raw_readw(address) & mask; +} + +/** + * raw_set_mask_w() - Sets bits in a 16-bit hardware register. + */ +static inline void raw_set_mask_w(uint16_t mask, void *address) +{ + __raw_writew(__raw_readw(address) | mask, address); +} + +/** + * raw_clr_mask_w() - Clears bits in a 16-bit hardware register. + */ +static inline void raw_clr_mask_w(uint16_t mask, void *address) +{ + __raw_writew(__raw_readw(address) & (~mask), address); +} + +/** + * raw_read_mask_l() - Reads masked bits in a 32-bit hardware register. + */ +static inline uint32_t raw_read_mask_l(uint32_t mask, void *address) +{ + return __raw_readl(address) & mask; +} + +/** + * raw_set_mask_l() - Sets bits in a 32-bit hardware register. + */ +static inline void raw_set_mask_l(uint32_t mask, void *address) +{ + __raw_writel(__raw_readl(address) | mask, address); +} + +/** + * raw_clr_mask_l() - Clears bits in a 32-bit hardware register. + */ +static inline void raw_clr_mask_l(uint32_t mask, void *address) +{ + __raw_writel(__raw_readl(address) & (~mask), address); +} + +/** + * is_large_page_chip() - Returns true for large page media. + * + * @this: Per-device data. + */ +static inline int is_large_page_chip(struct imx_nfc_data *this) +{ + return (this->physical_geometry.page_data_size > 512); +} + +/** + * is_small_page_chip() - Returns true for small page media. + * + * @this: Per-device data. + */ +static inline int is_small_page_chip(struct imx_nfc_data *this) +{ + return !is_large_page_chip(this); +} + +/** + * get_cycle_in_ns() - Returns the given device's cycle period, in ns. + * + * @this: Per-device data. + */ +static inline unsigned get_cycle_in_ns(struct imx_nfc_data *this) +{ + unsigned long cycle_in_ns; + + cycle_in_ns = 1000000000 / clk_get_rate(this->clock); + + if (!this->nfc->get_symmetric(this)) + cycle_in_ns *= 2; + + return cycle_in_ns; + +} + +/** + * nfc_util_set_best_cycle() - Sets the closest possible NAND Flash bus cycle. + * + * This function computes the clock setup that will best approximate the given + * target Flash bus cycle period. + * + * For some NFC versions, we can make the clock "symmetric." When the clock + * is "symmetric," the hardware waits one NFC clock for every read/write cycle. + * When the clock is "asymmetric," the hardware waits two NFC clocks for every + * read/write cycle. Thus, making the clock asymmetric essentially divides the + * NFC clock by two. + * + * We compute the target frequency that matches the given target period. We then + * discover the closest available match with that frequency and the closest + * available match with double that frequency (for use with an asymmetric + * clock). We implement the best choice of original clock and symmetric or + * asymmetric setting, preferring symmetric clocks. + * + * @this: Per-device data. + * @ns: The target cycle period, in nanoseconds. + * @no_asym: Disallow making the clock asymmetric. + * @no_sym: Disallow making the clock symmetric. + */ +static int nfc_util_set_best_cycle(struct imx_nfc_data *this, + unsigned int ns, int no_asym, int no_sym) +{ + unsigned long target_hz; + long symmetric_hz; + long symmetric_delta_hz; + long asymmetric_hz; + long asymmetric_delta_hz; + unsigned long best_hz; + int best_symmetry_setting; + struct device *dev = this->dev; + + /* The target cycle period must be greater than zero. */ + + if (!ns) + return -EINVAL; + + /* Compute the target frequency. */ + + target_hz = 1000000000 / ns; + + /* Find out how close we can get with a symmetric clock. */ + + if (!no_sym && this->nfc->can_be_symmetric) + symmetric_hz = clk_round_rate(this->clock, target_hz); + else + symmetric_hz = -EINVAL; + + /* Find out how close we can get with an asymmetric clock. */ + + if (!no_asym) + asymmetric_hz = clk_round_rate(this->clock, target_hz * 2); + else + asymmetric_hz = -EINVAL; + + /* Does anything work at all? */ + + if ((symmetric_hz == -EINVAL) && (asymmetric_hz == -EINVAL)) { + dev_err(dev, "Can't support Flash bus cycle of %uns\n", ns); + return -EINVAL; + } + + /* Discover the best match. */ + + if ((symmetric_hz != -EINVAL) && (asymmetric_hz != -EINVAL)) { + + symmetric_delta_hz = target_hz - symmetric_hz; + asymmetric_delta_hz = target_hz - (asymmetric_hz / 2); + + if (symmetric_delta_hz <= asymmetric_delta_hz) + best_symmetry_setting = true; + else + best_symmetry_setting = false; + + } else if (symmetric_hz != -EINVAL) { + best_symmetry_setting = true; + } else { + best_symmetry_setting = false; + } + + best_hz = best_symmetry_setting ? symmetric_hz : asymmetric_hz; + + /* Implement the best match. */ + + this->nfc->set_symmetric(this, best_symmetry_setting); + + return clk_set_rate(this->clock, best_hz); + +} + +/** + * nfc_util_wait_for_the_nfc() - Waits for the NFC to finish an operation. + * + * @this: Per-device data. + * @use_irq: Indicates that we should wait for an interrupt rather than polling + * and delaying. + */ +static void nfc_util_wait_for_the_nfc(struct imx_nfc_data *this, int use_irq) +{ + unsigned spin_count; + struct device *dev = this->dev; + + /* Apply the override, if any. */ + + switch (this->interrupt_override) { + + case NEVER: + use_irq = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + use_irq = true; + break; + + } + + /* Check if we're using interrupts. */ + + if (use_irq) { + + /* + * If control arrives here, the caller wants to use interrupts. + * Presumably, this operation is known to take a very long time. + */ + + if (this->nfc->is_interrupting(this)) { + add_event("Waiting for the NFC (early interrupt)", 1); + this->nfc->clear_interrupt(this); + } else { + add_event("Waiting for the NFC (interrupt)", 1); + this->nfc->unmask_interrupt(this); + wait_for_completion(&this->done); + } + + add_event("NFC done", -1); + + } else { + + /* + * If control arrives here, the caller doesn't want to use + * interrupts. Presumably, this operation is too quick to + * justify the overhead. Leave the interrupt masked, and loop + * until the interrupt bit lights up, or we time out. + * + * We spin for a maximum of about 2ms before declaring a time + * out. No operation we could possibly spin on should take that + * long. + */ + + spin_count = 2000; + + add_event("Waiting for the NFC (polling)", 1); + + for (; spin_count > 0; spin_count--) { + + if (this->nfc->is_interrupting(this)) { + this->nfc->clear_interrupt(this); + add_event("NFC done", -1); + return; + } + + udelay(1); + + } + + /* Timed out. */ + + add_event("Timed out", -1); + + dev_err(dev, "[wait_for_the_nfc] ===== Time Out =====\n"); + dump_event_trace(); + + } + +} + +/** + * nfc_util_bytewise_copy_from_nfc_mem() - Copies bytes from the NFC memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_bytewise_copy_from_nfc_mem(const void *from, + void *to, size_t n) +{ + unsigned int i; + const uint8_t *f = from; + uint8_t *t = to; + uint16_t *p; + uint16_t x; + + for (i = 0; i < n; i++, f++, t++) { + + p = (uint16_t *) (((unsigned long) f) & ~((unsigned long) 1)); + + x = __raw_readw(p); + + if (((unsigned long) f) & 0x1) + *t = (x >> 8) & 0xff; + else + *t = (x >> 0) & 0xff; + + } + +} + +/** + * nfc_util_bytewise_copy_to_nfc_mem() - Copies bytes to the NFC memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_bytewise_copy_to_nfc_mem(const void *from, + void *to, size_t n) +{ + unsigned int i; + const uint8_t *f = from; + uint8_t *t = to; + uint16_t *p; + uint16_t x; + + for (i = 0; i < n; i++, f++, t++) { + + p = (uint16_t *) (((unsigned long) t) & ~((unsigned long) 1)); + + x = __raw_readw(p); + + if (((unsigned long) t) & 0x1) + ((uint8_t *)(&x))[1] = *f; + else + ((uint8_t *)(&x))[0] = *f; + + __raw_writew(x, p); + + } + +} + +/** + * nfc_util_copy_from_nfc_mem() - Copies from the NFC memory to main memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_copy_from_nfc_mem(const void *from, void *to, size_t n) +{ + unsigned int chunk_count; + + /* + * Check if we're testing bytewise copies. + */ + + if (imx_nfc_module_force_bytewise_copy) + goto force_bytewise_copy; + + /* + * We'd like to use memcpy to get data out of the NFC but, because that + * memory responds only to 16- and 32-byte reads, we can only do so + * safely if both the start and end of both the source and destination + * are perfectly aligned on 4-byte boundaries. + */ + + if (!(((unsigned long) from) & 0x3) && !(((unsigned long) to) & 0x3)) { + + /* + * If control arrives here, both the source and destination are + * aligned on 4-byte boundaries. Compute the number of whole, + * 4-byte chunks we can move. + */ + + chunk_count = n / 4; + + /* + * Move all the chunks we can, and then update the pointers and + * byte count to show what's left. + */ + + if (chunk_count) { + memcpy(to, from, chunk_count * 4); + from += chunk_count * 4; + to += chunk_count * 4; + n -= chunk_count * 4; + } + + } + + /* + * Move what's left. + */ + +force_bytewise_copy: + + nfc_util_bytewise_copy_from_nfc_mem(from, to, n); + +} + +/** + * nfc_util_copy_to_nfc_mem() - Copies from main memory to the NFC memory. + * + * @from: A pointer to the source memory. + * @to: A pointer to the destination memory. + * @size: The number of bytes to copy. + */ +static void nfc_util_copy_to_nfc_mem(const void *from, void *to, size_t n) +{ + unsigned int chunk_count; + + /* + * Check if we're testing bytewise copies. + */ + + if (imx_nfc_module_force_bytewise_copy) + goto force_bytewise_copy; + + /* + * We'd like to use memcpy to get data into the NFC but, because that + * memory responds only to 16- and 32-byte writes, we can only do so + * safely if both the start and end of both the source and destination + * are perfectly aligned on 4-byte boundaries. + */ + + if (!(((unsigned long) from) & 0x3) && !(((unsigned long) to) & 0x3)) { + + /* + * If control arrives here, both the source and destination are + * aligned on 4-byte boundaries. Compute the number of whole, + * 4-byte chunks we can move. + */ + + chunk_count = n / 4; + + /* + * Move all the chunks we can, and then update the pointers and + * byte count to show what's left. + */ + + if (chunk_count) { + memcpy(to, from, chunk_count * 4); + from += chunk_count * 4; + to += chunk_count * 4; + n -= chunk_count * 4; + } + + } + + /* + * Move what's left. + */ + +force_bytewise_copy: + + nfc_util_bytewise_copy_to_nfc_mem(from, to, n); + +} + +/** + * nfc_util_copy_from_the_nfc() - Copies bytes out of the NFC. + * + * This function makes the data in the NFC look like a contiguous, model page. + * + * @this: Per-device data. + * @start: The index of the starting byte in the NFC. + * @buf: A pointer to the destination buffer. + * @len: The number of bytes to copy out. + */ +static void nfc_util_copy_from_the_nfc(struct imx_nfc_data *this, + unsigned int start, uint8_t *buf, unsigned int len) +{ + unsigned int i; + unsigned int count; + unsigned int offset; + unsigned int data_size; + unsigned int oob_size; + unsigned int total_size; + void *spare_base; + unsigned int first_spare; + void *from; + struct nfc_geometry *geometry = this->nfc_geometry; + + /* + * During initialization, the HIL will attempt to read ID bytes. For + * some NFC hardware versions, the ID bytes are deposited in the NFC + * memory, so this function will be called to deliver them. At that + * point, we won't know the NFC geometry. That's OK because we're only + * going to be reading a byte at a time. + * + * If we don't yet know the NFC geometry, just plug in some values that + * make things work for now. + */ + + if (unlikely(!geometry)) { + data_size = NFC_MAIN_BUF_SIZE; + oob_size = 0; + } else { + data_size = geometry->page_data_size; + oob_size = geometry->page_oob_size; + } + + total_size = data_size + oob_size; + + /* Validate. */ + + if ((start >= total_size) || ((start + len) > total_size)) { + dev_err(this->dev, "Bad copy from NFC memory: [%u, %u]\n", + start, len); + return; + } + + /* Check if we're copying anything at all. */ + + if (!len) + return; + + /* Check if anything comes from the main area. */ + + if (start < data_size) { + + /* Compute the bytes to copy from the main area. */ + + count = min(len, data_size - start); + + /* Copy. */ + + nfc_util_copy_from_nfc_mem(this->buffers + start, buf, count); + + buf += count; + start += count; + len -= count; + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the base address of the spare buffers. */ + + spare_base = this->buffers + + (this->nfc->max_buffer_count * NFC_MAIN_BUF_SIZE); + + /* Discover in which spare buffer the copying begins. */ + + first_spare = (start - data_size) / geometry->spare_buf_size; + + /* Check if anything comes from the regular spare buffer area. */ + + if (first_spare < geometry->buffer_count) { + + /* Start copying from spare buffers. */ + + for (i = first_spare; i < geometry->buffer_count; i++) { + + /* Compute the offset into this spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size * i)); + + /* Compute the address of that offset. */ + + from = spare_base + offset + + (this->nfc->spare_buf_stride * i); + + /* Compute the bytes to copy from this spare area. */ + + count = min(len, geometry->spare_buf_size - offset); + + /* Copy. */ + + nfc_util_copy_from_nfc_mem(from, buf, count); + + buf += count; + start += count; + len -= count; + + } + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the offset into the extra spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size*geometry->buffer_count)); + + /* Compute the address of that offset. */ + + from = spare_base + offset + + (this->nfc->spare_buf_stride * geometry->buffer_count); + + /* Compute the bytes to copy from the extra spare area. */ + + count = min(len, geometry->spare_buf_spill - offset); + + /* Copy. */ + + nfc_util_copy_from_nfc_mem(from, buf, count); + +} + +/** + * nfc_util_copy_to_the_nfc() - Copies bytes into the NFC memory. + * + * This function makes the data in the NFC look like a contiguous, model page. + * + * @this: Per-device data. + * @buf: A pointer to the source buffer. + * @start: The index of the starting byte in the NFC memory. + * @len: The number of bytes to copy in. + */ +static void nfc_util_copy_to_the_nfc(struct imx_nfc_data *this, + const uint8_t *buf, unsigned int start, unsigned int len) +{ + unsigned int i; + unsigned int count; + unsigned int offset; + unsigned int data_size; + unsigned int oob_size; + unsigned int total_size; + void *spare_base; + unsigned int first_spare; + void *to; + struct nfc_geometry *geometry = this->nfc_geometry; + + /* Establish some important facts. */ + + data_size = geometry->page_data_size; + oob_size = geometry->page_oob_size; + total_size = data_size + oob_size; + + /* Validate. */ + + if ((start >= total_size) || ((start + len) > total_size)) { + dev_err(this->dev, "Bad copy to NFC memory: [%u, %u]\n", + start, len); + return; + } + + /* Check if we're copying anything at all. */ + + if (!len) + return; + + /* Check if anything goes to the main area. */ + + if (start < data_size) { + + /* Compute the bytes to copy to the main area. */ + + count = min(len, data_size - start); + + /* Copy. */ + + nfc_util_copy_to_nfc_mem(buf, this->buffers + start, count); + + buf += count; + start += count; + len -= count; + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the base address of the spare buffers. */ + + spare_base = this->buffers + + (this->nfc->max_buffer_count * NFC_MAIN_BUF_SIZE); + + /* Discover in which spare buffer the copying begins. */ + + first_spare = (start - data_size) / geometry->spare_buf_size; + + /* Check if anything goes to the regular spare buffer area. */ + + if (first_spare < geometry->buffer_count) { + + /* Start copying to spare buffers. */ + + for (i = first_spare; i < geometry->buffer_count; i++) { + + /* Compute the offset into this spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size * i)); + + /* Compute the address of that offset. */ + + to = spare_base + offset + + (this->nfc->spare_buf_stride * i); + + /* Compute the bytes to copy to this spare area. */ + + count = min(len, geometry->spare_buf_size - offset); + + /* Copy. */ + + nfc_util_copy_to_nfc_mem(buf, to, count); + + buf += count; + start += count; + len -= count; + + } + + } + + /* Check if we're done. */ + + if (!len) + return; + + /* Compute the offset into the extra spare area. */ + + offset = start - + (data_size + (geometry->spare_buf_size*geometry->buffer_count)); + + /* Compute the address of that offset. */ + + to = spare_base + offset + + (this->nfc->spare_buf_stride * geometry->buffer_count); + + /* Compute the bytes to copy to the extra spare area. */ + + count = min(len, geometry->spare_buf_spill - offset); + + /* Copy. */ + + nfc_util_copy_to_nfc_mem(buf, to, count); + +} + +/** + * nfc_util_isr() - i.MX NFC ISR. + * + * @irq: The arriving interrupt number. + * @context: A cookie for this ISR. + */ +static irqreturn_t nfc_util_isr(int irq, void *cookie) +{ + struct imx_nfc_data *this = cookie; + this->nfc->mask_interrupt(this); + this->nfc->clear_interrupt(this); + complete(&this->done); + return IRQ_HANDLED; +} + +/** + * nfc_util_send_cmd() - Sends a command to the current chip, without waiting. + * + * @this: Per-device data. + * @command: The command code. + */ + +static void nfc_util_send_cmd(struct imx_nfc_data *this, unsigned int command) +{ + + add_event("Entering nfc_util_send_cmd", 1); + + this->nfc->command_cycle(this, command); + + add_event("Exiting nfc_util_send_cmd", -1); + +} + +/** + * nfc_util_send_cmd_and_addrs() - Sends a cmd and addrs to the current chip. + * + * This function conveniently combines sending a command, and then sending + * optional addresses, waiting for the NFC to finish will all steps. + * + * @this: Per-device data. + * @command: The command code. + * @column: The column address to send, or -1 if no column address applies. + * @page: The page address to send, or -1 if no page address applies. + */ + +static void nfc_util_send_cmd_and_addrs(struct imx_nfc_data *this, + unsigned command, int column, int page) +{ + uint32_t page_mask; + + add_event("Entering nfc_util_send_cmd_and_addrs", 1); + + /* Send the command.*/ + + add_event("Sending the command...", 0); + + nfc_util_send_cmd(this, command); + + nfc_util_wait_for_the_nfc(this, false); + + /* Send the addresses. */ + + add_event("Sending the addresses...", 0); + + if (column != -1) { + + this->nfc->write_cycle(this, (column >> 0) & 0xff); + + if (is_large_page_chip(this)) + this->nfc->write_cycle(this, (column >> 8) & 0xff); + + } + + if (page != -1) { + + page_mask = this->nand.pagemask; + + do { + this->nfc->write_cycle(this, page & 0xff); + page_mask >>= 8; + page >>= 8; + } while (page_mask != 0); + + } + + add_event("Exiting nfc_util_send_cmd_and_addrs", -1); + +} + +/** + * nfc_2_x_exit() - Version-specific shut down. + * + * @this: Per-device data. + */ +static void nfc_2_x_exit(struct imx_nfc_data *this) +{ +} + +/** + * nfc_2_x_clear_interrupt() - Clears an interrupt. + * + * @this: Per-device data. + */ +static void nfc_2_x_clear_interrupt(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + raw_clr_mask_w(NFC_2_X_CONFIG2_INT_MSK, base + NFC_2_X_CONFIG2_REG_OFF); +} + +/** + * nfc_2_x_is_interrupting() - Returns the interrupt bit status. + * + * @this: Per-device data. + */ +static int nfc_2_x_is_interrupting(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + return raw_read_mask_w(NFC_2_X_CONFIG2_INT_MSK, + base + NFC_2_X_CONFIG2_REG_OFF); +} + +/** + * nfc_2_x_command_cycle() - Sends a command. + * + * @this: Per-device data. + * @command: The command code. + */ +static void nfc_2_x_command_cycle(struct imx_nfc_data *this, unsigned command) +{ + void *base = this->primary_regs; + + /* Write the command we want to send. */ + + __raw_writew(command, base + NFC_2_X_FLASH_CMD_REG_OFF); + + /* Launch a command cycle. */ + + __raw_writew(NFC_2_X_CONFIG2_FCMD_MSK, base + NFC_2_X_CONFIG2_REG_OFF); + +} + +/** + * nfc_2_x_write_cycle() - Writes a single byte. + * + * @this: Per-device data. + * @byte: The byte. + */ +static void nfc_2_x_write_cycle(struct imx_nfc_data *this, unsigned int byte) +{ + void *base = this->primary_regs; + + /* Give the NFC the byte we want to write. */ + + __raw_writew(byte, base + NFC_2_X_FLASH_ADDR_REG_OFF); + + /* Launch an address cycle. + * + * This is *sort* of a hack, but not really. The intent of the NFC + * design is for this operation to send an address byte. In fact, the + * NFC neither knows nor cares what we're sending. It justs runs a write + * cycle. + */ + + __raw_writew(NFC_2_X_CONFIG2_FADD_MSK, base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + +} + +/** + * nfc_2_0_init() - Version-specific hardware initialization. + * + * @this: Per-device data. + */ +static int nfc_2_0_init(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + + /* Initialize the interrupt machinery. */ + + this->nfc->mask_interrupt(this); + this->nfc->clear_interrupt(this); + + /* Unlock the NFC memory. */ + + __raw_writew(0x2, base + NFC_2_X_CONFIG_REG_OFF); + + /* Set the unlocked block range to cover the entire medium. */ + + __raw_writew(0 , base + NFC_2_0_UNLOCK_START_REG_OFF); + __raw_writew(~0, base + NFC_2_0_UNLOCK_END_REG_OFF); + + /* Unlock all blocks. */ + + __raw_writew(0x4, base + NFC_2_X_WR_PROT_REG_OFF); + + /* Return success. */ + + return 0; + +} + +/** + * nfc_2_0_mask_interrupt() - Masks interrupts. + * + * @this: Per-device data. + */ +static void nfc_2_0_mask_interrupt(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + raw_set_mask_w(NFC_2_0_CONFIG1_INT_MSK_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); +} + +/** + * nfc_2_0_unmask_interrupt() - Unmasks interrupts. + * + * @this: Per-device data. + */ +static void nfc_2_0_unmask_interrupt(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + raw_clr_mask_w(NFC_2_0_CONFIG1_INT_MSK_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); +} + +/** + * nfc_2_0_set_ecc() - Turns ECC on or off. + * + * @this: Per-device data. + * @on: Indicates if ECC should be on or off. + */ +static void nfc_2_0_set_ecc(struct imx_nfc_data *this, int on) +{ + void *base = this->primary_regs; + + if (on) + raw_set_mask_w(NFC_2_0_CONFIG1_ECC_EN_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + else + raw_clr_mask_w(NFC_2_0_CONFIG1_ECC_EN_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + +} + +/** + * nfc_2_0_get_ecc_status() - Reports ECC errors. + * + * @this: Per-device data. + */ +static int nfc_2_0_get_ecc_status(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + uint16_t status_reg; + unsigned int buffer_status[4]; + int status; + + /* Get the entire status register. */ + + status_reg = __raw_readw(base + NFC_2_0_ECC_STATUS_REG_OFF); + + /* Pick out the status for each buffer. */ + + buffer_status[0] = (status_reg & NFC_2_0_ECC_STATUS_NOSER1_MSK) + >> NFC_2_0_ECC_STATUS_NOSER1_POS; + + buffer_status[1] = (status_reg & NFC_2_0_ECC_STATUS_NOSER2_MSK) + >> NFC_2_0_ECC_STATUS_NOSER2_POS; + + buffer_status[2] = (status_reg & NFC_2_0_ECC_STATUS_NOSER3_MSK) + >> NFC_2_0_ECC_STATUS_NOSER3_POS; + + buffer_status[3] = (status_reg & NFC_2_0_ECC_STATUS_NOSER4_MSK) + >> NFC_2_0_ECC_STATUS_NOSER4_POS; + + /* Loop through the buffers we're actually using. */ + + status = 0; + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + if (buffer_status[i] > this->nfc_geometry->ecc_strength) { + status = -1; + break; + } + + status += buffer_status[i]; + + } + + /* Return the final result. */ + + return status; + +} + +/** + * nfc_2_0_get_symmetric() - Indicates if the clock is symmetric. + * + * @this: Per-device data. + */ +static int nfc_2_0_get_symmetric(struct imx_nfc_data *this) +{ + void *base = this->primary_regs; + + return !!raw_read_mask_w(NFC_2_0_CONFIG1_ONE_CYLE_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + +} + +/** + * nfc_2_0_set_symmetric() - Turns symmetric clock mode on or off. + * + * @this: Per-device data. + */ +static void nfc_2_0_set_symmetric(struct imx_nfc_data *this, int on) +{ + void *base = this->primary_regs; + + if (on) + raw_set_mask_w(NFC_2_0_CONFIG1_ONE_CYLE_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + else + raw_clr_mask_w(NFC_2_0_CONFIG1_ONE_CYLE_MSK, + base + NFC_2_0_CONFIG1_REG_OFF); + +} + +/** + * nfc_2_0_set_geometry() - Configures for the medium geometry. + * + * @this: Per-device data. + */ +static int nfc_2_0_set_geometry(struct imx_nfc_data *this) +{ + struct physical_geometry *physical = &this->physical_geometry; + + /* Select an NFC geometry. */ + + switch (physical->page_data_size) { + + case 512: + this->nfc_geometry = &nfc_geometry_512_16_RS_ECC4; + break; + + case 2048: + this->nfc_geometry = &nfc_geometry_2K_64_RS_ECC4; + break; + + default: + dev_err(this->dev, "NFC can't handle page size: %u", + physical->page_data_size); + return !0; + break; + + } + + /* + * This NFC version receives page size information from a register + * that's external to the NFC. We must rely on platform-specific code + * to set this register for us. + */ + + return this->pdata->set_page_size(physical->page_data_size); + +} + +/** + * nfc_2_0_select_chip() - Selects the current chip. + * + * @this: Per-device data. + * @chip: The chip number to select, or -1 to select no chip. + */ +static void nfc_2_0_select_chip(struct imx_nfc_data *this, int chip) +{ +} + +/** + * nfc_2_0_read_cycle() - Applies a single read cycle to the current chip. + * + * @this: Per-device data. + */ +static unsigned int nfc_2_0_read_cycle(struct imx_nfc_data *this) +{ + uint8_t byte; + unsigned int result; + void *base = this->primary_regs; + + /* Read into main buffer 0. */ + + __raw_writew(0x0, base + NFC_2_0_BUF_ADDR_REG_OFF); + + /* Launch a "Data Out" operation. */ + + __raw_writew(0x4 << NFC_2_X_CONFIG2_FDO_POS, + base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + + /* Get the result from the NFC memory. */ + + nfc_util_copy_from_the_nfc(this, 0, &byte, 1); + result = byte; + + /* Return the results. */ + + return result; + +} + +/** + * nfc_2_0_read_page() - Reads a page from the current chip into the NFC. + * + * @this: Per-device data. + */ +static void nfc_2_0_read_page(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + + /* Loop over the number of buffers in use. */ + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + /* Make the NFC read into the current buffer. */ + + __raw_writew(i << NFC_2_0_BUF_ADDR_RBA_POS, + base + NFC_2_0_BUF_ADDR_REG_OFF); + + /* Launch a page data out operation. */ + + __raw_writew(0x1 << NFC_2_X_CONFIG2_FDO_POS, + base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + } + +} + +/** + * nfc_2_0_send_page() - Sends a page from the NFC to the current chip. + * + * @this: Per-device data. + */ +static void nfc_2_0_send_page(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + + /* Loop over the number of buffers in use. */ + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + /* Make the NFC send from the current buffer. */ + + __raw_writew(i << NFC_2_0_BUF_ADDR_RBA_POS, + base + NFC_2_0_BUF_ADDR_REG_OFF); + + /* Launch a page data in operation. */ + + __raw_writew(0x1 << NFC_2_X_CONFIG2_FDI_POS, + base + NFC_2_X_CONFIG2_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + } + +} + +/** + * nfc_3_2_init() - Hardware initialization. + * + * @this: Per-device data. + */ +static int nfc_3_2_init(struct imx_nfc_data *this) +{ + int error; + unsigned int no_sdma; + unsigned int fmp; + unsigned int rbb_mode; + unsigned int num_of_devices; + unsigned int dma_mode; + unsigned int sbb; + unsigned int nf_big; + unsigned int sb2r; + unsigned int fw; + unsigned int too; + unsigned int add_op; + uint32_t config3; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + /* Initialize the interrupt machinery. */ + + this->nfc->mask_interrupt(this); + this->nfc->clear_interrupt(this); + + /* Set up the clock. */ + + error = this->nfc->set_closest_cycle(this, + this->pdata->target_cycle_in_ns); + + if (error) + return !0; + + /* We never read the spare area alone. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_SP_EN_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Tell the NFC the "Read Status" command code. */ + + raw_clr_mask_l(NFC_3_2_CONFIG2_ST_CMD_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + raw_set_mask_l(NAND_CMD_STATUS << NFC_3_2_CONFIG2_ST_CMD_POS, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + /* + * According to erratum ENGcm09051, the CONFIG3 register doesn't reset + * correctly, so we need to re-build the entire register just in case. + */ + + /* + * Set the NO_SDMA bit to tell the NFC that we are NOT using SDMA. If + * you clear this bit (to indicates you *are* using SDMA), but you + * don't actually set up SDMA, the NFC has been observed to crash the + * hardware when it asserts its DMA request signals. In the future, we + * *may* use SDMA, but it's not worth the effort at this writing. + */ + + no_sdma = 0x1; + + /* + * Set the default FIFO Mode Protection (128 bytes). FMP doesn't work if + * the NO_SDMA bit is set. + */ + + fmp = 0x2; + + /* + * The rbb_mode bit determines how the NFC figures out whether chips are + * ready during automatic operations only (this has no effect on atomic + * operations). The two choices are either to monitor the ready/busy + * signals, or to read the status register. We monitor the ready/busy + * signals. + */ + + rbb_mode = 0x1; + + /* + * We don't yet know how many devices are connected. We'll find out in + * out in nfc_3_2_set_geometry(). + */ + + num_of_devices = 0; + + /* Set the default DMA mode. */ + + dma_mode = 0x0; + + /* Set the default status busy bit. */ + + sbb = 0x6; + + /* Little-endian (the default). */ + + nf_big = 0x0; + + /* Set the default (standard) status bit to record. */ + + sb2r = 0x0; + + /* We support only 8-bit Flash bus width. */ + + fw = 0x1; + + /* We don't support "two-on-one." */ + + too = 0x0; + + /* Set the addressing option. */ + + add_op = 0x3; + + /* Set the CONFIG3 register. */ + + config3 = 0; + + config3 |= no_sdma << NFC_3_2_CONFIG3_NO_SDMA_POS; + config3 |= fmp << NFC_3_2_CONFIG3_FMP_POS; + config3 |= rbb_mode << NFC_3_2_CONFIG3_RBB_MODE_POS; + config3 |= num_of_devices << NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + config3 |= dma_mode << NFC_3_2_CONFIG3_DMA_MODE_POS; + config3 |= sbb << NFC_3_2_CONFIG3_SBB_POS; + config3 |= nf_big << NFC_3_2_CONFIG3_NF_BIG_POS; + config3 |= sb2r << NFC_3_2_CONFIG3_SB2R_POS; + config3 |= fw << NFC_3_2_CONFIG3_FW_POS; + config3 |= too << NFC_3_2_CONFIG3_TOO_POS; + config3 |= add_op << NFC_3_2_CONFIG3_ADD_OP_POS; + + __raw_writel(config3, secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + /* Return success. */ + + return 0; + +} + +/** + * nfc_3_2_set_geometry() - Configures for the medium geometry. + * + * @this: Per-device data. + */ +static int nfc_3_2_set_geometry(struct imx_nfc_data *this) +{ + unsigned int ps; + unsigned int cmd_phases; + unsigned int pages_per_chip; + unsigned int addr_phases0; + unsigned int addr_phases1; + unsigned int pages_per_block; + unsigned int ecc_mode; + unsigned int ppb; + unsigned int spas; + unsigned int mask; + uint32_t config2; + unsigned int num_of_devices; + uint32_t config3; + unsigned int x; + unsigned int chip; + struct physical_geometry *physical = &this->physical_geometry; + void *secondary_base = this->secondary_regs; + + /* + * Select an NFC geometry based on the physical geometry and the + * capabilities of this NFC. + */ + + switch (physical->page_data_size) { + + case 512: + this->nfc_geometry = &nfc_geometry_512_16_BCH_ECC4; + ps = 0; + break; + + case 2048: + this->nfc_geometry = &nfc_geometry_2K_64_BCH_ECC4; + ps = 1; + break; + + case 4096: + + switch (this->physical_geometry.page_oob_size) { + + case 128: + this->nfc_geometry = &nfc_geometry_4K_128_BCH_ECC4; + break; + + case 218: + this->nfc_geometry = &nfc_geometry_4K_218_BCH_ECC8; + break; + + default: + dev_err(this->dev, + "NFC can't handle page geometry: %u+%u", + physical->page_data_size, + physical->page_oob_size); + return !0; + break; + + } + + ps = 2; + + break; + + default: + dev_err(this->dev, "NFC can't handle page size: %u", + physical->page_data_size); + return !0; + break; + + } + + /* Compute the ECC mode. */ + + switch (this->nfc_geometry->ecc_strength) { + + case 4: + ecc_mode = 0; + break; + + case 8: + ecc_mode = 1; + break; + + default: + dev_err(this->dev, "NFC can't handle ECC strength: %u", + this->nfc_geometry->ecc_strength); + return !0; + break; + + } + + /* Compute the pages per block. */ + + pages_per_block = physical->block_size / physical->page_data_size; + + switch (pages_per_block) { + case 32: + ppb = 0; + break; + case 64: + ppb = 1; + break; + case 128: + ppb = 2; + break; + case 256: + ppb = 3; + break; + default: + dev_err(this->dev, "NFC can't handle pages per block: %d", + pages_per_block); + return !0; + break; + } + + /* + * The hardware needs to know the physical size of the spare area, in + * units of half-words (16 bits). This may be different from the amount + * of the spare area we actually expose to MTD. For example, for for + * 2K+112, we only expose 64 spare bytes, but the hardware needs to know + * the real facts. + */ + + spas = this->physical_geometry.page_oob_size >> 1; + + /* + * The number of command phases needed to read a page is directly + * dependent on whether this is a small page or large page device. Large + * page devices need more address phases, terminated by a second command + * phase. + */ + + cmd_phases = is_large_page_chip(this) ? 1 : 0; + + /* + * The num_adr_phases1 field contains the number of phases needed to + * transmit addresses for read and program operations. This is the sum + * of the number of phases for a page address and the number of phases + * for a column address. + * + * The number of phases for a page address is the number of bytes needed + * to contain a page address. + * + * The number of phases for a column address is the number of bytes + * needed to contain a column address. + * + * After computing the sum, we subtract three because a value of zero in + * this field indicates three address phases, and this is the minimum + * number of phases the hardware can comprehend. + * + * We compute the number of phases based on the *physical* geometry, not + * the NFC geometry. For example, even if we are treating a very large + * device as if it contains fewer pages than it actually does, the + * hardware still needs the additional address phases. + */ + + pages_per_chip = + physical->chip_size >> (fls(physical->page_data_size) - 1); + + addr_phases1 = (fls(pages_per_chip) >> 3) + 1; + + addr_phases1 += (fls(physical->page_data_size) >> 3) + 1; + + addr_phases1 -= 3; + + /* + * The num_adr_phases0 field contains the number of phases needed to + * transmit a page address for an erase operation. That is, this is + * the value of addr_phases1, less the number of phases for the column + * address. + * + * The hardware expresses this phase count as one or two cycles less + * than the count indicated by add_phases1 (see the reference manual). + */ + + addr_phases0 = is_large_page_chip(this) ? 1 : 0; + + /* Set the CONFIG2 register. */ + + mask = + NFC_3_2_CONFIG2_PS_MSK | + NFC_3_2_CONFIG2_CMD_PHASES_MSK | + NFC_3_2_CONFIG2_ADDR_PHASES0_MSK | + NFC_3_2_CONFIG2_ECC_MODE_MSK | + NFC_3_2_CONFIG2_PPB_MSK | + NFC_3_2_CONFIG2_ADDR_PHASES1_MSK | + NFC_3_2_CONFIG2_SPAS_MSK ; + + config2 = __raw_readl(secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + config2 &= ~mask; + + config2 |= ps << NFC_3_2_CONFIG2_PS_POS; + config2 |= cmd_phases << NFC_3_2_CONFIG2_CMD_PHASES_POS; + config2 |= addr_phases0 << NFC_3_2_CONFIG2_ADDR_PHASES0_POS; + config2 |= ecc_mode << NFC_3_2_CONFIG2_ECC_MODE_POS; + config2 |= ppb << NFC_3_2_CONFIG2_PPB_POS; + config2 |= addr_phases1 << NFC_3_2_CONFIG2_ADDR_PHASES1_POS; + config2 |= spas << NFC_3_2_CONFIG2_SPAS_POS; + + config2 = __raw_writel(config2, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + + /* + * Compute the num_of_devices field. + * + * It's very important to set this field correctly. This controls the + * set of ready/busy lines to which the NFC listens with automatic + * transactions. If this number is too large, the NFC will listen to + * ready/busy signals that are electrically floating, or it will try to + * read the status registers of chips that don't exist. Conversely, if + * the number is too small, the NFC could believe an operation is + * finished when some chips are still busy. + */ + + num_of_devices = physical->chip_count - 1; + + /* Set the CONFIG3 register. */ + + mask = NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK; + + config3 = __raw_readl(secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + config3 &= ~mask; + + config3 |= num_of_devices << NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + + __raw_writel(config3, secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + /* + * Check if the physical chip count is a power of 2. If not, then + * automatic operations aren't available. This is because we use an + * addressing option (see the ADD_OP field of CONFIG3) that requires + * a number of chips that is a power of 2. + */ + + if (ffs(physical->chip_count) != fls(physical->chip_count)) { + this->nfc->start_auto_read = 0; + this->nfc->start_auto_write = 0; + this->nfc->start_auto_erase = 0; + } + + /* Unlock the NFC RAM. */ + + x = __raw_readl(secondary_base + NFC_3_2_WRPROT_REG_OFF); + x &= ~NFC_3_2_WRPROT_BLS_MSK; + x |= 0x2 << NFC_3_2_WRPROT_BLS_POS; + __raw_writel(x, secondary_base + NFC_3_2_WRPROT_REG_OFF); + + /* Loop over chip selects, setting the unlocked ranges. */ + + for (chip = 0; chip < this->nfc->max_chip_count; chip++) { + + /* Set the unlocked range to cover the entire chip.*/ + + __raw_writel(0xffff0000, secondary_base + + NFC_3_2_UNLOCK_BLK_ADD0_REG_OFF + (chip * 4)); + + /* Unlock. */ + + x = __raw_readl(secondary_base + NFC_3_2_WRPROT_REG_OFF); + x &= ~(NFC_3_2_WRPROT_CS2L_MSK | NFC_3_2_WRPROT_WPC_MSK); + x |= chip << NFC_3_2_WRPROT_CS2L_POS; + x |= 0x4 << NFC_3_2_WRPROT_WPC_POS ; + __raw_writel(x, secondary_base + NFC_3_2_WRPROT_REG_OFF); + + } + + /* Return success. */ + + return 0; + +} + +/** + * nfc_3_2_exit() - Hardware cleanup. + * + * @this: Per-device data. + */ +static void nfc_3_2_exit(struct imx_nfc_data *this) +{ +} + +/** + * nfc_3_2_set_closest_cycle() - Version-specific hardware cleanup. + * + * @this: Per-device data. + */ +static int nfc_3_2_set_closest_cycle(struct imx_nfc_data *this, unsigned ns) +{ + struct clk *parent_clock; + unsigned long parent_clock_rate_in_hz; + unsigned long sym_low_clock_rate_in_hz; + unsigned long asym_low_clock_rate_in_hz; + unsigned int sym_high_cycle_in_ns; + unsigned int asym_high_cycle_in_ns; + + /* + * According to ENGcm09121: + * + * - If the NFC is set to SYMMETRIC mode, the NFC clock divider must + * divide the EMI Slow Clock by NO MORE THAN 4. + * + * - If the NFC is set for ASYMMETRIC mode, the NFC clock divider must + * divide the EMI Slow Clock by NO MORE THAN 3. + * + * We need to compute the corresponding cycle time constraints. Start + * by getting information about the parent clock. + */ + + parent_clock = clk_get_parent(this->clock); + parent_clock_rate_in_hz = clk_get_rate(parent_clock); + + /* Compute the limit frequencies. */ + + sym_low_clock_rate_in_hz = parent_clock_rate_in_hz / 4; + asym_low_clock_rate_in_hz = parent_clock_rate_in_hz / 3; + + /* Compute the corresponding limit cycle periods. */ + + sym_high_cycle_in_ns = 1000000000 / sym_low_clock_rate_in_hz; + asym_high_cycle_in_ns = (1000000000 / asym_low_clock_rate_in_hz) * 2; + + /* Attempt to implement the given cycle. */ + + return nfc_util_set_best_cycle(this, ns, + ns > asym_high_cycle_in_ns, ns > sym_high_cycle_in_ns); + +} + +/** + * nfc_3_2_mask_interrupt() - Masks interrupts. + * + * @this: Per-device data. + */ +static void nfc_3_2_mask_interrupt(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + raw_set_mask_l(NFC_3_2_CONFIG2_INT_MSK_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); +} + +/** + * nfc_3_2_unmask_interrupt() - Unmasks interrupts. + * + * @this: Per-device data. + */ +static void nfc_3_2_unmask_interrupt(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + raw_clr_mask_l(NFC_3_2_CONFIG2_INT_MSK_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); +} + +/** + * nfc_3_2_clear_interrupt() - Clears an interrupt. + * + * @this: Per-device data. + */ +static void nfc_3_2_clear_interrupt(struct imx_nfc_data *this) +{ + int done; + void *secondary_base = this->secondary_regs; + + /* Request IP bus interface access. */ + + raw_set_mask_l(NFC_3_2_IPC_CREQ_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + /* Wait for access. */ + + do + done = !!raw_read_mask_l(NFC_3_2_IPC_CACK_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + while (!done); + + /* Clear the interrupt. */ + + raw_clr_mask_l(NFC_3_2_IPC_INT_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + /* Release the IP bus interface. */ + + raw_clr_mask_l(NFC_3_2_IPC_CREQ_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + +} + +/** + * nfc_3_2_is_interrupting() - Returns the interrupt bit status. + * + * @this: Per-device data. + */ +static int nfc_3_2_is_interrupting(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + return !!raw_read_mask_l(NFC_3_2_IPC_INT_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); +} + +/** + * nfc_3_2_is_ready() - Returns the ready/busy status. + * + * @this: Per-device data. + */ +static int nfc_3_2_is_ready(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + return !!raw_read_mask_l(NFC_3_2_IPC_RB_B_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); +} + +/** + * nfc_3_2_set_force_ce() - Can force CE to be asserted always. + * + * @this: Per-device data. + * @on: Indicates if the hardware CE signal should be asserted always. + */ +static void nfc_3_2_set_force_ce(struct imx_nfc_data *this, int on) +{ + void *primary_base = this->primary_regs; + + if (on) + raw_set_mask_l(NFC_3_2_CONFIG1_NF_CE_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + else + raw_clr_mask_l(NFC_3_2_CONFIG1_NF_CE_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + +} + +/** + * nfc_3_2_set_ecc() - Turns ECC on or off. + * + * @this: Per-device data. + * @on: Indicates if ECC should be on or off. + */ +static void nfc_3_2_set_ecc(struct imx_nfc_data *this, int on) +{ + void *secondary_base = this->secondary_regs; + + if (on) + raw_set_mask_l(NFC_3_2_CONFIG2_ECC_EN_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + else + raw_clr_mask_l(NFC_3_2_CONFIG2_ECC_EN_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + +} + +/** + * nfc_3_2_get_ecc_status() - Reports ECC errors. + * + * @this: Per-device data. + */ +static int nfc_3_2_get_ecc_status(struct imx_nfc_data *this) +{ + unsigned int i; + void *base = this->primary_regs; + uint16_t status_reg; + unsigned int buffer_status[8]; + int status; + + /* Get the entire status register. */ + + status_reg = __raw_readw(base + NFC_3_2_ECC_STATUS_REG_OFF); + + /* Pick out the status for each buffer. */ + + buffer_status[0] = (status_reg & NFC_3_2_ECC_STATUS_NOBER1_MSK) + >> NFC_3_2_ECC_STATUS_NOBER1_POS; + + buffer_status[1] = (status_reg & NFC_3_2_ECC_STATUS_NOBER2_MSK) + >> NFC_3_2_ECC_STATUS_NOBER2_POS; + + buffer_status[2] = (status_reg & NFC_3_2_ECC_STATUS_NOBER3_MSK) + >> NFC_3_2_ECC_STATUS_NOBER3_POS; + + buffer_status[3] = (status_reg & NFC_3_2_ECC_STATUS_NOBER4_MSK) + >> NFC_3_2_ECC_STATUS_NOBER4_POS; + + buffer_status[4] = (status_reg & NFC_3_2_ECC_STATUS_NOBER5_MSK) + >> NFC_3_2_ECC_STATUS_NOBER5_POS; + + buffer_status[5] = (status_reg & NFC_3_2_ECC_STATUS_NOBER6_MSK) + >> NFC_3_2_ECC_STATUS_NOBER6_POS; + + buffer_status[6] = (status_reg & NFC_3_2_ECC_STATUS_NOBER7_MSK) + >> NFC_3_2_ECC_STATUS_NOBER7_POS; + + buffer_status[7] = (status_reg & NFC_3_2_ECC_STATUS_NOBER8_MSK) + >> NFC_3_2_ECC_STATUS_NOBER8_POS; + + /* Loop through the buffers we're actually using. */ + + status = 0; + + for (i = 0; i < this->nfc_geometry->buffer_count; i++) { + + if (buffer_status[i] > this->nfc_geometry->ecc_strength) { + status = -1; + break; + } + + status += buffer_status[i]; + + } + + /* Return the final result. */ + + return status; + +} + +/** + * nfc_3_2_get_symmetric() - Indicates if the clock is symmetric. + * + * @this: Per-device data. + */ +static int nfc_3_2_get_symmetric(struct imx_nfc_data *this) +{ + void *secondary_base = this->secondary_regs; + + return !!raw_read_mask_w(NFC_3_2_CONFIG2_SYM_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + +} + +/** + * nfc_3_2_set_symmetric() - Turns symmetric clock mode on or off. + * + * @this: Per-device data. + */ +static void nfc_3_2_set_symmetric(struct imx_nfc_data *this, int on) +{ + void *secondary_base = this->secondary_regs; + + if (on) + raw_set_mask_l(NFC_3_2_CONFIG2_SYM_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + else + raw_clr_mask_l(NFC_3_2_CONFIG2_SYM_MSK, + secondary_base + NFC_3_2_CONFIG2_REG_OFF); + +} + +/** + * nfc_3_2_select_chip() - Selects the current chip. + * + * @this: Per-device data. + * @chip: The chip number to select, or -1 to select no chip. + */ +static void nfc_3_2_select_chip(struct imx_nfc_data *this, int chip) +{ + unsigned long x; + void *primary_base = this->primary_regs; + + if (chip < 0) + return; + + x = __raw_readl(primary_base + NFC_3_2_CONFIG1_REG_OFF); + + x &= ~NFC_3_2_CONFIG1_CS_MSK; + + x |= (chip << NFC_3_2_CONFIG1_CS_POS) & NFC_3_2_CONFIG1_CS_MSK; + + __raw_writel(x, primary_base + NFC_3_2_CONFIG1_REG_OFF); + +} + +/** + * nfc_3_2_command_cycle() - Sends a command. + * + * @this: Per-device data. + * @command: The command code. + */ +static void nfc_3_2_command_cycle(struct imx_nfc_data *this, unsigned command) +{ + void *primary_base = this->primary_regs; + + /* Write the command we want to send. */ + + __raw_writel(command, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Launch a command cycle. */ + + __raw_writel(NFC_3_2_LAUNCH_FCMD_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +} + +/** + * nfc_3_2_write_cycle() - writes a single byte. + * + * @this: Per-device data. + * @byte: The byte. + */ +static void nfc_3_2_write_cycle(struct imx_nfc_data *this, unsigned int byte) +{ + void *primary_base = this->primary_regs; + + /* Give the NFC the byte we want to write. */ + + __raw_writel(byte, primary_base + NFC_3_2_ADD0_REG_OFF); + + /* Launch an address cycle. + * + * This is *sort* of a hack, but not really. The intent of the NFC + * design is for this operation to send an address byte. In fact, the + * NFC neither knows nor cares what we're sending. It justs runs a write + * cycle. + */ + + __raw_writel(NFC_3_2_LAUNCH_FADD_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + +} + +/** + * nfc_3_2_read_cycle() - Applies a single read cycle to the current chip. + * + * @this: Per-device data. + */ +static unsigned int nfc_3_2_read_cycle(struct imx_nfc_data *this) +{ + unsigned int result; + void *primary_base = this->primary_regs; + + /* Launch a "Data Out" operation. */ + + __raw_writel(0x4 << NFC_3_2_LAUNCH_FDO_POS, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, false); + + /* Get the result. */ + + result = __raw_readl(primary_base + NFC_3_2_CONFIG1_REG_OFF) + >> NFC_3_2_CONFIG1_STATUS_POS; + result &= 0xff; + + /* Return the result. */ + + return result; + +} + +/** + * nfc_3_2_read_page() - Reads a page into the NFC memory. + * + * @this: Per-device data. + */ +static void nfc_3_2_read_page(struct imx_nfc_data *this) +{ + void *primary_base = this->primary_regs; + + /* Start reading into buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch a page data out operation. */ + + __raw_writel(0x1 << NFC_3_2_LAUNCH_FDO_POS, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + +} + +/** + * nfc_3_2_send_page() - Sends a page from the NFC to the current chip. + * + * @this: Per-device data. + */ +static void nfc_3_2_send_page(struct imx_nfc_data *this) +{ + void *primary_base = this->primary_regs; + + /* Start sending from buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch a page data in operation. */ + + __raw_writel(NFC_3_2_LAUNCH_FDI_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + +} + +/** + * nfc_3_2_add_state_events() - Adds events to display important state. + * + * @this: Per-device data. + */ +static void nfc_3_2_add_state_events(struct imx_nfc_data *this) +{ +#ifdef EVENT_REPORTING + void *secondary_base = this->secondary_regs; + + add_state_event_l + ( + secondary_base + NFC_3_2_IPC_REG_OFF, + NFC_3_2_IPC_INT_MSK, + " Interrupt : 0", + " Interrupt : X" + ); + + add_state_event_l + ( + secondary_base + NFC_3_2_IPC_REG_OFF, + NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + " auto_prog_done: 0", + " auto_prog_done: X" + ); + + add_state_event_l + ( + secondary_base + NFC_3_2_IPC_REG_OFF, + NFC_3_2_IPC_RB_B_MSK, + " Medium : Busy", + " Medium : Ready" + ); +#endif +} + +/** + * nfc_3_2_get_auto_loop_params() - Gets automatic operation loop parameters. + * + * This function and the corresponding "setter" enable the automatic operations + * to keep some state as they iterate over chips. + * + * The most "obvious" way to save state would be to allocate a private data + * structure and hang it off the owning struct nfc_hal. On the other hand, + * writing the code to allocate the memory and then release it when the NFC + * shuts down is annoying - and we have some perfectly good memory in the NFC + * hardware that we can use. Since we only use two commands at a time, we can + * stash our loop limits and loop index in the top 16 bits of the NAND_CMD + * register. To paraphrase the reference manual: + * + * + * NAND_CMD + * + * |<-- 4 bits -->|<-- 4 bits -->|<-- 8 bits -->| + * +----------------+---------------+--------------------------------+ + * | First | Last | Loop Index | + * +----------------+---------------+--------------------------------+ + * | NAND COMMAND1 | NAND COMMAND0 | + * +--------------------------------+--------------------------------+ + * |<-- 16 bits -->|<-- 16 bits -->| + * + * + * @this: Per-device data. + * @first: A pointer to a variable that will receive the first chip number. + * @last: A pointer to a variable that will receive the last chip number. + * @index: A pointer to a variable that will receive the current chip number. + */ +static void nfc_3_2_get_auto_loop_params(struct imx_nfc_data *this, + unsigned *first, unsigned *last, unsigned *index) +{ + uint32_t x; + void *primary_base = this->primary_regs; + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + *first = (x >> 28) & 0x0f; + *last = (x >> 24) & 0x0f; + *index = (x >> 16) & 0xff; + +} + +/** + * nfc_3_2_set_auto_loop_params() - Sets automatic operation loop parameters. + * + * See nfc_3_2_get_auto_loop_params() for detailed information about these + * functions. + * + * @this: Per-device data. + * @first: The first chip number. + * @last: The last chip number. + * @index: The current chip number. + */ +static void nfc_3_2_set_auto_loop_params(struct imx_nfc_data *this, + unsigned first, unsigned last, unsigned index) +{ + uint32_t x; + void *primary_base = this->primary_regs; + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0x0000ffff; + x |= (first & 0x0f) << 28; + x |= (last & 0x0f) << 24; + x |= (index & 0xff) << 16; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + +} + +/** + * nfc_3_2_get_auto_addresses() - Gets automatic operation addresses. + * + * @this: Per-device data. + * @group: The address group number. + * @chip: A pointer to a variable that will receive the chip number. + * @column: A pointer to a variable that will receive the column address. + * A NULL pointer indicates there is no column address. + * @page: A pointer to a variable that will receive the page address. + */ +static void nfc_3_2_get_auto_addresses(struct imx_nfc_data *this, + unsigned group, unsigned *chip, unsigned *column, unsigned *page) +{ + uint32_t x; + unsigned int chip_count; + unsigned int cs_width; + unsigned int cs_mask; + unsigned int page_lsbs; + unsigned int page_msbs; + uint32_t *low; + uint16_t *high; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + /* + * The width of the chip select field depends on the number of connected + * chips. + * + * Notice that these computations work only if the number of chips is a + * power of 2. In fact, that is a fundamental limitation for using + * automatic operations. + */ + + x = __raw_readl(secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + chip_count = + (x & NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK) >> + NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + chip_count++; + + cs_width = ffs(chip_count) - 1; + cs_mask = chip_count - 1; + + /* Construct pointers to the pieces of the given address group. */ + + low = primary_base + NFC_3_2_ADD0_REG_OFF; + low += group; + + high = primary_base + NFC_3_2_ADD8_REG_OFF; + high += group; + + /* Check if there's a column address. */ + + if (column) { + + /* + * The low 32 bits of the address group look like this: + * + * 16 - n n + * | <- bits ->|<->|<- 16 bits ->| + * +-------------+---+----------------+ + * | Page LSBs |CS | Column | + * +-------------+---+----------------+ + */ + + x = __raw_readl(low); + + *column = x & 0xffff; + *chip = (x >> 16) & cs_mask; + page_lsbs = x >> (16 + cs_width); + + /* The high 16 bits contain the MSB's of the page address. */ + + page_msbs = __raw_readw(high); + + *page = (page_msbs << (16 - cs_width)) | page_lsbs; + + } else { + + /* + * The low 32 bits of the address group look like this: + * + * n + * | <- (32 - n) bits ->|<->| + * +-----------------------------+---+ + * | Page LSBs |CS | + * +-----------------------------+---+ + */ + + x = __raw_readl(low); + + *chip = x & cs_mask; + page_lsbs = x >> cs_width; + + /* The high 16 bits contain the MSB's of the page address. */ + + page_msbs = __raw_readw(high); + + *page = (page_msbs << (32 - cs_width)) | page_lsbs; + + } + +} + +/** + * nfc_3_2_set_auto_addresses() - Sets automatic operation addresses. + * + * @this: Per-device data. + * @group: The address group number. + * @chip: The chip number. + * @column: The column address. The sentinel value ~0 indicates that there is + * no column address. + * @page: The page address. + */ +static void nfc_3_2_set_auto_addresses(struct imx_nfc_data *this, + unsigned group, unsigned chip, unsigned column, unsigned page) +{ + uint32_t x; + unsigned chip_count; + unsigned int cs_width; + unsigned int cs_mask; + uint32_t *low; + uint16_t *high; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + /* + * The width of the chip select field depends on the number of connected + * chips. + * + * Notice that these computations work only if the number of chips is a + * power of 2. In fact, that is a fundamental limitation for using + * automatic operations. + */ + + x = __raw_readl(secondary_base + NFC_3_2_CONFIG3_REG_OFF); + + chip_count = + (x & NFC_3_2_CONFIG3_NUM_OF_DEVICES_MSK) >> + NFC_3_2_CONFIG3_NUM_OF_DEVICES_POS; + chip_count++; + + cs_width = ffs(chip_count) - 1; + cs_mask = chip_count - 1; + + /* Construct pointers to the pieces of the given address group. */ + + low = primary_base + NFC_3_2_ADD0_REG_OFF; + low += group; + + high = primary_base + NFC_3_2_ADD8_REG_OFF; + high += group; + + /* Check if we have a column address. */ + + if (column != ~0) { + + /* + * The low 32 bits of the address group look like this: + * + * 16 - n n + * | <- bits ->|<->|<- 16 bits ->| + * +-------------+---+----------------+ + * | Page LSBs |CS | Column | + * +-------------+---+----------------+ + */ + + x = 0; + x |= column & 0xffff; + x |= (chip & cs_mask) << 16; + x |= page << (16 + cs_width); + + __raw_writel(x, low); + + /* The high 16 bits contain the MSB's of the page address. */ + + x = (page >> (16 - cs_width)) & 0xffff; + + __raw_writew(x, high); + + } else { + + /* + * The low 32 bits of the address group look like this: + * + * n + * | <- (32 - n) bits ->|<->| + * +-----------------------------+---+ + * | Page LSBs |CS | + * +-----------------------------+---+ + */ + + x = 0; + x |= chip & cs_mask; + x |= page << cs_width; + + __raw_writel(x, low); + + /* The high 16 bits contain the MSB's of the page address. */ + + x = (page >> (32 - cs_width)) & 0xffff; + + __raw_writew(x, high); + + } + +} + +/** + * nfc_3_2_start_auto_read() - Starts an automatic read. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + * @start: The first physical chip number on which to operate. + * @count: The number of physical chips on which to operate. + * @column: The column address. + * @page: The page address. + */ +static int nfc_3_2_start_auto_read(struct imx_nfc_data *this, + unsigned start, unsigned count, unsigned column, unsigned page) +{ + uint32_t x; + int return_value = 0; + void *primary_base = this->primary_regs; + + add_event("Entering nfc_3_2_start_auto_read", 1); + + /* Check for nonsense. */ + + if ((start > 7) || (!count) || (count > 8)) { + return_value = !0; + goto exit; + } + + /* Set state. */ + + nfc_3_2_set_auto_loop_params(this, start, start + count - 1, start); + nfc_3_2_set_auto_addresses(this, 0, start, column, page); + + /* Set up for ONE iteration at a time. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_ITER_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Reset to buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* + * Set up the commands. Note that the number of command phases was + * configured in the set_geometry() function so, even though we're + * giving both commands here, they won't necessarily both be used. + */ + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0xffff0000; + x |= NAND_CMD_READ0 << 0; + x |= NAND_CMD_READSTART << 8; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Launch the operation. */ + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_READ_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_start_auto_read", -1); + + return return_value; + +} + +/** + * nfc_3_2_wait_for_auto_read() - Waits until auto read is ready for the CPU. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + */ +static int nfc_3_2_wait_for_auto_read(struct imx_nfc_data *this) +{ + unsigned int first; + unsigned int last; + unsigned int index; + int return_value = 0; + + add_event("Entering nfc_3_2_wait_for_auto_read", 1); + + /* Get state. */ + + nfc_3_2_get_auto_loop_params(this, &first, &last, &index); + + /* This function should be called for every chip. */ + + if ((index < first) || (index > last)) { + return_value = !0; + goto exit; + } + + /* Wait for the NFC to completely finish and interrupt. */ + + nfc_util_wait_for_the_nfc(this, true); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_wait_for_auto_read", -1); + + return return_value; + +} + +/** + * nfc_3_2_resume_auto_read() - Resumes auto read after CPU intervention. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + */ +static int nfc_3_2_resume_auto_read(struct imx_nfc_data *this) +{ + unsigned int first; + unsigned int last; + unsigned int index; + unsigned int chip; + unsigned int column; + unsigned int page; + int return_value = 0; + void *primary_base = this->primary_regs; + + add_event("Entering nfc_3_2_resume_auto_read", 1); + + /* Get state. */ + + nfc_3_2_get_auto_loop_params(this, &first, &last, &index); + nfc_3_2_get_auto_addresses(this, 0, &chip, &column, &page); + + /* This function should be called for every chip, except the last. */ + + if ((index < first) || (index >= last)) { + return_value = !0; + goto exit; + } + + /* Move to the next chip. */ + + index++; + + /* Update state. */ + + nfc_3_2_set_auto_loop_params(this, first, last, index); + nfc_3_2_set_auto_addresses(this, 0, index, column, page); + + /* Reset to buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch the operation. */ + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_READ_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_resume_auto_read", -1); + + return return_value; + +} + +/** + * nfc_3_2_start_auto_write() - Starts an automatic write. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + * @start: The first physical chip number on which to operate. + * @count: The number of physical chips on which to operate. + * @column: The column address. + * @page: The page address. + */ +static int nfc_3_2_start_auto_write(struct imx_nfc_data *this, + unsigned start, unsigned count, unsigned column, unsigned page) +{ + uint32_t x; + int return_value = 0; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + add_event("Entering nfc_3_2_start_auto_write", 1); + + /* Check for nonsense. */ + + if ((start > 7) || (!count) || (count > 8)) { + return_value = !0; + goto exit; + } + + /* Set state. */ + + nfc_3_2_set_auto_loop_params(this, start, start + count - 1, start); + nfc_3_2_set_auto_addresses(this, 0, start, column, page); + + /* Set up for ONE iteration at a time. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_ITER_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Set up the commands. */ + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0xffff0000; + x |= NAND_CMD_SEQIN << 0; + x |= NAND_CMD_PAGEPROG << 8; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Clear the auto_prog_done bit. */ + + raw_clr_mask_l(NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_start_auto_write", -1); + + return return_value; + +} + +/** + * nfc_3_2_wait_for_auto_write() - Waits for auto write to be writey for the CPU. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + */ +static int nfc_3_2_wait_for_auto_write(struct imx_nfc_data *this) +{ + unsigned int first; + unsigned int last; + unsigned int index; + unsigned int chip; + unsigned int column; + unsigned int page; + uint32_t x; + int interrupt; + int transmitted; + int ready; + int return_value = 0; + void *primary_base = this->primary_regs; + void *secondary_base = this->secondary_regs; + + add_event("Entering nfc_3_2_wait_for_auto_write", 1); + + /* Get state. */ + + nfc_3_2_get_auto_loop_params(this, &first, &last, &index); + nfc_3_2_get_auto_addresses(this, 0, &chip, &column, &page); + + /* This function should be called for every chip. */ + + if ((index < first) || (index > last)) { + return_value = !0; + goto exit; + } + + /* Reset to buffer 0. */ + + raw_clr_mask_l(NFC_3_2_CONFIG1_RBA_MSK, + primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Launch the operation. */ + + nfc_3_2_add_state_events(this); + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_PROG_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + + nfc_3_2_add_state_events(this); + + /* Wait for the NFC to transmit the page. */ + + add_event("Spinning while the NFC transmits the page...", 0); + + do + transmitted = !!raw_read_mask_l(NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + while (!transmitted); + + /* + * When control arrives here, the auto_prog_done bit is set. This + * indicates the NFC has finished transmitting the current page. The CPU + * is now free to write the next page into the NFC's memory. The Flash + * hardware is still busy programming the page into its storage array. + * + * Clear the auto_prog_done bit. This is analogous to acknowledging an + * interrupt. + */ + + nfc_3_2_add_state_events(this); + + add_event("Acknowledging the page...", 0); + + raw_clr_mask_l(NFC_3_2_IPC_AUTO_PROG_DONE_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + nfc_3_2_add_state_events(this); + + /* + * If this is *not* the last iteration, move to the next chip and return + * to the caller so he can put the next page in the NFC buffer. + */ + + if (index < last) { + + add_event("Moving to the next chip...", 0); + + index++; + + nfc_3_2_set_auto_loop_params(this, first, last, index); + nfc_3_2_set_auto_addresses(this, 0, index, column, page); + + goto exit; + + } + + /* + * If control arrives here, this is the last iteration, so it's time to + * close out the entire operation. We need to wait for the medium to be + * ready and then acknowledge the final interrupt. + * + * Because of the way the NFC hardware works, the code here requires a + * bit of explanation. The most important rule is: + * + * During automatic operations, the NFC sets its + * interrupt bit *whenever* it sees the ready/busy + * signal transition from "Busy" to "Ready". + * + * Recall that the ready/busy signals from all the chips in the medium + * are "wire-anded." Thus, the NFC will only see that the medium is + * ready if *all* chips are ready. + * + * Because of variability in NAND Flash timing, the medium *may* have + * become ready during previous iterations, which means the interrupt + * bit *may* be set at this moment. This is a "left-over" interrupt, and + * can complicate our logic. + * + * The two bits of state that interest us here are the interrupt bit + * and the ready/busy bit. It boils down to the following truth table: + * + * | Interrupt | Ready/Busy | Description + * +------------+------------+--------------- + * | | | Busy medium and no left-over interrupt. + * | 0 | 0 | The final interrupt will arrive in the + * | | | future. + * +------------+------------+--------------- + * | | | Ready medium and no left-over interrupt. + * | 0 | 1 | There will be no final interrupt. This + * | | | case should be impossible. + * +------------+------------+--------------- + * | | | Busy medium and left-over interrupt. + * | 1 | 0 | The final interrupt will arrive in the + * | | | future. This is the hard case. + * +------------+------------+--------------- + * | | | Ready medium and left-over interrupt. + * | 1 | 1 | The final interrupt has already + * | | | arrived. Acknowledge it and exit. + * +------------+------------+--------------- + * + * Case #3 is a small problem. If we clear the interrupt, we may or may + * not have another interrupt following. + */ + + /* Sample the IPC register. */ + + x = __raw_readl(secondary_base + NFC_3_2_IPC_REG_OFF); + + interrupt = !!(x & NFC_3_2_IPC_INT_MSK); + ready = !!(x & NFC_3_2_IPC_RB_B_MSK); + + /* Check for the easy cases. */ + + if (!interrupt && !ready) { + add_event("Waiting for the final interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, true); + goto exit; + } else if (!interrupt && ready) { + add_event("Done." , 0); + goto exit; + } else if (interrupt && ready) { + add_event("Acknowledging the final interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, false); + goto exit; + + } + + /* + * If control arrives here, we hit case #3. Begin by acknowledging the + * interrupt we have right now. + */ + + add_event("Clearing the left-over interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, false); + + /* + * Check the ready/busy bit again. If the medium is still busy, then + * we're going to get one more interrupt. + */ + + ready = !!raw_read_mask_l(NFC_3_2_IPC_RB_B_MSK, + secondary_base + NFC_3_2_IPC_REG_OFF); + + if (!ready) { + add_event("Waiting for the final interrupt..." , 0); + nfc_util_wait_for_the_nfc(this, true); + } + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_wait_for_auto_write", -1); + + return return_value; + +} + +/** + * nfc_3_2_start_auto_erase() - Starts an automatic erase. + * + * This function returns 0 if everything went well. + * + * @this: Per-device data. + * @start: The first physical chip number on which to operate. + * @count: The number of physical chips on which to operate. + * @page: The page address. + */ +static int nfc_3_2_start_auto_erase(struct imx_nfc_data *this, + unsigned start, unsigned count, unsigned page) +{ + uint32_t x; + unsigned i; + int return_value = 0; + void *primary_base = this->primary_regs; + + add_event("Entering nfc_3_2_start_auto_erase", 1); + + /* Check for nonsense. */ + + if ((start > 7) || (!count) || (count > 8)) { + return_value = !0; + goto exit; + } + + /* Set up the commands. */ + + x = __raw_readl(primary_base + NFC_3_2_CMD_REG_OFF); + + x &= 0xffff0000; + x |= NAND_CMD_ERASE1 << 0; + x |= NAND_CMD_ERASE2 << 8; + + __raw_writel(x, primary_base + NFC_3_2_CMD_REG_OFF); + + /* Set the iterations. */ + + x = __raw_readl(primary_base + NFC_3_2_CONFIG1_REG_OFF); + + x &= ~NFC_3_2_CONFIG1_ITER_MSK; + x |= ((count - 1) << NFC_3_2_CONFIG1_ITER_POS) & + NFC_3_2_CONFIG1_ITER_MSK; + + __raw_writel(x, primary_base + NFC_3_2_CONFIG1_REG_OFF); + + /* Loop over chips, setting up the address groups. */ + + for (i = 0; i < count; i++) + nfc_3_2_set_auto_addresses(this, i, start + i, ~0, page); + + /* Launch the operation. */ + + add_event("Launching", 0); + + __raw_writel(NFC_3_2_LAUNCH_AUTO_ERASE_MSK, + primary_base + NFC_3_2_LAUNCH_REG_OFF); + +exit: /* Return. */ + + add_event("Exiting nfc_3_2_start_auto_erase", -1); + + return return_value; + +} + +/* + * At this point, we've defined all the version-specific primitives. We're now + * ready to construct the NFC HAL structures for every version. + */ + +struct nfc_hal nfc_1_0_hal = { + .major_version = 1, + .minor_version = 0, + .max_chip_count = 1, + .max_buffer_count = 4, + .spare_buf_stride = 16, + .has_secondary_regs = 0, + .can_be_symmetric = 0, + }; + +struct nfc_hal nfc_2_0_hal = { + .major_version = 2, + .minor_version = 0, + .max_chip_count = 1, + .max_buffer_count = 4, + .spare_buf_stride = 16, + .has_secondary_regs = false, + .can_be_symmetric = true, + .init = nfc_2_0_init, + .set_geometry = nfc_2_0_set_geometry, + .exit = nfc_2_x_exit, + .mask_interrupt = nfc_2_0_mask_interrupt, + .unmask_interrupt = nfc_2_0_unmask_interrupt, + .clear_interrupt = nfc_2_x_clear_interrupt, + .is_interrupting = nfc_2_x_is_interrupting, + .is_ready = 0, /* Ready/Busy not exposed. */ + .set_ecc = nfc_2_0_set_ecc, + .get_ecc_status = nfc_2_0_get_ecc_status, + .get_symmetric = nfc_2_0_get_symmetric, + .set_symmetric = nfc_2_0_set_symmetric, + .select_chip = nfc_2_0_select_chip, + .command_cycle = nfc_2_x_command_cycle, + .write_cycle = nfc_2_x_write_cycle, + .read_cycle = nfc_2_0_read_cycle, + .read_page = nfc_2_0_read_page, + .send_page = nfc_2_0_send_page, + .start_auto_read = 0, /* Not supported. */ + .wait_for_auto_read = 0, /* Not supported. */ + .resume_auto_read = 0, /* Not supported. */ + .start_auto_write = 0, /* Not supported. */ + .wait_for_auto_write = 0, /* Not supported. */ + .start_auto_erase = 0, /* Not supported. */ + }; + +struct nfc_hal nfc_2_1_hal = { + .major_version = 2, + .minor_version = 1, + .max_chip_count = 4, + .max_buffer_count = 8, + .spare_buf_stride = 64, + .has_secondary_regs = 0, + .can_be_symmetric = !0, + }; + +struct nfc_hal nfc_3_1_hal = { + .major_version = 3, + .minor_version = 1, + .max_chip_count = 4, + .max_buffer_count = 8, + .spare_buf_stride = 64, + .has_secondary_regs = !0, + .can_be_symmetric = !0, + }; + +struct nfc_hal nfc_3_2_hal = { + .major_version = 3, + .minor_version = 2, + .max_chip_count = 8, + .max_buffer_count = 8, + .spare_buf_stride = 64, + .has_secondary_regs = true, + .can_be_symmetric = true, + .init = nfc_3_2_init, + .set_geometry = nfc_3_2_set_geometry, + .exit = nfc_3_2_exit, + .set_closest_cycle = nfc_3_2_set_closest_cycle, + .mask_interrupt = nfc_3_2_mask_interrupt, + .unmask_interrupt = nfc_3_2_unmask_interrupt, + .clear_interrupt = nfc_3_2_clear_interrupt, + .is_interrupting = nfc_3_2_is_interrupting, + .is_ready = nfc_3_2_is_ready, + .set_force_ce = nfc_3_2_set_force_ce, + .set_ecc = nfc_3_2_set_ecc, + .get_ecc_status = nfc_3_2_get_ecc_status, + .get_symmetric = nfc_3_2_get_symmetric, + .set_symmetric = nfc_3_2_set_symmetric, + .select_chip = nfc_3_2_select_chip, + .command_cycle = nfc_3_2_command_cycle, + .write_cycle = nfc_3_2_write_cycle, + .read_cycle = nfc_3_2_read_cycle, + .read_page = nfc_3_2_read_page, + .send_page = nfc_3_2_send_page, + .start_auto_read = nfc_3_2_start_auto_read, + .wait_for_auto_read = nfc_3_2_wait_for_auto_read, + .resume_auto_read = nfc_3_2_resume_auto_read, + .start_auto_write = nfc_3_2_start_auto_write, + .wait_for_auto_write = nfc_3_2_wait_for_auto_write, + .start_auto_erase = nfc_3_2_start_auto_erase, + }; + +/* + * This array has a pointer to every NFC HAL structure. The probing process will + * find the one that matches the version given by the platform. + */ + +struct nfc_hal *(nfc_hals[]) = { + &nfc_1_0_hal, + &nfc_2_0_hal, + &nfc_2_1_hal, + &nfc_3_1_hal, + &nfc_3_2_hal, +}; + +/** + * mal_init() - Initialize the Medium Abstraction Layer. + * + * @this: Per-device data. + */ +static void mal_init(struct imx_nfc_data *this) +{ + this->interrupt_override = DRIVER_CHOICE; + this->auto_op_override = DRIVER_CHOICE; + this->inject_ecc_error = 0; +} + +/** + * mal_set_physical_geometry() - Set up the physical medium geometry. + * + * This function retrieves the physical geometry information discovered by + * nand_scan(), corrects it, and records it in the per-device data structure. + * + * @this: Per-device data. + */ +static int mal_set_physical_geometry(struct imx_nfc_data *this) +{ + struct mtd_info *mtd = &this->mtd; + struct nand_chip *nand = &this->nand; + struct device *dev = this->dev; + uint8_t manufacturer_id; + uint8_t device_id; + unsigned int block_size_in_pages; + unsigned int chip_size_in_blocks; + unsigned int chip_size_in_pages; + uint64_t medium_size_in_bytes; + struct physical_geometry *physical = &this->physical_geometry; + + /* + * Begin by transcribing exactly what the MTD code discovered. If there + * are any mistakes, we'll fix them in a moment. + */ + + physical->chip_count = nand->numchips; + physical->chip_size = nand->chipsize; + physical->block_size = mtd->erasesize; + physical->page_data_size = mtd->writesize; + physical->page_oob_size = mtd->oobsize; + + /* Read some of the ID bytes from the first NAND Flash chip. */ + + nand->select_chip(mtd, 0); + + nfc_util_send_cmd_and_addrs(this, NAND_CMD_READID, 0x00, -1); + + manufacturer_id = nand->read_byte(mtd); + device_id = nand->read_byte(mtd); + + /* + * Most manufacturers sell 4K page devices with 218 out-of-band bytes + * per page to accomodate ECC-8. + * + * Samsung and Hynix claim their parts have better reliability, so they + * only need ECC-4 and they have only 128 out-of-band bytes. + * + * The MTD code pays no attention to the manufacturer ID (something that + * eventually will have to change), so it believes that all 4K pages + * have 218 out-of-band bytes. + * + * We correct that mistake here. + */ + + if (physical->page_data_size == 4096) { + if ((manufacturer_id == NAND_MFR_SAMSUNG) || + (manufacturer_id == NAND_MFR_HYNIX)) { + physical->page_oob_size = 128; + } + } + + /* Compute some interesting facts. */ + + block_size_in_pages = + physical->block_size / physical->page_data_size; + chip_size_in_pages = + physical->chip_size >> (fls(physical->page_data_size) - 1); + chip_size_in_blocks = + physical->chip_size >> (fls(physical->block_size) - 1); + medium_size_in_bytes = + physical->chip_size * physical->chip_count; + + /* Report. */ + + dev_dbg(dev, "-----------------\n"); + dev_dbg(dev, "Physical Geometry\n"); + dev_dbg(dev, "-----------------\n"); + dev_dbg(dev, "Chip Count : %d\n", physical->chip_count); + dev_dbg(dev, "Page Data Size in Bytes: %u (0x%x)\n", + physical->page_data_size, physical->page_data_size); + dev_dbg(dev, "Page OOB Size in Bytes : %u\n", + physical->page_oob_size); + dev_dbg(dev, "Block Size in Bytes : %u (0x%x)\n", + physical->block_size, physical->block_size); + dev_dbg(dev, "Block Size in Pages : %u (0x%x)\n", + block_size_in_pages, block_size_in_pages); + dev_dbg(dev, "Chip Size in Bytes : %llu (0x%llx)\n", + physical->chip_size, physical->chip_size); + dev_dbg(dev, "Chip Size in Pages : %u (0x%x)\n", + chip_size_in_pages, chip_size_in_pages); + dev_dbg(dev, "Chip Size in Blocks : %u (0x%x)\n", + chip_size_in_blocks, chip_size_in_blocks); + dev_dbg(dev, "Medium Size in Bytes : %llu (0x%llx)\n", + medium_size_in_bytes, medium_size_in_bytes); + + /* Return success. */ + + return 0; + +} + +/** + * mal_set_nfc_geometry() - Set up the NFC geometry. + * + * This function calls the NFC HAL to select an NFC geometry that is compatible + * with the medium's physical geometry. + * + * @this: Per-device data. + */ +static int mal_set_nfc_geometry(struct imx_nfc_data *this) +{ + struct device *dev = this->dev; + struct nfc_geometry *nfc; + + /* Set the NFC geometry. */ + + if (this->nfc->set_geometry(this)) + return !0; + + /* Get a pointer to the new NFC geometry information. */ + + nfc = this->nfc_geometry; + + /* Report. */ + + dev_dbg(dev, "------------\n"); + dev_dbg(dev, "NFC Geometry\n"); + dev_dbg(dev, "------------\n"); + dev_dbg(dev, "Page Data Size in Bytes: %u (0x%x)\n", + nfc->page_data_size, nfc->page_data_size); + dev_dbg(dev, "Page OOB Size in Bytes : %u\n", nfc->page_oob_size); + dev_dbg(dev, "ECC Algorithm : %s\n", nfc->ecc_algorithm); + dev_dbg(dev, "ECC Strength : %d\n", nfc->ecc_strength); + dev_dbg(dev, "Buffer Count : %u\n", nfc->buffer_count); + dev_dbg(dev, "Spare Buffer Size : %u\n", nfc->spare_buf_size); + dev_dbg(dev, "Spare Buffer Spillover : %u\n", nfc->spare_buf_spill); + dev_dbg(dev, "Auto Read Available : %s\n", + this->nfc->start_auto_read ? "Yes" : "No"); + dev_dbg(dev, "Auto Write Available : %s\n", + this->nfc->start_auto_write ? "Yes" : "No"); + dev_dbg(dev, "Auto Erase Available : %s\n", + this->nfc->start_auto_erase ? "Yes" : "No"); + + /* Return success. */ + + return 0; + +} + +/** + * mal_set_logical_geometry() - Set up the logical medium geometry. + * + * This function constructs the logical geometry that we will expose to MTD, + * based on the physical and NFC geometries, and whether or not interleaving is + * on. + * + * @this: Per-device data. + */ +static int mal_set_logical_geometry(struct imx_nfc_data *this) +{ + const uint32_t max_medium_size_in_bytes = ~0; + int we_are_interleaving; + uint64_t physical_medium_size_in_bytes; + unsigned int usable_blocks; + unsigned int block_size_in_pages; + unsigned int chip_size_in_blocks; + unsigned int chip_size_in_pages; + unsigned int usable_medium_size_in_pages; + unsigned int usable_medium_size_in_blocks; + struct physical_geometry *physical = &this->physical_geometry; + struct nfc_geometry *nfc = this->nfc_geometry; + struct logical_geometry *logical = &this->logical_geometry; + struct device *dev = this->dev; + + /* Figure out if we're interleaving. */ + + we_are_interleaving = this->pdata->interleave; + + switch (imx_nfc_module_interleave_override) { + + case NEVER: + we_are_interleaving = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + we_are_interleaving = true; + break; + + } + + /* Compute the physical size of the medium. */ + + physical_medium_size_in_bytes = + physical->chip_count * physical->chip_size; + + /* Compute the logical geometry. */ + + if (!we_are_interleaving) { + + /* + * At this writing, MTD uses unsigned 32-bit variables to + * represent the size of the medium. If the physical medium is + * larger than that, the logical medium must be smaller. Here, + * we compute the total number of physical blocks in the medium + * that we can actually use. + */ + + if (physical_medium_size_in_bytes <= max_medium_size_in_bytes) { + usable_blocks = + physical_medium_size_in_bytes >> + (ffs(physical->block_size) - 1); + } else { + usable_blocks = + max_medium_size_in_bytes / physical->block_size; + } + + /* Set up the logical geometry. + * + * Notice that the usable medium size is not necessarily the + * same as the chip size multiplied by the number of physical + * chips. We can't afford to touch the physical chip size + * because the NAND Flash MTD code *requires* it to be a power + * of 2. + */ + + logical->chip_count = physical->chip_count; + logical->chip_size = physical->chip_size; + logical->usable_size = usable_blocks * physical->block_size; + logical->block_size = physical->block_size; + logical->page_data_size = nfc->page_data_size; + + /* Use the MTD layout that best matches the NFC geometry. */ + + logical->mtd_layout = &nfc->mtd_layout; + logical->page_oob_size = nfc->mtd_layout.eccbytes + + nfc->mtd_layout.oobavail; + + } else { + + /* + * If control arrives here, we are interleaving. Specifically, + * we are "horizontally concatenating" the pages in all the + * physical chips. + * + * - A logical page will be the size of a physical page + * multiplied by the number of physical chips. + * + * - A logical block will have the same number of pages as a + * physical block but, since the logical page size is larger, + * the logical block size is larger. + * + * - The entire medium will appear to be a single chip. + * + * At this writing, MTD uses unsigned 32-bit variables to + * represent the size of the medium. If the physical medium is + * larger than that, the logical medium must be smaller. + * + * The NAND Flash MTD code represents the size of a single chip + * as an unsigned 32-bit value. It also *requires* that the size + * of a chip be a power of two. Thus, the largest possible chip + * size is 2GiB. + * + * When interleaving, the entire medium appears to be one chip. + * Thus, when interleaving, the largest possible medium size is + * 2GiB. + */ + + if (physical_medium_size_in_bytes <= max_medium_size_in_bytes) { + logical->chip_size = + 0x1 << (fls(physical_medium_size_in_bytes) - 1); + } else { + logical->chip_size = + 0x1 << (fls(max_medium_size_in_bytes) - 1); + } + + /* + * If control arrives here, we're interleaving. The logical + * geometry is very different from the physical geometry. + */ + + logical->chip_count = 1; + logical->usable_size = logical->chip_size; + logical->block_size = + physical->block_size * physical->chip_count; + logical->page_data_size = + nfc->page_data_size * physical->chip_count; + + /* + * Since the logical geometry doesn't match the physical + * geometry, we can't use the MTD layout that matches the + * NFC geometry. We synthesize one here. + * + * Our "logical" OOB will be the concatenation of the first 5 + * bytes of the "physical" OOB of every chip. This has some + * important properties: + * + * - This will make the block mark of every physical chip + * visible (even for small page chips, which put their block + * mark in the 5th OOB byte). + * + * - None of the NFC controllers put ECC in the first 5 OOB + * bytes, so this layout exposes no ECC. + */ + + logical->page_oob_size = 5 * physical->chip_count; + + synthetic_layout.eccbytes = 0; + synthetic_layout.oobavail = 5 * physical->chip_count; + synthetic_layout.oobfree[0].offset = 0; + synthetic_layout.oobfree[0].length = synthetic_layout.oobavail; + + /* Install the synthetic layout. */ + + logical->mtd_layout = &synthetic_layout; + + } + + /* Compute some interesting facts. */ + + block_size_in_pages = logical->block_size / logical->page_data_size; + chip_size_in_pages = logical->chip_size / logical->page_data_size; + chip_size_in_blocks = logical->chip_size / logical->block_size; + usable_medium_size_in_pages = + logical->usable_size / logical->page_data_size; + usable_medium_size_in_blocks = + logical->usable_size / logical->block_size; + + /* Report. */ + + dev_dbg(dev, "----------------\n"); + dev_dbg(dev, "Logical Geometry\n"); + dev_dbg(dev, "----------------\n"); + dev_dbg(dev, "Chip Count : %d\n", logical->chip_count); + dev_dbg(dev, "Page Data Size in Bytes: %u (0x%x)\n", + logical->page_data_size, logical->page_data_size); + dev_dbg(dev, "Page OOB Size in Bytes : %u\n", + logical->page_oob_size); + dev_dbg(dev, "Block Size in Bytes : %u (0x%x)\n", + logical->block_size, logical->block_size); + dev_dbg(dev, "Block Size in Pages : %u (0x%x)\n", + block_size_in_pages, block_size_in_pages); + dev_dbg(dev, "Chip Size in Bytes : %u (0x%x)\n", + logical->chip_size, logical->chip_size); + dev_dbg(dev, "Chip Size in Pages : %u (0x%x)\n", + chip_size_in_pages, chip_size_in_pages); + dev_dbg(dev, "Chip Size in Blocks : %u (0x%x)\n", + chip_size_in_blocks, chip_size_in_blocks); + dev_dbg(dev, "Physical Size in Bytes : %llu (0x%llx)\n", + physical_medium_size_in_bytes, physical_medium_size_in_bytes); + dev_dbg(dev, "Usable Size in Bytes : %u (0x%x)\n", + logical->usable_size, logical->usable_size); + dev_dbg(dev, "Usable Size in Pages : %u (0x%x)\n", + usable_medium_size_in_pages, usable_medium_size_in_pages); + dev_dbg(dev, "Usable Size in Blocks : %u (0x%x)\n", + usable_medium_size_in_blocks, usable_medium_size_in_blocks); + + /* Return success. */ + + return 0; + +} + +/** + * mal_set_mtd_geometry() - Set up the MTD geometry. + * + * This function adjusts the owning MTD data structures to match the logical + * geometry we've chosen. + * + * @this: Per-device data. + */ +static int mal_set_mtd_geometry(struct imx_nfc_data *this) +{ + struct logical_geometry *logical = &this->logical_geometry; + struct mtd_info *mtd = &this->mtd; + struct nand_chip *nand = &this->nand; + + /* Configure the struct mtd_info. */ + + mtd->size = logical->usable_size; + mtd->erasesize = logical->block_size; + mtd->writesize = logical->page_data_size; + mtd->ecclayout = logical->mtd_layout; + mtd->oobavail = mtd->ecclayout->oobavail; + mtd->oobsize = mtd->ecclayout->oobavail + mtd->ecclayout->eccbytes; + mtd->subpage_sft = 0; /* We don't support sub-page writing. */ + + /* Configure the struct nand_chip. */ + + nand->numchips = logical->chip_count; + nand->chipsize = logical->chip_size; + nand->page_shift = ffs(logical->page_data_size) - 1; + nand->pagemask = (nand->chipsize >> nand->page_shift) - 1; + nand->subpagesize = mtd->writesize >> mtd->subpage_sft; + nand->phys_erase_shift = ffs(logical->block_size) - 1; + nand->bbt_erase_shift = nand->phys_erase_shift; + nand->chip_shift = ffs(logical->chip_size) - 1; + nand->oob_poi = nand->buffers->databuf+logical->page_data_size; + nand->ecc.layout = logical->mtd_layout; + + /* Set up the pattern that describes block marks. */ + + if (is_small_page_chip(this)) + nand->badblock_pattern = &small_page_block_mark_descriptor; + else + nand->badblock_pattern = &large_page_block_mark_descriptor; + + /* Return success. */ + + return 0; +} + +/** + * mal_set_geometry() - Set up the medium geometry. + * + * @this: Per-device data. + */ +static int mal_set_geometry(struct imx_nfc_data *this) +{ + + /* Set up the various layers of geometry, in this specific order. */ + + if (mal_set_physical_geometry(this)) + return !0; + + if (mal_set_nfc_geometry(this)) + return !0; + + if (mal_set_logical_geometry(this)) + return !0; + + if (mal_set_mtd_geometry(this)) + return !0; + + /* Return success. */ + + return 0; + +} + +/** + * mal_reset() - Resets the given chip. + * + * This is the fully-generalized reset operation, including support for + * interleaving. All reset operations funnel through here. + * + * @this: Per-device data. + * @chip: The logical chip of interest. + */ +static void mal_reset(struct imx_nfc_data *this, unsigned chip) +{ + int we_are_interleaving; + unsigned int start; + unsigned int end; + unsigned int i; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_get_status", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++) { + + /* Select the current chip. */ + + this->nfc->select_chip(this, i); + + /* Reset the current chip. */ + + add_event("Resetting...", 0); + + nfc_util_send_cmd(this, NAND_CMD_RESET); + nfc_util_wait_for_the_nfc(this, false); + + } + + add_event("Exiting mal_get_status", -1); + +} + +/** + * mal_get_status() - Abstracted status retrieval. + * + * For media with a single chip, or concatenated chips, the HIL explicitly + * addresses a single chip at a time and wants the status from that chip only. + * + * For interleaved media, we must combine the individual chip states. At this + * writing, the NAND MTD system knows about the following bits in status + * registers: + * + * +------------------------+-------+---------+ + * | | | Combine | + * | Macro | Value | With | + * +------------------------+-------+---------+ + * | NAND_STATUS_FAIL | 0x01 | OR | + * | NAND_STATUS_FAIL_N1 | 0x02 | OR | + * | NAND_STATUS_TRUE_READY | 0x20 | AND | + * | NAND_STATUS_READY | 0x40 | AND | + * | NAND_STATUS_WP | 0x80 | AND | + * +------------------------+-------+---------+ + * + * @this: Per-device data. + * @chip: The logical chip of interest. + */ +static uint8_t mal_get_status(struct imx_nfc_data *this, unsigned chip) +{ + int we_are_interleaving; + unsigned int start; + unsigned int end; + unsigned int i; + unsigned int x; + unsigned int or_mask; + unsigned int and_mask; + uint8_t status; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_get_status", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + + /* Compute the masks we need. */ + + or_mask = NAND_STATUS_FAIL | NAND_STATUS_FAIL_N1; + and_mask = NAND_STATUS_TRUE_READY | NAND_STATUS_READY | NAND_STATUS_WP; + + /* Assume the chip is successful, ready and writeable. */ + + status = and_mask & ~or_mask; + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++) { + + /* Select the current chip. */ + + this->nfc->select_chip(this, i); + + /* Get the current chip's status. */ + + add_event("Sending the command...", 0); + + nfc_util_send_cmd(this, NAND_CMD_STATUS); + nfc_util_wait_for_the_nfc(this, false); + + add_event("Reading the status...", 0); + + x = this->nfc->read_cycle(this); + + /* Fold this chip's status into the combined status. */ + + status |= (x & or_mask); + status &= (x & and_mask) | or_mask; + + } + + add_event("Exiting mal_get_status", -1); + + return status; + +} + +/** + * mal_read_a_page() - Abstracted page read. + * + * This function returns the ECC status for the entire read operation. A + * positive return value indicates the number of errors that were corrected + * (symbol errors for Reed-Solomon hardware engines, bit errors for BCH hardware + * engines). A negative return value indicates that the ECC engine failed to + * correct all errors and the data is corrupted. A zero return value indicates + * there were no errors at all. + * + * @this: Per-device data. + * @use_ecc: Indicates if we're to use ECC. + * @chip: The logical chip of interest. + * @page: The logical page number to read. + * @data: A pointer to the destination data buffer. If this pointer is null, + * that indicates the caller doesn't want the data. + * @oob: A pointer to the destination OOB buffer. If this pointer is null, + * that indicates the caller doesn't want the OOB. + */ +static int mal_read_a_page(struct imx_nfc_data *this, int use_ecc, + unsigned chip, unsigned page, uint8_t *data, uint8_t *oob) +{ + int we_are_interleaving; + int use_automatic_op; + unsigned int start; + unsigned int end; + unsigned int current_chip; + unsigned int oob_bytes_to_copy; + unsigned int data_bytes_to_copy; + int status; + unsigned int worst_case_ecc_status; + int return_value = 0; + struct physical_geometry *physical = &this->physical_geometry; + struct nfc_geometry *nfc = this->nfc_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_read_a_page", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + use_automatic_op = !!this->nfc->start_auto_read; + + /* Apply the automatic operation override, if any. */ + + switch (this->auto_op_override) { + + case NEVER: + use_automatic_op = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + if (this->nfc->start_auto_read) + use_automatic_op = true; + break; + + } + + /* Set up ECC. */ + + this->nfc->set_ecc(this, use_ecc); + + /* Check if we're interleaving and set up the loop iterations. */ + + if (we_are_interleaving) { + + start = 0; + end = physical->chip_count; + + data_bytes_to_copy = + this->logical_geometry.page_data_size / + this->physical_geometry.chip_count; + oob_bytes_to_copy = + this->logical_geometry.page_oob_size / + this->physical_geometry.chip_count; + + } else { + + start = chip; + end = start + 1; + + data_bytes_to_copy = this->logical_geometry.page_data_size; + oob_bytes_to_copy = this->logical_geometry.page_oob_size; + + } + + /* If we're using the automatic operation, start it now. */ + + if (use_automatic_op) { + add_event("Starting the automatic operation...", 0); + this->nfc->start_auto_read(this, start, end - start, 0, page); + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (current_chip = start; current_chip < end; current_chip++) { + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + add_event("Waiting...", 0); + this->nfc->wait_for_auto_read(this); + + } else { + + /* Select the current chip. */ + + this->nfc->select_chip(this, current_chip); + + /* Set up the chip. */ + + add_event("Sending the command and addresses...", 0); + + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_READ0, 0, page); + + if (is_large_page_chip(this)) { + add_event("Sending the final command...", 0); + nfc_util_send_cmd(this, NAND_CMD_READSTART); + } + + /* Wait till the page is ready. */ + + add_event("Waiting for the page to arrive...", 0); + + nfc_util_wait_for_the_nfc(this, true); + + /* Read the page. */ + + add_event("Reading the page...", 0); + + this->nfc->read_page(this); + + } + + /* Copy a page out of the NFC. */ + + add_event("Copying from the NFC...", 0); + + if (oob) { + nfc_util_copy_from_the_nfc(this, + nfc->page_data_size, oob, oob_bytes_to_copy); + oob += oob_bytes_to_copy; + } + + if (data) { + nfc_util_copy_from_the_nfc(this, + 0, data, data_bytes_to_copy); + data += data_bytes_to_copy; + } + + /* + * If we're using ECC, and we haven't already seen an ECC + * failure, continue to gather ECC status. Note that, if we + * *do* see an ECC failure, we continue to read because the + * client might want the data for forensic purposes. + */ + + if (use_ecc && (return_value >= 0)) { + + add_event("Getting ECC status...", 0); + + status = this->nfc->get_ecc_status(this); + + if (status >= 0) + return_value += status; + else + return_value = -1; + + } + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + /* + * If this is not the last iteration, resume the + * automatic operation. + */ + + if (current_chip < (end - 1)) { + add_event("Resuming...", 0); + this->nfc->resume_auto_read(this); + } + + } + + } + + /* Check if we're supposed to inject an ECC error. */ + + if (use_ecc && this->inject_ecc_error) { + + /* Inject the appropriate error. */ + + if (this->inject_ecc_error < 0) { + + add_event("Injecting an uncorrectable error...", 0); + + return_value = -1; + + } else { + + add_event("Injecting correctable errors...", 0); + + worst_case_ecc_status = + physical->chip_count * + nfc->buffer_count * + nfc->ecc_strength; + + if (this->inject_ecc_error > worst_case_ecc_status) + return_value = worst_case_ecc_status; + else + return_value = this->inject_ecc_error; + + } + + /* Stop injecting further errors. */ + + this->inject_ecc_error = 0; + + } + + /* Return. */ + + add_event("Exiting mal_read_a_page", -1); + + return return_value; + +} + +/** + * mal_write_a_page() - Abstracted page write. + * + * This function returns zero if the operation succeeded, or -EIO if the + * operation failed. + * + * @this: Per-device data. + * @use_ecc: Indicates if we're to use ECC. + * @chip: The logical chip of interest. + * @page: The logical page number to write. + * @data: A pointer to the source data buffer. + * @oob: A pointer to the source OOB buffer. + */ +static int mal_write_a_page(struct imx_nfc_data *this, int use_ecc, + unsigned chip, unsigned page, const uint8_t *data, const uint8_t *oob) +{ + int we_are_interleaving; + int use_automatic_op; + unsigned int start; + unsigned int end; + unsigned int current_chip; + unsigned int oob_bytes_to_copy; + unsigned int data_bytes_to_copy; + int return_value = 0; + struct physical_geometry *physical = &this->physical_geometry; + struct nfc_geometry *nfc = this->nfc_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_write_a_page", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + use_automatic_op = !!this->nfc->start_auto_write; + + /* Apply the automatic operation override, if any. */ + + switch (this->auto_op_override) { + + case NEVER: + use_automatic_op = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + if (this->nfc->start_auto_write) + use_automatic_op = true; + break; + + } + + /* Set up ECC. */ + + this->nfc->set_ecc(this, use_ecc); + + /* Check if we're interleaving and set up the loop iterations. */ + + if (we_are_interleaving) { + + start = 0; + end = physical->chip_count; + + data_bytes_to_copy = + this->logical_geometry.page_data_size / + this->physical_geometry.chip_count; + oob_bytes_to_copy = + this->logical_geometry.page_oob_size / + this->physical_geometry.chip_count; + + } else { + + start = chip; + end = start + 1; + + data_bytes_to_copy = this->logical_geometry.page_data_size; + oob_bytes_to_copy = this->logical_geometry.page_oob_size; + + } + + /* If we're using the automatic operation, start the hardware now. */ + + if (use_automatic_op) { + add_event("Starting the automatic operation...", 0); + this->nfc->start_auto_write(this, start, end - start, 0, page); + } + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (current_chip = start; current_chip < end; current_chip++) { + + /* Copy a page into the NFC. */ + + add_event("Copying to the NFC...", 0); + + nfc_util_copy_to_the_nfc(this, oob, nfc->page_data_size, + oob_bytes_to_copy); + oob += oob_bytes_to_copy; + + nfc_util_copy_to_the_nfc(this, data, 0, data_bytes_to_copy); + + data += data_bytes_to_copy; + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + /* Wait for the write operation to finish. */ + + add_event("Waiting...", 0); + + this->nfc->wait_for_auto_write(this); + + } else { + + /* Select the current chip. */ + + this->nfc->select_chip(this, current_chip); + + /* Set up the chip. */ + + add_event("Sending the command and addresses...", 0); + + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_SEQIN, 0, page); + + /* Send the page. */ + + add_event("Sending the page...", 0); + + this->nfc->send_page(this); + + /* Start programming the page. */ + + add_event("Programming the page...", 0); + + nfc_util_send_cmd(this, NAND_CMD_PAGEPROG); + + /* Wait until the page is finished. */ + + add_event("Waiting...", 0); + + nfc_util_wait_for_the_nfc(this, true); + + } + + } + + /* Get status. */ + + add_event("Gathering status...", 0); + + if (mal_get_status(this, chip) & NAND_STATUS_FAIL) { + add_event("Bad status", 0); + return_value = -EIO; + } else { + add_event("Good status", 0); + } + + /* Return. */ + + add_event("Exiting mal_write_a_page", -1); + + return return_value; + +} + +/** + * mal_erase_a_block() - Abstract block erase operation. + * + * Note that this function does *not* wait for the operation to finish. The + * caller is expected to call waitfunc() at some later time. + * + * @this: Per-device data. + * @chip: The logical chip of interest. + * @page: A logical page address that identifies the block to erase. + */ +static void mal_erase_a_block(struct imx_nfc_data *this, + unsigned chip, unsigned page) +{ + int we_are_interleaving; + int use_automatic_op; + unsigned int start; + unsigned int end; + unsigned int i; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + add_event("Entering mal_erase_a_block", 1); + + /* Establish some important facts. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + use_automatic_op = !!this->nfc->start_auto_erase; + + /* Apply the automatic operation override, if any. */ + + switch (this->auto_op_override) { + + case NEVER: + use_automatic_op = false; + break; + + case DRIVER_CHOICE: + break; + + case ALWAYS: + if (this->nfc->start_auto_erase) + use_automatic_op = true; + break; + + } + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Check if we're using the automatic operation. */ + + if (use_automatic_op) { + + /* + * Start the operation. Note that we don't wait for it to + * finish because the HIL will call our waitfunc(). + */ + + add_event("Starting the automatic operation...", 0); + + this->nfc->start_auto_erase(this, start, end - start, page); + + } else { + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++) { + + /* Select the current chip. */ + + this->nfc->select_chip(this, i); + + /* Set up the chip. */ + + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_ERASE1, -1, page); + + /* Start the erase. */ + + nfc_util_send_cmd(this, NAND_CMD_ERASE2); + + /* + * If this is the last time through the loop, break out + * now so we don't try to wait (the HIL will call our + * waitfunc() for the final wait). + */ + + if (i >= (end - 1)) + break; + + /* Wait for the erase on the current chip to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + } + + } + + add_event("Exiting mal_erase_a_block", -1); + +} + +/** + * mal_is_block_bad() - Abstract bad block check. + * + * @this: Per-device data. + * @chip: The logical chip of interest. + * @page: The logical page number to read. + */ + #if 0 + +/* TODO: Finish this function and plug it in. */ + +static int mal_is_block_bad(struct imx_nfc_data *this, + unsigned chip, unsigned page) +{ + int we_are_interleaving; + unsigned int start; + unsigned int end; + unsigned int i; + uint8_t *p; + int return_value = 0; + struct nand_chip *nand = &this->nand; + struct physical_geometry *physical = &this->physical_geometry; + struct logical_geometry *logical = &this->logical_geometry; + + /* Figure out if we're interleaving. */ + + we_are_interleaving = logical->chip_count != physical->chip_count; + + /* + * We're about to use the NAND Flash MTD layer's buffer, so invalidate + * the page cache. + */ + + this->nand.pagebuf = -1; + + /* + * Read the OOB of the given page, using the NAND Flash MTD's buffer. + * + * Notice that ECC is off, which it *must* be when scanning block marks. + */ + + mal_read_a_page(this, false, + this->current_chip, this->page_address, 0, nand->oob_poi); + + /* Choose the loop bounds. */ + + if (we_are_interleaving) { + start = 0; + end = physical->chip_count; + } else { + start = chip; + end = start + 1; + } + + /* Start scanning at the beginning of the OOB data. */ + + p = nand->oob_poi; + + /* Loop over physical chips. */ + + add_event("Looping over physical chips...", 0); + + for (i = start; i < end; i++, p += 5) { + + /* Examine the OOB for this chip. */ + + if (p[nand->badblockpos] != 0xff) { + return_value = !0; + break; + } + + } + + /* Return. */ + + return return_value; + +} +#endif + +/** + * mil_init() - Initializes the MTD Interface Layer. + * + * @this: Per-device data. + */ +static void mil_init(struct imx_nfc_data *this) +{ + this->current_chip = -1; /* No chip is selected yet. */ + this->command_is_new = false; /* No command yet. */ +} + +/** + * mil_cmd_ctrl() - MTD Interface cmd_ctrl() + * + * @mtd: A pointer to the owning MTD. + * @dat: The data signals to present to the chip. + * @ctrl: The control signals to present to the chip. + */ +static void mil_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_dev_ready() - MTD Interface dev_ready() + * + * @mtd: A pointer to the owning MTD. + */ +static int mil_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc dev_ready]\n"); + + add_event("Entering mil_dev_ready", 1); + + if (this->nfc->is_ready(this)) { + add_event("Exiting mil_dev_ready - Returning ready", -1); + return !0; + } else { + add_event("Exiting mil_dev_ready - Returning busy", -1); + return 0; + } + +} + +/** + * mil_select_chip() - MTD Interface select_chip() + * + * @mtd: A pointer to the owning MTD. + * @chip: The chip number to select, or -1 to select no chip. + */ +static void mil_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc select_chip] chip: %d\n", chip); + + /* Figure out what kind of transition this is. */ + + if ((this->current_chip < 0) && (chip >= 0)) { + start_event_trace("Entering mil_select_chip"); + if (this->pdata->force_ce) + this->nfc->set_force_ce(this, true); + clk_enable(this->clock); + add_event("Exiting mil_select_chip", -1); + } else if ((this->current_chip >= 0) && (chip < 0)) { + add_event("Entering mil_select_chip", 1); + if (this->pdata->force_ce) + this->nfc->set_force_ce(this, false); + clk_disable(this->clock); + stop_event_trace("Exiting mil_select_chip"); + } else { + add_event("Entering mil_select_chip", 1); + add_event("Exiting mil_select_chip", -1); + } + + this->current_chip = chip; + +} + +/** + * mil_cmdfunc() - MTD Interface cmdfunc() + * + * This function handles NAND Flash command codes from the HIL. Since only the + * HIL calls this function (none of the reference implementations we use do), it + * needs to handle very few command codes. + * + * @mtd: A pointer to the owning MTD. + * @command: The command code. + * @column: The column address associated with this command code, or -1 if no + * column address applies. + * @page: The page address associated with this command code, or -1 if no + * page address applies. + */ +static void mil_cmdfunc(struct mtd_info *mtd, + unsigned command, int column, int page) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc cmdfunc] command: 0x%02x, " + "column: 0x%04x, page: 0x%06x\n", command, column, page); + + add_event("Entering mil_cmdfunc", 1); + + /* Record the command and the fact that it hasn't yet been sent. */ + + this->command = command; + this->command_is_new = true; + + /* + * Process the command code. + * + * Note the default case to trap unrecognized codes. Thus, every command + * we support must have a case here, even if we don't have to do any + * pre-processing work. If the HIL changes and starts sending commands + * we haven't explicitly implemented, this will warn us. + */ + + switch (command) { + + case NAND_CMD_READ0: + add_event("NAND_CMD_READ0", 0); + /* + * After calling this function to send the command and + * addresses, the HIL will call ecc.read_page() or + * ecc.read_page_raw() to collect the data. + * + * The column address from the HIL is always zero. The only + * information we need to keep from this call is the page + * address. + */ + this->page_address = page; + break; + + case NAND_CMD_STATUS: + add_event("NAND_CMD_STATUS", 0); + /* + * After calling this function to send the command, the HIL + * will call read_byte() once to collect the status. + */ + break; + + case NAND_CMD_READID: + add_event("NAND_CMD_READID", 0); + /* + * After calling this function to send the command, the HIL + * will call read_byte() repeatedly to collect ID bytes. + */ + break; + + case NAND_CMD_RESET: + add_event("NAND_CMD_RESET", 0); + mal_reset(this, this->current_chip); + break; + + default: + dev_emerg(this->dev, "Unsupported NAND Flash command code: " + "0x%02x\n", command); + BUG(); + break; + + } + + add_event("Exiting mil_cmdfunc", -1); + +} + +/** + * mil_waitfunc() - MTD Interface waifunc() + * + * This function blocks until the current chip is ready and then returns the + * contents of the chip's status register. The HIL only calls this function + * after starting an erase operation. + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + */ +static int mil_waitfunc(struct mtd_info *mtd, struct nand_chip *nand) +{ + int status; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc waitfunc]\n"); + + add_event("Entering mil_waitfunc", 1); + + /* Wait for the NFC to finish. */ + + nfc_util_wait_for_the_nfc(this, true); + + /* Get the status. */ + + status = mal_get_status(this, this->current_chip); + + add_event("Exiting mil_waitfunc", -1); + + return status; + +} + +/** + * mil_read_byte() - MTD Interface read_byte(). + * + * @mtd: A pointer to the owning MTD. + */ +static uint8_t mil_read_byte(struct mtd_info *mtd) +{ + uint8_t byte = 0; + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + add_event("Entering mil_read_byte", 1); + + /* + * The command sent by the HIL before it called this function determines + * how we get the byte we're going to return. + */ + + switch (this->command) { + + case NAND_CMD_STATUS: + add_event("NAND_CMD_STATUS", 0); + byte = mal_get_status(this, this->current_chip); + break; + + case NAND_CMD_READID: + add_event("NAND_CMD_READID", 0); + + /* + * Check if the command is new. If so, then the HIL just + * recently called cmdfunc(), so the current chip isn't selected + * and the command hasn't been sent to the chip. + */ + + if (this->command_is_new) { + add_event("Sending the \"Read ID\" command...", 0); + this->nfc->select_chip(this, this->current_chip); + nfc_util_send_cmd_and_addrs(this, + NAND_CMD_READID, 0, -1); + this->command_is_new = false; + } + + /* Read the ID byte. */ + + add_event("Reading the ID byte...", 0); + + byte = this->nfc->read_cycle(this); + + break; + + default: + dev_emerg(this->dev, "Unsupported NAND Flash command code: " + "0x%02x\n", this->command); + BUG(); + break; + + } + + DEBUG(MTD_DEBUG_LEVEL2, + "[imx_nfc read_byte] Returning: 0x%02x\n", byte); + + add_event("Exiting mil_read_byte", -1); + + return byte; + +} + +/** + * mil_read_word() - MTD Interface read_word(). + * + * @mtd: A pointer to the owning MTD. + */ +static uint16_t mil_read_word(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_read_buf() - MTD Interface read_buf(). + * + * @mtd: A pointer to the owning MTD. + * @buf: The destination buffer. + * @len: The number of bytes to read. + */ +static void mil_read_buf(struct mtd_info *mtd, uint8_t * buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_write_buf() - MTD Interface write_buf(). + * + * @mtd: A pointer to the owning MTD. + * @buf: The source buffer. + * @len: The number of bytes to read. + */ +static void mil_write_buf(struct mtd_info *mtd, const uint8_t * buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_verify_buf() - MTD Interface verify_buf(). + * + * @mtd: A pointer to the owning MTD. + * @buf: The destination buffer. + * @len: The number of bytes to read. + */ +static int mil_verify_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_ecc_hwctl() - MTD Interface ecc.hwctl(). + * + * @mtd: A pointer to the owning MTD. + * @mode: The ECC mode. + */ +static void mil_ecc_hwctl(struct mtd_info *mtd, int mode) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_ecc_calculate() - MTD Interface ecc.calculate(). + * + * @mtd: A pointer to the owning MTD. + * @dat: A pointer to the source data. + * @ecc_code: A pointer to a buffer that will receive the resulting ECC. + */ +static int mil_ecc_calculate(struct mtd_info *mtd, + const uint8_t *dat, uint8_t *ecc_code) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_ecc_correct() - MTD Interface ecc.correct(). + * + * @mtd: A pointer to the owning MTD. + * @dat: A pointer to the source data. + * @read_ecc: A pointer to the ECC that was read from the medium. + * @calc_ecc: A pointer to the ECC that was calculated for the source data. + */ +static int mil_ecc_correct(struct mtd_info *mtd, + uint8_t *dat, uint8_t *read_ecc, uint8_t *calc_ecc) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); + return 0; +} + +/** + * mil_ecc_read_page() - MTD Interface ecc.read_page(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the destination buffer. + */ +static int mil_ecc_read_page(struct mtd_info *mtd, + struct nand_chip *nand, uint8_t *buf) +{ + int ecc_status; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_read_page]\n"); + + add_event("Entering mil_ecc_read_page", 1); + + /* Read the page. */ + + ecc_status = + mal_read_a_page(this, true, this->current_chip, + this->page_address, buf, nand->oob_poi); + + /* Propagate ECC information. */ + + if (ecc_status < 0) { + add_event("ECC Failure", 0); + mtd->ecc_stats.failed++; + } else if (ecc_status > 0) { + add_event("ECC Corrections", 0); + mtd->ecc_stats.corrected += ecc_status; + } + + add_event("Exiting mil_ecc_read_page", -1); + + return 0; + +} + +/** + * mil_ecc_read_page_raw() - MTD Interface ecc.read_page_raw(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the destination buffer. + */ +static int mil_ecc_read_page_raw(struct mtd_info *mtd, + struct nand_chip *nand, uint8_t *buf) +{ + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_read_page_raw]\n"); + + add_event("Entering mil_ecc_read_page_raw", 1); + + mal_read_a_page(this, false, this->current_chip, + this->page_address, buf, nand->oob_poi); + + add_event("Exiting mil_ecc_read_page_raw", -1); + + return 0; + +} + +/** + * mil_ecc_write_page() - MTD Interface ecc.write_page(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the source buffer. + */ +static void mil_ecc_write_page(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf) +{ + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_ecc_write_page_raw() - MTD Interface ecc.write_page_raw(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the source buffer. + */ +static void mil_ecc_write_page_raw(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf) +{ + struct imx_nfc_data *this = nand->priv; + unimplemented(this, __func__); +} + +/** + * mil_write_page() - MTD Interface ecc.write_page(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @buf: A pointer to the source buffer. + * @page: The page number to write. + * @cached: Indicates cached programming (ignored). + * @raw: Indicates not to use ECC. + */ +static int mil_write_page(struct mtd_info *mtd, + struct nand_chip *nand, const uint8_t *buf, + int page, int cached, int raw) +{ + int return_value; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc write_page]\n"); + + add_event("Entering mil_write_page", 1); + + return_value = mal_write_a_page(this, !raw, + this->current_chip, page, buf, nand->oob_poi); + + add_event("Exiting mil_write_page", -1); + + return return_value; + +} + +/** + * mil_ecc_read_oob() - MTD Interface read_oob(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @page: The page number to read. + * @sndcmd: Indicates this function should send a command to the chip before + * reading the out-of-band bytes. This is only false for small page + * chips that support auto-increment. + */ +static int mil_ecc_read_oob(struct mtd_info *mtd, struct nand_chip *nand, + int page, int sndcmd) +{ + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_read_oob] " + "page: 0x%06x, sndcmd: %s\n", page, sndcmd ? "Yes" : "No"); + + add_event("Entering mil_ecc_read_oob", 1); + + mal_read_a_page(this, false, + this->current_chip, page, 0, nand->oob_poi); + + add_event("Exiting mil_ecc_read_oob", -1); + + /* + * Return true, indicating that the next call to this function must send + * a command. + */ + + return true; + +} + +/** + * mil_ecc_write_oob() - MTD Interface write_oob(). + * + * @mtd: A pointer to the owning MTD. + * @nand: A pointer to the owning NAND Flash MTD. + * @page: The page number to write. + */ +static int mil_ecc_write_oob(struct mtd_info *mtd, + struct nand_chip *nand, int page) +{ + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc ecc_write_oob] page: 0x%06x\n", page); + + /* + * There are fundamental incompatibilities between the i.MX NFC and the + * NAND Flash MTD model that make it essentially impossible to write the + * out-of-band bytes. + */ + + dev_emerg(this->dev, "This driver doesn't support writing the OOB\n"); + WARN_ON(1); + + /* Return status. */ + + return -EIO; + +} + +/** + * mil_erase_cmd() - MTD Interface erase_cmd(). + * + * We set the erase_cmd pointer in struct nand_chip to point to this function. + * Thus, the HIL will call here for all erase operations. + * + * Strictly speaking, since the erase_cmd pointer is marked "Internal," we + * shouldn't be doing this. However, the only reason the HIL uses that pointer + * is to install a different function for erasing conventional NAND Flash or AND + * Flash. Since AND Flash is obsolete and we don't support it, this isn't + * important. + * + * Furthermore, to cleanly implement interleaving (which is critical to speeding + * up erase operations), we want to "hook into" the operation at the highest + * semantic level possible. If we don't hook this function, then the only way + * we'll know that an erase is happening is when the HIL calls cmdfunc() with + * an erase command. Implementing interleaving at that level is roughly a + * billion times less desirable. + * + * This function does *not* wait for the operation to finish. The HIL will call + * waitfunc() later to wait for the operation to finish. + * + * @mtd: A pointer to the owning MTD. + * @page: A page address that identifies the block to erase. + */ +static void mil_erase_cmd(struct mtd_info *mtd, int page) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc erase_cmd] page: 0x%06x\n", page); + + add_event("Entering mil_erase_cmd", 1); + + mal_erase_a_block(this, this->current_chip, page); + + add_event("Exiting mil_erase_cmd", -1); + +} + +/** + * mil_block_bad() - MTD Interface block_bad(). + * + * @mtd: A pointer to the owning MTD. + * @ofs: The offset of the block of interest, from the start of the medium. + * @getchip: Indicates this function must acquire the MTD before using it. + */ +#if 0 + +/* TODO: Finish this function and plug it in. */ + +static int mil_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + unsigned int chip; + unsigned int page; + int return_value; + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc block_bad] page: 0x%06x\n", page); + + add_event("Entering mil_block_bad", 1); + + /* Compute the logical chip number that contains the given offset. */ + + chip = (unsigned int) (ofs >> nand->chip_shift); + + /* Compute the logical page address within the logical chip. */ + + page = ((unsigned int) (ofs >> nand->page_shift)) & nand->pagemask; + + /* Check if the block is bad. */ + + return_value = mal_is_block_bad(this, chip, page); + + if (return_value) + add_event("Bad block", 0); + + /* Return. */ + + add_event("Exiting mil_block_bad", -1); + + return return_value; + +} +#endif + +/** + * mil_scan_bbt() - MTD Interface scan_bbt(). + * + * The HIL calls this function once, when it initializes the NAND Flash MTD. + * + * Nominally, the purpose of this function is to look for or create the bad + * block table. In fact, since the HIL calls this function at the very end of + * the initialization process started by nand_scan(), and the HIL doesn't have a + * more formal mechanism, everyone "hooks" this function to continue the + * initialization process. + * + * At this point, the physical NAND Flash chips have been identified and + * counted, so we know the physical geometry. This enables us to make some + * important configuration decisions. + * + * The return value of this function propogates directly back to this driver's + * call to nand_scan(). Anything other than zero will cause this driver to + * tear everything down and declare failure. + * + * @mtd: A pointer to the owning MTD. + */ +static int mil_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct imx_nfc_data *this = nand->priv; + + DEBUG(MTD_DEBUG_LEVEL2, "[imx_nfc scan_bbt] \n"); + + add_event("Entering mil_scan_bbt", 1); + + /* + * We replace the erase_cmd() function that the MTD NAND Flash system + * has installed with our own. See mil_erase_cmd() for the reasons. + */ + + nand->erase_cmd = mil_erase_cmd; + + /* + * Tell MTD users that the out-of-band area can't be written. + * + * This flag is not part of the standard kernel source tree. It comes + * from a patch that touches both MTD and JFFS2. + * + * The problem is that, without this patch, JFFS2 believes it can write + * the data area and the out-of-band area separately. This is wrong for + * two reasons: + * + * 1) Our NFC distributes out-of-band bytes throughout the page, + * intermingled with the data, and covered by the same ECC. + * Thus, it's not possible to write the out-of-band bytes and + * data bytes separately. + * + * 2) Large page (MLC) Flash chips don't support partial page + * writes. You must write the entire page at a time. Thus, even + * if our NFC didn't force you to write out-of-band and data + * bytes together, it would *still* be a bad idea to do + * otherwise. + */ + + mtd->flags &= ~MTD_OOB_WRITEABLE; + + /* Set up geometry. */ + + mal_set_geometry(this); + + /* We use the reference implementation for bad block management. */ + + add_event("Exiting mil_scan_bbt", -1); + + return nand_scan_bbt(mtd, nand->badblock_pattern); + +} + +/** + * parse_bool_param() - Parses the value of a boolean parameter string. + * + * @s: The string to parse. + */ +static int parse_bool_param(const char *s) +{ + + if (!strcmp(s, "1") || !strcmp(s, "on") || + !strcmp(s, "yes") || !strcmp(s, "true")) { + return 1; + } else if (!strcmp(s, "0") || !strcmp(s, "off") || + !strcmp(s, "no") || !strcmp(s, "false")) { + return 0; + } else { + return -1; + } + +} + +/** + * set_module_enable() - Controls whether this driver is enabled. + * + * Note that this state can be controlled from the command line. Disabling this + * driver is sometimes useful for debugging. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_enable(const char *s, struct kernel_param *kp) +{ + + switch (parse_bool_param(s)) { + + case 1: + imx_nfc_module_enable = true; + break; + + case 0: + imx_nfc_module_enable = false; + break; + + default: + return -EINVAL; + break; + + } + + return 0; + +} + +/** + * get_module_enable() - Indicates whether this driver is enabled. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_enable(char *p, struct kernel_param *kp) +{ + p[0] = imx_nfc_module_enable ? '1' : '0'; + p[1] = 0; + return 1; +} + +#ifdef EVENT_REPORTING + +/** + * set_module_report_events() - Controls whether this driver reports events. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_report_events(const char *s, struct kernel_param *kp) +{ + + switch (parse_bool_param(s)) { + + case 1: + imx_nfc_module_report_events = true; + break; + + case 0: + imx_nfc_module_report_events = false; + reset_event_trace(); + break; + + default: + return -EINVAL; + break; + + } + + return 0; + +} + +/** + * get_module_report_events() - Indicates whether the driver reports events. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_report_events(char *p, struct kernel_param *kp) +{ + p[0] = imx_nfc_module_report_events ? '1' : '0'; + p[1] = 0; + return 1; +} + +/** + * set_module_dump_events() - Forces the driver to dump current events. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_dump_events(const char *s, struct kernel_param *kp) +{ + dump_event_trace(); + return 0; +} + +#endif /*EVENT_REPORTING*/ + +/** + * set_module_interleave_override() - Controls the interleave override. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_interleave_override(const char *s, + struct kernel_param *kp) +{ + + if (!strcmp(s, "-1")) + imx_nfc_module_interleave_override = NEVER; + else if (!strcmp(s, "0")) + imx_nfc_module_interleave_override = DRIVER_CHOICE; + else if (!strcmp(s, "1")) + imx_nfc_module_interleave_override = ALWAYS; + else + return -EINVAL; + + return 0; + +} + +/** + * get_module_interleave_override() - Indicates the interleave override state. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_interleave_override(char *p, struct kernel_param *kp) +{ + return sprintf(p, "%d", imx_nfc_module_interleave_override); +} + +/** + * set_force_bytewise_copy() - Controls forced bytewise copy from/to the NFC. + * + * @s: The new value of the parameter. + * @kp: The owning kernel parameter. + */ +static int set_module_force_bytewise_copy(const char *s, + struct kernel_param *kp) +{ + + switch (parse_bool_param(s)) { + + case 1: + imx_nfc_module_force_bytewise_copy = true; + break; + + case 0: + imx_nfc_module_force_bytewise_copy = false; + break; + + default: + return -EINVAL; + break; + + } + + return 0; + +} + +/** + * get_force_bytewise_copy() - Indicates whether bytewise copy is being forced. + * + * @p: A pointer to a (small) buffer that will receive the response. + * @kp: The owning kernel parameter. + */ +static int get_module_force_bytewise_copy(char *p, struct kernel_param *kp) +{ + p[0] = imx_nfc_module_force_bytewise_copy ? '1' : '0'; + p[1] = 0; + return 1; +} + +/* Module attributes that appear in sysfs. */ + +module_param_call(enable, set_module_enable, get_module_enable, 0, 0444); +MODULE_PARM_DESC(enable, "enables/disables probing"); + +#ifdef EVENT_REPORTING +module_param_call(report_events, + set_module_report_events, get_module_report_events, 0, 0644); +MODULE_PARM_DESC(report_events, "enables/disables event reporting"); + +module_param_call(dump_events, set_module_dump_events, 0, 0, 0644); +MODULE_PARM_DESC(dump_events, "forces current event dump"); +#endif + +module_param_call(interleave_override, set_module_interleave_override, + get_module_interleave_override, 0, 0444); +MODULE_PARM_DESC(interleave_override, "overrides interleaving choice"); + +module_param_call(force_bytewise_copy, set_module_force_bytewise_copy, + get_module_force_bytewise_copy, 0, 0644); +MODULE_PARM_DESC(force_bytewise_copy, "forces bytewise copy from/to NFC"); + +/** + * show_device_platform_info() - Shows the device's platform information. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_platform_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + void *buffer_base; + void *primary_base; + void *secondary_base; + unsigned int interrupt_number; + struct resource *r; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct platform_device *pdev = this->pdev; + struct imx_nfc_platform_data *pdata = this->pdata; + struct mtd_partition *partition; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_BUFFERS_ADDR_RES_NAME); + + buffer_base = (void *) r->start; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME); + + primary_base = (void *) r->start; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + + secondary_base = (void *) r->start; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + IMX_NFC_INTERRUPT_RES_NAME); + + interrupt_number = r->start; + + o += sprintf(buf, + "NFC Major Version : %u\n" + "NFC Minor Version : %u\n" + "Force CE : %s\n" + "Target Cycle in ns : %u\n" + "Clock Name : %s\n" + "Interleave : %s\n" + "Buffer Base : 0x%p\n" + "Primary Registers Base : 0x%p\n" + "Secondary Registers Base: 0x%p\n" + "Interrupt Number : %u\n" + , + pdata->nfc_major_version, + pdata->nfc_minor_version, + pdata->force_ce ? "Yes" : "No", + pdata->target_cycle_in_ns, + pdata->clock_name, + pdata->interleave ? "Yes" : "No", + buffer_base, + primary_base, + secondary_base, + interrupt_number + ); + + #ifdef CONFIG_MTD_PARTITIONS + + o += sprintf(buf + o, + "Partition Count : %u\n" + , + pdata->partition_count + ); + + /* Loop over partitions. */ + + for (i = 0; i < pdata->partition_count; i++) { + + partition = pdata->partitions + i; + + o += sprintf(buf+o, " [%d]\n", i); + o += sprintf(buf+o, " Name : %s\n", partition->name); + + switch (partition->offset) { + + case MTDPART_OFS_NXTBLK: + o += sprintf(buf+o, " Offset: " + "MTDPART_OFS_NXTBLK\n"); + break; + case MTDPART_OFS_APPEND: + o += sprintf(buf+o, " Offset: " + "MTDPART_OFS_APPEND\n"); + break; + default: + o += sprintf(buf+o, " Offset: %u (%u MiB)\n", + partition->offset, + partition->offset / (1024 * 1024)); + break; + + } + + if (partition->size == MTDPART_SIZ_FULL) { + o += sprintf(buf+o, " Size : " + "MTDPART_SIZ_FULL\n"); + } else { + o += sprintf(buf+o, " Size : %u (%u MiB)\n", + partition->size, + partition->size / (1024 * 1024)); + } + + } + + #endif + + return o; + +} + +/** + * show_device_physical_geometry() - Shows the physical Flash device geometry. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_physical_geometry(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct physical_geometry *physical = &this->physical_geometry; + + return sprintf(buf, + "Chip Count : %u\n" + "Chip Size in Bytes : %llu\n" + "Block Size in Bytes : %u\n" + "Page Data Size in Bytes: %u\n" + "Page OOB Size in Bytes : %u\n" + , + physical->chip_count, + physical->chip_size, + physical->block_size, + physical->page_data_size, + physical->page_oob_size + ); + +} + +/** + * show_device_nfc_info() - Shows the NFC-specific information. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_nfc_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long parent_clock_rate_in_hz; + unsigned long clock_rate_in_hz; + struct clk *parent_clock; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct nfc_hal *nfc = this->nfc; + + parent_clock = clk_get_parent(this->clock); + parent_clock_rate_in_hz = clk_get_rate(parent_clock); + clock_rate_in_hz = clk_get_rate(this->clock); + + return sprintf(buf, + "Major Version : %u\n" + "Minor Version : %u\n" + "Max Chip Count : %u\n" + "Max Buffer Count : %u\n" + "Spare Buffer Stride : %u\n" + "Has Secondary Registers : %s\n" + "Can Be Symmetric : %s\n" + "Exposes Ready/Busy : %s\n" + "Parent Clock Rate in Hz : %lu\n" + "Clock Rate in Hz : %lu\n" + "Symmetric Clock : %s\n" + , + nfc->major_version, + nfc->minor_version, + nfc->max_chip_count, + nfc->max_buffer_count, + nfc->spare_buf_stride, + nfc->has_secondary_regs ? "Yes" : "No", + nfc->can_be_symmetric ? "Yes" : "No", + nfc->is_ready ? "Yes" : "No", + parent_clock_rate_in_hz, + clock_rate_in_hz, + this->nfc->get_symmetric(this) ? "Yes" : "No" + ); + +} + +/** + * show_device_nfc_geometry() - Shows the NFC view of the device geometry. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_nfc_geometry(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + return sprintf(buf, + "Page Data Size in Bytes : %u\n" + "Page OOB Size in Bytes : %u\n" + "ECC Algorithm : %s\n" + "ECC Strength : %u\n" + "Buffers in Use : %u\n" + "Spare Buffer Size in Use: %u\n" + "Spare Buffer Spillover : %u\n" + , + this->nfc_geometry->page_data_size, + this->nfc_geometry->page_oob_size, + this->nfc_geometry->ecc_algorithm, + this->nfc_geometry->ecc_strength, + this->nfc_geometry->buffer_count, + this->nfc_geometry->spare_buf_size, + this->nfc_geometry->spare_buf_spill + ); + +} + +/** + * show_device_logical_geometry() - Shows the logical device geometry. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_logical_geometry(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct logical_geometry *logical = &this->logical_geometry; + + return sprintf(buf, + "Chip Count : %u\n" + "Chip Size in Bytes : %u\n" + "Usable Size in Bytes : %u\n" + "Block Size in Bytes : %u\n" + "Page Data Size in Bytes: %u\n" + "Page OOB Size in Bytes : %u\n" + , + logical->chip_count, + logical->chip_size, + logical->usable_size, + logical->block_size, + logical->page_data_size, + logical->page_oob_size + ); + +} + +/** + * show_device_mtd_nand_info() - Shows the device's MTD NAND-specific info. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_mtd_nand_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + unsigned int j; + static const unsigned int columns = 8; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct nand_chip *nand = &this->nand; + + o += sprintf(buf + o, + "Options : 0x%08x\n" + "Chip Count : %u\n" + "Chip Size : %lu\n" + "Minimum Writable Size: %u\n" + "Page Shift : %u\n" + "Page Mask : 0x%x\n" + "Block Shift : %u\n" + "BBT Block Shift : %u\n" + "Chip Shift : %u\n" + "Block Mark Offset : %u\n" + "Cached Page Number : %d\n" + , + nand->options, + nand->numchips, + nand->chipsize, + nand->subpagesize, + nand->page_shift, + nand->pagemask, + nand->phys_erase_shift, + nand->bbt_erase_shift, + nand->chip_shift, + nand->badblockpos, + nand->pagebuf + ); + + o += sprintf(buf + o, + "ECC Byte Count : %u\n" + , + nand->ecc.layout->eccbytes + ); + + /* Loop over rows. */ + + for (i = 0; (i * columns) < nand->ecc.layout->eccbytes; i++) { + + /* Loop over columns within rows. */ + + for (j = 0; j < columns; j++) { + + if (((i * columns) + j) >= nand->ecc.layout->eccbytes) + break; + + o += sprintf(buf + o, " %3u", + nand->ecc.layout->eccpos[(i * columns) + j]); + + } + + o += sprintf(buf + o, "\n"); + + } + + o += sprintf(buf + o, + "OOB Available Bytes : %u\n" + , + nand->ecc.layout->oobavail + ); + + j = 0; + + for (i = 0; j < nand->ecc.layout->oobavail; i++) { + + j += nand->ecc.layout->oobfree[i].length; + + o += sprintf(buf + o, + " [%3u, %2u]\n" + , + nand->ecc.layout->oobfree[i].offset, + nand->ecc.layout->oobfree[i].length + ); + + } + + return o; + +} + +/** + * show_device_mtd_info() - Shows the device's MTD-specific information. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_mtd_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + unsigned int j; + static const unsigned int columns = 8; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct mtd_info *mtd = &this->mtd; + + o += sprintf(buf + o, + "Name : %s\n" + "Type : %u\n" + "Flags : 0x%08x\n" + "Size in Bytes : %u\n" + "Erase Region Count : %d\n" + "Erase Size in Bytes: %u\n" + "Write Size in Bytes: %u\n" + "OOB Size in Bytes : %u\n" + "Errors Corrected : %u\n" + "Failed Reads : %u\n" + "Bad Block Count : %u\n" + "BBT Block Count : %u\n" + , + mtd->name, + mtd->type, + mtd->flags, + mtd->size, + mtd->numeraseregions, + mtd->erasesize, + mtd->writesize, + mtd->oobsize, + mtd->ecc_stats.corrected, + mtd->ecc_stats.failed, + mtd->ecc_stats.badblocks, + mtd->ecc_stats.bbtblocks + ); + + o += sprintf(buf + o, + "ECC Byte Count : %u\n" + , + mtd->ecclayout->eccbytes + ); + + /* Loop over rows. */ + + for (i = 0; (i * columns) < mtd->ecclayout->eccbytes; i++) { + + /* Loop over columns within rows. */ + + for (j = 0; j < columns; j++) { + + if (((i * columns) + j) >= mtd->ecclayout->eccbytes) + break; + + o += sprintf(buf + o, " %3u", + mtd->ecclayout->eccpos[(i * columns) + j]); + + } + + o += sprintf(buf + o, "\n"); + + } + + o += sprintf(buf + o, + "OOB Available Bytes: %u\n" + , + mtd->ecclayout->oobavail + ); + + j = 0; + + for (i = 0; j < mtd->ecclayout->oobavail; i++) { + + j += mtd->ecclayout->oobfree[i].length; + + o += sprintf(buf + o, + " [%3u, %2u]\n" + , + mtd->ecclayout->oobfree[i].offset, + mtd->ecclayout->oobfree[i].length + ); + + } + + return o; + +} + +/** + * show_device_bbt_pages() - Shows the pages in which BBT's appear. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_bbt_pages(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int o = 0; + unsigned int i; + struct imx_nfc_data *this = dev_get_drvdata(dev); + struct nand_chip *nand = &this->nand; + + /* Loop over main BBT pages. */ + + if (nand->bbt_td) + for (i = 0; i < NAND_MAX_CHIPS; i++) + o += sprintf(buf + o, "%d: 0x%08x\n", + i, nand->bbt_td->pages[i]); + + /* Loop over mirror BBT pages. */ + + if (nand->bbt_md) + for (i = 0; i < NAND_MAX_CHIPS; i++) + o += sprintf(buf + o, "%d: 0x%08x\n", + i, nand->bbt_md->pages[i]); + + return o; + +} + +/** + * show_device_cycle_in_ns() - Shows the device's cycle in ns. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_cycle_in_ns(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", get_cycle_in_ns(this)); +} + +/** + * store_device_cycle_in_ns() - Sets the device's cycle in ns. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_cycle_in_ns(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int error; + unsigned long new_cycle_in_ns; + struct imx_nfc_data *this = dev_get_drvdata(dev); + + /* Look for nonsense. */ + + if (!size) + return -EINVAL; + + /* Try to understand the new cycle period. */ + + if (strict_strtoul(buf, 0, &new_cycle_in_ns)) + return -EINVAL; + + /* Try to implement the new cycle period. */ + + error = this->nfc->set_closest_cycle(this, new_cycle_in_ns); + + if (error) + return -EINVAL; + + /* Return success. */ + + return size; + +} + +/** + * show_device_interrupt_override() - Shows the device's interrupt override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_interrupt_override(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + switch (this->interrupt_override) { + + case NEVER: + return sprintf(buf, "-1\n"); + break; + + case DRIVER_CHOICE: + return sprintf(buf, "0\n"); + break; + + case ALWAYS: + return sprintf(buf, "1\n"); + break; + + default: + return sprintf(buf, "?\n"); + break; + + } + +} + +/** + * store_device_interrupt_override() - Sets the device's interrupt override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_interrupt_override(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + if (!strcmp(buf, "-1")) + this->interrupt_override = NEVER; + else if (!strcmp(buf, "0")) + this->interrupt_override = DRIVER_CHOICE; + else if (!strcmp(buf, "1")) + this->interrupt_override = ALWAYS; + else + return -EINVAL; + + return size; + +} + +/** + * show_device_auto_op_override() - Shows the device's automatic op override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_auto_op_override(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + switch (this->auto_op_override) { + + case NEVER: + return sprintf(buf, "-1\n"); + break; + + case DRIVER_CHOICE: + return sprintf(buf, "0\n"); + break; + + case ALWAYS: + return sprintf(buf, "1\n"); + break; + + default: + return sprintf(buf, "?\n"); + break; + + } + +} + +/** + * store_device_auto_op_override() - Sets the device's automatic op override. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_auto_op_override(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + if (!strcmp(buf, "-1")) + this->auto_op_override = NEVER; + else if (!strcmp(buf, "0")) + this->auto_op_override = DRIVER_CHOICE; + else if (!strcmp(buf, "1")) + this->auto_op_override = ALWAYS; + else + return -EINVAL; + + return size; + +} + +/** + * show_device_inject_ecc_error() - Shows the device's error injection flag. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer that will receive a representation of the attribute. + */ +static ssize_t show_device_inject_ecc_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", this->inject_ecc_error); + +} + +/** + * store_device_inject_ecc_error() - Sets the device's error injection flag. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_inject_ecc_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + unsigned long new_inject_ecc_error; + struct imx_nfc_data *this = dev_get_drvdata(dev); + + /* Look for nonsense. */ + + if (!size) + return -EINVAL; + + /* Try to understand the new cycle period. */ + + if (strict_strtol(buf, 0, &new_inject_ecc_error)) + return -EINVAL; + + /* Store the value. */ + + this->inject_ecc_error = new_inject_ecc_error; + + /* Return success. */ + + return size; + +} + +/** + * store_device_invalidate_page_cache() - Invalidates the device's page cache. + * + * @dev: The device of interest. + * @attr: The attribute of interest. + * @buf: A buffer containing a new attribute value. + * @size: The size of the buffer. + */ +static ssize_t store_device_invalidate_page_cache(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct imx_nfc_data *this = dev_get_drvdata(dev); + + /* Invalidate the page cache. */ + + this->nand.pagebuf = -1; + + /* Return success. */ + + return size; + +} + +/* Device attributes that appear in sysfs. */ + +static DEVICE_ATTR(platform_info , 0444, show_device_platform_info , 0); +static DEVICE_ATTR(physical_geometry, 0444, show_device_physical_geometry, 0); +static DEVICE_ATTR(nfc_info , 0444, show_device_nfc_info , 0); +static DEVICE_ATTR(nfc_geometry , 0444, show_device_nfc_geometry , 0); +static DEVICE_ATTR(logical_geometry , 0444, show_device_logical_geometry , 0); +static DEVICE_ATTR(mtd_nand_info , 0444, show_device_mtd_nand_info , 0); +static DEVICE_ATTR(mtd_info , 0444, show_device_mtd_info , 0); +static DEVICE_ATTR(bbt_pages , 0444, show_device_bbt_pages , 0); + +static DEVICE_ATTR(cycle_in_ns, 0644, + show_device_cycle_in_ns, store_device_cycle_in_ns); + +static DEVICE_ATTR(interrupt_override, 0644, + show_device_interrupt_override, store_device_interrupt_override); + +static DEVICE_ATTR(auto_op_override, 0644, + show_device_auto_op_override, store_device_auto_op_override); + +static DEVICE_ATTR(inject_ecc_error, 0644, + show_device_inject_ecc_error, store_device_inject_ecc_error); + +static DEVICE_ATTR(invalidate_page_cache, 0644, + 0, store_device_invalidate_page_cache); + +static struct device_attribute *device_attributes[] = { + &dev_attr_platform_info, + &dev_attr_physical_geometry, + &dev_attr_nfc_info, + &dev_attr_nfc_geometry, + &dev_attr_logical_geometry, + &dev_attr_mtd_nand_info, + &dev_attr_mtd_info, + &dev_attr_bbt_pages, + &dev_attr_cycle_in_ns, + &dev_attr_interrupt_override, + &dev_attr_auto_op_override, + &dev_attr_inject_ecc_error, + &dev_attr_invalidate_page_cache, +}; + +/** + * validate_the_platform() - Validates information about the platform. + * + * Note that this function doesn't validate the NFC version. That's done when + * the probing process attempts to configure for the specific hardware version. + * + * @pdev: A pointer to the platform device. + */ +static int validate_the_platform(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_nfc_platform_data *pdata = pdev->dev.platform_data; + + /* Validate the clock name. */ + + if (!pdata->clock_name) { + dev_err(dev, "No clock name\n"); + return -ENXIO; + } + + /* Validate the partitions. */ + + if ((pdata->partitions && (!pdata->partition_count)) || + (!pdata->partitions && (pdata->partition_count))) { + dev_err(dev, "Bad partition data\n"); + return -ENXIO; + } + + /* Return success */ + + return 0; + +} + +/** + * set_up_the_nfc_hal() - Sets up for the specific NFC hardware HAL. + * + * @this: Per-device data. + */ +static int set_up_the_nfc_hal(struct imx_nfc_data *this) +{ + unsigned int i; + struct nfc_hal *p; + struct imx_nfc_platform_data *pdata = this->pdata; + + for (i = 0; i < ARRAY_SIZE(nfc_hals); i++) { + + p = nfc_hals[i]; + + /* + * Restrict to 3.2 until others are fully implemented. + * + * TODO: Remove this. + */ + + if ((p->major_version != 3) && (p->minor_version != 2)) + continue; + + if ((p->major_version == pdata->nfc_major_version) && + (p->minor_version == pdata->nfc_minor_version)) { + this->nfc = p; + return 0; + break; + } + + } + + dev_err(this->dev, "Unkown NFC version %d.%d\n", + pdata->nfc_major_version, pdata->nfc_minor_version); + + return !0; + +} + +/** + * acquire_resources() - Tries to acquire resources. + * + * @this: Per-device data. + */ +static int acquire_resources(struct imx_nfc_data *this) +{ + + int error = 0; + struct platform_device *pdev = this->pdev; + struct device *dev = this->dev; + struct imx_nfc_platform_data *pdata = this->pdata; + struct resource *r; + + /* Find the buffers and map them. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_BUFFERS_ADDR_RES_NAME); + + if (!r) { + dev_err(dev, "Can't get '%s'\n", IMX_NFC_BUFFERS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_buffers; + } + + this->buffers = ioremap(r->start, r->end - r->start + 1); + + if (!this->buffers) { + dev_err(dev, "Can't remap buffers\n"); + error = -EIO; + goto exit_buffers; + } + + /* Find the primary registers and map them. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME); + + if (!r) { + dev_err(dev, "Can't get '%s'\n", + IMX_NFC_PRIMARY_REGS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_primary_registers; + } + + this->primary_regs = ioremap(r->start, r->end - r->start + 1); + + if (!this->primary_regs) { + dev_err(dev, "Can't remap the primary registers\n"); + error = -EIO; + goto exit_primary_registers; + } + + /* Check for secondary registers. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + + if (r && !this->nfc->has_secondary_regs) { + + dev_err(dev, "Resource '%s' should not be present\n", + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_secondary_registers; + + } + + if (this->nfc->has_secondary_regs) { + + if (!r) { + dev_err(dev, "Can't get '%s'\n", + IMX_NFC_SECONDARY_REGS_ADDR_RES_NAME); + error = -ENXIO; + goto exit_secondary_registers; + } + + this->secondary_regs = ioremap(r->start, r->end - r->start + 1); + + if (!this->secondary_regs) { + dev_err(dev, + "Can't remap the secondary registers\n"); + error = -EIO; + goto exit_secondary_registers; + } + + } + + /* Find out what our interrupt is and try to own it. */ + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + IMX_NFC_INTERRUPT_RES_NAME); + + if (!r) { + dev_err(dev, "Can't get '%s'\n", IMX_NFC_INTERRUPT_RES_NAME); + error = -ENXIO; + goto exit_irq; + } + + this->interrupt = r->start; + + error = request_irq(this->interrupt, + nfc_util_isr, 0, this->dev->bus_id, this); + + if (error) { + dev_err(dev, "Can't own interrupt %d\n", this->interrupt); + goto exit_irq; + } + + /* Find out the name of our clock and try to own it. */ + + this->clock = clk_get(dev, pdata->clock_name); + + if (this->clock == ERR_PTR(-ENOENT)) { + dev_err(dev, "Can't get clock '%s'\n", pdata->clock_name); + error = -ENXIO; + goto exit_clock; + } + + /* Return success. */ + + return 0; + + /* Error return paths begin here. */ + +exit_clock: + free_irq(this->interrupt, this); +exit_irq: + if (this->secondary_regs) + iounmap(this->secondary_regs); +exit_secondary_registers: + iounmap(this->primary_regs); +exit_primary_registers: + iounmap(this->buffers); +exit_buffers: + return error; + +} + +/** + * release_resources() - Releases resources. + * + * @this: Per-device data. + */ +static void release_resources(struct imx_nfc_data *this) +{ + + /* Release our clock. */ + + clk_disable(this->clock); + clk_put(this->clock); + + /* Release our interrupt. */ + + free_irq(this->interrupt, this); + + /* Release mapped memory. */ + + iounmap(this->buffers); + iounmap(this->primary_regs); + if (this->secondary_regs) + iounmap(this->secondary_regs); + +} + +/** + * register_with_mtd() - Registers this medium with MTD. + * + * @this: Per-device data. + */ +static int register_with_mtd(struct imx_nfc_data *this) +{ + int error = 0; + struct mtd_info *mtd = &this->mtd; + struct nand_chip *nand = &this->nand; + struct device *dev = this->dev; + struct imx_nfc_platform_data *pdata = this->pdata; + + /* Link the MTD structures together, along with our own data. */ + + mtd->priv = nand; + nand->priv = this; + + /* Prepare the MTD structure. */ + + mtd->owner = THIS_MODULE; + + /* + * Signal Control Functions + */ + + nand->cmd_ctrl = mil_cmd_ctrl; + + /* + * Chip Control Functions + * + * Not all of our NFC hardware versions expose Ready/Busy signals. For + * versions that don't, the is_ready function pointer will be NULL. In + * those cases, we leave the dev_ready member unassigned, which will + * cause the HIL to use a reference implementation's algorithm to + * discover when the hardware is ready. + */ + + nand->select_chip = mil_select_chip; + nand->cmdfunc = mil_cmdfunc; + nand->waitfunc = mil_waitfunc; + + if (this->nfc->is_ready) + nand->dev_ready = mil_dev_ready; + + /* + * Low-level I/O Functions + */ + + nand->read_byte = mil_read_byte; + nand->read_word = mil_read_word; + nand->read_buf = mil_read_buf; + nand->write_buf = mil_write_buf; + nand->verify_buf = mil_verify_buf; + + /* + * ECC Control Functions + */ + + nand->ecc.hwctl = mil_ecc_hwctl; + nand->ecc.calculate = mil_ecc_calculate; + nand->ecc.correct = mil_ecc_correct; + + /* + * ECC-Aware I/O Functions + */ + + nand->ecc.read_page = mil_ecc_read_page; + nand->ecc.read_page_raw = mil_ecc_read_page_raw; + nand->ecc.write_page = mil_ecc_write_page; + nand->ecc.write_page_raw = mil_ecc_write_page_raw; + + /* + * High-level I/O Functions + */ + + nand->write_page = mil_write_page; + nand->ecc.read_oob = mil_ecc_read_oob; + nand->ecc.write_oob = mil_ecc_write_oob; + + /* + * Bad Block Marking Functions + * + * We want to use the reference block_bad and block_markbad + * implementations, so we don't assign those members. + */ + + nand->scan_bbt = mil_scan_bbt; + + /* + * Error Recovery Functions + * + * We don't fill in the errstat function pointer because it's optional + * and we don't have a need for it. + */ + + /* + * Device Attributes + * + * We don't fill in the chip_delay member because we don't have a need + * for it. + * + * We support only 8-bit Flash bus width. + */ + + /* + * ECC Attributes + * + * Members that aren't set here are configured by a version-specific + * set_geometry() function. + */ + + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.size = NFC_MAIN_BUF_SIZE; + nand->ecc.prepad = 0; + nand->ecc.postpad = 0; + + /* + * Bad Block Management Attributes + * + * We don't fill in the following attributes: + * + * badblockpos + * bbt + * badblock_pattern + * bbt_erase_shift + * + * These attributes aren't hardware-specific, and the HIL makes fine + * choices without our help. + */ + + nand->options |= NAND_USE_FLASH_BBT; + nand->bbt_td = &bbt_main_descriptor; + nand->bbt_md = &bbt_mirror_descriptor; + + /* + * Device Control + * + * We don't fill in the controller attribute. In principle, we could set + * up a structure to represent the controller. However, since it's + * vanishingly improbable that we'll have more than one medium behind + * the controller, it's not worth the effort. We let the HIL handle it. + */ + + /* + * Memory-mapped I/O + * + * We don't fill in the following attributes: + * + * IO_ADDR_R + * IO_ADDR_W + * + * None of these are necessary because we don't have a memory-mapped + * implementation. + */ + + /* + * Install a "fake" ECC layout. + * + * We'll be calling nand_scan() to do the final MTD setup. If we haven't + * already chosen an ECC layout, then nand_scan() will choose one based + * on the part geometry it discovers. Unfortunately, it doesn't make + * good choices. It would be best if we could install the correct ECC + * layout now, before we call nand_scan(). We can't do that because we + * don't know the medium geometry yet. Here, we install a "fake" ECC + * layout just to stop nand_scan() from trying to pick on for itself. + * Later, in imx_nfc_scan_bbt(), when we know the medium geometry, we'll + * install the correct choice. + * + * Of course, this tactic depends critically on nand_scan() not using + * the fake layout before we can install a good one. This is in fact the + * case. + */ + + nand->ecc.layout = &nfc_geometry_512_16_RS_ECC1.mtd_layout; + + /* + * Ask the NAND Flash system to scan for chips. + * + * This will fill in reference implementations for all the members of + * the MTD structures that we didn't set, and will make the medium fully + * usable. + */ + + error = nand_scan(mtd, this->nfc->max_chip_count); + + if (error) { + dev_err(dev, "Chip scan failed\n"); + error = -ENXIO; + goto exit_scan; + } + + /* Register the MTD that represents the entire medium. */ + + mtd->name = "NAND Flash Medium"; + + add_mtd_device(mtd); + + /* Check if we're doing partitions and register MTD's accordingly. */ + + #ifdef CONFIG_MTD_PARTITIONS + + /* + * Look for partition information. If we find some, install + * them. Otherwise, use the partitions handed to us by the + * platform. + */ + + this->partition_count = + parse_mtd_partitions(mtd, partition_source_types, + &this->partitions, 0); + + if ((this->partition_count <= 0) && (pdata->partitions)) { + this->partition_count = pdata->partition_count; + this->partitions = pdata->partitions; + } + + if (this->partitions) + add_mtd_partitions(mtd, this->partitions, + this->partition_count); + + #endif + + /* Return success. */ + + return 0; + + /* Error return paths begin here. */ + +exit_scan: + return error; + +} + +/** + * unregister_with_mtd() - Unregisters this medium with MTD. + * + * @this: Per-device data. + */ +static void unregister_with_mtd(struct imx_nfc_data *this) +{ + + /* Get MTD to let go. */ + + nand_release(&this->mtd); + +} + +/** + * manage_sysfs_files() - Creates/removes sysfs files for this device. + * + * @this: Per-device data. + */ +static void manage_sysfs_files(struct imx_nfc_data *this, int create) +{ + int error; + unsigned int i; + struct device_attribute **attr; + + for (i = 0, attr = device_attributes; + i < ARRAY_SIZE(device_attributes); i++, attr++) { + + if (create) { + error = device_create_file(this->dev, *attr); + if (error) { + while (--attr >= device_attributes) + device_remove_file(this->dev, *attr); + return; + } + } else { + device_remove_file(this->dev, *attr); + } + + } + +} + +/** + * imx_nfc_probe() - Probes for a device and, if possible, takes ownership. + * + * @pdev: A pointer to the platform device. + */ +static int imx_nfc_probe(struct platform_device *pdev) +{ + int error = 0; + char *symmetric_clock; + struct clk *parent_clock; + unsigned long parent_clock_rate_in_hz; + unsigned long parent_clock_rate_in_mhz; + unsigned long nfc_clock_rate_in_hz; + unsigned long nfc_clock_rate_in_mhz; + unsigned long clock_divisor; + unsigned long cycle_in_ns; + struct device *dev = &pdev->dev; + struct imx_nfc_platform_data *pdata = pdev->dev.platform_data; + struct imx_nfc_data *this = 0; + + /* Say hello. */ + + dev_info(dev, "Probing...\n"); + + /* Check if we're enabled. */ + + if (!imx_nfc_module_enable) { + dev_info(dev, "Disabled\n"); + return -ENXIO; + } + + /* Validate the platform device data. */ + + error = validate_the_platform(pdev); + + if (error) + goto exit_validate_platform; + + /* Allocate memory for the per-device data. */ + + this = kzalloc(sizeof(*this), GFP_KERNEL); + + if (!this) { + dev_err(dev, "Failed to allocate per-device memory\n"); + error = -ENOMEM; + goto exit_allocate_this; + } + + /* Link our per-device data to the owning device. */ + + platform_set_drvdata(pdev, this); + + /* Fill in the convenience pointers in our per-device data. */ + + this->pdev = pdev; + this->dev = &pdev->dev; + this->pdata = pdata; + + /* Initialize the interrupt service pathway. */ + + init_completion(&this->done); + + /* Set up the NFC HAL. */ + + error = set_up_the_nfc_hal(this); + + if (error) + goto exit_set_up_nfc_hal; + + /* Attempt to acquire the resources we need. */ + + error = acquire_resources(this); + + if (error) + goto exit_acquire_resources; + + /* Initialize the NFC HAL. */ + + if (this->nfc->init(this)) { + error = -ENXIO; + goto exit_nfc_init; + } + + /* Tell the platform we're bringing this device up. */ + + if (pdata->init) + error = pdata->init(); + + if (error) + goto exit_platform_init; + + /* Report. */ + + parent_clock = clk_get_parent(this->clock); + parent_clock_rate_in_hz = clk_get_rate(parent_clock); + parent_clock_rate_in_mhz = parent_clock_rate_in_hz / 1000000; + nfc_clock_rate_in_hz = clk_get_rate(this->clock); + nfc_clock_rate_in_mhz = nfc_clock_rate_in_hz / 1000000; + + clock_divisor = parent_clock_rate_in_hz / nfc_clock_rate_in_hz; + symmetric_clock = this->nfc->get_symmetric(this) ? "Yes" : "No"; + cycle_in_ns = get_cycle_in_ns(this); + + dev_dbg(dev, "-------------\n"); + dev_dbg(dev, "Configuration\n"); + dev_dbg(dev, "-------------\n"); + dev_dbg(dev, "NFC Version : %d.%d\n" , this->nfc->major_version, + this->nfc->minor_version); + dev_dbg(dev, "Buffers : 0x%p\n" , this->buffers); + dev_dbg(dev, "Primary Regs : 0x%p\n" , this->primary_regs); + dev_dbg(dev, "Secondary Regs : 0x%p\n" , this->secondary_regs); + dev_dbg(dev, "Interrupt : %u\n" , this->interrupt); + dev_dbg(dev, "Clock Name : %s\n" , pdata->clock_name); + dev_dbg(dev, "Parent Clock Rate: %lu Hz (%lu MHz)\n", + parent_clock_rate_in_hz, + parent_clock_rate_in_mhz); + dev_dbg(dev, "Clock Divisor : %lu\n", clock_divisor); + dev_dbg(dev, "NFC Clock Rate : %lu Hz (%lu MHz)\n", + nfc_clock_rate_in_hz, + nfc_clock_rate_in_mhz); + dev_dbg(dev, "Symmetric Clock : %s\n" , symmetric_clock); + dev_dbg(dev, "Actual Cycle : %lu ns\n" , cycle_in_ns); + dev_dbg(dev, "Target Cycle : %u ns\n" , pdata->target_cycle_in_ns); + + /* Initialize the Medium Abstraction Layer. */ + + mal_init(this); + + /* Initialize the MTD Interface Layer. */ + + mil_init(this); + + /* Register this medium with MTD. */ + + error = register_with_mtd(this); + + if (error) + goto exit_mtd_registration; + + /* Create sysfs entries for this device. */ + + manage_sysfs_files(this, true); + + /* Return success. */ + + return 0; + + /* Error return paths begin here. */ + +exit_mtd_registration: + if (pdata->exit) + pdata->exit(); +exit_platform_init: + this->nfc->exit(this); +exit_nfc_init: +exit_acquire_resources: +exit_set_up_nfc_hal: + platform_set_drvdata(pdev, NULL); + kfree(this); +exit_allocate_this: +exit_validate_platform: + return error; + +} + +/** + * imx_nfc_remove() - Dissociates this driver from the given device. + * + * @pdev: A pointer to the device. + */ +static int __exit imx_nfc_remove(struct platform_device *pdev) +{ + struct imx_nfc_data *this = platform_get_drvdata(pdev); + + /* Remove sysfs entries for this device. */ + + manage_sysfs_files(this, false); + + /* Unregister with the NAND Flash MTD system. */ + + unregister_with_mtd(this); + + /* Tell the platform we're shutting down this device. */ + + if (this->pdata->exit) + this->pdata->exit(); + + /* Shut down the NFC. */ + + this->nfc->exit(this); + + /* Release our resources. */ + + release_resources(this); + + /* Unlink our per-device data from the platform device. */ + + platform_set_drvdata(pdev, NULL); + + /* Free our per-device data. */ + + kfree(this); + + /* Return success. */ + + return 0; +} + +#ifdef CONFIG_PM + +/** + * suspend() - Puts the NFC in a low power state. + * + * Refer to Documentation/driver-model/driver.txt for more information. + * + * @pdev: A pointer to the device. + * @state: The new power state. + */ + +static int imx_nfc_suspend(struct platform_device *pdev, pm_message_t state) +{ + int error = 0; + struct imx_nfc_data *this = platform_get_drvdata(pdev); + struct mtd_info *mtd = &this->mtd; + struct device *dev = &this->pdev->dev; + + dev_dbg(dev, "Suspending...\n"); + + /* Suspend MTD's use of this device. */ + + error = mtd->suspend(mtd); + + /* Suspend the actual hardware. */ + + clk_disable(this->clock); + + return error; + +} + +/** + * resume() - Brings the NFC back from a low power state. + * + * Refer to Documentation/driver-model/driver.txt for more information. + * + * @pdev: A pointer to the device. + */ +static int imx_nfc_resume(struct platform_device *pdev) +{ + struct imx_nfc_data *this = platform_get_drvdata(pdev); + struct mtd_info *mtd = &this->mtd; + struct device *dev = &this->pdev->dev; + + dev_dbg(dev, "Resuming...\n"); + + /* Resume MTD's use of this device. */ + + mtd->resume(mtd); + + return 0; + +} + +#else + +#define suspend NULL +#define resume NULL + +#endif /* CONFIG_PM */ + +/* + * This structure represents this driver to the platform management system. + */ +static struct platform_driver imx_nfc_driver = { + .driver = { + .name = IMX_NFC_DRIVER_NAME, + }, + .probe = imx_nfc_probe, + .remove = __exit_p(imx_nfc_remove), + .suspend = imx_nfc_suspend, + .resume = imx_nfc_resume, +}; + +/** + * imx_nfc_init() - Initializes this module. + */ +static int __init imx_nfc_init(void) +{ + + pr_info("i.MX NFC driver %s\n", DRIVER_VERSION); + + /* Register this driver with the platform management system. */ + + if (platform_driver_register(&imx_nfc_driver) != 0) { + pr_err("i.MX NFC driver registration failed\n"); + return -ENODEV; + } + + return 0; + +} + +/** + * imx_nfc_exit() - Deactivates this module. + */ +static void __exit imx_nfc_exit(void) +{ + pr_debug("i.MX NFC driver exiting...\n"); + platform_driver_unregister(&imx_nfc_driver); +} + +module_init(imx_nfc_init); +module_exit(imx_nfc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX NAND Flash Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/lba/Makefile b/drivers/mtd/nand/lba/Makefile new file mode 100644 index 000000000000..0e576bfa856d --- /dev/null +++ b/drivers/mtd/nand/lba/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_MTD_NAND_GPMI_LBA) += gpmi_lba.o +gpmi_lba-objs += gpmi-transport.o lba-core.o lba-blk.o diff --git a/drivers/mtd/nand/lba/gpmi-transport.c b/drivers/mtd/nand/lba/gpmi-transport.c new file mode 100644 index 000000000000..0073842db6b9 --- /dev/null +++ b/drivers/mtd/nand/lba/gpmi-transport.c @@ -0,0 +1,832 @@ +/* + * Freescale STMP37XX/STMP378X GPMI transport layer for LBA driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * Clock settings and hw init comes from + * gpmi driver by Dmitry Pervushin. + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <asm/div64.h> +#include <mach/platform.h> +#include <mach/regs-apbh.h> +#include <mach/regs-gpmi.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> +#include "gpmi.h" +#include "lba.h" + +struct lba_data *g_data; +static int max_chips = 1; +static long clk = -1; + +struct gpmi_nand_timing gpmi_safe_timing = { + .address_setup = 25, + .data_setup = 80, + .data_hold = 60, + .dsample_time = 6, +}; + +/****************************************************************************** + * HW init part + ******************************************************************************/ + +/** + * gpmi_irq - IRQ handler + * + * @irq: irq no + * @context: IRQ context, pointer to gpmi_nand_data + */ +static irqreturn_t gpmi_irq(int irq, void *context) +{ + struct lba_data *data = context; + int i; + + for (i = 0; i < max_chips; i++) { + if (stmp3xxx_dma_is_interrupt(data->nand[i].dma_ch)) { + stmp3xxx_dma_clear_interrupt(data->nand[i].dma_ch); + complete(&data->nand[i].done); + } + + } + stmp3xxx_clearl(BM_GPMI_CTRL1_DEV_IRQ | BM_GPMI_CTRL1_TIMEOUT_IRQ, + REGS_GPMI_BASE + HW_GPMI_CTRL1); + return IRQ_HANDLED; +} + +static inline u32 gpmi_cycles_ceil(u32 ntime, u32 period) +{ + int k; + + k = (ntime + period - 1) / period; + if (k == 0) + k++; + return k ; +} + + +/** + * gpmi_set_timings - set GPMI timings + * @pdev: pointer to GPMI platform device + * @tm: pointer to structure &gpmi_nand_timing with new timings + * + * During initialization, GPMI uses safe sub-optimal timings, which + * can be changed after reading boot control blocks + */ +void gpmi_set_timings(struct lba_data *data, struct gpmi_nand_timing *tm) +{ + u32 period_ns = 1000000 / clk_get_rate(data->clk) + 1; + u32 address_cycles, data_setup_cycles; + u32 data_hold_cycles, data_sample_cycles; + u32 busy_timeout; + u32 t0, reg; + + address_cycles = gpmi_cycles_ceil(tm->address_setup, period_ns); + data_setup_cycles = gpmi_cycles_ceil(tm->data_setup, period_ns); + data_hold_cycles = gpmi_cycles_ceil(tm->data_hold, period_ns); + data_sample_cycles = gpmi_cycles_ceil(tm->dsample_time + period_ns / 4, + period_ns / 2); + busy_timeout = gpmi_cycles_ceil(10000000 / 4096, period_ns); + + t0 = address_cycles << BP_GPMI_TIMING0_ADDRESS_SETUP; + t0 |= data_setup_cycles << BP_GPMI_TIMING0_DATA_SETUP; + t0 |= data_hold_cycles << BP_GPMI_TIMING0_DATA_HOLD; + __raw_writel(t0, REGS_GPMI_BASE + HW_GPMI_TIMING0); + + __raw_writel(busy_timeout, REGS_GPMI_BASE + HW_GPMI_TIMING1); + + reg = __raw_readl(REGS_GPMI_BASE + HW_GPMI_CTRL1); +#ifdef CONFIG_ARCH_STMP378X + reg &= ~BM_GPMI_CTRL1_RDN_DELAY; + reg |= data_sample_cycles << BP_GPMI_CTRL1_RDN_DELAY; +#else + reg &= ~BM_GPMI_CTRL1_DSAMPLE_TIME; + reg |= data_sample_cycles << BP_GPMI_CTRL1_DSAMPLE_TIME; +#endif + __raw_writel(reg, REGS_GPMI_BASE + HW_GPMI_CTRL1); +} + +void queue_plug(struct lba_data *data) +{ + u32 ctrl1; + clk_enable(data->clk); + if (clk <= 0) + clk = 24000; /* safe setting, some chips do not work on + speeds >= 24kHz */ + clk_set_rate(data->clk, clk); + + clk = clk_get_rate(data->clk); + + + stmp3xxx_reset_block(HW_GPMI_CTRL0 + REGS_GPMI_BASE, 1); + + ctrl1 = __raw_readl(REGS_GPMI_BASE + HW_GPMI_CTRL1); + + /* write protection OFF */ + ctrl1 |= BM_GPMI_CTRL1_DEV_RESET; + + /* IRQ polarity */ + ctrl1 |= BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY; + + /* ...and ECC module */ + /*HW_GPMI_CTRL1_SET(bch_mode());*/ + + /* choose NAND mode (1 means ATA, 0 - NAND */ + ctrl1 &= ~BM_GPMI_CTRL1_GPMI_MODE; + + __raw_writel(ctrl1, REGS_GPMI_BASE + HW_GPMI_CTRL1); + + gpmi_set_timings(data, &gpmi_safe_timing); +} + +void queue_release(struct lba_data *data) +{ + stmp3xxx_setl(BM_GPMI_CTRL0_SFTRST, REGS_GPMI_BASE + HW_GPMI_CTRL0); + + clk_disable(data->clk); +} + + +/** + * gpmi_init_hw - initialize the hardware + * @pdev: pointer to platform device + * + * Initialize GPMI hardware and set default (safe) timings for NAND access. + * Returns error code or 0 on success + */ +static int gpmi_init_hw(struct platform_device *pdev, int request_pins) +{ + struct lba_data *data = platform_get_drvdata(pdev); + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + int err = 0; + + data->clk = clk_get(NULL, "gpmi"); + if (IS_ERR(data->clk)) { + err = PTR_ERR(data->clk); + dev_err(&pdev->dev, "cannot set failsafe clockrate\n"); + goto out; + } + if (request_pins) + gpd->pinmux(1); + + queue_plug(data); + +out: + return err; +} +/** + * gpmi_release_hw - free the hardware + * @pdev: pointer to platform device + * + * In opposite to gpmi_init_hw, release all acquired resources + */ +static void gpmi_release_hw(struct platform_device *pdev) +{ + struct gpmi_platform_data *gpd = + (struct gpmi_platform_data *)pdev->dev.platform_data; + struct lba_data *data = platform_get_drvdata(pdev); + + queue_release(data); + clk_put(data->clk); + gpd->pinmux(0); +} + + +/** + * gpmi_alloc_buffers - allocate DMA buffers for one chip + * + * @pdev: GPMI platform device + * @g: pointer to structure associated with NAND chip + * + * Allocate buffer using dma_alloc_coherent + */ +static int gpmi_alloc_buffers(struct platform_device *pdev, + struct gpmi_perchip_data *g) +{ + g->cmd_buffer = dma_alloc_coherent(&pdev->dev, + g->cmd_buffer_size, + &g->cmd_buffer_handle, GFP_DMA); + if (!g->cmd_buffer) + goto out1; + + g->write_buffer = dma_alloc_coherent(&pdev->dev, + g->write_buffer_size, + &g->write_buffer_handle, GFP_DMA); + if (!g->write_buffer) + goto out2; + + g->data_buffer = dma_alloc_coherent(&pdev->dev, + g->data_buffer_size, + &g->data_buffer_handle, GFP_DMA); + if (!g->data_buffer) + goto out3; + + g->oob_buffer = dma_alloc_coherent(&pdev->dev, + g->oob_buffer_size, + &g->oob_buffer_handle, GFP_DMA); + if (!g->oob_buffer) + goto out4; + + g->cmdtail_buffer = dma_alloc_coherent(&pdev->dev, + g->cmdtail_buffer_size, + &g->cmdtail_buffer_handle, GFP_DMA); + if (!g->oob_buffer) + goto out5; + + return 0; + +out5: + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); + +out4: + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +out3: + dma_free_coherent(&pdev->dev, g->write_buffer_size, + g->write_buffer, g->write_buffer_handle); +out2: + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); +out1: + return -ENOMEM; +} + +/** + * gpmi_free_buffers - free buffers allocated by gpmi_alloc_buffers + * + * @pdev: platform device + * @g: pointer to structure associated with NAND chip + * + * Deallocate buffers on exit + */ +static void gpmi_free_buffers(struct platform_device *pdev, + struct gpmi_perchip_data *g) +{ + dma_free_coherent(&pdev->dev, g->oob_buffer_size, + g->oob_buffer, g->oob_buffer_handle); + dma_free_coherent(&pdev->dev, g->write_buffer_size, + g->write_buffer, g->write_buffer_handle); + dma_free_coherent(&pdev->dev, g->cmd_buffer_size, + g->cmd_buffer, g->cmd_buffer_handle); + dma_free_coherent(&pdev->dev, g->cmdtail_buffer_size, + g->cmdtail_buffer, g->cmdtail_buffer_handle); + dma_free_coherent(&pdev->dev, g->data_buffer_size, + g->data_buffer, g->data_buffer_handle); +} + + +/****************************************************************************** + * Arch specific chain_* callbaks + ******************************************************************************/ + +/** + * chain_w4r - Initialize descriptor to perform W4R operation + * + * @chain: Descriptor to use + * @cs: CS for this operation + * + * Due to HW bug we have to put W4R into separate desc. + */ +static void chain_w4r(struct stmp3xxx_dma_descriptor *chain, int cs) +{ + chain->command->cmd = + (4 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDWAIT4READY | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + (BV_APBH_CHn_CMD_COMMAND__NO_DMA_XFER << BP_APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + (BV_GPMI_CTRL0_ADDRESS__NAND_DATA << BP_GPMI_CTRL0_ADDRESS) | + (cs << BP_GPMI_CTRL0_CS); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = 0; +} + +/** + * chain_cmd - Initialize descriptor to push CMD to the bus + * + * @chain: Descriptor to use + * @cmd_handle: dma_addr_t pointer that holds the command + * @lba_cmd: flags and lenghth of this command. + * @cs: CS for this operation + * + * CLE || CLE+ALE + */ +static void chain_cmd(struct stmp3xxx_dma_descriptor *chain, + dma_addr_t cmd_handle, + struct lba_cmd *lba_cmd, + int cs) +{ + /* output command */ + chain->command->cmd = + (lba_cmd->len << BP_APBH_CHn_CMD_XFER_COUNT) | + (3 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + (BV_APBH_CHn_CMD_COMMAND__DMA_READ << BP_APBH_CHn_CMD_COMMAND); + chain->command->cmd |= BM_APBH_CHn_CMD_CHAIN; + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__WRITE << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + (cs << BP_GPMI_CTRL0_CS) | + (BV_GPMI_CTRL0_ADDRESS__NAND_CLE << BP_GPMI_CTRL0_ADDRESS) | + (lba_cmd->len << BP_GPMI_CTRL0_XFER_COUNT); + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->buf_ptr = cmd_handle; + + if (lba_cmd->flag & FE_CMD_INC) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; +/*BUG if (lba_cmd->flag & FE_W4R) */ +/* chain->command->cmd |= BM_APBH_CHn_CMD_NANDWAIT4READY; */ +} + +/** + * chain_cmd - Initialize descriptor to read data from the bus + * + * @chain: Descriptor to use + * @data_handle: dma_addr_t pointer to buffer to store data + * @data_len: the size of the data buffer to read + * @cmd_handle: dma_addr_t pointer that holds the command + * @lba_cmd: flags and lenghth of this command. + * @cs: CS for this operation + */ +static void chain_read_data(struct stmp3xxx_dma_descriptor *chain, + dma_addr_t data_handle, + dma_addr_t data_len, + struct lba_cmd *lba_cmd, + int cs) +{ + chain->command->cmd = + (data_len << BP_APBH_CHn_CMD_XFER_COUNT) | + (4 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + (BV_APBH_CHn_CMD_COMMAND__DMA_WRITE << BP_APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__READ << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + (cs << BP_GPMI_CTRL0_CS) | + (BV_GPMI_CTRL0_ADDRESS__NAND_DATA << BP_GPMI_CTRL0_ADDRESS) | + (data_len << BP_GPMI_CTRL0_XFER_COUNT); + + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = data_handle; + + if (lba_cmd->flag & FE_CMD_INC) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; +/*BUG if (lba_cmd->flag & FE_W4R) */ +/* chain->command->cmd |= BM_APBH_CHn_CMD_NANDWAIT4READY; */ + +} + +/** + * chain_cmd - Initialize descriptor to read data from the bus + * + * @chain: Descriptor to use + * @data_handle: dma_addr_t pointer to buffer to read data from + * @data_len: the size of the data buffer to write + * @cmd_handle: dma_addr_t pointer that holds the command + * @lba_cmd: flags and lenghth of this command. + * @cs: CS for this operation + */ +static void chain_write_data(struct stmp3xxx_dma_descriptor *chain, + dma_addr_t data_handle, + int data_len, + struct lba_cmd *lba_cmd, + int cs) +{ + + chain->command->cmd = + (data_len << BP_APBH_CHn_CMD_XFER_COUNT) | + (4 << BP_APBH_CHn_CMD_CMDWORDS) | + BM_APBH_CHn_CMD_WAIT4ENDCMD | + BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN | + (BV_APBH_CHn_CMD_COMMAND__DMA_READ << BP_APBH_CHn_CMD_COMMAND); + chain->command->pio_words[0] = + (BV_GPMI_CTRL0_COMMAND_MODE__WRITE << BP_GPMI_CTRL0_COMMAND_MODE) | + BM_GPMI_CTRL0_WORD_LENGTH | + BM_GPMI_CTRL0_LOCK_CS | + (cs << BP_GPMI_CTRL0_CS) | + (BV_GPMI_CTRL0_ADDRESS__NAND_DATA << BP_GPMI_CTRL0_ADDRESS) | + (data_len << BP_GPMI_CTRL0_XFER_COUNT); + + chain->command->pio_words[1] = 0; + chain->command->pio_words[2] = 0; + chain->command->pio_words[3] = 0; + chain->command->buf_ptr = data_handle; + + if (lba_cmd->flag & FE_CMD_INC) + chain->command->pio_words[0] |= BM_GPMI_CTRL0_ADDRESS_INCREMENT; +/*BUG if (lba_cmd->flag & FE_W4R) */ +/* chain->command->cmd |= BM_APBH_CHn_CMD_NANDWAIT4READY; */ + +} + + +/****************************************************************************** + * Interface to arch independent part + ******************************************************************************/ +/** + * queue_cmd - Setup a chain of descriptors + * + * @priv: private data passed + * @cmd_buf: pointer to command buffer (to be removed) + * @cmd_handle: dma_addr_t pointer that holds the command + * @cmd_len: the size of the command buffer (to be removed) + * @data_handle: dma_addr_t pointer to a data buffer + * @data_len: the size of the data buffer + * @cmd_flags: commands flags + */ +int queue_cmd(void *priv, + uint8_t *cmd_buf, dma_addr_t cmd_handle, int cmd_len, + dma_addr_t data, int data_len, + struct lba_cmd *cmd_flags) +{ + + struct gpmi_perchip_data *g = priv; + unsigned long flags; + int idx; + int ret = 0; + struct stmp3xxx_dma_descriptor *chain ; + int i; + + if (!g || !(cmd_buf || cmd_handle)) + BUG(); + + spin_lock_irqsave(&g->lock, flags); + + /* Keep it for debug purpose */ + chain = g->d; + for (i = g->d_tail; i < GPMI_DMA_MAX_CHAIN; i++) { + chain[i].command->cmd = 0; + chain[i].command->buf_ptr = 0; + } + /* End */ + + if (!cmd_handle) { + if (!cmd_buf) + BUG(); + memcpy(g->cmd_buffer, cmd_buf, cmd_len); + cmd_handle = g->cmd_buffer_handle; + } + + idx = g->d_tail; + chain = &g->d[idx]; + + do { + if (!cmd_flags) + BUG(); + + if (cmd_flags->flag & FE_W4R) { + /* there seems to be HW BUG with W4R flag. + * IRQ controller hangs forever when it's combined + * with real operation. + */ + chain_w4r(chain, g->cs); + chain++; idx++; + } + + + switch (cmd_flags->flag & F_MASK) { + + case F_CMD: + chain_cmd(chain, cmd_handle, cmd_flags, g->cs); + break; + case F_DATA_READ: + chain_read_data(chain, data, data_len, + cmd_flags, g->cs); + break; + case F_DATA_WRITE: + chain_write_data(chain, data, data_len, + cmd_flags, g->cs); + break; + default:{ + if (cmd_flags->flag & FE_END) + goto out; + else{ + printk(KERN_ERR "uknown cmd\n"); + BUG(); + } + } + } + + + chain++; idx++; + cmd_handle += cmd_flags->len; + + if (idx >= GPMI_DMA_MAX_CHAIN) { + printk(KERN_ERR "to many chains; idx is 0x%x\n", idx); + BUG(); + } + + } while (!((cmd_flags++)->flag & FE_END)); + +out: + if (idx < GPMI_DMA_MAX_CHAIN) { + ret = idx; + g->d_tail = idx; + } + spin_unlock_irqrestore(g->lock, flags); + + return ret; + +} + +dma_addr_t queue_get_cmd_handle(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->cmd_buffer_handle; +} + +uint8_t *queue_get_cmd_ptr(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->cmd_buffer; +} + +dma_addr_t queue_get_data_handle(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->data_buffer_handle; +} + +uint8_t *queue_get_data_ptr(void *priv) +{ + struct gpmi_perchip_data *g = priv; + return g->data_buffer; +} + + +/** + * queue_run - run the chain + * + * @priv: private data. + */ +int queue_run(void *priv) +{ + struct gpmi_perchip_data *g = priv; + + if (!g->d_tail) + return 0; + stmp3xxx_dma_reset_channel(g->dma_ch); + stmp3xxx_dma_clear_interrupt(g->dma_ch); + stmp3xxx_dma_enable_interrupt(g->dma_ch); + + g->d[g->d_tail-1].command->cmd &= ~(BM_APBH_CHn_CMD_NANDLOCK | + BM_APBH_CHn_CMD_CHAIN); + g->d[g->d_tail-1].command->cmd |= BM_APBH_CHn_CMD_IRQONCMPLT ; + + g->d[g->d_tail-1].command->pio_words[0] &= ~BM_GPMI_CTRL0_LOCK_CS; + +#ifdef DEBUG + /*stmp37cc_dma_print_chain(&g->chain);*/ +#endif + + init_completion(&g->done); + stmp3xxx_dma_go(g->dma_ch, g->d, 1); + wait_for_completion(&g->done); + + g->d_tail = 0; + + return 0; + +} + +/****************************************************************************** + * Platform specific part / chard driver and misc functions + ******************************************************************************/ + + + +static int __init lba_probe(struct platform_device *pdev) +{ + struct lba_data *data; + struct resource *r; + struct gpmi_perchip_data *g; + int err; + + /* Allocate memory for the device structure (and zero it) */ + data = kzalloc(sizeof(*data) + sizeof(struct gpmi_perchip_data), + GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "failed to allocate gpmi_nand_data\n"); + err = -ENOMEM; + goto out1; + } + g_data = data; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get resource\n"); + err = -ENXIO; + goto out2; + } + data->io_base = ioremap(r->start, r->end - r->start + 1); + if (!data->io_base) { + dev_err(&pdev->dev, "ioremap failed\n"); + err = -EIO; + goto out2; + } + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!r) { + err = -EIO; + dev_err(&pdev->dev, "can't get IRQ resource\n"); + goto out3; + } + data->irq = r->start; + + platform_set_drvdata(pdev, data); + err = gpmi_init_hw(pdev, 1); + if (err) + goto out3; + + + err = request_irq(data->irq, + gpmi_irq, 0, dev_name(&pdev->dev), data); + if (err) { + dev_err(&pdev->dev, "can't request GPMI IRQ\n"); + goto out4; + } + + g = data->nand; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) { + dev_err(&pdev->dev, "can't get DMA resource\n"); + goto out_res; + } + g->cs = 0; + g->dma_ch = r->start; + + err = stmp3xxx_dma_request(g->dma_ch, NULL, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "can't request DMA channel 0x%x\n", + g->dma_ch); + goto out_res; + } + + err = stmp3xxx_dma_make_chain(g->dma_ch, &g->chain, + g->d, ARRAY_SIZE(g->d)); + if (err) { + dev_err(&pdev->dev, "can't setup DMA chain\n"); + stmp3xxx_dma_release(g->dma_ch); + goto out_res; + } + + g->cmd_buffer_size = GPMI_CMD_BUF_SZ; + g->cmdtail_buffer_size = GPMI_CMD_BUF_SZ; + g->write_buffer_size = GPMI_WRITE_BUF_SZ; + g->data_buffer_size = GPMI_DATA_BUF_SZ; + g->oob_buffer_size = GPMI_OOB_BUF_SZ; + + err = gpmi_alloc_buffers(pdev, g); + if (err) { + dev_err(&pdev->dev, "can't setup buffers\n"); + stmp3xxx_dma_free_chain(&g->chain); + stmp3xxx_dma_release(g->dma_ch); + goto out_res; + } + + g->dev = pdev; + g->chip.priv = g; + g->index = 0; + g->timing = gpmi_safe_timing; + + g->cmd_buffer_sz = + g->write_buffer_sz = + g->data_buffer_sz = + 0; + g->valid = !0; /* mark the data as valid */ + + + lba_core_init(data); + + return 0; + +out_res: + free_irq(data->irq, data); +out4: + gpmi_release_hw(pdev); +out3: + platform_set_drvdata(pdev, NULL); + iounmap(data->io_base); +out2: + kfree(data); +out1: + return err; +} + +static int gpmi_suspend(struct platform_device *pdev, pm_message_t pm) +{ + struct lba_data *data = platform_get_drvdata(pdev); + int err; + + printk(KERN_INFO "%s: %d\n", __func__, __LINE__); + err = lba_core_suspend(pdev, data); + if (!err) + gpmi_release_hw(pdev); + + return err; +} + +static int gpmi_resume(struct platform_device *pdev) +{ + struct lba_data *data = platform_get_drvdata(pdev); + int r; + + printk(KERN_INFO "%s: %d\n", __func__, __LINE__); + r = gpmi_init_hw(pdev, 1); + lba_core_resume(pdev, data); + return r; +} + +/** + * gpmi_nand_remove - remove a GPMI device + * + */ +static int __devexit lba_remove(struct platform_device *pdev) +{ + struct lba_data *data = platform_get_drvdata(pdev); + int i; + + lba_core_remove(data); + gpmi_release_hw(pdev); + free_irq(data->irq, data); + + for (i = 0; i < max_chips; i++) { + if (!data->nand[i].valid) + continue; + gpmi_free_buffers(pdev, &data->nand[i]); + stmp3xxx_dma_free_chain(&data->nand[i].chain); + stmp3xxx_dma_release(data->nand[i].dma_ch); + } + iounmap(data->io_base); + kfree(data); + + return 0; +} + +static struct platform_driver lba_driver = { + .probe = lba_probe, + .remove = __devexit_p(lba_remove), + .driver = { + .name = "gpmi", + .owner = THIS_MODULE, + }, + .suspend = gpmi_suspend, + .resume = gpmi_resume, + +}; + + +static int __init lba_init(void) +{ + return platform_driver_register(&lba_driver); +} + +static void lba_exit(void) +{ + platform_driver_unregister(&lba_driver); +} + +module_init(lba_init); +module_exit(lba_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/lba/gpmi.h b/drivers/mtd/nand/lba/gpmi.h new file mode 100644 index 000000000000..a647c948040f --- /dev/null +++ b/drivers/mtd/nand/lba/gpmi.h @@ -0,0 +1,103 @@ +/* + * Freescale STMP37XX/STMP378X GPMI (General-Purpose-Media-Interface) + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __DRIVERS_GPMI_H +#define __DRIVERS_GPMI_H + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> + +#include <mach/gpmi.h> +#include <mach/regs-gpmi.h> +#include <mach/regs-apbh.h> +#ifdef CONFIG_MTD_NAND_GPMI_BCH +#include <mach/regs-bch.h> +#endif + + +struct gpmi_nand_timing { + u8 data_setup; + u8 data_hold; + u8 address_setup; + u8 dsample_time; +}; + +#define GPMI_DMA_MAX_CHAIN 20 /* max DMA commands in chain */ + +#define GPMI_CMD_BUF_SZ 10 +#define GPMI_DATA_BUF_SZ 4096 +#define GPMI_WRITE_BUF_SZ 4096 +#define GPMI_OOB_BUF_SZ 218 + + +struct gpmi_perchip_data { + int valid; + struct nand_chip chip; + struct platform_device *dev; + int index; + + spinlock_t lock; /* protect chain operations */ + struct stmp37xx_circ_dma_chain chain; + struct stmp3xxx_dma_descriptor d[GPMI_DMA_MAX_CHAIN]; + int d_tail; + + struct completion done; + + u8 *cmd_buffer; + dma_addr_t cmd_buffer_handle; + int cmd_buffer_size, cmd_buffer_sz; + + u8 *cmdtail_buffer; + dma_addr_t cmdtail_buffer_handle; + int cmdtail_buffer_size, cmdtail_buffer_sz; + + u8 *write_buffer; + dma_addr_t write_buffer_handle; + int write_buffer_size, write_buffer_sz; + + u8 *data_buffer; + dma_addr_t data_buffer_handle; + u8 *data_buffer_cptr; + int data_buffer_size, data_buffer_sz, bytes2read; + + u8 *oob_buffer; + dma_addr_t oob_buffer_handle; + int oob_buffer_size; + + int cs; + unsigned dma_ch; + + int ecc_oob_bytes, oob_free; + + struct gpmi_nand_timing timing; + + void *p2w, *oob2w, *p2r, *oob2r; + size_t p2w_size, oob2w_size, p2r_size, oob2r_size; + dma_addr_t p2w_dma, oob2w_dma, p2r_dma, oob2r_dma; + unsigned read_memcpy:1, write_memcpy:1, + read_oob_memcpy:1, write_oob_memcpy:1; +}; + + +extern struct gpmi_nand_timing gpmi_safe_timing; + + +#endif diff --git a/drivers/mtd/nand/lba/lba-blk.c b/drivers/mtd/nand/lba/lba-blk.c new file mode 100644 index 000000000000..228aa9d27760 --- /dev/null +++ b/drivers/mtd/nand/lba/lba-blk.c @@ -0,0 +1,345 @@ +/* + * Freescale STMP37XX/STMP378X LBA/block driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/kthread.h> +#include <linux/timer.h> +#include <linux/types.h> /* size_t */ +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/hdreg.h> /* HDIO_GETGEO */ +#include <linux/kdev_t.h> +#include <linux/vmalloc.h> +#include <linux/genhd.h> +#include <linux/blkdev.h> +#include <linux/buffer_head.h> /* invalidate_bdev */ +#include <linux/bio.h> +#include <linux/dma-mapping.h> +#include <linux/hdreg.h> +#include <linux/blkdev.h> +#include "lba.h" + +static int lba_major; + +#define LBA_NAME "lba" + +#if 0 +#define TAG() printk(KERNE_ERR "%s: %d\n", __func__, __LINE__) +#else +#define TAG() +#endif + +/* + * The internal representation of our device. + */ +struct lba_blk_dev { + int size; /* Device size in sectors */ + spinlock_t lock; /* For mutual exclusion */ + int users; + struct request_queue *queue; /* The device request queue */ + struct gendisk *gd; /* The gendisk structure */ + struct lba_data *data; /* pointer from lba core */ + + struct task_struct *thread; + struct bio *bio_head; + struct bio *bio_tail; + wait_queue_head_t wait_q; + struct semaphore busy; + +}; + +static struct lba_blk_dev *g_lba_blk; + +static void blk_add_bio(struct lba_blk_dev *dev, struct bio *bio); + + +/* + * Transfer a single BIO. + */ +static int lba_blk_xfer_bio(struct lba_blk_dev *dev, struct bio *bio) +{ + int i; + struct bio_vec *bvec; + sector_t sector = bio->bi_sector; + enum dma_data_direction dir; + int status = 0; + int (*lba_xfer)(void *priv, + unsigned int sector, + unsigned int count, + void *buffer, + dma_addr_t handle); + + if (bio_data_dir(bio) == WRITE) { + lba_xfer = lba_write_sectors; + dir = DMA_TO_DEVICE; + } else { + lba_xfer = lba_read_sectors; + dir = DMA_FROM_DEVICE; + } + + /* Fixme: merge segments */ + bio_for_each_segment(bvec, bio, i) { + void *buffer = page_address(bvec->bv_page); + dma_addr_t handle ; + if (!buffer) + BUG(); + buffer += bvec->bv_offset; + handle = dma_map_single(&dev->data->nand->dev->dev, + buffer, + bvec->bv_len, + dir); + status = lba_xfer(dev->data->nand, sector, + bvec->bv_len >> 9, + buffer, + handle); + + dma_unmap_single(&dev->data->nand->dev->dev, + handle, + bvec->bv_len, + dir); + if (status) + break; + + sector += bio_cur_bytes(bio) >> 9; + } + + return status; +} + + +/* + * The direct make request version. + */ +static int lba_make_request(struct request_queue *q, struct bio *bio) +{ + struct lba_blk_dev *dev = q->queuedata; + + blk_add_bio(dev, bio); + return 0; +} + +/* + * Open and close. + */ + +static int lba_blk_open(struct block_device *bdev, fmode_t mode) +{ + struct lba_blk_dev *dev = bdev->bd_disk->private_data; + + TAG(); + + spin_lock_irq(&dev->lock); + dev->users++; + spin_unlock_irq(&dev->lock); + return 0; +} + + +static int lba_blk_release(struct gendisk *gd, fmode_t mode) +{ + struct lba_blk_dev *dev = gd->private_data; + + spin_lock(&dev->lock); + dev->users--; + spin_unlock(&dev->lock); + + return 0; +} + +static int lba_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + /* + * get geometry: we have to fake one... trim the size to a + * multiple of 2048 (1M): tell we have 32 sectors, 64 heads, + * whatever cylinders. + */ + geo->heads = 1 << 6; + geo->sectors = 1 << 5; + geo->cylinders = get_capacity(bdev->bd_disk) >> 11; + return 0; +} + +/* + * Add bio to back of pending list + */ +static void blk_add_bio(struct lba_blk_dev *dev, struct bio *bio) +{ + unsigned long flags; + spin_lock_irqsave(&dev->lock, flags); + if (dev->bio_tail) { + dev->bio_tail->bi_next = bio; + dev->bio_tail = bio; + } else + dev->bio_head = dev->bio_tail = bio; + wake_up(&dev->wait_q); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* + * Grab first pending buffer + */ +static struct bio *blk_get_bio(struct lba_blk_dev *dev) +{ + struct bio *bio; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + bio = dev->bio_head; + if (bio) { + if (bio == dev->bio_tail) { + dev->bio_tail = NULL; + dev->bio_head = NULL; + } + dev->bio_head = bio->bi_next; + bio->bi_next = NULL; + } + spin_unlock_irqrestore(&dev->lock, flags); + + return bio; +} + +static int lba_thread(void *data) +{ + struct lba_blk_dev *dev = data; + struct bio *bio; + int status; + + set_user_nice(current, -20); + + while (!kthread_should_stop() || dev->bio_head) { + + wait_event_interruptible(dev->wait_q, + dev->bio_head || kthread_should_stop()); + + if (!dev->bio_head) + continue; + + if (lba_core_lock_mode(dev->data, LBA_MODE_MDP)) + continue; + + bio = blk_get_bio(dev); + status = lba_blk_xfer_bio(dev, bio); + bio_endio(bio, status); + + lba_core_unlock_mode(dev->data); + } + + return 0; +} + + + +/* + * The device operations structure. + */ +static struct block_device_operations lba_blk_ops = { + .owner = THIS_MODULE, + .open = lba_blk_open, + .release = lba_blk_release, + .getgeo = lba_getgeo, +}; + + +int lba_blk_init(struct lba_data *data) +{ + + struct lba_blk_dev *dev; + int err; + if (!data) + BUG(); + + printk(KERN_INFO "LBA block driver v0.1\n"); + lba_major = LBA_MAJOR; + dev = g_lba_blk = kzalloc(sizeof(struct lba_blk_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->data = data; + register_blkdev(lba_major, "lba"); + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->wait_q); + sema_init(&dev->busy, 1); + + dev->queue = blk_alloc_queue(GFP_KERNEL); + if (!dev->queue) + goto out2; + blk_queue_make_request(dev->queue, lba_make_request); + /*dev->queue->unplug_fn = lba_unplug_device;*/ + blk_queue_logical_block_size(dev->queue, 512); + + dev->queue->queuedata = dev; + dev->gd = alloc_disk(32); + if (!dev->gd) { + printk(KERN_ERR "failed to alloc disk\n"); + goto out3; + } + dev->size = data->mdp_size ; + printk(KERN_INFO "%s: set capacity of the device to 0x%x\n", + __func__, dev->size); + dev->gd->major = lba_major; + dev->gd->first_minor = 0; + dev->gd->fops = &lba_blk_ops; + dev->gd->queue = dev->queue; + dev->gd->private_data = dev; + snprintf(dev->gd->disk_name, 8, LBA_NAME); + set_capacity(dev->gd, dev->size); + + dev->thread = kthread_create(lba_thread, dev, "lba-%d", 1); + if (IS_ERR(dev->thread)) { + err = PTR_ERR(dev->thread); + goto out3; + } + wake_up_process(dev->thread); + + add_disk(dev->gd); + + + TAG(); + + return 0; +out3: +out2: + unregister_blkdev(lba_major, "lba"); + return -ENOMEM; +} + +int lba_blk_remove(struct lba_data *data) +{ + + struct lba_blk_dev *dev = g_lba_blk; + + del_gendisk(dev->gd); + kthread_stop(dev->thread); + blk_cleanup_queue(dev->queue); + put_disk(dev->gd); + + unregister_blkdev(lba_major, LBA_NAME); + kfree(dev); + return 0; +} + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(254); diff --git a/drivers/mtd/nand/lba/lba-core.c b/drivers/mtd/nand/lba/lba-core.c new file mode 100644 index 000000000000..cdf28ca42b2a --- /dev/null +++ b/drivers/mtd/nand/lba/lba-core.c @@ -0,0 +1,619 @@ +/* + * Freescale STMP37XX/STMP378X LBA/core driver + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/dma-mapping.h> +#include <linux/ctype.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <mach/stmp3xxx.h> +#include <mach/dma.h> +#include "gpmi.h" +#include "lba.h" + +#define LBA_SELFPM_TIMEOUT 2000 /* msecs */ +dma_addr_t g_cmd_handle; +dma_addr_t g_data_handle; +uint8_t *g_data_buffer; +uint8_t *g_cmd_buffer; + +uint8_t lba_get_status1(void *priv) +{ + uint8_t cmd_buf[] = { 0x70 } ; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + *g_data_buffer = 0; + queue_cmd(priv, cmd_buf, 0, 1, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} + + +int lba_wait_for_ready(void *priv) +{ + int stat; + unsigned long j_start = jiffies; + + stat = lba_get_status1(priv); + if ((stat & 0x60) != 0x60) { + while (((stat & 0x60) != 0x60) && + (jiffies - j_start < msecs_to_jiffies(2000))) { + schedule(); + stat = lba_get_status1(priv); + } + } + if (stat != 0x60) + return stat; + + return 0; +} + +int lba_write_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle) +{ + uint8_t cmd_buf[] = { + 0x80, + count & 0xff, (count >> 8) & 0xff, /* Count */ + (sector & 0xff), (sector >> 8) & 0xff, /* Address */ + (sector >> 16) & 0xff, (sector >> 24) & 0xff, /* Addres */ + /* Data goes here */ + 0x10 + }; + + struct lba_cmd flags_t1[] = { /* Transmition mode 1/A */ + {7 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_WRITE}, + {1 , F_CMD | FE_END} + }; + + if (count > 8) + return -EINVAL; + + if (lba_wait_for_ready(priv)) + return -EIO; + + while (count) { + int cnt = (count < 8) ? count : 8; + int data_len = cnt * 512; + + queue_cmd(priv, cmd_buf, 0, 8, + handle, data_len, flags_t1); + + handle += data_len; + count -= cnt; + + } + + queue_run(priv); + + return count; + +} + +int lba_read_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle) +{ + + int data_len; + int cnt; + uint8_t cmd_buf[] = { + 0x00, + count & 0xff, (count >> 8) & 0xff, /* Count */ + (sector & 0xff), (sector >> 8) & 0xff, /* Addr */ + (sector >> 16) & 0xff, (sector >> 24) & 0xff, /* Addr */ + 0x30 + /* Data goes here <data> */ + + }; + + struct lba_cmd flags_r3[] = { /* Read mode 3/A */ + {7 , F_CMD | FE_CMD_INC | FE_W4R}, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END }, + }; + struct lba_cmd flags_r3c[] = { /* Read mode 3/A */ + {0 , F_DATA_READ | FE_W4R | FE_END }, + }; + struct lba_cmd *flags = flags_r3; + int flags_len = 8; + + if (count > 8) + return -EINVAL; + + if (lba_wait_for_ready(priv)) + return -EIO; + + while (count) { + cnt = (count < 8) ? count : 8; + data_len = cnt * 512; + queue_cmd(priv, cmd_buf, 0, flags_len, handle, data_len, flags); + handle += data_len; + count -= cnt; + flags = flags_r3c; + flags_len = 0; + } + + queue_run(priv); + + return count; + +} + + +uint8_t lba_get_id1(void *priv, uint8_t *ret_buffer) +{ + uint8_t cmd_buf[] = { 0x90 , 0x00, /* Data read 5bytes*/ }; + struct lba_cmd lba_flags[] = { + {2 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + + queue_cmd(priv, cmd_buf, 0, 2, g_data_handle, 5, lba_flags); + queue_run(priv); + memcpy(ret_buffer, g_data_buffer, 5); + + return 0; +} + +uint8_t lba_get_id2(void *priv, uint8_t *ret_buffer) +{ + uint8_t cmd_buf[] = { 0x92 , 0x00, /* Data read 5bytes*/ }; + struct lba_cmd lba_flags[] = { + {2 , F_CMD | FE_CMD_INC | FE_W4R}, + {0, F_DATA_READ | FE_END}, + }; + + queue_cmd(priv, cmd_buf, 0, 2, g_data_handle, 5, lba_flags); + queue_run(priv); + memcpy(ret_buffer, g_data_buffer, 5); + return 0; +} + +uint8_t lba_get_status2(void *priv) +{ + uint8_t cmd_buf[] = { 0x71 }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_CMD_INC | FE_W4R}, + {0 , F_DATA_READ | FE_END}, + }; + *g_data_buffer = 0; + queue_cmd(priv, cmd_buf, 0, 1, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} + +static uint8_t lba_parse_status2(void *priv) +{ + uint8_t stat; + + stat = lba_get_status2(priv); + printk(KERN_INFO "Status2:|"); + if (stat & 0x40) + printk(" C.PAR.ERR |"); /* no KERN_ here */ + if (stat & 0x20) + printk(" NO spare |"); + if (stat & 0x10) + printk(" ADDR OoRange |"); + if (stat & 0x8) + printk(" high speed |"); + if ((stat & 0x6) == 6) + printk(" MDP |"); + if ((stat & 0x6) == 4) + printk(" VFP |"); + if ((stat & 0x6) == 2) + printk(" PNP |"); + if (stat & 1) + printk(" PSW |"); + + printk("\n"); + return 0; +} + + +int lba_2mdp(void *priv) +{ + uint8_t cmd_buf[] = { 0xFC }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 1, 0, 0, lba_flags); + queue_run(priv); + return 0; +} + +void _lba_misc_cmd_set(void *priv, uint8_t *cmd_buf) +{ + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD | FE_END }, + }; + + queue_cmd(priv, cmd_buf, 0, 7, 0, 0, lba_flags); + queue_run(priv); +} + +uint8_t _lba_misc_cmd_get(void *priv, uint8_t *cmd_buf) +{ + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 1, lba_flags); + queue_run(priv); + return *g_data_buffer; +} +void lba_mdp2vfp(void *priv, uint8_t pass[2]) +{ + uint8_t cmd_buf[] = { 0x0, 0xbe, pass[0], pass[1], 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_bcm2vfp(void *priv, uint8_t pass[2]) +{ + lba_mdp2vfp(priv, pass); +} + +void lba_powersave_enable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xba, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} +void lba_powersave_disable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbb, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_highspeed_enable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbc, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_highspeed_disable(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xbd, 0, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_prot1_set(void *priv, uint8_t mode) +{ + uint8_t cmd_buf[] = { 0x0, 0xa2, mode, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +void lba_prot2_set(void *priv, uint8_t mode) +{ + uint8_t cmd_buf[] = { 0x0, 0xa3, mode, 0, 0, 0, 0x57 }; + _lba_misc_cmd_set(priv, cmd_buf); +} + +uint8_t lba_prot1_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb2, 0, 0, 0, 0, 0x57 }; + return _lba_misc_cmd_get(priv, cmd_buf); +} + +uint8_t lba_prot2_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb3, 0, 0, 0, 0, 0x57 }; + return _lba_misc_cmd_get(priv, cmd_buf); +} + +uint64_t lba_mdp_size_get(void *priv) +{ + uint8_t cmd_buf[] = { 0x0, 0xb0, 0, 0, 0, 0, 0x57 }; + struct lba_cmd lba_flags[] = { + {6 , F_CMD | FE_CMD_INC | FE_W4R }, + {1 , F_CMD }, + {0 , F_DATA_READ | FE_W4R | FE_END} + }; + + memset((void *)g_data_buffer, 0, 8); + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); + return le64_to_cpu(*(long long *)g_data_buffer); +} + +void lba_cache_flush(void *priv) +{ + uint8_t cmd_buf[] = { 0xF9 }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R }, + {0 , FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); +} + +void lba_reboot(void *priv) +{ + uint8_t cmd_buf[] = { 0xFD }; + struct lba_cmd lba_flags[] = { + {1 , F_CMD | FE_W4R }, + {0 , FE_W4R | FE_END} + }; + + queue_cmd(priv, cmd_buf, 0, 7, g_data_handle, 5, lba_flags); + queue_run(priv); +} + +void lba_def_state(void *priv) +{ + lba_wait_for_ready(priv); + lba_reboot(priv); + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + lba_wait_for_ready(priv); + lba_2mdp(priv); + + lba_wait_for_ready(priv); + lba_prot1_set(priv, LBA_T_SIZE8); /* 512 * 8 */ + + lba_wait_for_ready(priv); +/* Type C read; Type A write; */ + lba_prot2_set(priv, LBA_P_WRITE_A | LBA_P_READ_C); +} + +/* + * Should be called with mode locked + */ +void lba_core_setvfp_passwd(struct lba_data *data, uint8_t pass[2]) +{ + memcpy(data->pass, pass, 2); +} + +int lba_core_lock_mode(struct lba_data *data, int mode) +{ + void *priv = &data->nand; + + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + /* + * MDP and VFP are the only supported + * modes for now. + */ + if ((mode != LBA_MODE_MDP) && + (mode != LBA_MODE_VFP)) { + up(&data->mode_lock); + return -EINVAL; + } + + while ((data->mode & LBA_MODE_MASK) == LBA_MODE_SUSP) { + up(&data->mode_lock); + + if (wait_event_interruptible( + data->suspend_q, + (data->mode & LBA_MODE_MASK) != LBA_MODE_SUSP)) + return -EAGAIN; + + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + + data->last_access = jiffies; + } + + if (data->mode & LBA_MODE_SELFPM) { + queue_plug(data); + data->mode &= ~LBA_MODE_SELFPM; + } + + if (mode == data->mode) + return 0; + + /* + * mode = VFP || MDP only + * Revisit when more modes are added + */ + switch (data->mode) { + case LBA_MODE_RST: + case LBA_MODE_PNR: + case LBA_MODE_BCM: + lba_def_state(priv); + if (mode == LBA_MODE_MDP) { + data->mode = LBA_MODE_MDP; + break; + } + /*no break -> fall down to set VFP mode*/ + case LBA_MODE_MDP: + lba_wait_for_ready(priv); + lba_mdp2vfp(priv, data->pass); + data->mode = LBA_MODE_VFP; + break; + case LBA_MODE_VFP: + lba_wait_for_ready(priv); + lba_2mdp(priv); + data->mode = LBA_MODE_MDP; + break; + default: + up(&data->mode_lock); + return -EINVAL; + } + + return 0; +} + +int lba_core_unlock_mode(struct lba_data *data) +{ + data->last_access = jiffies; + up(&data->mode_lock); + wake_up(&data->selfpm_q); + return 0; +} + +static int selfpm_timeout_expired(struct lba_data *data) +{ + return jiffies_to_msecs(jiffies - data->last_access) > 2000; +} + +static int lba_selfpm_thread(void *d) +{ + struct lba_data *data = d; + + set_user_nice(current, -5); + + while (!kthread_should_stop()) { + + if (wait_event_interruptible(data->selfpm_q, + kthread_should_stop() || + !(data->mode & LBA_MODE_SELFPM))) + continue; + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(LBA_SELFPM_TIMEOUT)); + + if (down_trylock(&data->mode_lock)) + continue; + + if (!selfpm_timeout_expired(data)) { + up(&data->mode_lock); + continue; + } + data->mode |= LBA_MODE_SELFPM; + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + queue_release(data); + up(&data->mode_lock); + + } + + return 0; +} + +int lba_core_init(struct lba_data *data) +{ + uint8_t id_buf[5]; + uint8_t capacity; + uint8_t id1_template[5] = {0x98, 0xDC, 0x00, 0x15, 0x00}; + uint8_t id2_template[5] = {0x98, 0x21, 0x00, 0x55, 0xAA}; + void *priv = (void *)data->nand; + + + g_data = data; + g_cmd_handle = queue_get_cmd_handle(priv); + g_data_handle = queue_get_data_handle(priv); + g_data_buffer = queue_get_data_ptr(priv); + g_cmd_buffer = queue_get_cmd_ptr(priv); + + spin_lock_init(&data->lock); + sema_init(&data->mode_lock, 1); + init_waitqueue_head(&data->suspend_q); + init_waitqueue_head(&data->selfpm_q); + + + lba_get_id1(data->nand, id_buf); + if (!memcmp(id_buf, id1_template, 5)) + printk(KERN_INFO + "LBA: Found LBA/SLC NAND emulated ID\n"); + else + return -ENODEV; + + lba_get_id2(data->nand, id_buf); + capacity = id_buf[2]; + id_buf[2] = 0; + + if (memcmp(id_buf, id2_template, 5)) { + printk(KERN_INFO + "LBA: Uknown LBA device\n"); + return -ENODEV; + } + printk(KERN_INFO + "LBA: Found %dGbytes LBA NAND device\n", + 1 << capacity); + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + lba_def_state(priv); + data->mode = LBA_MODE_MDP; + + g_data->pnp_size = 0xff; + g_data->vfp_size = 16384; + + lba_wait_for_ready(priv); + g_data->mdp_size = lba_mdp_size_get(priv); + + lba_wait_for_ready(priv); + /*lba_powersave_enable(priv);*/ + /*lba_highspeed_enable(priv);*/ + + lba_wait_for_ready(priv); + lba_parse_status2(priv); + + data->thread = kthread_create(lba_selfpm_thread, + data, "lba-selfpm-%d", 1); + if (IS_ERR(data->thread)) + return PTR_ERR(data->thread); + + lba_blk_init(g_data); + + wake_up_process(data->thread); + return 0; + +}; + +int lba_core_remove(struct lba_data *data) +{ + kthread_stop(data->thread); + lba_blk_remove(data); + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + return 0; +} + +int lba_core_suspend(struct platform_device *pdev, struct lba_data *data) +{ + BUG_ON((data->mode & 0xffff) == LBA_MODE_SUSP); + if (down_interruptible(&data->mode_lock)) + return -EAGAIN; + if (data->mode & LBA_MODE_SELFPM) + queue_plug(data); + + data->mode = LBA_MODE_SUSP | LBA_MODE_SELFPM; + up(&data->mode_lock); + lba_wait_for_ready((void *)data->nand); + lba_cache_flush((void *)data->nand); + return 0; +} + +int lba_core_resume(struct platform_device *pdev, struct lba_data *data) +{ + BUG_ON((data->mode & 0xffff) != LBA_MODE_SUSP); + lba_def_state((void *)data->nand); + data->last_access = jiffies; + data->mode = LBA_MODE_MDP; + wake_up(&data->suspend_q); + return 0; +} diff --git a/drivers/mtd/nand/lba/lba.h b/drivers/mtd/nand/lba/lba.h new file mode 100644 index 000000000000..3466f96881eb --- /dev/null +++ b/drivers/mtd/nand/lba/lba.h @@ -0,0 +1,140 @@ +/* + * Freescale STMP37XX/STMP378X LBA interface + * + * Author: Dmitrij Frasenyak <sed@embeddedalley.com> + * + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __INCLUDE_LBA_H__ +#define __INCLUDE_LBA_H__ + + +#include <linux/spinlock.h> +#include <linux/kthread.h> +#include "gpmi.h" + +struct lba_cmd { + uint8_t len; +#define F_MASK 0x0f +#define F_ALE 0x01 +#define F_CMD 0x02 +#define F_DATA_READ 0x04 +#define F_DATA_WRITE 0x08 + +#define FE_W4R 0x10 +#define FE_CMD_INC 0x20 +#define FE_END 0x40 + + uint8_t flag; +}; + +#define LBA_P_READ_A 0 +#define LBA_P_READ_B 2 +#define LBA_P_READ_C 3 +#define LBA_P_WRITE_A 0 +#define LBA_P_WRITE_B 4 +#define LBA_T_SIZE1 1 +#define LBA_T_SIZE4 2 +#define LBA_T_SIZE8 4 +#define LBA_T_CRC (1 << 6) +#define LBA_T_ECC_CHECK (2 << 6) +#define LBA_T_ECC_CORRECT (3 << 6) + +struct lba_data { + void __iomem *io_base; + struct clk *clk; + int irq; + + spinlock_t lock; + int use_count; + int mode; + struct semaphore mode_lock; +#define LBA_MODE_MASK 0x0000ffff +#define LBA_FLAG_MASK 0xffff0000 +#define LBA_MODE_RST 0 +#define LBA_MODE_PNR 1 +#define LBA_MODE_BCM 2 +#define LBA_MODE_MDP 3 +#define LBA_MODE_VFP 4 +#define LBA_MODE_SUSP 5 +#define LBA_MODE_SELFPM 0x80000000 + wait_queue_head_t suspend_q; + wait_queue_head_t selfpm_q; + struct task_struct *thread; + long long last_access; + /* PNR specific */ + /* BCM specific */ + /* VFP specific */ + uint8_t pass[2]; + + /* Size of the partiotions: pages for PNP; sectors for others */ + unsigned int pnp_size; + unsigned int vfp_size; + long long mdp_size; + void *priv; + /*should be last*/ + struct gpmi_perchip_data nand[0]; + +}; + +extern struct lba_data *g_data; + +void stmp37cc_dma_print_chain(struct stmp37xx_circ_dma_chain *chain); + +int lba_blk_init(struct lba_data *data); +int lba_blk_remove(struct lba_data *data); +int lba_blk_suspend(struct platform_device *pdev, struct lba_data *data); +int lba_blk_resume(struct platform_device *pdev, struct lba_data *data); + + +int lba_core_init(struct lba_data *data); +int lba_core_remove(struct lba_data *data); +int lba_core_suspend(struct platform_device *pdev, struct lba_data *data); +int lba_core_resume(struct platform_device *pdev, struct lba_data *data); +int lba_core_lock_mode(struct lba_data *data, int mode); +int lba_core_unlock_mode(struct lba_data *data); + +int lba_write_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle); +int lba_read_sectors(void *priv, unsigned int sector, unsigned int count, + void *buffer, dma_addr_t handle); +void lba_protocol1_set(void *priv, uint8_t param); +uint8_t lba_protocol1_get(void *priv); +uint8_t lba_get_status1(void *priv); +uint8_t lba_get_status2(void *priv); + +uint8_t lba_get_id1(void *priv, uint8_t *ret_buffer); +uint8_t lba_get_id2(void *priv, uint8_t *); + + +int queue_cmd(void *priv, + uint8_t *cmd_buf, dma_addr_t cmd_handle, int cmd_len, + dma_addr_t data, int data_len, + struct lba_cmd *cmd_flags); + +int queue_run(void *priv); + +dma_addr_t queue_get_cmd_handle(void *priv); + +uint8_t *queue_get_cmd_ptr(void *priv); + +dma_addr_t queue_get_data_handle(void *priv); + +uint8_t *queue_get_data_ptr(void *priv); + +void queue_plug(struct lba_data *data); +void queue_release(struct lba_data *data); + + +#endif diff --git a/drivers/mtd/nand/mxc_nd.c b/drivers/mtd/nand/mxc_nd.c new file mode 100644 index 000000000000..fc95af3bd442 --- /dev/null +++ b/drivers/mtd/nand/mxc_nd.c @@ -0,0 +1,1413 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <asm/io.h> +#include <asm/mach/flash.h> + +#include "mxc_nd.h" + +#define DVR_VER "2.1" + +struct mxc_mtd_s { + struct mtd_info mtd; + struct nand_chip nand; + struct mtd_partition *parts; + struct device *dev; +}; + +static struct mxc_mtd_s *mxc_nand_data; + +/* + * Define delays in microsec for NAND device operations + */ +#define TROP_US_DELAY 2000 +/* + * Macros to get half word and bit positions of ECC + */ +#define COLPOS(x) ((x) >> 4) +#define BITPOS(x) ((x) & 0xf) + +/* Define single bit Error positions in Main & Spare area */ +#define MAIN_SINGLEBIT_ERROR 0x4 +#define SPARE_SINGLEBIT_ERROR 0x1 + +struct nand_info { + bool bSpareOnly; + bool bStatusRequest; + u16 colAddr; +}; + +static struct nand_info g_nandfc_info; + +#ifdef CONFIG_MTD_NAND_MXC_SWECC +static int hardware_ecc = 0; +#else +static int hardware_ecc = 1; +#endif + +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 +static int Ecc_disabled; +#endif + +static int is2k_Pagesize = 0; + +static struct clk *nfc_clk; + +/* + * OOB placement block for use with hardware ecc generation + */ +static struct nand_ecclayout nand_hw_eccoob_8 = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobfree = {{0, 5}, {11, 5}} +}; + +static struct nand_ecclayout nand_hw_eccoob_16 = { + .eccbytes = 5, + .eccpos = {6, 7, 8, 9, 10}, + .oobfree = {{0, 6}, {12, 4}} +}; + +static struct nand_ecclayout nand_hw_eccoob_2k = { + .eccbytes = 20, + .eccpos = {6, 7, 8, 9, 10, 22, 23, 24, 25, 26, + 38, 39, 40, 41, 42, 54, 55, 56, 57, 58}, + .oobfree = { + {.offset = 0, + .length = 5}, + + {.offset = 11, + .length = 10}, + + {.offset = 27, + .length = 10}, + + {.offset = 43, + .length = 10}, + + {.offset = 59, + .length = 5} + } +}; + +/*! + * @defgroup NAND_MTD NAND Flash MTD Driver for MXC processors + */ + +/*! + * @file mxc_nd.c + * + * @brief This file contains the hardware specific layer for NAND Flash on + * MXC processor + * + * @ingroup NAND_MTD + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +static wait_queue_head_t irq_waitq; + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + NFC_CONFIG1 |= NFC_INT_MSK; /* Disable interrupt */ + wake_up(&irq_waitq); + + return IRQ_RETVAL(1); +} + +/*! + * This function polls the NANDFC to wait for the basic operation to complete by + * checking the INT bit of config2 register. + * + * @param maxRetries number of retry attempts (separated by 1 us) + * @param param parameter for debug + * @param useirq True if IRQ should be used rather than polling + */ +static void wait_op_done(int maxRetries, u16 param, bool useirq) +{ + if (useirq) { + if ((NFC_CONFIG2 & NFC_INT) == 0) { + NFC_CONFIG1 &= ~NFC_INT_MSK; /* Enable interrupt */ + wait_event(irq_waitq, NFC_CONFIG2 & NFC_INT); + } + NFC_CONFIG2 &= ~NFC_INT; + } else { + while (maxRetries-- > 0) { + if (NFC_CONFIG2 & NFC_INT) { + NFC_CONFIG2 &= ~NFC_INT; + break; + } + udelay(1); + } + if (maxRetries <= 0) + DEBUG(MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", + __FUNCTION__, param); + } +} + +/*! + * This function issues the specified command to the NAND device and + * waits for completion. + * + * @param cmd command for NAND Flash + * @param useirq True if IRQ should be used rather than polling + */ +static void send_cmd(u16 cmd, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_cmd(0x%x, %d)\n", cmd, useirq); + + NFC_FLASH_CMD = (u16) cmd; + NFC_CONFIG2 = NFC_CMD; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, cmd, useirq); +} + +/*! + * This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. + * + * @param addr address to be written to NFC. + * @param islast True if this is the last address cycle for command + */ +static void send_addr(u16 addr, bool islast) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_addr(0x%x %d)\n", addr, islast); + + NFC_FLASH_ADDR = addr; + NFC_CONFIG2 = NFC_ADDR; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, addr, islast); +} + +/*! + * This function requests the NANDFC to initate the transfer + * of data currently in the NANDFC RAM buffer to the NAND device. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param bSpareOnly set true if only the spare area is transferred + */ +static void send_prog_page(u8 buf_id, bool bSpareOnly) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_prog_page (%d)\n", bSpareOnly); + + /* NANDFC buffer 0 is used for page read/write */ + + NFC_BUF_ADDR = buf_id; + + /* Configure spare or page+spare access */ + if (!is2k_Pagesize) { + if (bSpareOnly) { + NFC_CONFIG1 |= NFC_SP_EN; + } else { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } + NFC_CONFIG2 = NFC_INPUT; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, bSpareOnly, true); +} + +/*! + * This function will correct the single bit ECC error + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param eccpos Ecc byte and bit position + * @param bSpareOnly set to true if only spare area needs correction + */ + +static void mxc_nd_correct_error(u8 buf_id, u16 eccpos, bool bSpareOnly) +{ + u16 col; + u8 pos; + volatile u16 *buf; + + /* Get col & bit position of error + these macros works for both 8 & 16 bits */ + col = COLPOS(eccpos); /* Get half-word position */ + pos = BITPOS(eccpos); /* Get bit position */ + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nd_correct_error (col=%d pos=%d)\n", col, pos); + + /* Set the pointer for main / spare area */ + if (!bSpareOnly) { + buf = (volatile u16 *)(MAIN_AREA0 + col + (256 * buf_id)); + } else { + buf = (volatile u16 *)(SPARE_AREA0 + col + (8 * buf_id)); + } + + /* Fix the data */ + *buf ^= (1 << pos); +} + +/*! + * This function will maintains state of single bit Error + * in Main & spare area + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param spare set to true if only spare area needs correction + */ +static void mxc_nd_correct_ecc(u8 buf_id, bool spare) +{ +#ifdef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + static int lastErrMain = 0, lastErrSpare = 0; /* To maintain single bit + error in previous page */ +#endif + u16 value, ecc_status; + /* Read the ECC result */ + ecc_status = NFC_ECC_STATUS_RESULT; + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nd_correct_ecc (Ecc status=%x)\n", ecc_status); + +#ifdef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + /* Check for Error in Mainarea */ + if ((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) { + /* Check for error in previous page */ + if (lastErrMain && !spare) { + value = NFC_RSLTMAIN_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, false); + } else { + /* Set if single bit error in current page */ + lastErrMain = 1; + } + } else { + /* Reset if no single bit error in current page */ + lastErrMain = 0; + } + + /* Check for Error in Sparearea */ + if ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR) { + /* Check for error in previous page */ + if (lastErrSpare) { + value = NFC_RSLTSPARE_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, true); + } else { + /* Set if single bit error in current page */ + lastErrSpare = 1; + } + } else { + /* Reset if no single bit error in current page */ + lastErrSpare = 0; + } +#else + if (((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) + || ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR)) { + if (Ecc_disabled) { + if ((ecc_status & 0xC) == MAIN_SINGLEBIT_ERROR) { + value = NFC_RSLTMAIN_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, false); + } + if ((ecc_status & 0x3) == SPARE_SINGLEBIT_ERROR) { + value = NFC_RSLTSPARE_AREA; + /* Correct single bit error in Mainarea + NFC will not correct the error in + current page */ + mxc_nd_correct_error(buf_id, value, true); + } + + } else { + /* Disable ECC */ + NFC_CONFIG1 &= ~(NFC_ECC_EN); + Ecc_disabled = 1; + } + } else if (ecc_status == 0) { + if (Ecc_disabled) { + /* Enable ECC */ + NFC_CONFIG1 |= NFC_ECC_EN; + Ecc_disabled = 0; + } + } else { + /* 2-bit Error Do nothing */ + } +#endif /* CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 */ + +} + +/*! + * This function requests the NANDFC to initated the transfer + * of data from the NAND device into in the NANDFC ram buffer. + * + * @param buf_id Specify Internal RAM Buffer number (0-3) + * @param bSpareOnly set true if only the spare area is transferred + */ +static void send_read_page(u8 buf_id, bool bSpareOnly) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_read_page (%d)\n", bSpareOnly); + + /* NANDFC buffer 0 is used for page read/write */ + NFC_BUF_ADDR = buf_id; + + /* Configure spare or page+spare access */ + if (!is2k_Pagesize) { + if (bSpareOnly) { + NFC_CONFIG1 |= NFC_SP_EN; + } else { + NFC_CONFIG1 &= ~(NFC_SP_EN); + } + } + + NFC_CONFIG2 = NFC_OUTPUT; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, bSpareOnly, true); + + /* If there are single bit errors in + two consecutive page reads then + the error is not corrected by the + NFC for the second page. + Correct single bit error in driver */ + + mxc_nd_correct_ecc(buf_id, bSpareOnly); +} + +/*! + * This function requests the NANDFC to perform a read of the + * NAND device ID. + */ +static void send_read_id(void) +{ + struct nand_chip *this = &mxc_nand_data->nand; + + /* NANDFC buffer 0 is used for device ID output */ + NFC_BUF_ADDR = 0x0; + + /* Read ID into main buffer */ + NFC_CONFIG1 &= (~(NFC_SP_EN)); + NFC_CONFIG2 = NFC_ID; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, 0, true); + + if (this->options & NAND_BUSWIDTH_16) { + volatile u16 *mainBuf = MAIN_AREA0; + + /* + * Pack the every-other-byte result for 16-bit ID reads + * into every-byte as the generic code expects and various + * chips implement. + */ + + mainBuf[0] = (mainBuf[0] & 0xff) | ((mainBuf[1] & 0xff) << 8); + mainBuf[1] = (mainBuf[2] & 0xff) | ((mainBuf[3] & 0xff) << 8); + mainBuf[2] = (mainBuf[4] & 0xff) | ((mainBuf[5] & 0xff) << 8); + } +} + +/*! + * This function requests the NANDFC to perform a read of the + * NAND device status and returns the current status. + * + * @return device status + */ +static u16 get_dev_status(void) +{ + volatile u16 *mainBuf = MAIN_AREA1; + u32 store; + u16 ret; + /* Issue status request to NAND device */ + + /* store the main area1 first word, later do recovery */ + store = *((u32 *) mainBuf); + /* + * NANDFC buffer 1 is used for device status to prevent + * corruption of read/write buffer on status requests. + */ + NFC_BUF_ADDR = 1; + + /* Read status into main buffer */ + NFC_CONFIG1 &= (~(NFC_SP_EN)); + NFC_CONFIG2 = NFC_STATUS; + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, 0, true); + + /* Status is placed in first word of main buffer */ + /* get status, then recovery area 1 data */ + ret = mainBuf[0]; + *((u32 *) mainBuf) = store; + + return ret; +} + +/*! + * This functions is used by upper layer to checks if device is ready + * + * @param mtd MTD structure for the NAND Flash + * + * @return 0 if device is busy else 1 + */ +static int mxc_nand_dev_ready(struct mtd_info *mtd) +{ + /* + * NFC handles R/B internally.Therefore,this function + * always returns status as ready. + */ + return 1; +} + +static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + /* + * If HW ECC is enabled, we turn it on during init. There is + * no need to enable again here. + */ +} + +static int mxc_nand_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + /* + * 1-Bit errors are automatically corrected in HW. No need for + * additional correction. 2-Bit errors cannot be corrected by + * HW ECC, so we need to return failure + */ + u16 ecc_status = NFC_ECC_STATUS_RESULT; + + if (((ecc_status & 0x3) == 2) || ((ecc_status >> 2) == 2)) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_NAND: HWECC uncorrectable 2-bit ECC error\n"); + return -1; + } + + return 0; +} + +static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, + u_char * ecc_code) +{ + /* + * Just return success. HW ECC does not read/write the NFC spare + * buffer. Only the FLASH spare area contains the calcuated ECC. + */ + return 0; +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u_char mxc_nand_read_byte(struct mtd_info *mtd) +{ + u_char retVal = 0; + u16 col, rdWord; + volatile u16 *mainBuf = MAIN_AREA0; + volatile u16 *spareBuf = SPARE_AREA0; + + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + /* Get column for 16-bit access */ + col = g_nandfc_info.colAddr >> 1; + + /* If we are accessing the spare region */ + if (g_nandfc_info.bSpareOnly) { + rdWord = spareBuf[col]; + } else { + rdWord = mainBuf[col]; + } + + /* Pick upper/lower byte of word from RAM buffer */ + if (g_nandfc_info.colAddr & 0x1) { + retVal = (rdWord >> 8) & 0xFF; + } else { + retVal = rdWord & 0xFF; + } + + /* Update saved column address */ + g_nandfc_info.colAddr++; + + return retVal; +} + +/*! + * This function reads word from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u16 mxc_nand_read_word(struct mtd_info *mtd) +{ + u16 col; + u16 rdWord, retVal; + volatile u16 *p; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_read_word(col = %d)\n", g_nandfc_info.colAddr); + + col = g_nandfc_info.colAddr; + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + if (col < mtd->writesize) + p = (MAIN_AREA0) + (col >> 1); + else + p = (SPARE_AREA0) + ((col - mtd->writesize) >> 1); + + if (col & 1) { + rdWord = *p; + retVal = (rdWord >> 8) & 0xff; + rdWord = *(p + 1); + retVal |= (rdWord << 8) & 0xff00; + + } else { + retVal = *p; + + } + + /* Update saved column address */ + g_nandfc_info.colAddr = col + 2; + + return retVal; +} + +/*! + * This function writes data of length \b len to buffer \b buf. The data to be + * written on NAND Flash is first copied to RAMbuffer. After the Data Input + * Operation by the NFC, the data is written to NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be written to NAND Flash + * @param len number of bytes to be written + */ +static void mxc_nand_write_buf(struct mtd_info *mtd, + const u_char * buf, int len) +{ + int n; + int col; + int i = 0; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_write_buf(col = %d, len = %d)\n", g_nandfc_info.colAddr, + len); + + col = g_nandfc_info.colAddr; + + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + n = mtd->writesize + mtd->oobsize - col; + n = min(len, n); + + DEBUG(MTD_DEBUG_LEVEL3, + "%s:%d: col = %d, n = %d\n", __FUNCTION__, __LINE__, col, n); + + while (n) { + volatile u32 *p; + if (col < mtd->writesize) + p = (volatile u32 *)((ulong) (MAIN_AREA0) + (col & ~3)); + else + p = (volatile u32 *)((ulong) (SPARE_AREA0) - + mtd->writesize + (col & ~3)); + + DEBUG(MTD_DEBUG_LEVEL3, "%s:%d: p = %p\n", __FUNCTION__, + __LINE__, p); + + if (((col | (int)&buf[i]) & 3) || n < 16) { + u32 data = 0; + + if (col & 3 || n < 4) + data = *p; + + switch (col & 3) { + case 0: + if (n) { + data = (data & 0xffffff00) | + (buf[i++] << 0); + n--; + col++; + } + case 1: + if (n) { + data = (data & 0xffff00ff) | + (buf[i++] << 8); + n--; + col++; + } + case 2: + if (n) { + data = (data & 0xff00ffff) | + (buf[i++] << 16); + n--; + col++; + } + case 3: + if (n) { + data = (data & 0x00ffffff) | + (buf[i++] << 24); + n--; + col++; + } + } + + *p = data; + } else { + int m = mtd->writesize - col; + + if (col >= mtd->writesize) + m += mtd->oobsize; + + m = min(n, m) & ~3; + + DEBUG(MTD_DEBUG_LEVEL3, + "%s:%d: n = %d, m = %d, i = %d, col = %d\n", + __FUNCTION__, __LINE__, n, m, i, col); + + memcpy((void *)(p), &buf[i], m); + col += m; + i += m; + n -= m; + } + } + /* Update saved column address */ + g_nandfc_info.colAddr = col; + +} + +/*! + * This function id is used to read the data buffer from the NAND Flash. To + * read the data from NAND Flash first the data output cycle is initiated by + * the NFC, which copies the data to RAMbuffer. This data of length \b len is + * then copied to buffer \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be read from NAND Flash + * @param len number of bytes to be read + */ +static void mxc_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + + int n; + int col; + int i = 0; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_read_buf(col = %d, len = %d)\n", g_nandfc_info.colAddr, + len); + + col = g_nandfc_info.colAddr; + /* Adjust saved column address */ + if (col < mtd->writesize && g_nandfc_info.bSpareOnly) + col += mtd->writesize; + + n = mtd->writesize + mtd->oobsize - col; + n = min(len, n); + + while (n) { + volatile u32 *p; + + if (col < mtd->writesize) + p = (volatile u32 *)((ulong) (MAIN_AREA0) + (col & ~3)); + else + p = (volatile u32 *)((ulong) (SPARE_AREA0) - + mtd->writesize + (col & ~3)); + + if (((col | (int)&buf[i]) & 3) || n < 16) { + u32 data; + + data = *p; + switch (col & 3) { + case 0: + if (n) { + buf[i++] = (u8) (data); + n--; + col++; + } + case 1: + if (n) { + buf[i++] = (u8) (data >> 8); + n--; + col++; + } + case 2: + if (n) { + buf[i++] = (u8) (data >> 16); + n--; + col++; + } + case 3: + if (n) { + buf[i++] = (u8) (data >> 24); + n--; + col++; + } + } + } else { + int m = mtd->writesize - col; + + if (col >= mtd->writesize) + m += mtd->oobsize; + + m = min(n, m) & ~3; + memcpy(&buf[i], (void *)(p), m); + col += m; + i += m; + n -= m; + } + } + /* Update saved column address */ + g_nandfc_info.colAddr = col; + +} + +/*! + * This function is used by the upper layer to verify the data in NAND Flash + * with the data in the \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be verified + * @param len length of the data to be verified + * + * @return -EFAULT if error else 0 + * + */ +static int +mxc_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, int len) +{ + return -EFAULT; +} + +/*! + * This function is used by upper layer for select and deselect of the NAND + * chip + * + * @param mtd MTD structure for the NAND Flash + * @param chip val indicating select or deselect + */ +static void mxc_nand_select_chip(struct mtd_info *mtd, int chip) +{ +#ifdef CONFIG_MTD_NAND_MXC_FORCE_CE + if (chip > 0) { + DEBUG(MTD_DEBUG_LEVEL0, + "ERROR: Illegal chip select (chip = %d)\n", chip); + return; + } + + if (chip == -1) { + NFC_CONFIG1 &= (~(NFC_CE)); + return; + } + + NFC_CONFIG1 |= NFC_CE; +#endif + + switch (chip) { + case -1: + /* Disable the NFC clock */ + clk_disable(nfc_clk); + break; + case 0: + /* Enable the NFC clock */ + clk_enable(nfc_clk); + break; + + default: + break; + } +} + +/*! + * This function is used by the upper layer to write command to NAND Flash for + * different operations to be carried out on NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param command command for NAND Flash + * @param column column offset for the page read + * @param page_addr page to be read from NAND Flash + */ +static void mxc_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + bool useirq = true; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", + command, column, page_addr); + + /* + * Reset command state information + */ + g_nandfc_info.bStatusRequest = false; + + /* Reset column address to 0 */ + g_nandfc_info.colAddr = 0; + + /* + * Command pre-processing step + */ + switch (command) { + + case NAND_CMD_STATUS: + g_nandfc_info.bStatusRequest = true; + break; + + case NAND_CMD_READ0: + g_nandfc_info.colAddr = column; + g_nandfc_info.bSpareOnly = false; + useirq = false; + break; + + case NAND_CMD_READOOB: + g_nandfc_info.colAddr = column; + g_nandfc_info.bSpareOnly = true; + useirq = false; + if (is2k_Pagesize) + command = NAND_CMD_READ0; /* only READ0 is valid */ + break; + + case NAND_CMD_SEQIN: + if (column >= mtd->writesize) { + if (is2k_Pagesize) { + /** + * FIXME: before send SEQIN command for write OOB, + * We must read one page out. + * For K9F1GXX has no READ1 command to set current HW + * pointer to spare area, we must write the whole page including OOB together. + */ + /* call itself to read a page */ + mxc_nand_command(mtd, NAND_CMD_READ0, 0, + page_addr); + } + g_nandfc_info.colAddr = column - mtd->writesize; + g_nandfc_info.bSpareOnly = true; + /* Set program pointer to spare region */ + if (!is2k_Pagesize) + send_cmd(NAND_CMD_READOOB, false); + } else { + g_nandfc_info.bSpareOnly = false; + g_nandfc_info.colAddr = column; + /* Set program pointer to page start */ + if (!is2k_Pagesize) + send_cmd(NAND_CMD_READ0, false); + } + useirq = false; + break; + + case NAND_CMD_PAGEPROG: +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + if (Ecc_disabled) { + /* Enable Ecc for page writes */ + NFC_CONFIG1 |= NFC_ECC_EN; + } +#endif + + send_prog_page(0, g_nandfc_info.bSpareOnly); + + if (is2k_Pagesize) { + /* data in 4 areas datas */ + send_prog_page(1, g_nandfc_info.bSpareOnly); + send_prog_page(2, g_nandfc_info.bSpareOnly); + send_prog_page(3, g_nandfc_info.bSpareOnly); + } + + break; + + case NAND_CMD_ERASE1: + useirq = false; + break; + } + + /* + * Write out the command to the device. + */ + send_cmd(command, useirq); + + /* + * Write out column address, if necessary + */ + if (column != -1) { + /* + * MXC NANDFC can only perform full page+spare or + * spare-only read/write. When the upper layers + * layers perform a read/write buf operation, + * we will used the saved column adress to index into + * the full page. + */ + send_addr(0, page_addr == -1); + if (is2k_Pagesize) + /* another col addr cycle for 2k page */ + send_addr(0, false); + } + + /* + * Write out page address, if necessary + */ + if (page_addr != -1) { + send_addr((page_addr & 0xff), false); /* paddr_0 - p_addr_7 */ + + if (is2k_Pagesize) { + /* One more address cycle for higher density devices */ + if (mtd->size >= 0x10000000) { + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, false); + send_addr((page_addr >> 16) & 0xff, true); + } else + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, true); + } else { + /* One more address cycle for higher density devices */ + if (mtd->size >= 0x4000000) { + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, false); + send_addr((page_addr >> 16) & 0xff, true); + } else + /* paddr_8 - paddr_15 */ + send_addr((page_addr >> 8) & 0xff, true); + } + } + + /* + * Command post-processing step + */ + switch (command) { + + case NAND_CMD_RESET: + break; + + case NAND_CMD_READOOB: + case NAND_CMD_READ0: + if (is2k_Pagesize) { + /* send read confirm command */ + send_cmd(NAND_CMD_READSTART, true); + /* read for each AREA */ + send_read_page(0, g_nandfc_info.bSpareOnly); + send_read_page(1, g_nandfc_info.bSpareOnly); + send_read_page(2, g_nandfc_info.bSpareOnly); + send_read_page(3, g_nandfc_info.bSpareOnly); + } else { + send_read_page(0, g_nandfc_info.bSpareOnly); + } + break; + + case NAND_CMD_READID: + send_read_id(); + break; + + case NAND_CMD_PAGEPROG: +#ifndef CONFIG_MTD_NAND_MXC_ECC_CORRECTION_OPTION2 + if (Ecc_disabled) { + /* Disble Ecc after page writes */ + NFC_CONFIG1 &= ~(NFC_ECC_EN); + } +#endif + break; + + case NAND_CMD_STATUS: + break; + + case NAND_CMD_ERASE2: + break; + } +} + +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr smallpage_memorybased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +/* Generic flash bbt decriptors +*/ +static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +static int mxc_nand_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + + /* Config before scanning */ + /* Do not rely on NFMS_BIT, set/clear NFMS bit based on mtd->writesize */ + if (mtd->writesize == 2048) { + NFMS |= (1 << NFMS_BIT); + is2k_Pagesize = 1; + } else { + if ((NFMS >> NFMS_BIT) & 0x1) { /* This case strangly happened on MXC91321 P1.2.2 */ + printk(KERN_INFO + "Oops... NFMS Bit set for 512B Page, resetting it. [RCSR: 0x%08x]\n", + NFMS); + NFMS &= ~(1 << NFMS_BIT); + } + is2k_Pagesize = 0; + } + + if (is2k_Pagesize) + this->ecc.layout = &nand_hw_eccoob_2k; + + /* jffs2 not write oob */ + mtd->flags &= ~MTD_OOB_WRITEABLE; + + /* use flash based bbt */ + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + + /* update flash based bbt */ + this->options |= NAND_USE_FLASH_BBT; + + if (!this->badblock_pattern) { + if (mtd->writesize == 2048) + this->badblock_pattern = &smallpage_memorybased; + else + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_memorybased : &smallpage_memorybased; + } + /* Build bad block table */ + return nand_scan_bbt(mtd, this->badblock_pattern); +} + +#ifdef CONFIG_MXC_NAND_LOW_LEVEL_ERASE +static void mxc_low_erase(struct mtd_info *mtd) +{ + + struct nand_chip *this = mtd->priv; + unsigned int page_addr, addr; + u_char status; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : mxc_low_erase:Erasing NAND\n"); + for (addr = 0; addr < this->chipsize; addr += mtd->erasesize) { + page_addr = addr / mtd->writesize; + mxc_nand_command(mtd, NAND_CMD_ERASE1, -1, page_addr); + mxc_nand_command(mtd, NAND_CMD_ERASE2, -1, -1); + mxc_nand_command(mtd, NAND_CMD_STATUS, -1, -1); + status = mxc_nand_read_byte(mtd); + if (status & NAND_STATUS_FAIL) { + printk(KERN_ERR + "ERASE FAILED(block = %d,status = 0x%x)\n", + addr / mtd->erasesize, status); + } + } + +} +#endif +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and + * remove functions + * + * @return The function always returns 0. + */ +static int __init mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct flash_platform_data *flash = pdev->dev.platform_data; + int nr_parts = 0; + + int err = 0; + /* Allocate memory for MTD device structure and private data */ + mxc_nand_data = kzalloc(sizeof(struct mxc_mtd_s), GFP_KERNEL); + if (!mxc_nand_data) { + printk(KERN_ERR "%s: failed to allocate mtd_info\n", + __FUNCTION__); + err = -ENOMEM; + goto out; + } + memset((char *)&g_nandfc_info, 0, sizeof(g_nandfc_info)); + + mxc_nand_data->dev = &pdev->dev; + /* structures must be linked */ + this = &mxc_nand_data->nand; + mtd = &mxc_nand_data->mtd; + mtd->priv = this; + mtd->owner = THIS_MODULE; + + /* 50 us command delay time */ + this->chip_delay = 5; + + this->priv = mxc_nand_data; + this->dev_ready = mxc_nand_dev_ready; + this->cmdfunc = mxc_nand_command; + this->select_chip = mxc_nand_select_chip; + this->read_byte = mxc_nand_read_byte; + this->read_word = mxc_nand_read_word; + this->write_buf = mxc_nand_write_buf; + this->read_buf = mxc_nand_read_buf; + this->verify_buf = mxc_nand_verify_buf; + this->scan_bbt = mxc_nand_scan_bbt; + + nfc_clk = clk_get(&pdev->dev, "nfc_clk"); + clk_enable(nfc_clk); + + NFC_CONFIG1 |= NFC_INT_MSK; + init_waitqueue_head(&irq_waitq); + err = request_irq(MXC_INT_NANDFC, mxc_nfc_irq, 0, "mxc_nd", NULL); + if (err) { + goto out_1; + } + + if (hardware_ecc) { + this->ecc.calculate = mxc_nand_calculate_ecc; + this->ecc.hwctl = mxc_nand_enable_hwecc; + this->ecc.correct = mxc_nand_correct_data; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; + this->ecc.bytes = 3; + this->ecc.layout = &nand_hw_eccoob_8; + NFC_CONFIG1 |= NFC_ECC_EN; + } else { + this->ecc.mode = NAND_ECC_SOFT; + } + + /* Reset NAND */ + this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + /* preset operation */ + /* Unlock the internal RAM Buffer */ + NFC_CONFIG = 0x2; + + /* Blocks to be unlocked */ + NFC_UNLOCKSTART_BLKADDR = 0x0; + NFC_UNLOCKEND_BLKADDR = 0x4000; + + /* Unlock Block Command for given address range */ + NFC_WRPROT = 0x4; + + /* NAND bus width determines access funtions used by upper layer */ + if (flash->width == 2) { + this->options |= NAND_BUSWIDTH_16; + this->ecc.layout = &nand_hw_eccoob_16; + } else { + this->options |= 0; + } + + is2k_Pagesize = 0; + + /* Scan to find existence of the device */ + if (nand_scan(mtd, 1)) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_ND: Unable to find any NAND device.\n"); + err = -ENXIO; + goto out_1; + } + + /* Register the partitions */ +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(mtd, part_probes, &mxc_nand_data->parts, 0); + if (nr_parts > 0) + add_mtd_partitions(mtd, mxc_nand_data->parts, nr_parts); + else if (flash->parts) + add_mtd_partitions(mtd, flash->parts, flash->nr_parts); + else +#endif + { + pr_info("Registering %s as whole device\n", mtd->name); + add_mtd_device(mtd); + } +#ifdef CONFIG_MXC_NAND_LOW_LEVEL_ERASE + /* Erase all the blocks of a NAND */ + mxc_low_erase(mtd); +#endif + + platform_set_drvdata(pdev, mtd); + return 0; + + out_1: + kfree(mxc_nand_data); + out: + return err; + +} + + /*! + * Dissociates the driver from the device. + * + * @param pdev the device structure used to give information on which + * + * @return The function always returns 0. + */ + +static int __exit mxcnd_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + clk_put(nfc_clk); + platform_set_drvdata(pdev, NULL); + + if (mxc_nand_data) { + nand_release(mtd); + free_irq(MXC_INT_NANDFC, NULL); + kfree(mxc_nand_data); + } + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the NAND in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ + +static int mxcnd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND suspend\n"); + if (info) + ret = info->suspend(info); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + return ret; +} + +/*! + * This function is called to bring the NAND back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcnd_resume(struct platform_device *pdev) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND : NAND resume\n"); + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + if (info) { + info->resume(info); + } + + return ret; +} + +#else +#define mxcnd_suspend NULL +#define mxcnd_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcnd_driver = { + .driver = { + .name = "mxc_nand_flash", + }, + .probe = mxcnd_probe, + .remove = __exit_p(mxcnd_remove), + .suspend = mxcnd_suspend, + .resume = mxcnd_resume, +}; + +/*! + * Main initialization routine + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_nd_init(void) +{ + /* Register the device driver structure. */ + pr_info("MXC MTD nand Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcnd_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcnd_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * Clean up routine + */ +static void __exit mxc_nd_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxcnd_driver); +} + +module_init(mxc_nd_init); +module_exit(mxc_nd_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/mxc_nd.h b/drivers/mtd/nand/mxc_nd.h new file mode 100644 index 000000000000..e38a9a637c1d --- /dev/null +++ b/drivers/mtd/nand/mxc_nd.h @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_nd.h + * + * @brief This file contains the NAND Flash Controller register information. + * + * + * @ingroup NAND_MTD + */ + +#ifndef __MXC_ND_H__ +#define __MXC_ND_H__ + +#include <mach/hardware.h> + +/* + * Addresses for NFC registers + */ +#define NFC_BUF_SIZE (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE00))) +#define NFC_BUF_ADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE04))) +#define NFC_FLASH_ADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE06))) +#define NFC_FLASH_CMD (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE08))) +#define NFC_CONFIG (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0A))) +#define NFC_ECC_STATUS_RESULT (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0C))) +#define NFC_RSLTMAIN_AREA (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE0E))) +#define NFC_RSLTSPARE_AREA (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE10))) +#define NFC_WRPROT (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE12))) +#define NFC_UNLOCKSTART_BLKADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE14))) +#define NFC_UNLOCKEND_BLKADDR (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE16))) +#define NFC_NF_WRPRST (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE18))) +#define NFC_CONFIG1 (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE1A))) +#define NFC_CONFIG2 (*((volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0xE1C))) + +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x000) +#define MAIN_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x200) +#define MAIN_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x400) +#define MAIN_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x600) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#define SPARE_AREA0 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x800) +#define SPARE_AREA1 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x810) +#define SPARE_AREA2 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x820) +#define SPARE_AREA3 (volatile u16 *)IO_ADDRESS(NFC_BASE_ADDR + 0x830) + +/*! + * Set INT to 0, FCMD to 1, rest to 0 in NFC_CONFIG2 Register for Command + * operation + */ +#define NFC_CMD 0x1 + +/*! + * Set INT to 0, FADD to 1, rest to 0 in NFC_CONFIG2 Register for Address + * operation + */ +#define NFC_ADDR 0x2 + +/*! + * Set INT to 0, FDI to 1, rest to 0 in NFC_CONFIG2 Register for Input + * operation + */ +#define NFC_INPUT 0x4 + +/*! + * Set INT to 0, FDO to 001, rest to 0 in NFC_CONFIG2 Register for Data Output + * operation + */ +#define NFC_OUTPUT 0x8 + +/*! + * Set INT to 0, FD0 to 010, rest to 0 in NFC_CONFIG2 Register for Read ID + * operation + */ +#define NFC_ID 0x10 + +/*! + * Set INT to 0, FDO to 100, rest to 0 in NFC_CONFIG2 Register for Read Status + * operation + */ +#define NFC_STATUS 0x20 + +/*! + * Set INT to 1, rest to 0 in NFC_CONFIG2 Register for Read Status + * operation + */ +#define NFC_INT 0x8000 + +#define NFC_SP_EN (1 << 2) +#define NFC_ECC_EN (1 << 3) +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_RST (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) + +#endif /* MXCND_H */ diff --git a/drivers/mtd/nand/mxc_nd2.c b/drivers/mtd/nand/mxc_nd2.c new file mode 100644 index 000000000000..65b0fd6cfaff --- /dev/null +++ b/drivers/mtd/nand/mxc_nd2.c @@ -0,0 +1,1448 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mtd/partitions.h> +#include <asm/mach/flash.h> +#include <asm/io.h> +#include "mxc_nd2.h" + +#define DVR_VER "2.5" + +/* Global address Variables */ +static void __iomem *nfc_axi_base, *nfc_ip_base; + +struct mxc_mtd_s { + struct mtd_info mtd; + struct nand_chip nand; + struct mtd_partition *parts; + struct device *dev; +}; + +static struct mxc_mtd_s *mxc_nand_data; + +/* + * Define delays in microsec for NAND device operations + */ +#define TROP_US_DELAY 2000 + +struct nand_info { + bool bStatusRequest; + u16 colAddr; +}; + +static struct nand_info g_nandfc_info; + +#ifdef CONFIG_MTD_NAND_MXC_SWECC +static int hardware_ecc = 0; +#else +static int hardware_ecc = 1; +#endif + +static u8 num_of_interleave = 1; + +static u8 *data_buf; +static u8 *oob_buf; + +static int g_page_mask; + +static struct clk *nfc_clk; + +/* + * OOB placement block for use with hardware ecc generation + */ +static struct nand_ecclayout nand_hw_eccoob_512 = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{0, 4}} +}; + +static struct nand_ecclayout nand_hw_eccoob_2k = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{2, 4}} +}; + +static struct nand_ecclayout nand_hw_eccoob_4k = { + .eccbytes = 9, + .eccpos = {7, 8, 9, 10, 11, 12, 13, 14, 15}, + .oobavail = 4, + .oobfree = {{2, 4}} +}; + +/*! + * @defgroup NAND_MTD NAND Flash MTD Driver for MXC processors + */ + +/*! + * @file mxc_nd2.c + * + * @brief This file contains the hardware specific layer for NAND Flash on + * MXC processor + * + * @ingroup NAND_MTD + */ + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "RedBoot", "cmdlinepart", NULL }; +#endif + +static wait_queue_head_t irq_waitq; + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + /* Disable Interuupt */ + raw_write(raw_read(REG_NFC_INTRRUPT) | NFC_INT_MSK, REG_NFC_INTRRUPT); + wake_up(&irq_waitq); + + return IRQ_HANDLED; +} + +static void nfc_memcpy(void *dest, void *src, int len) +{ + u8 *d = dest; + u8 *s = src; + + while (len > 0) { + if (len >= 4) { + *(u32 *)d = *(u32 *)s; + d += 4; + s += 4; + len -= 4; + } else { + *(u16 *)d = *(u16 *)s; + len -= 2; + break; + } + } + + if (len) + BUG(); +} + +/* + * Functions to transfer data to/from spare erea. + */ +static void +copy_spare(struct mtd_info *mtd, void *pbuf, void *pspare, int len, bool bfrom) +{ + u16 i, j; + u16 m = mtd->oobsize; + u16 n = mtd->writesize >> 9; + u8 *d = (u8 *) pbuf; + u8 *s = (u8 *) pspare; + u16 t = SPARE_LEN; + + m /= num_of_interleave; + n /= num_of_interleave; + + j = (m / n >> 1) << 1; + + if (bfrom) { + for (i = 0; i < n - 1; i++) + nfc_memcpy(&d[i * j], &s[i * t], j); + + /* the last section */ + nfc_memcpy(&d[i * j], &s[i * t], len - i * j); + } else { + for (i = 0; i < n - 1; i++) + nfc_memcpy(&s[i * t], &d[i * j], j); + + /* the last section */ + nfc_memcpy(&s[i * t], &d[i * j], len - i * j); + } +} + +/*! + * This function polls the NFC to wait for the basic operation to complete by + * checking the INT bit of config2 register. + * + * @param maxRetries number of retry attempts (separated by 1 us) + * @param useirq True if IRQ should be used rather than polling + */ +static void wait_op_done(int maxRetries, bool useirq) +{ + if (useirq) { + if ((raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT) == 0) { + /* Enable Interuupt */ + raw_write(raw_read(REG_NFC_INTRRUPT) & ~NFC_INT_MSK, + REG_NFC_INTRRUPT); + wait_event(irq_waitq, + (raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT)); + } + WRITE_NFC_IP_REG((raw_read(REG_NFC_OPS_STAT) & + ~NFC_OPS_STAT), REG_NFC_OPS_STAT); + } else { + while (1) { + maxRetries--; + if (raw_read(REG_NFC_OPS_STAT) & NFC_OPS_STAT) { + WRITE_NFC_IP_REG((raw_read(REG_NFC_OPS_STAT) & + ~NFC_OPS_STAT), + REG_NFC_OPS_STAT); + break; + } + udelay(1); + if (maxRetries <= 0) { + DEBUG(MTD_DEBUG_LEVEL0, "%s(%d): INT not set\n", + __func__, __LINE__); + } + } + } +} + +static inline void send_atomic_cmd(u16 cmd, bool useirq) +{ + /* fill command */ + raw_write(cmd, REG_NFC_FLASH_CMD); + + /* clear status */ + ACK_OPS; + + /* send out command */ + raw_write(NFC_CMD, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, useirq); +} + +static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr); +static int mxc_check_ecc_status(struct mtd_info *mtd); + +#ifdef NFC_AUTO_MODE_ENABLE +/*! + * This function handle the interleave related work + * @param mtd mtd info + * @param cmd command + */ +static void auto_cmd_interleave(struct mtd_info *mtd, u16 cmd) +{ + u32 i, page_addr, ncs; + u32 j = num_of_interleave; + struct nand_chip *this = mtd->priv; + u32 addr_low = raw_read(NFC_FLASH_ADDR0); + u32 addr_high = raw_read(NFC_FLASH_ADDR8); + u8 *dbuf = data_buf; + u8 *obuf = oob_buf; + u32 dlen = mtd->writesize / j; + u32 olen = mtd->oobsize / j; + + /* adjust the addr value + * since ADD_OP mode is 01 + */ + if (cmd == NAND_CMD_ERASE2) + page_addr = addr_low; + else + page_addr = addr_low >> 16 | addr_high << 16; + + ncs = page_addr >> (this->chip_shift - this->page_shift); + + if (j > 1) { + page_addr *= j; + } else { + page_addr *= this->numchips; + page_addr += ncs; + } + + switch (cmd) { + case NAND_CMD_PAGEPROG: + for (i = 0; i < j; i++) { + /* reset addr cycle */ + mxc_do_addr_cycle(mtd, 0, page_addr++); + + /* data transfer */ + memcpy(MAIN_AREA0, dbuf, dlen); + copy_spare(mtd, obuf, SPARE_AREA0, olen, false); + + /* update the value */ + dbuf += dlen; + obuf += olen; + + NFC_SET_RBA(0); + ACK_OPS; + raw_write(NFC_AUTO_PROG, REG_NFC_OPS); + + /* wait auto_prog_done bit set */ + while (!(raw_read(REG_NFC_OPS_STAT) & NFC_OP_DONE)) ; + } + + wait_op_done(TROP_US_DELAY, false); + while (!(raw_read(REG_NFC_OPS_STAT) & NFC_RB)) ; + + break; + case NAND_CMD_READSTART: + for (i = 0; i < j; i++) { + /* reset addr cycle */ + mxc_do_addr_cycle(mtd, 0, page_addr++); + + NFC_SET_RBA(0); + ACK_OPS; + raw_write(NFC_AUTO_READ, REG_NFC_OPS); + wait_op_done(TROP_US_DELAY, false); + + /* check ecc error */ + mxc_check_ecc_status(mtd); + + /* data transfer */ + memcpy(dbuf, MAIN_AREA0, dlen); + copy_spare(mtd, obuf, SPARE_AREA0, olen, true); + + /* update the value */ + dbuf += dlen; + obuf += olen; + } + break; + case NAND_CMD_ERASE2: + for (i = 0; i < j; i++) { + mxc_do_addr_cycle(mtd, -1, page_addr++); + ACK_OPS; + raw_write(NFC_AUTO_ERASE, REG_NFC_OPS); + wait_op_done(TROP_US_DELAY, true); + } + break; + case NAND_CMD_RESET: + for (i = 0; i < j; i++) { + if (j > 1) + NFC_SET_NFC_ACTIVE_CS(i); + send_atomic_cmd(cmd, false); + } + break; + default: + break; + } +} +#endif + +static void send_addr(u16 addr, bool useirq); + +/*! + * This function issues the specified command to the NAND device and + * waits for completion. + * + * @param cmd command for NAND Flash + * @param useirq True if IRQ should be used rather than polling + */ +static void send_cmd(struct mtd_info *mtd, u16 cmd, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_cmd(0x%x, %d)\n", cmd, useirq); + +#ifdef NFC_AUTO_MODE_ENABLE + switch (cmd) { + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + raw_write(NAND_CMD_READ0, REG_NFC_FLASH_CMD); + break; + case NAND_CMD_SEQIN: + case NAND_CMD_ERASE1: + raw_write(cmd, REG_NFC_FLASH_CMD); + break; + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE2: + case NAND_CMD_READSTART: + raw_write(raw_read(REG_NFC_FLASH_CMD) | cmd << NFC_CMD_1_SHIFT, + REG_NFC_FLASH_CMD); + auto_cmd_interleave(mtd, cmd); + break; + case NAND_CMD_READID: + send_atomic_cmd(cmd, useirq); + send_addr(0, false); + break; + case NAND_CMD_RESET: + auto_cmd_interleave(mtd, cmd); + case NAND_CMD_STATUS: + break; + default: + break; + } +#else + send_atomic_cmd(cmd, useirq); +#endif +} + +/*! + * This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. + * + * @param addr address to be written to NFC. + * @param useirq True if IRQ should be used rather than polling + */ +static void send_addr(u16 addr, bool useirq) +{ + DEBUG(MTD_DEBUG_LEVEL3, "send_addr(0x%x %d)\n", addr, useirq); + + /* fill address */ + raw_write((addr << NFC_FLASH_ADDR_SHIFT), REG_NFC_FLASH_ADDR); + + /* clear status */ + ACK_OPS; + + /* send out address */ + raw_write(NFC_ADDR, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, useirq); +} + +/*! + * This function requests the NFC to initate the transfer + * of data currently in the NFC RAM buffer to the NAND device. + * + * @param buf_id Specify Internal RAM Buffer number + */ +static void send_prog_page(u8 buf_id) +{ +#ifndef NFC_AUTO_MODE_ENABLE + DEBUG(MTD_DEBUG_LEVEL3, "%s\n", __FUNCTION__); + + /* set ram buffer id */ + NFC_SET_RBA(buf_id); + + /* clear status */ + ACK_OPS; + + /* transfer data from NFC ram to nand */ + raw_write(NFC_INPUT, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); +#endif +} + +/*! + * This function requests the NFC to initated the transfer + * of data from the NAND device into in the NFC ram buffer. + * + * @param buf_id Specify Internal RAM Buffer number + */ +static void send_read_page(u8 buf_id) +{ +#ifndef NFC_AUTO_MODE_ENABLE + DEBUG(MTD_DEBUG_LEVEL3, "%s(%d)\n", __FUNCTION__, buf_id); + + /* set ram buffer id */ + NFC_SET_RBA(buf_id); + + /* clear status */ + ACK_OPS; + + /* transfer data from nand to NFC ram */ + raw_write(NFC_OUTPUT, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); +#endif +} + +/*! + * This function requests the NFC to perform a read of the + * NAND device ID. + */ +static void send_read_id(void) +{ + /* Set RBA bits for BUFFER0 */ + NFC_SET_RBA(0); + + /* clear status */ + ACK_OPS; + + /* Read ID into main buffer */ + raw_write(NFC_ID, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); + +} + +#ifdef NFC_AUTO_MODE_ENABLE +static inline void read_dev_status(u16 *status) +{ + u32 mask = 0xFF << 16; + + /* clear status */ + ACK_OPS; + + do { + /* send auto read status command */ + raw_write(NFC_AUTO_STATE, REG_NFC_OPS); + if (cpu_is_mx51_rev(CHIP_REV_2_0) == 1) + wait_op_done(TROP_US_DELAY, false); + *status = (raw_read(NFC_CONFIG1) & mask) >> 16; + } while ((*status & NAND_STATUS_READY) == 0); +} +#endif + +/*! + * This function requests the NFC to perform a read of the + * NAND device status and returns the current status. + * + * @return device status + */ +static u16 get_dev_status(void) +{ +#ifdef NFC_AUTO_MODE_ENABLE + int i; + u16 status = 0; + for (i = 0; i < num_of_interleave; i++) { + + /* set ative cs */ + NFC_SET_NFC_ACTIVE_CS(i); + + /* FIXME, NFC Auto erase may have + * problem, have to pollingit until + * the nand get idle, otherwise + * it may get error + */ + read_dev_status(&status); + if (status & NAND_STATUS_FAIL) + break; + } + + return status; +#else + volatile u16 *mainBuf = MAIN_AREA1; + u8 val = 1; + u16 ret; + + /* Set ram buffer id */ + NFC_SET_RBA(val); + + /* clear status */ + ACK_OPS; + + /* Read status into main buffer */ + raw_write(NFC_STATUS, REG_NFC_OPS); + + /* Wait for operation to complete */ + wait_op_done(TROP_US_DELAY, false); + + /* Status is placed in first word of main buffer */ + /* get status, then recovery area 1 data */ + ret = *mainBuf; + + return ret; +#endif +} + +static void mxc_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + raw_write((raw_read(REG_NFC_ECC_EN) | NFC_ECC_EN), REG_NFC_ECC_EN); + return; +} + +/* + * Function to record the ECC corrected/uncorrected errors resulted + * after a page read. This NFC detects and corrects upto to 4 symbols + * of 9-bits each. + */ +static int mxc_check_ecc_status(struct mtd_info *mtd) +{ + u32 ecc_stat, err; + int no_subpages = 1; + int ret = 0; + u8 ecc_bit_mask, err_limit; + + ecc_bit_mask = (IS_4BIT_ECC ? 0x7 : 0xf); + err_limit = (IS_4BIT_ECC ? 0x4 : 0x8); + + no_subpages = mtd->writesize >> 9; + + no_subpages /= num_of_interleave; + + ecc_stat = GET_NFC_ECC_STATUS(); + do { + err = ecc_stat & ecc_bit_mask; + if (err > err_limit) { + mtd->ecc_stats.failed++; + printk(KERN_WARNING "UnCorrectable RS-ECC Error\n"); + return -1; + } else { + ret += err; + } + ecc_stat >>= 4; + } while (--no_subpages); + + mtd->ecc_stats.corrected += ret; + pr_debug("%d Symbol Correctable RS-ECC Error\n", ret); + + return ret; +} + +/* + * Function to correct the detected errors. This NFC corrects all the errors + * detected. So this function just return 0. + */ +static int mxc_nand_correct_data(struct mtd_info *mtd, u_char * dat, + u_char * read_ecc, u_char * calc_ecc) +{ + return 0; +} + +/* + * Function to calculate the ECC for the data to be stored in the Nand device. + * This NFC has a hardware RS(511,503) ECC engine together with the RS ECC + * CONTROL blocks are responsible for detection and correction of up to + * 8 symbols of 9 bits each in 528 byte page. + * So this function is just return 0. + */ + +static int mxc_nand_calculate_ecc(struct mtd_info *mtd, const u_char * dat, + u_char * ecc_code) +{ + return 0; +} + +/*! + * This function id is used to read the data buffer from the NAND Flash. To + * read the data from NAND Flash first the data output cycle is initiated by + * the NFC, which copies the data to RAMbuffer. This data of length \b len is + * then copied to buffer \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be read from NAND Flash + * @param len number of bytes to be read + */ +static void mxc_nand_read_buf(struct mtd_info *mtd, u_char * buf, int len) +{ + u16 col = g_nandfc_info.colAddr; + + if (mtd->writesize) { + + int j = mtd->writesize - col; + int n = mtd->oobsize + j; + + n = min(n, len); + + if (j > 0) { + if (n > j) { + memcpy(buf, &data_buf[col], j); + memcpy(buf + j, &oob_buf[0], n - j); + } else { + memcpy(buf, &data_buf[col], n); + } + } else { + col -= mtd->writesize; + memcpy(buf, &oob_buf[col], len); + } + + /* update */ + g_nandfc_info.colAddr += n; + + } else { + /* At flash identify phase, + * mtd->writesize has not been + * set correctly, it should + * be zero.And len will less 2 + */ + memcpy(buf, &data_buf[col], len); + + /* update */ + g_nandfc_info.colAddr += len; + } + +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static uint8_t mxc_nand_read_byte(struct mtd_info *mtd) +{ + uint8_t ret; + + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + mxc_nand_read_buf(mtd, &ret, 1); + + return ret; +} + +/*! + * This function reads word from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u16 mxc_nand_read_word(struct mtd_info *mtd) +{ + u16 ret; + + mxc_nand_read_buf(mtd, (uint8_t *) &ret, sizeof(u16)); + + return ret; +} + +/*! + * This function reads byte from the NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * + * @return data read from the NAND Flash + */ +static u_char mxc_nand_read_byte16(struct mtd_info *mtd) +{ + /* Check for status request */ + if (g_nandfc_info.bStatusRequest) { + return (get_dev_status() & 0xFF); + } + + return mxc_nand_read_word(mtd) & 0xFF; +} + +/*! + * This function writes data of length \b len from buffer \b buf to the NAND + * internal RAM buffer's MAIN area 0. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be written to NAND Flash + * @param len number of bytes to be written + */ +static void mxc_nand_write_buf(struct mtd_info *mtd, + const u_char * buf, int len) +{ + u16 col = g_nandfc_info.colAddr; + int j = mtd->writesize - col; + int n = mtd->oobsize + j; + + n = min(n, len); + + if (j > 0) { + if (n > j) { + memcpy(&data_buf[col], buf, j); + memcpy(&oob_buf[0], buf + j, n - j); + } else { + memcpy(&data_buf[col], buf, n); + } + } else { + col -= mtd->writesize; + memcpy(&oob_buf[col], buf, len); + } + + /* update */ + g_nandfc_info.colAddr += n; +} + +/*! + * This function is used by the upper layer to verify the data in NAND Flash + * with the data in the \b buf. + * + * @param mtd MTD structure for the NAND Flash + * @param buf data to be verified + * @param len length of the data to be verified + * + * @return -EFAULT if error else 0 + * + */ +static int mxc_nand_verify_buf(struct mtd_info *mtd, const u_char * buf, + int len) +{ + u_char *s = data_buf; + + const u_char *p = buf; + + for (; len > 0; len--) { + if (*p++ != *s++) + return -EFAULT; + } + + return 0; +} + +/*! + * This function is used by upper layer for select and deselect of the NAND + * chip + * + * @param mtd MTD structure for the NAND Flash + * @param chip val indicating select or deselect + */ +static void mxc_nand_select_chip(struct mtd_info *mtd, int chip) +{ + + switch (chip) { + case -1: + /* Disable the NFC clock */ + clk_disable(nfc_clk); + break; + case 0 ... 7: + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + NFC_SET_NFC_ACTIVE_CS(chip); + break; + + default: + break; + } +} + +/* + * Function to perform the address cycles. + */ +static void mxc_do_addr_cycle(struct mtd_info *mtd, int column, int page_addr) +{ +#ifdef NFC_AUTO_MODE_ENABLE + + if (page_addr != -1 && column != -1) { + u32 mask = 0xFFFF; + /* the column address */ + raw_write(column & mask, NFC_FLASH_ADDR0); + raw_write((raw_read(NFC_FLASH_ADDR0) | + ((page_addr & mask) << 16)), NFC_FLASH_ADDR0); + /* the row address */ + raw_write(((raw_read(NFC_FLASH_ADDR8) & (mask << 16)) | + ((page_addr & (mask << 16)) >> 16)), + NFC_FLASH_ADDR8); + } else if (page_addr != -1) { + raw_write(page_addr, NFC_FLASH_ADDR0); + raw_write(0, NFC_FLASH_ADDR8); + } + + DEBUG(MTD_DEBUG_LEVEL3, + "AutoMode:the ADDR REGS value is (0x%x, 0x%x)\n", + raw_read(NFC_FLASH_ADDR0), raw_read(NFC_FLASH_ADDR8)); +#else + + u32 page_mask = g_page_mask; + + if (column != -1) { + send_addr(column & 0xFF, false); + if (IS_2K_PAGE_NAND) { + /* another col addr cycle for 2k page */ + send_addr((column >> 8) & 0xF, false); + } else if (IS_4K_PAGE_NAND) { + /* another col addr cycle for 4k page */ + send_addr((column >> 8) & 0x1F, false); + } + } + if (page_addr != -1) { + do { + send_addr((page_addr & 0xff), false); + page_mask >>= 8; + page_addr >>= 8; + } while (page_mask != 0); + } +#endif +} + +/*! + * This function is used by the upper layer to write command to NAND Flash for + * different operations to be carried out on NAND Flash + * + * @param mtd MTD structure for the NAND Flash + * @param command command for NAND Flash + * @param column column offset for the page read + * @param page_addr page to be read from NAND Flash + */ +static void mxc_nand_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + bool useirq = false; + + DEBUG(MTD_DEBUG_LEVEL3, + "mxc_nand_command (cmd = 0x%x, col = 0x%x, page = 0x%x)\n", + command, column, page_addr); + /* + * Reset command state information + */ + g_nandfc_info.bStatusRequest = false; + + /* + * Command pre-processing step + */ + switch (command) { + case NAND_CMD_STATUS: + g_nandfc_info.colAddr = 0; + g_nandfc_info.bStatusRequest = true; + break; + + case NAND_CMD_READ0: + g_nandfc_info.colAddr = column; + break; + + case NAND_CMD_READOOB: + g_nandfc_info.colAddr = column; + command = NAND_CMD_READ0; + break; + + case NAND_CMD_SEQIN: + if (column != 0) { + + /* FIXME: before send SEQIN command for + * partial write,We need read one page out. + * FSL NFC does not support partial write + * It alway send out 512+ecc+512+ecc ... + * for large page nand flash. But for small + * page nand flash, it did support SPARE + * ONLY operation. But to make driver + * simple. We take the same as large page,read + * whole page out and update. As for MLC nand + * NOP(num of operation) = 1. Partial written + * on one programed page is not allowed! We + * can't limit it on the driver, it need the + * upper layer applicaiton take care it + */ + + mxc_nand_command(mtd, NAND_CMD_READ0, 0, page_addr); + } + + g_nandfc_info.colAddr = column; + column = 0; + + break; + + case NAND_CMD_PAGEPROG: +#ifndef NFC_AUTO_MODE_ENABLE + /* FIXME:the NFC interal buffer + * access has some limitation, it + * does not allow byte access. To + * make the code simple and ease use + * not every time check the address + * alignment.Use the temp buffer + * to accomadate the data.since We + * know data_buf will be at leat 4 + * byte alignment, so we can use + * memcpy safely + */ + nfc_memcpy(MAIN_AREA0, data_buf, mtd->writesize); + copy_spare(mtd, oob_buf, SPARE_AREA0, mtd->oobsize, false); +#endif + + if (IS_LARGE_PAGE_NAND) + PROG_PAGE(); + else + send_prog_page(0); + + useirq = true; + + break; + + case NAND_CMD_ERASE1: + break; + case NAND_CMD_ERASE2: + useirq = true; + + break; + } + + /* + * Write out the command to the device. + */ + send_cmd(mtd, command, useirq); + + mxc_do_addr_cycle(mtd, column, page_addr); + + /* + * Command post-processing step + */ + switch (command) { + + case NAND_CMD_READOOB: + case NAND_CMD_READ0: + if (IS_LARGE_PAGE_NAND) { + /* send read confirm command */ + send_cmd(mtd, NAND_CMD_READSTART, false); + /* read for each AREA */ + READ_PAGE(); + } else { + send_read_page(0); + } + +#ifndef NFC_AUTO_MODE_ENABLE + /* FIXME, the NFC interal buffer + * access has some limitation, it + * does not allow byte access. To + * make the code simple and ease use + * not every time check the address + * alignment.Use the temp buffer + * to accomadate the data.since We + * know data_buf will be at leat 4 + * byte alignment, so we can use + * memcpy safely + */ + nfc_memcpy(data_buf, MAIN_AREA0, mtd->writesize); + copy_spare(mtd, oob_buf, SPARE_AREA0, mtd->oobsize, true); +#endif + + break; + + case NAND_CMD_READID: + send_read_id(); + g_nandfc_info.colAddr = column; + nfc_memcpy(data_buf, MAIN_AREA0, 2048); + + break; + } +} + +static int mxc_nand_read_oob(struct mtd_info *mtd, + struct nand_chip *chip, int page, int sndcmd) +{ + if (sndcmd) { + + chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); + sndcmd = 0; + } + + memcpy(chip->oob_poi, oob_buf, mtd->oobsize); + + return sndcmd; +} + +static int mxc_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t * buf) +{ + +#ifndef NFC_AUTO_MODE_ENABLE + mxc_check_ecc_status(mtd); +#endif + + memcpy(buf, data_buf, mtd->writesize); + memcpy(chip->oob_poi, oob_buf, mtd->oobsize); + + return 0; +} + +static void mxc_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t * buf) +{ + memcpy(data_buf, buf, mtd->writesize); + memcpy(oob_buf, chip->oob_poi, mtd->oobsize); + +} + +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr smallpage_memorybased = { + .options = NAND_BBT_SCAN2NDPAGE, + .offs = 5, + .len = 1, + .pattern = scan_ff_pattern +}; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern +}; + +/* Generic flash bbt decriptors +*/ +static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +static int mxc_nand_scan_bbt(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd->priv; + + g_page_mask = this->pagemask; + + /* limit to 2G size due to Kernel + * larger 4G space support,need fix + * it later + */ + if (mtd->size == 0) { + mtd->size = 1 << 31; + this->numchips = 1; + this->chipsize = mtd->size; + } + + if (IS_2K_PAGE_NAND) { + NFC_SET_NFMS(1 << NFMS_NF_PG_SZ); + this->ecc.layout = &nand_hw_eccoob_2k; + } else if (IS_4K_PAGE_NAND) { + NFC_SET_NFMS(1 << NFMS_NF_PG_SZ); + this->ecc.layout = &nand_hw_eccoob_4k; + } else { + this->ecc.layout = &nand_hw_eccoob_512; + } + + /* propagate ecc.layout to mtd_info */ + mtd->ecclayout = this->ecc.layout; + + /* jffs2 not write oob */ + mtd->flags &= ~MTD_OOB_WRITEABLE; + + /* use flash based bbt */ + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + + /* update flash based bbt */ + this->options |= NAND_USE_FLASH_BBT; + + if (!this->badblock_pattern) { + this->badblock_pattern = (mtd->writesize > 512) ? + &largepage_memorybased : &smallpage_memorybased; + } + + /* Build bad block table */ + return nand_scan_bbt(mtd, this->badblock_pattern); +} + +static void mxc_nfc_init(void) +{ + /* Disable interrupt */ + raw_write((raw_read(REG_NFC_INTRRUPT) | NFC_INT_MSK), REG_NFC_INTRRUPT); + + /* disable spare enable */ + raw_write(raw_read(REG_NFC_SP_EN) & ~NFC_SP_EN, REG_NFC_SP_EN); + + /* Unlock the internal RAM Buffer */ + raw_write(NFC_SET_BLS(NFC_BLS_UNLCOKED), REG_NFC_BLS); + + /* Blocks to be unlocked */ + UNLOCK_ADDR(0x0, 0xFFFF); + + /* Unlock Block Command for given address range */ + raw_write(NFC_SET_WPC(NFC_WPC_UNLOCK), REG_NFC_WPC); + + /* Enable symetric mode by default except mx37TO1.0 */ + if (!(cpu_is_mx37_rev(CHIP_REV_1_0) == 1)) + raw_write(raw_read(REG_NFC_ONE_CYCLE) | + NFC_ONE_CYCLE, REG_NFC_ONE_CYCLE); +} + +static int mxc_alloc_buf(void) +{ + int err = 0; + + data_buf = kzalloc(NAND_MAX_PAGESIZE, GFP_KERNEL); + if (!data_buf) { + printk(KERN_ERR "%s: failed to allocate data_buf\n", __func__); + err = -ENOMEM; + goto out; + } + oob_buf = kzalloc(NAND_MAX_OOBSIZE, GFP_KERNEL); + if (!oob_buf) { + printk(KERN_ERR "%s: failed to allocate oob_buf\n", __func__); + err = -ENOMEM; + goto out; + } + + out: + return err; +} + +static void mxc_free_buf(void) +{ + kfree(data_buf); + kfree(oob_buf); +} + +/*! + * This function is called during the driver binding process. + * + * @param pdev the device structure used to store device specific + * information that is used by the suspend, resume and + * remove functions + * + * @return The function always returns 0. + */ +static int __init mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct flash_platform_data *flash = pdev->dev.platform_data; + int nr_parts = 0, err = 0; + + nfc_axi_base = IO_ADDRESS(NFC_AXI_BASE_ADDR); + nfc_ip_base = IO_ADDRESS(NFC_BASE_ADDR); + + /* init the nfc */ + mxc_nfc_init(); + + /* init data buf */ + if (mxc_alloc_buf()) + goto out; + + /* Allocate memory for MTD device structure and private data */ + mxc_nand_data = kzalloc(sizeof(struct mxc_mtd_s), GFP_KERNEL); + if (!mxc_nand_data) { + printk(KERN_ERR "%s: failed to allocate mtd_info\n", + __FUNCTION__); + err = -ENOMEM; + goto out; + } + + memset((char *)&g_nandfc_info, 0, sizeof(g_nandfc_info)); + + mxc_nand_data->dev = &pdev->dev; + /* structures must be linked */ + this = &mxc_nand_data->nand; + mtd = &mxc_nand_data->mtd; + mtd->priv = this; + mtd->owner = THIS_MODULE; + + this->priv = mxc_nand_data; + this->cmdfunc = mxc_nand_command; + this->select_chip = mxc_nand_select_chip; + this->read_byte = mxc_nand_read_byte; + this->read_word = mxc_nand_read_word; + this->write_buf = mxc_nand_write_buf; + this->read_buf = mxc_nand_read_buf; + this->verify_buf = mxc_nand_verify_buf; + this->scan_bbt = mxc_nand_scan_bbt; + + /* NAND bus width determines access funtions used by upper layer */ + if (flash->width == 2) { + this->read_byte = mxc_nand_read_byte16; + this->options |= NAND_BUSWIDTH_16; + NFC_SET_NFMS(1 << NFMS_NF_DWIDTH); + } else { + NFC_SET_NFMS(0); + } + + nfc_clk = clk_get(&pdev->dev, "nfc_clk"); + clk_enable(nfc_clk); + + init_waitqueue_head(&irq_waitq); + err = request_irq(MXC_INT_NANDFC, mxc_nfc_irq, 0, "mxc_nd", NULL); + if (err) { + goto out_1; + } + + if (hardware_ecc) { + this->ecc.read_page = mxc_nand_read_page; + this->ecc.write_page = mxc_nand_write_page; + this->ecc.read_oob = mxc_nand_read_oob; + this->ecc.layout = &nand_hw_eccoob_512; + this->ecc.calculate = mxc_nand_calculate_ecc; + this->ecc.hwctl = mxc_nand_enable_hwecc; + this->ecc.correct = mxc_nand_correct_data; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; + this->ecc.bytes = 9; + raw_write((raw_read(REG_NFC_ECC_EN) | NFC_ECC_EN), + REG_NFC_ECC_EN); + } else { + this->ecc.mode = NAND_ECC_SOFT; + raw_write((raw_read(REG_NFC_ECC_EN) & ~NFC_ECC_EN), + REG_NFC_ECC_EN); + } + + /* config the gpio */ + if (flash->init) + flash->init(); + + /* Reset NAND */ + this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + + /* Scan to find existence of the device */ + if (nand_scan(mtd, NFC_GET_MAXCHIP_SP())) { + DEBUG(MTD_DEBUG_LEVEL0, + "MXC_ND2: Unable to find any NAND device.\n"); + err = -ENXIO; + goto out_1; + } + + /* Register the partitions */ +#ifdef CONFIG_MTD_PARTITIONS + nr_parts = + parse_mtd_partitions(mtd, part_probes, &mxc_nand_data->parts, 0); + if (nr_parts > 0) + add_mtd_partitions(mtd, mxc_nand_data->parts, nr_parts); + else if (flash->parts) + add_mtd_partitions(mtd, flash->parts, flash->nr_parts); + else +#endif + { + pr_info("Registering %s as whole device\n", mtd->name); + add_mtd_device(mtd); + } + + platform_set_drvdata(pdev, mtd); + + return 0; + + out_1: + kfree(mxc_nand_data); + out: + return err; + +} + + /*! + * Dissociates the driver from the device. + * + * @param pdev the device structure used to give information on which + * + * @return The function always returns 0. + */ + +static int __exit mxcnd_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct flash_platform_data *flash = pdev->dev.platform_data; + + if (flash->exit) + flash->exit(); + + mxc_free_buf(); + + clk_disable(nfc_clk); + clk_put(nfc_clk); + platform_set_drvdata(pdev, NULL); + + if (mxc_nand_data) { + nand_release(mtd); + free_irq(MXC_INT_NANDFC, NULL); + kfree(mxc_nand_data); + } + + return 0; +} + +#ifdef CONFIG_PM +/*! + * This function is called to put the NAND in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure + */ + +static int mxcnd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND2 : NAND suspend\n"); + if (info) + ret = info->suspend(info); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + /* Disable the NFC clock */ + clk_disable(nfc_clk); + + return ret; +} + +/*! + * This function is called to bring the NAND back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device information structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxcnd_resume(struct platform_device *pdev) +{ + struct mtd_info *info = platform_get_drvdata(pdev); + int ret = 0; + + DEBUG(MTD_DEBUG_LEVEL0, "MXC_ND2 : NAND resume\n"); + /* Enable the NFC clock */ + clk_enable(nfc_clk); + + if (info) { + info->resume(info); + } + + return ret; +} + +#else +#define mxcnd_suspend NULL +#define mxcnd_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcnd_driver = { + .driver = { + .name = "mxc_nandv2_flash", + }, + .probe = mxcnd_probe, + .remove = __exit_p(mxcnd_remove), + .suspend = mxcnd_suspend, + .resume = mxcnd_resume, +}; + +/*! + * Main initialization routine + * @return 0 if successful; non-zero otherwise + */ +static int __init mxc_nd_init(void) +{ + /* Register the device driver structure. */ + pr_info("MXC MTD nand Driver %s\n", DVR_VER); + if (platform_driver_register(&mxcnd_driver) != 0) { + printk(KERN_ERR "Driver register failed for mxcnd_driver\n"); + return -ENODEV; + } + return 0; +} + +/*! + * Clean up routine + */ +static void __exit mxc_nd_cleanup(void) +{ + /* Unregister the device structure */ + platform_driver_unregister(&mxcnd_driver); +} + +module_init(mxc_nd_init); +module_exit(mxc_nd_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver Version 2-5"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/mxc_nd2.h b/drivers/mtd/nand/mxc_nd2.h new file mode 100644 index 000000000000..9cb92100b5a4 --- /dev/null +++ b/drivers/mtd/nand/mxc_nd2.h @@ -0,0 +1,679 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mxc_nd2.h + * + * @brief This file contains the NAND Flash Controller register information. + * + * + * @ingroup NAND_MTD + */ + +#ifndef __MXC_ND2_H__ +#define __MXC_ND2_H__ + +#include <mach/hardware.h> + +#define IS_2K_PAGE_NAND ((mtd->writesize / num_of_interleave) \ + == NAND_PAGESIZE_2KB) +#define IS_4K_PAGE_NAND ((mtd->writesize / num_of_interleave) \ + == NAND_PAGESIZE_4KB) +#define IS_LARGE_PAGE_NAND ((mtd->writesize / num_of_interleave) > 512) + +#define GET_NAND_OOB_SIZE (mtd->oobsize / num_of_interleave) + +#define NAND_PAGESIZE_2KB 2048 +#define NAND_PAGESIZE_4KB 4096 + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3 +/* + * For V3 NFC registers Definition + */ +/* AXI Bus Mapped */ +#define NFC_AXI_BASE_ADDR NFC_BASE_ADDR_AXI + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) /* mx37 */ +#define MXC_INT_NANDFC MXC_INT_EMI +#define NFC_FLASH_ADDR_CMD (nfc_axi_base + 0x1E00) +#define NFC_CONFIG1 (nfc_axi_base + 0x1E04) +#define NFC_ECC_STATUS_RESULT (nfc_axi_base + 0x1E08) +#define LAUNCH_NFC (nfc_axi_base + 0x1E0c) +#define NFC_WRPROT (nfc_ip_base + 0x00) +#define NFC_WRPROT_UNLOCK_BLK_ADD0 (nfc_ip_base + 0x04) +#define NFC_CONFIG2 (nfc_ip_base + 0x14) +#define NFC_IPC (nfc_ip_base + 0x18) +#elif defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) /* mx51 */ +#define MXC_INT_NANDFC MXC_INT_NFC +#define NFC_AUTO_MODE_ENABLE +#define NFC_FLASH_CMD (nfc_axi_base + 0x1E00) +#define NFC_FLASH_ADDR0 (nfc_axi_base + 0x1E04) +#define NFC_FLASH_ADDR8 (nfc_axi_base + 0x1E24) +#define NFC_CONFIG1 (nfc_axi_base + 0x1E34) +#define NFC_ECC_STATUS_RESULT (nfc_axi_base + 0x1E38) +#define NFC_ECC_STATUS_SUM (nfc_axi_base + 0x1E3C) +#define LAUNCH_NFC (nfc_axi_base + 0x1E40) +#define NFC_WRPROT (nfc_ip_base + 0x00) +#define NFC_WRPROT_UNLOCK_BLK_ADD0 (nfc_ip_base + 0x04) +#define NFC_CONFIG2 (nfc_ip_base + 0x24) +#define NFC_CONFIG3 (nfc_ip_base + 0x28) +#define NFC_IPC (nfc_ip_base + 0x2C) +#define NFC_DELAY_LINE (nfc_ip_base + 0x34) +#else /* skye */ +#define NFC_FLASH_ADDR_CMD (nfc_axi_base + 0xE00) +#define NFC_CONFIG1 (nfc_axi_base + 0xE04) +#define NFC_ECC_STATUS_RESULT (nfc_axi_base + 0xE08) +#define LAUNCH_NFC (nfc_axi_base + 0xE0C) +#define NFC_WRPROT (nfc_ip_base + 0x00) +#define NFC_WRPROT_UNLOCK_BLK_ADD0 (nfc_ip_base + 0x04) +#define NFC_CONFIG2 (nfc_ip_base + 0x14) +#define NFC_IPC (nfc_ip_base + 0x18) +#endif +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 ((u16 *)(nfc_axi_base + 0x000)) +#define MAIN_AREA1 ((u16 *)(nfc_axi_base + 0x200)) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define SPARE_AREA0 ((u16 *)(nfc_axi_base + 0x1000)) +#define SPARE_LEN 64 +#define SPARE_COUNT 8 +#define SPARE_SIZE (SPARE_LEN * SPARE_COUNT) +#else +#define SPARE_AREA0 ((u16 *)(nfc_axi_base + 0x800)) +#define SPARE_LEN 16 +#define SPARE_COUNT 4 +#define SPARE_SIZE (SPARE_LEN * SPARE_COUNT) +#endif + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define NFC_SPAS_WIDTH 8 +#define NFC_SPAS_SHIFT 16 + +#define IS_4BIT_ECC \ +( \ + cpu_is_mx51_rev(CHIP_REV_2_0) > 0 ? \ + !((raw_read(NFC_CONFIG2) & NFC_ECC_MODE_4) >> 6) : \ + ((raw_read(NFC_CONFIG2) & NFC_ECC_MODE_4) >> 6) \ +) + +#define NFC_SET_SPAS(v) \ + raw_write((((raw_read(NFC_CONFIG2) & \ + NFC_FIELD_RESET(NFC_SPAS_WIDTH, NFC_SPAS_SHIFT)) | ((v) << 16))), \ + NFC_CONFIG2) + +#define NFC_SET_ECC_MODE(v) \ +do { \ + if (cpu_is_mx51_rev(CHIP_REV_2_0) > 0) { \ + if ((v) == NFC_SPAS_218 || (v) == NFC_SPAS_112) \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) | \ + NFC_ECC_MODE_4), NFC_CONFIG2); \ + else \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) & \ + NFC_ECC_MODE_8), NFC_CONFIG2); \ + } else { \ + if ((v) == NFC_SPAS_218 || (v) == NFC_SPAS_112) \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) & \ + NFC_ECC_MODE_8), NFC_CONFIG2); \ + else \ + raw_write(((raw_read(NFC_CONFIG2) & \ + NFC_ECC_MODE_MASK) | \ + NFC_ECC_MODE_4), NFC_CONFIG2); \ + } \ +} while (0) + +#define WRITE_NFC_IP_REG(val,reg) \ + do { \ + raw_write(NFC_IPC_CREQ, NFC_IPC); \ + while (!((raw_read(NFC_IPC) & NFC_IPC_ACK)>>1));\ + raw_write(val, reg); \ + raw_write(0, NFC_IPC); \ + } while(0) + +#else +#define IS_4BIT_ECC 1 +#define NFC_SET_SPAS(v) +#define NFC_SET_ECC_MODE(v) +#define NFC_SET_NFMS(v) (NFMS |= (v)) + +#define WRITE_NFC_IP_REG(val,reg) \ + raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), \ + REG_NFC_OPS_STAT) +#endif + +#define GET_NFC_ECC_STATUS() raw_read(REG_NFC_ECC_STATUS_RESULT); + +/*! + * Set 1 to specific operation bit, rest to 0 in LAUNCH_NFC Register for + * Specific operation + */ +#define NFC_CMD 0x1 +#define NFC_ADDR 0x2 +#define NFC_INPUT 0x4 +#define NFC_OUTPUT 0x8 +#define NFC_ID 0x10 +#define NFC_STATUS 0x20 + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 /* mx51 */ +#define NFC_AUTO_PROG 0x40 +#define NFC_AUTO_READ 0x80 +#define NFC_AUTO_ERASE 0x200 +#define NFC_COPY_BACK_0 0x400 +#define NFC_COPY_BACK_1 0x800 +#define NFC_AUTO_STATE 0x1000 +#endif + +/* Bit Definitions for NFC_IPC*/ +#define NFC_OPS_STAT (1 << 31) + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 /* mx51 */ +#define NFC_OP_DONE (1 << 30) +#define NFC_RB (1 << 28) +#define NFC_PS_WIDTH 2 +#define NFC_PS_SHIFT 0 +#define NFC_PS_512 0 +#define NFC_PS_2K 1 +#define NFC_PS_4K 2 +#else +#define NFC_RB (1 << 29) +#endif + +#define NFC_ONE_CYCLE (1 << 2) + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 /* mx51 */ +#define NFC_INT_MSK (1 << 15) +#define NFC_AUTO_PROG_DONE_MSK (1 << 14) +#define NFC_NUM_ADDR_PHASE1_WIDTH 2 +#define NFC_NUM_ADDR_PHASE1_SHIFT 12 + +#define NFC_NUM_ADDR_PHASE0_WIDTH 1 +#define NFC_NUM_ADDR_PHASE0_SHIFT 5 + +#define NFC_ONE_LESS_PHASE1 0 +#define NFC_TWO_LESS_PHASE1 1 + +#define NFC_FLASH_ADDR_SHIFT 0 +#else +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_FLASH_ADDR_SHIFT 16 +#endif + +#define NFC_UNLOCK_END_ADDR_SHIFT 16 + +/* Bit definition for NFC_CONFIGRATION_1 */ +#define NFC_SP_EN (1 << 0) +#define NFC_CE (1 << 1) +#define NFC_RST (1 << 2) +#define NFC_ECC_EN (1 << 3) + +#define NFC_FIELD_RESET(width, shift) ~(((1 << (width)) - 1) << (shift)) + +#define NFC_RBA_SHIFT 4 + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) /* mx51 */ +#define NFC_RBA_WIDTH 3 +#else +#define NFC_RBA_WIDTH 2 +#endif + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) /* mx51 */ +#define NFC_ITERATION_SHIFT 8 +#define NFC_ITERATION_WIDTH 4 +#define NFC_ACTIVE_CS_SHIFT 12 +#define NFC_ACTIVE_CS_WIDTH 3 +/* bit definition for CONFIGRATION3 */ +#define NFC_NO_SDMA (1 << 20) +#define NFC_FMP_SHIFT 16 +#define NFC_FMP_WIDTH 4 +#define NFC_RBB_MODE (1 << 15) +#define NFC_NUM_OF_DEVICES_SHIFT 12 +#define NFC_NUM_OF_DEVICES_WIDTH 4 +#define NFC_DMA_MODE_SHIFT 11 +#define NFC_DMA_MODE_WIDTH 1 +#define NFC_SBB_SHIFT 8 +#define NFC_SBB_WIDTH 3 +#define NFC_BIG (1 << 7) +#define NFC_SB2R_SHIFT 4 +#define NFC_SB2R_WIDTH 3 +#define NFC_FW_SHIFT 3 +#define NFC_FW_WIDTH 1 +#define NFC_TOO (1 << 2) +#define NFC_ADD_OP_SHIFT 0 +#define NFC_ADD_OP_WIDTH 2 +#define NFC_FW_8 1 +#define NFC_FW_16 0 +#define NFC_ST_CMD_SHITF 24 +#define NFC_ST_CMD_WIDTH 8 +#endif + +#define NFC_PPB_32 (0 << 7) +#define NFC_PPB_64 (1 << 7) +#define NFC_PPB_128 (2 << 7) +#define NFC_PPB_256 (3 << 7) +#define NFC_PPB_RESET ~(3 << 7) + +#define NFC_BLS_LOCKED (0 << 16) +#define NFC_BLS_LOCKED_DEFAULT (1 << 16) +#define NFC_BLS_UNLCOKED (2 << 16) +#define NFC_BLS_RESET ~(3 << 16) +#define NFC_WPC_LOCK_TIGHT 1 +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) +#define NFC_WPC_RESET ~(7) +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_1) || \ + defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define NFC_ECC_MODE_4 (1 << 6) +#define NFC_ECC_MODE_8 ~(1 << 6) +#define NFC_ECC_MODE_MASK ~(1 << 6) +#define NFC_SPAS_16 8 +#define NFC_SPAS_64 32 +#define NFC_SPAS_128 64 +#define NFC_SPAS_112 56 +#define NFC_SPAS_218 109 +#define NFC_IPC_CREQ (1 << 0) +#define NFC_IPC_ACK (1 << 1) +#endif + +#define REG_NFC_OPS_STAT NFC_IPC +#define REG_NFC_INTRRUPT NFC_CONFIG2 +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR0 +#define REG_NFC_FLASH_CMD NFC_FLASH_CMD +#else +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR_CMD +#define REG_NFC_FLASH_CMD NFC_FLASH_ADDR_CMD +#endif +#define REG_NFC_OPS LAUNCH_NFC +#define REG_NFC_SET_RBA NFC_CONFIG1 +#define REG_NFC_RB NFC_IPC +#define REG_NFC_ECC_EN NFC_CONFIG2 +#define REG_NFC_ECC_STATUS_RESULT NFC_ECC_STATUS_RESULT +#define REG_NFC_CE NFC_CONFIG1 +#define REG_NFC_RST NFC_CONFIG1 +#define REG_NFC_PPB NFC_CONFIG2 +#define REG_NFC_SP_EN NFC_CONFIG1 +#define REG_NFC_BLS NFC_WRPROT +#define REG_UNLOCK_BLK_ADD0 NFC_WRPROT_UNLOCK_BLK_ADD0 +#define REG_UNLOCK_BLK_ADD1 NFC_WRPROT_UNLOCK_BLK_ADD1 +#define REG_UNLOCK_BLK_ADD2 NFC_WRPROT_UNLOCK_BLK_ADD2 +#define REG_UNLOCK_BLK_ADD3 NFC_WRPROT_UNLOCK_BLK_ADD3 +#define REG_NFC_WPC NFC_WRPROT +#define REG_NFC_ONE_CYCLE NFC_CONFIG2 + +/* NFC V3 Specific MACRO functions definitions */ +#define raw_write(v,a) __raw_writel(v,a) +#define raw_read(a) __raw_readl(a) + +/* Explcit ack ops status (if any), before issue of any command */ +#define ACK_OPS \ + raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), \ + REG_NFC_OPS_STAT); + +/* Set RBA buffer id*/ +#define NFC_SET_RBA(val) \ + raw_write((raw_read(REG_NFC_SET_RBA) & \ + (NFC_FIELD_RESET(NFC_RBA_WIDTH, NFC_RBA_SHIFT))) | \ + ((val) << NFC_RBA_SHIFT), REG_NFC_SET_RBA); + +#define NFC_SET_PS(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_PS_WIDTH, NFC_PS_SHIFT))) | \ + ((val) << NFC_PS_SHIFT), NFC_CONFIG2); + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_2 +#define UNLOCK_ADDR(start_addr,end_addr) \ +{ \ + int i = 0; \ + for (; i < NAND_MAX_CHIPS; i++) \ + raw_write(start_addr | \ + (end_addr << NFC_UNLOCK_END_ADDR_SHIFT), \ + REG_UNLOCK_BLK_ADD0 + (i << 2)); \ +} +#define NFC_SET_NFC_ACTIVE_CS(val) \ + raw_write((raw_read(NFC_CONFIG1) & \ + (NFC_FIELD_RESET(NFC_ACTIVE_CS_WIDTH, NFC_ACTIVE_CS_SHIFT))) | \ + ((val) << NFC_ACTIVE_CS_SHIFT), NFC_CONFIG1); + +#define NFC_GET_MAXCHIP_SP() 8 + +#else +#define UNLOCK_ADDR(start_addr,end_addr) \ + raw_write(start_addr | \ + (end_addr << NFC_UNLOCK_END_ADDR_SHIFT), REG_UNLOCK_BLK_ADD0); + +#define NFC_SET_NFC_ACTIVE_CS(val) +#define NFC_GET_MAXCHIP_SP() 1 +#endif + +#define NFC_SET_BLS(val) ((raw_read(REG_NFC_BLS) & NFC_BLS_RESET) | val ) +#define NFC_SET_WPC(val) ((raw_read(REG_NFC_WPC) & NFC_WPC_RESET) | val ) +#define CHECK_NFC_RB raw_read(REG_NFC_RB) & NFC_RB + +#if defined(CONFIG_ARCH_MXC_HAS_NFC_V3_2) +#define NFC_SET_NFC_NUM_ADDR_PHASE1(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_NUM_ADDR_PHASE1_WIDTH, \ + NFC_NUM_ADDR_PHASE1_SHIFT))) | \ + ((val) << NFC_NUM_ADDR_PHASE1_SHIFT), NFC_CONFIG2); + +#define NFC_SET_NFC_NUM_ADDR_PHASE0(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_NUM_ADDR_PHASE0_WIDTH, \ + NFC_NUM_ADDR_PHASE0_SHIFT))) | \ + ((val) << NFC_NUM_ADDR_PHASE0_SHIFT), NFC_CONFIG2); + +#define NFC_SET_NFC_ITERATION(val) \ + raw_write((raw_read(NFC_CONFIG1) & \ + (NFC_FIELD_RESET(NFC_ITERATION_WIDTH, NFC_ITERATION_SHIFT))) | \ + ((val) << NFC_ITERATION_SHIFT), NFC_CONFIG1); + +#define NFC_SET_FW(val) \ + raw_write((raw_read(NFC_CONFIG3) & \ + (NFC_FIELD_RESET(NFC_FW_WIDTH, NFC_FW_SHIFT))) | \ + ((val) << NFC_FW_SHIFT), NFC_CONFIG3); + +#define NFC_SET_NUM_OF_DEVICE(val) \ + raw_write((raw_read(NFC_CONFIG3) & \ + (NFC_FIELD_RESET(NFC_NUM_OF_DEVICES_WIDTH, \ + NFC_NUM_OF_DEVICES_SHIFT))) | \ + ((val) << NFC_NUM_OF_DEVICES_SHIFT), NFC_CONFIG3); + +#define NFC_SET_ADD_OP_MODE(val) \ + raw_write((raw_read(NFC_CONFIG3) & \ + (NFC_FIELD_RESET(NFC_ADD_OP_WIDTH, NFC_ADD_OP_SHIFT))) | \ + ((val) << NFC_ADD_OP_SHIFT), NFC_CONFIG3); + +#define NFC_SET_ADD_CS_MODE(val) \ +{ \ + NFC_SET_ADD_OP_MODE(val); \ + NFC_SET_NUM_OF_DEVICE(this->numchips - 1); \ +} + +#define NFC_SET_ST_CMD(val) \ + raw_write((raw_read(NFC_CONFIG2) & \ + (NFC_FIELD_RESET(NFC_ST_CMD_WIDTH, \ + NFC_ST_CMD_SHITF))) | \ + ((val) << NFC_ST_CMD_SHITF), NFC_CONFIG2); + +#define NFMS_NF_DWIDTH 0 +#define NFMS_NF_PG_SZ 1 +#define NFC_CMD_1_SHIFT 8 + +#define NUM_OF_ADDR_CYCLE (fls(g_page_mask) >> 3) +#define SET_NFC_DELAY_LINE(val) raw_write((val), NFC_DELAY_LINE) + +/*should set the fw,ps,spas,ppb*/ +#define NFC_SET_NFMS(v) \ +do { \ + if (!(v)) \ + NFC_SET_FW(NFC_FW_8); \ + if (((v) & (1 << NFMS_NF_DWIDTH))) \ + NFC_SET_FW(NFC_FW_16); \ + if (((v) & (1 << NFMS_NF_PG_SZ))) { \ + if (IS_2K_PAGE_NAND) { \ + NFC_SET_PS(NFC_PS_2K); \ + NFC_SET_NFC_NUM_ADDR_PHASE1(NUM_OF_ADDR_CYCLE); \ + NFC_SET_NFC_NUM_ADDR_PHASE0(NFC_TWO_LESS_PHASE1); \ + } else if (IS_4K_PAGE_NAND) { \ + NFC_SET_PS(NFC_PS_4K); \ + NFC_SET_NFC_NUM_ADDR_PHASE1(NUM_OF_ADDR_CYCLE); \ + NFC_SET_NFC_NUM_ADDR_PHASE0(NFC_TWO_LESS_PHASE1); \ + } else { \ + NFC_SET_PS(NFC_PS_512); \ + NFC_SET_NFC_NUM_ADDR_PHASE1(NUM_OF_ADDR_CYCLE - 1); \ + NFC_SET_NFC_NUM_ADDR_PHASE0(NFC_ONE_LESS_PHASE1); \ + } \ + NFC_SET_ADD_CS_MODE(1); \ + NFC_SET_SPAS(GET_NAND_OOB_SIZE >> 1); \ + NFC_SET_ECC_MODE(GET_NAND_OOB_SIZE >> 1); \ + NFC_SET_ST_CMD(0x70); \ + raw_write(raw_read(NFC_CONFIG3) | 1 << 20, NFC_CONFIG3); \ + SET_NFC_DELAY_LINE(0); \ + } \ +} while (0) +#endif + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V3_1 +#define NFC_SET_NFMS(v) +#endif + +#define READ_PAGE() send_read_page(0) +#define PROG_PAGE() send_prog_page(0) + +#elif CONFIG_ARCH_MXC_HAS_NFC_V2 + +/* + * For V1/V2 NFC registers Definition + */ + +#define NFC_AXI_BASE_ADDR 0x00 +/* + * Addresses for NFC registers + */ +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_REG_BASE (nfc_ip_base + 0x1000) +#else +#define NFC_REG_BASE nfc_ip_base +#endif +#define NFC_BUF_SIZE (NFC_REG_BASE + 0xE00) +#define NFC_BUF_ADDR (NFC_REG_BASE + 0xE04) +#define NFC_FLASH_ADDR (NFC_REG_BASE + 0xE06) +#define NFC_FLASH_CMD (NFC_REG_BASE + 0xE08) +#define NFC_CONFIG (NFC_REG_BASE + 0xE0A) +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_ECC_STATUS_RESULT (NFC_REG_BASE + 0xE0C) +#define NFC_ECC_STATUS_RESULT_1 (NFC_REG_BASE + 0xE0C) +#define NFC_ECC_STATUS_RESULT_2 (NFC_REG_BASE + 0xE0E) +#define NFC_SPAS (NFC_REG_BASE + 0xE10) +#else +#define NFC_ECC_STATUS_RESULT (NFC_REG_BASE + 0xE0C) +#define NFC_RSLTMAIN_AREA (NFC_REG_BASE + 0xE0E) +#define NFC_RSLTSPARE_AREA (NFC_REG_BASE + 0xE10) +#endif +#define NFC_WRPROT (NFC_REG_BASE + 0xE12) +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_UNLOCKSTART_BLKADDR (NFC_REG_BASE + 0xE20) +#define NFC_UNLOCKEND_BLKADDR (NFC_REG_BASE + 0xE22) +#define NFC_UNLOCKSTART_BLKADDR1 (NFC_REG_BASE + 0xE24) +#define NFC_UNLOCKEND_BLKADDR1 (NFC_REG_BASE + 0xE26) +#define NFC_UNLOCKSTART_BLKADDR2 (NFC_REG_BASE + 0xE28) +#define NFC_UNLOCKEND_BLKADDR2 (NFC_REG_BASE + 0xE2A) +#define NFC_UNLOCKSTART_BLKADDR3 (NFC_REG_BASE + 0xE2C) +#define NFC_UNLOCKEND_BLKADDR3 (NFC_REG_BASE + 0xE2E) +#else +#define NFC_UNLOCKSTART_BLKADDR (NFC_REG_BASE + 0xE14) +#define NFC_UNLOCKEND_BLKADDR (NFC_REG_BASE + 0xE16) +#endif +#define NFC_NF_WRPRST (NFC_REG_BASE + 0xE18) +#define NFC_CONFIG1 (NFC_REG_BASE + 0xE1A) +#define NFC_CONFIG2 (NFC_REG_BASE + 0xE1C) + +/*! + * Addresses for NFC RAM BUFFER Main area 0 + */ +#define MAIN_AREA0 (u16 *)(nfc_ip_base + 0x000) +#define MAIN_AREA1 (u16 *)(nfc_ip_base + 0x200) + +/*! + * Addresses for NFC SPARE BUFFER Spare area 0 + */ +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define SPARE_AREA0 (u16 *)(nfc_ip_base + 0x1000) +#define SPARE_LEN 64 +#define SPARE_COUNT 8 +#else +#define SPARE_AREA0 (u16 *)(nfc_ip_base + 0x800) +#define SPARE_LEN 16 +#define SPARE_COUNT 4 +#endif +#define SPARE_SIZE (SPARE_LEN * SPARE_COUNT) + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define REG_NFC_ECC_MODE NFC_CONFIG1 +#define SPAS_SHIFT (0) +#define REG_NFC_SPAS NFC_SPAS +#define SPAS_MASK (0xFF00) +#define IS_4BIT_ECC \ + ((raw_read(REG_NFC_ECC_MODE) & NFC_ECC_MODE_4) >> 0) + +#define NFC_SET_SPAS(v) \ + raw_write(((raw_read(REG_NFC_SPAS) & SPAS_MASK) | ((v<<SPAS_SHIFT))), \ + REG_NFC_SPAS) + +#define NFC_SET_ECC_MODE(v) \ +do { \ + if ((v) == NFC_SPAS_218 || (v) == NFC_SPAS_112) { \ + raw_write((raw_read(REG_NFC_ECC_MODE) & NFC_ECC_MODE_8), \ + REG_NFC_ECC_MODE); \ + } else { \ + raw_write((raw_read(REG_NFC_ECC_MODE) | NFC_ECC_MODE_4), \ + REG_NFC_ECC_MODE); \ + } \ +} while (0) + +#define GET_ECC_STATUS() __raw_readl(REG_NFC_ECC_STATUS_RESULT); +#define NFC_SET_NFMS(v) \ +do { \ + (NFMS |= (v)); \ + if (((v) & (1 << NFMS_NF_PG_SZ))) { \ + NFC_SET_SPAS(GET_NAND_OOB_SIZE >> 1); \ + NFC_SET_ECC_MODE(GET_NAND_OOB_SIZE >> 1); \ + } \ +} while (0) +#else +#define IS_4BIT_ECC (1) +#define NFC_SET_SPAS(v) +#define NFC_SET_ECC_MODE(v) +#define GET_ECC_STATUS() raw_read(REG_NFC_ECC_STATUS_RESULT); +#define NFC_SET_NFMS(v) (NFMS |= (v)) +#endif + +#define WRITE_NFC_IP_REG(val,reg) \ + raw_write((raw_read(REG_NFC_OPS_STAT) & ~NFC_OPS_STAT), \ + REG_NFC_OPS_STAT) + +#define GET_NFC_ECC_STATUS() raw_read(REG_NFC_ECC_STATUS_RESULT); + +/*! + * Set INT to 0, Set 1 to specific operation bit, rest to 0 in LAUNCH_NFC Register for + * Specific operation + */ +#define NFC_CMD 0x1 +#define NFC_ADDR 0x2 +#define NFC_INPUT 0x4 +#define NFC_OUTPUT 0x8 +#define NFC_ID 0x10 +#define NFC_STATUS 0x20 + +/* Bit Definitions */ +#define NFC_OPS_STAT (1 << 15) +#define NFC_SP_EN (1 << 2) +#define NFC_ECC_EN (1 << 3) +#define NFC_INT_MSK (1 << 4) +#define NFC_BIG (1 << 5) +#define NFC_RST (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) +#define NFC_BLS_LOCKED 0 +#define NFC_BLS_LOCKED_DEFAULT 1 +#define NFC_BLS_UNLCOKED 2 +#define NFC_WPC_LOCK_TIGHT 1 +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) +#define NFC_FLASH_ADDR_SHIFT 0 +#define NFC_UNLOCK_END_ADDR_SHIFT 0 + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define NFC_ECC_MODE_4 (1<<0) +#define NFC_ECC_MODE_8 ~(1<<0) +#define NFC_SPAS_16 8 +#define NFC_SPAS_64 32 +#define NFC_SPAS_112 56 +#define NFC_SPAS_128 64 +#define NFC_SPAS_218 109 +#endif +/* NFC Register Mapping */ +#define REG_NFC_OPS_STAT NFC_CONFIG2 +#define REG_NFC_INTRRUPT NFC_CONFIG1 +#define REG_NFC_FLASH_ADDR NFC_FLASH_ADDR +#define REG_NFC_FLASH_CMD NFC_FLASH_CMD +#define REG_NFC_OPS NFC_CONFIG2 +#define REG_NFC_SET_RBA NFC_BUF_ADDR +#define REG_NFC_ECC_EN NFC_CONFIG1 +#define REG_NFC_ECC_STATUS_RESULT NFC_ECC_STATUS_RESULT +#define REG_NFC_CE NFC_CONFIG1 +#define REG_NFC_SP_EN NFC_CONFIG1 +#define REG_NFC_BLS NFC_CONFIG +#define REG_NFC_WPC NFC_WRPROT +#define REG_START_BLKADDR NFC_UNLOCKSTART_BLKADDR +#define REG_END_BLKADDR NFC_UNLOCKEND_BLKADDR +#define REG_NFC_RST NFC_CONFIG1 +#define REG_NFC_ONE_CYCLE NFC_CONFIG1 + +/* NFC V1/V2 Specific MACRO functions definitions */ + +#define raw_write(v,a) __raw_writew(v,a) +#define raw_read(a) __raw_readw(a) + +#define NFC_SET_BLS(val) val + +#define UNLOCK_ADDR(start_addr,end_addr) \ +{ \ + raw_write(start_addr,REG_START_BLKADDR); \ + raw_write(end_addr,REG_END_BLKADDR); \ +} + +#define NFC_SET_NFC_ACTIVE_CS(val) +#define NFC_GET_MAXCHIP_SP() 1 +#define NFC_SET_WPC(val) val + +/* NULL Definitions */ +#define ACK_OPS +#define NFC_SET_RBA(val) raw_write(val, REG_NFC_SET_RBA); + +#ifdef CONFIG_ARCH_MXC_HAS_NFC_V2_1 +#define READ_PAGE() send_read_page(0) +#define PROG_PAGE() send_prog_page(0) +#else +#define READ_PAGE() \ +do { \ + send_read_page(0); \ + send_read_page(1); \ + send_read_page(2); \ + send_read_page(3); \ +} while (0) + +#define PROG_PAGE() \ +do { \ + send_prog_page(0); \ + send_prog_page(1); \ + send_prog_page(2); \ + send_prog_page(3); \ +} while (0) +#endif +#define CHECK_NFC_RB 1 + +#endif + +#endif /* __MXC_ND2_H__ */ |