From d3a2f71853ce543c5515d4982e202751e15b0b6d Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Thu, 31 Jul 2008 20:44:28 +0200 Subject: [PATCH 01/11] mfd: TMIO MMC structures and accessors. Signed-off-by: Ian Molton Signed-off-by: Samuel Ortiz --- include/linux/mfd/tmio.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/include/linux/mfd/tmio.h b/include/linux/mfd/tmio.h index 9438d8c9ac1c..ec612e66391c 100644 --- a/include/linux/mfd/tmio.h +++ b/include/linux/mfd/tmio.h @@ -1,6 +1,21 @@ #ifndef MFD_TMIO_H #define MFD_TMIO_H +#define tmio_ioread8(addr) readb(addr) +#define tmio_ioread16(addr) readw(addr) +#define tmio_ioread16_rep(r, b, l) readsw(r, b, l) +#define tmio_ioread32(addr) \ + (((u32) readw((addr))) | (((u32) readw((addr) + 2)) << 16)) + +#define tmio_iowrite8(val, addr) writeb((val), (addr)) +#define tmio_iowrite16(val, addr) writew((val), (addr)) +#define tmio_iowrite16_rep(r, b, l) writesw(r, b, l) +#define tmio_iowrite32(val, addr) \ + do { \ + writew((val), (addr)); \ + writew((val) >> 16, (addr) + 2); \ + } while (0) + /* * data for the NAND controller */ @@ -10,8 +25,4 @@ struct tmio_nand_data { unsigned int num_partitions; }; -#define TMIO_NAND_CONFIG "tmio-nand-config" -#define TMIO_NAND_CONTROL "tmio-nand-control" -#define TMIO_NAND_IRQ "tmio-nand" - #endif From 1f192015ca5b2f4d0a79c191f03f64e72fd8fc29 Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Tue, 15 Jul 2008 15:09:43 +0100 Subject: [PATCH 02/11] mfd: driver for the T7L66XB TMIO SoC This patchset provides support for the core functinality of the T7L66XB SoC from Toshiba. Supported in this patchset is the IRQ MUX, MMC controller and NAND flash controller. Signed-off-by: Ian Molton Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 6 + drivers/mfd/Makefile | 1 + drivers/mfd/t7l66xb.c | 409 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/t7l66xb.h | 36 ++++ 4 files changed, 452 insertions(+) create mode 100644 drivers/mfd/t7l66xb.c create mode 100644 include/linux/mfd/t7l66xb.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 883e7ea31de2..fc7c919693bf 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -50,6 +50,12 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config MFD_T7L66XB + bool "Support Toshiba T7L66XB" + select MFD_CORE + help + Support for Toshiba Mobile IO Controller T7L66XB + config MFD_TC6393XB bool "Support Toshiba TC6393XB" depends on GPIOLIB && ARM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 33daa2f45dd8..3531ad2a276c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_MFD_ASIC3) += asic3.o obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o +obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o obj-$(CONFIG_MFD_CORE) += mfd-core.o diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c new file mode 100644 index 000000000000..5be42054f739 --- /dev/null +++ b/drivers/mfd/t7l66xb.c @@ -0,0 +1,409 @@ +/* + * + * Toshiba T7L66XB core mfd support + * + * Copyright (c) 2005, 2007, 2008 Ian Molton + * Copyright (c) 2008 Dmitry Baryshkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * T7L66 features: + * + * Supported in this driver: + * SD/MMC + * SM/NAND flash controller + * + * As yet not supported + * GPIO interface (on NAND pins) + * Serial interface + * TFT 'interface converter' + * PCMCIA interface logic + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + T7L66XB_CELL_NAND, + T7L66XB_CELL_MMC, +}; + +#define SCR_REVID 0x08 /* b Revision ID */ +#define SCR_IMR 0x42 /* b Interrupt Mask */ +#define SCR_DEV_CTL 0xe0 /* b Device control */ +#define SCR_ISR 0xe1 /* b Interrupt Status */ +#define SCR_GPO_OC 0xf0 /* b GPO output control */ +#define SCR_GPO_OS 0xf1 /* b GPO output enable */ +#define SCR_GPI_S 0xf2 /* w GPI status */ +#define SCR_APDC 0xf8 /* b Active pullup down ctrl */ + +#define SCR_DEV_CTL_USB BIT(0) /* USB enable */ +#define SCR_DEV_CTL_MMC BIT(1) /* MMC enable */ + +/*--------------------------------------------------------------------------*/ + +struct t7l66xb { + void __iomem *scr; + /* Lock to protect registers requiring read/modify/write ops. */ + spinlock_t lock; + + struct resource rscr; + int irq; + int irq_base; +}; + +/*--------------------------------------------------------------------------*/ + +static int t7l66xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned long flags; + u8 dev_ctl; + + if (pdata->enable_clk32k) + pdata->enable_clk32k(dev); + + spin_lock_irqsave(&t7l66xb->lock, flags); + + dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL); + dev_ctl |= SCR_DEV_CTL_MMC; + tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL); + + spin_unlock_irqrestore(&t7l66xb->lock, flags); + + return 0; +} + +static int t7l66xb_mmc_disable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned long flags; + u8 dev_ctl; + + spin_lock_irqsave(&t7l66xb->lock, flags); + + dev_ctl = tmio_ioread8(t7l66xb->scr + SCR_DEV_CTL); + dev_ctl &= ~SCR_DEV_CTL_MMC; + tmio_iowrite8(dev_ctl, t7l66xb->scr + SCR_DEV_CTL); + + spin_unlock_irqrestore(&t7l66xb->lock, flags); + + if (pdata->disable_clk32k) + pdata->disable_clk32k(dev); + + return 0; +} + +/*--------------------------------------------------------------------------*/ + +const static struct resource t7l66xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x200, + .end = 0x2ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_T7L66XB_MMC, + .end = IRQ_T7L66XB_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +const static struct resource t7l66xb_nand_resources[] = { + { + .start = 0xc00, + .end = 0xc07, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x0100, + .end = 0x01ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_T7L66XB_NAND, + .end = IRQ_T7L66XB_NAND, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell t7l66xb_cells[] = { + [T7L66XB_CELL_MMC] = { + .name = "tmio-mmc", + .enable = t7l66xb_mmc_enable, + .disable = t7l66xb_mmc_disable, + .num_resources = ARRAY_SIZE(t7l66xb_mmc_resources), + .resources = t7l66xb_mmc_resources, + }, + [T7L66XB_CELL_NAND] = { + .name = "tmio-nand", + .num_resources = ARRAY_SIZE(t7l66xb_nand_resources), + .resources = t7l66xb_nand_resources, + }, +}; + +/*--------------------------------------------------------------------------*/ + +/* Handle the T7L66XB interrupt mux */ +static void t7l66xb_irq(unsigned int irq, struct irq_desc *desc) +{ + struct t7l66xb *t7l66xb = get_irq_data(irq); + unsigned int isr; + unsigned int i, irq_base; + + irq_base = t7l66xb->irq_base; + + while ((isr = tmio_ioread8(t7l66xb->scr + SCR_ISR) & + ~tmio_ioread8(t7l66xb->scr + SCR_IMR))) + for (i = 0; i < T7L66XB_NR_IRQS; i++) + if (isr & (1 << i)) + generic_handle_irq(irq_base + i); +} + +static void t7l66xb_irq_mask(unsigned int irq) +{ + struct t7l66xb *t7l66xb = get_irq_chip_data(irq); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&t7l66xb->lock, flags); + imr = tmio_ioread8(t7l66xb->scr + SCR_IMR); + imr |= 1 << (irq - t7l66xb->irq_base); + tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR); + spin_unlock_irqrestore(&t7l66xb->lock, flags); +} + +static void t7l66xb_irq_unmask(unsigned int irq) +{ + struct t7l66xb *t7l66xb = get_irq_chip_data(irq); + unsigned long flags; + u8 imr; + + spin_lock_irqsave(&t7l66xb->lock, flags); + imr = tmio_ioread8(t7l66xb->scr + SCR_IMR); + imr &= ~(1 << (irq - t7l66xb->irq_base)); + tmio_iowrite8(imr, t7l66xb->scr + SCR_IMR); + spin_unlock_irqrestore(&t7l66xb->lock, flags); +} + +static struct irq_chip t7l66xb_chip = { + .name = "t7l66xb", + .ack = t7l66xb_irq_mask, + .mask = t7l66xb_irq_mask, + .unmask = t7l66xb_irq_unmask, +}; + +/*--------------------------------------------------------------------------*/ + +/* Install the IRQ handler */ +static void t7l66xb_attach_irq(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = t7l66xb->irq_base; + + for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) { + set_irq_chip(irq, &t7l66xb_chip); + set_irq_chip_data(irq, t7l66xb); + set_irq_handler(irq, handle_level_irq); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); +#endif + } + + set_irq_type(t7l66xb->irq, IRQ_TYPE_EDGE_FALLING); + set_irq_data(t7l66xb->irq, t7l66xb); + set_irq_chained_handler(t7l66xb->irq, t7l66xb_irq); +} + +static void t7l66xb_detach_irq(struct platform_device *dev) +{ + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + unsigned int irq, irq_base; + + irq_base = t7l66xb->irq_base; + + set_irq_chained_handler(t7l66xb->irq, NULL); + set_irq_data(t7l66xb->irq, NULL); + + for (irq = irq_base; irq < irq_base + T7L66XB_NR_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip(irq, NULL); + set_irq_chip_data(irq, NULL); + } +} + +/*--------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int t7l66xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + + if (pdata && pdata->suspend) + pdata->suspend(dev); + + return 0; +} + +static int t7l66xb_resume(struct platform_device *dev) +{ + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + + if (pdata && pdata->resume) + pdata->resume(dev); + + return 0; +} +#else +#define t7l66xb_suspend NULL +#define t7l66xb_resume NULL +#endif + +/*--------------------------------------------------------------------------*/ + +static int t7l66xb_probe(struct platform_device *dev) +{ + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + struct t7l66xb *t7l66xb; + struct resource *iomem, *rscr; + int ret; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) + return -EINVAL; + + t7l66xb = kzalloc(sizeof *t7l66xb, GFP_KERNEL); + if (!t7l66xb) + return -ENOMEM; + + spin_lock_init(&t7l66xb->lock); + + platform_set_drvdata(dev, t7l66xb); + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + t7l66xb->irq = ret; + else + goto err_noirq; + + t7l66xb->irq_base = pdata->irq_base; + + rscr = &t7l66xb->rscr; + rscr->name = "t7l66xb-core"; + rscr->start = iomem->start; + rscr->end = iomem->start + 0xff; + rscr->flags = IORESOURCE_MEM; + + ret = request_resource(iomem, rscr); + if (ret) + goto err_request_scr; + + t7l66xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); + if (!t7l66xb->scr) { + ret = -ENOMEM; + goto err_ioremap; + } + + if (pdata && pdata->enable) + pdata->enable(dev); + + /* Mask all interrupts */ + tmio_iowrite8(0xbf, t7l66xb->scr + SCR_IMR); + + printk(KERN_INFO "%s rev %d @ 0x%08lx, irq %d\n", + dev->name, tmio_ioread8(t7l66xb->scr + SCR_REVID), + (unsigned long)iomem->start, t7l66xb->irq); + + t7l66xb_attach_irq(dev); + + t7l66xb_cells[T7L66XB_CELL_NAND].driver_data = pdata->nand_data; + + ret = mfd_add_devices(dev, t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells), + iomem, t7l66xb->irq_base); + + if (!ret) + return 0; + + t7l66xb_detach_irq(dev); + iounmap(t7l66xb->scr); +err_ioremap: + release_resource(&t7l66xb->rscr); +err_noirq: +err_request_scr: + kfree(t7l66xb); + return ret; +} + +static int t7l66xb_remove(struct platform_device *dev) +{ + struct t7l66xb_platform_data *pdata = dev->dev.platform_data; + struct t7l66xb *t7l66xb = platform_get_drvdata(dev); + int ret; + + ret = pdata->disable(dev); + + t7l66xb_detach_irq(dev); + iounmap(t7l66xb->scr); + release_resource(&t7l66xb->rscr); + mfd_remove_devices(dev); + platform_set_drvdata(dev, NULL); + kfree(t7l66xb); + + return ret; + +} + +static struct platform_driver t7l66xb_platform_driver = { + .driver = { + .name = "t7l66xb", + .owner = THIS_MODULE, + }, + .suspend = t7l66xb_suspend, + .resume = t7l66xb_resume, + .probe = t7l66xb_probe, + .remove = t7l66xb_remove, +}; + +/*--------------------------------------------------------------------------*/ + +static int __init t7l66xb_init(void) +{ + int retval = 0; + + retval = platform_driver_register(&t7l66xb_platform_driver); + return retval; +} + +static void __exit t7l66xb_exit(void) +{ + platform_driver_unregister(&t7l66xb_platform_driver); +} + +module_init(t7l66xb_init); +module_exit(t7l66xb_exit); + +MODULE_DESCRIPTION("Toshiba T7L66XB core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton"); +MODULE_ALIAS("platform:t7l66xb"); diff --git a/include/linux/mfd/t7l66xb.h b/include/linux/mfd/t7l66xb.h new file mode 100644 index 000000000000..e83c7f2036f9 --- /dev/null +++ b/include/linux/mfd/t7l66xb.h @@ -0,0 +1,36 @@ +/* + * This file contains the definitions for the T7L66XB + * + * (C) Copyright 2005 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#ifndef MFD_T7L66XB_H +#define MFD_T7L66XB_H + +#include +#include + +struct t7l66xb_platform_data { + int (*enable_clk32k)(struct platform_device *dev); + void (*disable_clk32k)(struct platform_device *dev); + int (*enable)(struct platform_device *dev); + int (*disable)(struct platform_device *dev); + int (*suspend)(struct platform_device *dev); + int (*resume)(struct platform_device *dev); + + int irq_base; /* The base for subdevice irqs */ + + struct tmio_nand_data *nand_data; +}; + + +#define IRQ_T7L66XB_MMC (1) +#define IRQ_T7L66XB_NAND (3) + +#define T7L66XB_NR_IRQS 8 + +#endif From cbdfb426392557d49b1a0e7cb59b16c20dc42955 Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Tue, 15 Jul 2008 15:12:52 +0100 Subject: [PATCH 03/11] mfd: driver for the TC6387XB TMIO controller. This patch adds support for the TC6387XB. Unlike other TMIO devices this one has only one subdevice and no interrupt mux, however using the MFD framework allows it to share the TMIO MMC driver. Signed-off-by: Ian Molton Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 6 ++ drivers/mfd/Makefile | 1 + drivers/mfd/tc6387xb.c | 172 +++++++++++++++++++++++++++++++++++ include/linux/mfd/tc6387xb.h | 23 +++++ 4 files changed, 202 insertions(+) create mode 100644 drivers/mfd/tc6387xb.c create mode 100644 include/linux/mfd/tc6387xb.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index fc7c919693bf..5beff5b7ef2e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -56,6 +56,12 @@ config MFD_T7L66XB help Support for Toshiba Mobile IO Controller T7L66XB +config MFD_TC6387XB + bool "Support Toshiba TC6387XB" + select MFD_CORE + help + Support for Toshiba Mobile IO Controller TC6387XB + config MFD_TC6393XB bool "Support Toshiba TC6393XB" depends on GPIOLIB && ARM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 3531ad2a276c..03ad239ecef0 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o +obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o obj-$(CONFIG_MFD_CORE) += mfd-core.o diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c new file mode 100644 index 000000000000..03718feda4d8 --- /dev/null +++ b/drivers/mfd/tc6387xb.c @@ -0,0 +1,172 @@ +/* + * Toshiba TC6387XB support + * Copyright (c) 2005 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This file contains TC6387XB base support. + * + */ + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_PM +static int tc6387xb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct tc6387xb_platform_data *pdata = platform_get_drvdata(dev); + + if (pdata && pdata->suspend) + pdata->suspend(dev); + + return 0; +} + +static int tc6387xb_resume(struct platform_device *dev) +{ + struct tc6387xb_platform_data *pdata = platform_get_drvdata(dev); + + if (pdata && pdata->resume) + pdata->resume(dev); + + return 0; +} +#else +#define tc6387xb_suspend NULL +#define tc6387xb_resume NULL +#endif + +/*--------------------------------------------------------------------------*/ + +static int tc6387xb_mmc_enable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb_platform_data *tc6387xb = dev->dev.platform_data; + + if (tc6387xb->enable_clk32k) + tc6387xb->enable_clk32k(dev); + + return 0; +} + +static int tc6387xb_mmc_disable(struct platform_device *mmc) +{ + struct platform_device *dev = to_platform_device(mmc->dev.parent); + struct tc6387xb_platform_data *tc6387xb = dev->dev.platform_data; + + if (tc6387xb->disable_clk32k) + tc6387xb->disable_clk32k(dev); + + return 0; +} + +/*--------------------------------------------------------------------------*/ + +static struct resource tc6387xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x200, + .end = 0x2ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell tc6387xb_cells[] = { + { + .name = "tmio-mmc", + .enable = tc6387xb_mmc_enable, + .disable = tc6387xb_mmc_disable, + .num_resources = ARRAY_SIZE(tc6387xb_mmc_resources), + .resources = tc6387xb_mmc_resources, + }, +}; + +static int tc6387xb_probe(struct platform_device *dev) +{ + struct tc6387xb_platform_data *data = platform_get_drvdata(dev); + struct resource *iomem; + int irq, ret; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!iomem) { + ret = -EINVAL; + goto err_resource; + } + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + irq = ret; + else + goto err_resource; + + if (data && data->enable) + data->enable(dev); + + printk(KERN_INFO "Toshiba tc6387xb initialised\n"); + + ret = mfd_add_devices(dev, tc6387xb_cells, + ARRAY_SIZE(tc6387xb_cells), iomem, irq); + + if (!ret) + return 0; + +err_resource: + return ret; +} + +static int tc6387xb_remove(struct platform_device *dev) +{ + struct tc6387xb_platform_data *data = platform_get_drvdata(dev); + + if (data && data->disable) + data->disable(dev); + + /* FIXME - free the resources! */ + + return 0; +} + + +static struct platform_driver tc6387xb_platform_driver = { + .driver = { + .name = "tc6387xb", + }, + .probe = tc6387xb_probe, + .remove = tc6387xb_remove, + .suspend = tc6387xb_suspend, + .resume = tc6387xb_resume, +}; + + +static int __init tc6387xb_init(void) +{ + return platform_driver_register(&tc6387xb_platform_driver); +} + +static void __exit tc6387xb_exit(void) +{ + platform_driver_unregister(&tc6387xb_platform_driver); +} + +module_init(tc6387xb_init); +module_exit(tc6387xb_exit); + +MODULE_DESCRIPTION("Toshiba TC6387XB core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ian Molton"); +MODULE_ALIAS("platform:tc6387xb"); diff --git a/include/linux/mfd/tc6387xb.h b/include/linux/mfd/tc6387xb.h new file mode 100644 index 000000000000..fa06e0610b8e --- /dev/null +++ b/include/linux/mfd/tc6387xb.h @@ -0,0 +1,23 @@ +/* + * This file contains the definitions for the TC6387XB + * + * (C) Copyright 2005 Ian Molton + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + */ +#ifndef MFD_TC6387XB_H +#define MFD_TC6387XB_H + +struct tc6387xb_platform_data { + int (*enable_clk32k)(struct platform_device *dev); + void (*disable_clk32k)(struct platform_device *dev); + + int (*enable)(struct platform_device *dev); + int (*disable)(struct platform_device *dev); + int (*suspend)(struct platform_device *dev); + int (*resume)(struct platform_device *dev); +}; + +#endif From befb844415f4e3b9c48594d05238a8a4c2af8941 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 1 Aug 2008 00:11:56 +0200 Subject: [PATCH 04/11] mfd: Fix tc6393 according to the new tmio.h Signed-off-by: Samuel Ortiz --- drivers/mfd/tc6393xb.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c index f4fd797c1590..81e2605ea10d 100644 --- a/drivers/mfd/tc6393xb.c +++ b/drivers/mfd/tc6393xb.c @@ -135,19 +135,16 @@ static int tc6393xb_nand_enable(struct platform_device *nand) static struct resource __devinitdata tc6393xb_nand_resources[] = { { - .name = TMIO_NAND_CONFIG, .start = 0x0100, .end = 0x01ff, .flags = IORESOURCE_MEM, }, { - .name = TMIO_NAND_CONTROL, .start = 0x1000, .end = 0x1007, .flags = IORESOURCE_MEM, }, { - .name = TMIO_NAND_IRQ, .start = IRQ_TC6393_NAND, .end = IRQ_TC6393_NAND, .flags = IORESOURCE_IRQ, From 56bf2bda0651ca368a259468e4f309c71ed35c35 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Fri, 1 Aug 2008 00:16:13 +0200 Subject: [PATCH 05/11] mfd: Fix 7l66 and 6387 according to the new mfd-core API Signed-off-by: Samuel Ortiz --- drivers/mfd/t7l66xb.c | 11 ++++++++--- drivers/mfd/tc6387xb.c | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index 5be42054f739..2d715fcea76f 100644 --- a/drivers/mfd/t7l66xb.c +++ b/drivers/mfd/t7l66xb.c @@ -338,9 +338,14 @@ static int t7l66xb_probe(struct platform_device *dev) t7l66xb_attach_irq(dev); t7l66xb_cells[T7L66XB_CELL_NAND].driver_data = pdata->nand_data; + t7l66xb_cells[T7L66XB_CELL_NAND].platform_data = + &t7l66xb_cells[T7L66XB_CELL_NAND]; + t7l66xb_cells[T7L66XB_CELL_NAND].data_size = + sizeof(t7l66xb_cells[T7L66XB_CELL_NAND]); - ret = mfd_add_devices(dev, t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells), - iomem, t7l66xb->irq_base); + ret = mfd_add_devices(&dev->dev, dev->id, + t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells), + iomem, t7l66xb->irq_base); if (!ret) return 0; @@ -366,7 +371,7 @@ static int t7l66xb_remove(struct platform_device *dev) t7l66xb_detach_irq(dev); iounmap(t7l66xb->scr); release_resource(&t7l66xb->rscr); - mfd_remove_devices(dev); + mfd_remove_devices(&dev->dev); platform_set_drvdata(dev, NULL); kfree(t7l66xb); diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c index 03718feda4d8..9fd6f80b50f6 100644 --- a/drivers/mfd/tc6387xb.c +++ b/drivers/mfd/tc6387xb.c @@ -119,8 +119,8 @@ static int tc6387xb_probe(struct platform_device *dev) printk(KERN_INFO "Toshiba tc6387xb initialised\n"); - ret = mfd_add_devices(dev, tc6387xb_cells, - ARRAY_SIZE(tc6387xb_cells), iomem, irq); + ret = mfd_add_devices(&dev->dev, dev->id, tc6387xb_cells, + ARRAY_SIZE(tc6387xb_cells), iomem, irq); if (!ret) return 0; From d2432a6321b8f578018690d0c5384ee5de19737d Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Mon, 4 Aug 2008 18:58:18 +0200 Subject: [PATCH 06/11] mfd: tc6387 MMC platform data We need to pass the cell as the platform data. Signed-off-by: Samuel Ortiz --- drivers/mfd/tc6387xb.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c index 9fd6f80b50f6..a22b21ac6cf8 100644 --- a/drivers/mfd/tc6387xb.c +++ b/drivers/mfd/tc6387xb.c @@ -17,6 +17,10 @@ #include #include +enum { + TC6387XB_CELL_MMC, +}; + #ifdef CONFIG_PM static int tc6387xb_suspend(struct platform_device *dev, pm_message_t state) { @@ -87,7 +91,7 @@ static struct resource tc6387xb_mmc_resources[] = { }; static struct mfd_cell tc6387xb_cells[] = { - { + [TC6387XB_CELL_MMC] = { .name = "tmio-mmc", .enable = tc6387xb_mmc_enable, .disable = tc6387xb_mmc_disable, @@ -119,6 +123,11 @@ static int tc6387xb_probe(struct platform_device *dev) printk(KERN_INFO "Toshiba tc6387xb initialised\n"); + tc6387xb_cells[TC6387XB_CELL_MMC].platform_data = + &tc6387xb_cells[TC6387XB_CELL_MMC]; + tc6387xb_cells[TC6387XB_CELL_MMC].data_size = + sizeof(tc6387xb_cells[TC6387XB_CELL_MMC]); + ret = mfd_add_devices(&dev->dev, dev->id, tc6387xb_cells, ARRAY_SIZE(tc6387xb_cells), iomem, irq); From 8a4fbe013fb6a2a65a4eddcddb888ebe38d0270d Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Mon, 4 Aug 2008 18:06:18 +0200 Subject: [PATCH 07/11] mfd: t7l66 MMC platform data The tmio MMC driver needs the cell to be passed as a platform data. Signed-off-by: Samuel Ortiz --- drivers/mfd/t7l66xb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c index 2d715fcea76f..49a0fffc02af 100644 --- a/drivers/mfd/t7l66xb.c +++ b/drivers/mfd/t7l66xb.c @@ -343,6 +343,11 @@ static int t7l66xb_probe(struct platform_device *dev) t7l66xb_cells[T7L66XB_CELL_NAND].data_size = sizeof(t7l66xb_cells[T7L66XB_CELL_NAND]); + t7l66xb_cells[T7L66XB_CELL_MMC].platform_data = + &t7l66xb_cells[T7L66XB_CELL_MMC]; + t7l66xb_cells[T7L66XB_CELL_MMC].data_size = + sizeof(t7l66xb_cells[T7L66XB_CELL_MMC]); + ret = mfd_add_devices(&dev->dev, dev->id, t7l66xb_cells, ARRAY_SIZE(t7l66xb_cells), iomem, t7l66xb->irq_base); From ec43b8161bd82535fa8099ee6e98cc554de48614 Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Tue, 15 Jul 2008 16:04:22 +0100 Subject: [PATCH 08/11] mfd: driver for the TMIO NAND controller This patch adds support for the NAND controller commonly found in TMIO based MFDs. Signed-off-by: Ian Molton Acked-By: David Woodhouse Signed-off-by: Samuel Ortiz --- drivers/mtd/nand/Kconfig | 7 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/tmio_nand.c | 556 +++++++++++++++++++++++++++++++++++ 3 files changed, 564 insertions(+) create mode 100644 drivers/mtd/nand/tmio_nand.c diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 02f9cc30d77b..fd6debb24252 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -351,6 +351,13 @@ config MTD_NAND_PASEMI Enables support for NAND Flash interface on PA Semi PWRficient based boards +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 d772581de573..b786c5da82da 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_MTD_NAND_ATMEL) += atmel_nand.o obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o obj-$(CONFIG_MTD_NAND_BASLER_EXCITE) += excite_nandflash.o obj-$(CONFIG_MTD_NAND_PXA3xx) += pxa3xx_nand.o +obj-$(CONFIG_MTD_NAND_TMIO) += tmio_nand.o obj-$(CONFIG_MTD_NAND_PLATFORM) += plat_nand.o obj-$(CONFIG_MTD_ALAUDA) += alauda.o obj-$(CONFIG_MTD_NAND_PASEMI) += pasemi_nand.o diff --git a/drivers/mtd/nand/tmio_nand.c b/drivers/mtd/nand/tmio_nand.c new file mode 100644 index 000000000000..cbab654b03c8 --- /dev/null +++ b/drivers/mtd/nand/tmio_nand.c @@ -0,0 +1,556 @@ +/* + * Toshiba TMIO NAND flash controller driver + * + * Slightly murky pre-git history of the driver: + * + * Copyright (c) Ian Molton 2004, 2005, 2008 + * Original work, independant of sharps code. Included hardware ECC support. + * Hard ECC did not work for writes in the early revisions. + * Copyright (c) Dirk Opfer 2005. + * Modifications developed from sharps code but + * NOT containing any, ported onto Ians base. + * Copyright (c) Chris Humbert 2005 + * Copyright (c) Dmitry Baryshkov 2008 + * Minor fixes + * + * Parts copyright Sebastian Carlier + * + * This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*--------------------------------------------------------------------------*/ + +/* + * NAND Flash Host Controller Configuration Register + */ +#define CCR_COMMAND 0x04 /* w Command */ +#define CCR_BASE 0x10 /* l NAND Flash Control Reg Base Addr */ +#define CCR_INTP 0x3d /* b Interrupt Pin */ +#define CCR_INTE 0x48 /* b Interrupt Enable */ +#define CCR_EC 0x4a /* b Event Control */ +#define CCR_ICC 0x4c /* b Internal Clock Control */ +#define CCR_ECCC 0x5b /* b ECC Control */ +#define CCR_NFTC 0x60 /* b NAND Flash Transaction Control */ +#define CCR_NFM 0x61 /* b NAND Flash Monitor */ +#define CCR_NFPSC 0x62 /* b NAND Flash Power Supply Control */ +#define CCR_NFDC 0x63 /* b NAND Flash Detect Control */ + +/* + * NAND Flash Control Register + */ +#define FCR_DATA 0x00 /* bwl Data Register */ +#define FCR_MODE 0x04 /* b Mode Register */ +#define FCR_STATUS 0x05 /* b Status Register */ +#define FCR_ISR 0x06 /* b Interrupt Status Register */ +#define FCR_IMR 0x07 /* b Interrupt Mask Register */ + +/* FCR_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 Calc 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 events active */ +#define FCR_MODE_EJECT_OFF 0x08 /* Ejection events ignored */ + +#define FCR_MODE_LOCK 0x6C /* Lock_Mode. Eject Switch Invalid */ +#define FCR_MODE_UNLOCK 0x0C /* UnLock_Mode. Eject Switch is valid */ + +#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 + +/*--------------------------------------------------------------------------*/ + +struct tmio_nand { + struct mtd_info mtd; + struct nand_chip chip; + + struct platform_device *dev; + + void __iomem *ccr; + void __iomem *fcr; + unsigned long fcr_phys; + + 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 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; + } + + tmio_iowrite8(mode, tmio->fcr + FCR_MODE); + tmio->read_good = 0; + } + + if (cmd != NAND_CMD_NONE) + tmio_iowrite8(cmd, chip->IO_ADDR_W); +} + +static int tmio_nand_dev_ready(struct mtd_info *mtd) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + + return !(tmio_ioread8(tmio->fcr + FCR_STATUS) & FCR_STATUS_BUSY); +} + +static irqreturn_t tmio_irq(int irq, void *__tmio) +{ + struct tmio_nand *tmio = __tmio; + struct nand_chip *nand_chip = &tmio->chip; + + /* disable RDYREQ interrupt */ + tmio_iowrite8(0x00, tmio->fcr + FCR_IMR); + + if (unlikely(!waitqueue_active(&nand_chip->controller->wq))) + dev_warn(&tmio->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); + long timeout; + + /* enable RDYREQ interrupt */ + tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR); + tmio_iowrite8(0x81, tmio->fcr + 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))) { + tmio_iowrite8(0x00, tmio->fcr + 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)) { + tmio_iowrite8(0x00, tmio->fcr + 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); + unsigned int data; + + if (tmio->read_good--) + return tmio->read; + + data = tmio_ioread16(tmio->fcr + FCR_DATA); + 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); + + tmio_iowrite16_rep(tmio->fcr + FCR_DATA, 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); + + tmio_ioread16_rep(tmio->fcr + FCR_DATA, 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); + u16 *p = (u16 *) buf; + + for (len >>= 1; len; len--) + if (*(p++) != tmio_ioread16(tmio->fcr + FCR_DATA)) + return -EFAULT; + return 0; +} + +static void tmio_nand_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct tmio_nand *tmio = mtd_to_tmio(mtd); + + tmio_iowrite8(FCR_MODE_HWECC_RESET, tmio->fcr + FCR_MODE); + tmio_ioread8(tmio->fcr + FCR_DATA); /* dummy read */ + tmio_iowrite8(FCR_MODE_HWECC_CALC, tmio->fcr + 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); + unsigned int ecc; + + tmio_iowrite8(FCR_MODE_HWECC_RESULT, tmio->fcr + FCR_MODE); + + ecc = tmio_ioread16(tmio->fcr + FCR_DATA); + ecc_code[1] = ecc; /* 000-255 LP7-0 */ + ecc_code[0] = ecc >> 8; /* 000-255 LP15-8 */ + ecc = tmio_ioread16(tmio->fcr + FCR_DATA); + ecc_code[2] = ecc; /* 000-255 CP5-0,11b */ + ecc_code[4] = ecc >> 8; /* 256-511 LP7-0 */ + ecc = tmio_ioread16(tmio->fcr + FCR_DATA); + ecc_code[3] = ecc; /* 256-511 LP15-8 */ + ecc_code[5] = ecc >> 8; /* 256-511 CP5-0,11b */ + + tmio_iowrite8(FCR_MODE_DATA, tmio->fcr + FCR_MODE); + return 0; +} + +static int tmio_hw_init(struct platform_device *dev, struct tmio_nand *tmio) +{ + struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data; + int ret; + + if (cell->enable) { + ret = cell->enable(dev); + if (ret) + return ret; + } + + /* (4Ch) CLKRUN Enable 1st spcrunc */ + tmio_iowrite8(0x81, tmio->ccr + CCR_ICC); + + /* (10h)BaseAddress 0x1000 spba.spba2 */ + tmio_iowrite16(tmio->fcr_phys, tmio->ccr + CCR_BASE); + tmio_iowrite16(tmio->fcr_phys >> 16, tmio->ccr + CCR_BASE + 16); + + /* (04h)Command Register I/O spcmd */ + tmio_iowrite8(0x02, tmio->ccr + CCR_COMMAND); + + /* (62h) Power Supply Control ssmpwc */ + /* HardPowerOFF - SuspendOFF - PowerSupplyWait_4MS */ + tmio_iowrite8(0x02, tmio->ccr + CCR_NFPSC); + + /* (63h) Detect Control ssmdtc */ + tmio_iowrite8(0x02, tmio->ccr + CCR_NFDC); + + /* Interrupt status register clear sintst */ + tmio_iowrite8(0x0f, tmio->fcr + FCR_ISR); + + /* After power supply, Media are reset smode */ + tmio_iowrite8(FCR_MODE_POWER_ON, tmio->fcr + FCR_MODE); + tmio_iowrite8(FCR_MODE_COMMAND, tmio->fcr + FCR_MODE); + tmio_iowrite8(NAND_CMD_RESET, tmio->fcr + FCR_DATA); + + /* Standby Mode smode */ + tmio_iowrite8(FCR_MODE_STANDBY, tmio->fcr + FCR_MODE); + + mdelay(5); + + return 0; +} + +static void tmio_hw_stop(struct platform_device *dev, struct tmio_nand *tmio) +{ + struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data; + + tmio_iowrite8(FCR_MODE_POWER_OFF, tmio->fcr + FCR_MODE); + if (cell->disable) + cell->disable(dev); +} + +static int tmio_probe(struct platform_device *dev) +{ + struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data; + struct tmio_nand_data *data = cell->driver_data; + struct resource *fcr = platform_get_resource(dev, + IORESOURCE_MEM, 0); + struct resource *ccr = platform_get_resource(dev, + IORESOURCE_MEM, 1); + int irq = platform_get_irq(dev, 0); + struct tmio_nand *tmio; + struct mtd_info *mtd; + struct nand_chip *nand_chip; +#ifdef CONFIG_MTD_PARTITIONS + struct mtd_partition *parts; + int nbparts = 0; +#endif + int retval; + + if (data == NULL) + dev_warn(&dev->dev, "NULL platform data!\n"); + + 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_phys = (unsigned long)fcr->start; + 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; + + if (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, tmio); + 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 && data) { + parts = data->partition; + nbparts = data->num_partitions; + } + + if (nbparts) + retval = add_mtd_partitions(mtd, parts, nbparts); + else +#endif + retval = add_mtd_device(mtd); + + if (!retval) + return retval; + + nand_release(mtd); + +err_scan: + if (tmio->irq) + free_irq(tmio->irq, tmio); +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 = (struct mfd_cell *)dev->dev.platform_data; + + 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 = (struct mfd_cell *)dev->dev.platform_data; + + /* FIXME - is this required or merely another attack of the broken + * SHARP platform? Looks suspicious. + */ + tmio_hw_init(dev, platform_get_drvdata(dev)); + + if (cell->resume) + cell->resume(dev); + + return 0; +} +#else +#define tmio_suspend NULL +#define tmio_resume NULL +#endif + +static struct platform_driver tmio_driver = { + .driver.name = "tmio-nand", + .driver.owner = THIS_MODULE, + .probe = tmio_probe, + .remove = tmio_remove, + .suspend = tmio_suspend, + .resume = tmio_resume, +}; + +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 v2"); +MODULE_AUTHOR("Ian Molton, Dirk Opfer, Chris Humbert, Dmitry Baryshkov"); +MODULE_DESCRIPTION("NAND flash driver on Toshiba Mobile IO controller"); +MODULE_ALIAS("platform:tmio-nand"); From 4a48998fa16121d0fe3436cce43afd6f47424103 Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Tue, 15 Jul 2008 16:02:21 +0100 Subject: [PATCH 09/11] mfd: TMIO MMC driver This patch adds support for the MMC subdevice 'cell' commonly found in TMIO based MFDs. Signed-off-by: Ian Molton Acked-by: Pierre Ossman Signed-off-by: Samuel Ortiz --- drivers/mmc/host/Kconfig | 6 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/tmio_mmc.c | 691 ++++++++++++++++++++++++++++++++++++ drivers/mmc/host/tmio_mmc.h | 194 ++++++++++ 4 files changed, 892 insertions(+) create mode 100644 drivers/mmc/host/tmio_mmc.c create mode 100644 drivers/mmc/host/tmio_mmc.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index dc6f2579f85c..6fef078a2049 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -174,3 +174,9 @@ config MMC_SDRICOH_CS To compile this driver as a module, choose M here: the module will be called sdricoh_cs. +config MMC_TMIO + tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support" + depends on MFD_CORE + help + This provides support for the SD/MMC cell found in TC6393XB, + T7L66XB and also ipaq ASIC3 diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index db52eebfb50e..c794cc5ce442 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -21,4 +21,5 @@ obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o obj-$(CONFIG_MMC_SPI) += mmc_spi.o obj-$(CONFIG_MMC_S3C) += s3cmci.o obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o +obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c new file mode 100644 index 000000000000..95430b81ec11 --- /dev/null +++ b/drivers/mmc/host/tmio_mmc.c @@ -0,0 +1,691 @@ +/* + * linux/drivers/mmc/tmio_mmc.c + * + * Copyright (C) 2004 Ian Molton + * Copyright (C) 2007 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Driver for the MMC / SD / SDIO cell found in: + * + * TC6393XB TC6391XB TC6387XB T7L66XB + * + * This driver draws mainly on scattered spec sheets, Reverse engineering + * of the toshiba e800 SD driver and some parts of the 2.4 ASIC3 driver (4 bit + * support). (Further 4 bit support from a later datasheet). + * + * TODO: + * Investigate using a workqueue for PIO transfers + * Eliminate FIXMEs + * SDIO support + * Better Power management + * Handle MMC errors better + * double buffer support + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "tmio_mmc.h" + +/* + * Fixme - documentation conflicts on what the clock values are for the + * various dividers. + * One document I have says that its a divisor of a 24MHz clock, another 33. + * This probably depends on HCLK for a given platform, so we may need to + * require HCLK be passed to us from the MFD core. + * + */ + +static void tmio_mmc_set_clock(struct tmio_mmc_host *host, int new_clock) +{ + void __iomem *cnf = host->cnf; + void __iomem *ctl = host->ctl; + u32 clk = 0, clock; + + if (new_clock) { + for (clock = 46875, clk = 0x100; new_clock >= (clock<<1); ) { + clock <<= 1; + clk >>= 1; + } + if (clk & 0x1) + clk = 0x20000; + + clk >>= 2; + tmio_iowrite8((clk & 0x8000) ? 0 : 1, cnf + CNF_SD_CLK_MODE); + clk |= 0x100; + } + + tmio_iowrite16(clk, ctl + CTL_SD_CARD_CLK_CTL); +} + +static void tmio_mmc_clk_stop(struct tmio_mmc_host *host) +{ + void __iomem *ctl = host->ctl; + + tmio_iowrite16(0x0000, ctl + CTL_CLK_AND_WAIT_CTL); + msleep(10); + tmio_iowrite16(tmio_ioread16(ctl + CTL_SD_CARD_CLK_CTL) & ~0x0100, + ctl + CTL_SD_CARD_CLK_CTL); + msleep(10); +} + +static void tmio_mmc_clk_start(struct tmio_mmc_host *host) +{ + void __iomem *ctl = host->ctl; + + tmio_iowrite16(tmio_ioread16(ctl + CTL_SD_CARD_CLK_CTL) | 0x0100, + ctl + CTL_SD_CARD_CLK_CTL); + msleep(10); + tmio_iowrite16(0x0100, ctl + CTL_CLK_AND_WAIT_CTL); + msleep(10); +} + +static void reset(struct tmio_mmc_host *host) +{ + void __iomem *ctl = host->ctl; + + /* FIXME - should we set stop clock reg here */ + tmio_iowrite16(0x0000, ctl + CTL_RESET_SD); + tmio_iowrite16(0x0000, ctl + CTL_RESET_SDIO); + msleep(10); + tmio_iowrite16(0x0001, ctl + CTL_RESET_SD); + tmio_iowrite16(0x0001, ctl + CTL_RESET_SDIO); + msleep(10); +} + +static void +tmio_mmc_finish_request(struct tmio_mmc_host *host) +{ + struct mmc_request *mrq = host->mrq; + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + mmc_request_done(host->mmc, mrq); +} + +/* These are the bitmasks the tmio chip requires to implement the MMC response + * types. Note that R1 and R6 are the same in this scheme. */ +#define APP_CMD 0x0040 +#define RESP_NONE 0x0300 +#define RESP_R1 0x0400 +#define RESP_R1B 0x0500 +#define RESP_R2 0x0600 +#define RESP_R3 0x0700 +#define DATA_PRESENT 0x0800 +#define TRANSFER_READ 0x1000 +#define TRANSFER_MULTI 0x2000 +#define SECURITY_CMD 0x4000 + +static int +tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command *cmd) +{ + void __iomem *ctl = host->ctl; + struct mmc_data *data = host->data; + int c = cmd->opcode; + + /* Command 12 is handled by hardware */ + if (cmd->opcode == 12 && !cmd->arg) { + tmio_iowrite16(0x001, ctl + CTL_STOP_INTERNAL_ACTION); + return 0; + } + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_NONE: c |= RESP_NONE; break; + case MMC_RSP_R1: c |= RESP_R1; break; + case MMC_RSP_R1B: c |= RESP_R1B; break; + case MMC_RSP_R2: c |= RESP_R2; break; + case MMC_RSP_R3: c |= RESP_R3; break; + default: + pr_debug("Unknown response type %d\n", mmc_resp_type(cmd)); + return -EINVAL; + } + + host->cmd = cmd; + +/* FIXME - this seems to be ok comented out but the spec suggest this bit should + * be set when issuing app commands. + * if(cmd->flags & MMC_FLAG_ACMD) + * c |= APP_CMD; + */ + if (data) { + c |= DATA_PRESENT; + if (data->blocks > 1) { + tmio_iowrite16(0x100, ctl + CTL_STOP_INTERNAL_ACTION); + c |= TRANSFER_MULTI; + } + if (data->flags & MMC_DATA_READ) + c |= TRANSFER_READ; + } + + enable_mmc_irqs(ctl, TMIO_MASK_CMD); + + /* Fire off the command */ + tmio_iowrite32(cmd->arg, ctl + CTL_ARG_REG); + tmio_iowrite16(c, ctl + CTL_SD_CMD); + + return 0; +} + +/* This chip always returns (at least?) as much data as you ask for. + * I'm unsure what happens if you ask for less than a block. This should be + * looked into to ensure that a funny length read doesnt hose the controller. + * + */ +static inline void tmio_mmc_pio_irq(struct tmio_mmc_host *host) +{ + void __iomem *ctl = host->ctl; + struct mmc_data *data = host->data; + unsigned short *buf; + unsigned int count; + unsigned long flags; + + if (!data) { + pr_debug("Spurious PIO IRQ\n"); + return; + } + + buf = (unsigned short *)(tmio_mmc_kmap_atomic(host, &flags) + + host->sg_off); + + count = host->sg_ptr->length - host->sg_off; + if (count > data->blksz) + count = data->blksz; + + pr_debug("count: %08x offset: %08x flags %08x\n", + count, host->sg_off, data->flags); + + /* Transfer the data */ + if (data->flags & MMC_DATA_READ) + tmio_ioread16_rep(ctl + CTL_SD_DATA_PORT, buf, count >> 1); + else + tmio_iowrite16_rep(ctl + CTL_SD_DATA_PORT, buf, count >> 1); + + host->sg_off += count; + + tmio_mmc_kunmap_atomic(host, &flags); + + if (host->sg_off == host->sg_ptr->length) + tmio_mmc_next_sg(host); + + return; +} + +static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host) +{ + void __iomem *ctl = host->ctl; + struct mmc_data *data = host->data; + struct mmc_command *stop = data->stop; + + host->data = NULL; + + if (!data) { + pr_debug("Spurious data end IRQ\n"); + return; + } + + /* FIXME - return correct transfer count on errors */ + if (!data->error) + data->bytes_xfered = data->blocks * data->blksz; + else + data->bytes_xfered = 0; + + pr_debug("Completed data request\n"); + + /*FIXME - other drivers allow an optional stop command of any given type + * which we dont do, as the chip can auto generate them. + * Perhaps we can be smarter about when to use auto CMD12 and + * only issue the auto request when we know this is the desired + * stop command, allowing fallback to the stop command the + * upper layers expect. For now, we do what works. + */ + + if (data->flags & MMC_DATA_READ) + disable_mmc_irqs(ctl, TMIO_MASK_READOP); + else + disable_mmc_irqs(ctl, TMIO_MASK_WRITEOP); + + if (stop) { + if (stop->opcode == 12 && !stop->arg) + tmio_iowrite16(0x000, ctl + CTL_STOP_INTERNAL_ACTION); + else + BUG(); + } + + tmio_mmc_finish_request(host); +} + +static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host, + unsigned int stat) +{ + void __iomem *ctl = host->ctl, *addr; + struct mmc_command *cmd = host->cmd; + int i; + + if (!host->cmd) { + pr_debug("Spurious CMD irq\n"); + return; + } + + host->cmd = NULL; + + /* This controller is sicker than the PXA one. Not only do we need to + * drop the top 8 bits of the first response word, we also need to + * modify the order of the response for short response command types. + */ + + for (i = 3, addr = ctl + CTL_RESPONSE ; i >= 0 ; i--, addr += 4) + cmd->resp[i] = tmio_ioread32(addr); + + if (cmd->flags & MMC_RSP_136) { + cmd->resp[0] = (cmd->resp[0] << 8) | (cmd->resp[1] >> 24); + cmd->resp[1] = (cmd->resp[1] << 8) | (cmd->resp[2] >> 24); + cmd->resp[2] = (cmd->resp[2] << 8) | (cmd->resp[3] >> 24); + cmd->resp[3] <<= 8; + } else if (cmd->flags & MMC_RSP_R3) { + cmd->resp[0] = cmd->resp[3]; + } + + if (stat & TMIO_STAT_CMDTIMEOUT) + cmd->error = -ETIMEDOUT; + else if (stat & TMIO_STAT_CRCFAIL && cmd->flags & MMC_RSP_CRC) + cmd->error = -EILSEQ; + + /* If there is data to handle we enable data IRQs here, and + * we will ultimatley finish the request in the data_end handler. + * If theres no data or we encountered an error, finish now. + */ + if (host->data && !cmd->error) { + if (host->data->flags & MMC_DATA_READ) + enable_mmc_irqs(ctl, TMIO_MASK_READOP); + else + enable_mmc_irqs(ctl, TMIO_MASK_WRITEOP); + } else { + tmio_mmc_finish_request(host); + } + + return; +} + + +static irqreturn_t tmio_mmc_irq(int irq, void *devid) +{ + struct tmio_mmc_host *host = devid; + void __iomem *ctl = host->ctl; + unsigned int ireg, irq_mask, status; + + pr_debug("MMC IRQ begin\n"); + + status = tmio_ioread32(ctl + CTL_STATUS); + irq_mask = tmio_ioread32(ctl + CTL_IRQ_MASK); + ireg = status & TMIO_MASK_IRQ & ~irq_mask; + + pr_debug_status(status); + pr_debug_status(ireg); + + if (!ireg) { + disable_mmc_irqs(ctl, status & ~irq_mask); + + pr_debug("tmio_mmc: Spurious irq, disabling! " + "0x%08x 0x%08x 0x%08x\n", status, irq_mask, ireg); + pr_debug_status(status); + + goto out; + } + + while (ireg) { + /* Card insert / remove attempts */ + if (ireg & (TMIO_STAT_CARD_INSERT | TMIO_STAT_CARD_REMOVE)) { + ack_mmc_irqs(ctl, TMIO_STAT_CARD_INSERT | + TMIO_STAT_CARD_REMOVE); + mmc_detect_change(host->mmc, 0); + } + + /* CRC and other errors */ +/* if (ireg & TMIO_STAT_ERR_IRQ) + * handled |= tmio_error_irq(host, irq, stat); + */ + + /* Command completion */ + if (ireg & TMIO_MASK_CMD) { + ack_mmc_irqs(ctl, TMIO_MASK_CMD); + tmio_mmc_cmd_irq(host, status); + } + + /* Data transfer */ + if (ireg & (TMIO_STAT_RXRDY | TMIO_STAT_TXRQ)) { + ack_mmc_irqs(ctl, TMIO_STAT_RXRDY | TMIO_STAT_TXRQ); + tmio_mmc_pio_irq(host); + } + + /* Data transfer completion */ + if (ireg & TMIO_STAT_DATAEND) { + ack_mmc_irqs(ctl, TMIO_STAT_DATAEND); + tmio_mmc_data_irq(host); + } + + /* Check status - keep going until we've handled it all */ + status = tmio_ioread32(ctl + CTL_STATUS); + irq_mask = tmio_ioread32(ctl + CTL_IRQ_MASK); + ireg = status & TMIO_MASK_IRQ & ~irq_mask; + + pr_debug("Status at end of loop: %08x\n", status); + pr_debug_status(status); + } + pr_debug("MMC IRQ end\n"); + +out: + return IRQ_HANDLED; +} + +static int tmio_mmc_start_data(struct tmio_mmc_host *host, + struct mmc_data *data) +{ + void __iomem *ctl = host->ctl; + + pr_debug("setup data transfer: blocksize %08x nr_blocks %d\n", + data->blksz, data->blocks); + + /* Hardware cannot perform 1 and 2 byte requests in 4 bit mode */ + if (data->blksz < 4 && host->mmc->ios.bus_width == MMC_BUS_WIDTH_4) { + printk(KERN_ERR "%s: %d byte block unsupported in 4 bit mode\n", + mmc_hostname(host->mmc), data->blksz); + return -EINVAL; + } + + tmio_mmc_init_sg(host, data); + host->data = data; + + /* Set transfer length / blocksize */ + tmio_iowrite16(data->blksz, ctl + CTL_SD_XFER_LEN); + tmio_iowrite16(data->blocks, ctl + CTL_XFER_BLK_COUNT); + + return 0; +} + +/* Process requests from the MMC layer */ +static void tmio_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct tmio_mmc_host *host = mmc_priv(mmc); + int ret; + + if (host->mrq) + pr_debug("request not null\n"); + + host->mrq = mrq; + + if (mrq->data) { + ret = tmio_mmc_start_data(host, mrq->data); + if (ret) + goto fail; + } + + ret = tmio_mmc_start_command(host, mrq->cmd); + + if (!ret) + return; + +fail: + mrq->cmd->error = ret; + mmc_request_done(mmc, mrq); +} + +/* Set MMC clock / power. + * Note: This controller uses a simple divider scheme therefore it cannot + * run a MMC card at full speed (20MHz). The max clock is 24MHz on SD, but as + * MMC wont run that fast, it has to be clocked at 12MHz which is the next + * slowest setting. + */ +static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct tmio_mmc_host *host = mmc_priv(mmc); + void __iomem *cnf = host->cnf; + void __iomem *ctl = host->ctl; + + if (ios->clock) + tmio_mmc_set_clock(host, ios->clock); + + /* Power sequence - OFF -> ON -> UP */ + switch (ios->power_mode) { + case MMC_POWER_OFF: /* power down SD bus */ + tmio_iowrite8(0x00, cnf + CNF_PWR_CTL_2); + tmio_mmc_clk_stop(host); + break; + case MMC_POWER_ON: /* power up SD bus */ + + tmio_iowrite8(0x02, cnf + CNF_PWR_CTL_2); + break; + case MMC_POWER_UP: /* start bus clock */ + tmio_mmc_clk_start(host); + break; + } + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + tmio_iowrite16(0x80e0, ctl + CTL_SD_MEM_CARD_OPT); + break; + case MMC_BUS_WIDTH_4: + tmio_iowrite16(0x00e0, ctl + CTL_SD_MEM_CARD_OPT); + break; + } + + /* Let things settle. delay taken from winCE driver */ + udelay(140); +} + +static int tmio_mmc_get_ro(struct mmc_host *mmc) +{ + struct tmio_mmc_host *host = mmc_priv(mmc); + void __iomem *ctl = host->ctl; + + return (tmio_ioread16(ctl + CTL_STATUS) & TMIO_STAT_WRPROTECT) ? 0 : 1; +} + +static struct mmc_host_ops tmio_mmc_ops = { + .request = tmio_mmc_request, + .set_ios = tmio_mmc_set_ios, + .get_ro = tmio_mmc_get_ro, +}; + +#ifdef CONFIG_PM +static int tmio_mmc_suspend(struct platform_device *dev, pm_message_t state) +{ + struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data; + struct mmc_host *mmc = platform_get_drvdata(dev); + int ret; + + ret = mmc_suspend_host(mmc, state); + + /* Tell MFD core it can disable us now.*/ + if (!ret && cell->disable) + cell->disable(dev); + + return ret; +} + +static int tmio_mmc_resume(struct platform_device *dev) +{ + struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data; + struct mmc_host *mmc = platform_get_drvdata(dev); + struct tmio_mmc_host *host = mmc_priv(mmc); + void __iomem *cnf = host->cnf; + int ret = 0; + + /* Enable the MMC/SD Control registers */ + tmio_iowrite16(SDCREN, cnf + CNF_CMD); + tmio_iowrite32(dev->resource[0].start & 0xfffe, cnf + CNF_CTL_BASE); + + /* Tell the MFD core we are ready to be enabled */ + if (cell->enable) { + ret = cell->enable(dev); + if (ret) + goto out; + } + + mmc_resume_host(mmc); + +out: + return ret; +} +#else +#define tmio_mmc_suspend NULL +#define tmio_mmc_resume NULL +#endif + +static int __devinit tmio_mmc_probe(struct platform_device *dev) +{ + struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data; + struct resource *res_ctl, *res_cnf; + struct tmio_mmc_host *host; + struct mmc_host *mmc; + int ret = -ENOMEM; + + if (dev->num_resources != 3) + goto out; + + res_ctl = platform_get_resource(dev, IORESOURCE_MEM, 0); + res_cnf = platform_get_resource(dev, IORESOURCE_MEM, 1); + if (!res_ctl || !res_cnf) { + ret = -EINVAL; + goto out; + } + + mmc = mmc_alloc_host(sizeof(struct tmio_mmc_host), &dev->dev); + if (!mmc) + goto out; + + host = mmc_priv(mmc); + host->mmc = mmc; + platform_set_drvdata(dev, mmc); + + host->ctl = ioremap(res_ctl->start, res_ctl->end - res_ctl->start); + if (!host->ctl) + goto host_free; + + host->cnf = ioremap(res_cnf->start, res_cnf->end - res_cnf->start); + if (!host->cnf) + goto unmap_ctl; + + mmc->ops = &tmio_mmc_ops; + mmc->caps = MMC_CAP_4_BIT_DATA; + mmc->f_min = 46875; /* 24000000 / 512 */ + mmc->f_max = 24000000; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + /* Enable the MMC/SD Control registers */ + tmio_iowrite16(SDCREN, host->cnf + CNF_CMD); + tmio_iowrite32(dev->resource[0].start & 0xfffe, + host->cnf + CNF_CTL_BASE); + + /* Tell the MFD core we are ready to be enabled */ + if (cell->enable) { + ret = cell->enable(dev); + if (ret) + goto unmap_cnf; + } + + /* Disable SD power during suspend */ + tmio_iowrite8(0x01, host->cnf + CNF_PWR_CTL_3); + + /* The below is required but why? FIXME */ + tmio_iowrite8(0x1f, host->cnf + CNF_STOP_CLK_CTL); + + /* Power down SD bus*/ + tmio_iowrite8(0x0, host->cnf + CNF_PWR_CTL_2); + + tmio_mmc_clk_stop(host); + reset(host); + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + host->irq = ret; + else + goto unmap_cnf; + + disable_mmc_irqs(host->ctl, TMIO_MASK_ALL); + + ret = request_irq(host->irq, tmio_mmc_irq, IRQF_DISABLED, "tmio-mmc", + host); + if (ret) + goto unmap_cnf; + + set_irq_type(host->irq, IRQ_TYPE_EDGE_FALLING); + + mmc_add_host(mmc); + + printk(KERN_INFO "%s at 0x%08lx irq %d\n", mmc_hostname(host->mmc), + (unsigned long)host->ctl, host->irq); + + /* Unmask the IRQs we want to know about */ + enable_mmc_irqs(host->ctl, TMIO_MASK_IRQ); + + return 0; + +unmap_cnf: + iounmap(host->cnf); +unmap_ctl: + iounmap(host->ctl); +host_free: + mmc_free_host(mmc); +out: + return ret; +} + +static int __devexit tmio_mmc_remove(struct platform_device *dev) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + + platform_set_drvdata(dev, NULL); + + if (mmc) { + struct tmio_mmc_host *host = mmc_priv(mmc); + mmc_remove_host(mmc); + mmc_free_host(mmc); + free_irq(host->irq, host); + iounmap(host->ctl); + iounmap(host->cnf); + } + + return 0; +} + +/* ------------------- device registration ----------------------- */ + +static struct platform_driver tmio_mmc_driver = { + .driver = { + .name = "tmio-mmc", + .owner = THIS_MODULE, + }, + .probe = tmio_mmc_probe, + .remove = __devexit_p(tmio_mmc_remove), + .suspend = tmio_mmc_suspend, + .resume = tmio_mmc_resume, +}; + + +static int __init tmio_mmc_init(void) +{ + return platform_driver_register(&tmio_mmc_driver); +} + +static void __exit tmio_mmc_exit(void) +{ + platform_driver_unregister(&tmio_mmc_driver); +} + +module_init(tmio_mmc_init); +module_exit(tmio_mmc_exit); + +MODULE_DESCRIPTION("Toshiba TMIO SD/MMC driver"); +MODULE_AUTHOR("Ian Molton "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:tmio-mmc"); diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h new file mode 100644 index 000000000000..9e647a06054f --- /dev/null +++ b/drivers/mmc/host/tmio_mmc.h @@ -0,0 +1,194 @@ +/* Definitons for use with the tmio_mmc.c + * + * (c) 2004 Ian Molton + * (c) 2007 Ian Molton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#define CNF_CMD 0x04 +#define CNF_CTL_BASE 0x10 +#define CNF_INT_PIN 0x3d +#define CNF_STOP_CLK_CTL 0x40 +#define CNF_GCLK_CTL 0x41 +#define CNF_SD_CLK_MODE 0x42 +#define CNF_PIN_STATUS 0x44 +#define CNF_PWR_CTL_1 0x48 +#define CNF_PWR_CTL_2 0x49 +#define CNF_PWR_CTL_3 0x4a +#define CNF_CARD_DETECT_MODE 0x4c +#define CNF_SD_SLOT 0x50 +#define CNF_EXT_GCLK_CTL_1 0xf0 +#define CNF_EXT_GCLK_CTL_2 0xf1 +#define CNF_EXT_GCLK_CTL_3 0xf9 +#define CNF_SD_LED_EN_1 0xfa +#define CNF_SD_LED_EN_2 0xfe + +#define SDCREN 0x2 /* Enable access to MMC CTL regs. (flag in COMMAND_REG)*/ + +#define CTL_SD_CMD 0x00 +#define CTL_ARG_REG 0x04 +#define CTL_STOP_INTERNAL_ACTION 0x08 +#define CTL_XFER_BLK_COUNT 0xa +#define CTL_RESPONSE 0x0c +#define CTL_STATUS 0x1c +#define CTL_IRQ_MASK 0x20 +#define CTL_SD_CARD_CLK_CTL 0x24 +#define CTL_SD_XFER_LEN 0x26 +#define CTL_SD_MEM_CARD_OPT 0x28 +#define CTL_SD_ERROR_DETAIL_STATUS 0x2c +#define CTL_SD_DATA_PORT 0x30 +#define CTL_TRANSACTION_CTL 0x34 +#define CTL_RESET_SD 0xe0 +#define CTL_SDIO_REGS 0x100 +#define CTL_CLK_AND_WAIT_CTL 0x138 +#define CTL_RESET_SDIO 0x1e0 + +/* Definitions for values the CTRL_STATUS register can take. */ +#define TMIO_STAT_CMDRESPEND 0x00000001 +#define TMIO_STAT_DATAEND 0x00000004 +#define TMIO_STAT_CARD_REMOVE 0x00000008 +#define TMIO_STAT_CARD_INSERT 0x00000010 +#define TMIO_STAT_SIGSTATE 0x00000020 +#define TMIO_STAT_WRPROTECT 0x00000080 +#define TMIO_STAT_CARD_REMOVE_A 0x00000100 +#define TMIO_STAT_CARD_INSERT_A 0x00000200 +#define TMIO_STAT_SIGSTATE_A 0x00000400 +#define TMIO_STAT_CMD_IDX_ERR 0x00010000 +#define TMIO_STAT_CRCFAIL 0x00020000 +#define TMIO_STAT_STOPBIT_ERR 0x00040000 +#define TMIO_STAT_DATATIMEOUT 0x00080000 +#define TMIO_STAT_RXOVERFLOW 0x00100000 +#define TMIO_STAT_TXUNDERRUN 0x00200000 +#define TMIO_STAT_CMDTIMEOUT 0x00400000 +#define TMIO_STAT_RXRDY 0x01000000 +#define TMIO_STAT_TXRQ 0x02000000 +#define TMIO_STAT_ILL_FUNC 0x20000000 +#define TMIO_STAT_CMD_BUSY 0x40000000 +#define TMIO_STAT_ILL_ACCESS 0x80000000 + +/* Define some IRQ masks */ +/* This is the mask used at reset by the chip */ +#define TMIO_MASK_ALL 0x837f031d +#define TMIO_MASK_READOP (TMIO_STAT_RXRDY | TMIO_STAT_DATAEND | \ + TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT) +#define TMIO_MASK_WRITEOP (TMIO_STAT_TXRQ | TMIO_STAT_DATAEND | \ + TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT) +#define TMIO_MASK_CMD (TMIO_STAT_CMDRESPEND | TMIO_STAT_CMDTIMEOUT | \ + TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT) +#define TMIO_MASK_IRQ (TMIO_MASK_READOP | TMIO_MASK_WRITEOP | TMIO_MASK_CMD) + +#define enable_mmc_irqs(ctl, i) \ + do { \ + u32 mask;\ + mask = tmio_ioread32((ctl) + CTL_IRQ_MASK); \ + mask &= ~((i) & TMIO_MASK_IRQ); \ + tmio_iowrite32(mask, (ctl) + CTL_IRQ_MASK); \ + } while (0) + +#define disable_mmc_irqs(ctl, i) \ + do { \ + u32 mask;\ + mask = tmio_ioread32((ctl) + CTL_IRQ_MASK); \ + mask |= ((i) & TMIO_MASK_IRQ); \ + tmio_iowrite32(mask, (ctl) + CTL_IRQ_MASK); \ + } while (0) + +#define ack_mmc_irqs(ctl, i) \ + do { \ + u32 mask;\ + mask = tmio_ioread32((ctl) + CTL_STATUS); \ + mask &= ~((i) & TMIO_MASK_IRQ); \ + tmio_iowrite32(mask, (ctl) + CTL_STATUS); \ + } while (0) + + +struct tmio_mmc_host { + void __iomem *cnf; + void __iomem *ctl; + struct mmc_command *cmd; + struct mmc_request *mrq; + struct mmc_data *data; + struct mmc_host *mmc; + int irq; + + /* pio related stuff */ + struct scatterlist *sg_ptr; + unsigned int sg_len; + unsigned int sg_off; +}; + +#include +#include + +static inline void tmio_mmc_init_sg(struct tmio_mmc_host *host, + struct mmc_data *data) +{ + host->sg_len = data->sg_len; + host->sg_ptr = data->sg; + host->sg_off = 0; +} + +static inline int tmio_mmc_next_sg(struct tmio_mmc_host *host) +{ + host->sg_ptr = sg_next(host->sg_ptr); + host->sg_off = 0; + return --host->sg_len; +} + +static inline char *tmio_mmc_kmap_atomic(struct tmio_mmc_host *host, + unsigned long *flags) +{ + struct scatterlist *sg = host->sg_ptr; + + local_irq_save(*flags); + return kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset; +} + +static inline void tmio_mmc_kunmap_atomic(struct tmio_mmc_host *host, + unsigned long *flags) +{ + kunmap_atomic(sg_page(host->sg_ptr), KM_BIO_SRC_IRQ); + local_irq_restore(*flags); +} + +#ifdef CONFIG_MMC_DEBUG + +#define STATUS_TO_TEXT(a) \ + do { \ + if (status & TMIO_STAT_##a) \ + printf(#a); \ + } while (0) + +void debug_status(u32 status) +{ + printk(KERN_DEBUG "status: %08x = ", status); + STATUS_TO_TEXT(CARD_REMOVE); + STATUS_TO_TEXT(CARD_INSERT); + STATUS_TO_TEXT(SIGSTATE); + STATUS_TO_TEXT(WRPROTECT); + STATUS_TO_TEXT(CARD_REMOVE_A); + STATUS_TO_TEXT(CARD_INSERT_A); + STATUS_TO_TEXT(SIGSTATE_A); + STATUS_TO_TEXT(CMD_IDX_ERR); + STATUS_TO_TEXT(STOPBIT_ERR); + STATUS_TO_TEXT(ILL_FUNC); + STATUS_TO_TEXT(CMD_BUSY); + STATUS_TO_TEXT(CMDRESPEND); + STATUS_TO_TEXT(DATAEND); + STATUS_TO_TEXT(CRCFAIL); + STATUS_TO_TEXT(DATATIMEOUT); + STATUS_TO_TEXT(CMDTIMEOUT); + STATUS_TO_TEXT(RXOVERFLOW); + STATUS_TO_TEXT(TXUNDERRUN); + STATUS_TO_TEXT(RXRDY); + STATUS_TO_TEXT(TXRQ); + STATUS_TO_TEXT(ILL_ACCESS); + printk("\n"); +} + +#else +#define pr_debug_status(s) do { } while (0) +#endif From 1c2c30acc52320d506d722f41d50e8eb8fda5cb5 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Tue, 5 Aug 2008 19:27:58 +0200 Subject: [PATCH 10/11] mfd: have TMIO drivers and subdevices depend on ARM The TMIO chips are only found (and thus tested) on ARM machines. Moreover, we don't want the TMIO cells to be built if one of the TMIO driver is not selected (which indirectly make the TMIO cells drivers depend on ARM as well). Signed-off-by: Samuel Ortiz --- drivers/mfd/Kconfig | 9 +++++++++ drivers/mmc/host/Kconfig | 2 +- drivers/mtd/nand/Kconfig | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 5beff5b7ef2e..10c44d3fe01a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -50,15 +50,23 @@ config HTC_PASIC3 HTC Magician devices, respectively. Actual functionality is handled by the leds-pasic3 and ds1wm drivers. +config MFD_TMIO + bool + default n + config MFD_T7L66XB bool "Support Toshiba T7L66XB" + depends on ARM select MFD_CORE + select MFD_TMIO help Support for Toshiba Mobile IO Controller T7L66XB config MFD_TC6387XB bool "Support Toshiba TC6387XB" + depends on ARM select MFD_CORE + select MFD_TMIO help Support for Toshiba Mobile IO Controller TC6387XB @@ -66,6 +74,7 @@ config MFD_TC6393XB bool "Support Toshiba TC6393XB" depends on GPIOLIB && ARM select MFD_CORE + select MFD_TMIO help Support for Toshiba Mobile IO Controller TC6393XB diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 6fef078a2049..ea8d7a3490d9 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -176,7 +176,7 @@ config MMC_SDRICOH_CS config MMC_TMIO tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support" - depends on MFD_CORE + depends on MFD_TMIO help This provides support for the SD/MMC cell found in TC6393XB, T7L66XB and also ipaq ASIC3 diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index fd6debb24252..41f361c49b32 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -353,7 +353,7 @@ config MTD_NAND_PASEMI config MTD_NAND_TMIO tristate "NAND Flash device on Toshiba Mobile IO Controller" - depends on MTD_NAND && MFD_CORE + depends on MTD_NAND && MFD_TMIO help Support for NAND flash connected to a Toshiba Mobile IO Controller in some PDAs, including the Sharp SL6000x. From 25d6cbd840d958aada29a342c9ee370590ff7b21 Mon Sep 17 00:00:00 2001 From: Ian Molton Date: Sun, 10 Aug 2008 23:32:07 +0200 Subject: [PATCH 11/11] mfd: tc6393 cleanup and update This patchset cleans up the TC6393XB support. * Add provision for the MMC subdevice * Disable / enable clocks on suspend / resume * Remove fragments of badly merged code (eg. linux/fb include etc.) * Use a device specific clock name to break dependancy on ARM/PXA2XX * Drop unnecessary resource names * Switch to tmio_io* accessors Signed-off-by: Ian Molton Signed-off-by: Samuel Ortiz --- drivers/mfd/tc6393xb.c | 158 +++++++++++++++++++++-------------- include/linux/mfd/tc6393xb.h | 9 +- 2 files changed, 97 insertions(+), 70 deletions(-) diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c index 81e2605ea10d..e4c1c788b5f8 100644 --- a/drivers/mfd/tc6393xb.c +++ b/drivers/mfd/tc6393xb.c @@ -19,8 +19,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -112,6 +112,7 @@ struct tc6393xb { enum { TC6393XB_CELL_NAND, + TC6393XB_CELL_MMC, }; /*--------------------------------------------------------------------------*/ @@ -126,7 +127,7 @@ static int tc6393xb_nand_enable(struct platform_device *nand) /* SMD buffer on */ dev_dbg(&dev->dev, "SMD buffer on\n"); - iowrite8(0xff, tc6393xb->scr + SCR_GPI_BCR(1)); + tmio_iowrite8(0xff, tc6393xb->scr + SCR_GPI_BCR(1)); spin_unlock_irqrestore(&tc6393xb->lock, flags); @@ -134,16 +135,16 @@ static int tc6393xb_nand_enable(struct platform_device *nand) } static struct resource __devinitdata tc6393xb_nand_resources[] = { - { - .start = 0x0100, - .end = 0x01ff, - .flags = IORESOURCE_MEM, - }, { .start = 0x1000, .end = 0x1007, .flags = IORESOURCE_MEM, }, + { + .start = 0x0100, + .end = 0x01ff, + .flags = IORESOURCE_MEM, + }, { .start = IRQ_TC6393_NAND, .end = IRQ_TC6393_NAND, @@ -151,6 +152,24 @@ static struct resource __devinitdata tc6393xb_nand_resources[] = { }, }; +static struct resource __devinitdata tc6393xb_mmc_resources[] = { + { + .start = 0x800, + .end = 0x9ff, + .flags = IORESOURCE_MEM, + }, + { + .start = 0x200, + .end = 0x2ff, + .flags = IORESOURCE_MEM, + }, + { + .start = IRQ_TC6393_MMC, + .end = IRQ_TC6393_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + static struct mfd_cell __devinitdata tc6393xb_cells[] = { [TC6393XB_CELL_NAND] = { .name = "tmio-nand", @@ -158,6 +177,11 @@ static struct mfd_cell __devinitdata tc6393xb_cells[] = { .num_resources = ARRAY_SIZE(tc6393xb_nand_resources), .resources = tc6393xb_nand_resources, }, + [TC6393XB_CELL_MMC] = { + .name = "tmio-mmc", + .num_resources = ARRAY_SIZE(tc6393xb_mmc_resources), + .resources = tc6393xb_mmc_resources, + }, }; /*--------------------------------------------------------------------------*/ @@ -168,7 +192,7 @@ static int tc6393xb_gpio_get(struct gpio_chip *chip, struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); /* XXX: does dsr also represent inputs? */ - return ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)) + return tmio_ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)) & TC_GPIO_BIT(offset); } @@ -178,13 +202,13 @@ static void __tc6393xb_gpio_set(struct gpio_chip *chip, struct tc6393xb *tc6393xb = container_of(chip, struct tc6393xb, gpio); u8 dsr; - dsr = ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)); + dsr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DSR(offset / 8)); if (value) dsr |= TC_GPIO_BIT(offset); else dsr &= ~TC_GPIO_BIT(offset); - iowrite8(dsr, tc6393xb->scr + SCR_GPO_DSR(offset / 8)); + tmio_iowrite8(dsr, tc6393xb->scr + SCR_GPO_DSR(offset / 8)); } static void tc6393xb_gpio_set(struct gpio_chip *chip, @@ -209,9 +233,9 @@ static int tc6393xb_gpio_direction_input(struct gpio_chip *chip, spin_lock_irqsave(&tc6393xb->lock, flags); - doecr = ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + doecr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); doecr &= ~TC_GPIO_BIT(offset); - iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + tmio_iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); spin_unlock_irqrestore(&tc6393xb->lock, flags); @@ -229,9 +253,9 @@ static int tc6393xb_gpio_direction_output(struct gpio_chip *chip, __tc6393xb_gpio_set(chip, offset, value); - doecr = ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + doecr = tmio_ioread8(tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); doecr |= TC_GPIO_BIT(offset); - iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); + tmio_iowrite8(doecr, tc6393xb->scr + SCR_GPO_DOECR(offset / 8)); spin_unlock_irqrestore(&tc6393xb->lock, flags); @@ -262,8 +286,8 @@ tc6393xb_irq(unsigned int irq, struct irq_desc *desc) irq_base = tc6393xb->irq_base; - while ((isr = ioread8(tc6393xb->scr + SCR_ISR) & - ~ioread8(tc6393xb->scr + SCR_IMR))) + while ((isr = tmio_ioread8(tc6393xb->scr + SCR_ISR) & + ~tmio_ioread8(tc6393xb->scr + SCR_IMR))) for (i = 0; i < TC6393XB_NR_IRQS; i++) { if (isr & (1 << i)) generic_handle_irq(irq_base + i); @@ -281,9 +305,9 @@ static void tc6393xb_irq_mask(unsigned int irq) u8 imr; spin_lock_irqsave(&tc6393xb->lock, flags); - imr = ioread8(tc6393xb->scr + SCR_IMR); + imr = tmio_ioread8(tc6393xb->scr + SCR_IMR); imr |= 1 << (irq - tc6393xb->irq_base); - iowrite8(imr, tc6393xb->scr + SCR_IMR); + tmio_iowrite8(imr, tc6393xb->scr + SCR_IMR); spin_unlock_irqrestore(&tc6393xb->lock, flags); } @@ -294,9 +318,9 @@ static void tc6393xb_irq_unmask(unsigned int irq) u8 imr; spin_lock_irqsave(&tc6393xb->lock, flags); - imr = ioread8(tc6393xb->scr + SCR_IMR); + imr = tmio_ioread8(tc6393xb->scr + SCR_IMR); imr &= ~(1 << (irq - tc6393xb->irq_base)); - iowrite8(imr, tc6393xb->scr + SCR_IMR); + tmio_iowrite8(imr, tc6393xb->scr + SCR_IMR); spin_unlock_irqrestore(&tc6393xb->lock, flags); } @@ -377,9 +401,8 @@ static int __devinit tc6393xb_probe(struct platform_device *dev) { struct tc6393xb_platform_data *tcpd = dev->dev.platform_data; struct tc6393xb *tc6393xb; - struct resource *iomem; - struct resource *rscr; - int retval, temp; + struct resource *iomem, *rscr; + int ret, temp; int i; iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); @@ -388,20 +411,26 @@ static int __devinit tc6393xb_probe(struct platform_device *dev) tc6393xb = kzalloc(sizeof *tc6393xb, GFP_KERNEL); if (!tc6393xb) { - retval = -ENOMEM; + ret = -ENOMEM; goto err_kzalloc; } spin_lock_init(&tc6393xb->lock); platform_set_drvdata(dev, tc6393xb); + + ret = platform_get_irq(dev, 0); + if (ret >= 0) + tc6393xb->irq = ret; + else + goto err_noirq; + tc6393xb->iomem = iomem; - tc6393xb->irq = platform_get_irq(dev, 0); tc6393xb->irq_base = tcpd->irq_base; - tc6393xb->clk = clk_get(&dev->dev, "GPIO27_CLK" /* "CK3P6MI" */); + tc6393xb->clk = clk_get(&dev->dev, "CLK_CK3P6MI"); if (IS_ERR(tc6393xb->clk)) { - retval = PTR_ERR(tc6393xb->clk); + ret = PTR_ERR(tc6393xb->clk); goto err_clk_get; } @@ -411,71 +440,73 @@ static int __devinit tc6393xb_probe(struct platform_device *dev) rscr->end = iomem->start + 0xff; rscr->flags = IORESOURCE_MEM; - retval = request_resource(iomem, rscr); - if (retval) + ret = request_resource(iomem, rscr); + if (ret) goto err_request_scr; tc6393xb->scr = ioremap(rscr->start, rscr->end - rscr->start + 1); if (!tc6393xb->scr) { - retval = -ENOMEM; + ret = -ENOMEM; goto err_ioremap; } - retval = clk_enable(tc6393xb->clk); - if (retval) + ret = clk_enable(tc6393xb->clk); + if (ret) goto err_clk_enable; - retval = tcpd->enable(dev); - if (retval) + ret = tcpd->enable(dev); + if (ret) goto err_enable; tc6393xb->suspend_state.fer = 0; + for (i = 0; i < 3; i++) { tc6393xb->suspend_state.gpo_dsr[i] = (tcpd->scr_gpo_dsr >> (8 * i)) & 0xff; tc6393xb->suspend_state.gpo_doecr[i] = (tcpd->scr_gpo_doecr >> (8 * i)) & 0xff; } - /* - * It may be necessary to change this back to - * platform-dependant code - */ + tc6393xb->suspend_state.ccr = SCR_CCR_UNK1 | SCR_CCR_HCLK_48; - retval = tc6393xb_hw_init(dev); - if (retval) + ret = tc6393xb_hw_init(dev); + if (ret) goto err_hw_init; printk(KERN_INFO "Toshiba tc6393xb revision %d at 0x%08lx, irq %d\n", - ioread8(tc6393xb->scr + SCR_REVID), + tmio_ioread8(tc6393xb->scr + SCR_REVID), (unsigned long) iomem->start, tc6393xb->irq); tc6393xb->gpio.base = -1; if (tcpd->gpio_base >= 0) { - retval = tc6393xb_register_gpio(tc6393xb, tcpd->gpio_base); - if (retval) + ret = tc6393xb_register_gpio(tc6393xb, tcpd->gpio_base); + if (ret) goto err_gpio_add; } - if (tc6393xb->irq) - tc6393xb_attach_irq(dev); + tc6393xb_attach_irq(dev); tc6393xb_cells[TC6393XB_CELL_NAND].driver_data = tcpd->nand_data; tc6393xb_cells[TC6393XB_CELL_NAND].platform_data = &tc6393xb_cells[TC6393XB_CELL_NAND]; tc6393xb_cells[TC6393XB_CELL_NAND].data_size = sizeof(tc6393xb_cells[TC6393XB_CELL_NAND]); + tc6393xb_cells[TC6393XB_CELL_MMC].platform_data = + &tc6393xb_cells[TC6393XB_CELL_MMC]; + tc6393xb_cells[TC6393XB_CELL_MMC].data_size = + sizeof(tc6393xb_cells[TC6393XB_CELL_MMC]); - retval = mfd_add_devices(&dev->dev, dev->id, + + ret = mfd_add_devices(&dev->dev, dev->id, tc6393xb_cells, ARRAY_SIZE(tc6393xb_cells), iomem, tcpd->irq_base); - return 0; + if (!ret) + return 0; - if (tc6393xb->irq) - tc6393xb_detach_irq(dev); + tc6393xb_detach_irq(dev); err_gpio_add: if (tc6393xb->gpio.base != -1) @@ -490,10 +521,11 @@ err_ioremap: release_resource(&tc6393xb->rscr); err_request_scr: clk_put(tc6393xb->clk); +err_noirq: err_clk_get: kfree(tc6393xb); err_kzalloc: - return retval; + return ret; } static int __devexit tc6393xb_remove(struct platform_device *dev) @@ -503,9 +535,7 @@ static int __devexit tc6393xb_remove(struct platform_device *dev) int ret; mfd_remove_devices(&dev->dev); - - if (tc6393xb->irq) - tc6393xb_detach_irq(dev); + tc6393xb_detach_irq(dev); if (tc6393xb->gpio.base != -1) { ret = gpiochip_remove(&tc6393xb->gpio); @@ -516,17 +546,11 @@ static int __devexit tc6393xb_remove(struct platform_device *dev) } ret = tcpd->disable(dev); - clk_disable(tc6393xb->clk); - iounmap(tc6393xb->scr); - release_resource(&tc6393xb->rscr); - platform_set_drvdata(dev, NULL); - clk_put(tc6393xb->clk); - kfree(tc6393xb); return ret; @@ -537,8 +561,7 @@ static int tc6393xb_suspend(struct platform_device *dev, pm_message_t state) { struct tc6393xb_platform_data *tcpd = dev->dev.platform_data; struct tc6393xb *tc6393xb = platform_get_drvdata(dev); - int i; - + int i, ret; tc6393xb->suspend_state.ccr = ioread16(tc6393xb->scr + SCR_CCR); tc6393xb->suspend_state.fer = ioread8(tc6393xb->scr + SCR_FER); @@ -551,14 +574,21 @@ static int tc6393xb_suspend(struct platform_device *dev, pm_message_t state) tc6393xb->suspend_state.gpi_bcr[i] = ioread8(tc6393xb->scr + SCR_GPI_BCR(i)); } + ret = tcpd->suspend(dev); + clk_disable(tc6393xb->clk); - return tcpd->suspend(dev); + return ret; } static int tc6393xb_resume(struct platform_device *dev) { struct tc6393xb_platform_data *tcpd = dev->dev.platform_data; - int ret = tcpd->resume(dev); + struct tc6393xb *tc6393xb = platform_get_drvdata(dev); + int ret; + + clk_enable(tc6393xb->clk); + + ret = tcpd->resume(dev); if (ret) return ret; @@ -595,7 +625,7 @@ static void __exit tc6393xb_exit(void) subsys_initcall(tc6393xb_init); module_exit(tc6393xb_exit); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov and Dirk Opfer"); MODULE_DESCRIPTION("tc6393xb Toshiba Mobile IO Controller"); MODULE_ALIAS("platform:tc6393xb"); diff --git a/include/linux/mfd/tc6393xb.h b/include/linux/mfd/tc6393xb.h index 7cc824a58f7c..fec7b3f7a81f 100644 --- a/include/linux/mfd/tc6393xb.h +++ b/include/linux/mfd/tc6393xb.h @@ -14,8 +14,8 @@ * published by the Free Software Foundation. */ -#ifndef TC6393XB_H -#define TC6393XB_H +#ifndef MFD_TC6393XB_H +#define MFD_TC6393XB_H /* Also one should provide the CK3P6MI clock */ struct tc6393xb_platform_data { @@ -29,7 +29,7 @@ struct tc6393xb_platform_data { int (*suspend)(struct platform_device *dev); int (*resume)(struct platform_device *dev); - int irq_base; /* a base for cascaded irq */ + int irq_base; /* base for subdevice irqs */ int gpio_base; struct tmio_nand_data *nand_data; @@ -40,9 +40,6 @@ struct tc6393xb_platform_data { */ #define IRQ_TC6393_NAND 0 #define IRQ_TC6393_MMC 1 -#define IRQ_TC6393_OHCI 2 -#define IRQ_TC6393_SERIAL 3 -#define IRQ_TC6393_FB 4 #define TC6393XB_NR_IRQS 8