[PATCH] mmc: sdio: support SDIO UHS cards

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds support for sdio UHS cards per the version 3.0
spec.

UHS mode is only enabled for version 3.0 cards when both the
host and the controller support UHS modes.

1.8v signaling support is removed if both the card and the
host do not support UHS.  This is done to maintain
compatibility and some system/card combinations break when
1.8v signaling is enabled when the host does not support UHS.

Signed-off-by: Philip Rakity <prakity@xxxxxxxxxxx>
Signed-off-by: Aaron Lu <Aaron.lu@xxxxxxx>
Reviewed-by: Arindam Nath <arindam.nath@xxxxxxx>
Tested-by: Bing Zhao <bzhao@xxxxxxxxxxx>
---
 drivers/mmc/core/bus.c   |    2 +-
 drivers/mmc/core/sd.c    |    2 +-
 drivers/mmc/core/sdio.c  |  329 ++++++++++++++++++++++++++++++++++++++++++----
 include/linux/mmc/card.h |    4 +-
 include/linux/mmc/sdio.h |   29 ++++-
 5 files changed, 336 insertions(+), 30 deletions(-)

diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c
index 46b6e84..5639fdf 100644
--- a/drivers/mmc/core/bus.c
+++ b/drivers/mmc/core/bus.c
@@ -303,7 +303,7 @@ int mmc_add_card(struct mmc_card *card)
        } else {
                printk(KERN_INFO "%s: new %s%s%s card at address %04x\n",
                        mmc_hostname(card->host),
-                       mmc_sd_card_uhs(card) ? "ultra high speed " :
+                       mmc_card_uhs(card) ? "ultra high speed " :
                        (mmc_card_highspeed(card) ? "high speed " : ""),
                        mmc_card_ddr_mode(card) ? "DDR " : "",
                        type, card->rca);
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index a230e7f..d68cf4a 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -959,7 +959,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
                        goto free_card;

                /* Card is an ultra-high-speed card */
-               mmc_sd_card_set_uhs(card);
+               mmc_card_set_uhs(card);

                /*
                 * Since initialization is now complete, enable preset
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index 3ab565e..8c04f7f 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -102,6 +102,7 @@ static int sdio_read_cccr(struct mmc_card *card)
        int ret;
        int cccr_vsn;
        unsigned char data;
+       unsigned char speed;

        memset(&card->cccr, 0, sizeof(struct sdio_cccr));

@@ -140,12 +141,60 @@ static int sdio_read_cccr(struct mmc_card *card)
        }

        if (cccr_vsn >= SDIO_CCCR_REV_1_20) {
-               ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data);
+               ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
                if (ret)
                        goto out;

-               if (data & SDIO_SPEED_SHS)
-                       card->cccr.high_speed = 1;
+               card->scr.sda_spec3 = 0;
+               card->sw_caps.sd3_bus_mode = 0;
+               card->sw_caps.sd3_drv_type = 0;
+               if (cccr_vsn >= SDIO_CCCR_REV_3_00) {
+                       card->scr.sda_spec3 = 1;
+                       ret = mmc_io_rw_direct(card, 0, 0,
+                               SDIO_CCCR_UHS, 0, &data);
+                       if (ret)
+                               goto out;
+
+                       if (card->host->caps &
+                               (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+                                MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 |
+                                MMC_CAP_UHS_DDR50)) {
+                               if (data & SDIO_UHS_DDR50)
+                                       card->sw_caps.sd3_bus_mode
+                                               |= SD_MODE_UHS_DDR50;
+
+                               if (data & SDIO_UHS_SDR50)
+                                       card->sw_caps.sd3_bus_mode
+                                               |= SD_MODE_UHS_SDR50;
+
+                               if (data & SDIO_UHS_SDR104)
+                                       card->sw_caps.sd3_bus_mode
+                                               |= SD_MODE_UHS_SDR104;
+                       }
+
+                       ret = mmc_io_rw_direct(card, 0, 0,
+                               SDIO_CCCR_DRIVE_STRENGTH, 0, &data);
+                       if (ret)
+                               goto out;
+
+                       if (data & SDIO_DRIVE_SDTA)
+                               card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_A;
+                       if (data & SDIO_DRIVE_SDTC)
+                               card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_C;
+                       if (data & SDIO_DRIVE_SDTD)
+                               card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_D;
+               }
+
+               /* if no uhs mode ensure we check for high speed */
+               if (!card->sw_caps.sd3_bus_mode) {
+                       if (speed & SDIO_SPEED_SHS) {
+                               card->cccr.high_speed = 1;
+                               card->sw_caps.hs_max_dtr = 50000000;
+                       } else {
+                               card->cccr.high_speed = 0;
+                               card->sw_caps.hs_max_dtr = 25000000;
+                       }
+               }
        }

 out:
@@ -327,6 +376,193 @@ static unsigned mmc_sdio_get_max_clock(struct mmc_card *card)
        return max_dtr;
 }

+static unsigned char host_drive_to_sdio_drive(int host_strength)
+{
+       switch (host_strength) {
+       case MMC_SET_DRIVER_TYPE_A:
+               return SDIO_DTSx_SET_TYPE_A;
+       case MMC_SET_DRIVER_TYPE_B:
+               return SDIO_DTSx_SET_TYPE_B;
+       case MMC_SET_DRIVER_TYPE_C:
+               return SDIO_DTSx_SET_TYPE_C;
+       case MMC_SET_DRIVER_TYPE_D:
+               return SDIO_DTSx_SET_TYPE_D;
+       default:
+               return SDIO_DTSx_SET_TYPE_B;
+       }
+}
+
+static void sdio_select_driver_type(struct mmc_card *card)
+{
+       int host_drv_type = SD_DRIVER_TYPE_B;
+       int card_drv_type = SD_DRIVER_TYPE_B;
+       int drive_strength;
+       unsigned char card_strength;
+       int err;
+
+       /*
+        * If the host doesn't support any of the Driver Types A,C or D,
+        * or there is no board specific handler then default Driver
+        * Type B is used.
+        */
+       if (!(card->host->caps &
+               (MMC_CAP_DRIVER_TYPE_A |
+                MMC_CAP_DRIVER_TYPE_C |
+                MMC_CAP_DRIVER_TYPE_D)))
+               return;
+
+       if (!card->host->ops->select_drive_strength)
+               return;
+
+       if (card->host->caps & MMC_CAP_DRIVER_TYPE_A)
+               host_drv_type |= SD_DRIVER_TYPE_A;
+
+       if (card->host->caps & MMC_CAP_DRIVER_TYPE_C)
+               host_drv_type |= SD_DRIVER_TYPE_C;
+
+       if (card->host->caps & MMC_CAP_DRIVER_TYPE_D)
+               host_drv_type |= SD_DRIVER_TYPE_D;
+
+       if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_A)
+               card_drv_type |= SD_DRIVER_TYPE_A;
+
+       if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_C)
+               card_drv_type |= SD_DRIVER_TYPE_C;
+
+       if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_D)
+               card_drv_type |= SD_DRIVER_TYPE_D;
+
+       /*
+        * The drive strength that the hardware can support
+        * depends on the board design.  Pass the appropriate
+        * information and let the hardware specific code
+        * return what is possible given the options
+        */
+       drive_strength = card->host->ops->select_drive_strength(
+               card->sw_caps.uhs_max_dtr,
+               host_drv_type, card_drv_type);
+
+       /* if error just use default for drive strength B */
+       err = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_DRIVE_STRENGTH, 0,
+               &card_strength);
+       if (err)
+               return;
+
+       card_strength &= ~(SDIO_DRIVE_DTSx_MASK<<SDIO_DRIVE_DTSx_SHIFT);
+       card_strength |= host_drive_to_sdio_drive(drive_strength);
+
+       err = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_DRIVE_STRENGTH,
+               card_strength, NULL);
+
+       /* if error default to drive strength B */
+       if (!err)
+               mmc_set_driver_type(card->host, drive_strength);
+}
+
+
+static int sdio_set_bus_speed_mode(struct mmc_card *card)
+{
+       unsigned int bus_speed, timing;
+       int err;
+       unsigned char speed;
+
+       /*
+        * If the host doesn't support any of the UHS-I modes, fallback on
+        * default speed.
+        */
+       if (!(card->host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+           MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50)))
+               return 0;
+
+       bus_speed = SDIO_SPEED_SDR12;
+       timing = MMC_TIMING_UHS_SDR12;
+       if ((card->host->caps & MMC_CAP_UHS_SDR104) &&
+           (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR104)) {
+                       bus_speed = SDIO_SPEED_SDR104;
+                       timing = MMC_TIMING_UHS_SDR104;
+                       card->sw_caps.uhs_max_dtr = UHS_SDR104_MAX_DTR;
+       } else if ((card->host->caps & MMC_CAP_UHS_DDR50) &&
+                  (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_DDR50)) {
+                       bus_speed = SDIO_SPEED_DDR50;
+                       timing = MMC_TIMING_UHS_DDR50;
+                       card->sw_caps.uhs_max_dtr = UHS_DDR50_MAX_DTR;
+       } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 |
+                   MMC_CAP_UHS_SDR50)) && (card->sw_caps.sd3_bus_mode &
+                   SD_MODE_UHS_SDR50)) {
+                       bus_speed = SDIO_SPEED_SDR50;
+                       timing = MMC_TIMING_UHS_SDR50;
+                       card->sw_caps.uhs_max_dtr = UHS_SDR50_MAX_DTR;
+       } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 |
+                   MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25)) &&
+                  (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR25)) {
+                       bus_speed = SDIO_SPEED_SDR25;
+                       timing = MMC_TIMING_UHS_SDR25;
+                       card->sw_caps.uhs_max_dtr = UHS_SDR25_MAX_DTR;
+       } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 |
+                   MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25 |
+                   MMC_CAP_UHS_SDR12)) && (card->sw_caps.sd3_bus_mode &
+                   SD_MODE_UHS_SDR12)) {
+                       bus_speed = SDIO_SPEED_SDR12;
+                       timing = MMC_TIMING_UHS_SDR12;
+                       card->sw_caps.uhs_max_dtr = UHS_SDR12_MAX_DTR;
+       }
+
+       err = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
+       if (err)
+               return err;
+
+       speed &= ~SDIO_SPEED_BSS_MASK;
+       speed |= bus_speed;
+       err = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL);
+       if (err)
+               return err;
+
+       if (bus_speed) {
+               mmc_set_timing(card->host, timing);
+               mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr);
+       }
+
+       return 0;
+}
+
+/*
+ * UHS-I specific initialization procedure
+ */
+static int mmc_sdio_init_uhs_card(struct mmc_card *card)
+{
+       int err;
+
+       if (!card->scr.sda_spec3)
+               return 0;
+
+       /*
+        * Switch to wider bus (if supported).
+        */
+       if (card->host->caps & MMC_CAP_4_BIT_DATA) {
+               err = sdio_enable_4bit_bus(card);
+               if (err > 0) {
+                       mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
+                       err = 0;
+               }
+       }
+
+       /* Set the driver strength for the card */
+       sdio_select_driver_type(card);
+
+       /* Set bus speed mode of the card */
+       err = sdio_set_bus_speed_mode(card);
+       if (err)
+               goto out;
+
+       /* Initialize and start re-tuning timer */
+       if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning)
+               err = card->host->ops->execute_tuning(card->host);
+
+out:
+
+       return err;
+}
+
 /*
  * Handle the detection and initialisation of a card.
  *
@@ -394,6 +630,30 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
                host->ops->init_card(host, card);

        /*
+        * If the host and card support UHS-I mode request the card
+        * to switch to 1.8V signaling level.  No 1.8v signalling if
+        * UHS mode is not enabled to maintain compatibilty and some
+        * systems that claim 1.8v signalling in fact do not support
+        * it.
+        */
+       if ((ocr & R4_18V_PRESENT) &&
+               (host->caps &
+                       (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+                        MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 |
+                        MMC_CAP_UHS_DDR50))) {
+               err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180,
+                               true);
+               if (err) {
+                       ocr &= ~R4_18V_PRESENT;
+                       host->ocr &= ~R4_18V_PRESENT;
+               }
+               err = 0;
+       } else {
+               ocr &= ~R4_18V_PRESENT;
+               host->ocr &= ~R4_18V_PRESENT;
+       }
+
+       /*
         * For native busses:  set card RCA and quit open drain mode.
         */
        if (!powered_resume && !mmc_host_is_spi(host)) {
@@ -492,29 +752,39 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
        if (err)
                goto remove;

-       /*
-        * Switch to high-speed (if supported).
-        */
-       err = sdio_enable_hs(card);
-       if (err > 0)
-               mmc_sd_go_highspeed(card);
-       else if (err)
-               goto remove;
+       /* Initialization sequence for UHS-I cards */
+       /* Only if card supports 1.8v and UHS signaling */
+       if ((ocr & R4_18V_PRESENT) && card->sw_caps.sd3_bus_mode) {
+               err = mmc_sdio_init_uhs_card(card);
+               if (err)
+                       goto remove;

-       /*
-        * Change to the card's maximum speed.
-        */
-       mmc_set_clock(host, mmc_sdio_get_max_clock(card));
+               /* Card is an ultra-high-speed card */
+               mmc_card_set_uhs(card);
+       } else {
+               /*
+                * Switch to high-speed (if supported).
+                */
+               err = sdio_enable_hs(card);
+               if (err > 0)
+                       mmc_sd_go_highspeed(card);
+               else if (err)
+                       goto remove;

-       /*
-        * Switch to wider bus (if supported).
-        */
-       err = sdio_enable_4bit_bus(card);
-       if (err > 0)
-               mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
-       else if (err)
-               goto remove;
+               /*
+                * Change to the card's maximum speed.
+                */
+               mmc_set_clock(host, mmc_sdio_get_max_clock(card));

+               /*
+                * Switch to wider bus (if supported).
+                */
+               err = sdio_enable_4bit_bus(card);
+               if (err > 0)
+                       mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
+               else if (err)
+                       goto remove;
+       }
 finish:
        if (!oldcard)
                host->card = card;
@@ -797,8 +1067,17 @@ int mmc_attach_sdio(struct mmc_host *host)
         * Detect and init the card.
         */
        err = mmc_sdio_init_card(host, host->ocr, NULL, 0);
-       if (err)
-               goto err;
+       if (err) {
+               if (err == -EAGAIN) {
+                       /*
+                        * Retry initialization with S18R set to 0.
+                        */
+                       host->ocr &= ~R4_18V_PRESENT;
+                       err = mmc_sdio_init_card(host, host->ocr, NULL, 0);
+               }
+               if (err)
+                       goto err;
+       }
        card = host->card;

        /*
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 415f2db..42cc1ca 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -363,7 +363,8 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data)
 #define mmc_card_highspeed(c)  ((c)->state & MMC_STATE_HIGHSPEED)
 #define mmc_card_blockaddr(c)  ((c)->state & MMC_STATE_BLOCKADDR)
 #define mmc_card_ddr_mode(c)   ((c)->state & MMC_STATE_HIGHSPEED_DDR)
-#define mmc_sd_card_uhs(c) ((c)->state & MMC_STATE_ULTRAHIGHSPEED)
+#define mmc_card_uhs(c)                ((c)->state & MMC_STATE_ULTRAHIGHSPEED)
+#define mmc_sd_card_uhs(c)     ((c)->state & MMC_STATE_ULTRAHIGHSPEED)
 #define mmc_card_ext_capacity(c) ((c)->state & MMC_CARD_SDXC)

 #define mmc_card_set_present(c)        ((c)->state |= MMC_STATE_PRESENT)
@@ -371,6 +372,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data)
 #define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED)
 #define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR)
 #define mmc_card_set_ddr_mode(c) ((c)->state |= MMC_STATE_HIGHSPEED_DDR)
+#define mmc_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED)
 #define mmc_sd_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED)
 #define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC)

diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h
index e0b1123..c9fe66c 100644
--- a/include/linux/mmc/sdio.h
+++ b/include/linux/mmc/sdio.h
@@ -38,6 +38,7 @@
  *      [8:0] Byte/block count
  */

+#define R4_18V_PRESENT (1<<24)
 #define R4_MEMORY_PRESENT (1 << 27)

 /*
@@ -85,6 +86,7 @@
 #define  SDIO_SD_REV_1_01      0       /* SD Physical Spec Version 1.01 */
 #define  SDIO_SD_REV_1_10      1       /* SD Physical Spec Version 1.10 */
 #define  SDIO_SD_REV_2_00      2       /* SD Physical Spec Version 2.00 */
+#define  SDIO_SD_REV_3_00      3       /* SD Physical Spev Version 3.00 */

 #define SDIO_CCCR_IOEx         0x02
 #define SDIO_CCCR_IORx         0x03
@@ -134,8 +136,31 @@
 #define SDIO_CCCR_SPEED                0x13

 #define  SDIO_SPEED_SHS                0x01    /* Supports High-Speed mode */
-#define  SDIO_SPEED_EHS                0x02    /* Enable High-Speed mode */
-
+#define  SDIO_SPEED_BSS_SHIFT  1
+#define  SDIO_SPEED_BSS_MASK   (7<<SDIO_SPEED_BSS_SHIFT)
+#define  SDIO_SPEED_SDR12      (0<<SDIO_SPEED_BSS_SHIFT)
+#define  SDIO_SPEED_SDR25      (1<<SDIO_SPEED_BSS_SHIFT)
+#define  SDIO_SPEED_SDR50      (2<<SDIO_SPEED_BSS_SHIFT)
+#define  SDIO_SPEED_SDR104     (3<<SDIO_SPEED_BSS_SHIFT)
+#define  SDIO_SPEED_DDR50      (4<<SDIO_SPEED_BSS_SHIFT)
+#define  SDIO_SPEED_EHS                SDIO_SPEED_SDR25        /* Enable High-Speed */
+
+#define SDIO_CCCR_UHS          0x14
+#define  SDIO_UHS_SDR50                0x01
+#define  SDIO_UHS_SDR104       0x02
+#define  SDIO_UHS_DDR50                0x04
+
+#define SDIO_CCCR_DRIVE_STRENGTH 0x15
+#define  SDIO_SDTx_MASK                0x07
+#define  SDIO_DRIVE_SDTA       (1<<0)
+#define  SDIO_DRIVE_SDTC       (1<<1)
+#define  SDIO_DRIVE_SDTD       (1<<2)
+#define  SDIO_DRIVE_DTSx_MASK  0x03
+#define  SDIO_DRIVE_DTSx_SHIFT 4
+#define  SDIO_DTSx_SET_TYPE_B  (0 << SDIO_DRIVE_DTSx_SHIFT)
+#define  SDIO_DTSx_SET_TYPE_A  (1 << SDIO_DRIVE_DTSx_SHIFT)
+#define  SDIO_DTSx_SET_TYPE_C  (2 << SDIO_DRIVE_DTSx_SHIFT)
+#define  SDIO_DTSx_SET_TYPE_D  (3 << SDIO_DRIVE_DTSx_SHIFT)
 /*
  * Function Basic Registers (FBR)
  */
--
1.7.6


--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux