xref: /openbmc/linux/drivers/mmc/host/sdhci-pltfm.c (revision 899171dc)
182c29810SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2a3456a2dSRichard Röjfors /*
3a3456a2dSRichard Röjfors  * sdhci-pltfm.c Support for SDHCI platform devices
4a3456a2dSRichard Röjfors  * Copyright (c) 2009 Intel Corporation
5a3456a2dSRichard Röjfors  *
681e49922SJerry Huang  * Copyright (c) 2007, 2011 Freescale Semiconductor, Inc.
738576af1SShawn Guo  * Copyright (c) 2009 MontaVista Software, Inc.
838576af1SShawn Guo  *
938576af1SShawn Guo  * Authors: Xiaobo Xie <X.Xie@freescale.com>
1038576af1SShawn Guo  *	    Anton Vorontsov <avorontsov@ru.mvista.com>
11a3456a2dSRichard Röjfors  */
12a3456a2dSRichard Röjfors 
13a3456a2dSRichard Röjfors /* Supports:
14a3456a2dSRichard Röjfors  * SDHCI platform devices
15a3456a2dSRichard Röjfors  *
16a3456a2dSRichard Röjfors  * Inspired by sdhci-pci.c, by Pierre Ossman
17a3456a2dSRichard Röjfors  */
18a3456a2dSRichard Röjfors 
1985d6509dSShawn Guo #include <linux/err.h>
2088b47679SPaul Gortmaker #include <linux/module.h>
218199d312SAdrian Hunter #include <linux/property.h>
2238576af1SShawn Guo #include <linux/of.h>
2338576af1SShawn Guo #ifdef CONFIG_PPC
2438576af1SShawn Guo #include <asm/machdep.h>
2538576af1SShawn Guo #endif
26515033f9SAnton Vorontsov #include "sdhci-pltfm.h"
27a3456a2dSRichard Röjfors 
sdhci_pltfm_clk_get_max_clock(struct sdhci_host * host)28d005d943SLars-Peter Clausen unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host)
29d005d943SLars-Peter Clausen {
30d005d943SLars-Peter Clausen 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
31d005d943SLars-Peter Clausen 
32d005d943SLars-Peter Clausen 	return clk_get_rate(pltfm_host->clk);
33d005d943SLars-Peter Clausen }
34d005d943SLars-Peter Clausen EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_max_clock);
35d005d943SLars-Peter Clausen 
36c915568dSLars-Peter Clausen static const struct sdhci_ops sdhci_pltfm_ops = {
371771059cSRussell King 	.set_clock = sdhci_set_clock,
382317f56cSRussell King 	.set_bus_width = sdhci_set_bus_width,
3903231f9bSRussell King 	.reset = sdhci_reset,
4096d7b78cSRussell King 	.set_uhs_signaling = sdhci_set_uhs_signaling,
41a3456a2dSRichard Röjfors };
42a3456a2dSRichard Röjfors 
sdhci_wp_inverted(struct device * dev)438199d312SAdrian Hunter static bool sdhci_wp_inverted(struct device *dev)
4438576af1SShawn Guo {
458199d312SAdrian Hunter 	if (device_property_present(dev, "sdhci,wp-inverted") ||
468199d312SAdrian Hunter 	    device_property_present(dev, "wp-inverted"))
4738576af1SShawn Guo 		return true;
4838576af1SShawn Guo 
4938576af1SShawn Guo 	/* Old device trees don't have the wp-inverted property. */
5038576af1SShawn Guo #ifdef CONFIG_PPC
5138576af1SShawn Guo 	return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds);
5238576af1SShawn Guo #else
5338576af1SShawn Guo 	return false;
5438576af1SShawn Guo #endif /* CONFIG_PPC */
5538576af1SShawn Guo }
5638576af1SShawn Guo 
sdhci_get_compatibility(struct platform_device * pdev)578199d312SAdrian Hunter static void sdhci_get_compatibility(struct platform_device *pdev)
5838576af1SShawn Guo {
5938576af1SShawn Guo 	struct sdhci_host *host = platform_get_drvdata(pdev);
608199d312SAdrian Hunter 	struct device_node *np = pdev->dev.of_node;
6138576af1SShawn Guo 
628199d312SAdrian Hunter 	if (!np)
638199d312SAdrian Hunter 		return;
648ed765aaSDaniel Drake 
6581e49922SJerry Huang 	if (of_device_is_compatible(np, "fsl,p2020-rev1-esdhc"))
6681e49922SJerry Huang 		host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
6781e49922SJerry Huang 
68147c3b33SJerry Huang 	if (of_device_is_compatible(np, "fsl,p2020-esdhc") ||
69147c3b33SJerry Huang 	    of_device_is_compatible(np, "fsl,p1010-esdhc") ||
704590205bSChunhe Lan 	    of_device_is_compatible(np, "fsl,t4240-esdhc") ||
71147c3b33SJerry Huang 	    of_device_is_compatible(np, "fsl,mpc8536-esdhc"))
72147c3b33SJerry Huang 		host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
7338576af1SShawn Guo }
748199d312SAdrian Hunter 
sdhci_get_property(struct platform_device * pdev)758199d312SAdrian Hunter void sdhci_get_property(struct platform_device *pdev)
768199d312SAdrian Hunter {
778199d312SAdrian Hunter 	struct device *dev = &pdev->dev;
788199d312SAdrian Hunter 	struct sdhci_host *host = platform_get_drvdata(pdev);
798199d312SAdrian Hunter 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
808199d312SAdrian Hunter 	u32 bus_width;
818199d312SAdrian Hunter 
828199d312SAdrian Hunter 	if (device_property_present(dev, "sdhci,auto-cmd12"))
838199d312SAdrian Hunter 		host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
848199d312SAdrian Hunter 
858199d312SAdrian Hunter 	if (device_property_present(dev, "sdhci,1-bit-only") ||
868199d312SAdrian Hunter 	    (device_property_read_u32(dev, "bus-width", &bus_width) == 0 &&
878199d312SAdrian Hunter 	    bus_width == 1))
888199d312SAdrian Hunter 		host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
898199d312SAdrian Hunter 
908199d312SAdrian Hunter 	if (sdhci_wp_inverted(dev))
918199d312SAdrian Hunter 		host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
928199d312SAdrian Hunter 
938199d312SAdrian Hunter 	if (device_property_present(dev, "broken-cd"))
948199d312SAdrian Hunter 		host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
958199d312SAdrian Hunter 
968199d312SAdrian Hunter 	if (device_property_present(dev, "no-1-8-v"))
978199d312SAdrian Hunter 		host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V;
988199d312SAdrian Hunter 
998199d312SAdrian Hunter 	sdhci_get_compatibility(pdev);
1008199d312SAdrian Hunter 
1018199d312SAdrian Hunter 	device_property_read_u32(dev, "clock-frequency", &pltfm_host->clock);
1028199d312SAdrian Hunter 
1038199d312SAdrian Hunter 	if (device_property_present(dev, "keep-power-in-suspend"))
1048199d312SAdrian Hunter 		host->mmc->pm_caps |= MMC_PM_KEEP_POWER;
1058199d312SAdrian Hunter 
1068199d312SAdrian Hunter 	if (device_property_read_bool(dev, "wakeup-source") ||
1078199d312SAdrian Hunter 	    device_property_read_bool(dev, "enable-sdio-wakeup")) /* legacy */
1088199d312SAdrian Hunter 		host->mmc->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;
1098199d312SAdrian Hunter }
1108199d312SAdrian Hunter EXPORT_SYMBOL_GPL(sdhci_get_property);
11138576af1SShawn Guo 
sdhci_pltfm_init(struct platform_device * pdev,const struct sdhci_pltfm_data * pdata,size_t priv_size)11285d6509dSShawn Guo struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev,
1130e748234SChristian Daudt 				    const struct sdhci_pltfm_data *pdata,
1140e748234SChristian Daudt 				    size_t priv_size)
115a3456a2dSRichard Röjfors {
116a3456a2dSRichard Röjfors 	struct sdhci_host *host;
117378382b8SMasahiro Yamada 	void __iomem *ioaddr;
1185c59065bSMasahiro Yamada 	int irq, ret;
119a3456a2dSRichard Röjfors 
12073e01ab2SJisheng Zhang 	ioaddr = devm_platform_ioremap_resource(pdev, 0);
121378382b8SMasahiro Yamada 	if (IS_ERR(ioaddr)) {
122378382b8SMasahiro Yamada 		ret = PTR_ERR(ioaddr);
123a3456a2dSRichard Röjfors 		goto err;
124a3456a2dSRichard Röjfors 	}
125a3456a2dSRichard Röjfors 
1265c59065bSMasahiro Yamada 	irq = platform_get_irq(pdev, 0);
1275c59065bSMasahiro Yamada 	if (irq < 0) {
1285c59065bSMasahiro Yamada 		ret = irq;
1295c59065bSMasahiro Yamada 		goto err;
1305c59065bSMasahiro Yamada 	}
1315c59065bSMasahiro Yamada 
1320e748234SChristian Daudt 	host = sdhci_alloc_host(&pdev->dev,
1330e748234SChristian Daudt 		sizeof(struct sdhci_pltfm_host) + priv_size);
134a3456a2dSRichard Röjfors 
135a3456a2dSRichard Röjfors 	if (IS_ERR(host)) {
136a3456a2dSRichard Röjfors 		ret = PTR_ERR(host);
137a3456a2dSRichard Röjfors 		goto err;
138a3456a2dSRichard Röjfors 	}
139a3456a2dSRichard Röjfors 
140378382b8SMasahiro Yamada 	host->ioaddr = ioaddr;
1415c59065bSMasahiro Yamada 	host->irq = irq;
14285d6509dSShawn Guo 	host->hw_name = dev_name(&pdev->dev);
143a7626b7aSAnton Vorontsov 	if (pdata && pdata->ops)
144a7626b7aSAnton Vorontsov 		host->ops = pdata->ops;
145a7626b7aSAnton Vorontsov 	else
146a3456a2dSRichard Röjfors 		host->ops = &sdhci_pltfm_ops;
147ad82ab65SAl Cooper 	if (pdata) {
148a7626b7aSAnton Vorontsov 		host->quirks = pdata->quirks;
149ad82ab65SAl Cooper 		host->quirks2 = pdata->quirks2;
150ad82ab65SAl Cooper 	}
151ad82ab65SAl Cooper 
152a3456a2dSRichard Röjfors 	platform_set_drvdata(pdev, host);
153a3456a2dSRichard Röjfors 
15485d6509dSShawn Guo 	return host;
155a3456a2dSRichard Röjfors err:
15685d6509dSShawn Guo 	dev_err(&pdev->dev, "%s failed %d\n", __func__, ret);
15785d6509dSShawn Guo 	return ERR_PTR(ret);
158a3456a2dSRichard Röjfors }
159f0de8369SShawn Guo EXPORT_SYMBOL_GPL(sdhci_pltfm_init);
160a3456a2dSRichard Röjfors 
sdhci_pltfm_free(struct platform_device * pdev)16185d6509dSShawn Guo void sdhci_pltfm_free(struct platform_device *pdev)
162a3456a2dSRichard Röjfors {
163a3456a2dSRichard Röjfors 	struct sdhci_host *host = platform_get_drvdata(pdev);
164a3456a2dSRichard Röjfors 
165a3456a2dSRichard Röjfors 	sdhci_free_host(host);
16685d6509dSShawn Guo }
167f0de8369SShawn Guo EXPORT_SYMBOL_GPL(sdhci_pltfm_free);
16885d6509dSShawn Guo 
sdhci_pltfm_init_and_add_host(struct platform_device * pdev,const struct sdhci_pltfm_data * pdata,size_t priv_size)169*899171dcSAdrian Hunter int sdhci_pltfm_init_and_add_host(struct platform_device *pdev,
1700e748234SChristian Daudt 				  const struct sdhci_pltfm_data *pdata,
1710e748234SChristian Daudt 				  size_t priv_size)
17285d6509dSShawn Guo {
17385d6509dSShawn Guo 	struct sdhci_host *host;
17485d6509dSShawn Guo 	int ret = 0;
17585d6509dSShawn Guo 
1760e748234SChristian Daudt 	host = sdhci_pltfm_init(pdev, pdata, priv_size);
17785d6509dSShawn Guo 	if (IS_ERR(host))
17885d6509dSShawn Guo 		return PTR_ERR(host);
17985d6509dSShawn Guo 
1808199d312SAdrian Hunter 	sdhci_get_property(pdev);
18138576af1SShawn Guo 
18285d6509dSShawn Guo 	ret = sdhci_add_host(host);
18385d6509dSShawn Guo 	if (ret)
18485d6509dSShawn Guo 		sdhci_pltfm_free(pdev);
18585d6509dSShawn Guo 
18685d6509dSShawn Guo 	return ret;
18785d6509dSShawn Guo }
188*899171dcSAdrian Hunter EXPORT_SYMBOL_GPL(sdhci_pltfm_init_and_add_host);
18985d6509dSShawn Guo 
sdhci_pltfm_remove(struct platform_device * pdev)19032261f9bSAdrian Hunter void sdhci_pltfm_remove(struct platform_device *pdev)
19132261f9bSAdrian Hunter {
19232261f9bSAdrian Hunter 	struct sdhci_host *host = platform_get_drvdata(pdev);
19332261f9bSAdrian Hunter 	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
19432261f9bSAdrian Hunter 
19532261f9bSAdrian Hunter 	sdhci_remove_host(host, dead);
19632261f9bSAdrian Hunter 	sdhci_pltfm_free(pdev);
19732261f9bSAdrian Hunter }
19832261f9bSAdrian Hunter EXPORT_SYMBOL_GPL(sdhci_pltfm_remove);
19932261f9bSAdrian Hunter 
2002b330999SUlf Hansson #ifdef CONFIG_PM_SLEEP
sdhci_pltfm_suspend(struct device * dev)20183a7b32aSMasahiro Yamada int sdhci_pltfm_suspend(struct device *dev)
202be8ae09dSGiuseppe Cavallaro {
20329495aa0SManuel Lauss 	struct sdhci_host *host = dev_get_drvdata(dev);
2041ab0d2d7SMasahiro Yamada 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
2051ab0d2d7SMasahiro Yamada 	int ret;
206be8ae09dSGiuseppe Cavallaro 
207d38dcad4SAdrian Hunter 	if (host->tuning_mode != SDHCI_TUNING_MODE_3)
208d38dcad4SAdrian Hunter 		mmc_retune_needed(host->mmc);
209d38dcad4SAdrian Hunter 
2101ab0d2d7SMasahiro Yamada 	ret = sdhci_suspend_host(host);
2111ab0d2d7SMasahiro Yamada 	if (ret)
2121ab0d2d7SMasahiro Yamada 		return ret;
2131ab0d2d7SMasahiro Yamada 
2141ab0d2d7SMasahiro Yamada 	clk_disable_unprepare(pltfm_host->clk);
2151ab0d2d7SMasahiro Yamada 
2161ab0d2d7SMasahiro Yamada 	return 0;
217be8ae09dSGiuseppe Cavallaro }
21883a7b32aSMasahiro Yamada EXPORT_SYMBOL_GPL(sdhci_pltfm_suspend);
219be8ae09dSGiuseppe Cavallaro 
sdhci_pltfm_resume(struct device * dev)22083a7b32aSMasahiro Yamada int sdhci_pltfm_resume(struct device *dev)
221be8ae09dSGiuseppe Cavallaro {
22229495aa0SManuel Lauss 	struct sdhci_host *host = dev_get_drvdata(dev);
2231ab0d2d7SMasahiro Yamada 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
2241ab0d2d7SMasahiro Yamada 	int ret;
225be8ae09dSGiuseppe Cavallaro 
2261ab0d2d7SMasahiro Yamada 	ret = clk_prepare_enable(pltfm_host->clk);
2271ab0d2d7SMasahiro Yamada 	if (ret)
2281ab0d2d7SMasahiro Yamada 		return ret;
2291ab0d2d7SMasahiro Yamada 
2301ab0d2d7SMasahiro Yamada 	ret = sdhci_resume_host(host);
2311ab0d2d7SMasahiro Yamada 	if (ret)
2321ab0d2d7SMasahiro Yamada 		clk_disable_unprepare(pltfm_host->clk);
2331ab0d2d7SMasahiro Yamada 
2341ab0d2d7SMasahiro Yamada 	return ret;
235be8ae09dSGiuseppe Cavallaro }
23683a7b32aSMasahiro Yamada EXPORT_SYMBOL_GPL(sdhci_pltfm_resume);
2372b330999SUlf Hansson #endif
23829495aa0SManuel Lauss 
23929495aa0SManuel Lauss const struct dev_pm_ops sdhci_pltfm_pmops = {
2402b330999SUlf Hansson 	SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, sdhci_pltfm_resume)
24129495aa0SManuel Lauss };
24229495aa0SManuel Lauss EXPORT_SYMBOL_GPL(sdhci_pltfm_pmops);
243f0de8369SShawn Guo 
sdhci_pltfm_drv_init(void)244f0de8369SShawn Guo static int __init sdhci_pltfm_drv_init(void)
245f0de8369SShawn Guo {
246f0de8369SShawn Guo 	pr_info("sdhci-pltfm: SDHCI platform and OF driver helper\n");
247f0de8369SShawn Guo 
248f0de8369SShawn Guo 	return 0;
249f0de8369SShawn Guo }
250f0de8369SShawn Guo module_init(sdhci_pltfm_drv_init);
251f0de8369SShawn Guo 
sdhci_pltfm_drv_exit(void)252f0de8369SShawn Guo static void __exit sdhci_pltfm_drv_exit(void)
253f0de8369SShawn Guo {
254f0de8369SShawn Guo }
255f0de8369SShawn Guo module_exit(sdhci_pltfm_drv_exit);
256f0de8369SShawn Guo 
257f0de8369SShawn Guo MODULE_DESCRIPTION("SDHCI platform and OF driver helper");
258f0de8369SShawn Guo MODULE_AUTHOR("Intel Corporation");
259f0de8369SShawn Guo MODULE_LICENSE("GPL v2");
260