xref: /openbmc/linux/drivers/mmc/host/dw_mmc-exynos.c (revision e6c784eded7b39caeaf2e9206336fa1daeccfd09)
1c3665006SThomas Abraham /*
2c3665006SThomas Abraham  * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver
3c3665006SThomas Abraham  *
4c3665006SThomas Abraham  * Copyright (C) 2012, Samsung Electronics Co., Ltd.
5c3665006SThomas Abraham  *
6c3665006SThomas Abraham  * This program is free software; you can redistribute it and/or modify
7c3665006SThomas Abraham  * it under the terms of the GNU General Public License as published by
8c3665006SThomas Abraham  * the Free Software Foundation; either version 2 of the License, or
9c3665006SThomas Abraham  * (at your option) any later version.
10c3665006SThomas Abraham  */
11c3665006SThomas Abraham 
12c3665006SThomas Abraham #include <linux/module.h>
13c3665006SThomas Abraham #include <linux/platform_device.h>
14c3665006SThomas Abraham #include <linux/clk.h>
15c3665006SThomas Abraham #include <linux/mmc/host.h>
16c3665006SThomas Abraham #include <linux/mmc/dw_mmc.h>
17c3665006SThomas Abraham #include <linux/of.h>
18c3665006SThomas Abraham #include <linux/of_gpio.h>
19c3665006SThomas Abraham 
20c3665006SThomas Abraham #include "dw_mmc.h"
21c3665006SThomas Abraham #include "dw_mmc-pltfm.h"
22c3665006SThomas Abraham 
23c3665006SThomas Abraham #define NUM_PINS(x)			(x + 2)
24c3665006SThomas Abraham 
25c3665006SThomas Abraham #define SDMMC_CLKSEL			0x09C
26c3665006SThomas Abraham #define SDMMC_CLKSEL_CCLK_SAMPLE(x)	(((x) & 7) << 0)
27c3665006SThomas Abraham #define SDMMC_CLKSEL_CCLK_DRIVE(x)	(((x) & 7) << 16)
28c3665006SThomas Abraham #define SDMMC_CLKSEL_CCLK_DIVIDER(x)	(((x) & 7) << 24)
29c3665006SThomas Abraham #define SDMMC_CLKSEL_GET_DRV_WD3(x)	(((x) >> 16) & 0x7)
30c3665006SThomas Abraham #define SDMMC_CLKSEL_TIMING(x, y, z)	(SDMMC_CLKSEL_CCLK_SAMPLE(x) |	\
31c3665006SThomas Abraham 					SDMMC_CLKSEL_CCLK_DRIVE(y) |	\
32c3665006SThomas Abraham 					SDMMC_CLKSEL_CCLK_DIVIDER(z))
33e2c63599SDoug Anderson #define SDMMC_CLKSEL_WAKEUP_INT		BIT(11)
34c3665006SThomas Abraham 
35c3665006SThomas Abraham #define EXYNOS4210_FIXED_CIU_CLK_DIV	2
36c3665006SThomas Abraham #define EXYNOS4412_FIXED_CIU_CLK_DIV	4
37c3665006SThomas Abraham 
38c3665006SThomas Abraham /* Variations in Exynos specific dw-mshc controller */
39c3665006SThomas Abraham enum dw_mci_exynos_type {
40c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS4210,
41c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS4412,
42c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS5250,
4300fd041bSYuvaraj Kumar C D 	DW_MCI_TYPE_EXYNOS5420,
44c3665006SThomas Abraham };
45c3665006SThomas Abraham 
46c3665006SThomas Abraham /* Exynos implementation specific driver private data */
47c3665006SThomas Abraham struct dw_mci_exynos_priv_data {
48c3665006SThomas Abraham 	enum dw_mci_exynos_type		ctrl_type;
49c3665006SThomas Abraham 	u8				ciu_div;
50c3665006SThomas Abraham 	u32				sdr_timing;
51c3665006SThomas Abraham 	u32				ddr_timing;
52c3665006SThomas Abraham };
53c3665006SThomas Abraham 
54c3665006SThomas Abraham static struct dw_mci_exynos_compatible {
55c3665006SThomas Abraham 	char				*compatible;
56c3665006SThomas Abraham 	enum dw_mci_exynos_type		ctrl_type;
57c3665006SThomas Abraham } exynos_compat[] = {
58c3665006SThomas Abraham 	{
59c3665006SThomas Abraham 		.compatible	= "samsung,exynos4210-dw-mshc",
60c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4210,
61c3665006SThomas Abraham 	}, {
62c3665006SThomas Abraham 		.compatible	= "samsung,exynos4412-dw-mshc",
63c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4412,
64c3665006SThomas Abraham 	}, {
65c3665006SThomas Abraham 		.compatible	= "samsung,exynos5250-dw-mshc",
66c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5250,
6700fd041bSYuvaraj Kumar C D 	}, {
6800fd041bSYuvaraj Kumar C D 		.compatible	= "samsung,exynos5420-dw-mshc",
6900fd041bSYuvaraj Kumar C D 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420,
70c3665006SThomas Abraham 	},
71c3665006SThomas Abraham };
72c3665006SThomas Abraham 
73c3665006SThomas Abraham static int dw_mci_exynos_priv_init(struct dw_mci *host)
74c3665006SThomas Abraham {
75*e6c784edSYuvaraj Kumar C D 	struct dw_mci_exynos_priv_data *priv = host->priv;
76c3665006SThomas Abraham 
77c3665006SThomas Abraham 	return 0;
78c3665006SThomas Abraham }
79c3665006SThomas Abraham 
80c3665006SThomas Abraham static int dw_mci_exynos_setup_clock(struct dw_mci *host)
81c3665006SThomas Abraham {
82c3665006SThomas Abraham 	struct dw_mci_exynos_priv_data *priv = host->priv;
83c3665006SThomas Abraham 
8400fd041bSYuvaraj Kumar C D 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5250 ||
8500fd041bSYuvaraj Kumar C D 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420)
86c3665006SThomas Abraham 		host->bus_hz /= (priv->ciu_div + 1);
87c3665006SThomas Abraham 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
88c3665006SThomas Abraham 		host->bus_hz /= EXYNOS4412_FIXED_CIU_CLK_DIV;
89c3665006SThomas Abraham 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
90c3665006SThomas Abraham 		host->bus_hz /= EXYNOS4210_FIXED_CIU_CLK_DIV;
91c3665006SThomas Abraham 
92c3665006SThomas Abraham 	return 0;
93c3665006SThomas Abraham }
94c3665006SThomas Abraham 
95e2c63599SDoug Anderson #ifdef CONFIG_PM_SLEEP
96e2c63599SDoug Anderson static int dw_mci_exynos_suspend(struct device *dev)
97e2c63599SDoug Anderson {
98e2c63599SDoug Anderson 	struct dw_mci *host = dev_get_drvdata(dev);
99e2c63599SDoug Anderson 
100e2c63599SDoug Anderson 	return dw_mci_suspend(host);
101e2c63599SDoug Anderson }
102e2c63599SDoug Anderson 
103e2c63599SDoug Anderson static int dw_mci_exynos_resume(struct device *dev)
104e2c63599SDoug Anderson {
105e2c63599SDoug Anderson 	struct dw_mci *host = dev_get_drvdata(dev);
106e2c63599SDoug Anderson 
107e2c63599SDoug Anderson 	return dw_mci_resume(host);
108e2c63599SDoug Anderson }
109e2c63599SDoug Anderson 
110e2c63599SDoug Anderson /**
111e2c63599SDoug Anderson  * dw_mci_exynos_resume_noirq - Exynos-specific resume code
112e2c63599SDoug Anderson  *
113e2c63599SDoug Anderson  * On exynos5420 there is a silicon errata that will sometimes leave the
114e2c63599SDoug Anderson  * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate
115e2c63599SDoug Anderson  * that it fired and we can clear it by writing a 1 back.  Clear it to prevent
116e2c63599SDoug Anderson  * interrupts from going off constantly.
117e2c63599SDoug Anderson  *
118e2c63599SDoug Anderson  * We run this code on all exynos variants because it doesn't hurt.
119e2c63599SDoug Anderson  */
120e2c63599SDoug Anderson 
121e2c63599SDoug Anderson static int dw_mci_exynos_resume_noirq(struct device *dev)
122e2c63599SDoug Anderson {
123e2c63599SDoug Anderson 	struct dw_mci *host = dev_get_drvdata(dev);
124e2c63599SDoug Anderson 	u32 clksel;
125e2c63599SDoug Anderson 
126e2c63599SDoug Anderson 	clksel = mci_readl(host, CLKSEL);
127e2c63599SDoug Anderson 	if (clksel & SDMMC_CLKSEL_WAKEUP_INT)
128e2c63599SDoug Anderson 		mci_writel(host, CLKSEL, clksel);
129e2c63599SDoug Anderson 
130e2c63599SDoug Anderson 	return 0;
131e2c63599SDoug Anderson }
132e2c63599SDoug Anderson #else
133e2c63599SDoug Anderson #define dw_mci_exynos_suspend		NULL
134e2c63599SDoug Anderson #define dw_mci_exynos_resume		NULL
135e2c63599SDoug Anderson #define dw_mci_exynos_resume_noirq	NULL
136e2c63599SDoug Anderson #endif /* CONFIG_PM_SLEEP */
137e2c63599SDoug Anderson 
138c3665006SThomas Abraham static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr)
139c3665006SThomas Abraham {
140c3665006SThomas Abraham 	/*
141c3665006SThomas Abraham 	 * Exynos4412 and Exynos5250 extends the use of CMD register with the
142c3665006SThomas Abraham 	 * use of bit 29 (which is reserved on standard MSHC controllers) for
143c3665006SThomas Abraham 	 * optionally bypassing the HOLD register for command and data. The
144c3665006SThomas Abraham 	 * HOLD register should be bypassed in case there is no phase shift
145c3665006SThomas Abraham 	 * applied on CMD/DATA that is sent to the card.
146c3665006SThomas Abraham 	 */
147c3665006SThomas Abraham 	if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL)))
148c3665006SThomas Abraham 		*cmdr |= SDMMC_CMD_USE_HOLD_REG;
149c3665006SThomas Abraham }
150c3665006SThomas Abraham 
151c3665006SThomas Abraham static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
152c3665006SThomas Abraham {
153c3665006SThomas Abraham 	struct dw_mci_exynos_priv_data *priv = host->priv;
154c3665006SThomas Abraham 
155c3665006SThomas Abraham 	if (ios->timing == MMC_TIMING_UHS_DDR50)
156c3665006SThomas Abraham 		mci_writel(host, CLKSEL, priv->ddr_timing);
157c3665006SThomas Abraham 	else
158c3665006SThomas Abraham 		mci_writel(host, CLKSEL, priv->sdr_timing);
159c3665006SThomas Abraham }
160c3665006SThomas Abraham 
161c3665006SThomas Abraham static int dw_mci_exynos_parse_dt(struct dw_mci *host)
162c3665006SThomas Abraham {
163*e6c784edSYuvaraj Kumar C D 	struct dw_mci_exynos_priv_data *priv;
164c3665006SThomas Abraham 	struct device_node *np = host->dev->of_node;
165c3665006SThomas Abraham 	u32 timing[2];
166c3665006SThomas Abraham 	u32 div = 0;
167*e6c784edSYuvaraj Kumar C D 	int idx;
168c3665006SThomas Abraham 	int ret;
169c3665006SThomas Abraham 
170*e6c784edSYuvaraj Kumar C D 	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
171*e6c784edSYuvaraj Kumar C D 	if (!priv) {
172*e6c784edSYuvaraj Kumar C D 		dev_err(host->dev, "mem alloc failed for private data\n");
173*e6c784edSYuvaraj Kumar C D 		return -ENOMEM;
174*e6c784edSYuvaraj Kumar C D 	}
175*e6c784edSYuvaraj Kumar C D 
176*e6c784edSYuvaraj Kumar C D 	for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
177*e6c784edSYuvaraj Kumar C D 		if (of_device_is_compatible(np, exynos_compat[idx].compatible))
178*e6c784edSYuvaraj Kumar C D 			priv->ctrl_type = exynos_compat[idx].ctrl_type;
179*e6c784edSYuvaraj Kumar C D 	}
180*e6c784edSYuvaraj Kumar C D 
181c3665006SThomas Abraham 	of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
182c3665006SThomas Abraham 	priv->ciu_div = div;
183c3665006SThomas Abraham 
184c3665006SThomas Abraham 	ret = of_property_read_u32_array(np,
185c3665006SThomas Abraham 			"samsung,dw-mshc-sdr-timing", timing, 2);
186c3665006SThomas Abraham 	if (ret)
187c3665006SThomas Abraham 		return ret;
188c3665006SThomas Abraham 
189c3665006SThomas Abraham 	ret = of_property_read_u32_array(np,
190c3665006SThomas Abraham 			"samsung,dw-mshc-ddr-timing", timing, 2);
191c3665006SThomas Abraham 	if (ret)
192c3665006SThomas Abraham 		return ret;
193c3665006SThomas Abraham 
194*e6c784edSYuvaraj Kumar C D 	priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
195c3665006SThomas Abraham 	priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
196*e6c784edSYuvaraj Kumar C D 	host->priv = priv;
197c3665006SThomas Abraham 	return 0;
198c3665006SThomas Abraham }
199c3665006SThomas Abraham 
2000f6e73d0SDongjin Kim /* Common capabilities of Exynos4/Exynos5 SoC */
2010f6e73d0SDongjin Kim static unsigned long exynos_dwmmc_caps[4] = {
202c3665006SThomas Abraham 	MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR |
203c3665006SThomas Abraham 		MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
204c3665006SThomas Abraham 	MMC_CAP_CMD23,
205c3665006SThomas Abraham 	MMC_CAP_CMD23,
206c3665006SThomas Abraham 	MMC_CAP_CMD23,
207c3665006SThomas Abraham };
208c3665006SThomas Abraham 
2090f6e73d0SDongjin Kim static const struct dw_mci_drv_data exynos_drv_data = {
2100f6e73d0SDongjin Kim 	.caps			= exynos_dwmmc_caps,
211c3665006SThomas Abraham 	.init			= dw_mci_exynos_priv_init,
212c3665006SThomas Abraham 	.setup_clock		= dw_mci_exynos_setup_clock,
213c3665006SThomas Abraham 	.prepare_command	= dw_mci_exynos_prepare_command,
214c3665006SThomas Abraham 	.set_ios		= dw_mci_exynos_set_ios,
215c3665006SThomas Abraham 	.parse_dt		= dw_mci_exynos_parse_dt,
216c3665006SThomas Abraham };
217c3665006SThomas Abraham 
218c3665006SThomas Abraham static const struct of_device_id dw_mci_exynos_match[] = {
2190f6e73d0SDongjin Kim 	{ .compatible = "samsung,exynos4412-dw-mshc",
2200f6e73d0SDongjin Kim 			.data = &exynos_drv_data, },
221c3665006SThomas Abraham 	{ .compatible = "samsung,exynos5250-dw-mshc",
2220f6e73d0SDongjin Kim 			.data = &exynos_drv_data, },
22300fd041bSYuvaraj Kumar C D 	{ .compatible = "samsung,exynos5420-dw-mshc",
22400fd041bSYuvaraj Kumar C D 			.data = &exynos_drv_data, },
225c3665006SThomas Abraham 	{},
226c3665006SThomas Abraham };
227517cb9f1SArnd Bergmann MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
228c3665006SThomas Abraham 
2299665f7f2SSachin Kamat static int dw_mci_exynos_probe(struct platform_device *pdev)
230c3665006SThomas Abraham {
2318e2b36eaSArnd Bergmann 	const struct dw_mci_drv_data *drv_data;
232c3665006SThomas Abraham 	const struct of_device_id *match;
233c3665006SThomas Abraham 
234c3665006SThomas Abraham 	match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
235c3665006SThomas Abraham 	drv_data = match->data;
236c3665006SThomas Abraham 	return dw_mci_pltfm_register(pdev, drv_data);
237c3665006SThomas Abraham }
238c3665006SThomas Abraham 
239e2c63599SDoug Anderson const struct dev_pm_ops dw_mci_exynos_pmops = {
240e2c63599SDoug Anderson 	SET_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend, dw_mci_exynos_resume)
241e2c63599SDoug Anderson 	.resume_noirq = dw_mci_exynos_resume_noirq,
242e2c63599SDoug Anderson 	.thaw_noirq = dw_mci_exynos_resume_noirq,
243e2c63599SDoug Anderson 	.restore_noirq = dw_mci_exynos_resume_noirq,
244e2c63599SDoug Anderson };
245e2c63599SDoug Anderson 
246c3665006SThomas Abraham static struct platform_driver dw_mci_exynos_pltfm_driver = {
247c3665006SThomas Abraham 	.probe		= dw_mci_exynos_probe,
248c3665006SThomas Abraham 	.remove		= __exit_p(dw_mci_pltfm_remove),
249c3665006SThomas Abraham 	.driver		= {
250c3665006SThomas Abraham 		.name		= "dwmmc_exynos",
25120183d50SSachin Kamat 		.of_match_table	= dw_mci_exynos_match,
252e2c63599SDoug Anderson 		.pm		= &dw_mci_exynos_pmops,
253c3665006SThomas Abraham 	},
254c3665006SThomas Abraham };
255c3665006SThomas Abraham 
256c3665006SThomas Abraham module_platform_driver(dw_mci_exynos_pltfm_driver);
257c3665006SThomas Abraham 
258c3665006SThomas Abraham MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
259c3665006SThomas Abraham MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
260c3665006SThomas Abraham MODULE_LICENSE("GPL v2");
261c3665006SThomas Abraham MODULE_ALIAS("platform:dwmmc-exynos");
262