[PATCH 04/10] mmc: sdhci-of-k1: add pinctrl state switching for voltage changes

Iker Pedrosa
2026-03-02T15:13:57Z

Add support for switching pinctrl states during voltage transitions to
ensure proper pin drive strength and configuration for different I/O
voltage domains (3.3V vs 1.8V).

Pinctrl states are optional and the driver degrades gracefully if they
are not defined in the device tree. This allows boards to enable proper
pin drive strength optimization for UHS-I modes without breaking
existing functionality.

The pinctrl setup uses:
- "default" state for 3.3V operation (higher drive strength)
- "state_uhs" state for 1.8V operation (lower drive strength)

Signed-off-by: Iker Pedrosa <ikerpedrosam@gmail.com>
---
drivers/mmc/host/sdhci-of-k1.c | 59 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/host/sdhci-of-k1.c b/drivers/mmc/host/sdhci-of-k1.c
index c260cb89704ae7a25bec0f07831d495553405bbd..2a982bca2a50ca96f43af7b4de5bc4fb4b5b195b 100644
--- a/drivers/mmc/host/sdhci-of-k1.c
+++ b/drivers/mmc/host/sdhci-of-k1.c
@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>

#include "sdhci.h"
@@ -70,6 +71,9 @@
struct spacemit_sdhci_host {
struct clk *clk_core;
struct clk *clk_io;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pinctrl_default;
+ struct pinctrl_state *pinctrl_uhs;
};

/* All helper functions will update clr/set while preserve rest bits */
@@ -219,7 +223,42 @@ static void spacemit_sdhci_pre_hs400_to_hs200(struct mmc_host *mmc)
static int spacemit_sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
struct mmc_ios *ios)
{
- return sdhci_start_signal_voltage_switch(mmc, ios);
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct spacemit_sdhci_host *sdhst = sdhci_pltfm_priv(pltfm_host);
+ struct pinctrl_state *state;
+ int ret;
+
+ ret = sdhci_start_signal_voltage_switch(mmc, ios);
+ if (ret)
+ return ret;
+
+ /* Select appropriate pinctrl state based on signal voltage */
+ if (sdhst->pinctrl) {
+ switch (ios->signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_330:
+ state = sdhst->pinctrl_default;
+ break;
+ case MMC_SIGNAL_VOLTAGE_180:
+ state = sdhst->pinctrl_uhs;
+ break;
+ default:
+ dev_warn(mmc_dev(mmc), "unsupported voltage %d\n", ios->signal_voltage);
+ return 0;
+ }
+
+ if (state) {
+ ret = pinctrl_select_state(sdhst->pinctrl, state);
+ if (ret) {
+ dev_warn(mmc_dev(mmc), "failed to select pinctrl state: %d\n", ret);
+ return 0;
+ }
+ dev_dbg(mmc_dev(mmc), "switched to %s pinctrl state\n",
+ ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180 ? "UHS" : "default");
+ }
+ }
+
+ return 0;
}

static inline int spacemit_sdhci_get_clocks(struct device *dev,
@@ -301,6 +340,24 @@ static int spacemit_sdhci_probe(struct platform_device *pdev)
if (ret)
dev_warn(dev, "Failed to get regulators: %d\n", ret);

+ sdhst = sdhci_pltfm_priv(pltfm_host);
+ sdhst->pinctrl = devm_pinctrl_get(dev);
+ if (!IS_ERR(sdhst->pinctrl)) {
+ sdhst->pinctrl_default = pinctrl_lookup_state(sdhst->pinctrl, "default");
+ if (IS_ERR(sdhst->pinctrl_default))
+ sdhst->pinctrl_default = NULL;
+
+ sdhst->pinctrl_uhs = pinctrl_lookup_state(sdhst->pinctrl, "state_uhs");
+ if (IS_ERR(sdhst->pinctrl_uhs))
+ sdhst->pinctrl_uhs = NULL;
+
+ dev_dbg(dev, "pinctrl setup: default=%p, uhs=%p\n",
+ sdhst->pinctrl_default, sdhst->pinctrl_uhs);
+ } else {
+ sdhst->pinctrl = NULL;
+ dev_dbg(dev, "pinctrl not available, voltage switching will work without it\n");
+ }
+
host->mmc_host_ops.start_signal_voltage_switch = spacemit_sdhci_start_signal_voltage_switch;

ret = spacemit_sdhci_get_clocks(dev, pltfm_host);

--
2.53.0