Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 8 Dec 2021 10:21:34 GMT
From:      Wojciech Macek <wma@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: de9c000cedfe - main - sdhci_fsl_fdt: Add support for HS200/HS400 modes
Message-ID:  <202112081021.1B8ALYH0041586@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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
 };
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202112081021.1B8ALYH0041586>