Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc
Linus Torvalds [Sun, 14 Jun 2009 20:46:57 +0000 (13:46 -0700)]
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc: (25 commits)
  atmel-mci: add MCI2 register definitions
  atmel-mci: Integrate AT91 specific definition in header file
  tmio_mmc: allow compilation for ASIC3
  mmc_block: do not DMA to stack
  sdhci: Print ADMA status and pointer on debug
  tmio_mmc: fix clock setup
  tmio_mmc: map SD control registers after enabling the MFD cell
  tmio_mmc: correct probe return value for num_resources != 3
  tmio_mmc: don't use set_irq_type
  tmio_mmc: add bus_shift support
  MFD,mmc: tmio_mmc: make HCLK configurable
  mmc_spi: don't use EINVAL for possible transmission errors
  cb710: more cleanup for the DEBUG case.
  sdhci: platform driver for SDHCI
  mxcmmc: remove frequency workaround
  cb710: handle DEBUG define in Makefile
  cb710: add missing parenthesis
  cb710: fix printk format string
  mmc: Driver for CB710/720 memory card reader (MMC part)
  pxamci: add regulator support.
  ...

30 files changed:
MAINTAINERS
drivers/mfd/t7l66xb.c
drivers/mfd/tc6387xb.c
drivers/mfd/tc6393xb.c
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/cb710/Kconfig [new file with mode: 0644]
drivers/misc/cb710/Makefile [new file with mode: 0644]
drivers/misc/cb710/core.c [new file with mode: 0644]
drivers/misc/cb710/debug.c [new file with mode: 0644]
drivers/misc/cb710/sgbuf2.c [new file with mode: 0644]
drivers/mmc/card/block.c
drivers/mmc/core/core.c
drivers/mmc/host/Kconfig
drivers/mmc/host/Makefile
drivers/mmc/host/atmel-mci-regs.h
drivers/mmc/host/cb710-mmc.c [new file with mode: 0644]
drivers/mmc/host/cb710-mmc.h [new file with mode: 0644]
drivers/mmc/host/mmc_spi.c
drivers/mmc/host/mxcmmc.c
drivers/mmc/host/omap.c
drivers/mmc/host/pxamci.c
drivers/mmc/host/sdhci-pltfm.c [new file with mode: 0644]
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h
drivers/mmc/host/tmio_mmc.c
drivers/mmc/host/tmio_mmc.h
include/linux/cb710.h [new file with mode: 0644]
include/linux/mfd/tmio.h
include/linux/pci_ids.h

index 9e28c5c..a1fe87a 100644 (file)
@@ -2112,6 +2112,15 @@ W:       http://sourceforge.net/projects/lpfcxxxx
 S:     Supported
 F:     drivers/scsi/lpfc/
 
+ENE CB710 FLASH CARD READER DRIVER
+P:     Michał Mirosław
+M:     mirq-linux@rere.qmqm.pl
+L:     linux-kernel@vger.kernel.org
+S:     Maintained
+F:     drivers/misc/cb710/
+F:     drivers/mmc/host/cb710-mmc.*
+F:     include/linux/cb710.h
+
 EPSON 1355 FRAMEBUFFER DRIVER
 P:     Christopher Hoover
 M:     ch@murgatroid.com
index e9f4323..875f7a8 100644 (file)
@@ -108,6 +108,10 @@ static int t7l66xb_mmc_disable(struct platform_device *mmc)
 
 /*--------------------------------------------------------------------------*/
 
+static const struct tmio_mmc_data t7166xb_mmc_data = {
+       .hclk = 24000000,
+};
+
 static const struct resource t7l66xb_mmc_resources[] = {
        {
                .start = 0x800,
@@ -149,6 +153,7 @@ static struct mfd_cell t7l66xb_cells[] = {
                .name = "tmio-mmc",
                .enable = t7l66xb_mmc_enable,
                .disable = t7l66xb_mmc_disable,
+               .driver_data = &t7166xb_mmc_data,
                .num_resources = ARRAY_SIZE(t7l66xb_mmc_resources),
                .resources = t7l66xb_mmc_resources,
        },
index 43222c1..c3993ac 100644 (file)
@@ -75,6 +75,10 @@ static int tc6387xb_mmc_disable(struct platform_device *mmc)
 
 /*--------------------------------------------------------------------------*/
 
+const static struct tmio_mmc_data tc6387xb_mmc_data = {
+       .hclk = 24000000,
+};
+
 static struct resource tc6387xb_mmc_resources[] = {
        {
                .start = 0x800,
@@ -98,6 +102,7 @@ static struct mfd_cell tc6387xb_cells[] = {
                .name = "tmio-mmc",
                .enable = tc6387xb_mmc_enable,
                .disable = tc6387xb_mmc_disable,
+               .driver_data = &tc6387xb_mmc_data,
                .num_resources = ARRAY_SIZE(tc6387xb_mmc_resources),
                .resources = tc6387xb_mmc_resources,
        },
index 77a12fc..9d2abb5 100644 (file)
@@ -136,6 +136,10 @@ static int tc6393xb_nand_enable(struct platform_device *nand)
        return 0;
 }
 
+const static struct tmio_mmc_data tc6393xb_mmc_data = {
+       .hclk = 24000000,
+};
+
 static struct resource __devinitdata tc6393xb_nand_resources[] = {
        {
                .start  = 0x1000,
@@ -351,6 +355,7 @@ static struct mfd_cell __devinitdata tc6393xb_cells[] = {
        },
        [TC6393XB_CELL_MMC] = {
                .name = "tmio-mmc",
+               .driver_data = &tc6393xb_mmc_data,
                .num_resources = ARRAY_SIZE(tc6393xb_mmc_resources),
                .resources = tc6393xb_mmc_resources,
        },
index 6d1ac18..68ab39d 100644 (file)
@@ -235,5 +235,6 @@ config ISL29003
 
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
+source "drivers/misc/cb710/Kconfig"
 
 endif # MISC_DEVICES
index 7871f05..36f733c 100644 (file)
@@ -21,3 +21,4 @@ obj-$(CONFIG_HP_ILO)          += hpilo.o
 obj-$(CONFIG_ISL29003)         += isl29003.o
 obj-$(CONFIG_C2PORT)           += c2port/
 obj-y                          += eeprom/
+obj-y                          += cb710/
diff --git a/drivers/misc/cb710/Kconfig b/drivers/misc/cb710/Kconfig
new file mode 100644 (file)
index 0000000..22429b8
--- /dev/null
@@ -0,0 +1,25 @@
+config CB710_CORE
+       tristate "ENE CB710/720 Flash memory card reader support"
+       depends on PCI
+       help
+         This option enables support for PCI ENE CB710/720 Flash memory card
+         reader found in some laptops (ie. some versions of HP Compaq nx9500).
+
+         You will also have to select some flash card format drivers (MMC/SD,
+         MemoryStick).
+
+         This driver can also be built as a module. If so, the module
+         will be called cb710.
+
+config CB710_DEBUG
+       bool "Enable driver debugging"
+       depends on CB710_CORE != n
+       default n
+       help
+         This is an option for use by developers; most people should
+         say N here.  This adds a lot of debugging output to dmesg.
+
+config CB710_DEBUG_ASSUMPTIONS
+       bool
+       depends on CB710_CORE != n
+       default y
diff --git a/drivers/misc/cb710/Makefile b/drivers/misc/cb710/Makefile
new file mode 100644 (file)
index 0000000..7b80cbf
--- /dev/null
@@ -0,0 +1,8 @@
+ifeq ($(CONFIG_CB710_DEBUG),y)
+       EXTRA_CFLAGS            += -DDEBUG
+endif
+
+obj-$(CONFIG_CB710_CORE)       += cb710.o
+
+cb710-y                                := core.o sgbuf2.o
+cb710-$(CONFIG_CB710_DEBUG)    += debug.o
diff --git a/drivers/misc/cb710/core.c b/drivers/misc/cb710/core.c
new file mode 100644 (file)
index 0000000..b14eab0
--- /dev/null
@@ -0,0 +1,357 @@
+/*
+ *  cb710/core.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/idr.h>
+#include <linux/cb710.h>
+
+static DEFINE_IDA(cb710_ida);
+static DEFINE_SPINLOCK(cb710_ida_lock);
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+       int reg, uint32_t mask, uint32_t xor)
+{
+       u32 rval;
+
+       pci_read_config_dword(pdev, reg, &rval);
+       rval = (rval & mask) ^ xor;
+       pci_write_config_dword(pdev, reg, rval);
+}
+EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg);
+
+/* Some magic writes based on Windows driver init code */
+static int __devinit cb710_pci_configure(struct pci_dev *pdev)
+{
+       unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0);
+       struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn);
+       u32 val;
+
+       cb710_pci_update_config_reg(pdev, 0x48,
+               ~0x000000FF, 0x0000003F);
+
+       pci_read_config_dword(pdev, 0x48, &val);
+       if (val & 0x80000000)
+               return 0;
+
+       if (!pdev0)
+               return -ENODEV;
+
+       if (pdev0->vendor == PCI_VENDOR_ID_ENE
+           && pdev0->device == PCI_DEVICE_ID_ENE_720) {
+               cb710_pci_update_config_reg(pdev0, 0x8C,
+                       ~0x00F00000, 0x00100000);
+               cb710_pci_update_config_reg(pdev0, 0xB0,
+                       ~0x08000000, 0x08000000);
+       }
+
+       cb710_pci_update_config_reg(pdev0, 0x8C,
+               ~0x00000F00, 0x00000200);
+       cb710_pci_update_config_reg(pdev0, 0x90,
+               ~0x00060000, 0x00040000);
+
+       pci_dev_put(pdev0);
+
+       return 0;
+}
+
+static irqreturn_t cb710_irq_handler(int irq, void *data)
+{
+       struct cb710_chip *chip = data;
+       struct cb710_slot *slot = &chip->slot[0];
+       irqreturn_t handled = IRQ_NONE;
+       unsigned nr;
+
+       spin_lock(&chip->irq_lock); /* incl. smp_rmb() */
+
+       for (nr = chip->slots; nr; ++slot, --nr) {
+               cb710_irq_handler_t handler_func = slot->irq_handler;
+               if (handler_func && handler_func(slot))
+                       handled = IRQ_HANDLED;
+       }
+
+       spin_unlock(&chip->irq_lock);
+
+       return handled;
+}
+
+static void cb710_release_slot(struct device *dev)
+{
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+       struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev));
+       struct cb710_chip *chip = cb710_slot_to_chip(slot);
+
+       /* slot struct can be freed now */
+       atomic_dec(&chip->slot_refs_count);
+#endif
+}
+
+static int __devinit cb710_register_slot(struct cb710_chip *chip,
+       unsigned slot_mask, unsigned io_offset, const char *name)
+{
+       int nr = chip->slots;
+       struct cb710_slot *slot = &chip->slot[nr];
+       int err;
+
+       dev_dbg(cb710_chip_dev(chip),
+               "register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n",
+               name, chip->platform_id, nr, slot_mask, io_offset);
+
+       /* slot->irq_handler == NULL here; this needs to be
+        * seen before platform_device_register() */
+       ++chip->slots;
+       smp_wmb();
+
+       slot->iobase = chip->iobase + io_offset;
+       slot->pdev.name = name;
+       slot->pdev.id = chip->platform_id;
+       slot->pdev.dev.parent = &chip->pdev->dev;
+       slot->pdev.dev.release = cb710_release_slot;
+
+       err = platform_device_register(&slot->pdev);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+       atomic_inc(&chip->slot_refs_count);
+#endif
+
+       if (err) {
+               /* device_initialize() called from platform_device_register()
+                * wants this on error path */
+               platform_device_put(&slot->pdev);
+
+               /* slot->irq_handler == NULL here anyway, so no lock needed */
+               --chip->slots;
+               return err;
+       }
+
+       chip->slot_mask |= slot_mask;
+
+       return 0;
+}
+
+static void cb710_unregister_slot(struct cb710_chip *chip,
+       unsigned slot_mask)
+{
+       int nr = chip->slots - 1;
+
+       if (!(chip->slot_mask & slot_mask))
+               return;
+
+       platform_device_unregister(&chip->slot[nr].pdev);
+
+       /* complementary to spin_unlock() in cb710_set_irq_handler() */
+       smp_rmb();
+       BUG_ON(chip->slot[nr].irq_handler != NULL);
+
+       /* slot->irq_handler == NULL here, so no lock needed */
+       --chip->slots;
+       chip->slot_mask &= ~slot_mask;
+}
+
+void cb710_set_irq_handler(struct cb710_slot *slot,
+       cb710_irq_handler_t handler)
+{
+       struct cb710_chip *chip = cb710_slot_to_chip(slot);
+       unsigned long flags;
+
+       spin_lock_irqsave(&chip->irq_lock, flags);
+       slot->irq_handler = handler;
+       spin_unlock_irqrestore(&chip->irq_lock, flags);
+}
+EXPORT_SYMBOL_GPL(cb710_set_irq_handler);
+
+#ifdef CONFIG_PM
+
+static int cb710_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+       struct cb710_chip *chip = pci_get_drvdata(pdev);
+
+       free_irq(pdev->irq, chip);
+       pci_save_state(pdev);
+       pci_disable_device(pdev);
+       if (state.event & PM_EVENT_SLEEP)
+               pci_set_power_state(pdev, PCI_D3cold);
+       return 0;
+}
+
+static int cb710_resume(struct pci_dev *pdev)
+{
+       struct cb710_chip *chip = pci_get_drvdata(pdev);
+       int err;
+
+       pci_set_power_state(pdev, PCI_D0);
+       pci_restore_state(pdev);
+       err = pcim_enable_device(pdev);
+       if (err)
+               return err;
+
+       return devm_request_irq(&pdev->dev, pdev->irq,
+               cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_probe(struct pci_dev *pdev,
+       const struct pci_device_id *ent)
+{
+       struct cb710_chip *chip;
+       unsigned long flags;
+       u32 val;
+       int err;
+       int n = 0;
+
+       err = cb710_pci_configure(pdev);
+       if (err)
+               return err;
+
+       /* this is actually magic... */
+       pci_read_config_dword(pdev, 0x48, &val);
+       if (!(val & 0x80000000)) {
+               pci_write_config_dword(pdev, 0x48, val|0x71000000);
+               pci_read_config_dword(pdev, 0x48, &val);
+       }
+
+       dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val);
+       if (!(val & 0x70000000))
+               return -ENODEV;
+       val = (val >> 28) & 7;
+       if (val & CB710_SLOT_MMC)
+               ++n;
+       if (val & CB710_SLOT_MS)
+               ++n;
+       if (val & CB710_SLOT_SM)
+               ++n;
+
+       chip = devm_kzalloc(&pdev->dev,
+               sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL);
+       if (!chip)
+               return -ENOMEM;
+
+       err = pcim_enable_device(pdev);
+       if (err)
+               return err;
+
+       err = pcim_iomap_regions(pdev, 0x0001, KBUILD_MODNAME);
+       if (err)
+               return err;
+
+       chip->pdev = pdev;
+       chip->iobase = pcim_iomap_table(pdev)[0];
+
+       pci_set_drvdata(pdev, chip);
+
+       err = devm_request_irq(&pdev->dev, pdev->irq,
+               cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip);
+       if (err)
+               return err;
+
+       do {
+               if (!ida_pre_get(&cb710_ida, GFP_KERNEL))
+                       return -ENOMEM;
+
+               spin_lock_irqsave(&cb710_ida_lock, flags);
+               err = ida_get_new(&cb710_ida, &chip->platform_id);
+               spin_unlock_irqrestore(&cb710_ida_lock, flags);
+
+               if (err && err != -EAGAIN)
+                       return err;
+       } while (err);
+
+
+       dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n",
+               chip->platform_id, chip->iobase, pdev->irq);
+
+       if (val & CB710_SLOT_MMC) {     /* MMC/SD slot */
+               err = cb710_register_slot(chip,
+                       CB710_SLOT_MMC, 0x00, "cb710-mmc");
+               if (err)
+                       return err;
+       }
+
+       if (val & CB710_SLOT_MS) {      /* MemoryStick slot */
+               err = cb710_register_slot(chip,
+                       CB710_SLOT_MS, 0x40, "cb710-ms");
+               if (err)
+                       goto unreg_mmc;
+       }
+
+       if (val & CB710_SLOT_SM) {      /* SmartMedia slot */
+               err = cb710_register_slot(chip,
+                       CB710_SLOT_SM, 0x60, "cb710-sm");
+               if (err)
+                       goto unreg_ms;
+       }
+
+       return 0;
+unreg_ms:
+       cb710_unregister_slot(chip, CB710_SLOT_MS);
+unreg_mmc:
+       cb710_unregister_slot(chip, CB710_SLOT_MMC);
+
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+       BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+       return err;
+}
+
+static void __devexit cb710_remove_one(struct pci_dev *pdev)
+{
+       struct cb710_chip *chip = pci_get_drvdata(pdev);
+       unsigned long flags;
+
+       cb710_unregister_slot(chip, CB710_SLOT_SM);
+       cb710_unregister_slot(chip, CB710_SLOT_MS);
+       cb710_unregister_slot(chip, CB710_SLOT_MMC);
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+       BUG_ON(atomic_read(&chip->slot_refs_count) != 0);
+#endif
+
+       spin_lock_irqsave(&cb710_ida_lock, flags);
+       ida_remove(&cb710_ida, chip->platform_id);
+       spin_unlock_irqrestore(&cb710_ida_lock, flags);
+}
+
+static const struct pci_device_id cb710_pci_tbl[] = {
+       { PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_CB710_FLASH,
+               PCI_ANY_ID, PCI_ANY_ID, },
+       { 0, }
+};
+
+static struct pci_driver cb710_driver = {
+       .name = KBUILD_MODNAME,
+       .id_table = cb710_pci_tbl,
+       .probe = cb710_probe,
+       .remove = __devexit_p(cb710_remove_one),
+#ifdef CONFIG_PM
+       .suspend = cb710_suspend,
+       .resume = cb710_resume,
+#endif
+};
+
+static int __init cb710_init_module(void)
+{
+       return pci_register_driver(&cb710_driver);
+}
+
+static void __exit cb710_cleanup_module(void)
+{
+       pci_unregister_driver(&cb710_driver);
+       ida_destroy(&cb710_ida);
+}
+
+module_init(cb710_init_module);
+module_exit(cb710_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, cb710_pci_tbl);
diff --git a/drivers/misc/cb710/debug.c b/drivers/misc/cb710/debug.c
new file mode 100644 (file)
index 0000000..02358d0
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ *  cb710/debug.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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/cb710.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define CB710_REG_COUNT                0x80
+
+static const u16 allow[CB710_REG_COUNT/16] = {
+       0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+       0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF,
+};
+static const char *const prefix[ARRAY_SIZE(allow)] = {
+       "MMC", "MMC", "MMC", "MMC",
+       "MS?", "MS?", "SM?", "SM?"
+};
+
+static inline int allow_reg_read(unsigned block, unsigned offset, unsigned bits)
+{
+       unsigned mask = (1 << bits/8) - 1;
+       offset *= bits/8;
+       return ((allow[block] >> offset) & mask) == mask;
+}
+
+#define CB710_READ_REGS_TEMPLATE(t)                                    \
+static void cb710_read_regs_##t(void __iomem *iobase,                  \
+       u##t *reg, unsigned select)                                     \
+{                                                                      \
+       unsigned i, j;                                                  \
+                                                                       \
+       for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {      \
+               if (!(select & (1 << i)))                                       \
+                       continue;                                       \
+                                                                       \
+               for (j = 0; j < 0x10/(t/8); ++j) {                      \
+                       if (!allow_reg_read(i, j, t))                   \
+                               continue;                               \
+                       reg[j] = ioread##t(iobase                       \
+                               + (i << 4) + (j * (t/8)));              \
+               }                                                       \
+       }                                                               \
+}
+
+static const char cb710_regf_8[] = "%02X";
+static const char cb710_regf_16[] = "%04X";
+static const char cb710_regf_32[] = "%08X";
+static const char cb710_xes[] = "xxxxxxxx";
+
+#define CB710_DUMP_REGS_TEMPLATE(t)                                    \
+static void cb710_dump_regs_##t(struct device *dev,                    \
+       const u##t *reg, unsigned select)                               \
+{                                                                      \
+       const char *const xp = &cb710_xes[8 - t/4];                     \
+       const char *const format = cb710_regf_##t;                      \
+                                                                       \
+       char msg[100], *p;                                              \
+       unsigned i, j;                                                  \
+                                                                       \
+       for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) {      \
+               if (!(select & (1 << i)))                               \
+                       continue;                                       \
+               p = msg;                                                \
+               for (j = 0; j < 0x10/(t/8); ++j) {                      \
+                       *p++ = ' ';                                     \
+                       if (j == 8/(t/8))                               \
+                               *p++ = ' ';                             \
+                       if (allow_reg_read(i, j, t))                    \
+                               p += sprintf(p, format, reg[j]);        \
+                       else                                            \
+                               p += sprintf(p, "%s", xp);              \
+               }                                                       \
+               dev_dbg(dev, "%s 0x%02X %s\n", prefix[i], i << 4, msg); \
+       }                                                               \
+}
+
+#define CB710_READ_AND_DUMP_REGS_TEMPLATE(t)                           \
+static void cb710_read_and_dump_regs_##t(struct cb710_chip *chip,      \
+       unsigned select)                                                \
+{                                                                      \
+       u##t regs[CB710_REG_COUNT/sizeof(u##t)];                        \
+                                                                       \
+       memset(&regs, 0, sizeof(regs));                                 \
+       cb710_read_regs_##t(chip->iobase, regs, select);                \
+       cb710_dump_regs_##t(cb710_chip_dev(chip), regs, select);        \
+}
+
+#define CB710_REG_ACCESS_TEMPLATES(t)          \
+  CB710_READ_REGS_TEMPLATE(t)                  \
+  CB710_DUMP_REGS_TEMPLATE(t)                  \
+  CB710_READ_AND_DUMP_REGS_TEMPLATE(t)
+
+CB710_REG_ACCESS_TEMPLATES(8)
+CB710_REG_ACCESS_TEMPLATES(16)
+CB710_REG_ACCESS_TEMPLATES(32)
+
+void cb710_dump_regs(struct cb710_chip *chip, unsigned select)
+{
+       if (!(select & CB710_DUMP_REGS_MASK))
+               select = CB710_DUMP_REGS_ALL;
+       if (!(select & CB710_DUMP_ACCESS_MASK))
+               select |= CB710_DUMP_ACCESS_8;
+
+       if (select & CB710_DUMP_ACCESS_32)
+               cb710_read_and_dump_regs_32(chip, select);
+       if (select & CB710_DUMP_ACCESS_16)
+               cb710_read_and_dump_regs_16(chip, select);
+       if (select & CB710_DUMP_ACCESS_8)
+               cb710_read_and_dump_regs_8(chip, select);
+}
+EXPORT_SYMBOL_GPL(cb710_dump_regs);
+
diff --git a/drivers/misc/cb710/sgbuf2.c b/drivers/misc/cb710/sgbuf2.c
new file mode 100644 (file)
index 0000000..d38a7ac
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ *  cb710/sgbuf2.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/cb710.h>
+
+static bool sg_dwiter_next(struct sg_mapping_iter *miter)
+{
+       if (sg_miter_next(miter)) {
+               miter->consumed = 0;
+               return true;
+       } else
+               return false;
+}
+
+static bool sg_dwiter_is_at_end(struct sg_mapping_iter *miter)
+{
+       return miter->length == miter->consumed && !sg_dwiter_next(miter);
+}
+
+static uint32_t sg_dwiter_read_buffer(struct sg_mapping_iter *miter)
+{
+       size_t len, left = 4;
+       uint32_t data;
+       void *addr = &data;
+
+       do {
+               len = min(miter->length - miter->consumed, left);
+               memcpy(addr, miter->addr + miter->consumed, len);
+               miter->consumed += len;
+               left -= len;
+               if (!left)
+                       return data;
+               addr += len;
+       } while (sg_dwiter_next(miter));
+
+       memset(addr, 0, left);
+       return data;
+}
+
+static inline bool needs_unaligned_copy(const void *ptr)
+{
+#ifdef HAVE_EFFICIENT_UNALIGNED_ACCESS
+       return false;
+#else
+       return ((ptr - NULL) & 3) != 0;
+#endif
+}
+
+static bool sg_dwiter_get_next_block(struct sg_mapping_iter *miter, uint32_t **ptr)
+{
+       size_t len;
+
+       if (sg_dwiter_is_at_end(miter))
+               return true;
+
+       len = miter->length - miter->consumed;
+
+       if (likely(len >= 4 && !needs_unaligned_copy(
+                       miter->addr + miter->consumed))) {
+               *ptr = miter->addr + miter->consumed;
+               miter->consumed += 4;
+               return true;
+       }
+
+       return false;
+}
+
+/**
+ * cb710_sg_dwiter_read_next_block() - get next 32-bit word from sg buffer
+ * @miter: sg mapping iterator used for reading
+ *
+ * Description:
+ *   Returns 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes past the buffer's end
+ *   are not accessed (read) but are returned as zeroes.  @miter@
+ *   is advanced by 4 bytes or to the end of buffer whichever is
+ *   closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ *
+ * Returns:
+ *   32-bit word just read.
+ */
+uint32_t cb710_sg_dwiter_read_next_block(struct sg_mapping_iter *miter)
+{
+       uint32_t *ptr = NULL;
+
+       if (likely(sg_dwiter_get_next_block(miter, &ptr)))
+               return ptr ? *ptr : 0;
+
+       return sg_dwiter_read_buffer(miter);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_dwiter_read_next_block);
+
+static void sg_dwiter_write_slow(struct sg_mapping_iter *miter, uint32_t data)
+{
+       size_t len, left = 4;
+       void *addr = &data;
+
+       do {
+               len = min(miter->length - miter->consumed, left);
+               memcpy(miter->addr, addr, len);
+               miter->consumed += len;
+               left -= len;
+               if (!left)
+                       return;
+               addr += len;
+               flush_kernel_dcache_page(miter->page);
+       } while (sg_dwiter_next(miter));
+}
+
+/**
+ * cb710_sg_dwiter_write_next_block() - write next 32-bit word to sg buffer
+ * @miter: sg mapping iterator used for writing
+ *
+ * Description:
+ *   Writes 32-bit word starting at byte pointed to by @miter@
+ *   handling any alignment issues.  Bytes which would be written
+ *   past the buffer's end are silently discarded. @miter@ is
+ *   advanced by 4 bytes or to the end of buffer whichever is closer.
+ *
+ * Context:
+ *   Same requirements as in sg_miter_next().
+ */
+void cb710_sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data)
+{
+       uint32_t *ptr = NULL;
+
+       if (likely(sg_dwiter_get_next_block(miter, &ptr))) {
+               if (ptr)
+                       *ptr = data;
+               else
+                       return;
+       } else
+               sg_dwiter_write_slow(miter, data);
+
+       if (miter->length == miter->consumed)
+               flush_kernel_dcache_page(miter->page);
+}
+EXPORT_SYMBOL_GPL(cb710_sg_dwiter_write_next_block);
+
index 98ffc41..adc205c 100644 (file)
@@ -147,7 +147,8 @@ struct mmc_blk_request {
 static u32 mmc_sd_num_wr_blocks(struct mmc_card *card)
 {
        int err;
-       __be32 blocks;
+       u32 result;
+       __be32 *blocks;
 
        struct mmc_request mrq;
        struct mmc_command cmd;
@@ -199,14 +200,21 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card)
        mrq.cmd = &cmd;
        mrq.data = &data;
 
-       sg_init_one(&sg, &blocks, 4);
+       blocks = kmalloc(4, GFP_KERNEL);
+       if (!blocks)
+               return (u32)-1;
+
+       sg_init_one(&sg, blocks, 4);
 
        mmc_wait_for_req(card->host, &mrq);
 
+       result = ntohl(*blocks);
+       kfree(blocks);
+
        if (cmd.error || data.error)
-               return (u32)-1;
+               result = (u32)-1;
 
-       return ntohl(blocks);
+       return result;
 }
 
 static u32 get_card_status(struct mmc_card *card, struct request *req)
index 2649117..d84c880 100644 (file)
@@ -708,7 +708,13 @@ static void mmc_power_up(struct mmc_host *host)
         */
        mmc_delay(10);
 
-       host->ios.clock = host->f_min;
+       if (host->f_min > 400000) {
+               pr_warning("%s: Minimum clock frequency too high for "
+                               "identification mode\n", mmc_hostname(host));
+               host->ios.clock = host->f_min;
+       } else
+               host->ios.clock = 400000;
+
        host->ios.power_mode = MMC_POWER_ON;
        mmc_set_ios(host);
 
@@ -855,61 +861,72 @@ void mmc_rescan(struct work_struct *work)
 
        mmc_bus_get(host);
 
-       if (host->bus_ops == NULL) {
-               /*
-                * Only we can add a new handler, so it's safe to
-                * release the lock here.
-                */
+       /* if there is a card registered, check whether it is still present */
+       if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)
+               host->bus_ops->detect(host);
+
+       mmc_bus_put(host);
+
+
+       mmc_bus_get(host);
+
+       /* if there still is a card present, stop here */
+       if (host->bus_ops != NULL) {
                mmc_bus_put(host);
+               goto out;
+       }
 
-               if (host->ops->get_cd && host->ops->get_cd(host) == 0)
-                       goto out;
+       /* detect a newly inserted card */
 
-               mmc_claim_host(host);
+       /*
+        * Only we can add a new handler, so it's safe to
+        * release the lock here.
+        */
+       mmc_bus_put(host);
 
-               mmc_power_up(host);
-               mmc_go_idle(host);
+       if (host->ops->get_cd && host->ops->get_cd(host) == 0)
+               goto out;
 
-               mmc_send_if_cond(host, host->ocr_avail);
+       mmc_claim_host(host);
 
-               /*
-                * First we search for SDIO...
-                */
-               err = mmc_send_io_op_cond(host, 0, &ocr);
-               if (!err) {
-                       if (mmc_attach_sdio(host, ocr))
-                               mmc_power_off(host);
-                       goto out;
-               }
+       mmc_power_up(host);
+       mmc_go_idle(host);
 
-               /*
-                * ...then normal SD...
-                */
-               err = mmc_send_app_op_cond(host, 0, &ocr);
-               if (!err) {
-                       if (mmc_attach_sd(host, ocr))
-                               mmc_power_off(host);
-                       goto out;
-               }
+       mmc_send_if_cond(host, host->ocr_avail);
 
-               /*
-                * ...and finally MMC.
-                */
-               err = mmc_send_op_cond(host, 0, &ocr);
-               if (!err) {
-                       if (mmc_attach_mmc(host, ocr))
-                               mmc_power_off(host);
-                       goto out;
-               }
+       /*
+        * First we search for SDIO...
+        */
+       err = mmc_send_io_op_cond(host, 0, &ocr);
+       if (!err) {
+               if (mmc_attach_sdio(host, ocr))
+                       mmc_power_off(host);
+               goto out;
+       }
 
-               mmc_release_host(host);
-               mmc_power_off(host);
-       } else {
-               if (host->bus_ops->detect && !host->bus_dead)
-                       host->bus_ops->detect(host);
+       /*
+        * ...then normal SD...
+        */
+       err = mmc_send_app_op_cond(host, 0, &ocr);
+       if (!err) {
+               if (mmc_attach_sd(host, ocr))
+                       mmc_power_off(host);
+               goto out;
+       }
 
-               mmc_bus_put(host);
+       /*
+        * ...and finally MMC.
+        */
+       err = mmc_send_op_cond(host, 0, &ocr);
+       if (!err) {
+               if (mmc_attach_mmc(host, ocr))
+                       mmc_power_off(host);
+               goto out;
        }
+
+       mmc_release_host(host);
+       mmc_power_off(host);
+
 out:
        if (host->caps & MMC_CAP_NEEDS_POLL)
                mmc_schedule_delayed_work(&host->detect, HZ);
index 3eb87bd..40111a6 100644 (file)
@@ -83,6 +83,17 @@ config MMC_SDHCI_OF
 
          If unsure, say N.
 
+config MMC_SDHCI_PLTFM
+       tristate "SDHCI support on the platform specific bus"
+       depends on MMC_SDHCI
+       help
+         This selects the platform specific bus support for Secure Digital Host
+         Controller Interface.
+
+         If you have a controller with this interface, say Y or M here.
+
+         If unsure, say N.
+
 config MMC_OMAP
        tristate "TI OMAP Multimedia Card Interface support"
        depends on ARCH_OMAP
@@ -237,7 +248,20 @@ config MMC_SDRICOH_CS
 
 config MMC_TMIO
        tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support"
-       depends on MFD_TMIO
+       depends on MFD_TMIO || MFD_ASIC3
        help
          This provides support for the SD/MMC cell found in TC6393XB,
-         T7L66XB and also ipaq ASIC3
+         T7L66XB and also HTC ASIC3
+
+config MMC_CB710
+       tristate "ENE CB710 MMC/SD Interface support"
+       depends on PCI
+       select CB710_CORE
+       help
+         This option enables support for MMC/SD part of ENE CB710/720 Flash
+         memory card reader found in some laptops (ie. some versions of
+         HP Compaq nx9500).
+
+         This driver can also be built as a module. If so, the module
+         will be called cb710-mmc.
+
index 970a997..79da397 100644 (file)
@@ -14,6 +14,7 @@ obj-$(CONFIG_MMC_SDHCI)               += sdhci.o
 obj-$(CONFIG_MMC_SDHCI_PCI)    += sdhci-pci.o
 obj-$(CONFIG_MMC_RICOH_MMC)    += ricoh_mmc.o
 obj-$(CONFIG_MMC_SDHCI_OF)     += sdhci-of.o
+obj-$(CONFIG_MMC_SDHCI_PLTFM)  += sdhci-pltfm.o
 obj-$(CONFIG_MMC_WBSD)         += wbsd.o
 obj-$(CONFIG_MMC_AU1X)         += au1xmmc.o
 obj-$(CONFIG_MMC_OMAP)         += omap.o
@@ -29,4 +30,8 @@ endif
 obj-$(CONFIG_MMC_S3C)          += s3cmci.o
 obj-$(CONFIG_MMC_SDRICOH_CS)   += sdricoh_cs.o
 obj-$(CONFIG_MMC_TMIO)         += tmio_mmc.o
+obj-$(CONFIG_MMC_CB710)        += cb710-mmc.o
 
+ifeq ($(CONFIG_CB710_DEBUG),y)
+       CFLAGS-cb710-mmc        += -DDEBUG
+endif
index b58364e..fc8a0fe 100644 (file)
@@ -7,6 +7,12 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  */
+
+/*
+ * Superset of MCI IP registers integrated in Atmel AVR32 and AT91 Processors
+ * Registers and bitfields marked with [2] are only available in MCI2
+ */
+
 #ifndef __DRIVERS_MMC_ATMEL_MCI_H__
 #define __DRIVERS_MMC_ATMEL_MCI_H__
 
 #define MCI_CR                 0x0000  /* Control */
 # define MCI_CR_MCIEN          (  1 <<  0)     /* MCI Enable */
 # define MCI_CR_MCIDIS         (  1 <<  1)     /* MCI Disable */
+# define MCI_CR_PWSEN          (  1 <<  2)     /* Power Save Enable */
+# define MCI_CR_PWSDIS         (  1 <<  3)     /* Power Save Disable */
 # define MCI_CR_SWRST          (  1 <<  7)     /* Software Reset */
 #define MCI_MR                 0x0004  /* Mode */
 # define MCI_MR_CLKDIV(x)      ((x) <<  0)     /* Clock Divider */
+# define MCI_MR_PWSDIV(x)      ((x) <<  8)     /* Power Saving Divider */
 # define MCI_MR_RDPROOF                (  1 << 11)     /* Read Proof */
 # define MCI_MR_WRPROOF                (  1 << 12)     /* Write Proof */
+# define MCI_MR_PDCFBYTE       (  1 << 13)     /* Force Byte Transfer */
+# define MCI_MR_PDCPADV                (  1 << 14)     /* Padding Value */
+# define MCI_MR_PDCMODE                (  1 << 15)     /* PDC-oriented Mode */
 #define MCI_DTOR               0x0008  /* Data Timeout */
 # define MCI_DTOCYC(x)         ((x) <<  0)     /* Data Timeout Cycles */
 # define MCI_DTOMUL(x)         ((x) <<  4)     /* Data Timeout Multiplier */
@@ -28,6 +40,7 @@
 # define MCI_SDCSEL_MASK       (  3 <<  0)
 # define MCI_SDCBUS_1BIT       (  0 <<  6)     /* 1-bit data bus */
 # define MCI_SDCBUS_4BIT       (  2 <<  6)     /* 4-bit data bus */
+# define MCI_SDCBUS_8BIT       (  3 <<  6)     /* 8-bit data bus[2] */
 # define MCI_SDCBUS_MASK       (  3 <<  6)
 #define MCI_ARGR               0x0010  /* Command Argument */
 #define MCI_CMDR               0x0014  /* Command */
@@ -56,6 +69,9 @@
 #define MCI_BLKR               0x0018  /* Block */
 # define MCI_BCNT(x)           ((x) <<  0)     /* Data Block Count */
 # define MCI_BLKLEN(x)         ((x) << 16)     /* Data Block Length */
+#define MCI_CSTOR              0x001c  /* Completion Signal Timeout[2] */
+# define MCI_CSTOCYC(x)                ((x) <<  0)     /* CST cycles */
+# define MCI_CSTOMUL(x)                ((x) <<  4)     /* CST multiplier */
 #define MCI_RSPR               0x0020  /* Response 0 */
 #define MCI_RSPR1              0x0024  /* Response 1 */
 #define MCI_RSPR2              0x0028  /* Response 2 */
 # define MCI_DTOE              (  1 <<  22)    /* Data Time-Out Error */
 # define MCI_OVRE              (  1 <<  30)    /* RX Overrun Error */
 # define MCI_UNRE              (  1 <<  31)    /* TX Underrun Error */
+#define MCI_DMA                        0x0050  /* DMA Configuration[2] */
+# define MCI_DMA_OFFSET(x)     ((x) <<  0)     /* DMA Write Buffer Offset */
+# define MCI_DMA_CHKSIZE(x)    ((x) <<  4)     /* DMA Channel Read and Write Chunk Size */
+# define MCI_DMAEN             (  1 <<  8)     /* DMA Hardware Handshaking Enable */
+#define MCI_CFG                        0x0054  /* Configuration[2] */
+# define MCI_CFG_FIFOMODE_1DATA        (  1 <<  0)     /* MCI Internal FIFO control mode */
+# define MCI_CFG_FERRCTRL_COR  (  1 <<  4)     /* Flow Error flag reset control mode */
+# define MCI_CFG_HSMODE                (  1 <<  8)     /* High Speed Mode */
+# define MCI_CFG_LSYNC         (  1 << 12)     /* Synchronize on the last block */
+#define MCI_WPMR               0x00e4  /* Write Protection Mode[2] */
+# define MCI_WP_EN             (  1 <<  0)     /* WP Enable */
+# define MCI_WP_KEY            (0x4d4349 << 8) /* WP Key */
+#define MCI_WPSR               0x00e8  /* Write Protection Status[2] */
+# define MCI_GET_WP_VS(x)      ((x) & 0x0f)
+# define MCI_GET_WP_VSRC(x)    (((x) >> 8) & 0xffff)
+#define MCI_FIFO_APERTURE      0x0200  /* FIFO Aperture[2] */
 
+/* This is not including the FIFO Aperture on MCI2 */
 #define MCI_REGS_SIZE          0x100
 
 /* Register access macros */
diff --git a/drivers/mmc/host/cb710-mmc.c b/drivers/mmc/host/cb710-mmc.c
new file mode 100644 (file)
index 0000000..11efefb
--- /dev/null
@@ -0,0 +1,804 @@
+/*
+ *  cb710/mmc.c
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "cb710-mmc.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/*     1, 2, 4, 8, 16, 32, 128, 512 */
+       0, 1, 2, 3,  4,  5,   7,   9
+};
+#define CB710_MAX_DIVIDER_IDX  \
+       (ARRAY_SIZE(cb710_clock_divider_log2) - 1)
+
+static const u8 cb710_src_freq_mhz[16] = {
+       33, 10, 20, 25, 30, 35, 40, 45,
+       50, 55, 60, 65, 70, 75, 80, 85
+};
+
+static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz)
+{
+       struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+       struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
+       u32 src_freq_idx;
+       u32 divider_idx;
+       int src_hz;
+
+       /* this is magic, unverifiable for me, unless I get
+        * MMC card with cables connected to bus signals */
+       pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+       src_freq_idx = (src_freq_idx >> 16) & 0xF;
+       src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+       for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
+               if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+                       break;
+       }
+
+       if (src_freq_idx)
+               divider_idx |= 0x8;
+
+       cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
+
+       dev_dbg(cb710_slot_dev(slot),
+               "clock set to %d Hz, wanted %d Hz; flag = %d\n",
+               src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+               hz, (divider_idx & 8) != 0);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_slot *slot,
+       unsigned short enable, unsigned short mask)
+{
+       /* clear global IE
+        * - it gets set later if any interrupt sources are enabled */
+       mask |= CB710_MMC_IE_IRQ_ENABLE;
+
+       /* look like interrupt is fired whenever
+        * WORD[0x0C] & WORD[0x10] != 0;
+        * -> bit 15 port 0x0C seems to be global interrupt enable
+        */
+
+       enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT)
+               & ~mask) | enable;
+
+       if (enable)
+               enable |= CB710_MMC_IE_IRQ_ENABLE;
+
+       cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable);
+}
+
+static void cb710_mmc_enable_irq(struct cb710_slot *slot,
+       unsigned short enable, unsigned short mask)
+{
+       struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
+       unsigned long flags;
+
+       spin_lock_irqsave(&reader->irq_lock, flags);
+       /* this is the only thing irq_lock protects */
+       __cb710_mmc_enable_irq(slot, enable, mask);
+       spin_unlock_irqrestore(&reader->irq_lock, flags);
+}
+
+static void cb710_mmc_reset_events(struct cb710_slot *slot)
+{
+       cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
+       cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
+       cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static int cb710_mmc_is_card_inserted(struct cb710_slot *slot)
+{
+       return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+               & CB710_MMC_S3_CARD_DETECTED;
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
+{
+       dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n",
+               enable ? 4 : 1, enable ? "s" : "");
+       if (enable)
+               cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+                       CB710_MMC_C1_4BIT_DATA_BUS, 0);
+       else
+               cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+                       0, CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check_event(struct cb710_slot *slot, u8 what)
+{
+       u16 status;
+
+       status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
+
+       if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
+               /* it is just a guess, so log it */
+               dev_dbg(cb710_slot_dev(slot),
+                       "CHECK : ignoring bit 6 in status %04X\n", status);
+               cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+                       CB710_MMC_S0_FIFO_UNDERFLOW);
+               status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
+       }
+
+       if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
+               dev_dbg(cb710_slot_dev(slot),
+                       "CHECK : returning EIO on status %04X\n", status);
+               cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
+               cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+                       CB710_MMC_S1_RESET);
+               return -EIO;
+       }
+
+       /* 'what' is a bit in MMC_STATUS1 */
+       if ((status >> 8) & what) {
+               cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
+{
+       int err = 0;
+       unsigned limit = 2000000;       /* FIXME: real timeout */
+
+#ifdef CONFIG_CB710_DEBUG
+       u32 e, x;
+       e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+       while (!(err = cb710_check_event(slot, what))) {
+               if (!--limit) {
+                       cb710_dump_regs(cb710_slot_to_chip(slot),
+                               CB710_DUMP_REGS_MMC);
+                       err = -ETIMEDOUT;
+                       break;
+               }
+               udelay(1);
+       }
+
+#ifdef CONFIG_CB710_DEBUG
+       x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+       limit = 2000000 - limit;
+       if (limit > 100)
+               dev_dbg(cb710_slot_dev(slot),
+                       "WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
+                       limit, what, e, x);
+#endif
+       return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
+{
+       unsigned limit = 500000;        /* FIXME: real timeout */
+       int err = 0;
+
+#ifdef CONFIG_CB710_DEBUG
+       u32 e, x;
+       e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+       while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
+               if (!--limit) {
+                       cb710_dump_regs(cb710_slot_to_chip(slot),
+                               CB710_DUMP_REGS_MMC);
+                       err = -ETIMEDOUT;
+                       break;
+               }
+               udelay(1);
+       }
+
+#ifdef CONFIG_CB710_DEBUG
+       x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+       limit = 500000 - limit;
+       if (limit > 100)
+               dev_dbg(cb710_slot_dev(slot),
+                       "WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
+                       limit, mask, e, x);
+#endif
+       return 0;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
+       size_t count, size_t blocksize)
+{
+       cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+       cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
+               ((count - 1) << 16)|(blocksize - 1));
+
+       dev_vdbg(cb710_slot_dev(slot), "set up for %zu block%s of %zu bytes\n",
+               count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
+{
+       /* without this, received data is prepended with 8-bytes of zeroes */
+       u32 r1, r2;
+       int ok = 0;
+
+       r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+       r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+       if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
+           & CB710_MMC_S0_FIFO_UNDERFLOW) {
+               cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+                       CB710_MMC_S0_FIFO_UNDERFLOW);
+               ok = 1;
+       }
+
+       dev_dbg(cb710_slot_dev(slot),
+               "FIFO-read-hack: expected STATUS0 bit was %s\n",
+               ok ? "set." : "NOT SET!");
+       dev_dbg(cb710_slot_dev(slot),
+               "FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
+               r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
+}
+
+static int cb710_mmc_receive_pio(struct cb710_slot *slot,
+       struct sg_mapping_iter *miter, size_t dw_count)
+{
+       if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
+               int err = cb710_wait_for_event(slot,
+                       CB710_MMC_S1_PIO_TRANSFER_DONE);
+               if (err)
+                       return err;
+       }
+
+       cb710_sg_dwiter_write_from_io(miter,
+               slot->iobase + CB710_MMC_DATA_PORT, dw_count);
+
+       return 0;
+}
+
+static bool cb710_is_transfer_size_supported(struct mmc_data *data)
+{
+       return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
+}
+
+static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
+{
+       struct sg_mapping_iter miter;
+       size_t len, blocks = data->blocks;
+       int err = 0;
+
+       /* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
+        * except single 8B block */
+       if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
+               return -EINVAL;
+
+       sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+               15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+       cb710_mmc_fifo_hack(slot);
+
+       while (blocks-- > 0) {
+               len = data->blksz;
+
+               while (len >= 16) {
+                       err = cb710_mmc_receive_pio(slot, &miter, 4);
+                       if (err)
+                               goto out;
+                       len -= 16;
+               }
+
+               if (!len)
+                       continue;
+
+               cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+                       len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+               len = (len >= 8) ? 4 : 2;
+               err = cb710_mmc_receive_pio(slot, &miter, len);
+               if (err)
+                       goto out;
+       }
+out:
+       cb710_sg_miter_stop_writing(&miter);
+       return err;
+}
+
+static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
+{
+       struct sg_mapping_iter miter;
+       size_t len, blocks = data->blocks;
+       int err = 0;
+
+       /* TODO: I don't know how/if the hardware handles multiple
+        * non-16B-boundary blocks */
+       if (unlikely(data->blocks > 1 && data->blksz & 15))
+               return -EINVAL;
+
+       sg_miter_start(&miter, data->sg, data->sg_len, 0);
+
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+               0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+       while (blocks-- > 0) {
+               len = (data->blksz + 15) >> 4;
+               do {
+                       if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+                           & CB710_MMC_S2_FIFO_EMPTY)) {
+                               err = cb710_wait_for_event(slot,
+                                       CB710_MMC_S1_PIO_TRANSFER_DONE);
+                               if (err)
+                                       goto out;
+                       }
+                       cb710_sg_dwiter_read_to_io(&miter,
+                               slot->iobase + CB710_MMC_DATA_PORT, 4);
+               } while (--len);
+       }
+out:
+       sg_miter_stop(&miter);
+       return err;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+       struct mmc_command *cmd)
+{
+       unsigned int flags = cmd->flags;
+       u16 cb_flags = 0;
+
+       /* Windows driver returned 0 for commands for which no response
+        * is expected. It happened that there were only two such commands
+        * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+        * as well be a bug in that driver.
+        *
+        * Original driver set bit 14 for MMC/SD application
+        * commands. There's no difference 'on the wire' and
+        * it apparently works without it anyway.
+        */
+
+       switch (flags & MMC_CMD_MASK) {
+       case MMC_CMD_AC:        cb_flags = CB710_MMC_CMD_AC;    break;
+       case MMC_CMD_ADTC:      cb_flags = CB710_MMC_CMD_ADTC;  break;
+       case MMC_CMD_BC:        cb_flags = CB710_MMC_CMD_BC;    break;
+       case MMC_CMD_BCR:       cb_flags = CB710_MMC_CMD_BCR;   break;
+       }
+
+       if (flags & MMC_RSP_BUSY)
+               cb_flags |= CB710_MMC_RSP_BUSY;
+
+       cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+       if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+               cb_flags |= CB710_MMC_DATA_READ;
+
+       if (flags & MMC_RSP_PRESENT) {
+               /* Windows driver set 01 at bits 4,3 except for
+                * MMC_SET_BLOCKLEN where it set 10. Maybe the
+                * hardware can do something special about this
+                * command? The original driver looks buggy/incomplete
+                * anyway so we ignore this for now.
+                *
+                * I assume that 00 here means no response is expected.
+                */
+               cb_flags |= CB710_MMC_RSP_PRESENT;
+
+               if (flags & MMC_RSP_136)
+                       cb_flags |= CB710_MMC_RSP_136;
+               if (!(flags & MMC_RSP_CRC))
+                       cb_flags |= CB710_MMC_RSP_NO_CRC;
+       }
+
+       return cb_flags;
+}
+
+static void cb710_receive_response(struct cb710_slot *slot,
+       struct mmc_command *cmd)
+{
+       unsigned rsp_opcode, wanted_opcode;
+
+       /* Looks like final byte with CRC is always stripped (same as SDHCI) */
+       if (cmd->flags & MMC_RSP_136) {
+               u32 resp[4];
+
+               resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
+               resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
+               resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
+               resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+               rsp_opcode = resp[0] >> 24;
+
+               cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+               cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+               cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+               cmd->resp[3] = (resp[3] << 8);
+       } else {
+               rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+               cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+       }
+
+       wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
+       if (rsp_opcode != wanted_opcode)
+               cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_slot *slot,
+       struct mmc_data *data)
+{
+       int error, to;
+
+       if (data->flags & MMC_DATA_READ)
+               error = cb710_mmc_receive(slot, data);
+       else
+               error = cb710_mmc_send(slot, data);
+
+       to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
+       if (!error)
+               error = to;
+
+       if (!error)
+               data->bytes_xfered = data->blksz * data->blocks;
+       return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+       struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+       struct cb710_mmc_reader *reader = mmc_priv(mmc);
+       struct mmc_data *data = cmd->data;
+
+       u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+       dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
+
+       if (data) {
+               if (!cb710_is_transfer_size_supported(data)) {
+                       data->error = -EINVAL;
+                       return -1;
+               }
+               cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
+       }
+
+       cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
+       cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+       cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+       cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+       cb710_mmc_reset_events(slot);
+       cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
+
+       cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
+       if (cmd->error)
+               return -1;
+
+       if (cmd->flags & MMC_RSP_PRESENT) {
+               cb710_receive_response(slot, cmd);
+               if (cmd->error)
+                       return -1;
+       }
+
+       if (data)
+               data->error = cb710_mmc_transfer_data(slot, data);
+       return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+       struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+       struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+       WARN_ON(reader->mrq != NULL);
+
+       reader->mrq = mrq;
+       cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+
+       if (cb710_mmc_is_card_inserted(slot)) {
+               if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+                       cb710_mmc_command(mmc, mrq->stop);
+               mdelay(1);
+       } else {
+               mrq->cmd->error = -ENOMEDIUM;
+       }
+
+       tasklet_schedule(&reader->finish_req_tasklet);
+}
+
+static int cb710_mmc_powerup(struct cb710_slot *slot)
+{
+#ifdef CONFIG_CB710_DEBUG
+       struct cb710_chip *chip = cb710_slot_to_chip(slot);
+#endif
+       int err;
+
+       /* a lot of magic; see comment in cb710_mmc_set_clock() */
+       dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+       if (unlikely(err))
+               return err;
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       mdelay(1);
+       dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+       if (unlikely(err))
+               return err;
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       mdelay(1);
+       dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+       if (unlikely(err))
+               return err;
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       mdelay(2);
+       dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+       if (unlikely(err))
+               return err;
+       /* This port behaves weird: quick byte reads of 0x08,0x09 return
+        * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
+        * read/written from userspace...  What am I missing here?
+        * (it doesn't depend on write-to-read delay) */
+       cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+       dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
+
+       return cb710_check_event(slot, 0);
+}
+
+static void cb710_mmc_powerdown(struct cb710_slot *slot)
+{
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
+       cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+       struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+       struct cb710_mmc_reader *reader = mmc_priv(mmc);
+       int err;
+
+       cb710_mmc_set_clock(mmc, ios->clock);
+
+       if (!cb710_mmc_is_card_inserted(slot)) {
+               dev_dbg(cb710_slot_dev(slot),
+                       "no card inserted - ignoring bus powerup request\n");
+               ios->power_mode = MMC_POWER_OFF;
+       }
+
+       if (ios->power_mode != reader->last_power_mode)
+       switch (ios->power_mode) {
+       case MMC_POWER_ON:
+               err = cb710_mmc_powerup(slot);
+               if (err) {
+                       dev_warn(cb710_slot_dev(slot),
+                               "powerup failed (%d)- retrying\n", err);
+                       cb710_mmc_powerdown(slot);
+                       udelay(1);
+                       err = cb710_mmc_powerup(slot);
+                       if (err)
+                               dev_warn(cb710_slot_dev(slot),
+                                       "powerup retry failed (%d) - expect errors\n",
+                                       err);
+               }
+               reader->last_power_mode = MMC_POWER_ON;
+               break;
+       case MMC_POWER_OFF:
+               cb710_mmc_powerdown(slot);
+               reader->last_power_mode = MMC_POWER_OFF;
+               break;
+       case MMC_POWER_UP:
+       default:
+               /* ignore */;
+       }
+
+       cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
+
+       cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+       struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+       return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+               & CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+       struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+       struct cb710_mmc_reader *reader = mmc_priv(mmc);
+       u32 status, config1, config2, irqen;
+
+       status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+       irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
+       config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
+       config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
+
+       dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
+               "ie: %08X, c2: %08X, c1: %08X\n",
+               status, irqen, config2, config1);
+
+       if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+               /* ack the event */
+               cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+                       CB710_MMC_S1_CARD_CHANGED);
+               if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
+                   == CB710_MMC_IE_CISTATUS_MASK)
+                       mmc_detect_change(mmc, HZ/5);
+       } else {
+               dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
+               spin_lock(&reader->irq_lock);
+               __cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK);
+               spin_unlock(&reader->irq_lock);
+       }
+
+       return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+       struct mmc_host *mmc = (void *)data;
+       struct cb710_mmc_reader *reader = mmc_priv(mmc);
+       struct mmc_request *mrq = reader->mrq;
+
+       reader->mrq = NULL;
+       mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+       .request = cb710_mmc_request,
+       .set_ios = cb710_mmc_set_ios,
+       .get_ro = cb710_mmc_get_ro
+};
+
+#ifdef CONFIG_PM
+
+static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+       struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+       struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+       int err;
+
+       err = mmc_suspend_host(mmc, state);
+       if (err)
+               return err;
+
+       cb710_mmc_enable_irq(slot, 0, ~0);
+       return 0;
+}
+
+static int cb710_mmc_resume(struct platform_device *pdev)
+{
+       struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+       struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+
+       cb710_mmc_enable_irq(slot, 0, ~0);
+
+       return mmc_resume_host(mmc);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+       struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+       struct cb710_chip *chip = cb710_slot_to_chip(slot);
+       struct mmc_host *mmc;
+       struct cb710_mmc_reader *reader;
+       int err;
+       u32 val;
+
+       mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
+       if (!mmc)
+               return -ENOMEM;
+
+       dev_set_drvdata(&pdev->dev, mmc);
+
+       /* harmless (maybe) magic */
+       pci_read_config_dword(chip->pdev, 0x48, &val);
+       val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+       dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
+       val *= 1000000;
+
+       mmc->ops = &cb710_mmc_host;
+       mmc->f_max = val;
+       mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
+       mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
+       mmc->caps = MMC_CAP_4_BIT_DATA;
+
+       reader = mmc_priv(mmc);
+
+       tasklet_init(&reader->finish_req_tasklet,
+               cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+       spin_lock_init(&reader->irq_lock);
+       cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+       cb710_mmc_enable_irq(slot, 0, ~0);
+       cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
+
+       err = mmc_add_host(mmc);
+       if (unlikely(err))
+               goto err_free_mmc;
+
+       dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
+               mmc_hostname(mmc));
+
+       cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0);
+
+       return 0;
+
+err_free_mmc:
+       dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
+
+       mmc_free_host(mmc);
+       return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+       struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+       struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+       struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+       cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS);
+
+       mmc_remove_host(mmc);
+
+       /* IRQs should be disabled now, but let's stay on the safe side */
+       cb710_mmc_enable_irq(slot, 0, ~0);
+       cb710_set_irq_handler(slot, NULL);
+
+       /* clear config ports - just in case */
+       cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
+       cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
+
+       tasklet_kill(&reader->finish_req_tasklet);
+
+       mmc_free_host(mmc);
+       return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+       .driver.name = "cb710-mmc",
+       .probe = cb710_mmc_init,
+       .remove = __devexit_p(cb710_mmc_exit),
+#ifdef CONFIG_PM
+       .suspend = cb710_mmc_suspend,
+       .resume = cb710_mmc_resume,
+#endif
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+       return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+       platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff --git a/drivers/mmc/host/cb710-mmc.h b/drivers/mmc/host/cb710-mmc.h
new file mode 100644 (file)
index 0000000..e845c77
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ *  cb710/cb710-mmc.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef LINUX_CB710_MMC_H
+#define LINUX_CB710_MMC_H
+
+#include <linux/cb710.h>
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+       struct tasklet_struct finish_req_tasklet;
+       struct mmc_request *mrq;
+       spinlock_t irq_lock;
+       unsigned char last_power_mode;
+};
+
+/* some device struct walking */
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+       return dev_get_drvdata(&slot->pdev.dev);
+}
+
+static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
+{
+       struct platform_device *pdev = container_of(mmc_dev(mmc),
+               struct platform_device, dev);
+       return cb710_pdev_to_slot(pdev);
+}
+
+/* registers (this might be all wrong ;) */
+
+#define CB710_MMC_DATA_PORT            0x00
+
+#define CB710_MMC_CONFIG_PORT          0x04
+#define CB710_MMC_CONFIG0_PORT         0x04
+#define CB710_MMC_CONFIG1_PORT         0x05
+#define   CB710_MMC_C1_4BIT_DATA_BUS           0x40
+#define CB710_MMC_CONFIG2_PORT         0x06
+#define   CB710_MMC_C2_READ_PIO_SIZE_MASK      0x0F    /* N-1 */
+#define CB710_MMC_CONFIG3_PORT         0x07
+
+#define CB710_MMC_CONFIGB_PORT         0x08
+
+#define CB710_MMC_IRQ_ENABLE_PORT      0x0C
+#define   CB710_MMC_IE_TEST_MASK               0x00BF
+#define   CB710_MMC_IE_CARD_INSERTION_STATUS   0x1000
+#define   CB710_MMC_IE_IRQ_ENABLE              0x8000
+#define   CB710_MMC_IE_CISTATUS_MASK           \
+               (CB710_MMC_IE_CARD_INSERTION_STATUS|CB710_MMC_IE_IRQ_ENABLE)
+
+#define CB710_MMC_STATUS_PORT          0x10
+#define   CB710_MMC_STATUS_ERROR_EVENTS                0x60FF
+#define CB710_MMC_STATUS0_PORT         0x10
+#define   CB710_MMC_S0_FIFO_UNDERFLOW          0x40
+#define CB710_MMC_STATUS1_PORT         0x11
+#define   CB710_MMC_S1_COMMAND_SENT            0x01
+#define   CB710_MMC_S1_DATA_TRANSFER_DONE      0x02
+#define   CB710_MMC_S1_PIO_TRANSFER_DONE       0x04
+#define   CB710_MMC_S1_CARD_CHANGED            0x10
+#define   CB710_MMC_S1_RESET                   0x20
+#define CB710_MMC_STATUS2_PORT         0x12
+#define   CB710_MMC_S2_FIFO_READY              0x01
+#define   CB710_MMC_S2_FIFO_EMPTY              0x02
+#define   CB710_MMC_S2_BUSY_10                 0x10
+#define   CB710_MMC_S2_BUSY_20                 0x20
+#define CB710_MMC_STATUS3_PORT         0x13
+#define   CB710_MMC_S3_CARD_DETECTED           0x02
+#define   CB710_MMC_S3_WRITE_PROTECTED         0x04
+
+#define CB710_MMC_CMD_TYPE_PORT                0x14
+#define   CB710_MMC_RSP_TYPE_MASK              0x0007
+#define     CB710_MMC_RSP_R1                   (0)
+#define     CB710_MMC_RSP_136                  (5)
+#define     CB710_MMC_RSP_NO_CRC               (2)
+#define   CB710_MMC_RSP_PRESENT_MASK           0x0018
+#define     CB710_MMC_RSP_NONE                 (0 << 3)
+#define     CB710_MMC_RSP_PRESENT              (1 << 3)
+#define     CB710_MMC_RSP_PRESENT_X            (2 << 3)
+#define   CB710_MMC_CMD_TYPE_MASK              0x0060
+#define     CB710_MMC_CMD_BC                   (0 << 5)
+#define     CB710_MMC_CMD_BCR                  (1 << 5)
+#define     CB710_MMC_CMD_AC                   (2 << 5)
+#define     CB710_MMC_CMD_ADTC                 (3 << 5)
+#define   CB710_MMC_DATA_READ                  0x0080
+#define   CB710_MMC_CMD_CODE_MASK              0x3F00
+#define   CB710_MMC_CMD_CODE_SHIFT             8
+#define   CB710_MMC_IS_APP_CMD                 0x4000
+#define   CB710_MMC_RSP_BUSY                   0x8000
+
+#define CB710_MMC_CMD_PARAM_PORT       0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT   0x1C
+#define CB710_MMC_RESPONSE0_PORT       0x20
+#define CB710_MMC_RESPONSE1_PORT       0x24
+#define CB710_MMC_RESPONSE2_PORT       0x28
+#define CB710_MMC_RESPONSE3_PORT       0x2C
+
+#endif /* LINUX_CB710_MMC_H */
index f48349d..240608c 100644 (file)
  */
 #define r1b_timeout            (HZ * 3)
 
+/* One of the critical speed parameters is the amount of data which may
+ * be transfered in one command. If this value is too low, the SD card
+ * controller has to do multiple partial block writes (argggh!). With
+ * today (2008) SD cards there is little speed gain if we transfer more
+ * than 64 KBytes at a time. So use this value until there is any indication
+ * that we should do more here.
+ */
+#define MMC_SPI_BLOCKSATONCE   128
 
 /****************************************************************************/
 
@@ -327,15 +335,16 @@ checkstatus:
 
        /* Status byte: the entire seven-bit R1 response.  */
        if (cmd->resp[0] != 0) {
-               if ((R1_SPI_PARAMETER | R1_SPI_ADDRESS
-                                     | R1_SPI_ILLEGAL_COMMAND)
+               if ((R1_SPI_PARAMETER | R1_SPI_ADDRESS)
                                & cmd->resp[0])
-                       value = -EINVAL;
+                       value = -EFAULT; /* Bad address */
+               else if (R1_SPI_ILLEGAL_COMMAND & cmd->resp[0])
+                       value = -ENOSYS; /* Function not implemented */
                else if (R1_SPI_COM_CRC & cmd->resp[0])
-                       value = -EILSEQ;
+                       value = -EILSEQ; /* Illegal byte sequence */
                else if ((R1_SPI_ERASE_SEQ | R1_SPI_ERASE_RESET)
                                & cmd->resp[0])
-                       value = -EIO;
+                       value = -EIO;    /* I/O error */
                /* else R1_SPI_IDLE, "it's resetting" */
        }
 
@@ -1366,6 +1375,10 @@ static int mmc_spi_probe(struct spi_device *spi)
 
        mmc->ops = &mmc_spi_ops;
        mmc->max_blk_size = MMC_SPI_BLOCKSIZE;
+       mmc->max_hw_segs = MMC_SPI_BLOCKSATONCE;
+       mmc->max_phys_segs = MMC_SPI_BLOCKSATONCE;
+       mmc->max_req_size = MMC_SPI_BLOCKSATONCE * MMC_SPI_BLOCKSIZE;
+       mmc->max_blk_count = MMC_SPI_BLOCKSATONCE;
 
        mmc->caps = MMC_CAP_SPI;
 
index f4cbe47..bc14bb1 100644 (file)
@@ -746,8 +746,6 @@ static int mxcmci_probe(struct platform_device *pdev)
        }
 
        mmc->f_min = clk_get_rate(host->clk) >> 16;
-       if (mmc->f_min < 400000)
-               mmc->f_min = 400000;
        mmc->f_max = clk_get_rate(host->clk) >> 1;
 
        /* recommended in data sheet */
index dceb5ee..e7a331d 100644 (file)
@@ -1593,7 +1593,6 @@ static int mmc_omap_resume(struct platform_device *pdev)
 #endif
 
 static struct platform_driver mmc_omap_driver = {
-       .probe          = mmc_omap_probe,
        .remove         = mmc_omap_remove,
        .suspend        = mmc_omap_suspend,
        .resume         = mmc_omap_resume,
@@ -1605,7 +1604,7 @@ static struct platform_driver mmc_omap_driver = {
 
 static int __init mmc_omap_init(void)
 {
-       return platform_driver_register(&mmc_omap_driver);
+       return platform_driver_probe(&mmc_omap_driver, mmc_omap_probe);
 }
 
 static void __exit mmc_omap_exit(void)
index 4300957..d7d7109 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/err.h>
 #include <linux/mmc/host.h>
 #include <linux/io.h>
+#include <linux/regulator/consumer.h>
 
 #include <asm/sizes.h>
 
@@ -67,8 +68,42 @@ struct pxamci_host {
        unsigned int            dma_dir;
        unsigned int            dma_drcmrrx;
        unsigned int            dma_drcmrtx;
+
+       struct regulator        *vcc;
 };
 
+static inline void pxamci_init_ocr(struct pxamci_host *host)
+{
+#ifdef CONFIG_REGULATOR
+       host->vcc = regulator_get(mmc_dev(host->mmc), "vmmc");
+
+       if (IS_ERR(host->vcc))
+               host->vcc = NULL;
+       else {
+               host->mmc->ocr_avail = mmc_regulator_get_ocrmask(host->vcc);
+               if (host->pdata && host->pdata->ocr_mask)
+                       dev_warn(mmc_dev(host->mmc),
+                               "ocr_mask/setpower will not be used\n");
+       }
+#endif
+       if (host->vcc == NULL) {
+               /* fall-back to platform data */
+               host->mmc->ocr_avail = host->pdata ?
+                       host->pdata->ocr_mask :
+                       MMC_VDD_32_33 | MMC_VDD_33_34;
+       }
+}
+
+static inline void pxamci_set_power(struct pxamci_host *host, unsigned int vdd)
+{
+#ifdef CONFIG_REGULATOR
+       if (host->vcc)
+               mmc_regulator_set_ocr(host->vcc, vdd);
+#endif
+       if (!host->vcc && host->pdata && host->pdata->setpower)
+               host->pdata->setpower(mmc_dev(host->mmc), vdd);
+}
+
 static void pxamci_stop_clock(struct pxamci_host *host)
 {
        if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
@@ -438,8 +473,7 @@ static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        if (host->power_mode != ios->power_mode) {
                host->power_mode = ios->power_mode;
 
-               if (host->pdata && host->pdata->setpower)
-                       host->pdata->setpower(mmc_dev(mmc), ios->vdd);
+               pxamci_set_power(host, ios->vdd);
 
                if (ios->power_mode == MMC_POWER_ON)
                        host->cmdat |= CMDAT_INIT;
@@ -562,9 +596,8 @@ static int pxamci_probe(struct platform_device *pdev)
        mmc->f_max = (cpu_is_pxa300() || cpu_is_pxa310()) ? 26000000
                                                          : host->clkrate;
 
-       mmc->ocr_avail = host->pdata ?
-                        host->pdata->ocr_mask :
-                        MMC_VDD_32_33|MMC_VDD_33_34;
+       pxamci_init_ocr(host);
+
        mmc->caps = 0;
        host->cmdat = 0;
        if (!cpu_is_pxa25x()) {
@@ -661,6 +694,9 @@ static int pxamci_remove(struct platform_device *pdev)
        if (mmc) {
                struct pxamci_host *host = mmc_priv(mmc);
 
+               if (host->vcc)
+                       regulator_put(host->vcc);
+
                if (host->pdata && host->pdata->exit)
                        host->pdata->exit(&pdev->dev, mmc);
 
diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c
new file mode 100644 (file)
index 0000000..297f40a
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * sdhci-pltfm.c Support for SDHCI platform devices
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Supports:
+ * SDHCI platform devices
+ *
+ * Inspired by sdhci-pci.c, by Pierre Ossman
+ */
+
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/platform_device.h>
+
+#include <linux/mmc/host.h>
+
+#include <linux/io.h>
+
+#include "sdhci.h"
+
+/*****************************************************************************\
+ *                                                                           *
+ * SDHCI core callbacks                                                      *
+ *                                                                           *
+\*****************************************************************************/
+
+static struct sdhci_ops sdhci_pltfm_ops = {
+};
+
+/*****************************************************************************\
+ *                                                                           *
+ * Device probing/removal                                                    *
+ *                                                                           *
+\*****************************************************************************/
+
+static int __devinit sdhci_pltfm_probe(struct platform_device *pdev)
+{
+       struct sdhci_host *host;
+       struct resource *iomem;
+       int ret;
+
+       BUG_ON(pdev == NULL);
+
+       iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!iomem) {
+               ret = -ENOMEM;
+               goto err;
+       }
+
+       if (resource_size(iomem) != 0x100)
+               dev_err(&pdev->dev, "Invalid iomem size. You may "
+                       "experience problems.\n");
+
+       if (pdev->dev.parent)
+               host = sdhci_alloc_host(pdev->dev.parent, 0);
+       else
+               host = sdhci_alloc_host(&pdev->dev, 0);
+
+       if (IS_ERR(host)) {
+               ret = PTR_ERR(host);
+               goto err;
+       }
+
+       host->hw_name = "platform";
+       host->ops = &sdhci_pltfm_ops;
+       host->irq = platform_get_irq(pdev, 0);
+
+       if (!request_mem_region(iomem->start, resource_size(iomem),
+               mmc_hostname(host->mmc))) {
+               dev_err(&pdev->dev, "cannot request region\n");
+               ret = -EBUSY;
+               goto err_request;
+       }
+
+       host->ioaddr = ioremap(iomem->start, resource_size(iomem));
+       if (!host->ioaddr) {
+               dev_err(&pdev->dev, "failed to remap registers\n");
+               ret = -ENOMEM;
+               goto err_remap;
+       }
+
+       ret = sdhci_add_host(host);
+       if (ret)
+               goto err_add_host;
+
+       platform_set_drvdata(pdev, host);
+
+       return 0;
+
+err_add_host:
+       iounmap(host->ioaddr);
+err_remap:
+       release_mem_region(iomem->start, resource_size(iomem));
+err_request:
+       sdhci_free_host(host);
+err:
+       printk(KERN_ERR"Probing of sdhci-pltfm failed: %d\n", ret);
+       return ret;
+}
+
+static int __devexit sdhci_pltfm_remove(struct platform_device *pdev)
+{
+       struct sdhci_host *host = platform_get_drvdata(pdev);
+       struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       int dead;
+       u32 scratch;
+
+       dead = 0;
+       scratch = readl(host->ioaddr + SDHCI_INT_STATUS);
+       if (scratch == (u32)-1)
+               dead = 1;
+
+       sdhci_remove_host(host, dead);
+       iounmap(host->ioaddr);
+       release_mem_region(iomem->start, resource_size(iomem));
+       sdhci_free_host(host);
+       platform_set_drvdata(pdev, NULL);
+
+       return 0;
+}
+
+static struct platform_driver sdhci_pltfm_driver = {
+       .driver = {
+               .name   = "sdhci",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = sdhci_pltfm_probe,
+       .remove         = __devexit_p(sdhci_pltfm_remove),
+};
+
+/*****************************************************************************\
+ *                                                                           *
+ * Driver init/exit                                                          *
+ *                                                                           *
+\*****************************************************************************/
+
+static int __init sdhci_drv_init(void)
+{
+       return platform_driver_register(&sdhci_pltfm_driver);
+}
+
+static void __exit sdhci_drv_exit(void)
+{
+       platform_driver_unregister(&sdhci_pltfm_driver);
+}
+
+module_init(sdhci_drv_init);
+module_exit(sdhci_drv_exit);
+
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface platform driver");
+MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sdhci");
+
index 9234be2..35789c6 100644 (file)
@@ -78,6 +78,11 @@ static void sdhci_dumpregs(struct sdhci_host *host)
                sdhci_readl(host, SDHCI_CAPABILITIES),
                sdhci_readl(host, SDHCI_MAX_CURRENT));
 
+       if (host->flags & SDHCI_USE_ADMA)
+               printk(KERN_DEBUG DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n",
+                      readl(host->ioaddr + SDHCI_ADMA_ERROR),
+                      readl(host->ioaddr + SDHCI_ADMA_ADDRESS));
+
        printk(KERN_DEBUG DRIVER_NAME ": ===========================================\n");
 }
 
@@ -1005,12 +1010,34 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
 {
        u8 pwr;
 
-       if (host->power == power)
+       if (power == (unsigned short)-1)
+               pwr = 0;
+       else {
+               switch (1 << power) {
+               case MMC_VDD_165_195:
+                       pwr = SDHCI_POWER_180;
+                       break;
+               case MMC_VDD_29_30:
+               case MMC_VDD_30_31:
+                       pwr = SDHCI_POWER_300;
+                       break;
+               case MMC_VDD_32_33:
+               case MMC_VDD_33_34:
+                       pwr = SDHCI_POWER_330;
+                       break;
+               default:
+                       BUG();
+               }
+       }
+
+       if (host->pwr == pwr)
                return;
 
-       if (power == (unsigned short)-1) {
+       host->pwr = pwr;
+
+       if (pwr == 0) {
                sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
-               goto out;
+               return;
        }
 
        /*
@@ -1020,35 +1047,16 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
        if (!(host->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE))
                sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
 
-       pwr = SDHCI_POWER_ON;
-
-       switch (1 << power) {
-       case MMC_VDD_165_195:
-               pwr |= SDHCI_POWER_180;
-               break;
-       case MMC_VDD_29_30:
-       case MMC_VDD_30_31:
-               pwr |= SDHCI_POWER_300;
-               break;
-       case MMC_VDD_32_33:
-       case MMC_VDD_33_34:
-               pwr |= SDHCI_POWER_330;
-               break;
-       default:
-               BUG();
-       }
-
        /*
         * At least the Marvell CaFe chip gets confused if we set the voltage
         * and set turn on power at the same time, so set the voltage first.
         */
        if ((host->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER))
-               sdhci_writeb(host, pwr & ~SDHCI_POWER_ON, SDHCI_POWER_CONTROL);
+               sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
 
-       sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
+       pwr |= SDHCI_POWER_ON;
 
-out:
-       host->power = power;
+       sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
 }
 
 /*****************************************************************************\
index 65c6f99..2de0834 100644 (file)
@@ -255,7 +255,7 @@ struct sdhci_host {
        unsigned int            timeout_clk;    /* Timeout freq (KHz) */
 
        unsigned int            clock;          /* Current clock (MHz) */
-       unsigned short          power;          /* Current voltage */
+       u8                      pwr;            /* Current voltage */
 
        struct mmc_request      *mrq;           /* Current request */
        struct mmc_command      *cmd;           /* Current command */
index 63fbd5b..91991b4 100644 (file)
@@ -10,7 +10,7 @@
  *
  * Driver for the MMC / SD / SDIO cell found in:
  *
- * TC6393XB TC6391XB TC6387XB T7L66XB
+ * TC6393XB TC6391XB TC6387XB T7L66XB ASIC3
  *
  * This driver draws mainly on scattered spec sheets, Reverse engineering
  * of the toshiba e800  SD driver and some parts of the 2.4 ASIC3 driver (4 bit
 
 #include "tmio_mmc.h"
 
-/*
- * Fixme - documentation conflicts on what the clock values are for the
- * various dividers.
- * One document I have says that its a divisor of a 24MHz clock, another 33.
- * This probably depends on HCLK for a given platform, so we may need to
- * require HCLK be passed to us from the MFD core.
- *
- */
-
 static void tmio_mmc_set_clock(struct tmio_mmc_host *host, int new_clock)
 {
-       void __iomem *cnf = host->cnf;
-       void __iomem *ctl = host->ctl;
        u32 clk = 0, clock;
 
        if (new_clock) {
-               for (clock = 46875, clk = 0x100; new_clock >= (clock<<1); ) {
+               for (clock = host->mmc->f_min, clk = 0x80000080;
+                       new_clock >= (clock<<1); clk >>= 1)
                        clock <<= 1;
-                       clk >>= 1;
-               }
-               if (clk & 0x1)
-                       clk = 0x20000;
-
-               clk >>= 2;
-               tmio_iowrite8((clk & 0x8000) ? 0 : 1, cnf + CNF_SD_CLK_MODE);
                clk |= 0x100;
        }
 
-       tmio_iowrite16(clk, ctl + CTL_SD_CARD_CLK_CTL);
+       sd_config_write8(host, CNF_SD_CLK_MODE, clk >> 22);
+       sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, clk & 0x1ff);
 }
 
 static void tmio_mmc_clk_stop(struct tmio_mmc_host *host)
 {
-       void __iomem *ctl = host->ctl;
-
-       tmio_iowrite16(0x0000, ctl + CTL_CLK_AND_WAIT_CTL);
+       sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0000);
        msleep(10);
-       tmio_iowrite16(tmio_ioread16(ctl + CTL_SD_CARD_CLK_CTL) & ~0x0100,
-              ctl + CTL_SD_CARD_CLK_CTL);
+       sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~0x0100 &
+               sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
        msleep(10);
 }
 
 static void tmio_mmc_clk_start(struct tmio_mmc_host *host)
 {
-       void __iomem *ctl = host->ctl;
-
-       tmio_iowrite16(tmio_ioread16(ctl + CTL_SD_CARD_CLK_CTL) | 0x0100,
-              ctl + CTL_SD_CARD_CLK_CTL);
+       sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x0100 |
+               sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
        msleep(10);
-       tmio_iowrite16(0x0100, ctl + CTL_CLK_AND_WAIT_CTL);
+       sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0100);
        msleep(10);
 }
 
 static void reset(struct tmio_mmc_host *host)
 {
-       void __iomem *ctl = host->ctl;
-
        /* FIXME - should we set stop clock reg here */
-       tmio_iowrite16(0x0000, ctl + CTL_RESET_SD);
-       tmio_iowrite16(0x0000, ctl + CTL_RESET_SDIO);
+       sd_ctrl_write16(host, CTL_RESET_SD, 0x0000);
+       sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0000);
        msleep(10);
-       tmio_iowrite16(0x0001, ctl + CTL_RESET_SD);
-       tmio_iowrite16(0x0001, ctl + CTL_RESET_SDIO);
+       sd_ctrl_write16(host, CTL_RESET_SD, 0x0001);
+       sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0001);
        msleep(10);
 }
 
@@ -129,13 +107,12 @@ tmio_mmc_finish_request(struct tmio_mmc_host *host)
 static int
 tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command *cmd)
 {
-       void __iomem *ctl = host->ctl;
        struct mmc_data *data = host->data;
        int c = cmd->opcode;
 
        /* Command 12 is handled by hardware */
        if (cmd->opcode == 12 && !cmd->arg) {
-               tmio_iowrite16(0x001, ctl + CTL_STOP_INTERNAL_ACTION);
+               sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x001);
                return 0;
        }
 
@@ -160,18 +137,18 @@ tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command *cmd)
        if (data) {
                c |= DATA_PRESENT;
                if (data->blocks > 1) {
-                       tmio_iowrite16(0x100, ctl + CTL_STOP_INTERNAL_ACTION);
+                       sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x100);
                        c |= TRANSFER_MULTI;
                }
                if (data->flags & MMC_DATA_READ)
                        c |= TRANSFER_READ;
        }
 
-       enable_mmc_irqs(ctl, TMIO_MASK_CMD);
+       enable_mmc_irqs(host, TMIO_MASK_CMD);
 
        /* Fire off the command */
-       tmio_iowrite32(cmd->arg, ctl + CTL_ARG_REG);
-       tmio_iowrite16(c, ctl + CTL_SD_CMD);
+       sd_ctrl_write32(host, CTL_ARG_REG, cmd->arg);
+       sd_ctrl_write16(host, CTL_SD_CMD, c);
 
        return 0;
 }
@@ -183,7 +160,6 @@ tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command *cmd)
  */
 static inline void tmio_mmc_pio_irq(struct tmio_mmc_host *host)
 {
-       void __iomem *ctl = host->ctl;
        struct mmc_data *data = host->data;
        unsigned short *buf;
        unsigned int count;
@@ -206,9 +182,9 @@ static inline void tmio_mmc_pio_irq(struct tmio_mmc_host *host)
 
        /* Transfer the data */
        if (data->flags & MMC_DATA_READ)
-               tmio_ioread16_rep(ctl + CTL_SD_DATA_PORT, buf, count >> 1);
+               sd_ctrl_read16_rep(host, CTL_SD_DATA_PORT, buf, count >> 1);
        else
-               tmio_iowrite16_rep(ctl + CTL_SD_DATA_PORT, buf, count >> 1);
+               sd_ctrl_write16_rep(host, CTL_SD_DATA_PORT, buf, count >> 1);
 
        host->sg_off += count;
 
@@ -222,7 +198,6 @@ static inline void tmio_mmc_pio_irq(struct tmio_mmc_host *host)
 
 static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host)
 {
-       void __iomem *ctl = host->ctl;
        struct mmc_data *data = host->data;
        struct mmc_command *stop;
 
@@ -251,13 +226,13 @@ static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host)
         */
 
        if (data->flags & MMC_DATA_READ)
-               disable_mmc_irqs(ctl, TMIO_MASK_READOP);
+               disable_mmc_irqs(host, TMIO_MASK_READOP);
        else
-               disable_mmc_irqs(ctl, TMIO_MASK_WRITEOP);
+               disable_mmc_irqs(host, TMIO_MASK_WRITEOP);
 
        if (stop) {
                if (stop->opcode == 12 && !stop->arg)
-                       tmio_iowrite16(0x000, ctl + CTL_STOP_INTERNAL_ACTION);
+                       sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x000);
                else
                        BUG();
        }
@@ -268,9 +243,8 @@ static inline void tmio_mmc_data_irq(struct tmio_mmc_host *host)
 static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host,
        unsigned int stat)
 {
-       void __iomem *ctl = host->ctl, *addr;
        struct mmc_command *cmd = host->cmd;
-       int i;
+       int i, addr;
 
        if (!host->cmd) {
                pr_debug("Spurious CMD irq\n");
@@ -284,8 +258,8 @@ static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host,
         * modify the order of the response for short response command types.
         */
 
-       for (i = 3, addr = ctl + CTL_RESPONSE ; i >= 0 ; i--, addr += 4)
-               cmd->resp[i] = tmio_ioread32(addr);
+       for (i = 3, addr = CTL_RESPONSE ; i >= 0 ; i--, addr += 4)
+               cmd->resp[i] = sd_ctrl_read32(host, addr);
 
        if (cmd->flags &  MMC_RSP_136) {
                cmd->resp[0] = (cmd->resp[0] << 8) | (cmd->resp[1] >> 24);
@@ -307,9 +281,9 @@ static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host,
         */
        if (host->data && !cmd->error) {
                if (host->data->flags & MMC_DATA_READ)
-                       enable_mmc_irqs(ctl, TMIO_MASK_READOP);
+                       enable_mmc_irqs(host, TMIO_MASK_READOP);
                else
-                       enable_mmc_irqs(ctl, TMIO_MASK_WRITEOP);
+                       enable_mmc_irqs(host, TMIO_MASK_WRITEOP);
        } else {
                tmio_mmc_finish_request(host);
        }
@@ -321,20 +295,19 @@ static inline void tmio_mmc_cmd_irq(struct tmio_mmc_host *host,
 static irqreturn_t tmio_mmc_irq(int irq, void *devid)
 {
        struct tmio_mmc_host *host = devid;
-       void __iomem *ctl = host->ctl;
        unsigned int ireg, irq_mask, status;
 
        pr_debug("MMC IRQ begin\n");
 
-       status = tmio_ioread32(ctl + CTL_STATUS);
-       irq_mask = tmio_ioread32(ctl + CTL_IRQ_MASK);
+       status = sd_ctrl_read32(host, CTL_STATUS);
+       irq_mask = sd_ctrl_read32(host, CTL_IRQ_MASK);
        ireg = status & TMIO_MASK_IRQ & ~irq_mask;
 
        pr_debug_status(status);
        pr_debug_status(ireg);
 
        if (!ireg) {
-               disable_mmc_irqs(ctl, status & ~irq_mask);
+               disable_mmc_irqs(host, status & ~irq_mask);
 
                pr_debug("tmio_mmc: Spurious irq, disabling! "
                        "0x%08x 0x%08x 0x%08x\n", status, irq_mask, ireg);
@@ -346,7 +319,7 @@ static irqreturn_t tmio_mmc_irq(int irq, void *devid)
        while (ireg) {
                /* Card insert / remove attempts */
                if (ireg & (TMIO_STAT_CARD_INSERT | TMIO_STAT_CARD_REMOVE)) {
-                       ack_mmc_irqs(ctl, TMIO_STAT_CARD_INSERT |
+                       ack_mmc_irqs(host, TMIO_STAT_CARD_INSERT |
                                TMIO_STAT_CARD_REMOVE);
                        mmc_detect_change(host->mmc, 0);
                }
@@ -358,25 +331,25 @@ static irqreturn_t tmio_mmc_irq(int irq, void *devid)
 
                /* Command completion */
                if (ireg & TMIO_MASK_CMD) {
-                       ack_mmc_irqs(ctl, TMIO_MASK_CMD);
+                       ack_mmc_irqs(host, TMIO_MASK_CMD);
                        tmio_mmc_cmd_irq(host, status);
                }
 
                /* Data transfer */
                if (ireg & (TMIO_STAT_RXRDY | TMIO_STAT_TXRQ)) {
-                       ack_mmc_irqs(ctl, TMIO_STAT_RXRDY | TMIO_STAT_TXRQ);
+                       ack_mmc_irqs(host, TMIO_STAT_RXRDY | TMIO_STAT_TXRQ);
                        tmio_mmc_pio_irq(host);
                }
 
                /* Data transfer completion */
                if (ireg & TMIO_STAT_DATAEND) {
-                       ack_mmc_irqs(ctl, TMIO_STAT_DATAEND);
+                       ack_mmc_irqs(host, TMIO_STAT_DATAEND);
                        tmio_mmc_data_irq(host);
                }
 
                /* Check status - keep going until we've handled it all */
-               status = tmio_ioread32(ctl + CTL_STATUS);
-               irq_mask = tmio_ioread32(ctl + CTL_IRQ_MASK);
+               status = sd_ctrl_read32(host, CTL_STATUS);
+               irq_mask = sd_ctrl_read32(host, CTL_IRQ_MASK);
                ireg = status & TMIO_MASK_IRQ & ~irq_mask;
 
                pr_debug("Status at end of loop: %08x\n", status);
@@ -391,8 +364,6 @@ out:
 static int tmio_mmc_start_data(struct tmio_mmc_host *host,
        struct mmc_data *data)
 {
-       void __iomem *ctl = host->ctl;
-
        pr_debug("setup data transfer: blocksize %08x  nr_blocks %d\n",
            data->blksz, data->blocks);
 
@@ -407,8 +378,8 @@ static int tmio_mmc_start_data(struct tmio_mmc_host *host,
        host->data = data;
 
        /* Set transfer length / blocksize */
-       tmio_iowrite16(data->blksz,  ctl + CTL_SD_XFER_LEN);
-       tmio_iowrite16(data->blocks, ctl + CTL_XFER_BLK_COUNT);
+       sd_ctrl_write16(host, CTL_SD_XFER_LEN, data->blksz);
+       sd_ctrl_write16(host, CTL_XFER_BLK_COUNT, data->blocks);
 
        return 0;
 }
@@ -449,8 +420,6 @@ fail:
 static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
        struct tmio_mmc_host *host = mmc_priv(mmc);
-       void __iomem *cnf = host->cnf;
-       void __iomem *ctl = host->ctl;
 
        if (ios->clock)
                tmio_mmc_set_clock(host, ios->clock);
@@ -458,12 +427,12 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        /* Power sequence - OFF -> ON -> UP */
        switch (ios->power_mode) {
        case MMC_POWER_OFF: /* power down SD bus */
-               tmio_iowrite8(0x00, cnf + CNF_PWR_CTL_2);
+               sd_config_write8(host, CNF_PWR_CTL_2, 0x00);
                tmio_mmc_clk_stop(host);
                break;
        case MMC_POWER_ON: /* power up SD bus */
 
-               tmio_iowrite8(0x02, cnf + CNF_PWR_CTL_2);
+               sd_config_write8(host, CNF_PWR_CTL_2, 0x02);
                break;
        case MMC_POWER_UP: /* start bus clock */
                tmio_mmc_clk_start(host);
@@ -472,10 +441,10 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 
        switch (ios->bus_width) {
        case MMC_BUS_WIDTH_1:
-               tmio_iowrite16(0x80e0, ctl + CTL_SD_MEM_CARD_OPT);
+               sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x80e0);
        break;
        case MMC_BUS_WIDTH_4:
-               tmio_iowrite16(0x00e0, ctl + CTL_SD_MEM_CARD_OPT);
+               sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x00e0);
        break;
        }
 
@@ -486,9 +455,8 @@ static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 static int tmio_mmc_get_ro(struct mmc_host *mmc)
 {
        struct tmio_mmc_host *host = mmc_priv(mmc);
-       void __iomem *ctl = host->ctl;
 
-       return (tmio_ioread16(ctl + CTL_STATUS) & TMIO_STAT_WRPROTECT) ? 0 : 1;
+       return (sd_ctrl_read16(host, CTL_STATUS) & TMIO_STAT_WRPROTECT) ? 0 : 1;
 }
 
 static struct mmc_host_ops tmio_mmc_ops = {
@@ -518,13 +486,8 @@ static int tmio_mmc_resume(struct platform_device *dev)
        struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data;
        struct mmc_host *mmc = platform_get_drvdata(dev);
        struct tmio_mmc_host *host = mmc_priv(mmc);
-       void __iomem *cnf = host->cnf;
        int ret = 0;
 
-       /* Enable the MMC/SD Control registers */
-       tmio_iowrite16(SDCREN, cnf + CNF_CMD);
-       tmio_iowrite32(dev->resource[0].start & 0xfffe, cnf + CNF_CTL_BASE);
-
        /* Tell the MFD core we are ready to be enabled */
        if (cell->enable) {
                ret = cell->enable(dev);
@@ -532,6 +495,11 @@ static int tmio_mmc_resume(struct platform_device *dev)
                        goto out;
        }
 
+       /* Enable the MMC/SD Control registers */
+       sd_config_write16(host, CNF_CMD, SDCREN);
+       sd_config_write32(host, CNF_CTL_BASE,
+               (dev->resource[0].start >> host->bus_shift) & 0xfffe);
+
        mmc_resume_host(mmc);
 
 out:
@@ -545,20 +513,25 @@ out:
 static int __devinit tmio_mmc_probe(struct platform_device *dev)
 {
        struct mfd_cell *cell = (struct mfd_cell *)dev->dev.platform_data;
+       struct tmio_mmc_data *pdata;
        struct resource *res_ctl, *res_cnf;
        struct tmio_mmc_host *host;
        struct mmc_host *mmc;
-       int ret = -ENOMEM;
+       int ret = -EINVAL;
 
        if (dev->num_resources != 3)
                goto out;
 
        res_ctl = platform_get_resource(dev, IORESOURCE_MEM, 0);
        res_cnf = platform_get_resource(dev, IORESOURCE_MEM, 1);
-       if (!res_ctl || !res_cnf) {
-               ret = -EINVAL;
+       if (!res_ctl || !res_cnf)
                goto out;
-       }
+
+       pdata = cell->driver_data;
+       if (!pdata || !pdata->hclk)
+               goto out;
+
+       ret = -ENOMEM;
 
        mmc = mmc_alloc_host(sizeof(struct tmio_mmc_host), &dev->dev);
        if (!mmc)
@@ -568,6 +541,9 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev)
        host->mmc = mmc;
        platform_set_drvdata(dev, mmc);
 
+       /* SD control register space size is 0x200, 0x400 for bus_shift=1 */
+       host->bus_shift = resource_size(res_ctl) >> 10;
+
        host->ctl = ioremap(res_ctl->start, resource_size(res_ctl));
        if (!host->ctl)
                goto host_free;
@@ -578,15 +554,10 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev)
 
        mmc->ops = &tmio_mmc_ops;
        mmc->caps = MMC_CAP_4_BIT_DATA;
-       mmc->f_min = 46875; /* 24000000 / 512 */
-       mmc->f_max = 24000000;
+       mmc->f_max = pdata->hclk;
+       mmc->f_min = mmc->f_max / 512;
        mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
 
-       /* Enable the MMC/SD Control registers */
-       tmio_iowrite16(SDCREN, host->cnf + CNF_CMD);
-       tmio_iowrite32(dev->resource[0].start & 0xfffe,
-               host->cnf + CNF_CTL_BASE);
-
        /* Tell the MFD core we are ready to be enabled */
        if (cell->enable) {
                ret = cell->enable(dev);
@@ -594,14 +565,19 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev)
                        goto unmap_cnf;
        }
 
+       /* Enable the MMC/SD Control registers */
+       sd_config_write16(host, CNF_CMD, SDCREN);
+       sd_config_write32(host, CNF_CTL_BASE,
+               (dev->resource[0].start >> host->bus_shift) & 0xfffe);
+
        /* Disable SD power during suspend */
-       tmio_iowrite8(0x01, host->cnf + CNF_PWR_CTL_3);
+       sd_config_write8(host, CNF_PWR_CTL_3, 0x01);
 
        /* The below is required but why? FIXME */
-       tmio_iowrite8(0x1f, host->cnf + CNF_STOP_CLK_CTL);
+       sd_config_write8(host, CNF_STOP_CLK_CTL, 0x1f);
 
        /* Power down SD bus*/
-       tmio_iowrite8(0x0,  host->cnf + CNF_PWR_CTL_2);
+       sd_config_write8(host, CNF_PWR_CTL_2, 0x00);
 
        tmio_mmc_clk_stop(host);
        reset(host);
@@ -612,22 +588,20 @@ static int __devinit tmio_mmc_probe(struct platform_device *dev)
        else
                goto unmap_cnf;
 
-       disable_mmc_irqs(host->ctl, TMIO_MASK_ALL);
+       disable_mmc_irqs(host, TMIO_MASK_ALL);
 
-       ret = request_irq(host->irq, tmio_mmc_irq, IRQF_DISABLED, "tmio-mmc",
-               host);
+       ret = request_irq(host->irq, tmio_mmc_irq, IRQF_DISABLED |
+               IRQF_TRIGGER_FALLING, "tmio-mmc", host);
        if (ret)
                goto unmap_cnf;
 
-       set_irq_type(host->irq, IRQ_TYPE_EDGE_FALLING);
-
        mmc_add_host(mmc);
 
        printk(KERN_INFO "%s at 0x%08lx irq %d\n", mmc_hostname(host->mmc),
               (unsigned long)host->ctl, host->irq);
 
        /* Unmask the IRQs we want to know about */
-       enable_mmc_irqs(host->ctl,  TMIO_MASK_IRQ);
+       enable_mmc_irqs(host, TMIO_MASK_IRQ);
 
        return 0;
 
index 9c831ab..9fa9985 100644 (file)
                TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT)
 #define TMIO_MASK_IRQ     (TMIO_MASK_READOP | TMIO_MASK_WRITEOP | TMIO_MASK_CMD)
 
-#define enable_mmc_irqs(ctl, i) \
+
+#define enable_mmc_irqs(host, i) \
        do { \
                u32 mask;\
-               mask  = tmio_ioread32((ctl) + CTL_IRQ_MASK); \
+               mask  = sd_ctrl_read32((host), CTL_IRQ_MASK); \
                mask &= ~((i) & TMIO_MASK_IRQ); \
-               tmio_iowrite32(mask, (ctl) + CTL_IRQ_MASK); \
+               sd_ctrl_write32((host), CTL_IRQ_MASK, mask); \
        } while (0)
 
-#define disable_mmc_irqs(ctl, i) \
+#define disable_mmc_irqs(host, i) \
        do { \
                u32 mask;\
-               mask  = tmio_ioread32((ctl) + CTL_IRQ_MASK); \
+               mask  = sd_ctrl_read32((host), CTL_IRQ_MASK); \
                mask |= ((i) & TMIO_MASK_IRQ); \
-               tmio_iowrite32(mask, (ctl) + CTL_IRQ_MASK); \
+               sd_ctrl_write32((host), CTL_IRQ_MASK, mask); \
        } while (0)
 
-#define ack_mmc_irqs(ctl, i) \
+#define ack_mmc_irqs(host, i) \
        do { \
                u32 mask;\
-               mask  = tmio_ioread32((ctl) + CTL_STATUS); \
+               mask  = sd_ctrl_read32((host), CTL_STATUS); \
                mask &= ~((i) & TMIO_MASK_IRQ); \
-               tmio_iowrite32(mask, (ctl) + CTL_STATUS); \
+               sd_ctrl_write32((host), CTL_STATUS, mask); \
        } while (0)
 
 
 struct tmio_mmc_host {
        void __iomem *cnf;
        void __iomem *ctl;
+       unsigned long bus_shift;
        struct mmc_command      *cmd;
        struct mmc_request      *mrq;
        struct mmc_data         *data;
@@ -123,6 +125,63 @@ struct tmio_mmc_host {
        unsigned int            sg_off;
 };
 
+#include <linux/io.h>
+
+static inline u16 sd_ctrl_read16(struct tmio_mmc_host *host, int addr)
+{
+       return readw(host->ctl + (addr << host->bus_shift));
+}
+
+static inline void sd_ctrl_read16_rep(struct tmio_mmc_host *host, int addr,
+               u16 *buf, int count)
+{
+       readsw(host->ctl + (addr << host->bus_shift), buf, count);
+}
+
+static inline u32 sd_ctrl_read32(struct tmio_mmc_host *host, int addr)
+{
+       return readw(host->ctl + (addr << host->bus_shift)) |
+              readw(host->ctl + ((addr + 2) << host->bus_shift)) << 16;
+}
+
+static inline void sd_ctrl_write16(struct tmio_mmc_host *host, int addr,
+               u16 val)
+{
+       writew(val, host->ctl + (addr << host->bus_shift));
+}
+
+static inline void sd_ctrl_write16_rep(struct tmio_mmc_host *host, int addr,
+               u16 *buf, int count)
+{
+       writesw(host->ctl + (addr << host->bus_shift), buf, count);
+}
+
+static inline void sd_ctrl_write32(struct tmio_mmc_host *host, int addr,
+               u32 val)
+{
+       writew(val, host->ctl + (addr << host->bus_shift));
+       writew(val >> 16, host->ctl + ((addr + 2) << host->bus_shift));
+}
+
+static inline void sd_config_write8(struct tmio_mmc_host *host, int addr,
+               u8 val)
+{
+       writeb(val, host->cnf + (addr << host->bus_shift));
+}
+
+static inline void sd_config_write16(struct tmio_mmc_host *host, int addr,
+               u16 val)
+{
+       writew(val, host->cnf + (addr << host->bus_shift));
+}
+
+static inline void sd_config_write32(struct tmio_mmc_host *host, int addr,
+               u32 val)
+{
+       writew(val, host->cnf + (addr << host->bus_shift));
+       writew(val >> 16, host->cnf + ((addr + 2) << host->bus_shift));
+}
+
 #include <linux/scatterlist.h>
 #include <linux/blkdev.h>
 
diff --git a/include/linux/cb710.h b/include/linux/cb710.h
new file mode 100644 (file)
index 0000000..63bc9a4
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ *  cb710/cb710.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef LINUX_CB710_DRIVER_H
+#define LINUX_CB710_DRIVER_H
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/mmc/host.h>
+
+struct cb710_slot;
+
+typedef int (*cb710_irq_handler_t)(struct cb710_slot *);
+
+/* per-virtual-slot structure */
+struct cb710_slot {
+       struct platform_device  pdev;
+       void __iomem            *iobase;
+       cb710_irq_handler_t     irq_handler;
+};
+
+/* per-device structure */
+struct cb710_chip {
+       struct pci_dev          *pdev;
+       void __iomem            *iobase;
+       unsigned                platform_id;
+#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS
+       atomic_t                slot_refs_count;
+#endif
+       unsigned                slot_mask;
+       unsigned                slots;
+       spinlock_t              irq_lock;
+       struct cb710_slot       slot[0];
+};
+
+/* NOTE: cb710_chip.slots is modified only during device init/exit and
+ * they are all serialized wrt themselves */
+
+/* cb710_chip.slot_mask values */
+#define CB710_SLOT_MMC         1
+#define CB710_SLOT_MS          2
+#define CB710_SLOT_SM          4
+
+/* slot port accessors - so the logic is more clear in the code */
+#define CB710_PORT_ACCESSORS(t) \
+static inline void cb710_write_port_##t(struct cb710_slot *slot,       \
+       unsigned port, u##t value)                                      \
+{                                                                      \
+       iowrite##t(value, slot->iobase + port);                         \
+}                                                                      \
+                                                                       \
+static inline u##t cb710_read_port_##t(struct cb710_slot *slot,                \
+       unsigned port)                                                  \
+{                                                                      \
+       return ioread##t(slot->iobase + port);                          \
+}                                                                      \
+                                                                       \
+static inline void cb710_modify_port_##t(struct cb710_slot *slot,      \
+       unsigned port, u##t set, u##t clear)                            \
+{                                                                      \
+       iowrite##t(                                                     \
+               (ioread##t(slot->iobase + port) & ~clear)|set,          \
+               slot->iobase + port);                                   \
+}
+
+CB710_PORT_ACCESSORS(8)
+CB710_PORT_ACCESSORS(16)
+CB710_PORT_ACCESSORS(32)
+
+void cb710_pci_update_config_reg(struct pci_dev *pdev,
+       int reg, uint32_t and, uint32_t xor);
+void cb710_set_irq_handler(struct cb710_slot *slot,
+       cb710_irq_handler_t handler);
+
+/* some device struct walking */
+
+static inline struct cb710_slot *cb710_pdev_to_slot(
+       struct platform_device *pdev)
+{
+       return container_of(pdev, struct cb710_slot, pdev);
+}
+
+static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot)
+{
+       return dev_get_drvdata(slot->pdev.dev.parent);
+}
+
+static inline struct device *cb710_slot_dev(struct cb710_slot *slot)
+{
+       return &slot->pdev.dev;
+}
+
+static inline struct device *cb710_chip_dev(struct cb710_chip *chip)
+{
+       return &chip->pdev->dev;
+}
+
+/* debugging aids */
+
+#ifdef CONFIG_CB710_DEBUG
+void cb710_dump_regs(struct cb710_chip *chip, unsigned dump);
+#else
+#define cb710_dump_regs(c, d) do {} while (0)
+#endif
+
+#define CB710_DUMP_REGS_MMC    0x0F
+#define CB710_DUMP_REGS_MS     0x30
+#define CB710_DUMP_REGS_SM     0xC0
+#define CB710_DUMP_REGS_ALL    0xFF
+#define CB710_DUMP_REGS_MASK   0xFF
+
+#define CB710_DUMP_ACCESS_8    0x100
+#define CB710_DUMP_ACCESS_16   0x200
+#define CB710_DUMP_ACCESS_32   0x400
+#define CB710_DUMP_ACCESS_ALL  0x700
+#define CB710_DUMP_ACCESS_MASK 0x700
+
+#endif /* LINUX_CB710_DRIVER_H */
+/*
+ *  cb710/sgbuf2.h
+ *
+ *  Copyright by Michał Mirosław, 2008-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef LINUX_CB710_SG_H
+#define LINUX_CB710_SG_H
+
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+
+/**
+ * cb710_sg_miter_stop_writing - stop mapping iteration after writing
+ * @miter: sg mapping iter to be stopped
+ *
+ * Description:
+ *   Stops mapping iterator @miter.  @miter should have been started
+ *   started using sg_miter_start().  A stopped iteration can be
+ *   resumed by calling sg_miter_next() on it.  This is useful when
+ *   resources (kmap) need to be released during iteration.
+ *
+ *   This is a convenience wrapper that will be optimized out for arches
+ *   that don't need flush_kernel_dcache_page().
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void cb710_sg_miter_stop_writing(struct sg_mapping_iter *miter)
+{
+       if (miter->page)
+               flush_kernel_dcache_page(miter->page);
+       sg_miter_stop(miter);
+}
+
+/*
+ * 32-bit PIO mapping sg iterator
+ *
+ * Hides scatterlist access issues - fragment boundaries, alignment, page
+ * mapping - for drivers using 32-bit-word-at-a-time-PIO (ie. PCI devices
+ * without DMA support).
+ *
+ * Best-case reading (transfer from device):
+ *   sg_miter_start();
+ *   cb710_sg_dwiter_write_from_io();
+ *   cb710_sg_miter_stop_writing();
+ *
+ * Best-case writing (transfer to device):
+ *   sg_miter_start();
+ *   cb710_sg_dwiter_read_to_io();
+ *   sg_miter_stop();
+ */
+
+uint32_t cb710_sg_dwiter_read_next_block(struct sg_mapping_iter *miter);
+void cb710_sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data);
+
+/**
+ * cb710_sg_dwiter_write_from_io - transfer data to mapped buffer from 32-bit IO port
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Reads @count 32-bit words from register @port and stores it in
+ *   buffer iterated by @miter.  Data that would overflow the buffer
+ *   is silently ignored.  Iterator is advanced by 4*@count bytes
+ *   or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void cb710_sg_dwiter_write_from_io(struct sg_mapping_iter *miter,
+       void __iomem *port, size_t count)
+{
+       while (count-- > 0)
+               cb710_sg_dwiter_write_next_block(miter, ioread32(port));
+}
+
+/**
+ * cb710_sg_dwiter_read_to_io - transfer data to 32-bit IO port from mapped buffer
+ * @miter: sg mapping iter
+ * @port: PIO port - IO or MMIO address
+ * @count: number of 32-bit words to transfer
+ *
+ * Description:
+ *   Writes @count 32-bit words to register @port from buffer iterated
+ *   through @miter.  If buffer ends before @count words are written
+ *   missing data is replaced by zeroes. @miter is advanced by 4*@count
+ *   bytes or to the buffer's end whichever is closer.
+ *
+ * Context:
+ *   IRQ disabled if the SG_MITER_ATOMIC is set.  Don't care otherwise.
+ */
+static inline void cb710_sg_dwiter_read_to_io(struct sg_mapping_iter *miter,
+       void __iomem *port, size_t count)
+{
+       while (count-- > 0)
+               iowrite32(cb710_sg_dwiter_read_next_block(miter), port);
+}
+
+#endif /* LINUX_CB710_SG_H */
index 516d955..c377118 100644 (file)
        } while (0)
 
 /*
+ * data for the MMC controller
+ */
+struct tmio_mmc_data {
+       unsigned int            hclk;
+};
+
+/*
  * data for the NAND controller
  */
 struct tmio_nand_data {
index 19f8e6d..9f36e1c 100644 (file)
 #define PCI_VENDOR_ID_MAINPINE         0x1522
 #define PCI_DEVICE_ID_MAINPINE_PBRIDGE 0x0100
 #define PCI_VENDOR_ID_ENE              0x1524
+#define PCI_DEVICE_ID_ENE_CB710_FLASH  0x0510
 #define PCI_DEVICE_ID_ENE_CB712_SD     0x0550
 #define PCI_DEVICE_ID_ENE_CB712_SD_2   0x0551
 #define PCI_DEVICE_ID_ENE_CB714_SD     0x0750