Hello,

I've been propagating this patch around internally, so I thought I'd
post it here too in case someone might be interested.  Basically, I've
been testing recent samples of new eMMC cards from our vendors.  These
cards have support for "card power class".  Cards are becoming more
powerful and as such are drawing more current.  This feature provides
a means to limit the maximum current consumption of a particular card.
 We need to support this because these new cards start off in power
class 0, which leads to dismal performance.  At present, this only
seems to impact 8-bit buses.

The patch is simple, but the card firmware implementations for this
feature have been a bit buggy.   Though recent samples from two of our
vendors are much more promising.  Also, the JEDEC spec is littered
with vagueness on many of the details of how this is supposed to work.
 This led to some of the firmware issues.  I've done my best to
interpret the spec and got at least one of our vendors to agree on the
interpretation.  :)  So far it has been validated on Sandisk and
Toshiba parts.

Some caveats with this patch:

1) This patch doesn't quite apply as nicely to mmc-next as it does our
older kernels.  One of the details that ended being tricky for the
card firmware was the sequence.  The spec indicates that the power
class switch command should be sent after the host has determined the
bus width, but *before* sending the bus width switch command.  I see a
new "trial and error" loop in mmc_init_card() that we don't have in
our older kernel.  For this to work and if my interpretation of the
spec is right, we need to send the power class command each time we
try one of the bus widths.

2) The point of the feature is to allow the host/platform to limit the
maximum current draw of the card.  As such, I have added a
"max_power_class" to the main host structure that the host driver
should populate.  The caveat here is that the value will be left as 0
by current host drivers, but 0 is a valid power class.  The net effect
of this patch will be nil for those hosts.  So either we can kludge
the power class value and interpret a zero here to mean "maximum
performance", or we can just leave it alone and let the various host
drivers figure out how they want to deal with it.  I opted for the
latter because it meant I could be lazier.  ;)

I left out the change in our host driver (an SDHCI-based one), since
it is a very old version and doesn't really apply here.  In our case,
the value will come from a platform_data structure.  Also, I haven't
back-ported mmc-next to our older kernel so that I can retest this
version of the patch.  It should be fine and it does compile on
linux-next, but I thought I should mention that little fact.  The only
part that had to be tweaked was for caveat #1 above.

Obviously I'm looking for comments.  Let me know what you think.

Thanks,
Russ
---

commit da15e2374677be57f9a5bb4d3fa3548fca2f7067
Author: Russ W. Knize <[email protected]>
Date:   Fri Apr 1 10:24:48 2011 -0500

    mmc: power class support

    Add power class support for v4 cards.  Power classes are provided as a
    means to control the maximum amount of current consumption of an MMC/SD
    device.  Each class defines a maximum current draw for a particular
    voltage, bus width, and bus speed.  Higher classes offer better
    performance potential.  Cards that support power classes seem to start
    up in their lowest class and will therefore have poor relative
    performance until they recieve the switch command.

diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 1f453ac..a9a6658 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -954,6 +954,63 @@ void mmc_set_timing(struct mmc_host *host,
unsigned int timing)
 }

 /*
+ * Try to upgrade the power class to the maximum supported.  The maximum power
+ * class supported by a card is stored as 8 values in 4 EXT_CSD bytes.  The
+ * value that applies depends on the operating voltage, bus speed, and bus
+ * width.
+ */
+int mmc_set_power_class(struct mmc_host *host, unsigned int hz,
+                         unsigned int width)
+{
+       struct mmc_card *card = host->card;
+       int class_index = MMC_EXT_CSD_PWR_CL(EXT_CSD_PWR_CL_52_195);
+       u8 power_class = 0;
+       int err = 0;
+
+       /*
+        * The spec is vague about what voltage threshold is used to determine
+        * which class value to use, but they probably intend it for "low"
+        * (1.7V-1.95V) versus "high" (2.7V-3.6V) voltage cards.
+        */
+       if (host->ocr >= MMC_VDD_27_28)
+               class_index = MMC_EXT_CSD_PWR_CL(EXT_CSD_PWR_CL_52_360);
+
+       /*
+        * More vagueness here.  Assume that the threshold is at 26MHz.
+        */
+       if (hz <= 26000000)
+               class_index++;
+
+       power_class = card->ext_csd.power_class[class_index];
+       if (width == MMC_BUS_WIDTH_4)
+               power_class &= 0xF;
+       else if (width == MMC_BUS_WIDTH_8)
+               power_class = (power_class & 0xF0) >> 4;
+
+       if (power_class > host->max_power_class)
+               power_class = host->max_power_class;
+
+       if (power_class > 0) {
+               pr_debug("%s: power class %d (max %d), %u HZ, width %d, 
OCR=0x%08X\n",
+                       mmc_hostname(card->host),
+                       power_class,
+                       host->max_power_class,
+                       hz,
+                       width,
+                       card->host->ocr);
+
+               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+                       EXT_CSD_POWER_CLASS, power_class);
+               if (err) {
+                       pr_warning("%s: switch power class failed (%d)\n",
+                               mmc_hostname(card->host), err);
+               }
+       }
+
+       return err;
+}
+
+/*
  * Apply power to the MMC stack.  This is a two-stage process.
  * First, we enable power to the card without the clock running.
  * We then wait a bit for the power to stabilise.  Finally,
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index 20b1c08..8e5847d 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -42,6 +42,8 @@ void mmc_set_bus_width_ddr(struct mmc_host *host,
unsigned int width,
                           unsigned int ddr);
 u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
 void mmc_set_timing(struct mmc_host *host, unsigned int timing);
+int mmc_set_power_class(struct mmc_host *host, unsigned int hz,
+                        unsigned int width);

 static inline void mmc_delay(unsigned int ms)
 {
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 2d48800..47f779d 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -304,6 +304,14 @@ static int mmc_read_ext_csd(struct mmc_card *card)

        if (card->ext_csd.rev >= 4) {
                /*
+                * Read power class table.
+                */
+               int i;
+               for (i = EXT_CSD_PWR_CL_52_195;
+                    i <= EXT_CSD_PWR_CL_26_360; i++)
+                       card->ext_csd.power_class[MMC_EXT_CSD_PWR_CL(i)] =
+                                                                  ext_csd[i];
+               /*
                 * Enhanced area feature support -- check whether the eMMC
                 * card has the Enhanced area enabled.  If so, export enhanced
                 * area offset and size to user by adding sysfs interface.
@@ -648,6 +656,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
                        bus_width = bus_widths[idx];
                        if (bus_width == MMC_BUS_WIDTH_1)
                                ddr = 0; /* no DDR for 1-bit width */
+
+                       /*
+                        * Ignore switch errors from buggy cards that actually
+                        * do switch successfully.
+                        */
+                       err = mmc_set_power_class(host, max_dtr, bus_width);
+                       if (err && err != -EBADMSG)
+                               goto free_card;
+
                        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                                         EXT_CSD_BUS_WIDTH,
                                         ext_csd_bits[idx][0]);
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 9e15f41..12740d4 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1981,6 +1981,8 @@ int sdhci_add_host(struct sdhci_host *host)
         */
        mmc->max_blk_count = (host->quirks & SDHCI_QUIRK_NO_MULTIBLOCK) ? 1 : 
65535;

+       mmc->max_power_class = host->max_power_class;
+
        /*
         * Init tasklets.
         */
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 557b732..e18dff0 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -46,6 +46,8 @@ struct mmc_ext_csd {
        u8                      erase_group_def;
        u8                      sec_feature_support;
        u8                      bootconfig;
+       u8                      power_class[4];
+#define MMC_EXT_CSD_PWR_CL(b)  (b - EXT_CSD_PWR_CL_52_195)
        unsigned int            sa_timeout;             /* Units: 100ns */
        unsigned int            hs_max_dtr;
        unsigned int            sectors;
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index bcb793e..7509e39 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -227,6 +227,8 @@ struct mmc_host {
        const struct mmc_bus_ops *bus_ops;      /* current bus driver */
        unsigned int            bus_refs;       /* reference counter */

+       unsigned int            max_power_class;
+
        unsigned int            sdio_irqs;
        struct task_struct      *sdio_irq_thread;
        atomic_t                sdio_irq_thread_abort;
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index b5ec88f..2e3a7fd 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -260,9 +260,14 @@ struct _mmc_csd {
 #define EXT_CSD_ERASED_MEM_CONT                181     /* RO */
 #define EXT_CSD_BUS_WIDTH              183     /* R/W */
 #define EXT_CSD_HS_TIMING              185     /* R/W */
+#define EXT_CSD_POWER_CLASS            187     /* R/W */
 #define EXT_CSD_REV                    192     /* RO */
 #define EXT_CSD_STRUCTURE              194     /* RO */
 #define EXT_CSD_CARD_TYPE              196     /* RO */
+#define EXT_CSD_PWR_CL_52_195          200     /* RO */
+#define EXT_CSD_PWR_CL_26_195          201     /* RO */
+#define EXT_CSD_PWR_CL_52_360          202     /* RO */
+#define EXT_CSD_PWR_CL_26_360          203     /* RO */
 #define EXT_CSD_SEC_CNT                        212     /* RO, 4 bytes */
 #define EXT_CSD_S_A_TIMEOUT            217     /* RO */
 #define EXT_CSD_HC_WP_GRP_SIZE         221     /* RO */
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index 83bd9f7..b1c30f3 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -145,6 +145,8 @@ struct sdhci_host {
        unsigned int            ocr_avail_sd;
        unsigned int            ocr_avail_mmc;

+       unsigned int            max_power_class;
+
        unsigned long private[0] ____cacheline_aligned;
 };
 #endif /* __SDHCI_H */
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to