git: de9c000cedfe - main - sdhci_fsl_fdt: Add support for HS200/HS400 modes
Date: Wed, 08 Dec 2021 10:21:34 UTC
The branch main has been updated by wma: URL: https://cgit.FreeBSD.org/src/commit/?id=de9c000cedfe77c6da4929178fee571b1fd0cc80 commit de9c000cedfe77c6da4929178fee571b1fd0cc80 Author: Hubert Mazur <hum@semihalf.com> AuthorDate: 2021-11-03 09:38:05 +0000 Commit: Wojciech Macek <wma@FreeBSD.org> CommitDate: 2021-12-08 10:21:02 +0000 sdhci_fsl_fdt: Add support for HS200/HS400 modes The controller requires some custom logic to perform MMC tuning and to later switch to HS400 mode. Implement it supplying mmcbr_tune and sdhci_set_uhs_timing devmethods respectivly. Since the latter is called unconditionally when the ios is updated we need to keep track of the tuning state in sc and execute the HS400 switch logic only when required. Two HS200/HS400 related errata were implemented. 1. In HS400 modes the clock divisors are limited to 4, 8, 12. Apply it by falling back to the closes, higher divider when needed. 2. Hardware tuning procedure can sometimes fails. If that is the case fallback to the software tuning. Reviewed by: manu, mw Obtained from: Semihalf Sponsored by: Alstom Group Differential revision: https://reviews.freebsd.org/D33320 --- sys/dev/sdhci/sdhci_fsl_fdt.c | 536 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 534 insertions(+), 2 deletions(-) diff --git a/sys/dev/sdhci/sdhci_fsl_fdt.c b/sys/dev/sdhci/sdhci_fsl_fdt.c index d17811634353..41804f6c1a11 100644 --- a/sys/dev/sdhci/sdhci_fsl_fdt.c +++ b/sys/dev/sdhci/sdhci_fsl_fdt.c @@ -72,6 +72,12 @@ __FBSDID("$FreeBSD$"); #define SDHCI_FSL_PROT_CTRL_DMA_MASK (3 << 8) #define SDHCI_FSL_PROT_CTRL_VOLT_SEL (1 << 10) +#define SDHCI_FSL_IRQSTAT 0x30 +#define SDHCI_FSL_IRQSTAT_BRR (1 << 5) +#define SDHCI_FSL_IRQSTAT_CINTSEN (1 << 8) +#define SDHCI_FSL_IRQSTAT_RTE (1 << 12) +#define SDHCI_FSL_IRQSTAT_TNE (1 << 26) + #define SDHCI_FSL_SYS_CTRL 0x2c #define SDHCI_FSL_CLK_IPGEN (1 << 0) #define SDHCI_FSL_CLK_SDCLKEN (1 << 3) @@ -84,18 +90,54 @@ __FBSDID("$FreeBSD$"); #define SDHCI_FSL_WTMK_RD_512B (0 << 0) #define SDHCI_FSL_WTMK_WR_512B (0 << 15) +#define SDHCI_FSL_AUTOCERR 0x3C +#define SDHCI_FSL_AUTOCERR_UHMS_HS200 (3 << 17) +#define SDHCI_FSL_AUTOCERR_UHMS (7 << 18) +#define SDHCI_FSL_AUTOCERR_EXTN (1 << 22) +#define SDHCI_FSL_AUTOCERR_SMPCLKSEL (1 << 23) + #define SDHCI_FSL_HOST_VERSION 0xfc #define SDHCI_FSL_VENDOR_V23 0x13 + #define SDHCI_FSL_CAPABILITIES2 0x114 #define SDHCI_FSL_TBCTL 0x120 + +#define SDHCI_FSL_TBSTAT 0x124 #define SDHCI_FSL_TBCTL_TBEN (1 << 2) +#define SDHCI_FSL_TBCTL_HS400_EN (1 << 4) +#define SDHCI_FSL_TBCTL_SAMP_CMD_DQS (1 << 5) +#define SDHCI_FSL_TBCTL_HS400_WND_ADJ (1 << 6) +#define SDHCI_FSL_TBCTL_TB_MODE_MASK 0x3 +#define SDHCI_FSL_TBCTL_MODE_1 0 +#define SDHCI_FSL_TBCTL_MODE_2 1 +#define SDHCI_FSL_TBCTL_MODE_3 2 +#define SDHCI_FSL_TBCTL_MODE_SW 3 + +#define SDHCI_FSL_TBPTR 0x128 +#define SDHCI_FSL_TBPTR_WND_START 0x7F00 +#define SDHCI_FSL_TBPTR_WND_END 0x7F + +#define SDHCI_FSL_SDCLKCTL 0x144 +#define SDHCI_FSL_SDCLKCTL_CMD_CLK_CTL (1 << 15) +#define SDHCI_FSL_SDCLKCTL_LPBK_CLK_SEL (1 << 31) + +#define SDHCI_FSL_SDTIMINGCTL 0x148 +#define SDHCI_FSL_SDTIMINGCTL_FLW_CTL (1 << 15) + +#define SDHCI_FSL_DLLCFG0 0x160 +#define SDHCI_FSL_DLLCFG0_FREQ_SEL (1 << 27) +#define SDHCI_FSL_DLLCFG0_EN (1 << 31) + +#define SDHCI_FSL_DLLCFG1 0x164 +#define SDHCI_FSL_DLLCFG1_PULSE_STRETCH (1 << 31) #define SDHCI_FSL_DLLCFG1 0x164 #define SDHCI_FSL_DLLCFG1_PULSE_STRETCH (1 << 31) #define SDHCI_FSL_ESDHC_CTRL 0x40c #define SDHCI_FSL_ESDHC_CTRL_SNOOP (1 << 6) +#define SDHCI_FSL_ESDHC_CTRL_FAF (1 << 18) #define SDHCI_FSL_ESDHC_CTRL_CLK_DIV2 (1 << 19) #define SDHCI_FSL_CAN_VDD_MASK \ @@ -105,6 +147,24 @@ __FBSDID("$FreeBSD$"); #define SDHCI_FSL_UNRELIABLE_PULSE_DET (1 << 0) /* On some platforms switching voltage to 1.8V is not supported */ #define SDHCI_FSL_UNSUPP_1_8V (1 << 1) +/* Hardware tuning can fail, fallback to SW tuning in that case. */ +#define SDHCI_FSL_TUNING_ERRATUM_TYPE1 (1 << 2) +/* + * Pointer window might not be set properly on some platforms. + * Check window and perform SW tuning. + */ +#define SDHCI_FSL_TUNING_ERRATUM_TYPE2 (1 << 3) +/* + * In HS400 mode only 4, 8, 12 clock dividers can be used. + * Use the smallest value, bigger than requested in that case. + */ +#define SDHCI_FSL_HS400_LIMITED_CLK_DIV (1 << 4) + +#define SDHCI_FSL_HS400_FLAG (1 << 0) +#define SDHCI_FSL_SWITCH_TO_HS400_FLAG (1 << 1) +#define SDHCI_FSL_HS400_DONE (1 << 2) + +#define SDHCI_FSL_MAX_RETRIES 10 struct sdhci_fsl_fdt_softc { device_t dev; @@ -121,6 +181,8 @@ struct sdhci_fsl_fdt_softc { uint16_t sdclk_bits; struct mmc_helper fdt_helper; uint8_t vendor_ver; + uint32_t flags; + enum mmc_bus_timing last_mode; uint32_t (* read)(struct sdhci_fsl_fdt_softc *, bus_size_t); void (* write)(struct sdhci_fsl_fdt_softc *, bus_size_t, uint32_t); @@ -142,7 +204,8 @@ static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1028a_soc_data = { .quirks = SDHCI_QUIRK_DONT_SET_HISPD_BIT | SDHCI_QUIRK_BROKEN_AUTO_STOP | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK, .baseclk_div = 2, - .errata = SDHCI_FSL_UNRELIABLE_PULSE_DET, + .errata = SDHCI_FSL_UNRELIABLE_PULSE_DET | + SDHCI_FSL_HS400_LIMITED_CLK_DIV, }; static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1046a_soc_data = { @@ -226,7 +289,7 @@ static void fsl_sdhc_fdt_set_clock(struct sdhci_fsl_fdt_softc *sc, struct sdhci_slot *slot, uint16_t val) { - uint32_t prescale, div, val32; + uint32_t prescale, div, val32, div_ratio; sc->sdclk_bits = val & SDHCI_DIVIDERS_MASK; val32 = RD4(sc, SDHCI_CLOCK_CONTROL); @@ -249,6 +312,29 @@ fsl_sdhc_fdt_set_clock(struct sdhci_fsl_fdt_softc *sc, struct sdhci_slot *slot, sc->baseclk_hz, prescale, div); #endif + div_ratio = prescale * div; + + /* + * According to limited clock division erratum, clock dividers in hs400 + * can be only 4, 8 or 12 + */ + if ((sc->soc_data->errata & SDHCI_FSL_HS400_LIMITED_CLK_DIV) && + (sc->slot.host.ios.timing == bus_timing_mmc_hs400 || + (sc->flags & SDHCI_FSL_HS400_FLAG))) { + if (div_ratio <= 4) { + prescale = 4; + div = 1; + } else if (div_ratio <= 8) { + prescale = 4; + div = 2; + } else if (div_ratio <= 12) { + prescale = 4; + div = 3; + } else { + device_printf(sc->dev, "Unsupported clock divider.\n"); + } + } + prescale >>= 1; div -= 1; @@ -416,6 +502,24 @@ sdhci_fsl_fdt_write_2(device_t dev, struct sdhci_slot *slot, bus_size_t off, WR4(sc, SDHCI_TRANSFER_MODE, sc->cmd_and_mode); sc->cmd_and_mode = 0; return; + /* + * When switching to HS400 mode, setting these registers + * is required for successful tuning. + */ + case SDHCI_HOST_CONTROL2: + if ((val & SDHCI_CTRL2_MMC_HS400) && + (sc->flags & SDHCI_FSL_SWITCH_TO_HS400_FLAG)) { + val32 = RD4(sc, SDHCI_FSL_TBCTL); + val32 |= SDHCI_FSL_TBCTL_HS400_EN; + WR4(sc, SDHCI_FSL_TBCTL, val32); + + val32 = RD4(sc, SDHCI_FSL_SDCLKCTL); + val32 |= SDHCI_FSL_SDCLKCTL_CMD_CLK_CTL; + WR4(sc, SDHCI_FSL_SDCLKCTL, val32); + + } + + val &= ~SDHCI_CTRL2_MMC_HS400; default: val32 = RD4(sc, off & ~3); val32 &= ~(UINT16_MAX << (off & 3) * 8); @@ -658,6 +762,24 @@ sdhci_fsl_fdt_of_parse(device_t dev) } } +static int +sdhci_fsl_poll_register(struct sdhci_fsl_fdt_softc *sc, + uint32_t reg, uint32_t mask, int value) +{ + int retries; + + retries = SDHCI_FSL_MAX_RETRIES; + + while ((RD4(sc, reg) & mask) != value) { + if (!retries--) + return (ENXIO); + + DELAY(10); + } + + return (0); +} + static int sdhci_fsl_fdt_attach(device_t dev) { @@ -677,6 +799,8 @@ sdhci_fsl_fdt_attach(device_t dev) sc->soc_data = (struct sdhci_fsl_fdt_soc_data *)ocd_data; sc->dev = dev; sc->slot.quirks = sc->soc_data->quirks; + sc->flags = 0; + sc->last_mode = bus_timing_normal; host = &sc->slot.host; rid = 0; @@ -903,6 +1027,411 @@ sdhci_fsl_fdt_reset(device_t dev, struct sdhci_slot *slot, uint8_t mask) val &= ~SDHCI_FSL_TBCTL_TBEN; WR4(sc, SDHCI_FSL_TBCTL, val); } + + sc->flags &= ~SDHCI_FSL_HS400_DONE; +} + +static void +sdhci_fsl_switch_tuning_block(device_t dev, bool enable) +{ + struct sdhci_fsl_fdt_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + + reg = RD4(sc, SDHCI_FSL_TBCTL); + + if (enable) + reg |= SDHCI_FSL_TBCTL_TBEN; + else + reg &= ~SDHCI_FSL_TBCTL_TBEN; + + WR4(sc, SDHCI_FSL_TBCTL, reg); +} + +static int +sdhci_hw_tuning(struct sdhci_fsl_fdt_softc *sc, device_t bus, device_t child, + bool hs400) +{ + int error, retries; + uint32_t reg; + + retries = SDHCI_FSL_MAX_RETRIES; + + /* Set SYSCTL2[EXTN] to start the tuning procedure. */ + reg = RD4(sc, SDHCI_FSL_AUTOCERR); + reg |= SDHCI_FSL_AUTOCERR_EXTN; + WR4(sc, SDHCI_FSL_AUTOCERR, reg); + + while (true) { + /* Execute generic tuning to issue CMD19 and CMD21. */ + error = sdhci_generic_tune(bus, child, hs400); + if (error != 0) { + device_printf(bus, "Generic tuning failed.\n"); + return (error); + } + + /* + * Tuning is done after a corresponding interrupt + * is set. + */ + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_IRQSTAT, + SDHCI_FSL_IRQSTAT_BRR, SDHCI_FSL_IRQSTAT_BRR); + if (error) + device_printf(sc->dev, + "Timeout while waiting for hardware to finish tuning.\n"); + + /* Clear IRQSTAT[BRR] register. */ + reg = RD4(sc, SDHCI_FSL_IRQSTAT); + reg &= ~SDHCI_FSL_IRQSTAT_BRR; + WR4(sc, SDHCI_FSL_IRQSTAT, reg); + + /* Check SYSCTL2[EXTN] register. */ + if ((RD4(sc, SDHCI_FSL_AUTOCERR) & + SDHCI_FSL_AUTOCERR_EXTN) == 0) + break; + + if (!retries--) { + error = ENXIO; + device_printf(bus, + "Failed to execute HW tuning.\n"); + break; + } + + DELAY(10); + } + + /* Check SYSCTL2[SMPCLKSEL]. */ + reg = RD4(sc, SDHCI_FSL_AUTOCERR); + if (!(reg & SDHCI_FSL_AUTOCERR_SMPCLKSEL)) { + error = ENXIO; + device_printf(bus, + "Hardware tuning failed. Turning off tuning block\n"); + sdhci_fsl_switch_tuning_block(bus, false); + } else + error = 0; + + return (error); +} + +static int +sdhci_fsl_sw_tuning(struct sdhci_fsl_fdt_softc *sc, device_t bus, + device_t child, bool hs400, uint32_t wnd_start, uint32_t wnd_end) +{ + uint32_t start_p, end_p, reg; + int error; + + wnd_start = 5 * sc->soc_data->baseclk_div; + wnd_end = 3 * sc->soc_data->baseclk_div; + + reg = RD4(sc, SDHCI_FSL_TBCTL); + reg &= ~SDHCI_FSL_TBCTL_TB_MODE_MASK; + reg |= SDHCI_FSL_TBCTL_MODE_SW; + WR4(sc, SDHCI_FSL_TBCTL, reg); + + reg = RD4(sc, SDHCI_FSL_TBPTR); + start_p = reg | SDHCI_FSL_TBPTR_WND_START; + end_p = reg | SDHCI_FSL_TBPTR_WND_END; + + if (abs(start_p - end_p) > (4 * sc->soc_data->baseclk_div + 2)) { + wnd_start = 8 * sc->soc_data->baseclk_div; + wnd_end = 4 * sc->soc_data->baseclk_div; + } else { + wnd_start = 5 * sc->soc_data->baseclk_div; + wnd_end = 3 * sc->soc_data->baseclk_div; + } + + reg |= (wnd_start << 14); + reg |= (wnd_end << 6); + WR4(sc, SDHCI_FSL_TBPTR, reg); + + reg = RD4(sc, SDHCI_FSL_AUTOCERR); + reg |= SDHCI_FSL_AUTOCERR_EXTN | SDHCI_FSL_AUTOCERR_SMPCLKSEL; + WR4(sc, SDHCI_FSL_AUTOCERR, reg); + + error = sdhci_generic_tune(bus, child, hs400); + if (error != 0) { + device_printf(bus, + "Failed to execute generic tune while performing software tuning.\n"); + return (error); + } + + reg = RD4(sc, SDHCI_FSL_IRQSTAT); + reg &= ~SDHCI_FSL_IRQSTAT_BRR; + WR4(sc, SDHCI_FSL_IRQSTAT, reg); + + reg = RD4(sc, SDHCI_FSL_AUTOCERR); + if (!(reg & SDHCI_FSL_AUTOCERR_SMPCLKSEL)) { + /* Error occured, need to disable tuning block. */ + reg = RD4(sc, SDHCI_FSL_TBCTL); + reg &= ~SDHCI_FSL_TBCTL_TBEN; + WR4(sc, SDHCI_FSL_TBCTL, reg); + error = ENXIO; + } + + return (error); +} + +static int +sdhci_fsl_fdt_tune(device_t bus, device_t child, bool hs400) +{ + uint32_t wnd_start, wnd_end, clk_divider, reg; + struct sdhci_fsl_fdt_softc *sc; + struct sdhci_slot *slot; + int error; + + sc = device_get_softc(bus); + slot = device_get_ivars(child); + error = 0; + clk_divider = slot->max_clk / slot->clock; + + /* For tuning mode SD clock divider must be within 3 to 16. */ + if (clk_divider < 3 || clk_divider > 16) + return (ENXIO); + + if (hs400) + sc->flags |= SDHCI_FSL_HS400_FLAG; + + /* Disable clock. */ + fsl_sdhc_fdt_set_clock(sc, slot, sc->sdclk_bits); + + /* Wait for PRSSTAT[SDSTB] to be set by hardware. */ + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error != 0) + device_printf(bus, + "Timeout while waiting for hardware.\n"); + + /* Set ESDHCCTL[FAF] register. */ + reg = RD4(sc, SDHCI_FSL_ESDHC_CTRL); + reg |= SDHCI_FSL_ESDHC_CTRL_FAF; + WR4(sc, SDHCI_FSL_ESDHC_CTRL, reg); + + /* Wait for ESDHC[FAF] to be cleared by hardware. */ + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_ESDHC_CTRL, + SDHCI_FSL_ESDHC_CTRL_FAF, 0); + if (error) + device_printf(bus, + "Timeout while waiting for hardware.\n"); + + /* Set AUTOCERR[UHSM] register. */ + reg = RD4(sc, SDHCI_FSL_AUTOCERR); + reg &= ~SDHCI_FSL_AUTOCERR_UHMS; + reg |= SDHCI_FSL_AUTOCERR_UHMS_HS200; + WR4(sc, SDHCI_FSL_AUTOCERR, reg); + + /* Set TBCTL[TB_EN] register and program valid tuning mode. */ + reg = RD4(sc, SDHCI_FSL_TBCTL); + reg &= ~SDHCI_FSL_TBCTL_TB_MODE_MASK; + reg |= SDHCI_FSL_TBCTL_TBEN | SDHCI_FSL_TBCTL_MODE_3; + WR4(sc, SDHCI_FSL_TBCTL, reg); + + /* Enable clock. */ + fsl_sdhc_fdt_set_clock(sc, slot, SDHCI_CLOCK_CARD_EN | sc->sdclk_bits); + + /* Wait for clock to stabilize. */ + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error) + device_printf(bus, + "Timeout while waiting for clock to stabilize.\n"); + + /* Perform hardware tuning. */ + error = sdhci_hw_tuning(sc, bus, child, hs400); + + reg = RD4(sc, SDHCI_FSL_TBPTR); + wnd_start = reg & SDHCI_FSL_TBPTR_WND_START; + wnd_end = reg & SDHCI_FSL_TBPTR_WND_END; + + /* For erratum type2 affected platforms, check tuning pointer window. */ + if (sc->soc_data->errata & SDHCI_FSL_TUNING_ERRATUM_TYPE2) { + if (abs(wnd_start - wnd_end) > + (4 * sc->soc_data->baseclk_div + 2)) + error = ENXIO; + } + + if (error != 0 && + (sc->soc_data->errata & + (SDHCI_FSL_TUNING_ERRATUM_TYPE1 | + SDHCI_FSL_TUNING_ERRATUM_TYPE2))) { + /* If hardware tuning failed, try software tuning. */ + error = sdhci_fsl_sw_tuning(sc, bus, child, hs400, wnd_start, + wnd_end); + if (error != 0) + device_printf(bus, "Software tuning failed.\n"); + } + + if (error != 0) { + sdhci_fsl_switch_tuning_block(bus, false); + + return (error); + } + + if (hs400) { + reg = RD4(sc, SDHCI_FSL_SDTIMINGCTL); + reg |= SDHCI_FSL_SDTIMINGCTL_FLW_CTL; + WR4(sc, SDHCI_FSL_SDTIMINGCTL, reg); + + /* + * Tuning block needs to be disabled now. + * It will be enabled by set_uhs_signalling + */ + reg = RD4(sc, SDHCI_FSL_TBCTL); + reg &= ~SDHCI_FSL_TBCTL_TBEN; + WR4(sc, SDHCI_FSL_TBCTL, reg); + } + + return (0); +} + +static void +sdhci_fsl_disable_hs400_mode(device_t dev, struct sdhci_fsl_fdt_softc *sc) +{ + uint32_t reg; + int error; + + reg = RD4(sc, SDHCI_FSL_SDTIMINGCTL); + reg &= ~SDHCI_FSL_SDTIMINGCTL_FLW_CTL; + WR4(sc, SDHCI_FSL_SDTIMINGCTL, reg); + + reg = RD4(sc, SDHCI_FSL_SDCLKCTL); + reg &= ~SDHCI_FSL_SDCLKCTL_CMD_CLK_CTL; + WR4(sc, SDHCI_FSL_SDCLKCTL, reg); + + fsl_sdhc_fdt_set_clock(sc, &sc->slot, sc->sdclk_bits); + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error != 0) + device_printf(dev, + "Internal clock never stabilized.\n"); + + reg = RD4(sc, SDHCI_FSL_TBCTL); + reg &= ~SDHCI_FSL_TBCTL_HS400_EN; + WR4(sc, SDHCI_FSL_TBCTL, reg); + + fsl_sdhc_fdt_set_clock(sc, &sc->slot, SDHCI_CLOCK_CARD_EN | + sc->sdclk_bits); + + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error != 0) + device_printf(dev, + "Internal clock never stabilized.\n"); + + reg = RD4(sc, SDHCI_FSL_DLLCFG0); + reg &= ~(SDHCI_FSL_DLLCFG0_EN | + SDHCI_FSL_DLLCFG0_FREQ_SEL); + WR4(sc, SDHCI_FSL_DLLCFG0, reg); + + reg = RD4(sc, SDHCI_FSL_TBCTL); + reg &= ~SDHCI_FSL_TBCTL_HS400_WND_ADJ; + WR4(sc, SDHCI_FSL_TBCTL, reg); + + sdhci_fsl_switch_tuning_block(dev, false); +} + +static void +sdhci_fsl_enable_hs400_mode(device_t dev, struct sdhci_slot *slot, + struct sdhci_fsl_fdt_softc *sc) +{ + uint32_t reg; + int error; + + sdhci_fsl_switch_tuning_block(dev, true); + fsl_sdhc_fdt_set_clock(sc, slot, sc->sdclk_bits); + + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error != 0) + device_printf(dev, "Internal clock never stabilized.\n"); + + fsl_sdhc_fdt_set_clock(sc, slot, SDHCI_CLOCK_CARD_EN | + sc->sdclk_bits); + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error != 0) + device_printf(dev, "Internal clock never stabilized.\n"); + + reg = sc->read(sc, SDHCI_FSL_DLLCFG0); + reg |= SDHCI_FSL_DLLCFG0_EN | SDHCI_FSL_DLLCFG0_FREQ_SEL; + sc->write(sc, SDHCI_FSL_DLLCFG0, reg); + + DELAY(20); + + reg = sc->read(sc, SDHCI_FSL_TBCTL); + reg |= SDHCI_FSL_TBCTL_HS400_WND_ADJ; + sc->write(sc, SDHCI_FSL_TBCTL, reg); + + fsl_sdhc_fdt_set_clock(sc, slot, sc->sdclk_bits); + + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error != 0) + device_printf(dev, + "Timeout while waiting for clock to stabilize.\n"); + + reg = sc->read(sc, SDHCI_FSL_ESDHC_CTRL); + reg |= SDHCI_FSL_ESDHC_CTRL_FAF; + sc->write(sc, SDHCI_FSL_ESDHC_CTRL, reg); + + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_ESDHC_CTRL, + SDHCI_FSL_ESDHC_CTRL_FAF, 0); + if (error != 0) + device_printf(dev, + "Timeout while waiting for hardware.\n"); + + fsl_sdhc_fdt_set_clock(sc, slot, SDHCI_CLOCK_CARD_EN | + sc->sdclk_bits); + + error = sdhci_fsl_poll_register(sc, SDHCI_FSL_PRES_STATE, + SDHCI_FSL_PRES_SDSTB, SDHCI_FSL_PRES_SDSTB); + if (error != 0) + device_printf(dev, + "Timeout while waiting for clock to stabilize.\n"); + + sdhci_generic_set_uhs_timing(dev, slot); + sc->flags = SDHCI_FSL_HS400_DONE; +} + +static void +sdhci_fsl_fdt_set_uhs_timing(device_t dev, struct sdhci_slot *slot) +{ + struct sdhci_fsl_fdt_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + + if (sc->flags & SDHCI_FSL_HS400_DONE) + return; + + reg = RD4(sc, SDHCI_FSL_TBCTL); + + if (slot->host.ios.timing == bus_timing_hs && + (!(sc->flags & SDHCI_FSL_SWITCH_TO_HS400_FLAG)) && + sc->last_mode >= bus_timing_mmc_hs200) { + sdhci_fsl_switch_tuning_block(dev, false); + + if (reg & SDHCI_FSL_TBCTL_HS400_EN) + sdhci_fsl_disable_hs400_mode(dev, sc); + + sc->flags |= SDHCI_FSL_SWITCH_TO_HS400_FLAG; + + return; + } + + if ((slot->host.ios.timing == bus_timing_mmc_hs400) && + sc->flags & SDHCI_FSL_SWITCH_TO_HS400_FLAG) { + if (sc->last_mode < bus_timing_uhs_sdr50) { + sc->last_mode = sc->slot.host.ios.timing; + return; + } + + sdhci_fsl_enable_hs400_mode(dev, slot, sc); + + } else + sdhci_generic_set_uhs_timing(dev, slot); + + sc->last_mode = sc->slot.host.ios.timing; } static const device_method_t sdhci_fsl_fdt_methods[] = { @@ -922,6 +1451,8 @@ static const device_method_t sdhci_fsl_fdt_methods[] = { DEVMETHOD(mmcbr_release_host, sdhci_generic_release_host), DEVMETHOD(mmcbr_switch_vccq, sdhci_fsl_fdt_switch_vccq), DEVMETHOD(mmcbr_update_ios, sdhci_fsl_fdt_update_ios), + DEVMETHOD(mmcbr_tune, sdhci_fsl_fdt_tune), + DEVMETHOD(mmcbr_retune, sdhci_generic_retune), /* SDHCI accessors. */ DEVMETHOD(sdhci_read_1, sdhci_fsl_fdt_read_1), @@ -934,6 +1465,7 @@ static const device_method_t sdhci_fsl_fdt_methods[] = { DEVMETHOD(sdhci_write_multi_4, sdhci_fsl_fdt_write_multi_4), DEVMETHOD(sdhci_get_card_present, sdhci_fsl_fdt_get_card_present), DEVMETHOD(sdhci_reset, sdhci_fsl_fdt_reset), + DEVMETHOD(sdhci_set_uhs_timing, sdhci_fsl_fdt_set_uhs_timing), DEVMETHOD_END };