From 917b3997a39396f5f51418930de7b933ad053bad Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Sat, 29 Dec 2007 15:14:23 +0000 Subject: [PATCH 08/64] Nand driver for TMIO devices --- drivers/mtd/nand/Kconfig | 7 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/tmio_nand.c | 557 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 565 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/nand/tmio_nand.c diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 246d451..43e489a 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -284,6 +284,13 @@ config MTD_NAND_CM_X270 depends on MTD_NAND && MACH_ARMCORE +config MTD_NAND_TMIO + tristate "NAND Flash device on Toshiba Mobile IO Controller" + depends on MTD_NAND && MFD_CORE + help + Support for NAND flash connected to a Toshiba Mobile IO + Controller in some PDAs, including the Sharp SL6000x. + config MTD_NAND_NANDSIM tristate "Support for NAND Flash Simulator" depends on MTD_PARTITIONS diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 3ad6c01..d839ebd 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_MTD_NAND_NDFC) += ndfc.o obj-$(CONFIG_MTD_NAND_AT91) += at91_nand.o obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o obj-$(CONFIG_MTD_NAND_BASLER_EXCITE) += excite_nandflash.o +obj-$(CONFIG_MTD_NAND_TMIO) += tmio_nand.o obj-$(CONFIG_MTD_NAND_PLATFORM) += plat_nand.o obj-$(CONFIG_MTD_ALAUDA) += alauda.o diff --git a/drivers/mtd/nand/tmio_nand.c b/drivers/mtd/nand/tmio_nand.c new file mode 100644 index 0000000..450b4ec --- /dev/null +++ b/drivers/mtd/nand/tmio_nand.c @@ -0,0 +1,557 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*--------------------------------------------------------------------------*/ + +/* tmio_nfcr.mode Register Command List */ +#define FCR_MODE_DATA 0x94 /* Data Data_Mode */ +#define FCR_MODE_COMMAND 0x95 /* Data Command_Mode */ +#define FCR_MODE_ADDRESS 0x96 /* Data Address_Mode */ + +#define FCR_MODE_HWECC_CALC 0xB4 /* HW-ECC Data */ +#define FCR_MODE_HWECC_RESULT 0xD4 /* HW-ECC Calculation Result Read_Mode */ +#define FCR_MODE_HWECC_RESET 0xF4 /* HW-ECC Reset */ + +#define FCR_MODE_POWER_ON 0x0C /* Power Supply ON to SSFDC card */ +#define FCR_MODE_POWER_OFF 0x08 /* Power Supply OFF to SSFDC card */ + +#define FCR_MODE_LED_OFF 0x00 /* LED OFF */ +#define FCR_MODE_LED_ON 0x04 /* LED ON */ + +#define FCR_MODE_EJECT_ON 0x68 /* Ejection Demand from Penguin is Advanced */ +#define FCR_MODE_EJECT_OFF 0x08 /* Ejection Demand from Penguin is Not Advanced */ + +#define FCR_MODE_LOCK 0x6C /* Operates By Lock_Mode. Ejection Switch is Invalid */ +#define FCR_MODE_UNLOCK 0x0C /* Operates By UnLock_Mode.Ejection Switch is Effective */ + +#define FCR_MODE_CONTROLLER_ID 0x40 /* Controller ID Read */ +#define FCR_MODE_STANDBY 0x00 /* SSFDC card Changes Standby State */ + +#define FCR_MODE_WE 0x80 +#define FCR_MODE_ECC1 0x40 +#define FCR_MODE_ECC0 0x20 +#define FCR_MODE_CE 0x10 +#define FCR_MODE_PCNT1 0x08 +#define FCR_MODE_PCNT0 0x04 +#define FCR_MODE_ALE 0x02 +#define FCR_MODE_CLE 0x01 + +#define FCR_STATUS_BUSY 0x80 + +/* + *NAND Flash Host Controller Configuration Register + */ +struct tmio_nfhccr { + u8 x00[4]; + u16 command; /* 0x04 Command */ + u8 x01[0x0a]; + u16 base[2]; /* 0x10 NAND Flash Control Reg Base Addr*/ + u8 x02[0x29]; + u8 intp; /* 0x3d Interrupt Pin */ + u8 x03[0x0a]; + u8 inte; /* 0x48 Interrupt Enable */ + u8 x04; + u8 ec; /* 0x4a Event Control */ + u8 x05; + u8 icc; /* 0x4c Internal Clock Control */ + u8 x06[0x0e]; + u8 eccc; /* 0x5b ECC Control */ + u8 x07[4]; + u8 nftc; /* 0x60 NAND Flash Transaction Control */ + u8 nfm; /* 0x61 NAND Flash Monitor */ + u8 nfpsc; /* 0x62 NAND Flash Power Supply Control */ + u8 nfdc; /* 0x63 NAND Flash Detect Control */ + u8 x08[0x9c]; +} __attribute__ ((packed)); + +/* + *NAND Flash Control Register + */ +struct tmio_nfcr { +union { + u8 u8; /* 0x00 Data Register */ + u16 u16; + u32 u32; +} __attribute__ ((packed)); + u8 mode; /* 0x04 Mode Register */ + u8 status; /* 0x05 Status Register */ + u8 isr; /* 0x06 Interrupt Status Register */ + u8 imr; /* 0x07 Interrupt Mask Register */ +} __attribute__ ((packed)); + +struct tmio_nand { + struct mtd_info mtd; + struct nand_chip chip; + + struct platform_device *dev; + + struct tmio_nfhccr __iomem *ccr; + struct tmio_nfcr __iomem *fcr; + + unsigned int irq; + + /* for tmio_nand_read_byte */ + u8 read; + unsigned read_good:1; +}; + +#define mtd_to_tmio(m) container_of(m, struct tmio_nand, mtd) + +#ifdef CONFIG_MTD_CMDLINE_PARTS +static const char *part_probes[] = { "cmdlinepart", NULL }; +#endif + +/*--------------------------------------------------------------------------*/ + +static void tmio_nand_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + struct nand_chip *chip = mtd->priv; + + if (ctrl & NAND_CTRL_CHANGE) { + u8 mode; + + if (ctrl & NAND_NCE) { + mode = FCR_MODE_DATA; + + if (ctrl & NAND_CLE) + mode |= FCR_MODE_CLE; + else + mode &= ~FCR_MODE_CLE; + + if (ctrl & NAND_ALE) + mode |= FCR_MODE_ALE; + else + mode &= ~FCR_MODE_ALE; + } else { + mode = FCR_MODE_STANDBY; + } + + iowrite8(mode, &fcr->mode); + tmio->read_good = 0; + } + + if (cmd != NAND_CMD_NONE) + writeb(cmd, chip->IO_ADDR_W); +} + +static int tmio_nand_dev_ready(struct mtd_info *mtd) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + + return !(ioread8(&fcr->status) & FCR_STATUS_BUSY); +} + +static irqreturn_t tmio_irq(int irq, void *__dev) +{ + struct platform_device *dev = __dev; + struct tmio_nand *tmio = platform_get_drvdata(dev); + struct nand_chip *nand_chip = &tmio->chip; + struct tmio_nfcr __iomem *fcr = tmio->fcr; + + /* disable RDYREQ interrupt */ + iowrite8(0x00, &fcr->imr); + + if (unlikely(!waitqueue_active(&nand_chip->controller->wq))) + dev_warn(&dev->dev, "spurious interrupt\n"); + + wake_up(&nand_chip->controller->wq); + return IRQ_HANDLED; +} + +/* + *The TMIO core has a RDYREQ interrupt on the posedge of #SMRB. + *This interrupt is normally disabled, but for long operations like + *erase and write, we enable it to wake us up. The irq handler + *disables the interrupt. + */ +static int +tmio_nand_wait(struct mtd_info *mtd, struct nand_chip *nand_chip) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + long timeout; + + /* enable RDYREQ interrupt */ + iowrite8(0x0f, &fcr->isr); + iowrite8(0x81, &fcr->imr); + + timeout = wait_event_timeout(nand_chip->controller->wq, tmio_nand_dev_ready(mtd), + msecs_to_jiffies(nand_chip->state == FL_ERASING ? 400 : 20)); + + if (unlikely(!tmio_nand_dev_ready(mtd))) { + iowrite8(0x00, &fcr->imr); + dev_warn(&tmio->dev->dev, "still busy with %s after %d ms\n", + nand_chip->state == FL_ERASING ? "erase" : "program", + nand_chip->state == FL_ERASING ? 400 : 20); + + } else if (unlikely(!timeout)) { + iowrite8(0x00, &fcr->imr); + dev_warn(&tmio->dev->dev, "timeout waiting for interrupt\n"); + } + + nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + return nand_chip->read_byte(mtd); +} + +/* + *The TMIO controller combines two 8-bit data bytes into one 16-bit + *word. This function separates them so nand_base.c works as expected, + *especially its NAND_CMD_READID routines. + * + *To prevent stale data from being read, tmio_nand_hwcontrol() clears + *tmio->read_good. + */ +static u_char tmio_nand_read_byte(struct mtd_info *mtd) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + unsigned int data; + + if (tmio->read_good--) + return tmio->read; + + data = ioread16(&fcr->u16); + tmio->read = data >> 8; + return data; +} + +/* + *The TMIO controller converts an 8-bit NAND interface to a 16-bit + *bus interface, so all data reads and writes must be 16-bit wide. + *Thus, we implement 16-bit versions of the read, write, and verify + *buffer functions. + */ +static void +tmio_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + + iowrite16_rep(&fcr->u16, buf, len >> 1); +} + +static void tmio_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + + ioread16_rep(&fcr->u16, buf, len >> 1); +} + +static int +tmio_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + u16 *p = (u16 *) buf; + + for (len >>= 1; len; len--) + if (*(p++) != ioread16(&fcr->u16)) + return -EFAULT; + return 0; +} + +static void tmio_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + + iowrite8(FCR_MODE_HWECC_RESET, &fcr->mode); + ioread8(&fcr->u8); /* dummy read */ + iowrite8(FCR_MODE_HWECC_CALC, &fcr->mode); +} + +static int tmio_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + unsigned int ecc; + + iowrite8(FCR_MODE_HWECC_RESULT, &fcr->mode); + + ecc = ioread16(&fcr->u16); + ecc_code[1] = ecc; /* 000-255 LP7-0 */ + ecc_code[0] = ecc >> 8; /* 000-255 LP15-8 */ + ecc = ioread16(&fcr->u16); + ecc_code[2] = ecc; /* 000-255 CP5-0,11b */ + ecc_code[4] = ecc >> 8; /* 256-511 LP7-0 */ + ecc = ioread16(&fcr->u16); + ecc_code[3] = ecc; /* 256-511 LP15-8 */ + ecc_code[5] = ecc >> 8; /* 256-511 CP5-0,11b */ + + iowrite8(FCR_MODE_DATA, &fcr->mode); + return 0; +} + +static int tmio_hw_init(struct platform_device *dev, struct tmio_nand *tmio) +{ + struct mfd_cell *cell = mfd_get_cell(dev); + const struct resource *nfcr = NULL; + struct tmio_nfhccr __iomem *ccr = tmio->ccr; + struct tmio_nfcr __iomem *fcr = tmio->fcr; + unsigned long base; + int i; + + for (i = 0; i < cell->num_resources; i++) + if (!strcmp((cell->resources+i)->name, TMIO_NAND_CONTROL)) + nfcr = &cell->resources[i]; + + if (nfcr == NULL) + return -ENOMEM; + + if (!cell->enable) { + printk(KERN_ERR "null cell enable!"); + return -EINVAL; + } + + cell->enable(dev); + + /* (4Ch) CLKRUN Enable 1st spcrunc */ + iowrite8(0x81, &ccr->icc); + + /* (10h)BaseAddress 0x1000 spba.spba2 */ + base = nfcr->start; + iowrite16(base, ccr->base + 0); + iowrite16(base >> 16, ccr->base + 1); + + /* (04h)Command Register I/O spcmd */ + iowrite8(0x02, &ccr->command); + + /* (62h) Power Supply Control ssmpwc */ + /* HardPowerOFF - SuspendOFF - PowerSupplyWait_4MS */ + iowrite8(0x02, &ccr->nfpsc); + + /* (63h) Detect Control ssmdtc */ + iowrite8(0x02, &ccr->nfdc); + + /* Interrupt status register clear sintst */ + iowrite8(0x0f, &fcr->isr); + + /* After power supply, Media are reset smode */ + iowrite8(FCR_MODE_POWER_ON, &fcr->mode); + iowrite8(FCR_MODE_COMMAND, &fcr->mode); + iowrite8(NAND_CMD_RESET, &fcr->u8); + + /* Standby Mode smode */ + iowrite8(FCR_MODE_STANDBY, &fcr->mode); + + mdelay(5); + + return 0; +} + +static void tmio_hw_stop(struct platform_device *dev, struct tmio_nand *tmio) +{ + struct mfd_cell *cell = mfd_get_cell(dev); + struct tmio_nfcr __iomem *fcr = tmio->fcr; + + iowrite8(FCR_MODE_POWER_OFF, &fcr->mode); + cell->disable(dev); +} + +static int tmio_probe(struct platform_device *dev) +{ + struct mfd_cell *cell = mfd_get_cell(dev); + struct tmio_nand_data *data = cell->driver_data; + struct resource *ccr = platform_get_resource_byname(dev, IORESOURCE_MEM, TMIO_NAND_CONFIG); + struct resource *fcr = platform_get_resource_byname(dev, IORESOURCE_MEM, TMIO_NAND_CONTROL); + int irq = platform_get_irq(dev, 0); + struct tmio_nand *tmio; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + struct mtd_partition *parts; + int nbparts = 0; + int retval; + + if (data == NULL) { + dev_err(&dev->dev, "NULL platform data!\n"); + return -EINVAL; + } + + tmio = kzalloc(sizeof *tmio, GFP_KERNEL); + if (!tmio) { + retval = -ENOMEM; + goto err_kzalloc; + } + + tmio->dev = dev; + + platform_set_drvdata(dev, tmio); + mtd = &tmio->mtd; + nand_chip = &tmio->chip; + mtd->priv = nand_chip; + mtd->name = "tmio-nand"; + + tmio->ccr = ioremap(ccr->start, ccr->end - ccr->start + 1); + if (!tmio->ccr) { + retval = -EIO; + goto err_iomap_ccr; + } + + tmio->fcr = ioremap(fcr->start, fcr->end - fcr->start + 1); + if (!tmio->fcr) { + retval = -EIO; + goto err_iomap_fcr; + } + + retval = tmio_hw_init(dev, tmio); + if (retval) + goto err_hwinit; + + /* Set address of NAND IO lines */ + nand_chip->IO_ADDR_R = tmio->fcr; + nand_chip->IO_ADDR_W = tmio->fcr; + + /* Set address of hardware control function */ + nand_chip->cmd_ctrl = tmio_nand_hwcontrol; + nand_chip->dev_ready = tmio_nand_dev_ready; + nand_chip->read_byte = tmio_nand_read_byte; + nand_chip->write_buf = tmio_nand_write_buf; + nand_chip->read_buf = tmio_nand_read_buf; + nand_chip->verify_buf = tmio_nand_verify_buf; + + /* set eccmode using hardware ECC */ + nand_chip->ecc.mode = NAND_ECC_HW; + nand_chip->ecc.size = 512; + nand_chip->ecc.bytes = 6; + nand_chip->ecc.hwctl = tmio_nand_enable_hwecc; + nand_chip->ecc.calculate = tmio_nand_calculate_ecc; + nand_chip->ecc.correct = nand_correct_data; + nand_chip->badblock_pattern = data->badblock_pattern; + + /* 15 us command delay time */ + nand_chip->chip_delay = 15; + + retval = request_irq(irq, &tmio_irq, + IRQF_DISABLED, dev->dev.bus_id, dev); + if (retval) { + dev_err(&dev->dev, "request_irq error %d\n", retval); + goto err_irq; + } + + tmio->irq = irq; + nand_chip->waitfunc = tmio_nand_wait; + + /* Scan to find existence of the device */ + if (nand_scan(mtd, 1)) { + retval = -ENODEV; + goto err_scan; + } + /* Register the partitions */ +#ifdef CONFIG_MTD_PARTITIONS +#ifdef CONFIG_MTD_CMDLINE_PARTS + nbparts = parse_mtd_partitions(mtd, part_probes, &parts, 0); +#endif + if (nbparts <= 0) { + parts = data->partition; + nbparts = data->num_partitions; + } + + retval = add_mtd_partitions(mtd, parts, nbparts); +#else + retval = add_mtd_device(mtd); +#endif + + if (!retval) + return retval; + + nand_release(mtd); + +err_scan: + if (tmio->irq) + free_irq(tmio->irq, dev); +err_irq: + tmio_hw_stop(dev, tmio); +err_hwinit: + iounmap(tmio->fcr); +err_iomap_fcr: + iounmap(tmio->ccr); +err_iomap_ccr: + kfree(tmio); +err_kzalloc: + return retval; +} + +static int tmio_remove(struct platform_device *dev) +{ + struct tmio_nand *tmio = platform_get_drvdata(dev); + + nand_release(&tmio->mtd); + if (tmio->irq) + free_irq(tmio->irq, tmio); + tmio_hw_stop(dev, tmio); + iounmap(tmio->fcr); + iounmap(tmio->ccr); + kfree(tmio); + return 0; +} + +#ifdef CONFIG_PM +static int tmio_suspend(struct platform_device *dev, pm_message_t state) +{ + struct mfd_cell *cell = mfd_get_cell(dev); + + if (cell->suspend) + cell->suspend(dev); + + tmio_hw_stop(dev, platform_get_drvdata(dev)); + return 0; +} + +static int tmio_resume(struct platform_device *dev) +{ + struct mfd_cell *cell = mfd_get_cell(dev); + + tmio_hw_init(dev, platform_get_drvdata(dev)); + + if (cell->resume) + cell->resume(dev); + + return 0; +} +#endif + +static struct platform_driver tmio_driver = { + .driver.name = "tmio-nand", + .driver.owner = THIS_MODULE, + .probe = tmio_probe, + .remove = tmio_remove, +#ifdef CONFIG_PM + .suspend = tmio_suspend, + .resume = tmio_resume, +#endif +}; + +static int __init tmio_init(void) +{ + return platform_driver_register(&tmio_driver); +} + +static void __exit tmio_exit(void) +{ + platform_driver_unregister(&tmio_driver); +} + +module_init(tmio_init); +module_exit(tmio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dirk Opfer, Chris Humbert, Dmitry Baryshkov"); +MODULE_DESCRIPTION("NAND flash driver on Toshiba Mobile IO controller"); -- 1.5.3.8