mmc: use the right voltage level for MMC DDR and HS200 modes
authorJean-Jacques Hiblot <jjhiblot@ti.com>
Thu, 21 Sep 2017 14:30:11 +0000 (16:30 +0200)
committerJaehoon Chung <jh80.chung@samsung.com>
Fri, 12 Jan 2018 09:11:04 +0000 (18:11 +0900)
HS200 only supports 1.2v and 1.8v signal voltages. DDR52 supports 3.3v/1.8v
or 1.2v signal voltages.
Select the lowest voltage available when using those modes.

Signed-off-by: Jean-Jacques Hiblot <jjhiblot@ti.com>
drivers/mmc/mmc.c
include/mmc.h

index 26589b8771a892e8ecf524c14a47d95f7dde5529..877ce1757da38bce892e0d6c319e29d39a5e7bf9 100644 (file)
@@ -767,6 +767,7 @@ static int mmc_get_capabilities(struct mmc *mmc)
        mmc->card_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT;
 
        cardtype = ext_csd[EXT_CSD_CARD_TYPE] & 0x3f;
+       mmc->cardtype = cardtype;
 
        if (cardtype & (EXT_CSD_CARD_TYPE_HS200_1_2V |
                        EXT_CSD_CARD_TYPE_HS200_1_8V)) {
@@ -1441,10 +1442,30 @@ struct mode_width_tuning {
        uint tuning;
 };
 
+int mmc_voltage_to_mv(enum mmc_voltage voltage)
+{
+       switch (voltage) {
+       case MMC_SIGNAL_VOLTAGE_000: return 0;
+       case MMC_SIGNAL_VOLTAGE_330: return 3300;
+       case MMC_SIGNAL_VOLTAGE_180: return 1800;
+       case MMC_SIGNAL_VOLTAGE_120: return 1200;
+       }
+       return -EINVAL;
+}
+
 static int mmc_set_signal_voltage(struct mmc *mmc, uint signal_voltage)
 {
+       int err;
+
+       if (mmc->signal_voltage == signal_voltage)
+               return 0;
+
        mmc->signal_voltage = signal_voltage;
-       return mmc_set_ios(mmc);
+       err = mmc_set_ios(mmc);
+       if (err)
+               debug("unable to set voltage (err %d)\n", err);
+
+       return err;
 }
 
 static const struct mode_width_tuning sd_modes_by_pref[] = {
@@ -1584,6 +1605,43 @@ static int mmc_read_and_compare_ext_csd(struct mmc *mmc)
        return -EBADMSG;
 }
 
+static int mmc_set_lowest_voltage(struct mmc *mmc, enum bus_mode mode,
+                                 uint32_t allowed_mask)
+{
+       u32 card_mask = 0;
+
+       switch (mode) {
+       case MMC_HS_200:
+               if (mmc->cardtype & EXT_CSD_CARD_TYPE_HS200_1_8V)
+                       card_mask |= MMC_SIGNAL_VOLTAGE_180;
+               if (mmc->cardtype & EXT_CSD_CARD_TYPE_HS200_1_2V)
+                       card_mask |= MMC_SIGNAL_VOLTAGE_120;
+               break;
+       case MMC_DDR_52:
+               if (mmc->cardtype & EXT_CSD_CARD_TYPE_DDR_1_8V)
+                       card_mask |= MMC_SIGNAL_VOLTAGE_330 |
+                                    MMC_SIGNAL_VOLTAGE_180;
+               if (mmc->cardtype & EXT_CSD_CARD_TYPE_DDR_1_2V)
+                       card_mask |= MMC_SIGNAL_VOLTAGE_120;
+               break;
+       default:
+               card_mask |= MMC_SIGNAL_VOLTAGE_330;
+               break;
+       }
+
+       while (card_mask & allowed_mask) {
+               enum mmc_voltage best_match;
+
+               best_match = 1 << (ffs(card_mask & allowed_mask) - 1);
+               if (!mmc_set_signal_voltage(mmc,  best_match))
+                       return 0;
+
+               allowed_mask &= ~best_match;
+       }
+
+       return -ENOTSUPP;
+}
+
 static const struct mode_width_tuning mmc_modes_by_pref[] = {
        {
                .mode = MMC_HS_200,
@@ -1655,10 +1713,17 @@ static int mmc_select_mode_and_width(struct mmc *mmc, uint card_caps)
        for_each_mmc_mode_by_pref(card_caps, mwt) {
                for_each_supported_width(card_caps & mwt->widths,
                                         mmc_is_mode_ddr(mwt->mode), ecbw) {
+                       enum mmc_voltage old_voltage;
                        debug("trying mode %s width %d (at %d MHz)\n",
                              mmc_mode_name(mwt->mode),
                              bus_width(ecbw->cap),
                              mmc_mode2freq(mmc, mwt->mode) / 1000000);
+                       old_voltage = mmc->signal_voltage;
+                       err = mmc_set_lowest_voltage(mmc, mwt->mode,
+                                                    MMC_ALL_SIGNAL_VOLTAGE);
+                       if (err)
+                               continue;
+
                        /* configure the bus width (card + host) */
                        err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
                                    EXT_CSD_BUS_WIDTH,
@@ -1702,6 +1767,7 @@ static int mmc_select_mode_and_width(struct mmc *mmc, uint card_caps)
                        if (!err)
                                return 0;
 error:
+                       mmc_set_signal_voltage(mmc, old_voltage);
                        /* if an error occured, revert to a safer bus mode */
                        mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
                                   EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_1);
index a9ebc880cb412e42f5b40d1e8525c13726083ef6..c11f69859ed708e77f033b54c66d1030a7da08f8 100644 (file)
@@ -311,11 +311,15 @@ static inline bool mmc_is_tuning_cmd(uint cmdidx)
 
 enum mmc_voltage {
        MMC_SIGNAL_VOLTAGE_000 = 0,
-       MMC_SIGNAL_VOLTAGE_120,
-       MMC_SIGNAL_VOLTAGE_180,
-       MMC_SIGNAL_VOLTAGE_330
+       MMC_SIGNAL_VOLTAGE_120 = 1,
+       MMC_SIGNAL_VOLTAGE_180 = 2,
+       MMC_SIGNAL_VOLTAGE_330 = 4,
 };
 
+#define MMC_ALL_SIGNAL_VOLTAGE (MMC_SIGNAL_VOLTAGE_120 |\
+                               MMC_SIGNAL_VOLTAGE_180 |\
+                               MMC_SIGNAL_VOLTAGE_330)
+
 /* Maximum block size for MMC */
 #define MMC_MAX_BLOCK_LEN      512
 
@@ -588,6 +592,8 @@ struct mmc {
 #endif
 #endif
        u8 *ext_csd;
+       u32 cardtype;           /* cardtype read from the MMC */
+       enum mmc_voltage current_voltage;
        enum bus_mode selected_mode; /* mode currently used */
        enum bus_mode best_mode; /* best mode is the supported mode with the
                                  * highest bandwidth. It may not always be the
@@ -646,6 +652,14 @@ int mmc_initialize(bd_t *bis);
 int mmc_init(struct mmc *mmc);
 int mmc_read(struct mmc *mmc, u64 src, uchar *dst, int size);
 
+/**
+ * mmc_voltage_to_mv() - Convert a mmc_voltage in mV
+ *
+ * @voltage:   The mmc_voltage to convert
+ * @return the value in mV if OK, -EINVAL on error (invalid mmc_voltage value)
+ */
+int mmc_voltage_to_mv(enum mmc_voltage voltage);
+
 /**
  * mmc_set_clock() - change the bus clock
  * @mmc:       MMC struct