sdmmc(4): check and retry bus width change

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

sdmmc(4): check and retry bus width change

Patrick Wildt-3
Hi,

it seems like some eMMCs are not capable of doing 8-bit operation,
even if the controller supports it.  I was questioning our drivers
first, but it looks like it's the same on Linux.  In the case that
8-bit doesn't work, they seem to fall back to lower values to make
that HW work.

This diff implements a mechanism that tries 8-bit, if available,
then 4-bit and in the end falls back to 1-bit.  This makes my HW
work, but I would like to have this tested by a broader audience.

Apparently there's a "bus test" command, but it isn't implemented
on all host controllers.  Hence I simply try to read the EXT_CSD
to make sure the transfer works.

For testing, a print like

    printf("%s: using %u-bit width\n", DEVNAME(sc), width);

could be added at line 928.

What could possible regressions be?  The width could become smaller
then previously.  This would reduce the read/write transfer speed.
Also it's possible that eMMCs are not recognized/initialized anymore.

What could possible improvements be?  eMMCs that previously didn't
work now work, with at least 1-bit or 4-bit wide transfers.

Please note that this only works for eMMCs.  SD cards are *not* using
this code path.  SD cards have a different initialization code path.

Please report any changes or non-changes.  If nothing changes, that's
perfect.

Patrick

diff --git a/sys/dev/sdmmc/sdmmc_mem.c b/sys/dev/sdmmc/sdmmc_mem.c
index 59bcb1b4a11..5856b9bb1b3 100644
--- a/sys/dev/sdmmc/sdmmc_mem.c
+++ b/sys/dev/sdmmc/sdmmc_mem.c
@@ -56,6 +56,8 @@ int sdmmc_mem_signal_voltage(struct sdmmc_softc *, int);
 
 int sdmmc_mem_sd_init(struct sdmmc_softc *, struct sdmmc_function *);
 int sdmmc_mem_mmc_init(struct sdmmc_softc *, struct sdmmc_function *);
+int sdmmc_mem_mmc_select_bus_width(struct sdmmc_softc *,
+    struct sdmmc_function *, int);
 int sdmmc_mem_single_read_block(struct sdmmc_function *, int, u_char *,
  size_t);
 int sdmmc_mem_read_block_subr(struct sdmmc_function *, bus_dmamap_t,
@@ -908,31 +910,20 @@ sdmmc_mem_mmc_init(struct sdmmc_softc *sc, struct sdmmc_function *sf)
     ext_csd[EXT_CSD_CARD_TYPE]);
  }
 
- if (ISSET(sc->sc_caps, SMC_CAPS_8BIT_MODE)) {
+ if (ISSET(sc->sc_caps, SMC_CAPS_8BIT_MODE) &&
+    sdmmc_mem_mmc_select_bus_width(sc, sf, 8) == 0)
  width = 8;
- value = EXT_CSD_BUS_WIDTH_8;
- } else if (ISSET(sc->sc_caps, SMC_CAPS_4BIT_MODE)) {
+ else if (ISSET(sc->sc_caps, SMC_CAPS_4BIT_MODE) &&
+    sdmmc_mem_mmc_select_bus_width(sc, sf, 4) == 0)
  width = 4;
- value = EXT_CSD_BUS_WIDTH_4;
- } else {
- width = 1;
- value = EXT_CSD_BUS_WIDTH_1;
- }
-
- if (width != 1) {
- error = sdmmc_mem_mmc_switch(sf, EXT_CSD_CMD_SET_NORMAL,
-    EXT_CSD_BUS_WIDTH, value);
- if (error == 0)
- error = sdmmc_chip_bus_width(sc->sct,
-    sc->sch, width);
- else {
+ else {
+ error = sdmmc_mem_mmc_select_bus_width(sc, sf, 1);
+ if (error != 0) {
  DPRINTF(("%s: can't change bus width"
     " (%d bit)\n", DEVNAME(sc), width));
  return error;
  }
-
- /* XXXX: need bus test? (using by CMD14 & CMD19) */
- sdmmc_delay(10000);
+ width = 1;
  }
 
  if (timing != SDMMC_TIMING_LEGACY) {
@@ -1041,6 +1032,59 @@ sdmmc_mem_mmc_init(struct sdmmc_softc *sc, struct sdmmc_function *sf)
  return error;
 }
 
+int
+sdmmc_mem_mmc_select_bus_width(struct sdmmc_softc *sc, struct sdmmc_function *sf,
+    int width)
+{
+ u_int8_t ext_csd[512];
+ int error, value;
+
+ switch (width) {
+ case 8:
+ value = EXT_CSD_BUS_WIDTH_8;
+ break;
+ case 4:
+ value = EXT_CSD_BUS_WIDTH_4;
+ break;
+ case 1:
+ value = EXT_CSD_BUS_WIDTH_1;
+ break;
+ default:
+ printf("%s: invalid bus width\n", DEVNAME(sc));
+ return EINVAL;
+ }
+
+ error = sdmmc_mem_mmc_switch(sf, EXT_CSD_CMD_SET_NORMAL,
+    EXT_CSD_BUS_WIDTH, value);
+ if (error != 0) {
+ DPRINTF(("%s: can't change card bus width"
+    " (%d bit)\n", DEVNAME(sc), width));
+ return error;
+ }
+
+ error = sdmmc_chip_bus_width(sc->sct,
+    sc->sch, width);
+ if (error != 0) {
+ DPRINTF(("%s: can't change host bus width"
+    " (%d bit)\n", DEVNAME(sc), width));
+ return error;
+ }
+
+ /* XXX: need bus test? (using by CMD14 & CMD19) */
+ sdmmc_delay(10000);
+
+ /* XXX: read EXT_CSD again, alternative to bus test */
+ error = sdmmc_mem_send_cxd_data(sc,
+    MMC_SEND_EXT_CSD, ext_csd, sizeof(ext_csd));
+ if (error != 0) {
+ DPRINTF(("%s: can't read EXT_CSD with bus width"
+    " (%d bit)\n", DEVNAME(sc), width));
+ return error;
+ }
+
+ return error;
+}
+
 /*
  * Get or set the card's memory OCR value (SD or MMC).
  */