EmbeddedPkg/MmcDxe: invoke SetIos() protocol method to set speed and width

Add the interface to change the bus width and speed.

By default, MMC is initialized with 1-bit mode and less than 400KHz bus
clock. It causes MMC working inefficiently.

Set I/O bus width on both MMC controller and EXTCSD. Otherwise, it may
cause unmatched failure case. And support more timing mode, high speed,
HS200 & HS400 mode.

Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Haojian Zhuang <haojian.zhuang@linaro.org>
Tested-by: Ryan Harkin <ryan.harkin@linaro.org>
Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
diff --git a/EmbeddedPkg/Universal/MmcDxe/Mmc.h b/EmbeddedPkg/Universal/MmcDxe/Mmc.h
index 112bc47..fb3f6c9 100644
--- a/EmbeddedPkg/Universal/MmcDxe/Mmc.h
+++ b/EmbeddedPkg/Universal/MmcDxe/Mmc.h
@@ -55,6 +55,11 @@
 #define MMC_R0_STATE_TRAN       4

 #define MMC_R0_STATE_DATA       5

 

+#define EMMC_CMD6_ARG_ACCESS(x)             (((x) & 0x3) << 24)

+#define EMMC_CMD6_ARG_INDEX(x)              (((x) & 0xFF) << 16)

+#define EMMC_CMD6_ARG_VALUE(x)              (((x) & 0xFF) << 8)

+#define EMMC_CMD6_ARG_CMD_SET(x)            (((x) & 0x7) << 0)

+

 typedef enum {

   UNKNOWN_CARD,

   MMC_CARD,              //MMC card

diff --git a/EmbeddedPkg/Universal/MmcDxe/MmcIdentification.c b/EmbeddedPkg/Universal/MmcDxe/MmcIdentification.c
index 9aefb26..decfd54 100644
--- a/EmbeddedPkg/Universal/MmcDxe/MmcIdentification.c
+++ b/EmbeddedPkg/Universal/MmcDxe/MmcIdentification.c
@@ -25,11 +25,111 @@
 #define EMMC_CARD_SIZE          512

 #define EMMC_ECSD_SIZE_OFFSET   53

 

+#define EXTCSD_BUS_WIDTH        183

+#define EXTCSD_HS_TIMING        185

+

+#define EMMC_TIMING_BACKWARD    0

+#define EMMC_TIMING_HS          1

+#define EMMC_TIMING_HS200       2

+#define EMMC_TIMING_HS400       3

+

+#define EMMC_BUS_WIDTH_1BIT     0

+#define EMMC_BUS_WIDTH_4BIT     1

+#define EMMC_BUS_WIDTH_8BIT     2

+#define EMMC_BUS_WIDTH_DDR_4BIT 5

+#define EMMC_BUS_WIDTH_DDR_8BIT 6

+

+#define EMMC_SWITCH_ERROR       (1 << 7)

+

+#define DEVICE_STATE(x)         (((x) >> 9) & 0xf)

+typedef enum _EMMC_DEVICE_STATE {

+  EMMC_IDLE_STATE = 0,

+  EMMC_READY_STATE,

+  EMMC_IDENT_STATE,

+  EMMC_STBY_STATE,

+  EMMC_TRAN_STATE,

+  EMMC_DATA_STATE,

+  EMMC_RCV_STATE,

+  EMMC_PRG_STATE,

+  EMMC_DIS_STATE,

+  EMMC_BTST_STATE,

+  EMMC_SLP_STATE

+} EMMC_DEVICE_STATE;

+

 UINT32 mEmmcRcaCount = 0;

 

 STATIC

 EFI_STATUS

 EFIAPI

+EmmcGetDeviceState (

+  IN  MMC_HOST_INSTANCE    *MmcHostInstance,

+  OUT EMMC_DEVICE_STATE    *State

+  )

+{

+  EFI_MMC_HOST_PROTOCOL *Host;

+  EFI_STATUS Status;

+  UINT32     Data, RCA;

+

+  if (State == NULL) {

+    return EFI_INVALID_PARAMETER;

+  }

+

+  Host = MmcHostInstance->MmcHost;

+  RCA = MmcHostInstance->CardInfo.RCA << RCA_SHIFT_OFFSET;

+  Status = Host->SendCommand (Host, MMC_CMD13, RCA);

+  if (EFI_ERROR (Status)) {

+    DEBUG ((EFI_D_ERROR, "EmmcGetDeviceState(): Failed to get card status, Status=%r.\n", Status));

+    return Status;

+  }

+  Status = Host->ReceiveResponse (Host, MMC_RESPONSE_TYPE_R1, &Data);

+  if (EFI_ERROR (Status)) {

+    DEBUG ((EFI_D_ERROR, "EmmcGetDeviceState(): Failed to get response of CMD13, Status=%r.\n", Status));

+    return Status;

+  }

+  if (Data & EMMC_SWITCH_ERROR) {

+    DEBUG ((EFI_D_ERROR, "EmmcGetDeviceState(): Failed to switch expected mode, Status=%r.\n", Status));

+    return EFI_DEVICE_ERROR;

+  }

+  *State = DEVICE_STATE(Data);

+  return EFI_SUCCESS;

+}

+

+STATIC

+EFI_STATUS

+EFIAPI

+EmmcSetEXTCSD (

+  IN MMC_HOST_INSTANCE     *MmcHostInstance,

+  UINT32                   ExtCmdIndex,

+  UINT32                   Value

+  )

+{

+  EFI_MMC_HOST_PROTOCOL *Host;

+  EMMC_DEVICE_STATE     State;

+  EFI_STATUS Status;

+  UINT32     Argument;

+

+  Host  = MmcHostInstance->MmcHost;

+  Argument = EMMC_CMD6_ARG_ACCESS(3) | EMMC_CMD6_ARG_INDEX(ExtCmdIndex) |

+             EMMC_CMD6_ARG_VALUE(Value) | EMMC_CMD6_ARG_CMD_SET(1);

+  Status = Host->SendCommand (Host, MMC_CMD6, Argument);

+  if (EFI_ERROR (Status)) {

+    DEBUG ((EFI_D_ERROR, "EmmcSetEXTCSD(): Failed to send CMD6, Status=%r.\n", Status));

+    return Status;

+  }

+  // Make sure device exiting prog mode

+  do {

+    Status = EmmcGetDeviceState (MmcHostInstance, &State);

+    if (EFI_ERROR (Status)) {

+      DEBUG ((EFI_D_ERROR, "EmmcSetEXTCSD(): Failed to get device state, Status=%r.\n", Status));

+      return Status;

+    }

+  } while (State == EMMC_PRG_STATE);

+  return EFI_SUCCESS;

+}

+

+STATIC

+EFI_STATUS

+EFIAPI

 EmmcIdentificationMode (

   IN MMC_HOST_INSTANCE     *MmcHostInstance,

   IN OCR_RESPONSE           Response

@@ -38,6 +138,7 @@
   EFI_MMC_HOST_PROTOCOL *Host;

   EFI_BLOCK_IO_MEDIA    *Media;

   EFI_STATUS Status;

+  EMMC_DEVICE_STATE     State;

   UINT32     RCA;

 

   Host  = MmcHostInstance->MmcHost;

@@ -84,6 +185,22 @@
     DEBUG ((EFI_D_ERROR, "EmmcIdentificationMode(): Card selection error, Status=%r.\n", Status));

   }

 

+  if (MMC_HOST_HAS_SETIOS(Host)) {

+    // Set 1-bit bus width

+    Status = Host->SetIos (Host, 0, 1, EMMCBACKWARD);

+    if (EFI_ERROR (Status)) {

+      DEBUG ((EFI_D_ERROR, "EmmcIdentificationMode(): Set 1-bit bus width error, Status=%r.\n", Status));

+      return Status;

+    }

+

+    // Set 1-bit bus width for EXTCSD

+    Status = EmmcSetEXTCSD (MmcHostInstance, EXTCSD_BUS_WIDTH, EMMC_BUS_WIDTH_1BIT);

+    if (EFI_ERROR (Status)) {

+      DEBUG ((EFI_D_ERROR, "EmmcIdentificationMode(): Set extcsd bus width error, Status=%r.\n", Status));

+      return Status;

+    }

+  }

+

   // Fetch ECSD

   Status = Host->SendCommand (Host, MMC_CMD8, RCA);

   if (EFI_ERROR (Status)) {

@@ -96,6 +213,15 @@
     return Status;

   }

 

+  // Make sure device exiting data mode

+  do {

+    Status = EmmcGetDeviceState (MmcHostInstance, &State);

+    if (EFI_ERROR (Status)) {

+      DEBUG ((EFI_D_ERROR, "EmmcIdentificationMode(): Failed to get device state, Status=%r.\n", Status));

+      return Status;

+    }

+  } while (State == EMMC_DATA_STATE);

+

   // Set up media

   Media->BlockSize = EMMC_CARD_SIZE; // 512-byte support is mandatory for eMMC cards

   Media->MediaId = MmcHostInstance->CardInfo.CIDData.PSN;

@@ -113,6 +239,57 @@
 

 STATIC

 EFI_STATUS

+InitializeEmmcDevice (

+  IN  MMC_HOST_INSTANCE   *MmcHostInstance

+  )

+{

+  EFI_MMC_HOST_PROTOCOL *Host;

+  EFI_STATUS Status = EFI_SUCCESS;

+  ECSD       *ECSDData;

+  UINT32     BusClockFreq, Idx;

+  UINT32     TimingMode[4] = {EMMCHS52DDR1V2, EMMCHS52DDR1V8, EMMCHS52, EMMCHS26};

+

+  Host  = MmcHostInstance->MmcHost;

+  ECSDData = &MmcHostInstance->CardInfo.ECSDData;

+  if (ECSDData->DEVICE_TYPE == EMMCBACKWARD)

+    return EFI_SUCCESS;

+

+  if (!MMC_HOST_HAS_SETIOS(Host)) {

+    return EFI_SUCCESS;

+  }

+  Status = EmmcSetEXTCSD (MmcHostInstance, EXTCSD_HS_TIMING, EMMC_TIMING_HS);

+  if (EFI_ERROR (Status)) {

+    DEBUG ((DEBUG_ERROR, "InitializeEmmcDevice(): Failed to switch high speed mode, Status:%r.\n", Status));

+    return Status;

+  }

+

+  for (Idx = 0; Idx < 4; Idx++) {

+    switch (TimingMode[Idx]) {

+    case EMMCHS52DDR1V2:

+    case EMMCHS52DDR1V8:

+    case EMMCHS52:

+      BusClockFreq = 52000000;

+      break;

+    case EMMCHS26:

+      BusClockFreq = 26000000;

+      break;

+    default:

+      return EFI_UNSUPPORTED;

+    }

+    Status = Host->SetIos (Host, BusClockFreq, 8, TimingMode[Idx]);

+    if (!EFI_ERROR (Status)) {

+      Status = EmmcSetEXTCSD (MmcHostInstance, EXTCSD_BUS_WIDTH, EMMC_BUS_WIDTH_DDR_8BIT);

+      if (EFI_ERROR (Status)) {

+        DEBUG ((DEBUG_ERROR, "InitializeEmmcDevice(): Failed to set EXTCSD bus width, Status:%r\n", Status));

+      }

+      return Status;

+    }

+  }

+  return Status;

+}

+

+STATIC

+EFI_STATUS

 InitializeSdMmcDevice (

   IN  MMC_HOST_INSTANCE   *MmcHostInstance

   )

@@ -431,9 +608,11 @@
 

   if (MmcHostInstance->CardInfo.CardType != EMMC_CARD) {

     Status = InitializeSdMmcDevice (MmcHostInstance);

-    if (EFI_ERROR (Status)) {

-      return Status;

-    }

+  } else {

+    Status = InitializeEmmcDevice (MmcHostInstance);

+  }

+  if (EFI_ERROR (Status)) {

+    return Status;

   }

 

   // Set Block Length