Add core support for the tc6393xb MFD device

From: Ian Molton <spyro@f2s.com>

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1205c89..9903d0a 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -9,6 +9,12 @@ config MFD_CORE
 	tristate
 	default n
 
+config MFD_TC6393XB
+	bool "Support Toshiba TC6393XB"
+	select MFD_CORE
+	help
+	  Support for Toshiba Mobile IO Controller TC6393XB
+
 config MFD_SM501
 	tristate "Support for Silicon Motion SM501"
 	 ---help---
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 6c20064..ffd342e 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -6,6 +6,8 @@ obj-$(CONFIG_MFD_SM501)		+= sm501.o
 
 obj-$(CONFIG_MFD_CORE)		+= mfd-core.o
 
+obj-$(CONFIG_MFD_TC6393XB)	+= tc6393xb.o
+
 obj-$(CONFIG_MCP)		+= mcp-core.o
 obj-$(CONFIG_MCP_SA11X0)	+= mcp-sa11x0.o
 obj-$(CONFIG_MCP_UCB1200)	+= ucb1x00-core.o
diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c
new file mode 100644
index 0000000..28fb9b8
--- /dev/null
+++ b/drivers/mfd/tc6393xb.c
@@ -0,0 +1,683 @@
+/*
+ * Toshiba TC6393XB SoC support
+ *
+ * Copyright(c) 2005-2006 Chris Humbert
+ * Copyright(c) 2005 Dirk Opfer
+ * Copyright(c) 2005 Ian Molton <spyro@f2s.com>
+ * Copyright(c) 2007 Dmitry Baryshkov
+ *
+ * Based on code written by Sharp/Lineo for 2.4 kernels
+ * Based on locomo.c
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/fb.h>
+#include <linux/mfd-core.h>
+#include <linux/mfd/tmio.h>
+#include <linux/mfd/tc6393xb.h>
+
+struct tc6393xb_scr {
+	u8 x00[8];
+	u8	revid;		/* 0x08 Revision ID			*/
+	u8 x01[0x47];
+	u8	isr;		/* 0x50 Interrupt Status		*/
+	u8 x02;
+	u8	imr;		/* 0x52 Interrupt Mask			*/
+	u8 x03;
+	u8	irr;		/* 0x54 Interrupt Routing		*/
+	u8 x04[0x0b];
+	u16	gper;		/* 0x60 GP Enable			*/
+	u8 x05[2];
+	u16	gpi_sr[2];	/* 0x64 GPI Status			*/
+	u16	gpi_imr[2];	/* 0x68 GPI INT Mask			*/
+	u16	gpi_eder[2];	/* 0x6c GPI Edge Detect Enable		*/
+	u16	gpi_lir[4];	/* 0x70 GPI Level Invert		*/
+	u16	gpo_dsr[2];	/* 0x78 GPO Data Set			*/
+	u16	gpo_doecr[2];	/* 0x7c GPO Data OE Control		*/
+	u16	gp_iarcr[2];	/* 0x80 GP Internal Active Reg Control	*/
+	u16	gp_iarlcr[2];	/* 0x84 GP Internal Active Reg Level Con*/
+	u8	gpi_bcr[4];	/* 0x88 GPI Buffer Control		*/
+	u16	gpa_iarcr;	/* 0x8c GPa Internal Active Reg Control	*/
+	u8 x06[2];
+	u16	gpa_iarlcr;	/* 0x90 GPa Internal Active Reg Level Co*/
+	u8 x07[2];
+	u16	gpa_bcr;	/* 0x94 GPa Buffer Control		*/
+	u8 x08[2];
+	u16	ccr;		/* 0x98 Clock Control			*/
+	u16	pll2cr;		/* 0x9a PLL2 Control			*/
+	u16	pll1cr[2];	/* 0x9c PLL1 Control			*/
+	u8	diarcr;		/* 0xa0 Device Internal Active Reg Contr*/
+	u8	dbocr;		/* 0xa1 Device Buffer Off Control	*/
+	u8 x09[0x3e];
+	u8	fer;		/* 0xe0 Function Enable			*/
+	u8 x10[3];
+	u16	mcr;		/* 0xe4 Mode Control			*/
+	u8 x11[0x14];
+	u8	config;		/* 0xfc Configuration Control		*/
+	u8 x12[2];
+	u8	debug;		/* 0xff Debug				*/
+} __attribute__ ((packed));
+
+/*--------------------------------------------------------------------------*/
+
+struct tc6393xb {
+	struct tc6393xb_scr __iomem	*scr;
+
+	spinlock_t			lock; /* protects RMW cycles */
+
+	struct {
+		union tc6393xb_scr_fer	fer;
+		union tc6393xb_scr_ccr	ccr;
+		u8			gpi_bcr[4];
+	} suspend_state;
+
+	struct resource			rscr;
+	struct resource			*iomem;
+	int				irq;
+};
+
+/*--------------------------------------------------------------------------*/
+
+static int tc6393xb_nand_disable(struct platform_device *nand)
+{
+	return 0;
+}
+
+static int tc6393xb_nand_enable(struct platform_device *nand)
+{
+	struct platform_device		*dev	= to_platform_device(nand->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	/* SMD buffer on */
+	dev_dbg(&dev->dev, "SMD buffer on\n");
+	iowrite8(0xff, scr->gpi_bcr + 1);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+
+int tc6393xb_lcd_set_power(struct platform_device *fb, bool on)
+{
+	struct platform_device		*dev	= to_platform_device(fb->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	union tc6393xb_scr_fer		fer;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	fer.raw	= ioread8(&scr->fer);
+	fer.bits.slcden = on ? 1 : 0;
+	iowrite8(fer.raw, &scr->fer);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL(tc6393xb_lcd_set_power);
+
+int tc6393xb_lcd_mode(struct platform_device *fb_dev,
+					struct fb_videomode *mode) {
+	struct tc6393xb			*tc6393xb =
+		platform_get_drvdata(to_platform_device(fb_dev->dev.parent));
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+
+	iowrite16(mode->pixclock,		scr->pll1cr + 0);
+	iowrite16(mode->pixclock >> 16,		scr->pll1cr + 1);
+
+	return 0;
+}
+EXPORT_SYMBOL(tc6393xb_lcd_mode);
+
+static int tc6393xb_ohci_disable(struct platform_device *ohci)
+{
+	struct platform_device		*dev	= to_platform_device(ohci->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	union tc6393xb_scr_ccr		ccr;
+	union tc6393xb_scr_fer		fer;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	fer.raw = ioread8(&scr->fer);
+	fer.bits.usben = 0;
+	iowrite8(fer.raw, &scr->fer);
+
+	ccr.raw = ioread16(&scr->ccr);
+	ccr.bits.usbcken = 0;
+	iowrite16(ccr.raw, &scr->ccr);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+
+static int tc6393xb_ohci_enable(struct platform_device *ohci)
+{
+	struct platform_device		*dev	= to_platform_device(ohci->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	union tc6393xb_scr_ccr		ccr;
+	union tc6393xb_scr_fer		fer;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	ccr.raw = ioread16(&scr->ccr);
+	ccr.bits.usbcken = 1;
+	iowrite16(ccr.raw, &scr->ccr);
+
+	fer.raw = ioread8(&scr->fer);
+	fer.bits.usben = 1;
+	iowrite8(fer.raw, &scr->fer);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+
+static int tc6393xb_ohci_suspend(struct platform_device *ohci)
+{
+	struct platform_device		*dev	= to_platform_device(ohci->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	union tc6393xb_scr_ccr		ccr;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	ccr.raw = ioread16(&scr->ccr);
+	ccr.bits.usbcken = 0;
+	iowrite16(ccr.raw, &scr->ccr);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+
+static int tc6393xb_ohci_resume(struct platform_device *ohci)
+{
+	struct platform_device		*dev	= to_platform_device(ohci->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	union tc6393xb_scr_ccr		ccr;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	ccr.raw = ioread16(&scr->ccr);
+	ccr.bits.usbcken = 1;
+	iowrite16(ccr.raw, &scr->ccr);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+
+static int tc6393xb_fb_disable(struct platform_device *fb)
+{
+	struct platform_device		*dev	= to_platform_device(fb->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	union tc6393xb_scr_ccr		ccr;
+	union tc6393xb_scr_fer		fer;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	/*
+	 * FIXME: is this correct or it should be moved to other _disable?
+	 */
+	fer.raw	= ioread8(&scr->fer);
+	fer.bits.slcden = 0;
+	fer.bits.lcdcven = 0;
+	iowrite8(fer.raw, &scr->fer);
+
+	ccr.raw = ioread16(&scr->ccr);
+	ccr.bits.mclksel = disable;
+	iowrite16(ccr.raw, &scr->ccr);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+
+static int tc6393xb_fb_enable(struct platform_device *fb)
+{
+	struct platform_device		*dev	= to_platform_device(fb->dev.parent);
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	union tc6393xb_scr_ccr		ccr;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+
+	ccr.raw = ioread16(&scr->ccr);
+	ccr.bits.mclksel = m48MHz;
+	iowrite16(ccr.raw, &scr->ccr);
+
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+
+	return 0;
+}
+
+const static struct resource 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 | IORESOURCE_IRQ_MFD_SUBDEVICE,
+	},
+};
+
+const static struct resource tc6393xb_ohci_resources[] = {
+	{
+		.name	= TMIO_OHCI_CONFIG,
+		.start	= 0x0300,
+		.end	= 0x03ff,
+		.flags	= IORESOURCE_MEM,
+	},
+	{
+		.name	= TMIO_OHCI_CONTROL,
+		.start	= 0x3000,
+		.end	= 0x31ff,
+		.flags	= IORESOURCE_MEM,
+	},
+	{
+		.name	= TMIO_OHCI_SRAM,
+		.start	= 0x010000,
+		.end	= 0x017fff,
+		.flags	= IORESOURCE_MEM,
+	},
+	{
+		.name	= TMIO_OHCI_SRAM_ALIAS,
+		.start	= 0x018000,
+		.end	= 0x01ffff,
+		.flags	= IORESOURCE_MEM,
+	},
+	{
+		.name	= TMIO_OHCI_IRQ,
+		.start	= IRQ_TC6393_OHCI,
+		.end	= IRQ_TC6393_OHCI,
+		.flags	= IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+	},
+};
+
+const static struct resource tc6393xb_fb_resources[] = {
+	{
+		.name	= TMIO_FB_CONFIG,
+		.start	= 0x0500,
+		.end	= 0x05ff,
+		.flags	= IORESOURCE_MEM,
+	},
+	{
+		.name	= TMIO_FB_CONTROL,
+		.start	= 0x5000,
+		.end	= 0x51ff,
+		.flags	= IORESOURCE_MEM,
+	},
+	{
+		.name	= TMIO_FB_VRAM,
+		.start	= 0x100000,
+		.end	= 0x1fffff,
+		.flags	= IORESOURCE_MEM,
+	},
+	{
+		.name	= TMIO_FB_IRQ,
+		.start	= IRQ_TC6393_FB,
+		.end	= IRQ_TC6393_FB,
+		.flags	= IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+	},
+};
+
+static struct mfd_cell tc6393xb_cells[] = {
+	{
+		.name = "tmio-nand",
+		.enable = tc6393xb_nand_enable,
+		.disable = tc6393xb_nand_disable,
+		.num_resources = ARRAY_SIZE(tc6393xb_nand_resources),
+		.resources = tc6393xb_nand_resources,
+	},
+	{
+		.name = "tmio-ohci",
+		.enable = tc6393xb_ohci_enable,
+		.disable = tc6393xb_ohci_disable,
+		.suspend = tc6393xb_ohci_suspend,
+		.resume = tc6393xb_ohci_resume,
+		.num_resources = ARRAY_SIZE(tc6393xb_ohci_resources),
+		.resources = tc6393xb_ohci_resources,
+	},
+	{
+		.name = "tmio-fb",
+		.enable = tc6393xb_fb_enable,
+		.disable = tc6393xb_fb_disable,
+		.num_resources = ARRAY_SIZE(tc6393xb_fb_resources),
+		.resources = tc6393xb_fb_resources,
+	},
+};
+
+/*--------------------------------------------------------------------------*/
+
+static void
+tc6393xb_irq(unsigned int irq, struct irq_desc *desc)
+{
+	struct platform_device		*dev	= get_irq_chip_data(irq);
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	unsigned int			isr;
+	unsigned int			i;
+
+	desc->chip->ack(irq);
+
+	while ((isr = ioread8(&scr->isr) & ~ioread8(&scr->imr)))
+		for (i = 0; i < TC6393XB_NR_IRQS; i++) {
+			if (isr & (1 << i))
+				desc_handle_irq(tcpd->irq_base + i,
+					irq_desc + tcpd->irq_base + i);
+		}
+}
+
+static void tc6393xb_irq_ack(unsigned int irq)
+{
+}
+
+static void tc6393xb_irq_mask(unsigned int irq)
+{
+	struct platform_device		*dev	= get_irq_chip_data(irq);
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+	iowrite8(ioread8(&scr->imr) | (1 << (irq - tcpd->irq_base)),
+								&scr->imr);
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+}
+
+static void tc6393xb_irq_unmask(unsigned int irq)
+{
+	struct platform_device		*dev	= get_irq_chip_data(irq);
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	unsigned long			flags;
+
+	spin_lock_irqsave(&tc6393xb->lock, flags);
+	iowrite8(ioread8(&scr->imr) & ~(1 << (irq - tcpd->irq_base)),
+								&scr->imr);
+	spin_unlock_irqrestore(&tc6393xb->lock, flags);
+}
+
+static struct irq_chip tc6393xb_chip = {
+	.name	= "tc6393xb",
+	.ack	= tc6393xb_irq_ack,
+	.mask	= tc6393xb_irq_mask,
+	.unmask	= tc6393xb_irq_unmask,
+};
+
+static void tc6393xb_attach_irq(struct platform_device *dev)
+{
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	unsigned int			irq;
+
+	for (
+			irq = tcpd->irq_base;
+			irq <= tcpd->irq_base + TC6393XB_NR_IRQS;
+			irq++) {
+		set_irq_chip(irq, &tc6393xb_chip);
+		set_irq_chip_data(irq, dev);
+		set_irq_handler(irq, handle_edge_irq);
+		set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+	}
+
+	set_irq_type(tc6393xb->irq, IRQT_FALLING);
+	set_irq_chip_data(tc6393xb->irq, dev);
+	set_irq_chained_handler(tc6393xb->irq, tc6393xb_irq);
+}
+
+static void tc6393xb_detach_irq(struct platform_device *dev)
+{
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	unsigned int			irq;
+
+	set_irq_chained_handler(tc6393xb->irq, NULL);
+	set_irq_chip_data(tc6393xb->irq, NULL);
+
+	for (
+			irq = tcpd->irq_base;
+			irq <= tcpd->irq_base + TC6393XB_NR_IRQS;
+			irq++) {
+		set_irq_flags(irq, 0);
+		set_irq_chip(irq, NULL);
+		set_irq_chip_data(irq, NULL);
+	}
+}
+
+static int tc6393xb_hw_init(struct platform_device *dev, int resume)
+{
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	struct tc6393xb_scr __iomem	*scr	= tc6393xb->scr;
+	int				i;
+
+	if (resume)
+	iowrite8(resume ?
+		tc6393xb->suspend_state.fer.raw :
+		0,				&scr->fer);
+	iowrite16(tcpd->scr_pll2cr,		&scr->pll2cr);
+	iowrite16(resume?
+		tc6393xb->suspend_state.ccr.raw :
+		tcpd->scr_ccr.raw,		&scr->ccr);
+	iowrite16(tcpd->scr_mcr.raw,		&scr->mcr);
+	iowrite16(tcpd->scr_gper,		&scr->gper);
+	iowrite8(0,				&scr->irr);
+	iowrite8(0xbf,				&scr->imr);
+	iowrite16(tcpd->scr_gpo_dsr,		scr->gpo_dsr + 0);
+	iowrite16(tcpd->scr_gpo_dsr >> 16,	scr->gpo_dsr + 1);
+	iowrite16(tcpd->scr_gpo_doecr,		scr->gpo_doecr + 0);
+	iowrite16(tcpd->scr_gpo_doecr >> 16,	scr->gpo_doecr + 1);
+
+	if (resume)
+		for (i = 0; i < 4; i++)
+			iowrite8(tc6393xb->suspend_state.gpi_bcr[i],
+						scr->gpi_bcr + i);
+
+	return 0;
+}
+
+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;
+
+	iomem = platform_get_resource(dev, IORESOURCE_MEM, 0);
+	if (!iomem)
+		return -EINVAL;
+
+	tc6393xb = kzalloc(sizeof *tc6393xb, GFP_KERNEL);
+	if (!tc6393xb) {
+		retval = -ENOMEM;
+		goto err_kzalloc;
+	}
+
+	spin_lock_init(&tc6393xb->lock);
+
+	platform_set_drvdata(dev, tc6393xb);
+	tc6393xb->iomem	= iomem;
+	tc6393xb->irq	= platform_get_irq(dev, 0);
+
+	rscr		= &tc6393xb->rscr;
+	rscr->name	= "tc6393xb-core";
+	rscr->start	= iomem->start;
+	rscr->end	= iomem->start + 0xff;
+	rscr->flags	= IORESOURCE_MEM;
+
+	retval = request_resource(iomem, rscr);
+	if (retval)
+		goto err_request_scr;
+
+	tc6393xb->scr	= ioremap(rscr->start, rscr->end - rscr->start + 1);
+	if (!tc6393xb->scr) {
+		retval = -ENOMEM;
+		goto err_ioremap;
+	}
+
+	retval = tcpd->enable(dev);
+	if (retval)
+		goto err_enable;
+
+	retval = tc6393xb_hw_init(dev, 0);
+	if (retval)
+		goto err_hw_init;
+
+	printk(KERN_INFO "Toshiba tc6393xb revision %d at 0x%08lx, irq %d\n",
+			ioread8(&tc6393xb->scr->revid),
+			(unsigned long) iomem->start, tc6393xb->irq);
+
+	if (tc6393xb->irq)
+		tc6393xb_attach_irq(dev);
+
+	tc6393xb_cells[0].driver_data = tcpd->nand_data;
+	tc6393xb_cells[1].driver_data = NULL; /* tcpd->ohci_data; */
+	tc6393xb_cells[2].driver_data = tcpd->fb_data;
+
+	retval = mfd_add_devices(dev,
+			tc6393xb_cells, ARRAY_SIZE(tc6393xb_cells),
+			iomem, 0, tcpd->irq_base);
+
+	if (retval == 0)
+		return 0;
+
+	if (tc6393xb->irq)
+		tc6393xb_detach_irq(dev);
+
+err_hw_init:
+	tcpd->disable(dev);
+err_enable:
+	iounmap(tc6393xb->scr);
+err_ioremap:
+	release_resource(rscr);
+err_request_scr:
+	kfree(tc6393xb);
+err_kzalloc:
+	release_resource(iomem);
+	return retval;
+}
+
+static int __devexit tc6393xb_remove(struct platform_device *dev) {
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	struct tc6393xb			*tc6393xb = platform_get_drvdata(dev);
+	int ret;
+
+	if (tc6393xb->irq)
+		tc6393xb_detach_irq(dev);
+
+	ret = tcpd->disable(dev);
+
+	iounmap(tc6393xb->scr);
+	release_resource(&tc6393xb->rscr);
+	release_resource(tc6393xb->iomem);
+
+	mfd_remove_devices(dev);
+
+	platform_set_drvdata(dev, NULL);
+
+	kfree(tc6393xb);
+
+	return ret;
+}
+
+#ifdef CONFIG_PM
+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);
+	struct tc6393xb_scr __iomem	*scr = tc6393xb->scr;
+	int i;
+
+
+	tc6393xb->suspend_state.ccr.raw		= ioread16(&scr->ccr);
+	tc6393xb->suspend_state.fer.raw		= ioread8(&scr->fer);
+	for (i = 0; i < 4; i++)
+		tc6393xb->suspend_state.gpi_bcr[i] =
+			ioread8(scr->gpi_bcr + i);
+
+	return tcpd->suspend(dev);
+}
+
+static int tc6393xb_resume(struct platform_device *dev)
+{
+	struct tc6393xb_platform_data	*tcpd	= dev->dev.platform_data;
+	int ret = tcpd->resume(dev);
+
+	if (ret)
+		return ret;
+
+	return tc6393xb_hw_init(dev, 1);
+}
+#else
+#define tc6393xb_suspend NULL
+#define tc6393xb_resume NULL
+#endif
+
+static struct platform_driver tc6393xb_driver = {
+	.probe = tc6393xb_probe,
+	.remove = __devexit_p(tc6393xb_remove),
+	.suspend = tc6393xb_suspend,
+	.resume = tc6393xb_resume,
+
+	.driver = {
+		.name = "tc6393xb",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init tc6393xb_init(void)
+{
+	return platform_driver_register(&tc6393xb_driver);
+}
+
+static void __exit tc6393xb_exit(void)
+{
+	platform_driver_unregister(&tc6393xb_driver);
+}
+
+module_init(tc6393xb_init);
+module_exit(tc6393xb_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ian Molton, Dmitry Baryshkov and Dirk Opfer");
+MODULE_DESCRIPTION("tc6393xb Toshiba Mobile IO Controller");
diff --git a/include/linux/mfd/tc6393xb.h b/include/linux/mfd/tc6393xb.h
new file mode 100644
index 0000000..1f5f59b
--- /dev/null
+++ b/include/linux/mfd/tc6393xb.h
@@ -0,0 +1,108 @@
+/*
+ * Toshiba TC6393XB SoC support
+ *
+ * Copyright(c) 2005-2006 Chris Humbert
+ * Copyright(c) 2005 Dirk Opfer
+ * Copyright(c) 2005 Ian Molton <spyro@f2s.com>
+ * Copyright(c) 2007 Dmitry Baryshkov
+ *
+ * Based on code written by Sharp/Lineo for 2.4 kernels
+ * Based on locomo.c
+ *
+ * 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 TC6393XB_H
+#define TC6393XB_H
+
+#include <linux/mfd-core.h>
+#include <linux/mfd/tmio.h>
+
+union tc6393xb_scr_fer {
+	u8		raw;
+struct {
+	unsigned	usben:1;	/* D0	USB enable		*/
+	unsigned	lcdcven:1;	/* D1	polysylicon TFT enable	*/
+	unsigned	slcden:1;	/* D2	SLCD enable		*/
+} __attribute__ ((packed)) bits;
+} __attribute__ ((packed));
+
+union tc6393xb_scr_ccr {
+	u16		raw;
+struct {
+	unsigned	ck32ken:1;	/* D0	SD host clock enable	*/
+	unsigned	usbcken:1;	/* D1	USB host clock enable	*/
+	unsigned	x00:2;
+	unsigned	sharp:1;	/* D4	??? set in Sharp's code	*/
+	unsigned	x01:3;
+	enum {				disable	= 0,
+					m12MHz	= 1,
+					m24MHz	= 2,
+					m48MHz	= 3,
+	}		mclksel:3;	/* D10-D8  LCD controller clock	*/
+	unsigned	x02:1;
+	enum {				h24MHz	= 0,
+					h48MHz	= 1,
+	}		hclksel:2;	/* D13-D12 host bus clock	*/
+	unsigned	x03:2;
+} __attribute__ ((packed)) bits;
+} __attribute__ ((packed));
+
+enum pincontrol {
+	opendrain	= 0,
+	tristate	= 1,
+	pushpull	= 2,
+	/* reserved	= 3, */
+};
+
+union tc6393xb_scr_mcr {
+	u16		raw;
+struct {
+	enum pincontrol	rdyst:2;	/* D1-D0   HRDY control		*/
+	unsigned	x00:1;
+	unsigned	aren:1;		/* D3	   HRDY pull up resistance cut off */
+	enum pincontrol	intst:2;	/* D5-D4   #HINT control	*/
+	unsigned	x01:1;
+	unsigned	aien:1;		/* D7      #HINT pull up resitance cut off */
+	unsigned	x02:8;
+} __attribute__ ((packed)) bits;
+} __attribute__ ((packed));
+
+struct tc6393xb_platform_data {
+	u16	scr_pll2cr;	/* PLL2 Control */
+	union tc6393xb_scr_ccr	scr_ccr;	/* Clock Control */
+	union tc6393xb_scr_mcr	scr_mcr;	/* Mode Control */
+	u16	scr_gper;	/* GP Enable */
+	u32	scr_gpo_doecr;	/* GPO Data OE Control */
+	u32	scr_gpo_dsr;	/* GPO Data Set */
+
+	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;	/* a base for cascaded irq */
+
+	struct tmio_nand_data	*nand_data;
+	struct tmio_fb_data	*fb_data;
+};
+
+extern int tc6393xb_lcd_set_power(struct platform_device *fb_dev, bool on);
+extern int tc6393xb_lcd_mode(struct platform_device *fb_dev,
+					struct fb_videomode *mode);
+
+
+/*
+ * Relative to irq_base
+ */
+#define	IRQ_TC6393_NAND		0
+#define	IRQ_TC6393_SD		1
+#define	IRQ_TC6393_OHCI		2
+#define	IRQ_TC6393_SERIAL	3
+#define	IRQ_TC6393_FB		4
+
+#define	TC6393XB_NR_IRQS	8
+
+#endif
