[PATCH v2 0/6] mmc: sdhci-of-at91: add LAN969x support

WARM3d

20 messages, 5 authors, 3d ago · open the first message on its own page

[PATCH v2 0/6] mmc: sdhci-of-at91: add LAN969x support

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-07 13:26:45

This series adds support for the two SDMMC controllers in the Microchip
LAN969x family. It also enables the onboard QSPI NOR and eMMC storage on
the EV23X71A evaluation board.

The LAN969x SDMMC controller uses the same internally generated base clock
layout as SAM9X60. However, gating its clocks during runtime suspend leaves
the controller unable to complete software resets or stabilise its internal
clock. Add a generic SoC data option for keeping clocks enabled, while
keeping capability and preset restoration independent from clock
enablement. LAN969x then selects this option through its match data.

Describe both controllers using the fabric clock for the bus interface and
their respective generated clocks for the controller core. Use clock rates
suitable for the supported SD and eMMC modes, including 8-bit eMMC DDR on
the EV23X71A.

The QSPI enablement is retained from v1 so both onboard non-volatile
storage devices are described by this series.

Tested on LAN969x hardware with an 8-bit eMMC operating in DDR mode.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

Changes in v2:
* Add driver support for the LAN969x SDMMC controller.
* Add a preparatory option for controllers that must keep clocks enabled.
* Restore capabilities and presets without unbalancing clock enable counts.
* Use the fabric clock for the SDMMC bus interface clock.
* Configure SDMMC1 with an exact 50 MHz generated clock.
* Keep the SDMMC nodes ordered by unit address.
* Remove the unnecessary maximum-frequency property.
* Retain Conor Dooley's Acked-by on the binding patch.
* Rebase onto next-20260904.

Robert Marko (6):
  dt-bindings: mmc: atmel,sama5d2-sdhci: add LAN969x compatible
  mmc: sdhci-of-at91: add option to keep clocks enabled
  mmc: sdhci-of-at91: add LAN969x support
  arm64: dts: microchip: lan969x: add SDMMC nodes
  arm64: dts: microchip: ev23x71a: enable QSPI
  arm64: dts: microchip: ev23x71a: enable eMMC

 .../bindings/mmc/atmel,sama5d2-sdhci.yaml     |  1 +
 arch/arm64/boot/dts/microchip/lan9691.dtsi    | 24 ++++++++++
 .../boot/dts/microchip/lan9696-ev23x71a.dts   | 25 ++++++++++
 drivers/mmc/host/sdhci-of-at91.c              | 47 ++++++++++++-------
 4 files changed, 81 insertions(+), 16 deletions(-)

-- 
2.55.0

[PATCH v2 1/6] dt-bindings: mmc: atmel,sama5d2-sdhci: add LAN969x compatible

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-07 13:26:41

The SDHCI controller in the LAN969x family uses the same register layout
as the SAM9X60 implementation. Add the microchip,lan9691-sdhci compatible
with microchip,sam9x60-sdhci as its fallback.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
Changes in v2:
* Clarify the relationship between the LAN969x and SAM9X60 controllers.
* Add Conor Dooley's Acked-by.

 Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml | 1 +
 1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml b/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml
index ba75623b7778..af1c66a1f882 100644
--- a/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml
+++ b/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml
@@ -21,6 +21,7 @@ properties:
           - microchip,sam9x60-sdhci
       - items:
           - enum:
+              - microchip,lan9691-sdhci
               - microchip,sam9x7-sdhci
               - microchip,sama7d65-sdhci
               - microchip,sama7g5-sdhci
-- 
2.55.0

[PATCH v2 2/6] mmc: sdhci-of-at91: add option to keep clocks enabled

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-07 13:26:48

sdhci_at91_set_clks_presets() both enables the controller clocks and
programs its capabilities and preset registers. This prevents callers from
restoring the registers without changing the clock enable counts.

Move clock enablement to callers and add a SoC data flag for controllers
that must keep their clocks enabled. Use it in the runtime PM paths while
keeping register restoration separate from clock enablement.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

 drivers/mmc/host/sdhci-of-at91.c | 39 +++++++++++++++++++-------------
 1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index 7c4ac65f247d..b7e2a89da348 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -38,6 +38,7 @@
 struct sdhci_at91_soc_data {
 	const struct sdhci_pltfm_data *pdata;
 	bool baseclk_is_generated_internally;
+	bool keep_clks_on;
 	unsigned int divider_for_baseclk;
 };
 
@@ -164,7 +165,7 @@ static const struct of_device_id sdhci_at91_dt_match[] = {
 };
 MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);
 
-static int sdhci_at91_set_clks_presets(struct device *dev)
+static void sdhci_at91_set_clks_presets(struct device *dev)
 {
 	struct sdhci_host *host = dev_get_drvdata(dev);
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -174,7 +175,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
 	unsigned int			gck_rate, clk_base_rate;
 	unsigned int			preset_div;
 
-	clk_prepare_enable(priv->hclock);
 	caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
 	caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
 
@@ -222,11 +222,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
 	preset_div = DIV_ROUND_UP(gck_rate, 50000000) - 1;
 	writew(SDHCI_AT91_PRESET_COMMON_CONF | preset_div,
 	       host->ioaddr + SDHCI_PRESET_FOR_DDR50);
-
-	clk_prepare_enable(priv->mainck);
-	clk_prepare_enable(priv->gck);
-
-	return 0;
 }
 
 static int sdhci_at91_suspend(struct device *dev)
@@ -254,9 +249,11 @@ static int sdhci_at91_runtime_suspend(struct device *dev)
 	if (host->tuning_mode != SDHCI_TUNING_MODE_3)
 		mmc_retune_needed(host->mmc);
 
-	clk_disable_unprepare(priv->gck);
-	clk_disable_unprepare(priv->hclock);
-	clk_disable_unprepare(priv->mainck);
+	if (!priv->soc_data->keep_clks_on) {
+		clk_disable_unprepare(priv->gck);
+		clk_disable_unprepare(priv->hclock);
+		clk_disable_unprepare(priv->mainck);
+	}
 
 	return 0;
 }
@@ -269,14 +266,23 @@ static int sdhci_at91_runtime_resume(struct device *dev)
 	int ret;
 
 	if (priv->restore_needed) {
-		ret = sdhci_at91_set_clks_presets(dev);
-		if (ret)
-			return ret;
+		if (!priv->soc_data->keep_clks_on)
+			clk_prepare_enable(priv->hclock);
+
+		sdhci_at91_set_clks_presets(dev);
+
+		if (!priv->soc_data->keep_clks_on) {
+			clk_prepare_enable(priv->mainck);
+			clk_prepare_enable(priv->gck);
+		}
 
 		priv->restore_needed = false;
 		goto out;
 	}
 
+	if (priv->soc_data->keep_clks_on)
+		goto out;
+
 	ret = clk_prepare_enable(priv->mainck);
 	if (ret) {
 		dev_err(dev, "can't enable mainck\n");
@@ -344,9 +350,10 @@ static int sdhci_at91_probe(struct platform_device *pdev)
 		return dev_err_probe(&pdev->dev, PTR_ERR(priv->gck),
 				     "failed to get multclk\n");
 
-	ret = sdhci_at91_set_clks_presets(&pdev->dev);
-	if (ret)
-		return ret;
+	clk_prepare_enable(priv->hclock);
+	sdhci_at91_set_clks_presets(&pdev->dev);
+	clk_prepare_enable(priv->mainck);
+	clk_prepare_enable(priv->gck);
 
 	priv->restore_needed = false;
 
-- 
2.55.0

[PATCH v2 3/6] mmc: sdhci-of-at91: add LAN969x support

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-07 13:26:49

LAN969x uses the same internally generated base clock layout as SAM9X60,
but its SDMMC controller stops responding when runtime PM gates its clocks.
Software resets then fail to complete and the internal SDHCI clock never
stabilises, causing subsequent I/O requests to time out.

Add LAN969x-specific SoC data using the SAM9X60 clock layout and select the
option to leave its clocks enabled across runtime suspend.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

 drivers/mmc/host/sdhci-of-at91.c | 8 ++++++++
 1 file changed, 8 insertions(+)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index b7e2a89da348..e7affca8da25 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -158,9 +158,17 @@ static const struct sdhci_at91_soc_data soc_data_sam9x60 = {
 	.divider_for_baseclk = 2,
 };
 
+static const struct sdhci_at91_soc_data soc_data_lan969x = {
+	.pdata = &sdhci_sama5d2_pdata,
+	.baseclk_is_generated_internally = true,
+	.keep_clks_on = true,
+	.divider_for_baseclk = 2,
+};
+
 static const struct of_device_id sdhci_at91_dt_match[] = {
 	{ .compatible = "atmel,sama5d2-sdhci", .data = &soc_data_sama5d2 },
 	{ .compatible = "microchip,sam9x60-sdhci", .data = &soc_data_sam9x60 },
+	{ .compatible = "microchip,lan9691-sdhci", .data = &soc_data_lan969x },
 	{}
 };
 MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);
-- 
2.55.0

[PATCH v2 5/6] arm64: dts: microchip: ev23x71a: enable QSPI

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-07 13:26:54

Enable the QSPI controller and describe the onboard SPI NOR flash.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

 .../arm64/boot/dts/microchip/lan9696-ev23x71a.dts | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
index 4012ea7d07bb..985ca31e74e7 100644
--- a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
+++ b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
@@ -463,6 +463,21 @@ phy27: phy@27 {
 	};
 };
 
+&qspi0 {
+	status = "okay";
+
+	flash@0 {
+		compatible = "jedec,spi-nor";
+		reg = <0>;
+		spi-max-frequency = <100000000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		spi-tx-bus-width = <1>;
+		spi-rx-bus-width = <4>;
+		m25p,fast-read;
+	};
+};
+
 &serdes {
 	status = "okay";
 };
-- 
2.55.0

[PATCH v2 4/6] arm64: dts: microchip: lan969x: add SDMMC nodes

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-07 13:26:54

Add nodes for both SDMMC controllers. Connect their bus interface clock to
the fabric clock and their core clock to the corresponding generated clock.

Configure SDMMC0 with a 100 MHz generated clock for 50 MHz eMMC DDR
operation. Configure SDMMC1 with an exact 50 MHz generated clock so the
integer base clock encoded in the SDHCI capabilities remains accurate and
the identification clock does not exceed 400 kHz.

Keep both nodes ordered by unit address.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
---
Changes in v2:
* Use the fabric clock for the SDMMC bus interface clock.
* Configure SDMMC1 with an exact 50 MHz generated clock.
* Move the SDMMC1 node to preserve unit-address ordering.
* Explain the selected clock rates in the commit description.

 arch/arm64/boot/dts/microchip/lan9691.dtsi | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9691.dtsi b/arch/arm64/boot/dts/microchip/lan9691.dtsi
index a8541c6a098d..5e330d5fab50 100644
--- a/arch/arm64/boot/dts/microchip/lan9691.dtsi
+++ b/arch/arm64/boot/dts/microchip/lan9691.dtsi
@@ -414,6 +414,18 @@ qspi0: spi@e0804000 {
 			status = "disabled";
 		};
 
+		sdmmc0: mmc@e0830000 {
+			compatible = "microchip,lan9691-sdhci",
+				     "microchip,sam9x60-sdhci";
+			reg = <0xe0830000 0x00000300>;
+			interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&fabric_clk>, <&clks GCK_ID_SDMMC0>;
+			clock-names = "hclock", "multclk";
+			assigned-clocks = <&clks GCK_ID_SDMMC0>;
+			assigned-clock-rates = <100000000>;
+			status = "disabled";
+		};
+
 		qspi2: spi@e0834000 {
 			compatible = "microchip,lan9691-qspi";
 			reg = <0xe0834000 0x00000100>,
@@ -429,6 +441,18 @@ qspi2: spi@e0834000 {
 			status = "disabled";
 		};
 
+		sdmmc1: mmc@e0838000 {
+			compatible = "microchip,lan9691-sdhci",
+				     "microchip,sam9x60-sdhci";
+			reg = <0xe0838000 0x00000300>;
+			interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&fabric_clk>, <&clks GCK_ID_SDMMC1>;
+			clock-names = "hclock", "multclk";
+			assigned-clocks = <&clks GCK_ID_SDMMC1>;
+			assigned-clock-rates = <50000000>;
+			status = "disabled";
+		};
+
 		reset: reset-controller@e201000c {
 			compatible = "microchip,lan9691-switch-reset",
 				     "microchip,lan966x-switch-reset";
-- 
2.55.0

[PATCH v2 6/6] arm64: dts: microchip: ev23x71a: enable eMMC

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-07 13:27:00

Enable the non-removable eMMC connected to SDMMC0. Configure its 8-bit bus
for 1.8 V DDR operation.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
---
Changes in v2:
* Remove the unnecessary maximum-frequency property.
* Expand the commit description.

 arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts | 10 ++++++++++
 1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
index 985ca31e74e7..89a161b87d6f 100644
--- a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
+++ b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
@@ -478,6 +478,16 @@ flash@0 {
 	};
 };
 
+&sdmmc0 {
+	pinctrl-0 = <&emmc_sd_pins>;
+	pinctrl-names = "default";
+	bus-width = <8>;
+	mmc-ddr-1_8v;
+	non-removable;
+	disable-wp;
+	status = "okay";
+};
+
 &serdes {
 	status = "okay";
 };
-- 
2.55.0

Re: [PATCH v2 4/6] arm64: dts: microchip: lan969x: add SDMMC nodes

From: Aubin Constans <aubin.constans@microchip.com>
Date: 2026-09-07 13:58:28

On 07/09/2026 15:25, Robert Marko wrote:
Add nodes for both SDMMC controllers. Connect their bus interface clock to
the fabric clock and their core clock to the corresponding generated clock.

Configure SDMMC0 with a 100 MHz generated clock for 50 MHz eMMC DDR
operation. Configure SDMMC1 with an exact 50 MHz generated clock so the
integer base clock encoded in the SDHCI capabilities remains accurate and
the identification clock does not exceed 400 kHz.

Keep both nodes ordered by unit address.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
Reviewed-by: Aubin Constans <aubin.constans@microchip.com>
quoted hunk
---
Changes in v2:
* Use the fabric clock for the SDMMC bus interface clock.
* Configure SDMMC1 with an exact 50 MHz generated clock.
* Move the SDMMC1 node to preserve unit-address ordering.
* Explain the selected clock rates in the commit description.

  arch/arm64/boot/dts/microchip/lan9691.dtsi | 24 ++++++++++++++++++++++
  1 file changed, 24 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9691.dtsi b/arch/arm64/boot/dts/microchip/lan9691.dtsi
index a8541c6a098d..5e330d5fab50 100644
--- a/arch/arm64/boot/dts/microchip/lan9691.dtsi
+++ b/arch/arm64/boot/dts/microchip/lan9691.dtsi
@@ -414,6 +414,18 @@ qspi0: spi@e0804000 {
                         status = "disabled";
                 };

+               sdmmc0: mmc@e0830000 {
+                       compatible = "microchip,lan9691-sdhci",
+                                    "microchip,sam9x60-sdhci";
+                       reg = <0xe0830000 0x00000300>;
+                       interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
+                       clocks = <&fabric_clk>, <&clks GCK_ID_SDMMC0>;
+                       clock-names = "hclock", "multclk";
+                       assigned-clocks = <&clks GCK_ID_SDMMC0>;
+                       assigned-clock-rates = <100000000>;
+                       status = "disabled";
+               };
+
                 qspi2: spi@e0834000 {
                         compatible = "microchip,lan9691-qspi";
                         reg = <0xe0834000 0x00000100>,
@@ -429,6 +441,18 @@ qspi2: spi@e0834000 {
                         status = "disabled";
                 };

+               sdmmc1: mmc@e0838000 {
+                       compatible = "microchip,lan9691-sdhci",
+                                    "microchip,sam9x60-sdhci";
+                       reg = <0xe0838000 0x00000300>;
+                       interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>;
+                       clocks = <&fabric_clk>, <&clks GCK_ID_SDMMC1>;
+                       clock-names = "hclock", "multclk";
+                       assigned-clocks = <&clks GCK_ID_SDMMC1>;
+                       assigned-clock-rates = <50000000>;
+                       status = "disabled";
+               };
+
                 reset: reset-controller@e201000c {
                         compatible = "microchip,lan9691-switch-reset",
                                      "microchip,lan966x-switch-reset";
--
2.55.0

Re: [PATCH v2 6/6] arm64: dts: microchip: ev23x71a: enable eMMC

From: Aubin Constans <aubin.constans@microchip.com>
Date: 2026-09-07 14:19:01

On 07/09/2026 15:25, Robert Marko wrote:
Enable the non-removable eMMC connected to SDMMC0. Configure its 8-bit bus
for 1.8 V DDR operation.
Adding the no-sd and no-sdio properties may be considered to speed
initialization up a bit.
Signed-off-by: Robert Marko <robert.marko@sartura.hr>
Reviewed-by: Aubin Constans <aubin.constans@microchip.com>
quoted hunk
---
Changes in v2:
* Remove the unnecessary maximum-frequency property.
* Expand the commit description.

  arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts | 10 ++++++++++
  1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
index 985ca31e74e7..89a161b87d6f 100644
--- a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
+++ b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
@@ -478,6 +478,16 @@ flash@0 {
         };
  };

+&sdmmc0 {
+       pinctrl-0 = <&emmc_sd_pins>;
+       pinctrl-names = "default";
+       bus-width = <8>;
+       mmc-ddr-1_8v;
+       non-removable;
+       disable-wp;
+       status = "okay";
+};
+
  &serdes {
         status = "okay";
  };
--
2.55.0

Re: [PATCH v2 2/6] mmc: sdhci-of-at91: add option to keep clocks enabled

From: Adrian Hunter <adrian.hunter@intel.com>
Date: 2026-09-09 10:31:33

On 07/09/2026 16:25, Robert Marko wrote:
quoted hunk
sdhci_at91_set_clks_presets() both enables the controller clocks and
programs its capabilities and preset registers. This prevents callers from
restoring the registers without changing the clock enable counts.

Move clock enablement to callers and add a SoC data flag for controllers
that must keep their clocks enabled. Use it in the runtime PM paths while
keeping register restoration separate from clock enablement.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

 drivers/mmc/host/sdhci-of-at91.c | 39 +++++++++++++++++++-------------
 1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index 7c4ac65f247d..b7e2a89da348 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -38,6 +38,7 @@
 struct sdhci_at91_soc_data {
 	const struct sdhci_pltfm_data *pdata;
 	bool baseclk_is_generated_internally;
+	bool keep_clks_on;
It can be a bit easier to read when conditions do not have to be
inverted, like

	if (priv->soc_data->suspend_clks)

instead of:
	
	if (!priv->soc_data->keep_clks_on)

quoted hunk
 	unsigned int divider_for_baseclk;
 };
 
@@ -164,7 +165,7 @@ static const struct of_device_id sdhci_at91_dt_match[] = {
 };
 MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);
 
-static int sdhci_at91_set_clks_presets(struct device *dev)
+static void sdhci_at91_set_clks_presets(struct device *dev)
 {
 	struct sdhci_host *host = dev_get_drvdata(dev);
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -174,7 +175,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
 	unsigned int			gck_rate, clk_base_rate;
 	unsigned int			preset_div;
 
-	clk_prepare_enable(priv->hclock);
Might just as well pass in a parameter and:

	if (prepare_clks)
		clk_prepare_enable(priv->hclock);
quoted hunk
 	caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
 	caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
 
@@ -222,11 +222,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
 	preset_div = DIV_ROUND_UP(gck_rate, 50000000) - 1;
 	writew(SDHCI_AT91_PRESET_COMMON_CONF | preset_div,
 	       host->ioaddr + SDHCI_PRESET_FOR_DDR50);
-
-	clk_prepare_enable(priv->mainck);
-	clk_prepare_enable(priv->gck);
Ditto
quoted hunk
-
-	return 0;
 }
 
 static int sdhci_at91_suspend(struct device *dev)
@@ -254,9 +249,11 @@ static int sdhci_at91_runtime_suspend(struct device *dev)
 	if (host->tuning_mode != SDHCI_TUNING_MODE_3)
 		mmc_retune_needed(host->mmc);
 
-	clk_disable_unprepare(priv->gck);
-	clk_disable_unprepare(priv->hclock);
-	clk_disable_unprepare(priv->mainck);
+	if (!priv->soc_data->keep_clks_on) {
+		clk_disable_unprepare(priv->gck);
+		clk_disable_unprepare(priv->hclock);
+		clk_disable_unprepare(priv->mainck);
+	}
 
 	return 0;
 }
@@ -269,14 +266,23 @@ static int sdhci_at91_runtime_resume(struct device *dev)
 	int ret;
 
 	if (priv->restore_needed) {
-		ret = sdhci_at91_set_clks_presets(dev);
-		if (ret)
-			return ret;
+		if (!priv->soc_data->keep_clks_on)
+			clk_prepare_enable(priv->hclock);
+
+		sdhci_at91_set_clks_presets(dev);
+
+		if (!priv->soc_data->keep_clks_on) {
+			clk_prepare_enable(priv->mainck);
+			clk_prepare_enable(priv->gck);
+		}
Then that all becomes just:

		sdhci_at91_set_clks_presets(dev, !priv->soc_data->keep_clks_on);
quoted hunk
 
 		priv->restore_needed = false;
 		goto out;
 	}
 
+	if (priv->soc_data->keep_clks_on)
+		goto out;
+
 	ret = clk_prepare_enable(priv->mainck);
 	if (ret) {
 		dev_err(dev, "can't enable mainck\n");
@@ -344,9 +350,10 @@ static int sdhci_at91_probe(struct platform_device *pdev)
 		return dev_err_probe(&pdev->dev, PTR_ERR(priv->gck),
 				     "failed to get multclk\n");
 
-	ret = sdhci_at91_set_clks_presets(&pdev->dev);
-	if (ret)
-		return ret;
+	clk_prepare_enable(priv->hclock);
+	sdhci_at91_set_clks_presets(&pdev->dev);
+	clk_prepare_enable(priv->mainck);
+	clk_prepare_enable(priv->gck);
And that all becomes just:

	sdhci_at91_set_clks_presets(&pdev->dev, true);
 
 	priv->restore_needed = false;
 

Re: [PATCH v2 3/6] mmc: sdhci-of-at91: add LAN969x support

From: Adrian Hunter <adrian.hunter@intel.com>
Date: 2026-09-09 10:42:15

On 07/09/2026 16:25, Robert Marko wrote:
LAN969x uses the same internally generated base clock layout as SAM9X60,
but its SDMMC controller stops responding when runtime PM gates its clocks.
Software resets then fail to complete and the internal SDHCI clock never
stabilises, causing subsequent I/O requests to time out.
Is this a known issue of the SoC?  Is there perhaps a hardware reset
for the controller that would bring it back to life?

Does that mean unbind and rebind of the device from the driver
also does not work?
quoted hunk
Add LAN969x-specific SoC data using the SAM9X60 clock layout and select the
option to leave its clocks enabled across runtime suspend.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

 drivers/mmc/host/sdhci-of-at91.c | 8 ++++++++
 1 file changed, 8 insertions(+)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index b7e2a89da348..e7affca8da25 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -158,9 +158,17 @@ static const struct sdhci_at91_soc_data soc_data_sam9x60 = {
 	.divider_for_baseclk = 2,
 };
 
+static const struct sdhci_at91_soc_data soc_data_lan969x = {
+	.pdata = &sdhci_sama5d2_pdata,
+	.baseclk_is_generated_internally = true,
Should be a comment here explaining the issue that needs
keep_clks_on = true
+	.keep_clks_on = true,
+	.divider_for_baseclk = 2,
+};
+
 static const struct of_device_id sdhci_at91_dt_match[] = {
 	{ .compatible = "atmel,sama5d2-sdhci", .data = &soc_data_sama5d2 },
 	{ .compatible = "microchip,sam9x60-sdhci", .data = &soc_data_sam9x60 },
+	{ .compatible = "microchip,lan9691-sdhci", .data = &soc_data_lan969x },
 	{}
 };
 MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);

Re: [PATCH v2 2/6] mmc: sdhci-of-at91: add option to keep clocks enabled

From: Aubin Constans <aubin.constans@microchip.com>
Date: 2026-09-14 13:47:19

Hi Robert,

On 07/09/2026 15:25, Robert Marko wrote:
sdhci_at91_set_clks_presets() both enables the controller clocks and
programs its capabilities and preset registers. This prevents callers from
restoring the registers without changing the clock enable counts.

Move clock enablement to callers and add a SoC data flag for controllers
that must keep their clocks enabled. Use it in the runtime PM paths while
keeping register restoration separate from clock enablement.
This change has been required for the SAMA7G54 MPU already, and implemented by
two complementary commits:

https://github.com/linux4microchip/linux/commit/763a3c05c8f9d0feb279c6f308fa5d48d14e07d3
https://github.com/linux4microchip/linux/commit/b5bbc888fb39471d46fe0a4ff28f69533c265dd0

At least they have been validated by test campaigns.
If that existing work seemed OK for you and the LAN969x, then I could squash
these two commits and submit the result.
quoted hunk
Signed-off-by: Robert Marko <robert.marko@sartura.hr>

  drivers/mmc/host/sdhci-of-at91.c | 39 +++++++++++++++++++-------------
  1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index 7c4ac65f247d..b7e2a89da348 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -38,6 +38,7 @@
  struct sdhci_at91_soc_data {
         const struct sdhci_pltfm_data *pdata;
         bool baseclk_is_generated_internally;
+       bool keep_clks_on;
         unsigned int divider_for_baseclk;
  };
@@ -164,7 +165,7 @@ static const struct of_device_id sdhci_at91_dt_match[] = {
  };
  MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);

-static int sdhci_at91_set_clks_presets(struct device *dev)
+static void sdhci_at91_set_clks_presets(struct device *dev)
  {
         struct sdhci_host *host = dev_get_drvdata(dev);
         struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -174,7 +175,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
         unsigned int                    gck_rate, clk_base_rate;
         unsigned int                    preset_div;

-       clk_prepare_enable(priv->hclock);
         caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
         caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
@@ -222,11 +222,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
         preset_div = DIV_ROUND_UP(gck_rate, 50000000) - 1;
         writew(SDHCI_AT91_PRESET_COMMON_CONF | preset_div,
                host->ioaddr + SDHCI_PRESET_FOR_DDR50);
-
-       clk_prepare_enable(priv->mainck);
-       clk_prepare_enable(priv->gck);
-
-       return 0;
  }

  static int sdhci_at91_suspend(struct device *dev)
@@ -254,9 +249,11 @@ static int sdhci_at91_runtime_suspend(struct device *dev)
         if (host->tuning_mode != SDHCI_TUNING_MODE_3)
                 mmc_retune_needed(host->mmc);

-       clk_disable_unprepare(priv->gck);
-       clk_disable_unprepare(priv->hclock);
-       clk_disable_unprepare(priv->mainck);
+       if (!priv->soc_data->keep_clks_on) {
+               clk_disable_unprepare(priv->gck);
+               clk_disable_unprepare(priv->hclock);
+               clk_disable_unprepare(priv->mainck);
+       }

         return 0;
  }
@@ -269,14 +266,23 @@ static int sdhci_at91_runtime_resume(struct device *dev)
         int ret;

         if (priv->restore_needed) {
-               ret = sdhci_at91_set_clks_presets(dev);
-               if (ret)
-                       return ret;
+               if (!priv->soc_data->keep_clks_on)
+                       clk_prepare_enable(priv->hclock);
+
+               sdhci_at91_set_clks_presets(dev);
+
+               if (!priv->soc_data->keep_clks_on) {
+                       clk_prepare_enable(priv->mainck);
+                       clk_prepare_enable(priv->gck);
+               }

                 priv->restore_needed = false;
                 goto out;
         }

+       if (priv->soc_data->keep_clks_on)
+               goto out;
+
         ret = clk_prepare_enable(priv->mainck);
         if (ret) {
                 dev_err(dev, "can't enable mainck\n");
@@ -344,9 +350,10 @@ static int sdhci_at91_probe(struct platform_device *pdev)
                 return dev_err_probe(&pdev->dev, PTR_ERR(priv->gck),
                                      "failed to get multclk\n");

-       ret = sdhci_at91_set_clks_presets(&pdev->dev);
-       if (ret)
-               return ret;
+       clk_prepare_enable(priv->hclock);
+       sdhci_at91_set_clks_presets(&pdev->dev);
+       clk_prepare_enable(priv->mainck);
+       clk_prepare_enable(priv->gck);

         priv->restore_needed = false;

--
2.55.0

Re: [PATCH v2 2/6] mmc: sdhci-of-at91: add option to keep clocks enabled

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-15 11:32:18

On Mon, Sep 14, 2026 at 3:46 PM Aubin Constans
[off-list ref] wrote:
Hi Robert,

On 07/09/2026 15:25, Robert Marko wrote:
quoted
sdhci_at91_set_clks_presets() both enables the controller clocks and
programs its capabilities and preset registers. This prevents callers from
restoring the registers without changing the clock enable counts.

Move clock enablement to callers and add a SoC data flag for controllers
that must keep their clocks enabled. Use it in the runtime PM paths while
keeping register restoration separate from clock enablement.
This change has been required for the SAMA7G54 MPU already, and implemented by
two complementary commits:

https://github.com/linux4microchip/linux/commit/763a3c05c8f9d0feb279c6f308fa5d48d14e07d3
https://github.com/linux4microchip/linux/commit/b5bbc888fb39471d46fe0a4ff28f69533c265dd0

At least they have been validated by test campaigns.
If that existing work seemed OK for you and the LAN969x, then I could squash
these two commits and submit the result.
Hi Aubin,
Yeah, I originally used those in our tree as well before trying to
simplify things for upstreaming.

Regards,
Robert
quoted
Signed-off-by: Robert Marko <robert.marko@sartura.hr>

  drivers/mmc/host/sdhci-of-at91.c | 39 +++++++++++++++++++-------------
  1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index 7c4ac65f247d..b7e2a89da348 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -38,6 +38,7 @@
  struct sdhci_at91_soc_data {
         const struct sdhci_pltfm_data *pdata;
         bool baseclk_is_generated_internally;
+       bool keep_clks_on;
         unsigned int divider_for_baseclk;
  };
@@ -164,7 +165,7 @@ static const struct of_device_id sdhci_at91_dt_match[] = {
  };
  MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);

-static int sdhci_at91_set_clks_presets(struct device *dev)
+static void sdhci_at91_set_clks_presets(struct device *dev)
  {
         struct sdhci_host *host = dev_get_drvdata(dev);
         struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -174,7 +175,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
         unsigned int                    gck_rate, clk_base_rate;
         unsigned int                    preset_div;

-       clk_prepare_enable(priv->hclock);
         caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
         caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
@@ -222,11 +222,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
         preset_div = DIV_ROUND_UP(gck_rate, 50000000) - 1;
         writew(SDHCI_AT91_PRESET_COMMON_CONF | preset_div,
                host->ioaddr + SDHCI_PRESET_FOR_DDR50);
-
-       clk_prepare_enable(priv->mainck);
-       clk_prepare_enable(priv->gck);
-
-       return 0;
  }

  static int sdhci_at91_suspend(struct device *dev)
@@ -254,9 +249,11 @@ static int sdhci_at91_runtime_suspend(struct device *dev)
         if (host->tuning_mode != SDHCI_TUNING_MODE_3)
                 mmc_retune_needed(host->mmc);

-       clk_disable_unprepare(priv->gck);
-       clk_disable_unprepare(priv->hclock);
-       clk_disable_unprepare(priv->mainck);
+       if (!priv->soc_data->keep_clks_on) {
+               clk_disable_unprepare(priv->gck);
+               clk_disable_unprepare(priv->hclock);
+               clk_disable_unprepare(priv->mainck);
+       }

         return 0;
  }
@@ -269,14 +266,23 @@ static int sdhci_at91_runtime_resume(struct device *dev)
         int ret;

         if (priv->restore_needed) {
-               ret = sdhci_at91_set_clks_presets(dev);
-               if (ret)
-                       return ret;
+               if (!priv->soc_data->keep_clks_on)
+                       clk_prepare_enable(priv->hclock);
+
+               sdhci_at91_set_clks_presets(dev);
+
+               if (!priv->soc_data->keep_clks_on) {
+                       clk_prepare_enable(priv->mainck);
+                       clk_prepare_enable(priv->gck);
+               }

                 priv->restore_needed = false;
                 goto out;
         }

+       if (priv->soc_data->keep_clks_on)
+               goto out;
+
         ret = clk_prepare_enable(priv->mainck);
         if (ret) {
                 dev_err(dev, "can't enable mainck\n");
@@ -344,9 +350,10 @@ static int sdhci_at91_probe(struct platform_device *pdev)
                 return dev_err_probe(&pdev->dev, PTR_ERR(priv->gck),
                                      "failed to get multclk\n");

-       ret = sdhci_at91_set_clks_presets(&pdev->dev);
-       if (ret)
-               return ret;
+       clk_prepare_enable(priv->hclock);
+       sdhci_at91_set_clks_presets(&pdev->dev);
+       clk_prepare_enable(priv->mainck);
+       clk_prepare_enable(priv->gck);

         priv->restore_needed = false;

--
2.55.0

-- 
Robert Marko
Staff Embedded Linux Engineer
Sartura d.d.
Lendavska ulica 16a
10000 Zagreb, Croatia
Email: robert.marko@sartura.hr
Web: www.sartura.hr

Re: [PATCH v2 2/6] mmc: sdhci-of-at91: add option to keep clocks enabled

From: Aubin Constans <aubin.constans@microchip.com>
Date: 2026-09-18 16:02:18

On 15/09/2026 13:31, Robert Marko wrote:
On Mon, Sep 14, 2026 at 3:46 PM Aubin Constans
[off-list ref] wrote:
quoted
Hi Robert,

On 07/09/2026 15:25, Robert Marko wrote:
quoted
sdhci_at91_set_clks_presets() both enables the controller clocks and
programs its capabilities and preset registers. This prevents callers from
restoring the registers without changing the clock enable counts.

Move clock enablement to callers and add a SoC data flag for controllers
that must keep their clocks enabled. Use it in the runtime PM paths while
keeping register restoration separate from clock enablement.
This change has been required for the SAMA7G54 MPU already, and implemented by
two complementary commits:

https://github.com/linux4microchip/linux/commit/763a3c05c8f9d0feb279c6f308fa5d48d14e07d3
https://github.com/linux4microchip/linux/commit/b5bbc888fb39471d46fe0a4ff28f69533c265dd0

At least they have been validated by test campaigns.
If that existing work seemed OK for you and the LAN969x, then I could squash
these two commits and submit the result.
Hi Aubin,
Yeah, I originally used those in our tree as well before trying to
simplify things for upstreaming.

Regards,
Robert
To follow up on that patch, I have just submitted the original work,
ported to tag next-20260917:

https://lore.kernel.org/linux-mmc/20260918153414.909333-1-aubin.constans@microchip.com/

Hope it will make easier to support the LAN969x, notably.

Regards,
Aubin
quoted
quoted
Signed-off-by: Robert Marko <robert.marko@sartura.hr>

   drivers/mmc/host/sdhci-of-at91.c | 39 +++++++++++++++++++-------------
   1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index 7c4ac65f247d..b7e2a89da348 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -38,6 +38,7 @@
   struct sdhci_at91_soc_data {
          const struct sdhci_pltfm_data *pdata;
          bool baseclk_is_generated_internally;
+       bool keep_clks_on;
          unsigned int divider_for_baseclk;
   };
@@ -164,7 +165,7 @@ static const struct of_device_id sdhci_at91_dt_match[] = {
   };
   MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);

-static int sdhci_at91_set_clks_presets(struct device *dev)
+static void sdhci_at91_set_clks_presets(struct device *dev)
   {
          struct sdhci_host *host = dev_get_drvdata(dev);
          struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -174,7 +175,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
          unsigned int                    gck_rate, clk_base_rate;
          unsigned int                    preset_div;

-       clk_prepare_enable(priv->hclock);
          caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
          caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
@@ -222,11 +222,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
          preset_div = DIV_ROUND_UP(gck_rate, 50000000) - 1;
          writew(SDHCI_AT91_PRESET_COMMON_CONF | preset_div,
                 host->ioaddr + SDHCI_PRESET_FOR_DDR50);
-
-       clk_prepare_enable(priv->mainck);
-       clk_prepare_enable(priv->gck);
-
-       return 0;
   }

   static int sdhci_at91_suspend(struct device *dev)
@@ -254,9 +249,11 @@ static int sdhci_at91_runtime_suspend(struct device *dev)
          if (host->tuning_mode != SDHCI_TUNING_MODE_3)
                  mmc_retune_needed(host->mmc);

-       clk_disable_unprepare(priv->gck);
-       clk_disable_unprepare(priv->hclock);
-       clk_disable_unprepare(priv->mainck);
+       if (!priv->soc_data->keep_clks_on) {
+               clk_disable_unprepare(priv->gck);
+               clk_disable_unprepare(priv->hclock);
+               clk_disable_unprepare(priv->mainck);
+       }

          return 0;
   }
@@ -269,14 +266,23 @@ static int sdhci_at91_runtime_resume(struct device *dev)
          int ret;

          if (priv->restore_needed) {
-               ret = sdhci_at91_set_clks_presets(dev);
-               if (ret)
-                       return ret;
+               if (!priv->soc_data->keep_clks_on)
+                       clk_prepare_enable(priv->hclock);
+
+               sdhci_at91_set_clks_presets(dev);
+
+               if (!priv->soc_data->keep_clks_on) {
+                       clk_prepare_enable(priv->mainck);
+                       clk_prepare_enable(priv->gck);
+               }

                  priv->restore_needed = false;
                  goto out;
          }

+       if (priv->soc_data->keep_clks_on)
+               goto out;
+
          ret = clk_prepare_enable(priv->mainck);
          if (ret) {
                  dev_err(dev, "can't enable mainck\n");
@@ -344,9 +350,10 @@ static int sdhci_at91_probe(struct platform_device *pdev)
                  return dev_err_probe(&pdev->dev, PTR_ERR(priv->gck),
                                       "failed to get multclk\n");

-       ret = sdhci_at91_set_clks_presets(&pdev->dev);
-       if (ret)
-               return ret;
+       clk_prepare_enable(priv->hclock);
+       sdhci_at91_set_clks_presets(&pdev->dev);
+       clk_prepare_enable(priv->mainck);
+       clk_prepare_enable(priv->gck);

          priv->restore_needed = false;

--
2.55.0

--
Robert Marko
Staff Embedded Linux Engineer
Sartura d.d.
Lendavska ulica 16a
10000 Zagreb, Croatia
Email: robert.marko@sartura.hr
Web: www.sartura.hr

Re: [PATCH v2 4/6] arm64: dts: microchip: lan969x: add SDMMC nodes

From: claudiu beznea <claudiu.beznea@tuxon.dev>
Date: 2026-09-19 14:14:24

Hi, Roberto,

On 9/7/26 16:25, Robert Marko wrote:
quoted hunk
Add nodes for both SDMMC controllers. Connect their bus interface clock to
the fabric clock and their core clock to the corresponding generated clock.

Configure SDMMC0 with a 100 MHz generated clock for 50 MHz eMMC DDR
operation. Configure SDMMC1 with an exact 50 MHz generated clock so the
integer base clock encoded in the SDHCI capabilities remains accurate and
the identification clock does not exceed 400 kHz.

Keep both nodes ordered by unit address.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
---
Changes in v2:
* Use the fabric clock for the SDMMC bus interface clock.
* Configure SDMMC1 with an exact 50 MHz generated clock.
* Move the SDMMC1 node to preserve unit-address ordering.
* Explain the selected clock rates in the commit description.

  arch/arm64/boot/dts/microchip/lan9691.dtsi | 24 ++++++++++++++++++++++
  1 file changed, 24 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9691.dtsi b/arch/arm64/boot/dts/microchip/lan9691.dtsi
index a8541c6a098d..5e330d5fab50 100644
--- a/arch/arm64/boot/dts/microchip/lan9691.dtsi
+++ b/arch/arm64/boot/dts/microchip/lan9691.dtsi
@@ -414,6 +414,18 @@ qspi0: spi@e0804000 {
  			status = "disabled";
  		};
  
+		sdmmc0: mmc@e0830000 {
+			compatible = "microchip,lan9691-sdhci",
+				     "microchip,sam9x60-sdhci";
+			reg = <0xe0830000 0x00000300>;
s/0x00000300/0x300
quoted hunk
+			interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&fabric_clk>, <&clks GCK_ID_SDMMC0>;
+			clock-names = "hclock", "multclk";
+			assigned-clocks = <&clks GCK_ID_SDMMC0>;
+			assigned-clock-rates = <100000000>;
+			status = "disabled";
+		};
+
  		qspi2: spi@e0834000 {
  			compatible = "microchip,lan9691-qspi";
  			reg = <0xe0834000 0x00000100>,
@@ -429,6 +441,18 @@ qspi2: spi@e0834000 {
  			status = "disabled";
  		};
  
+		sdmmc1: mmc@e0838000 {
+			compatible = "microchip,lan9691-sdhci",
+				     "microchip,sam9x60-sdhci";
+			reg = <0xe0838000 0x00000300>;
Same here. I can adjust it while applying.

With that addressed:
Reviewed-by: Claudiu Beznea <claudiu.beznea@tuxon.dev>

Re: [PATCH v2 6/6] arm64: dts: microchip: ev23x71a: enable eMMC

From: claudiu beznea <claudiu.beznea@tuxon.dev>
Date: 2026-09-19 14:18:29


On 9/7/26 16:25, Robert Marko wrote:
Enable the non-removable eMMC connected to SDMMC0. Configure its 8-bit bus
for 1.8 V DDR operation.

Signed-off-by: Robert Marko<robert.marko@sartura.hr>
Reviewed-by: Claudiu Beznea <claudiu.beznea@tuxon.dev>

Re: [PATCH v2 3/6] mmc: sdhci-of-at91: add LAN969x support

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-22 12:10:17

On Wed, Sep 9, 2026 at 12:42 PM Adrian Hunter [off-list ref] wrote:
On 07/09/2026 16:25, Robert Marko wrote:
quoted
LAN969x uses the same internally generated base clock layout as SAM9X60,
but its SDMMC controller stops responding when runtime PM gates its clocks.
Software resets then fail to complete and the internal SDHCI clock never
stabilises, causing subsequent I/O requests to time out.
Is this a known issue of the SoC?  Is there perhaps a hardware reset
for the controller that would bring it back to life?
Hi Adrian,
It seems like the controller was reused from SAMA7G5, which shares the
same behaviour.
Unfortunately, there is no HW reset for the controller.
Does that mean unbind and rebind of the device from the driver
also does not work?
It works as long as we avoid disabling the clocks.

Regards,
Robert
quoted
Add LAN969x-specific SoC data using the SAM9X60 clock layout and select the
option to leave its clocks enabled across runtime suspend.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

 drivers/mmc/host/sdhci-of-at91.c | 8 ++++++++
 1 file changed, 8 insertions(+)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index b7e2a89da348..e7affca8da25 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -158,9 +158,17 @@ static const struct sdhci_at91_soc_data soc_data_sam9x60 = {
      .divider_for_baseclk = 2,
 };

+static const struct sdhci_at91_soc_data soc_data_lan969x = {
+     .pdata = &sdhci_sama5d2_pdata,
+     .baseclk_is_generated_internally = true,
Should be a comment here explaining the issue that needs
keep_clks_on = true
quoted
+     .keep_clks_on = true,
+     .divider_for_baseclk = 2,
+};
+
 static const struct of_device_id sdhci_at91_dt_match[] = {
      { .compatible = "atmel,sama5d2-sdhci", .data = &soc_data_sama5d2 },
      { .compatible = "microchip,sam9x60-sdhci", .data = &soc_data_sam9x60 },
+     { .compatible = "microchip,lan9691-sdhci", .data = &soc_data_lan969x },
      {}
 };
 MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);

-- 
Robert Marko
Staff Embedded Linux Engineer
Sartura d.d.
Lendavska ulica 16a
10000 Zagreb, Croatia
Email: robert.marko@sartura.hr
Web: www.sartura.hr

Re: [PATCH v2 2/6] mmc: sdhci-of-at91: add option to keep clocks enabled

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-22 12:11:18

On Fri, Sep 18, 2026 at 6:02 PM Aubin Constans
[off-list ref] wrote:
On 15/09/2026 13:31, Robert Marko wrote:
quoted
On Mon, Sep 14, 2026 at 3:46 PM Aubin Constans
[off-list ref] wrote:
quoted
Hi Robert,

On 07/09/2026 15:25, Robert Marko wrote:
quoted
sdhci_at91_set_clks_presets() both enables the controller clocks and
programs its capabilities and preset registers. This prevents callers from
restoring the registers without changing the clock enable counts.

Move clock enablement to callers and add a SoC data flag for controllers
that must keep their clocks enabled. Use it in the runtime PM paths while
keeping register restoration separate from clock enablement.
This change has been required for the SAMA7G54 MPU already, and implemented by
two complementary commits:

https://github.com/linux4microchip/linux/commit/763a3c05c8f9d0feb279c6f308fa5d48d14e07d3
https://github.com/linux4microchip/linux/commit/b5bbc888fb39471d46fe0a4ff28f69533c265dd0

At least they have been validated by test campaigns.
If that existing work seemed OK for you and the LAN969x, then I could squash
these two commits and submit the result.
Hi Aubin,
Yeah, I originally used those in our tree as well before trying to
simplify things for upstreaming.

Regards,
Robert
To follow up on that patch, I have just submitted the original work,
ported to tag next-20260917:

https://lore.kernel.org/linux-mmc/20260918153414.909333-1-aubin.constans@microchip.com/

Hope it will make easier to support the LAN969x, notably.
Hi Aubin,
As long as that is merged, then LAN969x support is really simple.
I tested it locally and it works, so I am waiting on that getting
merged before sending v3 for LAN969x.

Regards,
Robert
Regards,
Aubin
quoted
quoted
quoted
Signed-off-by: Robert Marko <robert.marko@sartura.hr>

   drivers/mmc/host/sdhci-of-at91.c | 39 +++++++++++++++++++-------------
   1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c
index 7c4ac65f247d..b7e2a89da348 100644
--- a/drivers/mmc/host/sdhci-of-at91.c
+++ b/drivers/mmc/host/sdhci-of-at91.c
@@ -38,6 +38,7 @@
   struct sdhci_at91_soc_data {
          const struct sdhci_pltfm_data *pdata;
          bool baseclk_is_generated_internally;
+       bool keep_clks_on;
          unsigned int divider_for_baseclk;
   };
@@ -164,7 +165,7 @@ static const struct of_device_id sdhci_at91_dt_match[] = {
   };
   MODULE_DEVICE_TABLE(of, sdhci_at91_dt_match);

-static int sdhci_at91_set_clks_presets(struct device *dev)
+static void sdhci_at91_set_clks_presets(struct device *dev)
   {
          struct sdhci_host *host = dev_get_drvdata(dev);
          struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -174,7 +175,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
          unsigned int                    gck_rate, clk_base_rate;
          unsigned int                    preset_div;

-       clk_prepare_enable(priv->hclock);
          caps0 = readl(host->ioaddr + SDHCI_CAPABILITIES);
          caps1 = readl(host->ioaddr + SDHCI_CAPABILITIES_1);
@@ -222,11 +222,6 @@ static int sdhci_at91_set_clks_presets(struct device *dev)
          preset_div = DIV_ROUND_UP(gck_rate, 50000000) - 1;
          writew(SDHCI_AT91_PRESET_COMMON_CONF | preset_div,
                 host->ioaddr + SDHCI_PRESET_FOR_DDR50);
-
-       clk_prepare_enable(priv->mainck);
-       clk_prepare_enable(priv->gck);
-
-       return 0;
   }

   static int sdhci_at91_suspend(struct device *dev)
@@ -254,9 +249,11 @@ static int sdhci_at91_runtime_suspend(struct device *dev)
          if (host->tuning_mode != SDHCI_TUNING_MODE_3)
                  mmc_retune_needed(host->mmc);

-       clk_disable_unprepare(priv->gck);
-       clk_disable_unprepare(priv->hclock);
-       clk_disable_unprepare(priv->mainck);
+       if (!priv->soc_data->keep_clks_on) {
+               clk_disable_unprepare(priv->gck);
+               clk_disable_unprepare(priv->hclock);
+               clk_disable_unprepare(priv->mainck);
+       }

          return 0;
   }
@@ -269,14 +266,23 @@ static int sdhci_at91_runtime_resume(struct device *dev)
          int ret;

          if (priv->restore_needed) {
-               ret = sdhci_at91_set_clks_presets(dev);
-               if (ret)
-                       return ret;
+               if (!priv->soc_data->keep_clks_on)
+                       clk_prepare_enable(priv->hclock);
+
+               sdhci_at91_set_clks_presets(dev);
+
+               if (!priv->soc_data->keep_clks_on) {
+                       clk_prepare_enable(priv->mainck);
+                       clk_prepare_enable(priv->gck);
+               }

                  priv->restore_needed = false;
                  goto out;
          }

+       if (priv->soc_data->keep_clks_on)
+               goto out;
+
          ret = clk_prepare_enable(priv->mainck);
          if (ret) {
                  dev_err(dev, "can't enable mainck\n");
@@ -344,9 +350,10 @@ static int sdhci_at91_probe(struct platform_device *pdev)
                  return dev_err_probe(&pdev->dev, PTR_ERR(priv->gck),
                                       "failed to get multclk\n");

-       ret = sdhci_at91_set_clks_presets(&pdev->dev);
-       if (ret)
-               return ret;
+       clk_prepare_enable(priv->hclock);
+       sdhci_at91_set_clks_presets(&pdev->dev);
+       clk_prepare_enable(priv->mainck);
+       clk_prepare_enable(priv->gck);

          priv->restore_needed = false;

--
2.55.0

--
Robert Marko
Staff Embedded Linux Engineer
Sartura d.d.
Lendavska ulica 16a
10000 Zagreb, Croatia
Email: robert.marko@sartura.hr
Web: www.sartura.hr

-- 
Robert Marko
Staff Embedded Linux Engineer
Sartura d.d.
Lendavska ulica 16a
10000 Zagreb, Croatia
Email: robert.marko@sartura.hr
Web: www.sartura.hr

Re: [PATCH v2 5/6] arm64: dts: microchip: ev23x71a: enable QSPI

From: Claudiu Beznea <claudiu.beznea@kernel.org>
Date: 2026-09-28 08:10:01

On Mon, Sep 07, 2026 at 03:25:33PM +0200, Robert Marko wrote:
Enable the QSPI controller and describe the onboard SPI NOR flash.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
Missing '---'
quoted hunk
 .../arm64/boot/dts/microchip/lan9696-ev23x71a.dts | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
index 4012ea7d07bb..985ca31e74e7 100644
--- a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
+++ b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
@@ -463,6 +463,21 @@ phy27: phy@27 {
 	};
 };
 
+&qspi0 {
+	status = "okay";
+
+	flash@0 {
+		compatible = "jedec,spi-nor";
+		reg = <0>;
+		spi-max-frequency = <100000000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
As sashiko pointed out, #address-cells, #size-cells, should not be
necessary here. I dropped them and applied to microchip-dt64.

Thank you,
Claudiu

Re: [PATCH v2 5/6] arm64: dts: microchip: ev23x71a: enable QSPI

From: Robert Marko <robert.marko@sartura.hr>
Date: 2026-09-28 12:30:49

On Mon, Sep 28, 2026 at 10:09 AM Claudiu Beznea
[off-list ref] wrote:
On Mon, Sep 07, 2026 at 03:25:33PM +0200, Robert Marko wrote:
quoted
Enable the QSPI controller and describe the onboard SPI NOR flash.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
Missing '---'
quoted
 .../arm64/boot/dts/microchip/lan9696-ev23x71a.dts | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
diff --git a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
index 4012ea7d07bb..985ca31e74e7 100644
--- a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
+++ b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts
@@ -463,6 +463,21 @@ phy27: phy@27 {
      };
 };

+&qspi0 {
+     status = "okay";
+
+     flash@0 {
+             compatible = "jedec,spi-nor";
+             reg = <0>;
+             spi-max-frequency = <100000000>;
+             #address-cells = <1>;
+             #size-cells = <1>;
As sashiko pointed out, #address-cells, #size-cells, should not be
necessary here. I dropped them and applied to microchip-dt64.
Thanks,
Will drop it from future v3 then.

Regards,
Robert
Thank you,
Claudiu


-- 
Robert Marko
Staff Embedded Linux Engineer
Sartura d.d.
Lendavska ulica 16a
10000 Zagreb, Croatia
Email: robert.marko@sartura.hr
Web: www.sartura.hr
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help