summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre Ossman <drzeus@drzeus.cx>2008-04-04 19:36:59 +0200
committerPierre Ossman <drzeus@drzeus.cx>2008-07-15 14:14:40 +0200
commit4489428ab5a49a6f443d9aa17f1d891417787d7b (patch)
treefe95fd7aed4858e03af828805461716eb27d7d7c
parentsdhci: toggle JMicron PMOS setting (diff)
downloadlinux-4489428ab5a49a6f443d9aa17f1d891417787d7b.tar.xz
linux-4489428ab5a49a6f443d9aa17f1d891417787d7b.zip
sdhci: support JMicron secondary interface
JMicron chips sometimes have two interfaces to work around limitations in Microsoft's sdhci driver. This patch allows us to use either interface. Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
-rw-r--r--drivers/mmc/host/sdhci-pci.c127
-rw-r--r--include/linux/pci_ids.h1
2 files changed, 126 insertions, 2 deletions
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
index ef77ed1bd114..5dcb4958e47b 100644
--- a/drivers/mmc/host/sdhci-pci.c
+++ b/drivers/mmc/host/sdhci-pci.c
@@ -39,12 +39,18 @@
#define MAX_SLOTS 8
struct sdhci_pci_chip;
+struct sdhci_pci_slot;
struct sdhci_pci_fixes {
unsigned int quirks;
int (*probe)(struct sdhci_pci_chip*);
+ int (*probe_slot)(struct sdhci_pci_slot*);
+ void (*remove_slot)(struct sdhci_pci_slot*);
+
+ int (*suspend)(struct sdhci_pci_chip*,
+ pm_message_t);
int (*resume)(struct sdhci_pci_chip*);
};
@@ -133,6 +139,38 @@ static int jmicron_probe(struct sdhci_pci_chip *chip)
int ret;
/*
+ * JMicron chips can have two interfaces to the same hardware
+ * in order to work around limitations in Microsoft's driver.
+ * We need to make sure we only bind to one of them.
+ *
+ * This code assumes two things:
+ *
+ * 1. The PCI code adds subfunctions in order.
+ *
+ * 2. The MMC interface has a lower subfunction number
+ * than the SD interface.
+ */
+ if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_SD) {
+ struct pci_dev *sd_dev;
+
+ sd_dev = NULL;
+ while ((sd_dev = pci_get_device(PCI_VENDOR_ID_JMICRON,
+ PCI_DEVICE_ID_JMICRON_JMB38X_MMC, sd_dev)) != NULL) {
+ if ((PCI_SLOT(chip->pdev->devfn) ==
+ PCI_SLOT(sd_dev->devfn)) &&
+ (chip->pdev->bus == sd_dev->bus))
+ break;
+ }
+
+ if (sd_dev) {
+ pci_dev_put(sd_dev);
+ dev_info(&chip->pdev->dev, "Refusing to bind to "
+ "secondary interface.\n");
+ return -ENODEV;
+ }
+ }
+
+ /*
* JMicron chips need a bit of a nudge to enable the power
* output pins.
*/
@@ -145,9 +183,58 @@ static int jmicron_probe(struct sdhci_pci_chip *chip)
return 0;
}
+static void jmicron_enable_mmc(struct sdhci_host *host, int on)
+{
+ u8 scratch;
+
+ scratch = readb(host->ioaddr + 0xC0);
+
+ if (on)
+ scratch |= 0x01;
+ else
+ scratch &= ~0x01;
+
+ writeb(scratch, host->ioaddr + 0xC0);
+}
+
+static int jmicron_probe_slot(struct sdhci_pci_slot *slot)
+{
+ /*
+ * The secondary interface requires a bit set to get the
+ * interrupts.
+ */
+ if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
+ jmicron_enable_mmc(slot->host, 1);
+
+ return 0;
+}
+
+static void jmicron_remove_slot(struct sdhci_pci_slot *slot)
+{
+ if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
+ jmicron_enable_mmc(slot->host, 0);
+}
+
+static int jmicron_suspend(struct sdhci_pci_chip *chip, pm_message_t state)
+{
+ int i;
+
+ if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
+ for (i = 0;i < chip->num_slots;i++)
+ jmicron_enable_mmc(chip->slots[i]->host, 0);
+ }
+
+ return 0;
+}
+
static int jmicron_resume(struct sdhci_pci_chip *chip)
{
- int ret;
+ int ret, i;
+
+ if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
+ for (i = 0;i < chip->num_slots;i++)
+ jmicron_enable_mmc(chip->slots[i]->host, 1);
+ }
ret = jmicron_pmos(chip, 1);
if (ret) {
@@ -165,6 +252,10 @@ static const struct sdhci_pci_fixes sdhci_jmicron = {
.probe = jmicron_probe,
+ .probe_slot = jmicron_probe_slot,
+ .remove_slot = jmicron_remove_slot,
+
+ .suspend = jmicron_suspend,
.resume = jmicron_resume,
};
@@ -225,6 +316,14 @@ static const struct pci_device_id pci_ids[] __devinitdata = {
.driver_data = (kernel_ulong_t)&sdhci_jmicron,
},
+ {
+ .vendor = PCI_VENDOR_ID_JMICRON,
+ .device = PCI_DEVICE_ID_JMICRON_JMB38X_MMC,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_jmicron,
+ },
+
{ /* Generic SD host controller */
PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
},
@@ -301,6 +400,15 @@ static int sdhci_pci_suspend (struct pci_dev *pdev, pm_message_t state)
}
}
+ if (chip->fixes && chip->fixes->suspend) {
+ ret = chip->fixes->suspend(chip, state);
+ if (ret) {
+ for (i = chip->num_slots - 1;i >= 0;i--)
+ sdhci_resume_host(chip->slots[i]->host);
+ return ret;
+ }
+ }
+
pci_save_state(pdev);
pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
pci_disable_device(pdev);
@@ -418,12 +526,22 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
goto release;
}
+ if (chip->fixes && chip->fixes->probe_slot) {
+ ret = chip->fixes->probe_slot(slot);
+ if (ret)
+ goto unmap;
+ }
+
ret = sdhci_add_host(host);
if (ret)
- goto unmap;
+ goto remove;
return slot;
+remove:
+ if (chip->fixes && chip->fixes->remove_slot)
+ chip->fixes->remove_slot(slot);
+
unmap:
iounmap(host->ioaddr);
@@ -437,7 +555,12 @@ release:
static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
{
sdhci_remove_host(slot->host);
+
+ if (slot->chip->fixes && slot->chip->fixes->remove_slot)
+ slot->chip->fixes->remove_slot(slot);
+
pci_release_region(slot->chip->pdev, slot->pci_bar);
+
sdhci_free_host(slot->host);
}
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 65953822c9cb..30153473bc37 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -2188,6 +2188,7 @@
#define PCI_DEVICE_ID_JMICRON_JMB366 0x2366
#define PCI_DEVICE_ID_JMICRON_JMB368 0x2368
#define PCI_DEVICE_ID_JMICRON_JMB38X_SD 0x2381
+#define PCI_DEVICE_ID_JMICRON_JMB38X_MMC 0x2382
#define PCI_DEVICE_ID_JMICRON_JMB38X_MS 0x2383
#define PCI_VENDOR_ID_KORENIX 0x1982