2007-11-20 21:08:14

by Ian molton

[permalink] [raw]
Subject: [patch] 0/4 Support for Toshiba TMIO multifunction devices

Hi guys.

This patchset contains support for three toshiba multifunction devices.

it introduces a small but useful bit of code as a library for similar
devices, and includes support for these chips on the toshiba e-series
family of handhelds.

please copy me on reply!


2007-11-20 22:04:39

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [patch] 0/4 Support for Toshiba TMIO multifunction devices

Hi,

ian wrote:
> Hi guys.
>
> This patchset contains support for three toshiba multifunction devices.

Just to note, that there is an alternative implementation for at least
the tc6393 chip devices. Most current version of those patches can be
found in the OpenEmbedded monotone.


>
> it introduces a small but useful bit of code as a library for similar
> devices, and includes support for these chips on the toshiba e-series
> family of handhelds.
>
> please copy me on reply!
>

--
With best wishes
Dmitry

2007-11-20 22:24:20

by Ian molton

[permalink] [raw]
Subject: Re: [patch] 0/4 Support for Toshiba TMIO multifunction devices

On Wed, 2007-11-21 at 01:04 +0300, Dmitry Baryshkov wrote:
> Just to note, that there is an alternative implementation for at least
> the tc6393 chip devices. Most current version of those patches can be
> found in the OpenEmbedded monotone.

Yes, this is true. The core code in both cases originates from my and
Dirks work.

I'd just like to get something pushed up to mainline so that work can
begin on expanding the number of chips supported.

This would include the ASICs found in many iPAQ PDAs (their MMC module
seems to be an IRQ-less TMIO chip).

I havent submitted the subdevice drivers yet, only the base/core
drivers.

The OE versions probably have better USB and video subdevice support
(which I shall evaluate as I go on), wheras the NAND flash and MMC
drivers in hh.org are probably more up to date (I run root on SD usinf
the hh.org tmio_mmc driver on all three of the MFD chips in this patch
series on a daily basis).

merging the best of these patch series is my goal and it shouldnt be too
hard.



2007-11-21 02:23:52

by Eric Miao

[permalink] [raw]
Subject: Re: [patch] 0/4 Support for Toshiba TMIO multifunction devices

Roughly went through the patch, looks good, here comes the remind, though :-)

1. is it possible to use some name other than "soc_core", maybe
"tmio_core" so that
other multifunction chips sharing a core base will live easier.

2. those C++ style comments "//" are not so pleasant...

- eric

On Nov 21, 2007 6:20 AM, ian <[email protected]> wrote:
> On Wed, 2007-11-21 at 01:04 +0300, Dmitry Baryshkov wrote:
> > Just to note, that there is an alternative implementation for at least
> > the tc6393 chip devices. Most current version of those patches can be
> > found in the OpenEmbedded monotone.
>
> Yes, this is true. The core code in both cases originates from my and
> Dirks work.
>
> I'd just like to get something pushed up to mainline so that work can
> begin on expanding the number of chips supported.
>
> This would include the ASICs found in many iPAQ PDAs (their MMC module
> seems to be an IRQ-less TMIO chip).
>
> I havent submitted the subdevice drivers yet, only the base/core
> drivers.
>
> The OE versions probably have better USB and video subdevice support
> (which I shall evaluate as I go on), wheras the NAND flash and MMC
> drivers in hh.org are probably more up to date (I run root on SD usinf
> the hh.org tmio_mmc driver on all three of the MFD chips in this patch
> series on a daily basis).
>
> merging the best of these patch series is my goal and it shouldnt be too
> hard.
>
>
>
>
> -
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>



--
Cheers
- eric

2007-11-21 03:57:37

by Ian molton

[permalink] [raw]
Subject: Re: [patch] 0/4 Support for Toshiba TMIO multifunction devices

On Wed, 2007-11-21 at 10:23 +0800, eric miao wrote:
> Roughly went through the patch, looks good, here comes the remind, though :-)
>
> 1. is it possible to use some name other than "soc_core", maybe
> "tmio_core" so that other multifunction chips sharing a core base
> will live easier.

It's (soc-core) not tmio MFD specific - its already used by other MFD
chips (although obviously not ones in mainline (yet!)

it might be better named 'mfd-core' though, as thats its intended use...

> 2. those C++ style comments "//" are not so pleasant...

Should I clean them up and resubmit?

More to the point, who should I be submitting them to? the files under
arm/ are obviously for RMK to peruse, but I couldnt find an entry for
drivers/mfd in MAINTAINERS...


2007-11-21 04:05:50

by Eric Miao

[permalink] [raw]
Subject: Re: [patch] 0/4 Support for Toshiba TMIO multifunction devices

On Nov 21, 2007 11:54 AM, ian <[email protected]> wrote:
> On Wed, 2007-11-21 at 10:23 +0800, eric miao wrote:
> > Roughly went through the patch, looks good, here comes the remind, though :-)
> >
> > 1. is it possible to use some name other than "soc_core", maybe
> > "tmio_core" so that other multifunction chips sharing a core base
> > will live easier.
>
> It's (soc-core) not tmio MFD specific - its already used by other MFD
> chips (although obviously not ones in mainline (yet!)
>
> it might be better named 'mfd-core' though, as thats its intended use...
>
> > 2. those C++ style comments "//" are not so pleasant...
>
> Should I clean them up and resubmit?
>

Will be nice then, anyway, could you inline them so others can comment?

> More to the point, who should I be submitting them to? the files under
> arm/ are obviously for RMK to peruse, but I couldnt find an entry for
> drivers/mfd in MAINTAINERS...
>

Well, I briefly went through the git history, looks like Russell is the proper
one you could sent them to (probably not) :-)

>
>



--
Cheers
- eric

2007-11-22 00:37:43

by Ian molton

[permalink] [raw]
Subject: [UPDATED PATCH] Support for Toshiba TMIO multifunction devices

On Wed, 2007-11-21 at 12:05 +0800, eric miao wrote:
> On Nov 21, 2007 11:54 AM, ian <[email protected]> wrote:
> > On Wed, 2007-11-21 at 10:23 +0800, eric miao wrote:
> > > Roughly went through the patch, looks good, here comes the remind, though :-)
> > >
> > > 1. is it possible to use some name other than "soc_core", maybe
> > > "tmio_core" so that other multifunction chips sharing a core base
> > > will live easier.
> >
> > It's (soc-core) not tmio MFD specific - its already used by other MFD
> > chips (although obviously not ones in mainline (yet!)

I've renamed soc-core to mfd-core in the patches attached to this
message.

> > > 2. those C++ style comments "//" are not so pleasant...
> >
> > Should I clean them up and resubmit?
>
> Will be nice then, anyway, could you inline them so others can comment?

All done.

> Well, I briefly went through the git history, looks like Russell is the proper
> one you could sent them to (probably not) :-)

I've added RMK to the CC.

I've ommitted the platform support for e-series - I'll send that to RMK
once this is merged.

Patches follow:

>From 9c4ffb764ae2366368a0038a6fbdd9a19ce430c4 Mon Sep 17 00:00:00 2001
From: Ian Molton <[email protected]>
Date: Wed, 21 Nov 2007 23:32:37 +0000
Subject: [PATCH] Reuseable MFD core code suitable for multifunction
chips with
built in IRQ multiplexing and local RAM.

---
drivers/mfd/Kconfig | 25 ++++++++++++
drivers/mfd/Makefile | 3 +
drivers/mfd/mfd-core.c | 102
++++++++++++++++++++++++++++++++++++++++++++++++
drivers/mfd/mfd-core.h | 26 ++++++++++++
include/linux/ioport.h | 3 +
5 files changed, 159 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/mfd-core.c
create mode 100644 drivers/mfd/mfd-core.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 2571619..38edfdc 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -15,6 +15,31 @@ config MFD_SM501
interface. The device may be connected by PCI or local bus with
varying functions enabled.

+config MFD_T7L66XB
+ bool "Toshiba T7L66XB SoC support"
+ ---help---
+ This driver supports the T7L66XB, which incorporates SD/MMC, and
+ USB host functionality. associated subdevices are:
+ tmio_mmc
+ tmio_ohci
+
+config MFD_TC6387XB
+ bool "Toshiba TC6387XB SoC support"
+ ---help---
+ This driver supports the TC6393XB, which incorporates SD/MMC, NAND,
+ Video, and USB host functionality. associated subdevices are:
+ tmio_mmc
+
+config MFD_TC6393XB
+ bool "Toshiba TC6393XB SoC support"
+ ---help---
+ This driver supports the TC6393XB, which incorporates SD/MMC, NAND,
+ Video, and USB host functionality. associated subdevices are:
+ tmio_mmc
+ tmio_nand
+ tmio_fb
+ tmio_ohci
+
endmenu

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 5143209..5ae3877 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -3,6 +3,9 @@
#

obj-$(CONFIG_MFD_SM501) += sm501.o
+obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o mfd-core.o
+obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o mfd-core.o
+obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o mfd-core.o

obj-$(CONFIG_MCP) += mcp-core.o
obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
new file mode 100644
index 0000000..e668c92
--- /dev/null
+++ b/drivers/mfd/mfd-core.c
@@ -0,0 +1,102 @@
+/*
+ * drivers/mfd/mfd-core.c
+ *
+ * core MFD support
+ * Copyright (c) 2006 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.
+ *
+ */
+
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include "mfd-core.h"
+
+void mfd_free_devices(struct platform_device *devices, int nr_devs)
+{
+ struct platform_device *dev = devices;
+ int i;
+
+ for (i = 0; i < nr_devs; i++) {
+ struct resource *res = dev->resource;
+ platform_device_unregister(dev++);
+ kfree(res);
+ }
+ kfree(devices);
+}
+EXPORT_SYMBOL_GPL(mfd_free_devices);
+
+#define SIGNED_SHIFT(val, shift) ((shift) >= 0 ? ((val) << (shift)) :
((val) >> -(shift)))
+
+struct platform_device *mfd_add_devices(struct platform_device *dev,
+ struct mfd_device_data *mfd, int nr_devs,
+ struct resource *mem,
+ int relative_addr_shift, int irq_base)
+{
+ struct platform_device *devices;
+ int i, r, base;
+
+ devices = kzalloc(nr_devs * sizeof(struct platform_device),
GFP_KERNEL);
+ if (!devices)
+ return NULL;
+
+ for (i = 0; i < nr_devs; i++) {
+ struct platform_device *sdev = &devices[i];
+ struct mfd_device_data *blk = &mfd[i];
+ struct resource *res;
+
+ sdev->id = -1;
+ sdev->name = blk->name;
+
+ sdev->dev.parent = &dev->dev;
+ sdev->dev.platform_data = (void *)blk->hwconfig;
+ sdev->num_resources = blk->num_resources;
+
+ /* Allocate space for the subdevice resources */
+ res = kzalloc(blk->num_resources * sizeof (struct resource),
GFP_KERNEL);
+ if (!res)
+ goto fail;
+
+ for (r = 0; r < blk->num_resources; r++) {
+ res[r].name = blk->res[r].name; // Fixme - should copy
+
+ /* Find out base to use */
+ base = 0;
+ if (blk->res[r].flags & IORESOURCE_MEM) {
+ base = mem->start;
+ } else if ((blk->res[r].flags & IORESOURCE_IRQ) &&
+ (blk->res[r].flags & IORESOURCE_IRQ_MFD_SUBDEVICE)) {
+ base = irq_base;
+ }
+
+ /* Adjust resource */
+ if (blk->res[r].flags & IORESOURCE_MEM) {
+ res[r].parent = mem;
+ res[r].start = base + SIGNED_SHIFT(blk->res[r].start,
relative_addr_shift);
+ res[r].end = base + SIGNED_SHIFT(blk->res[r].end,
relative_addr_shift);
+ } else {
+ res[r].start = base + blk->res[r].start;
+ res[r].end = base + blk->res[r].end;
+ }
+ res[r].flags = blk->res[r].flags;
+ }
+
+ sdev->resource = res;
+ if (platform_device_register(sdev)) {
+ kfree(res);
+ goto fail;
+ }
+
+ printk(KERN_INFO "MFD: registering %s\n", blk->name);
+ }
+ return devices;
+
+fail:
+ mfd_free_devices(devices, i + 1);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(mfd_add_devices);
diff --git a/drivers/mfd/mfd-core.h b/drivers/mfd/mfd-core.h
new file mode 100644
index 0000000..b65803d
--- /dev/null
+++ b/drivers/mfd/mfd-core.h
@@ -0,0 +1,26 @@
+/*
+ * drivers/mfd/mfd-core.h
+ *
+ * core MFD support
+ * Copyright (c) 2006 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.
+ *
+ */
+
+struct mfd_device_data {
+ char *name;
+ struct resource *res;
+ int num_resources;
+ void *hwconfig; /* platform_data to pass to the subdevice */
+};
+
+struct platform_device *mfd_add_devices(struct platform_device *dev,
+ struct mfd_device_data *mfd, int n_devs,
+ struct resource *mem,
+ int relative_addr_shift, int irq_base);
+
+void mfd_free_devices(struct platform_device *devices, int nr_devs);
+
diff --git a/include/linux/ioport.h b/include/linux/ioport.h
index 6187a85..5e8360a 100644
--- a/include/linux/ioport.h
+++ b/include/linux/ioport.h
@@ -57,6 +57,9 @@ struct resource_list {
#define IORESOURCE_IRQ_LOWLEVEL (1<<3)
#define IORESOURCE_IRQ_SHAREABLE (1<<4)

+/* MFD device IRQ specific bits (IORESOURCE_BITS) */
+#define IORESOURCE_IRQ_MFD_SUBDEVICE (1<<5)
+
/* ISA PnP DMA specific bits (IORESOURCE_BITS) */
#define IORESOURCE_DMA_TYPE_MASK (3<<0)
#define IORESOURCE_DMA_8BIT (0<<0)
--
1.5.3.5.737.gdee1b

>From 53de37ad4c365b801ed2a5248272bed82958b908 Mon Sep 17 00:00:00 2001
From: Ian Molton <[email protected]>
Date: Thu, 22 Nov 2007 00:17:41 +0000
Subject: [PATCH] Preliminary support for Toshibas TMIO based
multifunction chips.

Includes support for:
* t7l66xb
* tc6387xb
* tc6393xb
---
drivers/mfd/t7l66xb.c | 291 +++++++++++++++++++++++++++++++++++
drivers/mfd/tc6387xb.c | 143 +++++++++++++++++
drivers/mfd/tc6393xb.c | 374
+++++++++++++++++++++++++++++++++++++++++++++
include/linux/t7l66xb.h | 56 +++++++
include/linux/tc6387xb.h | 34 ++++
include/linux/tc6393.h | 136 ++++++++++++++++
include/linux/tmio_mmc.h | 26 +++
include/linux/tmio_nand.h | 15 ++
include/linux/tmio_ohci.h | 15 ++
9 files changed, 1090 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/t7l66xb.c
create mode 100644 drivers/mfd/tc6387xb.c
create mode 100644 drivers/mfd/tc6393xb.c
create mode 100644 include/linux/t7l66xb.h
create mode 100644 include/linux/tc6387xb.h
create mode 100644 include/linux/tc6393.h
create mode 100644 include/linux/tmio_mmc.h
create mode 100644 include/linux/tmio_nand.h
create mode 100644 include/linux/tmio_ohci.h

diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c
new file mode 100644
index 0000000..3b74542
--- /dev/null
+++ b/drivers/mfd/t7l66xb.c
@@ -0,0 +1,291 @@
+/*
+ * drivers/mfd/t7l66xb.c
+ *
+ * Toshiba T7L66XB 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 is based on my and Dirk Opfers work on the tc6393xb SoC.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/arch/pxa-regs.h>
+
+#include <linux/t7l66xb.h>
+#include <linux/tmio_mmc.h>
+#include "mfd-core.h"
+
+#define platform_get_platdata(_dev) ((_dev)->dev.platform_data)
+
+struct t7l66xb_data
+{
+ int irq_base, irq_nr;
+ void *mapbase;
+ struct platform_device *devices;
+ int ndevices;
+};
+
+struct tmio_ohci_hwconfig t7l66xb_ohci_hwconfig = {
+ .start = NULL,
+};
+
+static struct resource t7l66xb_mmc_resources[] = {
+ {
+ .name = "control",
+ .start = T7L66XB_MMC_CTL_BASE,
+ .end = T7L66XB_MMC_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = T7L66XB_MMC_CNF_BASE,
+ .end = T7L66XB_MMC_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = T7L66XB_MMC_IRQ,
+ .end = T7L66XB_MMC_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+ },
+};
+
+static struct mfd_device_data t7l66xb_devices[] = {
+ {
+ .name = "tmio_mmc",
+ .res = t7l66xb_mmc_resources,
+ .num_resources = ARRAY_SIZE(t7l66xb_mmc_resources),
+ },
+};
+
+/* Handle the T7L66XB interrupt mux */
+static void t7l66xb_irq_handler(unsigned int irq, struct irq_desc
*desc)
+{
+ int req, i;
+ struct t7l66xb_data *data = get_irq_data(irq);
+
+ /* Acknowledge the parent IRQ */
+ desc->chip->ack(irq);
+
+ while ( (req = (readb(data->mapbase + T7L66XB_SYS_ISR)
+ & ~(readb(data->mapbase + T7L66XB_SYS_IMR)))) ) {
+ for (i = 0; i <= 7; i++) {
+ int dev_irq = data->irq_base + i;
+ struct irq_desc *d = NULL;
+ if ((req & (1<<i))) {
+ d = irq_desc + dev_irq;
+ d->handle_irq(dev_irq, d);
+ }
+ }
+ }
+}
+
+
+static void t7l66xb_mask_irq(unsigned int irq)
+{
+ struct t7l66xb_data *data = get_irq_chip_data(irq);
+
+ writeb(readb(data->mapbase + T7L66XB_SYS_IMR) | 1 << (irq -
data->irq_base), data->mapbase + T7L66XB_SYS_IMR);
+}
+
+static void t7l66xb_unmask_irq(unsigned int irq)
+{
+ struct t7l66xb_data *data = get_irq_chip_data(irq);
+
+ writeb(readb(data->mapbase + T7L66XB_SYS_IMR) & ~( 1 << (irq -
data->irq_base)),data->mapbase + T7L66XB_SYS_IMR);
+}
+
+static struct irq_chip t7l66xb_chip = {
+ .name = "t7l66xb",
+ .ack = t7l66xb_mask_irq,
+ .mask = t7l66xb_mask_irq,
+ .unmask = t7l66xb_unmask_irq,
+};
+
+/* Install the IRQ handler */
+static void t7l66xb_setup_irq(struct t7l66xb_data *tchip)
+{
+ int i;
+
+ for (i = 0; i < T7L66XB_NR_IRQS; i++) {
+ int irq = tchip->irq_base + i;
+ set_irq_chip (irq, &t7l66xb_chip);
+ set_irq_chip_data (irq, tchip);
+ set_irq_handler(irq, handle_level_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ }
+
+ set_irq_data (tchip->irq_nr, tchip);
+ set_irq_chained_handler (tchip->irq_nr, t7l66xb_irq_handler);
+ set_irq_type (tchip->irq_nr, IRQT_FALLING);
+}
+
+static void t7l66xb_hwinit(struct platform_device *dev)
+{
+ struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->hw_init)
+ pdata->hw_init();
+}
+
+
+#ifdef CONFIG_PM
+
+static int t7l66xb_suspend(struct platform_device *dev, pm_message_t
state)
+{
+ struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
+
+
+ if (pdata && pdata->suspend)
+ pdata->suspend();
+
+ return 0;
+}
+
+static int t7l66xb_resume(struct platform_device *dev)
+{
+ struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->resume)
+ pdata->resume();
+
+ t7l66xb_hwinit(dev);
+
+ return 0;
+}
+#endif
+
+static int t7l66xb_probe(struct platform_device *dev)
+{
+ struct t7l66xb_platform_data *pdata = dev->dev.platform_data;
+ unsigned long pbase = (unsigned long)dev->resource[0].start;
+ unsigned long plen = dev->resource[0].end - dev->resource[0].start;
+ int err = -ENOMEM;
+ struct t7l66xb_data *data;
+
+ data = kmalloc (sizeof (struct t7l66xb_data), GFP_KERNEL);
+ if (!data)
+ goto out;
+
+ data->irq_base = pdata->irq_base;
+ data->irq_nr = dev->resource[1].start;
+
+ if (!data->irq_base) {
+ printk("t7166xb: uninitialized irq_base!\n");
+ goto out_free_data;
+ }
+
+ data->mapbase = ioremap(pbase, plen);
+ if(!data->mapbase)
+ goto out_free_irqs;
+
+ platform_set_drvdata(dev, data);
+ t7l66xb_setup_irq(data);
+ t7l66xb_hwinit(dev);
+
+ /* Mask IRQs -- should we mask/unmask on suspend/resume? */
+ writew(0xbf, data->mapbase + T7L66XB_SYS_IMR);
+
+ printk(KERN_INFO "%s rev %d @ 0x%08lx using irq %d-%d on irq %d\n",
+ dev->name, readw(data->mapbase + T7L66XB_SYS_RIDR),
+ (unsigned long)data->mapbase, data->irq_base,
+ data->irq_base + T7L66XB_NR_IRQS - 1, data->irq_nr);
+
+ data->devices = mfd_add_devices(dev, t7l66xb_devices,
+ ARRAY_SIZE(t7l66xb_devices),
+ &dev->resource[0], 0, data->irq_base);
+
+ if(!data->devices){
+ printk(KERN_INFO "%s: Failed to allocate devices!\n",
+ dev->name);
+ goto out_free_devices;
+ }
+
+ return 0;
+
+out_free_devices:
+ mfd_free_devices(data->devices, ARRAY_SIZE(t7l66xb_devices));
+out_free_irqs:
+out_free_data:
+ kfree(data);
+out:
+ return err;
+}
+
+static int t7l66xb_remove(struct platform_device *dev)
+{
+ struct t7l66xb_data *tchip = platform_get_drvdata(dev);
+ int i;
+
+ /* Free the subdevice resources */
+ for (i = 0; i < tchip->ndevices; i++) {
+ platform_device_unregister (&tchip->devices[i]);
+ kfree (tchip->devices[i].resource);
+ }
+
+ /* Take down IRQ handling */
+ for (i = 0; i < T7L66XB_NR_IRQS; i++) {
+ int irq = i + tchip->irq_base;
+ set_irq_handler (irq, NULL);
+ set_irq_chip (irq, NULL);
+ set_irq_chip_data (irq, NULL);
+ }
+ set_irq_chained_handler (tchip->irq_nr, NULL);
+
+ /* Free core resources */
+ iounmap (tchip->mapbase);
+ mfd_free_devices(tchip->devices, ARRAY_SIZE(t7l66xb_devices));
+ kfree (tchip);
+
+ return 0;
+}
+
+
+static struct platform_driver t7l66xb_platform_driver = {
+ .driver = {
+ .name = "t7l66xb",
+ },
+ .probe = t7l66xb_probe,
+ .remove = t7l66xb_remove,
+#ifdef CONFIG_PM
+ .suspend = t7l66xb_suspend,
+ .resume = t7l66xb_resume,
+#endif
+};
+
+
+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");
+MODULE_AUTHOR("Ian Molton and Dirk Opfer");
diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c
new file mode 100644
index 0000000..d309b9f
--- /dev/null
+++ b/drivers/mfd/tc6387xb.c
@@ -0,0 +1,143 @@
+/*
+ * drivers/mfd/tc6387xb.c
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+
+#include <linux/tmio_mmc.h>
+#include <linux/tc6387xb.h>
+#include "mfd-core.h"
+
+struct tc6387xb_data {
+ int irq;
+ struct tc6387xb_platform_data *platform;
+};
+
+static void tc6387xb_hwinit(struct platform_device *dev)
+{
+ struct tc6387xb_data *data = platform_get_drvdata(dev);
+
+ if (data && data->platform && data->platform->hw_init)
+ data->platform->hw_init();
+
+}
+
+#ifdef CONFIG_PM
+
+static int tc6387xb_suspend(struct platform_device *dev, pm_message_t
state)
+{
+ struct tc6387xb_data *data = platform_get_drvdata(dev);
+
+ if (data && data->platform && data->platform->suspend)
+ data->platform->suspend();
+
+ return 0;
+}
+
+static int tc6387xb_resume(struct platform_device *dev)
+{
+ struct tc6387xb_data *data = platform_get_drvdata(dev);
+
+ if (data && data->platform && data->platform->resume)
+ data->platform->resume();
+
+ return 0;
+}
+#endif
+
+static struct resource tc6387xb_mmc_resources[] = {
+ {
+ .name = "control",
+ .start = TC6387XB_MMC_CTL_BASE,
+ .end = TC6387XB_MMC_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = TC6387XB_MMC_CNF_BASE,
+ .end = TC6387XB_MMC_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = TC6387XB_MMC_IRQ,
+ .end = TC6387XB_MMC_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+ },
+};
+
+static struct mfd_device_data tc6387xb_devices[] = {
+ {
+ .name = "tmio_mmc",
+ .res = tc6387xb_mmc_resources,
+ .num_resources = ARRAY_SIZE(tc6387xb_mmc_resources),
+ },
+};
+
+
+static int tc6387xb_probe(struct platform_device *pdev)
+{
+ struct tc6387xb_data *data;
+
+ data = kmalloc(sizeof(struct tc6387xb_data), GFP_KERNEL);
+ if(!data)
+ return -ENOMEM;
+
+ data->irq = pdev->resource[1].start;
+ data->platform = pdev->dev.platform_data;
+ platform_set_drvdata(pdev, data);
+
+ tc6387xb_hwinit(pdev);
+
+ mfd_add_devices(pdev, tc6387xb_devices, ARRAY_SIZE(tc6387xb_devices),
&pdev->resource[0], 0, data->irq);
+
+ /* Init finished. */
+ return 0;
+}
+
+static struct platform_driver tc6387xb_platform_driver = {
+ .driver = {
+ .name = "tc6387xb",
+ },
+ .probe = tc6387xb_probe,
+#ifdef CONFIG_PM
+ .suspend = tc6387xb_suspend,
+ .resume = tc6387xb_resume,
+#endif
+};
+
+
+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");
+MODULE_AUTHOR("Ian Molton");
diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c
new file mode 100644
index 0000000..860d67b
--- /dev/null
+++ b/drivers/mfd/tc6393xb.c
@@ -0,0 +1,374 @@
+/*
+ * drivers/mfd/tc6393xb.c
+ *
+ * Toshiba TC6393 support
+ * Copyright (c) 2005 Dirk Opfer
+ * Copyright (c) 2005 Ian Molton <[email protected]>
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/arch/pxa-regs.h>
+
+#include <linux/tmio_mmc.h>
+#include <linux/tmio_nand.h>
+#include <linux/tmio_ohci.h>
+#include <linux/tc6393.h>
+#include "mfd-core.h"
+
+#define platform_get_platdata(_dev) ((_dev)->dev.platform_data)
+
+struct tc6393xb_data
+{
+ int irq_base, irq_nr;
+ void *mapbase;
+ struct platform_device *devices;
+ int ndevices;
+};
+
+/* Setup the TC6393XB NAND flash controllers configuration registers */
+static void tc6393xb_nand_hwinit(struct platform_device *sdev) {
+
+ struct tc6393xb_data *chip = platform_get_drvdata(sdev);
+
+ /* Sequence:
+ * SMD Buffer ON (gpio related)
+ * Enable the clock (SCRUNEN)
+ * Set the ctl reg base address
+ * Enable the ctl reg
+ * Configure power control (control bt PCNT[1,0] 4ms startup delay)
+ */
+ /* (89h) SMD Buffer ON By TC6393XB SystemConfig gpibfc1*/
+ writew(0xff, chip->mapbase + TC6393_SYS_GPIBCR1);
+
+}
+
+static struct tmio_nand_hwconfig tc6393xb_nand_hwconfig = {
+ .hwinit = tc6393xb_nand_hwinit,
+};
+
+static void tc6393xb_mmc_set_clock(struct platform_device *sdev, int
state) {
+ struct tc6393xb_data *chip = platform_get_drvdata(sdev);
+ unsigned char tmp;
+
+ if(state == MMC_CLOCK_ENABLED){
+ tmp = readw(chip->mapbase + TC6393_SYS_GPIBCR1);
+ writew(tmp | CK32KEN, chip->mapbase + TC6393_SYS_GPIBCR1);
+ }
+}
+
+static struct tmio_mmc_hwconfig tc6393xb_mmc_hwconfig = {
+ .set_mmc_clock = tc6393xb_mmc_set_clock,
+};
+
+static struct resource tc6393xb_mmc_resources[] = {
+ {
+ .name = "control",
+ .start = TC6393XB_MMC_CTL_BASE,
+ .end = TC6393XB_MMC_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = TC6393XB_MMC_CNF_BASE,
+ .end = TC6393XB_MMC_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = TC6393XB_MMC_IRQ,
+ .end = TC6393XB_MMC_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+ },
+};
+
+static struct resource tc6393xb_nand_resources[] = {
+ {
+ .name = "control",
+ .start = TC6393XB_NAND_CTL_BASE,
+ .end = TC6393XB_NAND_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = TC6393XB_NAND_CNF_BASE,
+ .end = TC6393XB_NAND_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct mfd_device_data tc6393xb_devices[] = {
+ {
+ .name = "tmio_mmc",
+ .res = tc6393xb_mmc_resources,
+ .num_resources = ARRAY_SIZE(tc6393xb_mmc_resources),
+ .hwconfig = &tc6393xb_mmc_hwconfig,
+ },
+ {
+ .name = "tmio-nand",
+ .res = tc6393xb_nand_resources,
+ .num_resources = ARRAY_SIZE(tc6393xb_nand_resources),
+ .hwconfig = &tc6393xb_nand_hwconfig,
+ },
+};
+
+/** TC6393 interrupt handling stuff.
+ * NOTE: TC6393 has a 1 to many mapping on all of its IRQs.
+ * that is, there is only one real hardware interrupt
+ * we determine which interrupt it is by reading some IO memory.
+ * We have two levels of expansion, first in the handler for the
+ * hardware interrupt we generate an interrupt
+ * IRQ_TC6393_*_BASE and those handlers generate more interrupts
+ *
+ */
+static void tc6393xb_irq_handler(unsigned int irq, struct irq_desc
*desc)
+{
+ int req, i;
+ struct tc6393xb_data *data = get_irq_data(irq);
+
+ /* Acknowledge the parent IRQ */
+ desc->chip->ack(irq);
+
+ while ( (req = (readb(data->mapbase + TC6393_SYS_ISR)
+ & ~(readb(data->mapbase + TC6393_SYS_IMR)))) ) {
+ for (i = 0; i <= 7; i++) {
+ int dev_irq = data->irq_base + i;
+ struct irq_desc *d = NULL;
+ if ((req & (1<<i))) {
+ if(i != 1) printk("IRQ! from %d\n", i);
+ d = irq_desc + dev_irq;
+ d->handle_irq(dev_irq, d);
+ }
+ }
+ }
+}
+
+static void tc6393xb_mask_irq(unsigned int irq)
+{
+ struct tc6393xb_data *tc6393 = get_irq_chip_data(irq);
+
+ writeb(readb(tc6393->mapbase + TC6393_SYS_IMR) | 1 << (irq -
tc6393->irq_base),tc6393->mapbase + TC6393_SYS_IMR);
+}
+
+static void tc6393xb_unmask_irq(unsigned int irq)
+{
+ struct tc6393xb_data *tc6393 = get_irq_chip_data(irq);
+
+ writeb(readb(tc6393->mapbase + TC6393_SYS_IMR) & ~( 1 << (irq -
tc6393->irq_base)),tc6393->mapbase + TC6393_SYS_IMR);
+}
+
+static struct irq_chip tc6393xb_chip = {
+ .ack = tc6393xb_mask_irq,
+ .mask = tc6393xb_mask_irq,
+ .unmask = tc6393xb_unmask_irq,
+};
+
+
+static void tc6393xb_setup_irq(struct tc6393xb_data *tchip)
+{
+ int i;
+
+ for (i = 0; i < TC6393XB_NR_IRQS; i++) {
+ int irq = tchip->irq_base + i;
+ set_irq_chip (irq, &tc6393xb_chip);
+ set_irq_chip_data (irq, tchip);
+ set_irq_handler(irq, handle_level_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ }
+
+ set_irq_data (tchip->irq_nr, tchip);
+ set_irq_chained_handler (tchip->irq_nr, tc6393xb_irq_handler);
+ set_irq_type (tchip->irq_nr, IRQT_FALLING);
+}
+
+void tc6393xb_hwinit(struct platform_device *dev)
+{
+ struct tc6393xb_platform_data *pdata = platform_get_platdata(dev);
+ struct tc6393xb_data *tchip = platform_get_drvdata(dev);
+
+ if(!pdata || !tchip){
+ BUG_ON("no driver data!\n");
+ return;
+ }
+
+ if (pdata->hw_init)
+ pdata->hw_init();
+
+ writew(0, tchip->mapbase + TC6393_SYS_FER);
+
+ /* Clock setting */
+ writew(pdata->sys_pll2cr, tchip->mapbase + TC6393_SYS_PLL2CR);
+ writew(pdata->sys_ccr, tchip->mapbase + TC6393_SYS_CCR);
+ writew(pdata->sys_mcr, tchip->mapbase + TC6393_SYS_MCR);
+
+ /* GPIO */
+ writew(pdata->sys_gper, tchip->mapbase + TC6393_SYS_GPER);
+ writew(pdata->sys_gpodsr1, tchip->mapbase + TC6393_SYS_GPODSR1);
+ writew(pdata->sys_gpooecr1, tchip->mapbase + TC6393_SYS_GPOOECR1);
+}
+
+
+#ifdef CONFIG_PM
+
+static int tc6393xb_suspend(struct platform_device *dev, pm_message_t
state)
+{
+ struct tc6393xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->suspend)
+ pdata->suspend();
+
+ return 0;
+}
+
+static int tc6393xb_resume(struct platform_device *dev)
+{
+ struct tc6393xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->resume)
+ pdata->resume();
+
+ tc6393xb_hwinit(dev);
+
+ return 0;
+}
+
+#endif
+
+static int tc6393xb_probe(struct platform_device *dev)
+{
+ struct tc6393xb_platform_data *pdata = dev->dev.platform_data;
+ unsigned long pbase = (unsigned long)dev->resource[0].start;
+ unsigned long plen = dev->resource[0].end - dev->resource[0].start;
+ int err = -ENOMEM;
+ struct tc6393xb_data *data;
+
+ data = kmalloc (sizeof (struct tc6393xb_data), GFP_KERNEL);
+ if (!data)
+ goto out;
+
+ data->irq_base = pdata->irq_base;
+ data->irq_nr = dev->resource[1].start;
+
+ if (!data->irq_base) {
+ printk("tc6393xb: uninitialized irq_base!\n");
+ goto out_free_data;
+ }
+
+ data->mapbase = ioremap(pbase, plen);
+ if(!data->mapbase)
+ goto out_free_irqs;
+
+ platform_set_drvdata(dev, data);
+ tc6393xb_setup_irq (data);
+ tc6393xb_hwinit(dev);
+
+ /* Enable (but mask!) our IRQs */
+ writew(0, data->mapbase + TC6393_SYS_IRR);
+ writew(0xbf, data->mapbase + TC6393_SYS_IMR);
+
+ printk(KERN_INFO "%s rev %d @ 0x%08lx using irq %d-%d on irq %d\n",
+ dev->name, readw(data->mapbase + TC6393_SYS_RIDR),
+ (unsigned long)data->mapbase, data->irq_base,
+ data->irq_base + TC6393XB_NR_IRQS - 1, data->irq_nr);
+
+ data->devices = mfd_add_devices(dev, tc6393xb_devices,
+ ARRAY_SIZE(tc6393xb_devices),
+ &dev->resource[0], 0, data->irq_base);
+
+ if(!data->devices) {
+ printk(KERN_INFO "%s: Failed to allocate devices!\n",
+ dev->name);
+ goto out_free_devices;
+ }
+
+ return 0;
+
+out_free_devices:
+ mfd_free_devices(data->devices, ARRAY_SIZE(tc6393xb_devices));
+out_free_irqs:
+out_free_data:
+ kfree(data);
+out:
+ return err;
+}
+
+static int tc6393xb_remove(struct platform_device *dev)
+{
+ struct tc6393xb_data *tchip = platform_get_drvdata(dev);
+ int i;
+
+ /* Free the subdevice resources */
+ for (i = 0; i < tchip->ndevices; i++) {
+ platform_device_unregister (&tchip->devices[i]);
+ kfree (tchip->devices[i].resource);
+ }
+
+ /* Take down IRQ handling */
+ for (i = 0; i < TC6393XB_NR_IRQS; i++) {
+ int irq = i + tchip->irq_base;
+ set_irq_handler (irq, NULL);
+ set_irq_chip (irq, NULL);
+ set_irq_chip_data (irq, NULL);
+ }
+
+ set_irq_chained_handler (tchip->irq_nr, NULL);
+
+ /* Free core resources */
+ iounmap (tchip->mapbase);
+ mfd_free_devices(tchip->devices, ARRAY_SIZE(tc6393xb_devices));
+ kfree (tchip);
+
+ return 0;
+}
+
+
+static struct platform_driver tc6393xb_device_driver = {
+ .driver = {
+ .name = "tc6393xb",
+ },
+ .probe = tc6393xb_probe,
+ .remove = tc6393xb_remove,
+#ifdef CONFIG_PM
+ .suspend = tc6393xb_suspend,
+ .resume = tc6393xb_resume,
+#endif
+};
+
+
+static int __init tc6393xb_init(void)
+{
+ int retval = 0;
+ retval = platform_driver_register (&tc6393xb_device_driver);
+ return retval;
+}
+
+static void __exit tc6393xb_exit(void)
+{
+ platform_driver_unregister(&tc6393xb_device_driver);
+}
+
+module_init(tc6393xb_init);
+module_exit(tc6393xb_exit);
+
+MODULE_DESCRIPTION("Toshiba TC6393 core driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dirk Opfer and Ian Molton");
diff --git a/include/linux/t7l66xb.h b/include/linux/t7l66xb.h
new file mode 100644
index 0000000..f2d013a
--- /dev/null
+++ b/include/linux/t7l66xb.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * This file contains the definitions for the T7L66XB
+ *
+ * (C) Copyright 2005 Ian Molton <[email protected]>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License. See linux/COPYING for more information.
+ *
+ */
+
+#include <linux/platform_device.h>
+
+
+/* FIXME - this needs to be a common struct to all TMIO based MFDs. */
+struct tmio_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
+struct tmio_ohci_hwconfig {
+ void (*start)(struct platform_device *dev);
+};
+
+struct t7l66xb_platform_data
+{
+ /* Standard MFD properties */
+ int irq_base;
+ struct platform_device **child_devs;
+ int num_child_devs;
+
+ void (* hw_init) (void);
+ void (* suspend) (void);
+ void (* resume) (void);
+};
+
+
+#define T7L66XB_NAND_CNF_BASE (0x000100)
+#define T7L66XB_NAND_CTL_BASE (0x001000)
+
+#define T7L66XB_MMC_CNF_BASE (0x000200)
+#define T7L66XB_MMC_CTL_BASE (0x000800)
+#define T7L66XB_MMC_IRQ (1)
+
+#define T7L66XB_USB_CNF_BASE (0x000300)
+#define T7L66XB_USB_CTL_BASE (0x002000)
+#define T7L66XB_OHCI_IRQ (0)
+
+/* System Configuration register */
+#define T7L66XB_SYS_RIDR 0x008 // Revision ID
+#define T7L66XB_SYS_ISR 0x0e1 // Interrupt Status
+#define T7L66XB_SYS_IMR 0x042 // Interrupt Mask
+
+#define T7L66XB_NR_IRQS 8
+
diff --git a/include/linux/tc6387xb.h b/include/linux/tc6387xb.h
new file mode 100644
index 0000000..99c01a3
--- /dev/null
+++ b/include/linux/tc6387xb.h
@@ -0,0 +1,34 @@
+/*
+ *
+ * (C) Copyright 2005 Ian Molton <[email protected]>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License. See linux/COPYING for more information.
+ *
+ */
+
+#include <linux/platform_device.h>
+
+/* FIXME - this needs to be a common struct to all TMIO based MFDs. */
+struct tmio_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
+struct tc6387xb_platform_data
+{
+ /* Standard MFD properties */
+ int irq_base;
+ struct platform_device **child_devs;
+ int num_child_devs;
+
+ void (* hw_init) (void);
+ void (* suspend) (void);
+ void (* resume) (void);
+};
+
+#define TC6387XB_MMC_CNF_BASE (0x000200)
+#define TC6387XB_MMC_CTL_BASE (0x000800)
+#define TC6387XB_MMC_IRQ (0)
+
diff --git a/include/linux/tc6393.h b/include/linux/tc6393.h
new file mode 100644
index 0000000..99ecd42
--- /dev/null
+++ b/include/linux/tc6393.h
@@ -0,0 +1,136 @@
+/*
+ *
+ * (C) Copyright 2005 Dirk Opfer
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License. See linux/COPYING for more information.
+ *
+ */
+
+struct tc6393xb_platform_data
+{
+ /* Standard MFD properties */
+ int irq_base;
+ struct platform_device **child_devs;
+ int num_child_devs;
+
+ u16 sys_gper;
+ u16 sys_gpodsr1;
+ u16 sys_gpooecr1;
+ u16 sys_pll2cr;
+ u16 sys_ccr;
+ u16 sys_mcr;
+ void (* hw_init) (void);
+ void (* suspend) (void);
+ void (* resume) (void);
+};
+
+#define CK32KEN 0x1
+#define USBCLK 0x2
+
+/*
+ TC6393XB Resource Area Map (Offset)
+ System Configration Register Area 0x00000000 - 0x000000FF
+ NAND Flash Host Controller Register Area 0x00000100 - 0x000001FF
+ USB Host Controller Register Area 0x00000300 - 0x000003FF
+ LCD Host Controller Register Area 0x00000500 - 0x000005FF
+ NAND Flash Control Register 0x00001000 - 0x00001007
+ USB Control Register 0x00003000 - 0x000031FF
+ LCD Control Register 0x00005000 - 0x000051FF
+ Local Memory 0 (32KB) 0x00010000 - 0x00017FFF
+ Local Memory 0 (32KB) alias 0x00018000 - 0x0001FFFF
+ Local Memory 1 (1MB) 0x00100000 - 0x001FFFFF
+*/
+
+#define TC6393_SYS_BASE 0
+
+#define TC6393XB_NAND_CNF_BASE (TC6393_SYS_BASE + 0x000100)
+#define TC6393XB_NAND_CTL_BASE (TC6393_SYS_BASE + 0x001000)
+
+#define TC6393XB_MMC_CNF_BASE (TC6393_SYS_BASE + 0x000200)
+#define TC6393XB_MMC_CTL_BASE (TC6393_SYS_BASE + 0x000800)
+#define TC6393XB_MMC_IRQ (1)
+
+#define TC6393XB_USB_CNF_BASE (TC6393_SYS_BASE + 0x000300)
+#define TC6393XB_USB_CTL_BASE (TC6393_SYS_BASE + 0x000a00)
+#define TC6393XB_USB_IRQ (0)
+
+#define TC6393_SERIAL_CONF_BASE (TC6393_SYS_BASE + 0x000400)
+#define TC6393_GC_CONF_BASE (TC6393_SYS_BASE + 0x000500)
+#define TC6393_RAM0_BASE (TC6393_SYS_BASE + 0x010000)
+#define TC6393_RAM0_SIZE (32*1024)
+#define TC6393_RAM1_BASE (TC6393_SYS_BASE + 0x100000)
+#define TC6393_RAM1_SIZE (64 * 1024 * 16)
+
+
+/*
+ * Internal Local Memory use purpose
+ * RAM0 is used for USB
+ * RAM1 is used for GC
+ */
+/* Internal register mapping */
+#define TC6393_GC_INTERNAL_REG_BASE 0x000600 /* Length 0x200 */
+
+
+/* System Configuration register */
+#define TC6393_SYS_RIDR 0x008 // Revision ID
+#define TC6393_SYS_ISR 0x050 // Interrupt Status
+#define TC6393_SYS_IMR 0x052 // Interrupt Mask
+#define TC6393_SYS_IRR 0x054 // Interrupt Routing
+#define TC6393_SYS_GPER 0x060 // GP Enable
+#define TC6393_SYS_GPAIOEN 0x061 // GP Alternative Enable
+#define TC6393_SYS_GPISR1 0x064 // GPI Status 1
+#define TC6393_SYS_GPISR2 0x066 // GPI Status 2
+#define TC6393_SYS_GPIIMR1 0x068 // GPI INT Mask 1
+#define TC6393_SYS_GPIIMR2 0x06A // GPI INT Mask 2
+#define TC6393_SYS_GPIEDER1 0x06C // GPI Edge Detect Enable 1
+#define TC6393_SYS_GPIEDER2 0x06E // GPI Edge Detect Enable 2
+#define TC6393_SYS_GPILIR1 0x070 // GPI Level Invert 1
+#define TC6393_SYS_GPILIR2 0x072 // GPI Level Invert 2
+#define TC6393_SYS_GPODSR1 0x078 // GPO Data set 1
+#define TC6393_SYS_GPODSR2 0x07A // GPO Data set 2
+#define TC6393_SYS_GPOOECR1 0x07C // GPO Data OE Contorol 1
+#define TC6393_SYS_GPOOECR2 0x07E // GPO Data OE Contorol 2
+#define TC6393_SYS_GPIARCR1 0x080 // GP Internal Active Register
Contorol 1
+#define TC6393_SYS_GPIARCR2 0x082 // GP Internal Active Register
Contorol 2
+#define TC6393_SYS_GPIARLCR1 0x084 // GP Internal Active Register
Level Contorol 1
+#define TC6393_SYS_GPIARLCR2 0x086 // GP Internal Active Register
Level Contorol 2
+
+#define TC6393_SYS_GPIBCR1 0x089 // GPa Internal Activ Register
Contorol 1
+
+#define TC6393_SYS_GPIBCR2 0x08A // GPa Internal Activ Register
Contorol 2
+#define TC6393_SYS_GPaIARCR 0x08C
+#define TC6393_SYS_GPaIARLCR 0x090
+#define TC6393_SYS_GPaIBCR 0x094
+#define TC6393_SYS_CCR 0x098 /* Clock Control Register */
+#define TC6393_SYS_PLL2CR 0x09A // PLL2 Control
+#define TC6393_SYS_PLL1CR1 0x09C // PLL1 Control 1
+#define TC6393_SYS_PLL1CR2 0x09E // PLL1 Control 2
+#define TC6393_SYS_DCR 0x0A0
+#define TC6393_SYS_FER 0x0E0 /* Function Enable Register */
+#define TC6393_SYS_MCR 0x0E4
+#define TC6393_SYS_ConfigCR 0x0FC
+
+/* GPIO bit */
+#define TC6393_GPIO19 ( 1 << 19 )
+#define TC6393_GPIO18 ( 1 << 18 )
+#define TC6393_GPIO17 ( 1 << 17 )
+#define TC6393_GPIO16 ( 1 << 16 )
+#define TC6393_GPIO15 ( 1 << 15 )
+#define TC6393_GPIO14 ( 1 << 14 )
+#define TC6393_GPIO13 ( 1 << 13 )
+#define TC6393_GPIO12 ( 1 << 12 )
+#define TC6393_GPIO11 ( 1 << 11 )
+#define TC6393_GPIO10 ( 1 << 10 )
+#define TC6393_GPIO9 ( 1 << 9 )
+#define TC6393_GPIO8 ( 1 << 8 )
+#define TC6393_GPIO7 ( 1 << 7 )
+#define TC6393_GPIO6 ( 1 << 6 )
+#define TC6393_GPIO5 ( 1 << 5 )
+#define TC6393_GPIO4 ( 1 << 4 )
+#define TC6393_GPIO3 ( 1 << 3 )
+#define TC6393_GPIO2 ( 1 << 2 )
+#define TC6393_GPIO1 ( 1 << 1 )
+#define TC6393_GPIO0 ( 1 << 0 )
+
+#define TC6393XB_NR_IRQS 8
diff --git a/include/linux/tmio_mmc.h b/include/linux/tmio_mmc.h
new file mode 100644
index 0000000..00358bf
--- /dev/null
+++ b/include/linux/tmio_mmc.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ *
+ */
+
+#include <linux/platform_device.h>
+
+#define MMC_CLOCK_DISABLED 0
+#define MMC_CLOCK_ENABLED 1
+
+#define TMIO_WP_ALWAYS_RW ((void*)-1)
+
+struct tmio_mmc_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*set_mmc_clock)(struct platform_device *sdev, int state);
+
+ /* NULL - use ASIC3 signal,
+ TMIO_WP_ALWAYS_RW - assume always R/W (e.g. miniSD)
+ otherwise - machine-specific handler */
+ int (*mmc_get_ro)(struct platform_device *pdev);
+ short address_shift;
+};
diff --git a/include/linux/tmio_nand.h b/include/linux/tmio_nand.h
new file mode 100644
index 0000000..e1b38eb
--- /dev/null
+++ b/include/linux/tmio_nand.h
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ *
+ */
+
+struct tmio_nand_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
diff --git a/include/linux/tmio_ohci.h b/include/linux/tmio_ohci.h
new file mode 100644
index 0000000..5d07083
--- /dev/null
+++ b/include/linux/tmio_ohci.h
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ *
+ */
+
+struct tmio_ohci_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
--
1.5.3.5.737.gdee1b



2007-11-22 00:52:53

by Russell King - ARM Linux

[permalink] [raw]
Subject: Re: [UPDATED PATCH] Support for Toshiba TMIO multifunction devices

On Thu, Nov 22, 2007 at 12:34:09AM +0000, ian wrote:
> +void mfd_free_devices(struct platform_device *devices, int nr_devs)
> +{
> + struct platform_device *dev = devices;
> + int i;
> +
> + for (i = 0; i < nr_devs; i++) {
> + struct resource *res = dev->resource;
> + platform_device_unregister(dev++);
> + kfree(res);
> + }
> + kfree(devices);
> +}
> +EXPORT_SYMBOL_GPL(mfd_free_devices);

Unfortunately, this is broken as designed (in fact this whole file is.)
I'm not sure why people just don't get it. sysfs. devices. device tree.
It has object lifetime rules. You can _not_ go around unregistering things
and then immediately freeing them - something else might _still_ be using
stuff even after the call to unregister returns.

It's a potential OOPS just waiting to happen.

That's why we have a proper management API for platform devices. Please
use it, I didn't add the code for just for fun.

See platform_device_alloc() + platform_device_add_resources() +
platform_device_add_data() + platform_device_add() to create, and
platform_device_unregister() to destroy.

(Not looked at the rest because you really really need to get this
right first.)

2007-11-22 00:55:51

by Anton Vorontsov

[permalink] [raw]
Subject: Re: [patch] 0/4 Support for Toshiba TMIO multifunction devices

Hi Ian,

Personally I'm very appreciate your patches, they'll will
help submitting HP iPaqs SOCs/MFDs, you know... ;-)

Thus, much thanks in advance.

Few comments...

On Wed, Nov 21, 2007 at 03:54:15AM +0000, ian wrote:
> On Wed, 2007-11-21 at 10:23 +0800, eric miao wrote:
> > Roughly went through the patch, looks good, here comes the remind, though :-)
> >
> > 1. is it possible to use some name other than "soc_core", maybe
> > "tmio_core" so that other multifunction chips sharing a core base
> > will live easier.
>
> It's (soc-core) not tmio MFD specific - its already used by other MFD
> chips (although obviously not ones in mainline (yet!)
>
> it might be better named 'mfd-core' though, as thats its intended use...
>
> > 2. those C++ style comments "//" are not so pleasant...
>
> Should I clean them up and resubmit?

I'd resubmit cleaned up version. I think four or even more
resubmissions is inevitable for such patch-set (new general code +
a lot of drivers).

About patches their self... I think soc_add_devices could be
split into two small functions, thus you'll get rid of high
indentation level + code will be more reader friendly.


Ideally, checkpatch.pl should be happy. If it will, then there will
be less nitpicks somebody can pull. ;-)

Here it is:
- - - -
~/linux-2.6$ scripts/checkpatch.pl ~/0001-Reuseable-SOC-core-code-suitable-for-multifunction-c.patch
WARNING: line over 80 characters
#57: FILE: drivers/mfd/soc-core.c:37:
+#define SIGNED_SHIFT(val, shift) ((shift) >= 0 ? ((val) << (shift)) : ((val) >> -(shift)))

WARNING: line over 80 characters
#60: FILE: drivers/mfd/soc-core.c:40:
+ struct soc_device_data *soc, int nr_devs,

WARNING: line over 80 characters
#84: FILE: drivers/mfd/soc-core.c:64:
+ res = kzalloc(blk->num_resources * sizeof (struct resource), GFP_KERNEL);

WARNING: no space between function name and open parenthesis '('
#84: FILE: drivers/mfd/soc-core.c:64:
+ res = kzalloc(blk->num_resources * sizeof (struct resource), GFP_KERNEL);

ERROR: do not use C99 // comments
#89: FILE: drivers/mfd/soc-core.c:69:
+ res[r].name = blk->res[r].name; // Fixme - should copy

WARNING: braces {} are not necessary for single statement blocks
#93: FILE: drivers/mfd/soc-core.c:73:
+ if (blk->res[r].flags & IORESOURCE_MEM) {
+ base = mem->start;
+ } else if ((blk->res[r].flags & IORESOURCE_IRQ) &&

WARNING: braces {} are not necessary for single statement blocks
#95: FILE: drivers/mfd/soc-core.c:75:
+ } else if ((blk->res[r].flags & IORESOURCE_IRQ) &&
+ (blk->res[r].flags & IORESOURCE_IRQ_SOC_SUBDEVICE)) {
+ base = irq_base;
+ }

WARNING: line over 80 characters
#96: FILE: drivers/mfd/soc-core.c:76:
+ (blk->res[r].flags & IORESOURCE_IRQ_SOC_SUBDEVICE)) {

WARNING: line over 80 characters
#103: FILE: drivers/mfd/soc-core.c:83:
+ res[r].start = base + SIGNED_SHIFT(blk->res[r].start, relative_addr_shift);

WARNING: line over 80 characters
#104: FILE: drivers/mfd/soc-core.c:84:
+ res[r].end = base + SIGNED_SHIFT(blk->res[r].end, relative_addr_shift);

ERROR: Missing Signed-off-by: line(s)

total: 2 errors, 9 warnings, 145 lines checked
Your patch has style problems, please review. If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
- - - -

There is false positive though:

if (...) {
single_stmt;
} else {
one;
two;
}

^^^ is perfectly OK and preferred, IIRC. checkpatch isn't ideal,
but it's mostly good.

> More to the point, who should I be submitting them to? the files under
> arm/ are obviously for RMK to peruse, but I couldnt find an entry for
> drivers/mfd in MAINTAINERS...

Well, don't know about drivers/mfd/*. Probably there simply isn't
any [official] maintainer, thus lkml is the right place.

There is one not so obvious thing though: you should not submit patches
with To/Cc'ing lkml (open list) and linux-arm-kernel (subscribers-only).

Russell King will probably point to linux-arm-kernel etiquette article
(http://www.arm.linux.org.uk/mailinglists/etiquette.php
"Cross-posting between linux-arm* lists and other lists.")

So, either place linux-arm-kernel into Bcc:, or duplicate stuff for
lkml and linux-arm-kernel separately, thus they'll not see each
others' To/Cc.


Looking forward to your patches!

--
Anton Vorontsov
email: [email protected]
backup email: [email protected]
irc://irc.freenode.net/bd2

2007-11-22 11:01:14

by Ian molton

[permalink] [raw]
Subject: Re: [UPDATED PATCH] Support for Toshiba TMIO multifunction devices

On Thu, 2007-11-22 at 00:52 +0000, Russell King - ARM Linux wrote:
> On Thu, Nov 22, 2007 at 12:34:09AM +0000, ian wrote:

> Unfortunately, this is broken as designed (in fact this whole file is.)

Fix attached below.

> (Not looked at the rest because you really really need to get this
> right first.)

Since this core handles all the resource management, this fix should
sort that for all three drivers.

Here goes then - pass three...
>From ac98fb6ac3d4c44b81347228d35bb5714f968e2e Mon Sep 17 00:00:00 2001
From: Ian Molton <[email protected]>
Date: Thu, 22 Nov 2007 10:48:47 +0000
Subject: [PATCH] Reuseable MFD core code suitable for multifunction
chips with
built in IRQ multiplexing and local RAM.

---
drivers/mfd/mfd-core.c | 104
++++++++++++++++++++++++++++++++++++++++++++++++
drivers/mfd/mfd-core.h | 26 ++++++++++++
include/linux/ioport.h | 3 +
3 files changed, 133 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/mfd-core.c
create mode 100644 drivers/mfd/mfd-core.h

diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
new file mode 100644
index 0000000..bff886e
--- /dev/null
+++ b/drivers/mfd/mfd-core.c
@@ -0,0 +1,104 @@
+/*
+ * drivers/mfd/mfd-core.c
+ *
+ * core MFD support
+ * Copyright (c) 2006 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.
+ *
+ */
+
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include "mfd-core.h"
+
+void mfd_free_devices(struct platform_device **devices, int nr_devs)
+{
+ int i;
+
+ for (i = 0; i < nr_devs; i++) {
+ platform_device_unregister(*devices++);
+ }
+ kfree(devices);
+}
+EXPORT_SYMBOL_GPL(mfd_free_devices);
+
+#define SIGNED_SHIFT(val, shift) ((shift) >= 0 ? ((val) << (shift)) :
((val) >> -(shift)))
+
+struct platform_device **mfd_add_devices(struct platform_device *dev,
+ struct mfd_device_data *mfd,
+ int nr_devs,
+ struct resource *mem,
+ int relative_addr_shift, int irq_base)
+{
+ struct platform_device **devices;
+ int i, r, base;
+
+ devices = kzalloc(nr_devs * sizeof(struct platform_device*),
+ GFP_KERNEL);
+ if (!devices)
+ return NULL;
+
+ for (i = 0; i < nr_devs; i++) {
+ struct platform_device *sdev;
+ struct mfd_device_data *blk = &mfd[i];
+ struct resource *res;
+
+ if (!(sdev = platform_device_alloc (blk->name, -1)))
+ goto fail;
+
+ sdev->dev.parent = &dev->dev;
+ devices[i] = sdev;
+ platform_device_add_data (sdev, &(blk->hwconfig),
+ sizeof(void*));
+
+ /* Allocate space for the subdevice resources temporarily so
+ that we can modify them */
+
+ res = kzalloc(blk->num_resources * sizeof (struct
resource), GFP_KERNEL);
+ if (!res)
+ goto fail;
+
+ for (r = 0; r < blk->num_resources; r++) {
+ res[r].name = blk->res[r].name;
+
+ /* Find out base to use */
+ base = 0;
+ if (blk->res[r].flags & IORESOURCE_MEM) {
+ base = mem->start;
+ } else if ((blk->res[r].flags & IORESOURCE_IRQ) &&
+ (blk->res[r].flags & IORESOURCE_IRQ_MFD_SUBDEVICE)) {
+ base = irq_base;
+ }
+
+ /* Adjust resource */
+ if (blk->res[r].flags & IORESOURCE_MEM) {
+ res[r].parent = mem;
+ res[r].start = base + SIGNED_SHIFT(blk->res[r].start,
relative_addr_shift);
+ res[r].end = base + SIGNED_SHIFT(blk->res[r].end,
relative_addr_shift);
+ } else {
+ res[r].start = base + blk->res[r].start;
+ res[r].end = base + blk->res[r].end;
+ }
+ res[r].flags = blk->res[r].flags;
+ }
+ platform_device_add_resources(sdev, res, blk->num_resources);
+ kfree(res);
+
+ if (platform_device_add(sdev)) {
+ goto fail;
+ }
+
+ printk(KERN_INFO "MFD: registering %s\n", blk->name);
+ }
+ return devices;
+
+fail:
+ mfd_free_devices(devices, i + 1);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(mfd_add_devices);
diff --git a/drivers/mfd/mfd-core.h b/drivers/mfd/mfd-core.h
new file mode 100644
index 0000000..8f9dd76
--- /dev/null
+++ b/drivers/mfd/mfd-core.h
@@ -0,0 +1,26 @@
+/*
+ * drivers/mfd/mfd-core.h
+ *
+ * core MFD support
+ * Copyright (c) 2006 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.
+ *
+ */
+
+struct mfd_device_data {
+ char *name;
+ struct resource *res;
+ int num_resources;
+ void *hwconfig; /* platform_data to pass to the subdevice */
+};
+
+struct platform_device **mfd_add_devices(struct platform_device *dev,
+ struct mfd_device_data *mfd, int n_devs,
+ struct resource *mem,
+ int relative_addr_shift, int irq_base);
+
+void mfd_free_devices(struct platform_device **devices, int nr_devs);
+
diff --git a/include/linux/ioport.h b/include/linux/ioport.h
index 6187a85..5e8360a 100644
--- a/include/linux/ioport.h
+++ b/include/linux/ioport.h
@@ -57,6 +57,9 @@ struct resource_list {
#define IORESOURCE_IRQ_LOWLEVEL (1<<3)
#define IORESOURCE_IRQ_SHAREABLE (1<<4)

+/* MFD device IRQ specific bits (IORESOURCE_BITS) */
+#define IORESOURCE_IRQ_MFD_SUBDEVICE (1<<5)
+
/* ISA PnP DMA specific bits (IORESOURCE_BITS) */
#define IORESOURCE_DMA_TYPE_MASK (3<<0)
#define IORESOURCE_DMA_8BIT (0<<0)
--
1.5.3.5.737.gdee1b

>From 0ff1dd5ecedd43794515c46f503abf606ea5ce12 Mon Sep 17 00:00:00 2001
From: Ian Molton <[email protected]>
Date: Thu, 22 Nov 2007 00:17:41 +0000
Subject: [PATCH] Preliminary support for Toshibas TMIO based
multifunction chips.

Includes support for:
* t7l66xb
* tc6387xb
* tc6393xb
---
drivers/mfd/Kconfig | 25 +++
drivers/mfd/Makefile | 3 +
drivers/mfd/t7l66xb.c | 286 +++++++++++++++++++++++++++++++++++
drivers/mfd/tc6387xb.c | 143 +++++++++++++++++
drivers/mfd/tc6393xb.c | 369
+++++++++++++++++++++++++++++++++++++++++++++
include/linux/t7l66xb.h | 56 +++++++
include/linux/tc6387xb.h | 34 ++++
include/linux/tc6393.h | 136 +++++++++++++++++
include/linux/tmio_mmc.h | 26 +++
include/linux/tmio_nand.h | 15 ++
include/linux/tmio_ohci.h | 15 ++
11 files changed, 1108 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/t7l66xb.c
create mode 100644 drivers/mfd/tc6387xb.c
create mode 100644 drivers/mfd/tc6393xb.c
create mode 100644 include/linux/t7l66xb.h
create mode 100644 include/linux/tc6387xb.h
create mode 100644 include/linux/tc6393.h
create mode 100644 include/linux/tmio_mmc.h
create mode 100644 include/linux/tmio_nand.h
create mode 100644 include/linux/tmio_ohci.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 2571619..38edfdc 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -15,6 +15,31 @@ config MFD_SM501
interface. The device may be connected by PCI or local bus with
varying functions enabled.

+config MFD_T7L66XB
+ bool "Toshiba T7L66XB SoC support"
+ ---help---
+ This driver supports the T7L66XB, which incorporates SD/MMC, and
+ USB host functionality. associated subdevices are:
+ tmio_mmc
+ tmio_ohci
+
+config MFD_TC6387XB
+ bool "Toshiba TC6387XB SoC support"
+ ---help---
+ This driver supports the TC6393XB, which incorporates SD/MMC, NAND,
+ Video, and USB host functionality. associated subdevices are:
+ tmio_mmc
+
+config MFD_TC6393XB
+ bool "Toshiba TC6393XB SoC support"
+ ---help---
+ This driver supports the TC6393XB, which incorporates SD/MMC, NAND,
+ Video, and USB host functionality. associated subdevices are:
+ tmio_mmc
+ tmio_nand
+ tmio_fb
+ tmio_ohci
+
endmenu

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 5143209..5ae3877 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -3,6 +3,9 @@
#

obj-$(CONFIG_MFD_SM501) += sm501.o
+obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o mfd-core.o
+obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o mfd-core.o
+obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o mfd-core.o

obj-$(CONFIG_MCP) += mcp-core.o
obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o
diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c
new file mode 100644
index 0000000..e3057f6
--- /dev/null
+++ b/drivers/mfd/t7l66xb.c
@@ -0,0 +1,286 @@
+/*
+ * drivers/mfd/t7l66xb.c
+ *
+ * Toshiba T7L66XB 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 is based on my and Dirk Opfers work on the tc6393xb SoC.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/arch/pxa-regs.h>
+
+#include <linux/t7l66xb.h>
+#include <linux/tmio_mmc.h>
+#include "mfd-core.h"
+
+#define platform_get_platdata(_dev) ((_dev)->dev.platform_data)
+
+struct t7l66xb_data
+{
+ int irq_base, irq_nr;
+ void *mapbase;
+ struct platform_device **devices;
+ int ndevices;
+};
+
+struct tmio_ohci_hwconfig t7l66xb_ohci_hwconfig = {
+ .start = NULL,
+};
+
+static struct resource t7l66xb_mmc_resources[] = {
+ {
+ .name = "control",
+ .start = T7L66XB_MMC_CTL_BASE,
+ .end = T7L66XB_MMC_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = T7L66XB_MMC_CNF_BASE,
+ .end = T7L66XB_MMC_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = T7L66XB_MMC_IRQ,
+ .end = T7L66XB_MMC_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+ },
+};
+
+static struct mfd_device_data t7l66xb_devices[] = {
+ {
+ .name = "tmio_mmc",
+ .res = t7l66xb_mmc_resources,
+ .num_resources = ARRAY_SIZE(t7l66xb_mmc_resources),
+ },
+};
+
+/* Handle the T7L66XB interrupt mux */
+static void t7l66xb_irq_handler(unsigned int irq, struct irq_desc
*desc)
+{
+ int req, i;
+ struct t7l66xb_data *data = get_irq_data(irq);
+
+ /* Acknowledge the parent IRQ */
+ desc->chip->ack(irq);
+
+ while ( (req = (readb(data->mapbase + T7L66XB_SYS_ISR)
+ & ~(readb(data->mapbase + T7L66XB_SYS_IMR)))) ) {
+ for (i = 0; i <= 7; i++) {
+ int dev_irq = data->irq_base + i;
+ struct irq_desc *d = NULL;
+ if ((req & (1<<i))) {
+ d = irq_desc + dev_irq;
+ d->handle_irq(dev_irq, d);
+ }
+ }
+ }
+}
+
+
+static void t7l66xb_mask_irq(unsigned int irq)
+{
+ struct t7l66xb_data *data = get_irq_chip_data(irq);
+
+ writeb(readb(data->mapbase + T7L66XB_SYS_IMR) | 1 << (irq -
data->irq_base), data->mapbase + T7L66XB_SYS_IMR);
+}
+
+static void t7l66xb_unmask_irq(unsigned int irq)
+{
+ struct t7l66xb_data *data = get_irq_chip_data(irq);
+
+ writeb(readb(data->mapbase + T7L66XB_SYS_IMR) & ~( 1 << (irq -
data->irq_base)),data->mapbase + T7L66XB_SYS_IMR);
+}
+
+static struct irq_chip t7l66xb_chip = {
+ .name = "t7l66xb",
+ .ack = t7l66xb_mask_irq,
+ .mask = t7l66xb_mask_irq,
+ .unmask = t7l66xb_unmask_irq,
+};
+
+/* Install the IRQ handler */
+static void t7l66xb_setup_irq(struct t7l66xb_data *tchip)
+{
+ int i;
+
+ for (i = 0; i < T7L66XB_NR_IRQS; i++) {
+ int irq = tchip->irq_base + i;
+ set_irq_chip (irq, &t7l66xb_chip);
+ set_irq_chip_data (irq, tchip);
+ set_irq_handler(irq, handle_level_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ }
+
+ set_irq_data (tchip->irq_nr, tchip);
+ set_irq_chained_handler (tchip->irq_nr, t7l66xb_irq_handler);
+ set_irq_type (tchip->irq_nr, IRQT_FALLING);
+}
+
+static void t7l66xb_hwinit(struct platform_device *dev)
+{
+ struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->hw_init)
+ pdata->hw_init();
+}
+
+
+#ifdef CONFIG_PM
+
+static int t7l66xb_suspend(struct platform_device *dev, pm_message_t
state)
+{
+ struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
+
+
+ if (pdata && pdata->suspend)
+ pdata->suspend();
+
+ return 0;
+}
+
+static int t7l66xb_resume(struct platform_device *dev)
+{
+ struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->resume)
+ pdata->resume();
+
+ t7l66xb_hwinit(dev);
+
+ return 0;
+}
+#endif
+
+static int t7l66xb_probe(struct platform_device *dev)
+{
+ struct t7l66xb_platform_data *pdata = dev->dev.platform_data;
+ unsigned long pbase = (unsigned long)dev->resource[0].start;
+ unsigned long plen = dev->resource[0].end - dev->resource[0].start;
+ int err = -ENOMEM;
+ struct t7l66xb_data *data;
+
+ data = kmalloc (sizeof (struct t7l66xb_data), GFP_KERNEL);
+ if (!data)
+ goto out;
+
+ data->irq_base = pdata->irq_base;
+ data->irq_nr = dev->resource[1].start;
+
+ if (!data->irq_base) {
+ printk("t7166xb: uninitialized irq_base!\n");
+ goto out_free_data;
+ }
+
+ data->mapbase = ioremap(pbase, plen);
+ if(!data->mapbase)
+ goto out_free_irqs;
+
+ platform_set_drvdata(dev, data);
+ t7l66xb_setup_irq(data);
+ t7l66xb_hwinit(dev);
+
+ /* Mask IRQs -- should we mask/unmask on suspend/resume? */
+ writew(0xbf, data->mapbase + T7L66XB_SYS_IMR);
+
+ printk(KERN_INFO "%s rev %d @ 0x%08lx using irq %d-%d on irq %d\n",
+ dev->name, readw(data->mapbase + T7L66XB_SYS_RIDR),
+ (unsigned long)data->mapbase, data->irq_base,
+ data->irq_base + T7L66XB_NR_IRQS - 1, data->irq_nr);
+
+ data->devices = mfd_add_devices(dev, t7l66xb_devices,
+ ARRAY_SIZE(t7l66xb_devices),
+ &dev->resource[0], 0, data->irq_base);
+
+ if(!data->devices){
+ printk(KERN_INFO "%s: Failed to allocate devices!\n",
+ dev->name);
+ goto out_free_devices;
+ }
+
+ return 0;
+
+out_free_devices:
+ mfd_free_devices(data->devices, ARRAY_SIZE(t7l66xb_devices));
+out_free_irqs:
+out_free_data:
+ kfree(data);
+out:
+ return err;
+}
+
+static int t7l66xb_remove(struct platform_device *dev)
+{
+ struct t7l66xb_data *tchip = platform_get_drvdata(dev);
+ int i;
+
+ /* Free subdevices */
+ mfd_free_devices(tchip->devices, ARRAY_SIZE(t7l66xb_devices));
+
+ /* Take down IRQ handling */
+ for (i = 0; i < T7L66XB_NR_IRQS; i++) {
+ int irq = i + tchip->irq_base;
+ set_irq_handler (irq, NULL);
+ set_irq_chip (irq, NULL);
+ set_irq_chip_data (irq, NULL);
+ }
+ set_irq_chained_handler (tchip->irq_nr, NULL);
+
+ /* Free core resources */
+ iounmap (tchip->mapbase);
+
+ return 0;
+}
+
+
+static struct platform_driver t7l66xb_platform_driver = {
+ .driver = {
+ .name = "t7l66xb",
+ },
+ .probe = t7l66xb_probe,
+ .remove = t7l66xb_remove,
+#ifdef CONFIG_PM
+ .suspend = t7l66xb_suspend,
+ .resume = t7l66xb_resume,
+#endif
+};
+
+
+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");
+MODULE_AUTHOR("Ian Molton and Dirk Opfer");
diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c
new file mode 100644
index 0000000..d309b9f
--- /dev/null
+++ b/drivers/mfd/tc6387xb.c
@@ -0,0 +1,143 @@
+/*
+ * drivers/mfd/tc6387xb.c
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+
+#include <linux/tmio_mmc.h>
+#include <linux/tc6387xb.h>
+#include "mfd-core.h"
+
+struct tc6387xb_data {
+ int irq;
+ struct tc6387xb_platform_data *platform;
+};
+
+static void tc6387xb_hwinit(struct platform_device *dev)
+{
+ struct tc6387xb_data *data = platform_get_drvdata(dev);
+
+ if (data && data->platform && data->platform->hw_init)
+ data->platform->hw_init();
+
+}
+
+#ifdef CONFIG_PM
+
+static int tc6387xb_suspend(struct platform_device *dev, pm_message_t
state)
+{
+ struct tc6387xb_data *data = platform_get_drvdata(dev);
+
+ if (data && data->platform && data->platform->suspend)
+ data->platform->suspend();
+
+ return 0;
+}
+
+static int tc6387xb_resume(struct platform_device *dev)
+{
+ struct tc6387xb_data *data = platform_get_drvdata(dev);
+
+ if (data && data->platform && data->platform->resume)
+ data->platform->resume();
+
+ return 0;
+}
+#endif
+
+static struct resource tc6387xb_mmc_resources[] = {
+ {
+ .name = "control",
+ .start = TC6387XB_MMC_CTL_BASE,
+ .end = TC6387XB_MMC_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = TC6387XB_MMC_CNF_BASE,
+ .end = TC6387XB_MMC_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = TC6387XB_MMC_IRQ,
+ .end = TC6387XB_MMC_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+ },
+};
+
+static struct mfd_device_data tc6387xb_devices[] = {
+ {
+ .name = "tmio_mmc",
+ .res = tc6387xb_mmc_resources,
+ .num_resources = ARRAY_SIZE(tc6387xb_mmc_resources),
+ },
+};
+
+
+static int tc6387xb_probe(struct platform_device *pdev)
+{
+ struct tc6387xb_data *data;
+
+ data = kmalloc(sizeof(struct tc6387xb_data), GFP_KERNEL);
+ if(!data)
+ return -ENOMEM;
+
+ data->irq = pdev->resource[1].start;
+ data->platform = pdev->dev.platform_data;
+ platform_set_drvdata(pdev, data);
+
+ tc6387xb_hwinit(pdev);
+
+ mfd_add_devices(pdev, tc6387xb_devices, ARRAY_SIZE(tc6387xb_devices),
&pdev->resource[0], 0, data->irq);
+
+ /* Init finished. */
+ return 0;
+}
+
+static struct platform_driver tc6387xb_platform_driver = {
+ .driver = {
+ .name = "tc6387xb",
+ },
+ .probe = tc6387xb_probe,
+#ifdef CONFIG_PM
+ .suspend = tc6387xb_suspend,
+ .resume = tc6387xb_resume,
+#endif
+};
+
+
+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");
+MODULE_AUTHOR("Ian Molton");
diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c
new file mode 100644
index 0000000..3d43ce3
--- /dev/null
+++ b/drivers/mfd/tc6393xb.c
@@ -0,0 +1,369 @@
+/*
+ * drivers/mfd/tc6393xb.c
+ *
+ * Toshiba TC6393 support
+ * Copyright (c) 2005 Dirk Opfer
+ * Copyright (c) 2005 Ian Molton <[email protected]>
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/arch/pxa-regs.h>
+
+#include <linux/tmio_mmc.h>
+#include <linux/tmio_nand.h>
+#include <linux/tmio_ohci.h>
+#include <linux/tc6393.h>
+#include "mfd-core.h"
+
+#define platform_get_platdata(_dev) ((_dev)->dev.platform_data)
+
+struct tc6393xb_data
+{
+ int irq_base, irq_nr;
+ void *mapbase;
+ struct platform_device **devices;
+ int ndevices;
+};
+
+/* Setup the TC6393XB NAND flash controllers configuration registers */
+static void tc6393xb_nand_hwinit(struct platform_device *sdev) {
+
+ struct tc6393xb_data *chip = platform_get_drvdata(sdev);
+
+ /* Sequence:
+ * SMD Buffer ON (gpio related)
+ * Enable the clock (SCRUNEN)
+ * Set the ctl reg base address
+ * Enable the ctl reg
+ * Configure power control (control bt PCNT[1,0] 4ms startup delay)
+ */
+ /* (89h) SMD Buffer ON By TC6393XB SystemConfig gpibfc1*/
+ writew(0xff, chip->mapbase + TC6393_SYS_GPIBCR1);
+
+}
+
+static struct tmio_nand_hwconfig tc6393xb_nand_hwconfig = {
+ .hwinit = tc6393xb_nand_hwinit,
+};
+
+static void tc6393xb_mmc_set_clock(struct platform_device *sdev, int
state) {
+ struct tc6393xb_data *chip = platform_get_drvdata(sdev);
+ unsigned char tmp;
+
+ if(state == MMC_CLOCK_ENABLED){
+ tmp = readw(chip->mapbase + TC6393_SYS_GPIBCR1);
+ writew(tmp | CK32KEN, chip->mapbase + TC6393_SYS_GPIBCR1);
+ }
+}
+
+static struct tmio_mmc_hwconfig tc6393xb_mmc_hwconfig = {
+ .set_mmc_clock = tc6393xb_mmc_set_clock,
+};
+
+static struct resource tc6393xb_mmc_resources[] = {
+ {
+ .name = "control",
+ .start = TC6393XB_MMC_CTL_BASE,
+ .end = TC6393XB_MMC_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = TC6393XB_MMC_CNF_BASE,
+ .end = TC6393XB_MMC_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = TC6393XB_MMC_IRQ,
+ .end = TC6393XB_MMC_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_MFD_SUBDEVICE,
+ },
+};
+
+static struct resource tc6393xb_nand_resources[] = {
+ {
+ .name = "control",
+ .start = TC6393XB_NAND_CTL_BASE,
+ .end = TC6393XB_NAND_CTL_BASE + 0x1ff,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "config",
+ .start = TC6393XB_NAND_CNF_BASE,
+ .end = TC6393XB_NAND_CNF_BASE + 0xff,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct mfd_device_data tc6393xb_devices[] = {
+ {
+ .name = "tmio_mmc",
+ .res = tc6393xb_mmc_resources,
+ .num_resources = ARRAY_SIZE(tc6393xb_mmc_resources),
+ .hwconfig = &tc6393xb_mmc_hwconfig,
+ },
+ {
+ .name = "tmio-nand",
+ .res = tc6393xb_nand_resources,
+ .num_resources = ARRAY_SIZE(tc6393xb_nand_resources),
+ .hwconfig = &tc6393xb_nand_hwconfig,
+ },
+};
+
+/** TC6393 interrupt handling stuff.
+ * NOTE: TC6393 has a 1 to many mapping on all of its IRQs.
+ * that is, there is only one real hardware interrupt
+ * we determine which interrupt it is by reading some IO memory.
+ * We have two levels of expansion, first in the handler for the
+ * hardware interrupt we generate an interrupt
+ * IRQ_TC6393_*_BASE and those handlers generate more interrupts
+ *
+ */
+static void tc6393xb_irq_handler(unsigned int irq, struct irq_desc
*desc)
+{
+ int req, i;
+ struct tc6393xb_data *data = get_irq_data(irq);
+
+ /* Acknowledge the parent IRQ */
+ desc->chip->ack(irq);
+
+ while ( (req = (readb(data->mapbase + TC6393_SYS_ISR)
+ & ~(readb(data->mapbase + TC6393_SYS_IMR)))) ) {
+ for (i = 0; i <= 7; i++) {
+ int dev_irq = data->irq_base + i;
+ struct irq_desc *d = NULL;
+ if ((req & (1<<i))) {
+ if(i != 1) printk("IRQ! from %d\n", i);
+ d = irq_desc + dev_irq;
+ d->handle_irq(dev_irq, d);
+ }
+ }
+ }
+}
+
+static void tc6393xb_mask_irq(unsigned int irq)
+{
+ struct tc6393xb_data *tc6393 = get_irq_chip_data(irq);
+
+ writeb(readb(tc6393->mapbase + TC6393_SYS_IMR) | 1 << (irq -
tc6393->irq_base),tc6393->mapbase + TC6393_SYS_IMR);
+}
+
+static void tc6393xb_unmask_irq(unsigned int irq)
+{
+ struct tc6393xb_data *tc6393 = get_irq_chip_data(irq);
+
+ writeb(readb(tc6393->mapbase + TC6393_SYS_IMR) & ~( 1 << (irq -
tc6393->irq_base)),tc6393->mapbase + TC6393_SYS_IMR);
+}
+
+static struct irq_chip tc6393xb_chip = {
+ .ack = tc6393xb_mask_irq,
+ .mask = tc6393xb_mask_irq,
+ .unmask = tc6393xb_unmask_irq,
+};
+
+
+static void tc6393xb_setup_irq(struct tc6393xb_data *tchip)
+{
+ int i;
+
+ for (i = 0; i < TC6393XB_NR_IRQS; i++) {
+ int irq = tchip->irq_base + i;
+ set_irq_chip (irq, &tc6393xb_chip);
+ set_irq_chip_data (irq, tchip);
+ set_irq_handler(irq, handle_level_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ }
+
+ set_irq_data (tchip->irq_nr, tchip);
+ set_irq_chained_handler (tchip->irq_nr, tc6393xb_irq_handler);
+ set_irq_type (tchip->irq_nr, IRQT_FALLING);
+}
+
+void tc6393xb_hwinit(struct platform_device *dev)
+{
+ struct tc6393xb_platform_data *pdata = platform_get_platdata(dev);
+ struct tc6393xb_data *tchip = platform_get_drvdata(dev);
+
+ if(!pdata || !tchip){
+ BUG_ON("no driver data!\n");
+ return;
+ }
+
+ if (pdata->hw_init)
+ pdata->hw_init();
+
+ writew(0, tchip->mapbase + TC6393_SYS_FER);
+
+ /* Clock setting */
+ writew(pdata->sys_pll2cr, tchip->mapbase + TC6393_SYS_PLL2CR);
+ writew(pdata->sys_ccr, tchip->mapbase + TC6393_SYS_CCR);
+ writew(pdata->sys_mcr, tchip->mapbase + TC6393_SYS_MCR);
+
+ /* GPIO */
+ writew(pdata->sys_gper, tchip->mapbase + TC6393_SYS_GPER);
+ writew(pdata->sys_gpodsr1, tchip->mapbase + TC6393_SYS_GPODSR1);
+ writew(pdata->sys_gpooecr1, tchip->mapbase + TC6393_SYS_GPOOECR1);
+}
+
+
+#ifdef CONFIG_PM
+
+static int tc6393xb_suspend(struct platform_device *dev, pm_message_t
state)
+{
+ struct tc6393xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->suspend)
+ pdata->suspend();
+
+ return 0;
+}
+
+static int tc6393xb_resume(struct platform_device *dev)
+{
+ struct tc6393xb_platform_data *pdata = platform_get_platdata(dev);
+
+ if (pdata && pdata->resume)
+ pdata->resume();
+
+ tc6393xb_hwinit(dev);
+
+ return 0;
+}
+
+#endif
+
+static int tc6393xb_probe(struct platform_device *dev)
+{
+ struct tc6393xb_platform_data *pdata = dev->dev.platform_data;
+ unsigned long pbase = (unsigned long)dev->resource[0].start;
+ unsigned long plen = dev->resource[0].end - dev->resource[0].start;
+ int err = -ENOMEM;
+ struct tc6393xb_data *data;
+
+ data = kmalloc (sizeof (struct tc6393xb_data), GFP_KERNEL);
+ if (!data)
+ goto out;
+
+ data->irq_base = pdata->irq_base;
+ data->irq_nr = dev->resource[1].start;
+
+ if (!data->irq_base) {
+ printk("tc6393xb: uninitialized irq_base!\n");
+ goto out_free_data;
+ }
+
+ data->mapbase = ioremap(pbase, plen);
+ if(!data->mapbase)
+ goto out_free_irqs;
+
+ platform_set_drvdata(dev, data);
+ tc6393xb_setup_irq (data);
+ tc6393xb_hwinit(dev);
+
+ /* Enable (but mask!) our IRQs */
+ writew(0, data->mapbase + TC6393_SYS_IRR);
+ writew(0xbf, data->mapbase + TC6393_SYS_IMR);
+
+ printk(KERN_INFO "%s rev %d @ 0x%08lx using irq %d-%d on irq %d\n",
+ dev->name, readw(data->mapbase + TC6393_SYS_RIDR),
+ (unsigned long)data->mapbase, data->irq_base,
+ data->irq_base + TC6393XB_NR_IRQS - 1, data->irq_nr);
+
+ data->devices = mfd_add_devices(dev, tc6393xb_devices,
+ ARRAY_SIZE(tc6393xb_devices),
+ &dev->resource[0], 0, data->irq_base);
+
+ if(!data->devices) {
+ printk(KERN_INFO "%s: Failed to allocate devices!\n",
+ dev->name);
+ goto out_free_devices;
+ }
+
+ return 0;
+
+out_free_devices:
+ mfd_free_devices(data->devices, ARRAY_SIZE(tc6393xb_devices));
+out_free_irqs:
+out_free_data:
+ kfree(data);
+out:
+ return err;
+}
+
+static int tc6393xb_remove(struct platform_device *dev)
+{
+ struct tc6393xb_data *tchip = platform_get_drvdata(dev);
+ int i;
+
+ /* Free subdevices */
+ mfd_free_devices(tchip->devices, ARRAY_SIZE(tc6393xb_devices));
+
+ /* Take down IRQ handling */
+ for (i = 0; i < TC6393XB_NR_IRQS; i++) {
+ int irq = i + tchip->irq_base;
+ set_irq_handler (irq, NULL);
+ set_irq_chip (irq, NULL);
+ set_irq_chip_data (irq, NULL);
+ }
+
+ set_irq_chained_handler (tchip->irq_nr, NULL);
+
+ /* Free core resources */
+ iounmap (tchip->mapbase);
+
+ return 0;
+}
+
+
+static struct platform_driver tc6393xb_device_driver = {
+ .driver = {
+ .name = "tc6393xb",
+ },
+ .probe = tc6393xb_probe,
+ .remove = tc6393xb_remove,
+#ifdef CONFIG_PM
+ .suspend = tc6393xb_suspend,
+ .resume = tc6393xb_resume,
+#endif
+};
+
+
+static int __init tc6393xb_init(void)
+{
+ int retval = 0;
+ retval = platform_driver_register (&tc6393xb_device_driver);
+ return retval;
+}
+
+static void __exit tc6393xb_exit(void)
+{
+ platform_driver_unregister(&tc6393xb_device_driver);
+}
+
+module_init(tc6393xb_init);
+module_exit(tc6393xb_exit);
+
+MODULE_DESCRIPTION("Toshiba TC6393 core driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dirk Opfer and Ian Molton");
diff --git a/include/linux/t7l66xb.h b/include/linux/t7l66xb.h
new file mode 100644
index 0000000..f2d013a
--- /dev/null
+++ b/include/linux/t7l66xb.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * This file contains the definitions for the T7L66XB
+ *
+ * (C) Copyright 2005 Ian Molton <[email protected]>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License. See linux/COPYING for more information.
+ *
+ */
+
+#include <linux/platform_device.h>
+
+
+/* FIXME - this needs to be a common struct to all TMIO based MFDs. */
+struct tmio_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
+struct tmio_ohci_hwconfig {
+ void (*start)(struct platform_device *dev);
+};
+
+struct t7l66xb_platform_data
+{
+ /* Standard MFD properties */
+ int irq_base;
+ struct platform_device **child_devs;
+ int num_child_devs;
+
+ void (* hw_init) (void);
+ void (* suspend) (void);
+ void (* resume) (void);
+};
+
+
+#define T7L66XB_NAND_CNF_BASE (0x000100)
+#define T7L66XB_NAND_CTL_BASE (0x001000)
+
+#define T7L66XB_MMC_CNF_BASE (0x000200)
+#define T7L66XB_MMC_CTL_BASE (0x000800)
+#define T7L66XB_MMC_IRQ (1)
+
+#define T7L66XB_USB_CNF_BASE (0x000300)
+#define T7L66XB_USB_CTL_BASE (0x002000)
+#define T7L66XB_OHCI_IRQ (0)
+
+/* System Configuration register */
+#define T7L66XB_SYS_RIDR 0x008 // Revision ID
+#define T7L66XB_SYS_ISR 0x0e1 // Interrupt Status
+#define T7L66XB_SYS_IMR 0x042 // Interrupt Mask
+
+#define T7L66XB_NR_IRQS 8
+
diff --git a/include/linux/tc6387xb.h b/include/linux/tc6387xb.h
new file mode 100644
index 0000000..99c01a3
--- /dev/null
+++ b/include/linux/tc6387xb.h
@@ -0,0 +1,34 @@
+/*
+ *
+ * (C) Copyright 2005 Ian Molton <[email protected]>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License. See linux/COPYING for more information.
+ *
+ */
+
+#include <linux/platform_device.h>
+
+/* FIXME - this needs to be a common struct to all TMIO based MFDs. */
+struct tmio_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
+struct tc6387xb_platform_data
+{
+ /* Standard MFD properties */
+ int irq_base;
+ struct platform_device **child_devs;
+ int num_child_devs;
+
+ void (* hw_init) (void);
+ void (* suspend) (void);
+ void (* resume) (void);
+};
+
+#define TC6387XB_MMC_CNF_BASE (0x000200)
+#define TC6387XB_MMC_CTL_BASE (0x000800)
+#define TC6387XB_MMC_IRQ (0)
+
diff --git a/include/linux/tc6393.h b/include/linux/tc6393.h
new file mode 100644
index 0000000..99ecd42
--- /dev/null
+++ b/include/linux/tc6393.h
@@ -0,0 +1,136 @@
+/*
+ *
+ * (C) Copyright 2005 Dirk Opfer
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License. See linux/COPYING for more information.
+ *
+ */
+
+struct tc6393xb_platform_data
+{
+ /* Standard MFD properties */
+ int irq_base;
+ struct platform_device **child_devs;
+ int num_child_devs;
+
+ u16 sys_gper;
+ u16 sys_gpodsr1;
+ u16 sys_gpooecr1;
+ u16 sys_pll2cr;
+ u16 sys_ccr;
+ u16 sys_mcr;
+ void (* hw_init) (void);
+ void (* suspend) (void);
+ void (* resume) (void);
+};
+
+#define CK32KEN 0x1
+#define USBCLK 0x2
+
+/*
+ TC6393XB Resource Area Map (Offset)
+ System Configration Register Area 0x00000000 - 0x000000FF
+ NAND Flash Host Controller Register Area 0x00000100 - 0x000001FF
+ USB Host Controller Register Area 0x00000300 - 0x000003FF
+ LCD Host Controller Register Area 0x00000500 - 0x000005FF
+ NAND Flash Control Register 0x00001000 - 0x00001007
+ USB Control Register 0x00003000 - 0x000031FF
+ LCD Control Register 0x00005000 - 0x000051FF
+ Local Memory 0 (32KB) 0x00010000 - 0x00017FFF
+ Local Memory 0 (32KB) alias 0x00018000 - 0x0001FFFF
+ Local Memory 1 (1MB) 0x00100000 - 0x001FFFFF
+*/
+
+#define TC6393_SYS_BASE 0
+
+#define TC6393XB_NAND_CNF_BASE (TC6393_SYS_BASE + 0x000100)
+#define TC6393XB_NAND_CTL_BASE (TC6393_SYS_BASE + 0x001000)
+
+#define TC6393XB_MMC_CNF_BASE (TC6393_SYS_BASE + 0x000200)
+#define TC6393XB_MMC_CTL_BASE (TC6393_SYS_BASE + 0x000800)
+#define TC6393XB_MMC_IRQ (1)
+
+#define TC6393XB_USB_CNF_BASE (TC6393_SYS_BASE + 0x000300)
+#define TC6393XB_USB_CTL_BASE (TC6393_SYS_BASE + 0x000a00)
+#define TC6393XB_USB_IRQ (0)
+
+#define TC6393_SERIAL_CONF_BASE (TC6393_SYS_BASE + 0x000400)
+#define TC6393_GC_CONF_BASE (TC6393_SYS_BASE + 0x000500)
+#define TC6393_RAM0_BASE (TC6393_SYS_BASE + 0x010000)
+#define TC6393_RAM0_SIZE (32*1024)
+#define TC6393_RAM1_BASE (TC6393_SYS_BASE + 0x100000)
+#define TC6393_RAM1_SIZE (64 * 1024 * 16)
+
+
+/*
+ * Internal Local Memory use purpose
+ * RAM0 is used for USB
+ * RAM1 is used for GC
+ */
+/* Internal register mapping */
+#define TC6393_GC_INTERNAL_REG_BASE 0x000600 /* Length 0x200 */
+
+
+/* System Configuration register */
+#define TC6393_SYS_RIDR 0x008 // Revision ID
+#define TC6393_SYS_ISR 0x050 // Interrupt Status
+#define TC6393_SYS_IMR 0x052 // Interrupt Mask
+#define TC6393_SYS_IRR 0x054 // Interrupt Routing
+#define TC6393_SYS_GPER 0x060 // GP Enable
+#define TC6393_SYS_GPAIOEN 0x061 // GP Alternative Enable
+#define TC6393_SYS_GPISR1 0x064 // GPI Status 1
+#define TC6393_SYS_GPISR2 0x066 // GPI Status 2
+#define TC6393_SYS_GPIIMR1 0x068 // GPI INT Mask 1
+#define TC6393_SYS_GPIIMR2 0x06A // GPI INT Mask 2
+#define TC6393_SYS_GPIEDER1 0x06C // GPI Edge Detect Enable 1
+#define TC6393_SYS_GPIEDER2 0x06E // GPI Edge Detect Enable 2
+#define TC6393_SYS_GPILIR1 0x070 // GPI Level Invert 1
+#define TC6393_SYS_GPILIR2 0x072 // GPI Level Invert 2
+#define TC6393_SYS_GPODSR1 0x078 // GPO Data set 1
+#define TC6393_SYS_GPODSR2 0x07A // GPO Data set 2
+#define TC6393_SYS_GPOOECR1 0x07C // GPO Data OE Contorol 1
+#define TC6393_SYS_GPOOECR2 0x07E // GPO Data OE Contorol 2
+#define TC6393_SYS_GPIARCR1 0x080 // GP Internal Active Register
Contorol 1
+#define TC6393_SYS_GPIARCR2 0x082 // GP Internal Active Register
Contorol 2
+#define TC6393_SYS_GPIARLCR1 0x084 // GP Internal Active Register
Level Contorol 1
+#define TC6393_SYS_GPIARLCR2 0x086 // GP Internal Active Register
Level Contorol 2
+
+#define TC6393_SYS_GPIBCR1 0x089 // GPa Internal Activ Register
Contorol 1
+
+#define TC6393_SYS_GPIBCR2 0x08A // GPa Internal Activ Register
Contorol 2
+#define TC6393_SYS_GPaIARCR 0x08C
+#define TC6393_SYS_GPaIARLCR 0x090
+#define TC6393_SYS_GPaIBCR 0x094
+#define TC6393_SYS_CCR 0x098 /* Clock Control Register */
+#define TC6393_SYS_PLL2CR 0x09A // PLL2 Control
+#define TC6393_SYS_PLL1CR1 0x09C // PLL1 Control 1
+#define TC6393_SYS_PLL1CR2 0x09E // PLL1 Control 2
+#define TC6393_SYS_DCR 0x0A0
+#define TC6393_SYS_FER 0x0E0 /* Function Enable Register */
+#define TC6393_SYS_MCR 0x0E4
+#define TC6393_SYS_ConfigCR 0x0FC
+
+/* GPIO bit */
+#define TC6393_GPIO19 ( 1 << 19 )
+#define TC6393_GPIO18 ( 1 << 18 )
+#define TC6393_GPIO17 ( 1 << 17 )
+#define TC6393_GPIO16 ( 1 << 16 )
+#define TC6393_GPIO15 ( 1 << 15 )
+#define TC6393_GPIO14 ( 1 << 14 )
+#define TC6393_GPIO13 ( 1 << 13 )
+#define TC6393_GPIO12 ( 1 << 12 )
+#define TC6393_GPIO11 ( 1 << 11 )
+#define TC6393_GPIO10 ( 1 << 10 )
+#define TC6393_GPIO9 ( 1 << 9 )
+#define TC6393_GPIO8 ( 1 << 8 )
+#define TC6393_GPIO7 ( 1 << 7 )
+#define TC6393_GPIO6 ( 1 << 6 )
+#define TC6393_GPIO5 ( 1 << 5 )
+#define TC6393_GPIO4 ( 1 << 4 )
+#define TC6393_GPIO3 ( 1 << 3 )
+#define TC6393_GPIO2 ( 1 << 2 )
+#define TC6393_GPIO1 ( 1 << 1 )
+#define TC6393_GPIO0 ( 1 << 0 )
+
+#define TC6393XB_NR_IRQS 8
diff --git a/include/linux/tmio_mmc.h b/include/linux/tmio_mmc.h
new file mode 100644
index 0000000..00358bf
--- /dev/null
+++ b/include/linux/tmio_mmc.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ *
+ */
+
+#include <linux/platform_device.h>
+
+#define MMC_CLOCK_DISABLED 0
+#define MMC_CLOCK_ENABLED 1
+
+#define TMIO_WP_ALWAYS_RW ((void*)-1)
+
+struct tmio_mmc_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*set_mmc_clock)(struct platform_device *sdev, int state);
+
+ /* NULL - use ASIC3 signal,
+ TMIO_WP_ALWAYS_RW - assume always R/W (e.g. miniSD)
+ otherwise - machine-specific handler */
+ int (*mmc_get_ro)(struct platform_device *pdev);
+ short address_shift;
+};
diff --git a/include/linux/tmio_nand.h b/include/linux/tmio_nand.h
new file mode 100644
index 0000000..e1b38eb
--- /dev/null
+++ b/include/linux/tmio_nand.h
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ *
+ */
+
+struct tmio_nand_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
diff --git a/include/linux/tmio_ohci.h b/include/linux/tmio_ohci.h
new file mode 100644
index 0000000..5d07083
--- /dev/null
+++ b/include/linux/tmio_ohci.h
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ *
+ */
+
+struct tmio_ohci_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*suspend)(struct platform_device *sdev);
+ void (*resume)(struct platform_device *sdev);
+};
+
--
1.5.3.5.737.gdee1b

>From 9d9be4115972ae4c75d27c1a6b7aa1e3a840a98f Mon Sep 17 00:00:00 2001
From: Ian Molton <[email protected]>
Date: Thu, 22 Nov 2007 00:00:20 +0000
Subject: [PATCH] Add eseries platform support for the relevant TMIO
multifunction devices used.

---
arch/arm/mach-pxa/Makefile | 5 ++
arch/arm/mach-pxa/e330_tc6387xb.c | 98
+++++++++++++++++++++++++++++++++
arch/arm/mach-pxa/e400_t7l66xb.c | 107
++++++++++++++++++++++++++++++++++++
arch/arm/mach-pxa/e740_t7l66xb.c | 103
+++++++++++++++++++++++++++++++++++
arch/arm/mach-pxa/e750_tc6393xb.c | 109
+++++++++++++++++++++++++++++++++++++
5 files changed, 422 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-pxa/e330_tc6387xb.c
create mode 100644 arch/arm/mach-pxa/e400_t7l66xb.c
create mode 100644 arch/arm/mach-pxa/e740_t7l66xb.c
create mode 100644 arch/arm/mach-pxa/e750_tc6393xb.c

diff --git a/arch/arm/mach-pxa/Makefile b/arch/arm/mach-pxa/Makefile
index 2c50e9d..938cb2c 100644
--- a/arch/arm/mach-pxa/Makefile
+++ b/arch/arm/mach-pxa/Makefile
@@ -30,6 +30,11 @@ ifeq ($(CONFIG_MACH_ZYLONITE),y)
endif

obj-$(CONFIG_ARCH_PXA_ESERIES) += eseries.o
+obj-$(CONFIG_MACH_E330) += e330_tc6387xb.o
+obj-$(CONFIG_MACH_E740) += e740_t7l66xb.o
+obj-$(CONFIG_MACH_E750) += e750_tc6393xb.o
+obj-$(CONFIG_MACH_E400) += e400_t7l66xb.o
+obj-$(CONFIG_MACH_E800) += e750_tc6393xb.o

obj-$(CONFIG_MACH_ARMCORE) += cm-x270.o

diff --git a/arch/arm/mach-pxa/e330_tc6387xb.c
b/arch/arm/mach-pxa/e330_tc6387xb.c
new file mode 100644
index 0000000..9859de2
--- /dev/null
+++ b/arch/arm/mach-pxa/e330_tc6387xb.c
@@ -0,0 +1,98 @@
+/*
+ *
+ * Copyright (C) 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+#include <asm/irq.h>
+
+#include <asm/arch/eseries-irq.h>
+#include <asm/arch/eseries-gpio.h>
+
+#include <linux/tc6387xb.h>
+
+static struct resource e330_tc6387xb_resources[] = {
+ [0] = {
+ .start = PXA_CS4_PHYS,
+ .end = PXA_CS4_PHYS + 0x1fffff,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = IRQ_GPIO(5),
+ .end = IRQ_GPIO(5),
+ .flags = IORESOURCE_IRQ,
+ },
+
+};
+
+/* FIXME - who should really be setting up the clock? bootloader or
kernel ? */
+static void e330_tc6387xb_hwinit(void) {
+ GPCR(45) = GPIO_bit(45); /* #SUSPEND low */
+ GPCR(19) = GPIO_bit(19); /* #PCLR low (reset)
*/
+ /* 33MHz is supplied from an Xtal on the e330 (I think) */
+ pxa_gpio_mode(GPIO12_32KHz_MD);
+ mdelay(10);
+ GPSR(45) = GPIO_bit(45); /* #SUSPEND high */
+ mdelay(10);
+ GPSR(19) = GPIO_bit(19); /* #PCLR high */
+ mdelay(10);
+}
+
+static void e330_tc6387xb_suspend(void) {
+ GPCR(45) = GPIO_bit(45); /* #SUSPEND low */
+ mdelay(10);
+#if 0
+ GPCR(19) = GPIO_bit(19); /* #PCLR low */
+#endif
+}
+
+static void e330_tc6387xb_resume(void) {
+ GPSR(45) = GPIO_bit(45); /* #SUSPEND high */
+ mdelay(10);
+}
+
+static struct tc6387xb_platform_data e330_tc6387xb_info = {
+ .hw_init = &e330_tc6387xb_hwinit,
+ .suspend = &e330_tc6387xb_suspend,
+ .resume = &e330_tc6387xb_resume,
+};
+
+static struct platform_device e330_tc6387xb_device = {
+ .name = "tc6387xb",
+ .id = -1,
+ .dev = {
+ .platform_data = &e330_tc6387xb_info,
+ },
+ .num_resources = ARRAY_SIZE(e330_tc6387xb_resources),
+ .resource = e330_tc6387xb_resources,
+};
+
+static int __init e330_tc6387xb_init(void)
+{
+ if(!machine_is_e330())
+ return -ENODEV;
+
+ platform_device_register(&e330_tc6387xb_device);
+ return 0;
+}
+
+module_init(e330_tc6387xb_init);
+
+MODULE_AUTHOR("Ian Molton <[email protected]>");
+MODULE_DESCRIPTION("e330 tc6387xb device support");
+MODULE_LICENSE("GPLv2");
+
+
diff --git a/arch/arm/mach-pxa/e400_t7l66xb.c
b/arch/arm/mach-pxa/e400_t7l66xb.c
new file mode 100644
index 0000000..5c77388
--- /dev/null
+++ b/arch/arm/mach-pxa/e400_t7l66xb.c
@@ -0,0 +1,107 @@
+/*
+ *
+ * Copyright (C) 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.
+ *
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+#include <asm/irq.h>
+
+#include <asm/arch/eseries-irq.h>
+#include <asm/arch/eseries-gpio.h>
+
+#include <linux/t7l66xb.h>
+
+/* FIXME - set properly when known */
+#define GPIO_E400_TMIO_IRQ GPIO_ESERIES_TMIO_IRQ
+
+static struct resource e400_t7l66xb_resources[] = {
+ [0] = {
+ .start = PXA_CS4_PHYS,
+ .end = PXA_CS4_PHYS + 0x1fffff,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = IRQ_GPIO(GPIO_E400_TMIO_IRQ),
+ .end = IRQ_GPIO(GPIO_E400_TMIO_IRQ),
+ .flags = IORESOURCE_IRQ,
+ },
+
+};
+
+/* FIXME - who should really be setting up the clock? bootloader or
kernel ? */
+static void e400_t7l66xb_hwinit(void) {
+ return;
+ GPCR(19) = GPIO_bit(19); /* #SUSPEND low */
+ GPCR(7) = GPIO_bit(7); /* #PCLR low (reset) */
+ pxa_gpio_mode(GPIO7_48MHz_MD);
+ pxa_gpio_mode(GPIO12_32KHz_MD);
+ mdelay(10);
+ GPSR(19) = GPIO_bit(19); /* #SUSPEND high */
+ mdelay(10);
+ GPSR(7) = GPIO_bit(7); /* #PCLR high */
+ mdelay(10);
+}
+
+static void e400_t7l66xb_suspend(void) {
+ return;
+ GPCR(19) = GPIO_bit(19); /* #SUSPEND low */
+ mdelay(10);
+ GPCR(7) = GPIO_bit(7); /* #PCLR low */
+
+#if 0
+ /* kill clock */
+ pxa_gpio_mode(GPIO7_48MHz_MD|GPIO_OUT);
+ GPSR0 = GPIO_bit(GPIO7_48MHz);
+#endif
+}
+
+static void e400_t7l66xb_resume(void) {
+ e400_t7l66xb_hwinit();
+}
+
+static struct t7l66xb_platform_data e400_t7l66xb_info = {
+ .irq_base = IRQ_BOARD_START,
+ .hw_init = &e400_t7l66xb_hwinit,
+ .suspend = &e400_t7l66xb_suspend,
+ .resume = &e400_t7l66xb_resume,
+};
+
+static struct platform_device e400_t7l66xb_device = {
+ .name = "t7l66xb",
+ .id = -1,
+ .dev = {
+ .platform_data = &e400_t7l66xb_info,
+ },
+ .num_resources = ARRAY_SIZE(e400_t7l66xb_resources),
+ .resource = e400_t7l66xb_resources,
+};
+
+static int __init e400_t7l66xb_init(void)
+{
+ if(!machine_is_e400())
+ return -ENODEV;
+
+ platform_device_register(&e400_t7l66xb_device);
+ return 0;
+}
+
+module_init(e400_t7l66xb_init);
+
+MODULE_AUTHOR("Ian Molton <[email protected]>");
+MODULE_DESCRIPTION("e400 t7l66xb device support");
+MODULE_LICENSE("GPLv2");
+
diff --git a/arch/arm/mach-pxa/e740_t7l66xb.c
b/arch/arm/mach-pxa/e740_t7l66xb.c
new file mode 100644
index 0000000..26d1252
--- /dev/null
+++ b/arch/arm/mach-pxa/e740_t7l66xb.c
@@ -0,0 +1,103 @@
+/*
+ *
+ * Copyright (C) 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+#include <asm/irq.h>
+
+#include <asm/arch/eseries-irq.h>
+#include <asm/arch/eseries-gpio.h>
+
+#include <linux/t7l66xb.h>
+
+static struct resource e740_t7l66xb_resources[] = {
+ [0] = {
+ .start = PXA_CS4_PHYS,
+ .end = PXA_CS4_PHYS + 0x1fffff,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = IRQ_GPIO(GPIO_ESERIES_TMIO_IRQ),
+ .end = IRQ_GPIO(GPIO_ESERIES_TMIO_IRQ),
+ .flags = IORESOURCE_IRQ,
+ },
+
+};
+
+/* FIXME - who should really be setting up the clock? bootloader or
kernel ? */
+static void e740_t7l66xb_hwinit(void) {
+
+ GPCR(19) = GPIO_bit(19); /* #SUSPEND low */
+ GPCR(7) = GPIO_bit(7); /* #PCLR low (reset) */
+ pxa_gpio_mode(GPIO7_48MHz_MD);
+ pxa_gpio_mode(GPIO12_32KHz_MD);
+ mdelay(10);
+ GPSR(19) = GPIO_bit(19); /* #SUSPEND high */
+ mdelay(10);
+ GPSR(7) = GPIO_bit(7); /* #PCLR high */
+ mdelay(10);
+}
+
+static void e740_t7l66xb_suspend(void) {
+ GPCR(19) = GPIO_bit(19); /* #SUSPEND low */
+ mdelay(10);
+ GPCR(7) = GPIO_bit(7); /* #PCLR low */
+
+#if 0
+ /* kill clock */
+ pxa_gpio_mode(GPIO7_48MHz_MD|GPIO_OUT);
+ GPSR0 = GPIO_bit(GPIO7_48MHz);
+#endif
+}
+
+static void e740_t7l66xb_resume(void) {
+ e740_t7l66xb_hwinit();
+}
+
+static struct t7l66xb_platform_data e740_t7l66xb_info = {
+ .irq_base = IRQ_BOARD_START,
+ .hw_init = &e740_t7l66xb_hwinit,
+ .suspend = &e740_t7l66xb_suspend,
+ .resume = &e740_t7l66xb_resume,
+};
+
+static struct platform_device e740_t7l66xb_device = {
+ .name = "t7l66xb",
+ .id = -1,
+ .dev = {
+ .platform_data = &e740_t7l66xb_info,
+ },
+ .num_resources = ARRAY_SIZE(e740_t7l66xb_resources),
+ .resource = e740_t7l66xb_resources,
+};
+
+static int __init e740_t7l66xb_init(void)
+{
+ if(!machine_is_e740())
+ return -ENODEV;
+
+ platform_device_register(&e740_t7l66xb_device);
+ return 0;
+}
+
+module_init(e740_t7l66xb_init);
+
+MODULE_AUTHOR("Ian Molton <[email protected]>");
+MODULE_DESCRIPTION("e740 t7l66xb device support");
+MODULE_LICENSE("GPLv2");
+
+
diff --git a/arch/arm/mach-pxa/e750_tc6393xb.c
b/arch/arm/mach-pxa/e750_tc6393xb.c
new file mode 100644
index 0000000..c204e2f
--- /dev/null
+++ b/arch/arm/mach-pxa/e750_tc6393xb.c
@@ -0,0 +1,109 @@
+/*
+ *
+ * Copyright (C) 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+#include <asm/irq.h>
+
+#include <asm/arch/eseries-irq.h>
+#include <asm/arch/eseries-gpio.h>
+
+#include <linux/tc6393.h>
+
+static struct resource e750_tc6393xb_resources[] = {
+ [0] = {
+ .start = PXA_CS4_PHYS,
+ .end = PXA_CS4_PHYS + 0x1fffff,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = IRQ_GPIO(GPIO_ESERIES_TMIO_IRQ),
+ .end = IRQ_GPIO(GPIO_ESERIES_TMIO_IRQ),
+ .flags = IORESOURCE_IRQ,
+ },
+
+};
+
+/* FIXME - who should really be setting up the clock? bootloader or
kernel ? */
+static void e750_tc6393xb_hwinit(void) {
+ pxa_gpio_mode(GPIO11_3_6MHz_MD);
+
+ GPCR(45) = GPIO_bit(45); /* #SUSPEND low */
+ GPCR(19) = GPIO_bit(19); /* #PCLR low (reset) */
+ mdelay(1);
+ GPSR(45) = GPIO_bit(45); /* #SUSPEND high */
+ mdelay(10); /* FIXME - probably 1000x too long */
+ GPSR(19) = GPIO_bit(19); /* #PCLR high */
+}
+
+static void e750_tc6393xb_suspend(void) {
+ GPSR(45) = GPIO_bit(45); /* #SUSPEND high */
+ mdelay(10);
+ GPSR(19) = GPIO_bit(19); /* #PCLR high */
+
+ pxa_gpio_mode(GPIO11_3_6MHz_MD|GPIO_OUT);
+ GPSR0 = GPIO_bit(GPIO11_3_6MHz);
+}
+
+/*
+MCR : 80aa
+CCR: 1310
+PLL2CR: 0c01
+PLL1CR1: f743
+PLL1CR2: 00f2
+SYS_DCR: 1033
+*/
+
+static struct tc6393xb_platform_data e750_tc6393xb_info = {
+ .irq_base = IRQ_BOARD_START,
+ .sys_pll2cr = 0x0cc1,
+ .sys_ccr = 0x1310,
+ .sys_mcr = 0x80aa,
+ .sys_gper = 0,
+ .sys_gpodsr1 = 0,
+ .sys_gpooecr1 = 0,
+ .hw_init = &e750_tc6393xb_hwinit,
+ .suspend = &e750_tc6393xb_suspend,
+};
+
+static struct platform_device e750_tc6393xb_device = {
+ .name = "tc6393xb",
+ .id = -1,
+ .dev = {
+ .platform_data = &e750_tc6393xb_info,
+ },
+ .num_resources = ARRAY_SIZE(e750_tc6393xb_resources),
+ .resource = e750_tc6393xb_resources,
+};
+
+static int __init e750_tc6393xb_init(void)
+{
+ if(!(machine_is_e750() || machine_is_e800()))
+ return -ENODEV;
+
+ platform_device_register(&e750_tc6393xb_device);
+ return 0;
+}
+
+module_init(e750_tc6393xb_init);
+
+MODULE_AUTHOR("Ian Molton <[email protected]>");
+MODULE_DESCRIPTION("e740 tc6393 device support");
+MODULE_LICENSE("GPLv2");
+
+
--
1.5.3.5.737.gdee1b



2007-11-26 12:23:55

by Russell King - ARM Linux

[permalink] [raw]
Subject: Re: [UPDATED PATCH] Support for Toshiba TMIO multifunction devices

On Thu, Nov 22, 2007 at 10:52:46AM +0000, ian wrote:
> On Thu, 2007-11-22 at 00:52 +0000, Russell King - ARM Linux wrote:
> > On Thu, Nov 22, 2007 at 12:34:09AM +0000, ian wrote:
>
> > Unfortunately, this is broken as designed (in fact this whole file is.)
>
> Fix attached below.

Thanks.

> + /* Allocate space for the subdevice resources temporarily so
> + that we can modify them */
> +
> + res = kzalloc(blk->num_resources * sizeof (struct
> resource), GFP_KERNEL);

Looks like your mailer wrapped the patches.

> + if (!res)
> + goto fail;

Plus weird indentation.

> + platform_device_add_resources(sdev, res, blk->num_resources);
> + kfree(res);
> +
> + if (platform_device_add(sdev)) {
> + goto fail;
> + }
> +
> + printk(KERN_INFO "MFD: registering %s\n", blk->name);
> + }
> + return devices;
> +
> +fail:
> + mfd_free_devices(devices, i + 1);

If 'sdev' failed to register, you don't want to call platform_device_del()
on it (via platform_device_unregister()) since it's not been registered
into the device tree.

The removal procedure for platform devices is:

if platform_device_alloc() fails, return -ENOMEM.
if platform_device_add() fails, call platform_device_put() on it.
if platform_device_add() suceeds, call either
platform_device_del() + platform_device_put() _or_
platform_device_unregister()

> diff --git a/include/linux/ioport.h b/include/linux/ioport.h
> index 6187a85..5e8360a 100644
> --- a/include/linux/ioport.h
> +++ b/include/linux/ioport.h
> @@ -57,6 +57,9 @@ struct resource_list {
> #define IORESOURCE_IRQ_LOWLEVEL (1<<3)
> #define IORESOURCE_IRQ_SHAREABLE (1<<4)
>
> +/* MFD device IRQ specific bits (IORESOURCE_BITS) */
> +#define IORESOURCE_IRQ_MFD_SUBDEVICE (1<<5)
> +

Something others (== mainline) needs to see first.

> diff --git a/drivers/mfd/t7l66xb.c b/drivers/mfd/t7l66xb.c
> new file mode 100644
> index 0000000..e3057f6
> --- /dev/null
> +++ b/drivers/mfd/t7l66xb.c
> @@ -0,0 +1,286 @@
> +/*
> + * drivers/mfd/t7l66xb.c
> + *
> + * Toshiba T7L66XB 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 is based on my and Dirk Opfers work on the tc6393xb SoC.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>

Do you need linux/delay.h ?

> +#include <linux/errno.h>
> +#include <linux/ioport.h>
> +#include <linux/irq.h>

Do you need linux/irq.h ?

> +#include <linux/device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include <asm/hardware.h>
> +#include <asm/mach-types.h>

Doesn't use machine types in this file.

> +#include <asm/io.h>
> +#include <asm/arch/pxa-regs.h>
> +
> +#include <linux/t7l66xb.h>
> +#include <linux/tmio_mmc.h>
> +#include "mfd-core.h"
> +
> +#define platform_get_platdata(_dev) ((_dev)->dev.platform_data)

Should this be something generic? Eg in platform_device.h ?

> + while ( (req = (readb(data->mapbase + T7L66XB_SYS_ISR)
> + & ~(readb(data->mapbase + T7L66XB_SYS_IMR)))) ) {

Do the additional parens buy anything here (other than more line noise) ?

> + for (i = 0; i <= 7; i++) {
> + int dev_irq = data->irq_base + i;
> + struct irq_desc *d = NULL;
> + if ((req & (1<<i))) {

Ditto.

> + set_irq_data (tchip->irq_nr, tchip);
> + set_irq_chained_handler (tchip->irq_nr, t7l66xb_irq_handler);

Weird indentation.
> + set_irq_type (tchip->irq_nr, IRQT_FALLING);
> +}
> +
> +static void t7l66xb_hwinit(struct platform_device *dev)
> +{
> + struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
> +
> + if (pdata && pdata->hw_init)
> + pdata->hw_init();
> +}
> +
> +
> +#ifdef CONFIG_PM
> +
> +static int t7l66xb_suspend(struct platform_device *dev, pm_message_t
> state)
> +{
> + struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
> +
> +
> + if (pdata && pdata->suspend)
> + pdata->suspend();
> +
> + return 0;
> +}
> +
> +static int t7l66xb_resume(struct platform_device *dev)
> +{
> + struct t7l66xb_platform_data *pdata = platform_get_platdata(dev);
> +
> + if (pdata && pdata->resume)
> + pdata->resume();
> +
> + t7l66xb_hwinit(dev);
> +
> + return 0;
> +}
> +#endif
> +
> +static int t7l66xb_probe(struct platform_device *dev)
> +{
> + struct t7l66xb_platform_data *pdata = dev->dev.platform_data;
> + unsigned long pbase = (unsigned long)dev->resource[0].start;
> + unsigned long plen = dev->resource[0].end - dev->resource[0].start;
> + int err = -ENOMEM;
> + struct t7l66xb_data *data;
> +
> + data = kmalloc (sizeof (struct t7l66xb_data), GFP_KERNEL);
> + if (!data)
> + goto out;
> +
> + data->irq_base = pdata->irq_base;
> + data->irq_nr = dev->resource[1].start;

platform_get_irq() ?

> +
> + if (!data->irq_base) {
> + printk("t7166xb: uninitialized irq_base!\n");

KERN_ERR ?

> + goto out_free_data;
> + }
> +
> + data->mapbase = ioremap(pbase, plen);
> + if(!data->mapbase)
> + goto out_free_irqs;
> +
> + platform_set_drvdata(dev, data);
> + t7l66xb_setup_irq(data);
> + t7l66xb_hwinit(dev);
> +
> + /* Mask IRQs -- should we mask/unmask on suspend/resume? */
> + writew(0xbf, data->mapbase + T7L66XB_SYS_IMR);
> +
> + printk(KERN_INFO "%s rev %d @ 0x%08lx using irq %d-%d on irq %d\n",
> + dev->name, readw(data->mapbase + T7L66XB_SYS_RIDR),
> + (unsigned long)data->mapbase, data->irq_base,
> + data->irq_base + T7L66XB_NR_IRQS - 1, data->irq_nr);
> +
> + data->devices = mfd_add_devices(dev, t7l66xb_devices,
> + ARRAY_SIZE(t7l66xb_devices),
> + &dev->resource[0], 0, data->irq_base);

dev->resource === &dev->resource[0]

> +
> + if(!data->devices){

should be:
if (!data->devices) {

> + printk(KERN_INFO "%s: Failed to allocate devices!\n",

KERN_ERR ?

> + dev->name);
> + goto out_free_devices;
> + }
> +
> + return 0;
> +
> +out_free_devices:
> + mfd_free_devices(data->devices, ARRAY_SIZE(t7l66xb_devices));
> +out_free_irqs:
> +out_free_data:
> + kfree(data);
> +out:
> + return err;
> +}
> +
> +static int t7l66xb_remove(struct platform_device *dev)
> +{
> + struct t7l66xb_data *tchip = platform_get_drvdata(dev);
> + int i;
> +
> + /* Free subdevices */
> + mfd_free_devices(tchip->devices, ARRAY_SIZE(t7l66xb_devices));

Weird indentation.

> +
> + /* Take down IRQ handling */
> + for (i = 0; i < T7L66XB_NR_IRQS; i++) {
> + int irq = i + tchip->irq_base;
> + set_irq_handler (irq, NULL);
> + set_irq_chip (irq, NULL);
> + set_irq_chip_data (irq, NULL);
> + }

Ditto for most of the above lines.

> +static int __init t7l66xb_init(void)
> +{
> + int retval = 0;
> +
> + retval = platform_driver_register (&t7l66xb_platform_driver);
> + return retval;

Looks like a long winded way of writing

return platform_driver_register (&t7l66xb_platform_driver);

> diff --git a/drivers/mfd/tc6387xb.c b/drivers/mfd/tc6387xb.c
> new file mode 100644
> index 0000000..d309b9f
> --- /dev/null
> +++ b/drivers/mfd/tc6387xb.c
> @@ -0,0 +1,143 @@
> +/*
> + * drivers/mfd/tc6387xb.c
> + *
> + * 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.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>

Delays not used?

> +#include <linux/errno.h>
> +#include <linux/ioport.h>
> +#include <linux/device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include <asm/hardware.h>
> +#include <asm/mach-types.h>

No machine types used?

> diff --git a/drivers/mfd/tc6393xb.c b/drivers/mfd/tc6393xb.c
> new file mode 100644
> index 0000000..3d43ce3
> --- /dev/null
> +++ b/drivers/mfd/tc6393xb.c
> @@ -0,0 +1,369 @@
> +/*
> + * drivers/mfd/tc6393xb.c
> + *
> + * Toshiba TC6393 support
> + * Copyright (c) 2005 Dirk Opfer
> + * Copyright (c) 2005 Ian Molton <[email protected]>
> + *
> + * 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/module.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/delay.h>

linux/delay.h not used?

> +#include <linux/errno.h>
> +#include <linux/ioport.h>
> +#include <linux/irq.h>

linux/irq.h not used?

> +#include <linux/device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include <asm/hardware.h>
> +#include <asm/mach-types.h>

machine types not used?

> +#include <asm/io.h>
> +#include <asm/arch/pxa-regs.h>
> +
> +#include <linux/tmio_mmc.h>
> +#include <linux/tmio_nand.h>
> +#include <linux/tmio_ohci.h>
> +#include <linux/tc6393.h>
> +#include "mfd-core.h"
> +
> +#define platform_get_platdata(_dev) ((_dev)->dev.platform_data)

should be in linux/platform_device.h ?

> +static struct tmio_mmc_hwconfig tc6393xb_mmc_hwconfig = {
> + .set_mmc_clock = tc6393xb_mmc_set_clock,

Indentation?

> + while ( (req = (readb(data->mapbase + TC6393_SYS_ISR)
> + & ~(readb(data->mapbase + TC6393_SYS_IMR)))) ) {

Revenge of the parens.

> + for (i = 0; i <= 7; i++) {
> + int dev_irq = data->irq_base + i;
> + struct irq_desc *d = NULL;
> + if ((req & (1<<i))) {

Parens strike back.

> + if(i != 1) printk("IRQ! from %d\n", i);

But strange spacing moves in for the kill.

> + if(!pdata || !tchip){

And again. Life points reducing.

> + data->irq_base = pdata->irq_base;
> + data->irq_nr = dev->resource[1].start;

platform_get_irq() ?

> +
> + if (!data->irq_base) {
> + printk("tc6393xb: uninitialized irq_base!\n");

KERN_ERR ?

> + goto out_free_data;
> + }
> +
> + data->mapbase = ioremap(pbase, plen);
> + if(!data->mapbase)

Spacing.

> + goto out_free_irqs;
> +
> + platform_set_drvdata(dev, data);
> + tc6393xb_setup_irq (data);
> + tc6393xb_hwinit(dev);
> +
> + /* Enable (but mask!) our IRQs */
> + writew(0, data->mapbase + TC6393_SYS_IRR);
> + writew(0xbf, data->mapbase + TC6393_SYS_IMR);
> +
> + printk(KERN_INFO "%s rev %d @ 0x%08lx using irq %d-%d on irq %d\n",
> + dev->name, readw(data->mapbase + TC6393_SYS_RIDR),
> + (unsigned long)data->mapbase, data->irq_base,
> + data->irq_base + TC6393XB_NR_IRQS - 1, data->irq_nr);
> +
> + data->devices = mfd_add_devices(dev, tc6393xb_devices,
> + ARRAY_SIZE(tc6393xb_devices),
> + &dev->resource[0], 0, data->irq_base);

dev->resource === &dev->resource[0]

> +
> + if(!data->devices) {

Spacing.

> + printk(KERN_INFO "%s: Failed to allocate devices!\n",

KERN_ERR.

> +static int tc6393xb_remove(struct platform_device *dev)
> +{
> + struct tc6393xb_data *tchip = platform_get_drvdata(dev);
> + int i;
> +
> + /* Free subdevices */
> + mfd_free_devices(tchip->devices, ARRAY_SIZE(tc6393xb_devices));

Indentation.

> +
> + /* Take down IRQ handling */
> + for (i = 0; i < TC6393XB_NR_IRQS; i++) {
> + int irq = i + tchip->irq_base;
> + set_irq_handler (irq, NULL);
> + set_irq_chip (irq, NULL);
> + set_irq_chip_data (irq, NULL);
> + }
> +
> + set_irq_chained_handler (tchip->irq_nr, NULL);
> +
> + /* Free core resources */
> + iounmap (tchip->mapbase);

Indentation.

I think by now you get the idea, so I'm not going to continue with this
review. Maybe if you can fix up the above plus further instances of them
in the remainder of the patch set, it can be reviewed again.

Note that leaving soo many such trivialities for a reviewer to find causes
reviewer tiredness, increases the number of reviews, and decreases the
motivation for reviewers to read your code.

There's a tool (scripts/checkpatch.pl) designed to aid you in identifying
trivialities like the above. Please consider using it. (It won't find
all of the above, and it's not always 100% correct, but that is where a
review by humans come in.)