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
This commit is contained in:
Hubert Mazur 2021-11-03 10:38:05 +01:00 committed by Wojciech Macek
parent ec9333c3aa
commit de9c000ced

View file

@ -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
};