mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Benoît Monin" <benoit.monin@bootlin.com>
To: Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	Jun Chang <jun.chang@metanoia-comm.com>,
	 Paul Walmsley <pjw@kernel.org>,
	Palmer Dabbelt <palmer@dabbelt.com>,
	 Albert Ou <aou@eecs.berkeley.edu>,
	Alexandre Ghiti <alex@ghiti.fr>,
	 Philipp Zabel <p.zabel@pengutronix.de>,
	Sebastian Reichel <sre@kernel.org>,
	 Vinod Koul <vkoul@kernel.org>,
	Neil Armstrong <neil.armstrong@linaro.org>,
	 Manivannan Sadhasivam <mani@kernel.org>,
	Conor Dooley <conor@kernel.org>,
	 Jonathan Cameron <jic23@kernel.org>,
	 Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>,
	 Thomas Gleixner <tglx@kernel.org>, Radu Rendec <radu@rendec.net>,
	 Samuel Holland <samuel.holland@sifive.com>,
	 Ben Zong-You Xie <ben717@andestech.com>,
	 Daniel Lezcano <daniel.lezcano@kernel.org>,
	Ulf Hansson <ulfh@kernel.org>,
	 Jisheng Zhang <jszhang@kernel.org>,
	CL Wang <cl634@andestech.com>,  Mark Brown <broonie@kernel.org>,
	 Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	 Wim Van Sebroeck <wim@linux-watchdog.org>,
	 Guenter Roeck <linux@roeck-us.net>,
	Adrian Hunter <adrian.hunter@intel.com>,
	 Minas Harutyunyan <hminas@synopsys.com>
Cc: "Thomas Petazzoni" <thomas.petazzoni@bootlin.com>,
	"Randolph Sheng-Kai Lin(林聖凱)" <randolph@andestech.com>,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-riscv@lists.infradead.org, linux-pm@vger.kernel.org,
	linux-phy@lists.infradead.org,
	"Paul Walmsley" <paul.walmsley@sifive.com>,
	linux-mmc@vger.kernel.org, linux-spi@vger.kernel.org,
	linux-usb@vger.kernel.org, linux-watchdog@vger.kernel.org,
	"Benoît Monin" <benoit.monin@bootlin.com>
Subject: [PATCH 21/25] mmc: sdhci-of-dwcmshc: add MT2824 tuning and HS400 support
Date: Fri, 06 Nov 2026 10:30:18 +0100	[thread overview]
Message-ID: <20261106-mt2824-v1-21-86087eaf5af4@bootlin.com> (raw)
In-Reply-To: <20261106-mt2824-v1-0-86087eaf5af4@bootlin.com>

The MT2824 databook names SDR104 and HS200 as the modes that need
tuning. The controller has a 128-tap sampling delay line, so scan every
center phase code, find the longest run of phases that return a good
tuning block, and select the middle phase. Do not tune at DDR50, where
the scan fails at every phase and the fixed sampling clock works.

During the scan, a CMD19 timeout at a bad phase can be followed by a
data CRC interrupt after the request has completed. Drop data interrupts
that arrive with no data request while scanning.

HS400 needs the PHY DLL locked on the card clock. Configure and enable
the DLL by hand after the clock is set, and reset the CMD and DATA
lines.

Originally-by: Jun Chang <jun.chang@metanoia-comm.com>
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
 drivers/mmc/host/sdhci-of-dwcmshc.c | 201 +++++++++++++++++++++++++++++++++++-
 1 file changed, 200 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c
index cb248fc572e2..324ae93295af 100644
--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
+++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
@@ -10,6 +10,7 @@
 #include <linux/acpi.h>
 #include <linux/arm-smccc.h>
 #include <linux/bitfield.h>
+#include <linux/bitmap.h>
 #include <linux/clk.h>
 #include <linux/clk-provider.h>
 #include <linux/dma-mapping.h>
@@ -86,8 +87,12 @@
 #define CV18XX_TUNE_STEP			1
 #define CV18XX_RETRY_TUNING_MAX			50
 
-/* Metanoia MT2824: HOST_CTRL3 sampling edge and PHY pad settings */
+/* Metanoia MT2824: HOST_CTRL3 sampling edge bits and software tuning */
 #define MT2824_HOST_CTRL3_NEGEDGE_DATAOUT	BIT(1)
+#define MT2824_AT_STAT_CENTER_PH_MASK		GENMASK(6, 0)
+#define MT2824_TUNING_PHASES			128	/* DWC_MSHC_DL_CW = 7 */
+/* narrowest passing window accepted, ~8 % of the 128-tap delay line */
+#define MT2824_TUNING_MIN_WINDOW		10
 /* 1.8 V eMMC probe attempts before falling back to the SD slot */
 #define MT2824_PROBE_ATTEMPTS			2
 /* PHY pad settings of the MT2824 pad ring */
@@ -98,6 +103,10 @@
 #define MT2824_PHY_TXSLEW_P_STROBE		0x3
 #define MT2824_PHY_SMPLDL_CNFG			0x0c
 #define MT2824_PHY_ATDL_CNFG			0x0c
+#define MT2824_PHY_DLL_CNFG1			0x20
+#define MT2824_PHY_DLLDL_CNFG			0x60
+#define MT2824_PHY_DLL_LOCKED			BIT(0)
+#define MT2824_PHY_DLL_LOCK_ERR			BIT(1)
 
 /* Rockchip specific Registers */
 #define DWCMSHC_EMMC_DLL_CTRL		0x800
@@ -245,6 +254,7 @@
 #define DLL_ERROR_STS			BIT(1)
 
 #define FLAG_IO_FIXED_1V8	BIT(0)
+#define FLAG_SW_TUNING		BIT(1)	/* MT2824: tuning scan in progress */
 
 #define BOUNDARY_OK(addr, len) \
 	((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
@@ -2271,6 +2281,38 @@ static int mt2824_phy_init(struct sdhci_host *host, bool v1v8)
 	return 0;
 }
 
+/* HS400 needs the PHY DLL locked on the card clock; lock it by hand */
+static void mt2824_set_dll(struct sdhci_host *host)
+{
+	u16 clk;
+	u8 status;
+	int ret;
+
+	clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+	clk &= ~SDHCI_CLOCK_CARD_EN;
+	sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+	sdhci_writeb(host, MT2824_PHY_DLL_CNFG1, PHY_DLL_CNFG1_R);
+	sdhci_writeb(host, MT2824_PHY_DLLDL_CNFG, PHY_DLLDL_CNFG_R);
+
+	sdhci_enable_clk(host, clk);
+	usleep_range(10, 20);
+
+	sdhci_writeb(host, PHY_DLL_CTRL_ENABLE, PHY_DLL_CTRL_R);
+	ret = read_poll_timeout(sdhci_readb, status,
+				status & MT2824_PHY_DLL_LOCKED, 10, 150000,
+				false, host, PHY_DLL_STATUS_R);
+	if (ret) {
+		dev_err(mmc_dev(host->mmc), "PHY DLL did not lock: 0x%02x\n",
+			status);
+		return;
+	}
+
+	if (status & MT2824_PHY_DLL_LOCK_ERR)
+		dev_err(mmc_dev(host->mmc), "PHY DLL locked with error: 0x%02x\n",
+			status);
+}
+
 static bool mt2824_wait_clk_stable(struct sdhci_host *host, u16 *clk,
 				   const char *what)
 {
@@ -2332,6 +2374,11 @@ static void mt2824_set_clock(struct sdhci_host *host, unsigned int clock)
 	if (clock)
 		mt2824_enable_clk(host, sdhci_calc_clk(host, clock,
 						       &mmc->actual_clock));
+
+	if (clock && host->timing == MMC_TIMING_MMC_HS400) {
+		mt2824_set_dll(host);
+		dwcmshc_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+	}
 }
 
 /* The controller must know that the card is an eMMC (EMMC_CTRL_R, 16 bit) */
@@ -2460,6 +2507,156 @@ static void mt2824_hw_reset(struct sdhci_host *host)
 	sdhci_enable_clk(host, clk);
 }
 
+/*
+ * Software tuning: try every center phase code of the tuning delay line,
+ * find the longest run of phases that return a good tuning block and
+ * select its middle. The sampling clock only follows the delay line while
+ * SAMPLE_CLK_SEL (SDHCI_CTRL_TUNED_CLK) is set, so set it for the scan and
+ * keep it; SW_TUNE_EN also stays set, so the selected phase code stays in
+ * control of the delay line.
+ */
+static int mt2824_set_center_phase(struct sdhci_host *host, u32 at_stat_reg,
+				   unsigned int phase)
+{
+	u32 at_stat;
+	int ret;
+	u16 clk;
+
+	clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+	clk &= ~SDHCI_CLOCK_CARD_EN;
+	sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+	at_stat = sdhci_readl(host, at_stat_reg);
+	at_stat &= ~MT2824_AT_STAT_CENTER_PH_MASK;
+	at_stat |= FIELD_PREP(MT2824_AT_STAT_CENTER_PH_MASK, phase);
+	sdhci_writel(host, at_stat, at_stat_reg);
+	/*
+	 * This configuration has no DL2 busy flag: read the phase code back,
+	 * then wait for the worst case of 180 hclk cycles of the delay line
+	 * (2 us covers an hclk of 90 MHz or more).
+	 */
+	ret = read_poll_timeout(sdhci_readl, at_stat,
+				FIELD_GET(MT2824_AT_STAT_CENTER_PH_MASK, at_stat) == phase,
+				0, 100, false, host, at_stat_reg);
+	if (ret)
+		dev_err(mmc_dev(host->mmc), "tuning phase %u not taken\n", phase);
+	udelay(2);
+
+	sdhci_enable_clk(host, clk);
+
+	return ret;
+}
+
+static int mt2824_execute_tuning(struct sdhci_host *host, u32 opcode)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host);
+	u32 at_ctrl_reg = dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL;
+	u32 at_stat_reg = dwc_priv->vendor_specific_area1 + DWCMSHC_AT_STAT;
+	DECLARE_BITMAP(good, MT2824_TUNING_PHASES);
+	unsigned int start, end, phase, best_start = 0, best_len = 0;
+	u32 at_ctrl;
+	u16 ctrl2;
+	int ret;
+
+	/*
+	 * The controller databook names SDR104 (and HS200) as the modes that
+	 * need tuning. At DDR50 the scan fails at every phase on this
+	 * controller, while the fixed sampling clock works: do not tune.
+	 */
+	if (host->timing == MMC_TIMING_UHS_DDR50)
+		return 0;
+
+	WRITE_ONCE(dwc_priv->flags, dwc_priv->flags | FLAG_SW_TUNING);
+
+	sdhci_reset_tuning(host);
+	bitmap_zero(good, MT2824_TUNING_PHASES);
+
+	/*
+	 * AT_CTRL_R must not be written while SAMPLE_CLK_SEL is set: enable
+	 * software tuning first, then select the tuned sampling clock.
+	 */
+	at_ctrl = sdhci_readl(host, at_ctrl_reg);
+	sdhci_writel(host, at_ctrl | AT_CTRL_SW_TUNE_EN, at_ctrl_reg);
+	ctrl2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+	sdhci_writew(host, ctrl2 | SDHCI_CTRL_TUNED_CLK, SDHCI_HOST_CONTROL2);
+	/*
+	 * That HOST_CONTROL2 write breaks the response of the next command:
+	 * send one tuning command for nothing before the scan.
+	 */
+	mmc_send_tuning(host->mmc, opcode, NULL);
+	host->ops->reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+
+	for (phase = 0; phase < MT2824_TUNING_PHASES; phase++) {
+		ret = mt2824_set_center_phase(host, at_stat_reg, phase);
+		if (ret)
+			goto fail;
+		if (!mmc_send_tuning(host->mmc, opcode, NULL))
+			set_bit(phase, good);
+		host->ops->reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+	}
+
+	/* the sample delay line is linear, so the phase codes do not wrap */
+	for_each_set_bitrange(start, end, good, MT2824_TUNING_PHASES) {
+		if (end - start > best_len) {
+			best_start = start;
+			best_len = end - start;
+		}
+	}
+	if (best_len < MT2824_TUNING_MIN_WINDOW) {
+		dev_err(mmc_dev(host->mmc), "tuning failed, best window %u\n",
+			best_len);
+		ret = -EIO;
+		goto fail;
+	}
+	phase = best_start + best_len / 2;
+	/* a window at an end of the delay line may be wider than measured */
+	if (!best_start || best_start + best_len == MT2824_TUNING_PHASES)
+		dev_dbg(mmc_dev(host->mmc), "tuning window %u+%u ends at the delay line end\n",
+			best_start, best_len);
+
+	ret = mt2824_set_center_phase(host, at_stat_reg, phase);
+	if (ret)
+		goto fail;
+	dev_dbg(mmc_dev(host->mmc), "tuning phase %u (window %u+%u), AT_STAT %08x\n",
+		phase, best_start, best_len, sdhci_readl(host, at_stat_reg));
+	sdhci_end_tuning(host);
+	WRITE_ONCE(dwc_priv->flags, dwc_priv->flags & ~FLAG_SW_TUNING);
+
+	return 0;
+
+fail:
+	/* clear SAMPLE_CLK_SEL before AT_CTRL_R is written again */
+	sdhci_reset_tuning(host);
+	sdhci_writel(host, at_ctrl, at_ctrl_reg);
+	host->ops->reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+	sdhci_end_tuning(host);
+	WRITE_ONCE(dwc_priv->flags, dwc_priv->flags & ~FLAG_SW_TUNING);
+	return ret;
+}
+
+/*
+ * A tuning command at a bad phase can time out while the card still sends
+ * the tuning block. Its data interrupt then comes when no data transfer is
+ * in progress (host->data is NULL); once the request reset cleared
+ * pending_reset, sdhci counts it as an unexpected interrupt. Drop such
+ * data interrupts while the tuning scan runs. A late interrupt that comes
+ * after the next tuning command started fails that phase only.
+ */
+static u32 mt2824_irq(struct sdhci_host *host, u32 intmask)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host);
+	u32 stray = intmask & SDHCI_INT_DATA_MASK;
+
+	if (!(READ_ONCE(dwc_priv->flags) & FLAG_SW_TUNING) || host->data || !stray)
+		return intmask;
+
+	sdhci_writel(host, stray, SDHCI_INT_STATUS);
+
+	return intmask & ~stray;
+}
+
 /*
  * With no card type from the DT, look for an eMMC only, at 1.8 V: an eMMC
  * answers CMD1, an SD card does not, so no 3.3 V signal reaches an eMMC.
@@ -2672,6 +2869,8 @@ static const struct sdhci_ops sdhci_dwcmshc_mt2824_ops = {
 	.reset			= mt2824_reset,
 	.hw_reset		= mt2824_hw_reset,
 	.adma_write_desc	= dwcmshc_adma_write_desc,
+	.platform_execute_tuning = mt2824_execute_tuning,
+	.irq			= mt2824_irq,
 };
 
 static const struct dwcmshc_pltfm_data sdhci_dwcmshc_mt2824_pdata = {

-- 
2.56.0


  parent reply	other threads:[~2026-11-06  9:33 UTC|newest]

Thread overview: 41+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-06  9:29 [PATCH 00/25] riscv: add support for the Metanoia MT2824 SoC and MT5824 EVB Benoît Monin
2026-11-06  9:29 ` [PATCH 01/25] dt-bindings: vendor-prefixes: add Metanoia Communications Benoît Monin
2026-11-06 11:17   ` Krzysztof Kozlowski
2026-11-06 11:39     ` Mark Brown
2026-11-06 11:50       ` Krzysztof Kozlowski
2026-11-06  9:29 ` [PATCH 02/25] dt-bindings: riscv: add Metanoia SoC-based boards Benoît Monin
2026-11-06  9:30 ` [PATCH 03/25] dt-bindings: reset: add Metanoia MT2824 Benoît Monin
2026-11-06 11:19   ` Krzysztof Kozlowski
2026-11-06  9:30 ` [PATCH 04/25] dt-bindings: power: reset: add Metanoia MT2824 reboot Benoît Monin
2026-11-06 11:23   ` Krzysztof Kozlowski
2026-11-06  9:30 ` [PATCH 05/25] dt-bindings: phy: add Metanoia USB2 PHY Benoît Monin
2026-11-06  9:30 ` [PATCH 06/25] dt-bindings: cache: andestech,ax45mp-cache: add MT2824 compatible Benoît Monin
2026-11-06  9:30 ` [PATCH 07/25] dt-bindings: interrupt-controller: sifive,plic-1.0.0: add Metanoia MT2824 Benoît Monin
2026-11-06  9:30 ` [PATCH 08/25] dt-bindings: interrupt-controller: andestech,plicsw: " Benoît Monin
2026-11-06  9:30 ` [PATCH 09/25] dt-bindings: timer: andestech,plmt0: " Benoît Monin
2026-11-06  9:30 ` [PATCH 10/25] dt-bindings: mmc: snps,dwcmshc-sdhci: " Benoît Monin
2026-11-06  9:30 ` [PATCH 11/25] dt-bindings: spi: andestech,ae350-spi: make DMA and interrupts optional Benoît Monin
2026-11-06 11:11   ` Mark Brown
2026-11-06 11:49   ` Krzysztof Kozlowski
2026-11-06  9:30 ` [PATCH 12/25] dt-bindings: spi: andestech,ae350-spi: add Metanoia MT2824 Benoît Monin
2026-11-06  9:30 ` [PATCH 13/25] dt-bindings: usb: dwc2: " Benoît Monin
2026-11-06 11:54   ` Krzysztof Kozlowski
2026-11-06  9:30 ` [PATCH 14/25] dt-bindings: watchdog: andestech,ae350-wdt: " Benoît Monin
2026-11-06  9:30 ` [PATCH 15/25] riscv: add ARCH_METANOIA Kconfig entry Benoît Monin
2026-11-06  9:30 ` [PATCH 16/25] reset: simple: add Metanoia MT2824 support Benoît Monin
2026-11-06  9:30 ` [PATCH 17/25] spi: atcspi200: add optional interrupt support Benoît Monin
2026-11-06  9:30 ` [PATCH 18/25] spi: atcspi200: allow building for Metanoia SoCs Benoît Monin
2026-11-06  9:30 ` [PATCH 19/25] watchdog: atcwdt200: " Benoît Monin
2026-11-06  9:30 ` [PATCH 20/25] mmc: sdhci-of-dwcmshc: add MT2824 support Benoît Monin
2026-11-06  9:30 ` Benoît Monin [this message]
2026-11-06  9:30 ` [PATCH 22/25] phy: metanoia: add USB2 PHY driver Benoît Monin
2026-11-06  9:56   ` Philipp Zabel
2026-11-06  9:30 ` [PATCH 23/25] usb: dwc2: add support for Metanoia MT2824 Benoît Monin
2026-11-06 11:52   ` Krzysztof Kozlowski
2026-11-06 11:52     ` Krzysztof Kozlowski
2026-11-06  9:30 ` [PATCH 24/25] riscv: add Metanoia MT2824 SoC and MT5824 EVB support Benoît Monin
2026-11-06 10:04   ` Philipp Zabel
2026-11-06 11:21   ` Krzysztof Kozlowski
2026-11-06  9:30 ` [PATCH 25/25] MAINTAINERS: add RISC-V Metanoia SoC support Benoît Monin
2026-11-06 11:22   ` Krzysztof Kozlowski
2026-11-06 11:15 ` [PATCH 00/25] riscv: add support for the Metanoia MT2824 SoC and MT5824 EVB Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261106-mt2824-v1-21-86087eaf5af4@bootlin.com \
    --to=benoit.monin@bootlin.com \
    --cc=adrian.hunter@intel.com \
    --cc=alex@ghiti.fr \
    --cc=aou@eecs.berkeley.edu \
    --cc=ben717@andestech.com \
    --cc=broonie@kernel.org \
    --cc=cl634@andestech.com \
    --cc=conor+dt@kernel.org \
    --cc=conor@kernel.org \
    --cc=daniel.lezcano@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=hminas@synopsys.com \
    --cc=jic23@kernel.org \
    --cc=jszhang@kernel.org \
    --cc=jun.chang@metanoia-comm.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mmc@vger.kernel.org \
    --cc=linux-phy@lists.infradead.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-riscv@lists.infradead.org \
    --cc=linux-spi@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=linux-watchdog@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=mani@kernel.org \
    --cc=neil.armstrong@linaro.org \
    --cc=p.zabel@pengutronix.de \
    --cc=palmer@dabbelt.com \
    --cc=paul.walmsley@sifive.com \
    --cc=pjw@kernel.org \
    --cc=prabhakar.mahadev-lad.rj@bp.renesas.com \
    --cc=radu@rendec.net \
    --cc=randolph@andestech.com \
    --cc=robh@kernel.org \
    --cc=samuel.holland@sifive.com \
    --cc=sre@kernel.org \
    --cc=tglx@kernel.org \
    --cc=thomas.petazzoni@bootlin.com \
    --cc=ulfh@kernel.org \
    --cc=vkoul@kernel.org \
    --cc=wim@linux-watchdog.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®