mmc: add hs400 enhanced strobe support for mmc subsystem
[firefly-linux-kernel-4.4.55.git] / drivers / mmc / core / mmc.c
index 3d5087b03999dea605f2b424899077da2592fd53..94bde1d5dae86e9c643fb0dd3110d55b59b77fb2 100644 (file)
@@ -235,6 +235,12 @@ static void mmc_select_card_type(struct mmc_card *card)
                avail_type |= EXT_CSD_CARD_TYPE_HS400_1_2V;
        }
 
+       if ((caps2 & MMC_CAP2_HS400_ENHANCED_STROBE) &&
+               card->ext_csd.strobe_support &&
+               ((card_type & EXT_CSD_CARD_TYPE_HS400_1_2V) ||
+               (card_type & EXT_CSD_CARD_TYPE_HS400_1_8V)))
+               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;
@@ -383,6 +389,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);
 
@@ -1062,10 +1075,11 @@ static int mmc_select_hs400(struct mmc_card *card)
        u8 val;
 
        /*
-        * HS400 mode requires 8-bit bus width
+        * HS400(ES) mode requires 8-bit bus width
         */
-       if (!(card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400 &&
-             host->ios.bus_width == MMC_BUS_WIDTH_8))
+       if (!(((card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400) ||
+               (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400ES)) &&
+               host->ios.bus_width == MMC_BUS_WIDTH_8))
                return 0;
 
        if (host->caps & MMC_CAP_WAIT_WHILE_BUSY)
@@ -1097,9 +1111,26 @@ static int mmc_select_hs400(struct mmc_card *card)
        }
 
        /* Switch card to DDR */
+       val = EXT_CSD_DDR_BUS_WIDTH_8;
+       if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400ES) {
+               val |= EXT_CSD_BUS_WIDTH_STROBE;
+               /*
+                * Make sure we are in non-enhanced strobe mode before we
+                * actually enable it in ext_csd.
+                */
+               if (host->ops->prepare_enhanced_strobe)
+                       err = host->ops->prepare_enhanced_strobe(host, false);
+
+               if (err) {
+                       pr_err("%s: unprepare_enhanced strobe failed, err:%d\n",
+                               mmc_hostname(host), err);
+                       return err;
+               }
+       }
+
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                         EXT_CSD_BUS_WIDTH,
-                        EXT_CSD_DDR_BUS_WIDTH_8,
+                        val,
                         card->ext_csd.generic_cmd6_time);
        if (err) {
                pr_err("%s: switch to bus width for hs400 failed, err:%d\n",
@@ -1124,6 +1155,35 @@ static int mmc_select_hs400(struct mmc_card *card)
        mmc_set_timing(host, MMC_TIMING_MMC_HS400);
        mmc_set_bus_speed(card);
 
+       if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400ES) {
+               /* Controller enable enhanced strobe function */
+               if (host->ops->prepare_enhanced_strobe)
+                       err = host->ops->prepare_enhanced_strobe(host, true);
+
+               if (err) {
+                       pr_err("%s: prepare enhanced strobe failed, err:%d\n",
+                               mmc_hostname(host), err);
+                       return err;
+               }
+
+               /*
+                * After switching to hs400 enhanced strobe mode, we expect to
+                * verify whether it works or not. If controller can't handle
+                * bus width test, compare ext_csd previously read in 1 bit mode
+                * against ext_csd at new bus width
+                */
+               if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST))
+                       err = mmc_compare_ext_csds(card, MMC_BUS_WIDTH_8);
+               else
+                       err = mmc_bus_test(card, MMC_BUS_WIDTH_8);
+
+               if (err) {
+                       pr_warn("%s: switch to enhanced strobe failed\n",
+                               mmc_hostname(host));
+                       return err;
+               }
+       }
+
        if (!send_status) {
                err = mmc_switch_status(card);
                if (err)
@@ -1297,7 +1357,8 @@ err:
 }
 
 /*
- * Activate High Speed or HS200 mode if supported.
+ * Activate High Speed or HS200 mode if supported. For HS400
+ * with enhanced strobe mode, we should activate High Speed.
  */
 static int mmc_select_timing(struct mmc_card *card)
 {
@@ -1306,7 +1367,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_hs(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);
@@ -1577,7 +1640,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
        } else if (mmc_card_hs(card)) {
                /* Select the desired bus width optionally */
                err = mmc_select_bus_width(card);
-               if (!IS_ERR_VALUE(err)) {
+               if (IS_ERR_VALUE(err))
+                       goto free_card;
+
+               if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400ES) {
+                       /* Directly from HS to HS400-ES */
+                       err = mmc_select_hs400(card);
+                       if (err)
+                               goto free_card;
+               } else {
                        err = mmc_select_hs_ddr(card);
                        if (err)
                                goto free_card;