Skip to contentSkip to conversation

mmc: sdhci-omap: Add UHS/HS200 mode support

PATCH Superseded Kishon Vijay Abraham I 2 participants 19 messages Apr 25, 2018 – Apr 26, 2018

Subsystems (4)

v4 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v4 cover mmc: sdhci-omap: Add UHS/HS200 mode support

Add UHS/HS200 mode support in sdhci-omap. The programming sequence for voltage switching, tuning is followed from AM572x TRM
http://www.ti.com/lit/ug/spruhz6j/spruhz6j.pdf
(Similar to all AM57x/DRA7x SoCs). The patch series also implements workaround for errata published in
http://www.ti.com/lit/er/sprz429l/sprz429l.pdf

patches are created on top of
git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc.git next

Changes from v3:
*) Fixed Adrian's comment on error handling when __sdhci_add_host()
fails
*) Used Adrian's patches for programming SW timeout value.

Changes from v2:
*) Patches in v2 already applied to mmc -next are dropped from the
series.
*) Changed SW timeout logic as per Adrians's suggestion *) Validated SDIO with the current patches (added a couple of fixes
found while adding SDIO support).
*) Used soc_device_match() instead of pdata-quirks as per Tony's
suggestions.

Changes from v1:
*) Only poll on DAT0 and DATI for card_busy status
*) Cleanup iodelay patch as suggested by Tony.
*) Added quirk to disable HW timeout
*) Use the existing data timer but program a relatively accurate
SW timeout value (Impacts all platforms)
*) Fix a bug in sdhci which was using data_timer for non data line
commands

Adrian Hunter (2):
mmc: sdhci: Add quirk to disable HW timeout
mmc: sdhci: Factor out target_timeout calculation

Kishon Vijay Abraham I (10):
mmc: sdhci-omap: Fix when capabilities are obtained from
SDHCI_CAPABILITIES reg
mmc: sdhci-omap: Remove setting ADMA capability in driver
mmc: sdhci-omap: Workaround for Errata i843
mmc: sdhci-omap: Invoke sdhci_get_of_property to read sdhci dt
properties
mmc: sdhci: Disable HS200 mode if controller can't support 1.8v
mmc: sdhci: Program a relatively accurate SW timeout value
mmc: sdhci-omap: Workaround for Errata i834
dt-bindings: sdhci-omap: Add K2G specific binding
mmc: sdhci-omap: Add support for MMC/SD controller in k2g SoC
mmc: sdhci-omap: Add sdhci_omap specific ops for enable_sdio_irq

 .../devicetree/bindings/mmc/sdhci-omap.txt    |   2 +
 drivers/mmc/host/sdhci-omap.c                 |  81 ++++++++++--
 drivers/mmc/host/sdhci.c                      | 123 ++++++++++++++----
 drivers/mmc/host/sdhci.h                      |  15 +++
 include/linux/mmc/host.h                      |   1 +
 5 files changed, 191 insertions(+), 31 deletions(-)
Signature · 1 line
2.17.0
#1 lore raw

PATCH v4 1/12 mmc: sdhci-omap: Fix when capabilities are obtained from SDHCI_CAPABILITIES reg

sdhci_omap_config_iodelay_pinctrl_state() requires caps and caps2 to be initialized (speed mode capabilities like UHS/HS200) before it is invoked. While mmc_of_parse() initializes caps/caps2 if capabilities is populated in device tree, it will remain uninitialized for capabilities obtained from SDHCI_CAPABILITIES register.
Fix sdhci_omap_config_iodelay_pinctrl_state() to be used even while getting the capabilities from SDHCI_CAPABILITIES register by invoking sdhci_setup_host() before sdhci_omap_config_iodelay_pinctrl_state().

---
 drivers/mmc/host/sdhci-omap.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)
drivers/mmc/host/sdhci-omap.c +12 -5
index 1456abd5eeb9..9bb53702e0e5 100644
@@ -916,10 +916,6 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 		goto err_put_sync;
 	}
 
-	ret = sdhci_omap_config_iodelay_pinctrl_state(omap_host);
-	if (ret)
-		goto err_put_sync;
-
 	host->mmc_host_ops.get_ro = mmc_gpio_get_ro;
 	host->mmc_host_ops.start_signal_voltage_switch =
 					sdhci_omap_start_signal_voltage_switch;
@@ -930,12 +926,23 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 	sdhci_read_caps(host);
 	host->caps |= SDHCI_CAN_DO_ADMA2;
 
-	ret = sdhci_add_host(host);
+	ret = sdhci_setup_host(host);
 	if (ret)
 		goto err_put_sync;
 
+	ret = sdhci_omap_config_iodelay_pinctrl_state(omap_host);
+	if (ret)
+		goto err_cleanup_host;
+
+	ret = __sdhci_add_host(host);
+	if (ret)
+		goto err_cleanup_host;
+
 	return 0;
 
+err_cleanup_host:
+	sdhci_cleanup_host(host);
+
 err_put_sync:
 	pm_runtime_put_sync(dev);
 
Signature · 1 line
2.17.0
maintainer Acked-by on v4 1/12
#2 lore raw

Re: [PATCH v4 01/12] mmc: sdhci-omap: Fix when capabilities are obtained from SDHCI_CAPABILITIES reg

Quoted 11 lines · Kishon Vijay Abraham I

sdhci_omap_config_iodelay_pinctrl_state() requires caps and caps2 to be initialized (speed mode capabilities like UHS/HS200) before it is invoked. While mmc_of_parse() initializes caps/caps2 if capabilities is populated in device tree, it will remain uninitialized for capabilities obtained from SDHCI_CAPABILITIES register.
Fix sdhci_omap_config_iodelay_pinctrl_state() to be used even while getting the capabilities from SDHCI_CAPABILITIES register by invoking sdhci_setup_host() before sdhci_omap_config_iodelay_pinctrl_state().

Quoted 45 lines
---
 drivers/mmc/host/sdhci-omap.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)
drivers/mmc/host/sdhci-omap.c +12 -5
index 1456abd5eeb9..9bb53702e0e5 100644
@@ -916,10 +916,6 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 		goto err_put_sync;
 	}
 
-	ret = sdhci_omap_config_iodelay_pinctrl_state(omap_host);
-	if (ret)
-		goto err_put_sync;
-
 	host->mmc_host_ops.get_ro = mmc_gpio_get_ro;
 	host->mmc_host_ops.start_signal_voltage_switch =
 					sdhci_omap_start_signal_voltage_switch;
@@ -930,12 +926,23 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 	sdhci_read_caps(host);
 	host->caps |= SDHCI_CAN_DO_ADMA2;
 
-	ret = sdhci_add_host(host);
+	ret = sdhci_setup_host(host);
 	if (ret)
 		goto err_put_sync;
 
+	ret = sdhci_omap_config_iodelay_pinctrl_state(omap_host);
+	if (ret)
+		goto err_cleanup_host;
+
+	ret = __sdhci_add_host(host);
+	if (ret)
+		goto err_cleanup_host;
+
 	return 0;
 
+err_cleanup_host:
+	sdhci_cleanup_host(host);
+
 err_put_sync:
 	pm_runtime_put_sync(dev);
 
#3 lore raw

PATCH v4 2/12 mmc: sdhci-omap: Remove setting ADMA capability in driver

In reply to Kishon Vijay Abraham I

sdhci can directly get ADMA capability from MMCHS_CAPA register. Remove explicitly setting ADMA here as some instances might not have ADMA enabled. (sdhci_read_caps() is also removed from here since sdhci_setup_host() invokes it).

---
 drivers/mmc/host/sdhci-omap.c | 3 ---
 1 file changed, 3 deletions(-)
drivers/mmc/host/sdhci-omap.c +0 -3
index 9bb53702e0e5..6e2cf4cfeec0 100644
@@ -923,9 +923,6 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 	host->mmc_host_ops.card_busy = sdhci_omap_card_busy;
 	host->mmc_host_ops.execute_tuning = sdhci_omap_execute_tuning;
 
-	sdhci_read_caps(host);
-	host->caps |= SDHCI_CAN_DO_ADMA2;
-
 	ret = sdhci_setup_host(host);
 	if (ret)
 		goto err_put_sync;
Signature · 1 line
2.17.0
#4 lore raw

PATCH v4 3/12 mmc: sdhci-omap: Workaround for Errata i843

In reply to Kishon Vijay Abraham I

Errata i843 in AM572x Sitara Processors Silicon Revision 2.0, 1.1 (SPRZ429L July 2014–Revised April 2018 [1]) mentions
PG 1.0/1.1 silicon has limitations w.r.t frequencies at which MMC1/2/3 can operate.

Use soc_device_match() to identify rev 1.0/1.1 silicon and override mmc->f_max according to the errata workaround. "max-frequency" dt property cannot be used since the device tree is added for rev 2.0 silicon.

soc_device_match() is also used in order to get the IODelay values for rev 1.0/1.1 silicon.

[1] -> http://www.ti.com/lit/er/sprz429l/sprz429l.pdf

---
 drivers/mmc/host/sdhci-omap.c | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)
drivers/mmc/host/sdhci-omap.c +34 -1
index 6e2cf4cfeec0..b4400be0606f 100644
@@ -26,6 +26,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/regulator/consumer.h>
 #include <linux/pinctrl/consumer.h>
+#include <linux/sys_soc.h>
 
 #include "sdhci-pltfm.h"
 
@@ -100,6 +101,7 @@ struct sdhci_omap_data {
 };
 
 struct sdhci_omap_host {
+	char			*version;
 	void __iomem		*base;
 	struct device		*dev;
 	struct	regulator	*pbias;
@@ -733,12 +735,21 @@ static struct pinctrl_state
 				  u32 *caps, u32 capmask)
 {
 	struct device *dev = omap_host->dev;
+	char *version = omap_host->version;
 	struct pinctrl_state *pinctrl_state = ERR_PTR(-ENODEV);
+	char str[20];
 
 	if (!(*caps & capmask))
 		goto ret;
 
-	pinctrl_state = pinctrl_lookup_state(omap_host->pinctrl, mode);
+	if (version) {
+		snprintf(str, 20, "%s-%s", mode, version);
+		pinctrl_state = pinctrl_lookup_state(omap_host->pinctrl, str);
+	}
+
+	if (IS_ERR(pinctrl_state))
+		pinctrl_state = pinctrl_lookup_state(omap_host->pinctrl, mode);
+
 	if (IS_ERR(pinctrl_state)) {
 		dev_err(dev, "no pinctrl state for %s mode", mode);
 		*caps &= ~capmask;
@@ -830,6 +841,16 @@ static int sdhci_omap_config_iodelay_pinctrl_state(struct sdhci_omap_host
 	return 0;
 }
 
+static const struct soc_device_attribute sdhci_omap_soc_devices[] = {
+	{
+		.machine = "DRA7[45]*",
+		.revision = "ES1.[01]",
+	},
+	{
+		/* sentinel */
+	}
+};
+
 static int sdhci_omap_probe(struct platform_device *pdev)
 {
 	int ret;
@@ -841,6 +862,7 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 	struct mmc_host *mmc;
 	const struct of_device_id *match;
 	struct sdhci_omap_data *data;
+	const struct soc_device_attribute *soc;
 
 	match = of_match_device(omap_sdhci_match, dev);
 	if (!match)
@@ -875,6 +897,17 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_pltfm_free;
 
+	soc = soc_device_match(sdhci_omap_soc_devices);
+	if (soc) {
+		omap_host->version = "rev11";
+		if (!strcmp(dev_name(dev), "4809c000.mmc"))
+			mmc->f_max = 96000000;
+		if (!strcmp(dev_name(dev), "480b4000.mmc"))
+			mmc->f_max = 48000000;
+		if (!strcmp(dev_name(dev), "480ad000.mmc"))
+			mmc->f_max = 48000000;
+	}
+
 	pltfm_host->clk = devm_clk_get(dev, "fck");
 	if (IS_ERR(pltfm_host->clk)) {
 		ret = PTR_ERR(pltfm_host->clk);
Signature · 1 line
2.17.0
#5 lore raw

PATCH v4 4/12 mmc: sdhci-omap: Invoke sdhci_get_of_property to read sdhci dt properties

In reply to Kishon Vijay Abraham I

Invoke sdhci_get_of_property defined in sdhci-pltfm.c to read sdhci specific properties from dt node.

---
 drivers/mmc/host/sdhci-omap.c | 1 +
 1 file changed, 1 insertion(+)
drivers/mmc/host/sdhci-omap.c +1 -0
index b4400be0606f..2d9ea23610d5 100644
@@ -893,6 +893,7 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 	host->ioaddr += offset;
 
 	mmc = host->mmc;
+	sdhci_get_of_property(pdev);
 	ret = mmc_of_parse(mmc);
 	if (ret)
 		goto err_pltfm_free;
Signature · 1 line
2.17.0
#6 lore raw

PATCH v4 5/12 mmc: sdhci: Disable HS200/HS400 mode if controller can't support 1.8v

In reply to Kishon Vijay Abraham I

Though MMC controller can indicate HS200/HS400 mode capability (by using "mmc-hs200-1_8v"/"mmc-hs400-1_8v" dt property), if the IO lines in the board is connected to 3.3v supply, HS200/HS400 mode cannot be supported. Such boards have "no-1-8-v" property in their dts file. Disable HS200/HS400 mode for boards which have "no-1-8-v" set.

---
 drivers/mmc/host/sdhci.c | 1 +
 include/linux/mmc/host.h | 1 +
 2 files changed, 2 insertions(+)
drivers/mmc/host/sdhci.c +1 -0
index 2ededa7f43df..b5f047b5f3ae 100644
@@ -3672,6 +3672,7 @@ int sdhci_setup_host(struct sdhci_host *host)
 	if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
 		host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 |
 				 SDHCI_SUPPORT_DDR50);
+		mmc->caps2 &= ~MMC_CAP2_HSX00_1_8V;
 	}
 
 	/* Any UHS-I mode in caps implies SDR12 and SDR25 support. */
include/linux/mmc/host.h +1 -0
index 7c6eaf63f5ce..58832451767b 100644
@@ -345,6 +345,7 @@ struct mmc_host {
 #define MMC_CAP2_HS400_1_2V	(1 << 16)	/* Can support HS400 1.2V */
 #define MMC_CAP2_HS400		(MMC_CAP2_HS400_1_8V | \
 				 MMC_CAP2_HS400_1_2V)
+#define MMC_CAP2_HSX00_1_8V	(MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS400_1_8V)
 #define MMC_CAP2_HSX00_1_2V	(MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V)
 #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
 #define MMC_CAP2_NO_WRITE_PROTECT (1 << 18)	/* No physical write protect pin, assume that card is always read-write */
Signature · 1 line
2.17.0
maintainer
#7 lore raw

Re: [PATCH v4 05/12] mmc: sdhci: Disable HS200/HS400 mode if controller can't support 1.8v

Quoted 18 lines · Kishon Vijay Abraham I

Though MMC controller can indicate HS200/HS400 mode capability (by using "mmc-hs200-1_8v"/"mmc-hs400-1_8v" dt property), if the IO lines in the board is connected to 3.3v supply, HS200/HS400 mode cannot be supported. Such boards have "no-1-8-v" property in their dts file. Disable HS200/HS400 mode for boards which have "no-1-8-v" set.

---
 drivers/mmc/host/sdhci.c | 1 +
 include/linux/mmc/host.h | 1 +
 2 files changed, 2 insertions(+)
drivers/mmc/host/sdhci.c +0 -0
index 2ededa7f43df..b5f047b5f3ae 100644

drivers/mmc/host/sdhci.c int sdhci_setup_host(struct sdhci_host *host) · line 3675

Quoted 4 lines
drivers/mmc/host/sdhci.c +1 -0
@@ -3672,6 +3672,7 @@ int sdhci_setup_host(struct sdhci_host *host)
 	if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
 		host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 |
 				 SDHCI_SUPPORT_DDR50);
+		mmc->caps2 &= ~MMC_CAP2_HSX00_1_8V;

Seems weird for sdhci to clear flags it never set. Also couldn't we reasonably expect dt properties to be consistent? Is this really about setting SDHCI_QUIRK2_NO_1_8_V in your driver and expecting it to override other dt properties? Although that still begs the question why anyone would set dt properties that the hardware doesn't support? I guess you should also clear MMC_CAP2_HS400_ES.

Quoted 16 lines
drivers/mmc/host/sdhci.c +0 -0
 	}
 
 	/* Any UHS-I mode in caps implies SDR12 and SDR25 support. */
include/linux/mmc/host.h +1 -0
index 7c6eaf63f5ce..58832451767b 100644
@@ -345,6 +345,7 @@ struct mmc_host {
 #define MMC_CAP2_HS400_1_2V	(1 << 16)	/* Can support HS400 1.2V */
 #define MMC_CAP2_HS400		(MMC_CAP2_HS400_1_8V | \
 				 MMC_CAP2_HS400_1_2V)
+#define MMC_CAP2_HSX00_1_8V	(MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS400_1_8V)
 #define MMC_CAP2_HSX00_1_2V	(MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V)
 #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
 #define MMC_CAP2_NO_WRITE_PROTECT (1 << 18)	/* No physical write protect pin, assume that card is always read-write */
#8 lore raw

Re: [PATCH v4 05/12] mmc: sdhci: Disable HS200/HS400 mode if controller can't support 1.8v

Hi Adrian,

Quoted 14 lines · Adrian Hunter
Quoted 13 lines · Kishon Vijay Abraham I

Though MMC controller can indicate HS200/HS400 mode capability (by using "mmc-hs200-1_8v"/"mmc-hs400-1_8v" dt property), if the IO lines in the board is connected to 3.3v supply, HS200/HS400 mode cannot be supported. Such boards have "no-1-8-v" property in their dts file. Disable HS200/HS400 mode for boards which have "no-1-8-v" set.

---
 drivers/mmc/host/sdhci.c | 1 +
 include/linux/mmc/host.h | 1 +
 2 files changed, 2 insertions(+)
Quoted 16 lines

drivers/mmc/host/sdhci.c int sdhci_setup_host(struct sdhci_host *host) · line 3675

Quoted 9 lines
drivers/mmc/host/sdhci.c +1 -0
index 2ededa7f43df..b5f047b5f3ae 100644
@@ -3672,6 +3672,7 @@ int sdhci_setup_host(struct sdhci_host *host)
 	if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
 		host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 |
 				 SDHCI_SUPPORT_DDR50);
+		mmc->caps2 &= ~MMC_CAP2_HSX00_1_8V;

Seems weird for sdhci to clear flags it never set. Also couldn't we reasonably expect dt properties to be consistent? Is this really about setting SDHCI_QUIRK2_NO_1_8_V in your driver and expecting it to override other dt properties? Although that still begs the question why anyone would set dt properties that the hardware doesn't support? I guess you should also clear MMC_CAP2_HS400_ES.

The SoC might support a specific mode like HS200. So the SoC specific dtsi file might have dt properties for HS200 mode set. But the board which uses the SoC might be modeled in a way a particular mode cannot be used (like IO lines not connected to 1.8v). One option is to use /delete-property/ in the board dts file. But since "no-1-8-v" property already indicates HS200 mode cannot be supported, having a /delete-property/ might be redundant.

I can reset caps2 in sdhci-omap after invoking mmc_of_parse if you feel that makes more sense.

Thanks
Kishon

maintainer
#9 lore raw

Re: [PATCH v4 05/12] mmc: sdhci: Disable HS200/HS400 mode if controller can't support 1.8v

Quoted 41 lines · Kishon Vijay Abraham I

Hi Adrian,

Quoted 14 lines · Adrian Hunter
Quoted 13 lines · Kishon Vijay Abraham I

Though MMC controller can indicate HS200/HS400 mode capability (by using "mmc-hs200-1_8v"/"mmc-hs400-1_8v" dt property), if the IO lines in the board is connected to 3.3v supply, HS200/HS400 mode cannot be supported. Such boards have "no-1-8-v" property in their dts file. Disable HS200/HS400 mode for boards which have "no-1-8-v" set.

---
 drivers/mmc/host/sdhci.c | 1 +
 include/linux/mmc/host.h | 1 +
 2 files changed, 2 insertions(+)
Quoted 16 lines

drivers/mmc/host/sdhci.c int sdhci_setup_host(struct sdhci_host *host) · line 3675

Quoted 9 lines
drivers/mmc/host/sdhci.c +1 -0
index 2ededa7f43df..b5f047b5f3ae 100644
@@ -3672,6 +3672,7 @@ int sdhci_setup_host(struct sdhci_host *host)
 	if (host->quirks2 & SDHCI_QUIRK2_NO_1_8_V) {
 		host->caps1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 |
 				 SDHCI_SUPPORT_DDR50);
+		mmc->caps2 &= ~MMC_CAP2_HSX00_1_8V;

Seems weird for sdhci to clear flags it never set. Also couldn't we reasonably expect dt properties to be consistent? Is this really about setting SDHCI_QUIRK2_NO_1_8_V in your driver and expecting it to override other dt properties? Although that still begs the question why anyone would set dt properties that the hardware doesn't support? I guess you should also clear MMC_CAP2_HS400_ES.

The SoC might support a specific mode like HS200. So the SoC specific dtsi file might have dt properties for HS200 mode set. But the board which uses the SoC might be modeled in a way a particular mode cannot be used (like IO lines not connected to 1.8v). One option is to use /delete-property/ in the board dts file. But since "no-1-8-v" property already indicates HS200 mode cannot be supported, having a /delete-property/ might be redundant.

Quoted 2 lines

I can reset caps2 in sdhci-omap after invoking mmc_of_parse if you feel that makes more sense.

Just add the explanation to the commit message, add a comment to the code, and also clear MMC_CAP_1_8V_DDR and MMC_CAP2_HS400_ES and MMC_CAP_UHS_*.

#10 lore raw

PATCH v4 6/12 mmc: sdhci: Add quirk to disable HW timeout

In reply to Kishon Vijay Abraham I

From: Adrian Hunter <[email protected]>

Add quirk to disable HW timeout if the requested timeout is more than the maximum obtainable timeout.

Also, if the quirk is set and ->get_max_timeout_count() is not implemented, max_busy_timeout is set to zero.

---
 drivers/mmc/host/sdhci.c | 38 ++++++++++++++++++++++++++++++++++----
 drivers/mmc/host/sdhci.h |  5 +++++
 2 files changed, 39 insertions(+), 4 deletions(-)
drivers/mmc/host/sdhci.c +34 -4
index b5f047b5f3ae..b91163de4da4 100644
@@ -709,12 +709,15 @@ static u32 sdhci_sdma_address(struct sdhci_host *host)
 		return sg_dma_address(host->data->sg);
 }
 
-static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd)
+static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd,
+			     bool *too_big)
 {
 	u8 count;
 	struct mmc_data *data = cmd->data;
 	unsigned target_timeout, current_timeout;
 
+	*too_big = true;
+
 	/*
 	 * If the host controller provides us with an incorrect timeout
 	 * value, just skip the check and use 0xE.  The hardware may take
@@ -768,9 +771,12 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd)
 	}
 
 	if (count >= 0xF) {
-		DBG("Too large timeout 0x%x requested for CMD%d!\n",
-		    count, cmd->opcode);
+		if (!(host->quirks2 & SDHCI_QUIRK2_DISABLE_HW_TIMEOUT))
+			DBG("Too large timeout 0x%x requested for CMD%d!\n",
+			    count, cmd->opcode);
 		count = 0xE;
+	} else {
+		*too_big = false;
 	}
 
 	return count;
@@ -790,6 +796,16 @@ static void sdhci_set_transfer_irqs(struct sdhci_host *host)
 	sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
 }
 
+static void sdhci_set_data_timeout_irq(struct sdhci_host *host, bool enable)
+{
+	if (enable)
+		host->ier |= SDHCI_INT_DATA_TIMEOUT;
+	else
+		host->ier &= ~SDHCI_INT_DATA_TIMEOUT;
+	sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+	sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+}
+
 static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
 {
 	u8 count;
@@ -797,7 +813,17 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
 	if (host->ops->set_timeout) {
 		host->ops->set_timeout(host, cmd);
 	} else {
-		count = sdhci_calc_timeout(host, cmd);
+		bool too_big = false;
+
+		count = sdhci_calc_timeout(host, cmd, &too_big);
+
+		if (too_big &&
+		    host->quirks2 & SDHCI_QUIRK2_DISABLE_HW_TIMEOUT) {
+			sdhci_set_data_timeout_irq(host, false);
+		} else if (!(host->ier & SDHCI_INT_DATA_TIMEOUT)) {
+			sdhci_set_data_timeout_irq(host, true);
+		}
+
 		sdhci_writeb(host, count, SDHCI_TIMEOUT_CONTROL);
 	}
 }
@@ -3616,6 +3642,10 @@ int sdhci_setup_host(struct sdhci_host *host)
 		mmc->max_busy_timeout /= host->timeout_clk;
 	}
 
+	if (host->quirks2 & SDHCI_QUIRK2_DISABLE_HW_TIMEOUT &&
+	    !host->ops->get_max_timeout_count)
+		mmc->max_busy_timeout = 0;
+
 	mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23;
 	mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD;
 
drivers/mmc/host/sdhci.h +5 -0
index c95b0a4a7594..f6555c0f4ad3 100644
@@ -437,6 +437,11 @@ struct sdhci_host {
 #define SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN		(1<<15)
 /* Controller has CRC in 136 bit Command Response */
 #define SDHCI_QUIRK2_RSP_136_HAS_CRC			(1<<16)
+/*
+ * Disable HW timeout if the requested timeout is more than the maximum
+ * obtainable timeout.
+ */
+#define SDHCI_QUIRK2_DISABLE_HW_TIMEOUT			(1<<17)
 
 	int irq;		/* Device IRQ */
 	void __iomem *ioaddr;	/* Mapped address */
Signature · 1 line
2.17.0
#11 lore raw

PATCH v4 7/12 mmc: sdhci: Factor out target_timeout calculation

In reply to Kishon Vijay Abraham I

From: Adrian Hunter <[email protected]>

Factor out the target_timeout calculation so it can be re-used.

---
 drivers/mmc/host/sdhci.c | 48 +++++++++++++++++++++++++---------------
 1 file changed, 30 insertions(+), 18 deletions(-)
drivers/mmc/host/sdhci.c +30 -18
index b91163de4da4..c432ec3644aa 100644
@@ -709,6 +709,35 @@ static u32 sdhci_sdma_address(struct sdhci_host *host)
 		return sg_dma_address(host->data->sg);
 }
 
+static unsigned int sdhci_target_timeout(struct sdhci_host *host,
+					 struct mmc_command *cmd,
+					 struct mmc_data *data)
+{
+	unsigned int target_timeout;
+
+	/* timeout in us */
+	if (!data) {
+		target_timeout = cmd->busy_timeout * 1000;
+	} else {
+		target_timeout = DIV_ROUND_UP(data->timeout_ns, 1000);
+		if (host->clock && data->timeout_clks) {
+			unsigned long long val;
+
+			/*
+			 * data->timeout_clks is in units of clock cycles.
+			 * host->clock is in Hz.  target_timeout is in us.
+			 * Hence, us = 1000000 * cycles / Hz.  Round up.
+			 */
+			val = 1000000ULL * data->timeout_clks;
+			if (do_div(val, host->clock))
+				target_timeout++;
+			target_timeout += val;
+		}
+	}
+
+	return target_timeout;
+}
+
 static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd,
 			     bool *too_big)
 {
@@ -732,24 +761,7 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd,
 		return 0xE;
 
 	/* timeout in us */
-	if (!data)
-		target_timeout = cmd->busy_timeout * 1000;
-	else {
-		target_timeout = DIV_ROUND_UP(data->timeout_ns, 1000);
-		if (host->clock && data->timeout_clks) {
-			unsigned long long val;
-
-			/*
-			 * data->timeout_clks is in units of clock cycles.
-			 * host->clock is in Hz.  target_timeout is in us.
-			 * Hence, us = 1000000 * cycles / Hz.  Round up.
-			 */
-			val = 1000000ULL * data->timeout_clks;
-			if (do_div(val, host->clock))
-				target_timeout++;
-			target_timeout += val;
-		}
-	}
+	target_timeout = sdhci_target_timeout(host, cmd, data);
 
 	/*
 	 * Figure out needed cycles.
Signature · 1 line
2.17.0
#12 lore raw

PATCH v4 8/12 mmc: sdhci: Program a relatively accurate SW timeout value

In reply to Kishon Vijay Abraham I

sdhci has a 10 second timeout to catch devices that stop responding. In the case of quirk SDHCI_QUIRK2_DISABLE_HW_TIMEOUT, instead of programming 10 second arbitrary value, calculate the total time it would take for the entire transfer to happen and program the timeout value accordingly.

---
 drivers/mmc/host/sdhci.c | 36 ++++++++++++++++++++++++++++++++++++
 drivers/mmc/host/sdhci.h | 10 ++++++++++
 2 files changed, 46 insertions(+)
drivers/mmc/host/sdhci.c +36 -0
index c432ec3644aa..1ed0b8b6570a 100644
@@ -738,6 +738,39 @@ static unsigned int sdhci_target_timeout(struct sdhci_host *host,
 	return target_timeout;
 }
 
+static void sdhci_calc_sw_timeout(struct sdhci_host *host,
+				  struct mmc_command *cmd)
+{
+	struct mmc_data *data = cmd->data;
+	struct mmc_host *mmc = host->mmc;
+	struct mmc_ios *ios = &mmc->ios;
+	unsigned char bus_width = 1 << ios->bus_width;
+	unsigned int blksz;
+	unsigned int freq;
+	u64 target_timeout;
+	u64 transfer_time;
+
+	target_timeout = sdhci_target_timeout(host, cmd, data);
+	target_timeout *= NSEC_PER_USEC;
+
+	if (data) {
+		blksz = data->blksz;
+		freq = host->mmc->actual_clock ? : host->clock;
+		transfer_time = (u64)blksz * NSEC_PER_SEC * (8 / bus_width);
+		do_div(transfer_time, freq);
+		/* multiply by '2' to account for any unknowns */
+		transfer_time = transfer_time * 2;
+		/* calculate timeout for the entire data */
+		host->data_timeout = data->blocks * target_timeout +
+				     transfer_time;
+	} else {
+		host->data_timeout = target_timeout;
+	}
+
+	if (host->data_timeout)
+		host->data_timeout += MMC_CMD_TRANSFER_TIME;
+}
+
 static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd,
 			     bool *too_big)
 {
@@ -831,6 +864,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
 
 		if (too_big &&
 		    host->quirks2 & SDHCI_QUIRK2_DISABLE_HW_TIMEOUT) {
+			sdhci_calc_sw_timeout(host, cmd);
 			sdhci_set_data_timeout_irq(host, false);
 		} else if (!(host->ier & SDHCI_INT_DATA_TIMEOUT)) {
 			sdhci_set_data_timeout_irq(host, true);
@@ -845,6 +879,8 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
 	u8 ctrl;
 	struct mmc_data *data = cmd->data;
 
+	host->data_timeout = 0;
+
 	if (sdhci_data_line_cmd(cmd))
 		sdhci_set_timeout(host, cmd);
 
drivers/mmc/host/sdhci.h +10 -0
index f6555c0f4ad3..23966f887da6 100644
@@ -332,6 +332,14 @@ struct sdhci_adma2_64_desc {
 /* Allow for a a command request and a data request at the same time */
 #define SDHCI_MAX_MRQS		2
 
+/*
+ * 48bit command and 136 bit response in 100KHz clock could take upto 2.48ms.
+ * However since the start time of the command, the time between
+ * command and response, and the time between response and start of data is
+ * not known, set the command transfer time to 10ms.
+ */
+#define MMC_CMD_TRANSFER_TIME	(10 * NSEC_PER_MSEC) /* max 10 ms */
+
 enum sdhci_cookie {
 	COOKIE_UNMAPPED,
 	COOKIE_PRE_MAPPED,	/* mapped by sdhci_pre_req() */
@@ -555,6 +563,8 @@ struct sdhci_host {
 	/* Host SDMA buffer boundary. */
 	u32			sdma_boundary;
 
+	u64			data_timeout;
+
 	unsigned long private[0] ____cacheline_aligned;
 };
 
Signature · 1 line
2.17.0
maintainer
#13 lore raw

Re: [PATCH v4 08/12] mmc: sdhci: Program a relatively accurate SW timeout value

Quoted 8 lines · Kishon Vijay Abraham I

sdhci has a 10 second timeout to catch devices that stop responding. In the case of quirk SDHCI_QUIRK2_DISABLE_HW_TIMEOUT, instead of programming 10 second arbitrary value, calculate the total time it would take for the entire transfer to happen and program the timeout value accordingly.

Looks like I lost a couple of chunks of the patch, sorry :-( i.e.

drivers/mmc/host/sdhci.c +9 -7
index 1ed0b8b6570a..a019fd01ccf2 100644
@@ -1234,13 +1234,6 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
 		mdelay(1);
 	}
 
-	timeout = jiffies;
-	if (!cmd->data && cmd->busy_timeout > 9000)
-		timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
-	else
-		timeout += 10 * HZ;
-	sdhci_mod_timer(host, cmd->mrq, timeout);
-
 	host->cmd = cmd;
 	if (sdhci_data_line_cmd(cmd)) {
 		WARN_ON(host->data_cmd);
@@ -1280,6 +1273,15 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
 	    cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200)
 		flags |= SDHCI_CMD_DATA;
 
+	timeout = jiffies;
+	if (host->data_timeout)
+		timeout += nsecs_to_jiffies(host->data_timeout);
+	else if (!cmd->data && cmd->busy_timeout > 9000)
+		timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
+	else
+		timeout += 10 * HZ;
+	sdhci_mod_timer(host, cmd->mrq, timeout);
+
 	sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
 }
 EXPORT_SYMBOL_GPL(sdhci_send_command);
Quoted 95 lines
---
 drivers/mmc/host/sdhci.c | 36 ++++++++++++++++++++++++++++++++++++
 drivers/mmc/host/sdhci.h | 10 ++++++++++
 2 files changed, 46 insertions(+)
drivers/mmc/host/sdhci.c +36 -0
index c432ec3644aa..1ed0b8b6570a 100644
@@ -738,6 +738,39 @@ static unsigned int sdhci_target_timeout(struct sdhci_host *host,
 	return target_timeout;
 }
 
+static void sdhci_calc_sw_timeout(struct sdhci_host *host,
+				  struct mmc_command *cmd)
+{
+	struct mmc_data *data = cmd->data;
+	struct mmc_host *mmc = host->mmc;
+	struct mmc_ios *ios = &mmc->ios;
+	unsigned char bus_width = 1 << ios->bus_width;
+	unsigned int blksz;
+	unsigned int freq;
+	u64 target_timeout;
+	u64 transfer_time;
+
+	target_timeout = sdhci_target_timeout(host, cmd, data);
+	target_timeout *= NSEC_PER_USEC;
+
+	if (data) {
+		blksz = data->blksz;
+		freq = host->mmc->actual_clock ? : host->clock;
+		transfer_time = (u64)blksz * NSEC_PER_SEC * (8 / bus_width);
+		do_div(transfer_time, freq);
+		/* multiply by '2' to account for any unknowns */
+		transfer_time = transfer_time * 2;
+		/* calculate timeout for the entire data */
+		host->data_timeout = data->blocks * target_timeout +
+				     transfer_time;
+	} else {
+		host->data_timeout = target_timeout;
+	}
+
+	if (host->data_timeout)
+		host->data_timeout += MMC_CMD_TRANSFER_TIME;
+}
+
 static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd,
 			     bool *too_big)
 {
@@ -831,6 +864,7 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
 
 		if (too_big &&
 		    host->quirks2 & SDHCI_QUIRK2_DISABLE_HW_TIMEOUT) {
+			sdhci_calc_sw_timeout(host, cmd);
 			sdhci_set_data_timeout_irq(host, false);
 		} else if (!(host->ier & SDHCI_INT_DATA_TIMEOUT)) {
 			sdhci_set_data_timeout_irq(host, true);
@@ -845,6 +879,8 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
 	u8 ctrl;
 	struct mmc_data *data = cmd->data;
 
+	host->data_timeout = 0;
+
 	if (sdhci_data_line_cmd(cmd))
 		sdhci_set_timeout(host, cmd);
 
drivers/mmc/host/sdhci.h +10 -0
index f6555c0f4ad3..23966f887da6 100644
@@ -332,6 +332,14 @@ struct sdhci_adma2_64_desc {
 /* Allow for a a command request and a data request at the same time */
 #define SDHCI_MAX_MRQS		2
 
+/*
+ * 48bit command and 136 bit response in 100KHz clock could take upto 2.48ms.
+ * However since the start time of the command, the time between
+ * command and response, and the time between response and start of data is
+ * not known, set the command transfer time to 10ms.
+ */
+#define MMC_CMD_TRANSFER_TIME	(10 * NSEC_PER_MSEC) /* max 10 ms */
+
 enum sdhci_cookie {
 	COOKIE_UNMAPPED,
 	COOKIE_PRE_MAPPED,	/* mapped by sdhci_pre_req() */
@@ -555,6 +563,8 @@ struct sdhci_host {
 	/* Host SDMA buffer boundary. */
 	u32			sdma_boundary;
 
+	u64			data_timeout;
+
 	unsigned long private[0] ____cacheline_aligned;
 };
 
#14 lore raw

PATCH v4 9/12 mmc: sdhci-omap: Workaround for Errata i834

In reply to Kishon Vijay Abraham I

Errata i834 in AM572x Sitara Processors Silicon Revision 2.0, 1.1 (SPRZ429L July 2014–Revised April 2018 [1]) mentions the maximum obtainable timeout through MMC host controller is 700ms. And for commands taking longer than 700ms, hardware timeout should be disabled and software timeout should be used.

The workaround for Errata i834 can be achieved by adding SDHCI_QUIRK2_DISABLE_HW_TIMEOUT quirk in sdhci-omap.

[1] -> http://www.ti.com/lit/er/sprz429l/sprz429l.pdf

---
 drivers/mmc/host/sdhci-omap.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/mmc/host/sdhci-omap.c +2 -1
index 2d9ea23610d5..b2c54940d032 100644
@@ -715,7 +715,8 @@ static const struct sdhci_pltfm_data sdhci_omap_pdata = {
 		  SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC,
 	.quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN |
 		   SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
-		   SDHCI_QUIRK2_RSP_136_HAS_CRC,
+		   SDHCI_QUIRK2_RSP_136_HAS_CRC |
+		   SDHCI_QUIRK2_DISABLE_HW_TIMEOUT,
 	.ops = &sdhci_omap_ops,
 };
 
Signature · 1 line
2.17.0
maintainer Acked-by on v4 9/12
#15 lore raw

Re: [PATCH v4 09/12] mmc: sdhci-omap: Workaround for Errata i834

Quoted 6 lines · Kishon Vijay Abraham I

Errata i834 in AM572x Sitara Processors Silicon Revision 2.0, 1.1 (SPRZ429L July 2014–Revised April 2018 [1]) mentions the maximum obtainable timeout through MMC host controller is 700ms. And for commands taking longer than 700ms, hardware timeout should be disabled and software timeout should be used.

Quoted 7 lines

The workaround for Errata i834 can be achieved by adding SDHCI_QUIRK2_DISABLE_HW_TIMEOUT quirk in sdhci-omap.

[1] -> http://www.ti.com/lit/er/sprz429l/sprz429l.pdf

Quoted 19 lines
---
 drivers/mmc/host/sdhci-omap.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/mmc/host/sdhci-omap.c +2 -1
index 2d9ea23610d5..b2c54940d032 100644
@@ -715,7 +715,8 @@ static const struct sdhci_pltfm_data sdhci_omap_pdata = {
 		  SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC,
 	.quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN |
 		   SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
-		   SDHCI_QUIRK2_RSP_136_HAS_CRC,
+		   SDHCI_QUIRK2_RSP_136_HAS_CRC |
+		   SDHCI_QUIRK2_DISABLE_HW_TIMEOUT,
 	.ops = &sdhci_omap_ops,
 };
 
#16 lore raw

PATCH v4 10/12 dt-bindings: sdhci-omap: Add K2G specific binding

In reply to Kishon Vijay Abraham I

Add binding for the TI's sdhci-omap controller present in K2G.

---
 Documentation/devicetree/bindings/mmc/sdhci-omap.txt | 2 ++
 1 file changed, 2 insertions(+)
Documentation/devicetree/bindings/mmc/sdhci-omap.txt +2 -0
index 51775a372c06..8d09b837e350 100644
@@ -4,7 +4,9 @@ Refer to mmc.txt for standard MMC bindings.
 
 Required properties:
 - compatible: Should be "ti,dra7-sdhci" for DRA7 and DRA72 controllers
+	      Should be "ti,k2g-sdhci" for K2G
 - ti,hwmods: Must be "mmc<n>", <n> is controller instance starting 1
+	     (Not required for K2G).
 
 Example:
 	mmc1: mmc@4809c000 {
Signature · 1 line
2.17.0
#17 lore raw

PATCH v4 11/12 mmc: sdhci-omap: Add support for MMC/SD controller in k2g SoC

In reply to Kishon Vijay Abraham I

Add support for the new compatible added specifically to support k2g's MMC/SD controller.

---
 drivers/mmc/host/sdhci-omap.c | 5 +++++
 1 file changed, 5 insertions(+)
drivers/mmc/host/sdhci-omap.c +5 -0
index b2c54940d032..c1a19e8d0a08 100644
@@ -720,6 +720,10 @@ static const struct sdhci_pltfm_data sdhci_omap_pdata = {
 	.ops = &sdhci_omap_ops,
 };
 
+static const struct sdhci_omap_data k2g_data = {
+	.offset = 0x200,
+};
+
 static const struct sdhci_omap_data dra7_data = {
 	.offset = 0x200,
 	.flags	= SDHCI_OMAP_REQUIRE_IODELAY,
@@ -727,6 +731,7 @@ static const struct sdhci_omap_data dra7_data = {
 
 static const struct of_device_id omap_sdhci_match[] = {
 	{ .compatible = "ti,dra7-sdhci", .data = &dra7_data },
+	{ .compatible = "ti,k2g-sdhci", .data = &k2g_data },
 	{},
 };
 MODULE_DEVICE_TABLE(of, omap_sdhci_match);
Signature · 1 line
2.17.0
#18 lore raw

PATCH v4 12/12 mmc: sdhci-omap: Add sdhci_omap specific ops for enable_sdio_irq

In reply to Kishon Vijay Abraham I

Add sdhci_omap_enable_sdio_irq to set CTPL and CLKEXTFREE bits in MMCHS_CON register required to detect asynchronous card interrupt on DAT[1].

---
 drivers/mmc/host/sdhci-omap.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
drivers/mmc/host/sdhci-omap.c +19 -0
index c1a19e8d0a08..912b48df9dd1 100644
@@ -36,6 +36,7 @@
 #define CON_DDR			BIT(19)
 #define CON_CLKEXTFREE		BIT(16)
 #define CON_PADEN		BIT(15)
+#define CON_CTPL		BIT(11)
 #define CON_INIT		BIT(1)
 #define CON_OD			BIT(0)
 
@@ -226,6 +227,23 @@ static void sdhci_omap_conf_bus_power(struct sdhci_omap_host *omap_host,
 	}
 }
 
+static void sdhci_omap_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host);
+	u32 reg;
+
+	reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_CON);
+	if (enable)
+		reg |= (CON_CTPL | CON_CLKEXTFREE);
+	else
+		reg &= ~(CON_CTPL | CON_CLKEXTFREE);
+	sdhci_omap_writel(omap_host, SDHCI_OMAP_CON, reg);
+
+	sdhci_enable_sdio_irq(mmc, enable);
+}
+
 static inline void sdhci_omap_set_dll(struct sdhci_omap_host *omap_host,
 				      int count)
 {
@@ -962,6 +980,7 @@ static int sdhci_omap_probe(struct platform_device *pdev)
 	host->mmc_host_ops.set_ios = sdhci_omap_set_ios;
 	host->mmc_host_ops.card_busy = sdhci_omap_card_busy;
 	host->mmc_host_ops.execute_tuning = sdhci_omap_execute_tuning;
+	host->mmc_host_ops.enable_sdio_irq = sdhci_omap_enable_sdio_irq;
 
 	ret = sdhci_setup_host(host);
 	if (ret)
Signature · 1 line
2.17.0