Merge tag 'lsk-v4.4-16.06-android'
[firefly-linux-kernel-4.4.55.git] / drivers / mmc / core / mmc.c
index 78187699467a11cb6ad43b9fb2298aaeee933a13..e26379d1461d4abe1997eefc11d56d4a7e25cd3c 100644 (file)
@@ -235,6 +235,11 @@ static void mmc_select_card_type(struct mmc_card *card)
                avail_type |= EXT_CSD_CARD_TYPE_HS400_1_2V;
        }
 
+       if ((caps2 & MMC_CAP2_HS400_ES) &&
+               card->ext_csd.strobe_support &&
+               (avail_type & EXT_CSD_CARD_TYPE_HS400))
+               avail_type |= EXT_CSD_CARD_TYPE_HS400ES;
+
        card->ext_csd.hs_max_dtr = hs_max_dtr;
        card->ext_csd.hs200_max_dtr = hs200_max_dtr;
        card->mmc_avail_type = avail_type;
@@ -386,6 +391,13 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd)
                        mmc_card_set_blockaddr(card);
        }
 
+       /*
+        * Enhance Strobe is supported since v5.1 which rev should be
+        * 8 but some eMMC devices can support it with rev 7. So handle
+        * Enhance Strobe here.
+        */
+       card->ext_csd.strobe_support = ext_csd[EXT_CSD_STROBE_SUPPORT];
+
        card->ext_csd.raw_card_type = ext_csd[EXT_CSD_CARD_TYPE];
        mmc_select_card_type(card);
 
@@ -1078,16 +1090,12 @@ static int mmc_select_hs400(struct mmc_card *card)
        if (host->caps & MMC_CAP_WAIT_WHILE_BUSY)
                send_status = false;
 
-       /* Reduce frequency to HS frequency */
-       max_dtr = card->ext_csd.hs_max_dtr;
-       mmc_set_clock(host, max_dtr);
-
        /* Switch card to HS mode */
        val = EXT_CSD_TIMING_HS;
        err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                           EXT_CSD_HS_TIMING, val,
                           card->ext_csd.generic_cmd6_time,
-                          true, send_status, true);
+                          true, false, true);
        if (err) {
                pr_err("%s: switch to high-speed from hs200 failed, err:%d\n",
                        mmc_hostname(host), err);
@@ -1097,6 +1105,10 @@ static int mmc_select_hs400(struct mmc_card *card)
        /* Set host controller to HS timing */
        mmc_set_timing(card->host, MMC_TIMING_MMC_HS);
 
+       /* Reduce frequency to HS frequency */
+       max_dtr = card->ext_csd.hs_max_dtr;
+       mmc_set_clock(host, max_dtr);
+
        if (!send_status) {
                err = mmc_switch_status(card);
                if (err)
@@ -1169,7 +1181,7 @@ int mmc_hs400_to_hs200(struct mmc_card *card)
        val = EXT_CSD_TIMING_HS;
        err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING,
                           val, card->ext_csd.generic_cmd6_time,
-                          true, send_status, true);
+                          true, false, true);
        if (err)
                goto out_err;
 
@@ -1281,7 +1293,7 @@ static int mmc_select_hs200(struct mmc_card *card)
                err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                                   EXT_CSD_HS_TIMING, val,
                                   card->ext_csd.generic_cmd6_time,
-                                  true, send_status, true);
+                                  true, false, true);
                if (err)
                        goto err;
                old_timing = host->ios.timing;
@@ -1303,8 +1315,81 @@ err:
        return err;
 }
 
+static int mmc_select_hs400es(struct mmc_card *card)
+{
+       struct mmc_host *host = card->host;
+       int err = 0;
+       u8 val;
+
+       if (!(host->caps & MMC_CAP_8_BIT_DATA)) {
+               err = -ENOTSUPP;
+               goto out_err;
+       }
+
+       err = mmc_select_bus_width(card);
+       if (IS_ERR_VALUE(err))
+               goto out_err;
+
+       /* Switch card to HS mode */
+       err = mmc_select_hs(card);
+       if (err) {
+               pr_err("%s: switch to high-speed failed, err:%d\n",
+                       mmc_hostname(host), err);
+               goto out_err;
+       }
+
+       err = mmc_switch_status(card);
+       if (err)
+               goto out_err;
+
+       /* Switch card to DDR with strobe bit */
+       val = EXT_CSD_DDR_BUS_WIDTH_8 | EXT_CSD_BUS_WIDTH_STROBE;
+
+       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+                        EXT_CSD_BUS_WIDTH,
+                        val,
+                        card->ext_csd.generic_cmd6_time);
+       if (err) {
+               pr_err("%s: switch to bus width for hs400es failed, err:%d\n",
+                       mmc_hostname(host), err);
+               goto out_err;
+       }
+
+       /* Switch card to HS400 */
+       val = EXT_CSD_TIMING_HS400 |
+             card->drive_strength << EXT_CSD_DRV_STR_SHIFT;
+       err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+                          EXT_CSD_HS_TIMING, val,
+                          card->ext_csd.generic_cmd6_time,
+                          true, false, true);
+       if (err) {
+               pr_err("%s: switch to hs400es failed, err:%d\n",
+                        mmc_hostname(host), err);
+               goto out_err;
+       }
+
+       /* Set host controller to HS400 timing and frequency */
+       mmc_set_timing(host, MMC_TIMING_MMC_HS400);
+
+       /* Controller enable enhanced strobe function */
+       host->ios.enhanced_strobe = true;
+       if (host->ops->hs400_enhanced_strobe)
+               host->ops->hs400_enhanced_strobe(host, &host->ios);
+
+       err = mmc_switch_status(card);
+       if (err)
+               goto out_err;
+
+       return 0;
+
+out_err:
+       pr_err("%s: %s failed, error %d\n", mmc_hostname(card->host),
+              __func__, err);
+       return err;
+}
+
 /*
- * Activate High Speed or HS200 mode if supported.
+ * Activate High Speed or HS200 or HS400ES mode if supported.
  */
 static int mmc_select_timing(struct mmc_card *card)
 {
@@ -1313,7 +1398,9 @@ static int mmc_select_timing(struct mmc_card *card)
        if (!mmc_can_ext_csd(card))
                goto bus_speed;
 
-       if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200)
+       if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400ES)
+               err = mmc_select_hs400es(card);
+       else if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200)
                err = mmc_select_hs200(card);
        else if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS)
                err = mmc_select_hs(card);