[MMC] sdhci: support for multiple voltages
Pierre Ossman [Fri, 30 Jun 2006 09:22:23 +0000 (02:22 -0700)]
The sdhci controllers can support up to three voltage levels.  Detect which
and report back to the MMC layer.

Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

drivers/mmc/sdhci.c
drivers/mmc/sdhci.h

index 405b615..74912dc 100644 (file)
@@ -530,6 +530,46 @@ out:
        host->clock = clock;
 }
 
+static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
+{
+       u8 pwr;
+
+       if (host->power == power)
+               return;
+
+       writeb(0, host->ioaddr + SDHCI_POWER_CONTROL);
+
+       if (power == (unsigned short)-1)
+               goto out;
+
+       pwr = SDHCI_POWER_ON;
+
+       switch (power) {
+       case MMC_VDD_170:
+       case MMC_VDD_180:
+       case MMC_VDD_190:
+               pwr |= SDHCI_POWER_180;
+               break;
+       case MMC_VDD_290:
+       case MMC_VDD_300:
+       case MMC_VDD_310:
+               pwr |= SDHCI_POWER_300;
+               break;
+       case MMC_VDD_320:
+       case MMC_VDD_330:
+       case MMC_VDD_340:
+               pwr |= SDHCI_POWER_330;
+               break;
+       default:
+               BUG();
+       }
+
+       writeb(pwr, host->ioaddr + SDHCI_POWER_CONTROL);
+
+out:
+       host->power = power;
+}
+
 /*****************************************************************************\
  *                                                                           *
  * MMC callbacks                                                             *
@@ -584,9 +624,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
        sdhci_set_clock(host, ios->clock);
 
        if (ios->power_mode == MMC_POWER_OFF)
-               writeb(0, host->ioaddr + SDHCI_POWER_CONTROL);
+               sdhci_set_power(host, -1);
        else
-               writeb(0xFF, host->ioaddr + SDHCI_POWER_CONTROL);
+               sdhci_set_power(host, ios->vdd);
 
        ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL);
        if (ios->bus_width == MMC_BUS_WIDTH_4)
@@ -1046,9 +1086,23 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
        mmc->ops = &sdhci_ops;
        mmc->f_min = host->max_clk / 256;
        mmc->f_max = host->max_clk;
-       mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
        mmc->caps = MMC_CAP_4_BIT_DATA;
 
+       mmc->ocr_avail = 0;
+       if (caps & SDHCI_CAN_VDD_330)
+               mmc->ocr_avail |= MMC_VDD_32_33|MMC_VDD_33_34;
+       else if (caps & SDHCI_CAN_VDD_300)
+               mmc->ocr_avail |= MMC_VDD_29_30|MMC_VDD_30_31;
+       else if (caps & SDHCI_CAN_VDD_180)
+               mmc->ocr_avail |= MMC_VDD_17_18|MMC_VDD_18_19;
+
+       if (mmc->ocr_avail == 0) {
+               printk(KERN_ERR "%s: Hardware doesn't report any "
+                       "support voltages.\n", host->slot_descr);
+               ret = -ENODEV;
+               goto unmap;
+       }
+
        spin_lock_init(&host->lock);
 
        /*
index 3b270ef..aed4abd 100644 (file)
 #define  SDHCI_CTRL_4BITBUS    0x02
 
 #define SDHCI_POWER_CONTROL    0x29
+#define  SDHCI_POWER_ON                0x01
+#define  SDHCI_POWER_180       0x0A
+#define  SDHCI_POWER_300       0x0C
+#define  SDHCI_POWER_330       0x0E
 
 #define SDHCI_BLOCK_GAP_CONTROL        0x2A
 
 /* 3E-3F reserved */
 
 #define SDHCI_CAPABILITIES     0x40
-#define  SDHCI_CAN_DO_DMA      0x00400000
 #define  SDHCI_CLOCK_BASE_MASK 0x00003F00
 #define  SDHCI_CLOCK_BASE_SHIFT        8
+#define  SDHCI_CAN_DO_DMA      0x00400000
+#define  SDHCI_CAN_VDD_330     0x01000000
+#define  SDHCI_CAN_VDD_300     0x02000000
+#define  SDHCI_CAN_VDD_180     0x04000000
 
 /* 44-47 reserved for more caps */
 
@@ -151,6 +158,7 @@ struct sdhci_host {
        unsigned int            max_clk;        /* Max possible freq (MHz) */
 
        unsigned int            clock;          /* Current clock (MHz) */
+       unsigned short          power;          /* Current voltage */
 
        struct mmc_request      *mrq;           /* Current request */
        struct mmc_command      *cmd;           /* Current command */