xref: /linux/drivers/mmc/host/dw_mmc-exynos.c (revision 21b2cec61c04bd175f0860d9411a472d5a0e7ba1)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2c3665006SThomas Abraham /*
3c3665006SThomas Abraham  * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver
4c3665006SThomas Abraham  *
5c3665006SThomas Abraham  * Copyright (C) 2012, Samsung Electronics Co., Ltd.
6c3665006SThomas Abraham  */
7c3665006SThomas Abraham 
8c3665006SThomas Abraham #include <linux/module.h>
9c3665006SThomas Abraham #include <linux/platform_device.h>
10c3665006SThomas Abraham #include <linux/clk.h>
11c3665006SThomas Abraham #include <linux/mmc/host.h>
12c537a1c5SSeungwon Jeon #include <linux/mmc/mmc.h>
13c3665006SThomas Abraham #include <linux/of.h>
14c3665006SThomas Abraham #include <linux/of_gpio.h>
15cf5237efSShawn Lin #include <linux/pm_runtime.h>
16c537a1c5SSeungwon Jeon #include <linux/slab.h>
17c3665006SThomas Abraham 
18c3665006SThomas Abraham #include "dw_mmc.h"
19c3665006SThomas Abraham #include "dw_mmc-pltfm.h"
200b5fce48SSeungwon Jeon #include "dw_mmc-exynos.h"
21c6d9dedaSSeungwon Jeon 
22c3665006SThomas Abraham /* Variations in Exynos specific dw-mshc controller */
23c3665006SThomas Abraham enum dw_mci_exynos_type {
24c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS4210,
25c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS4412,
26c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS5250,
2700fd041bSYuvaraj Kumar C D 	DW_MCI_TYPE_EXYNOS5420,
286bce431cSYuvaraj Kumar C D 	DW_MCI_TYPE_EXYNOS5420_SMU,
2989ad2be7SAbhilash Kesavan 	DW_MCI_TYPE_EXYNOS7,
3089ad2be7SAbhilash Kesavan 	DW_MCI_TYPE_EXYNOS7_SMU,
31c3665006SThomas Abraham };
32c3665006SThomas Abraham 
33c3665006SThomas Abraham /* Exynos implementation specific driver private data */
34c3665006SThomas Abraham struct dw_mci_exynos_priv_data {
35c3665006SThomas Abraham 	enum dw_mci_exynos_type		ctrl_type;
36c3665006SThomas Abraham 	u8				ciu_div;
37c3665006SThomas Abraham 	u32				sdr_timing;
38c3665006SThomas Abraham 	u32				ddr_timing;
3980113132SSeungwon Jeon 	u32				hs400_timing;
4080113132SSeungwon Jeon 	u32				tuned_sample;
41c6d9dedaSSeungwon Jeon 	u32				cur_speed;
4280113132SSeungwon Jeon 	u32				dqs_delay;
4380113132SSeungwon Jeon 	u32				saved_dqs_en;
4480113132SSeungwon Jeon 	u32				saved_strobe_ctrl;
45c3665006SThomas Abraham };
46c3665006SThomas Abraham 
47c3665006SThomas Abraham static struct dw_mci_exynos_compatible {
48c3665006SThomas Abraham 	char				*compatible;
49c3665006SThomas Abraham 	enum dw_mci_exynos_type		ctrl_type;
50c3665006SThomas Abraham } exynos_compat[] = {
51c3665006SThomas Abraham 	{
52c3665006SThomas Abraham 		.compatible	= "samsung,exynos4210-dw-mshc",
53c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4210,
54c3665006SThomas Abraham 	}, {
55c3665006SThomas Abraham 		.compatible	= "samsung,exynos4412-dw-mshc",
56c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4412,
57c3665006SThomas Abraham 	}, {
58c3665006SThomas Abraham 		.compatible	= "samsung,exynos5250-dw-mshc",
59c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5250,
6000fd041bSYuvaraj Kumar C D 	}, {
6100fd041bSYuvaraj Kumar C D 		.compatible	= "samsung,exynos5420-dw-mshc",
6200fd041bSYuvaraj Kumar C D 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420,
636bce431cSYuvaraj Kumar C D 	}, {
646bce431cSYuvaraj Kumar C D 		.compatible	= "samsung,exynos5420-dw-mshc-smu",
656bce431cSYuvaraj Kumar C D 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420_SMU,
6689ad2be7SAbhilash Kesavan 	}, {
6789ad2be7SAbhilash Kesavan 		.compatible	= "samsung,exynos7-dw-mshc",
6889ad2be7SAbhilash Kesavan 		.ctrl_type	= DW_MCI_TYPE_EXYNOS7,
6989ad2be7SAbhilash Kesavan 	}, {
7089ad2be7SAbhilash Kesavan 		.compatible	= "samsung,exynos7-dw-mshc-smu",
7189ad2be7SAbhilash Kesavan 		.ctrl_type	= DW_MCI_TYPE_EXYNOS7_SMU,
72c3665006SThomas Abraham 	},
73c3665006SThomas Abraham };
74c3665006SThomas Abraham 
7580113132SSeungwon Jeon static inline u8 dw_mci_exynos_get_ciu_div(struct dw_mci *host)
7680113132SSeungwon Jeon {
7780113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
7880113132SSeungwon Jeon 
7980113132SSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
8080113132SSeungwon Jeon 		return EXYNOS4412_FIXED_CIU_CLK_DIV;
8180113132SSeungwon Jeon 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
8280113132SSeungwon Jeon 		return EXYNOS4210_FIXED_CIU_CLK_DIV;
8380113132SSeungwon Jeon 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
8480113132SSeungwon Jeon 			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
8580113132SSeungwon Jeon 		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL64)) + 1;
8680113132SSeungwon Jeon 	else
8780113132SSeungwon Jeon 		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL)) + 1;
8880113132SSeungwon Jeon }
8980113132SSeungwon Jeon 
905659eeadSJaehoon Chung static void dw_mci_exynos_config_smu(struct dw_mci *host)
91c3665006SThomas Abraham {
92e6c784edSYuvaraj Kumar C D 	struct dw_mci_exynos_priv_data *priv = host->priv;
93c3665006SThomas Abraham 
945659eeadSJaehoon Chung 	/*
955659eeadSJaehoon Chung 	 * If Exynos is provided the Security management,
965659eeadSJaehoon Chung 	 * set for non-ecryption mode at this time.
975659eeadSJaehoon Chung 	 */
9889ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU ||
9989ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU) {
1006bce431cSYuvaraj Kumar C D 		mci_writel(host, MPSBEGIN0, 0);
1010b5fce48SSeungwon Jeon 		mci_writel(host, MPSEND0, SDMMC_ENDING_SEC_NR_MAX);
1020b5fce48SSeungwon Jeon 		mci_writel(host, MPSCTRL0, SDMMC_MPSCTRL_SECURE_WRITE_BIT |
1030b5fce48SSeungwon Jeon 			   SDMMC_MPSCTRL_NON_SECURE_READ_BIT |
1040b5fce48SSeungwon Jeon 			   SDMMC_MPSCTRL_VALID |
1050b5fce48SSeungwon Jeon 			   SDMMC_MPSCTRL_NON_SECURE_WRITE_BIT);
1066bce431cSYuvaraj Kumar C D 	}
1075659eeadSJaehoon Chung }
1085659eeadSJaehoon Chung 
1095659eeadSJaehoon Chung static int dw_mci_exynos_priv_init(struct dw_mci *host)
1105659eeadSJaehoon Chung {
1115659eeadSJaehoon Chung 	struct dw_mci_exynos_priv_data *priv = host->priv;
1125659eeadSJaehoon Chung 
1135659eeadSJaehoon Chung 	dw_mci_exynos_config_smu(host);
1146bce431cSYuvaraj Kumar C D 
11580113132SSeungwon Jeon 	if (priv->ctrl_type >= DW_MCI_TYPE_EXYNOS5420) {
11680113132SSeungwon Jeon 		priv->saved_strobe_ctrl = mci_readl(host, HS400_DLINE_CTRL);
11780113132SSeungwon Jeon 		priv->saved_dqs_en = mci_readl(host, HS400_DQS_EN);
11880113132SSeungwon Jeon 		priv->saved_dqs_en |= AXI_NON_BLOCKING_WR;
11980113132SSeungwon Jeon 		mci_writel(host, HS400_DQS_EN, priv->saved_dqs_en);
12080113132SSeungwon Jeon 		if (!priv->dqs_delay)
12180113132SSeungwon Jeon 			priv->dqs_delay =
12280113132SSeungwon Jeon 				DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl);
12380113132SSeungwon Jeon 	}
12480113132SSeungwon Jeon 
125a2a1fed8SSeungwon Jeon 	host->bus_hz /= (priv->ciu_div + 1);
126a2a1fed8SSeungwon Jeon 
127c3665006SThomas Abraham 	return 0;
128c3665006SThomas Abraham }
129c3665006SThomas Abraham 
13080113132SSeungwon Jeon static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing)
13180113132SSeungwon Jeon {
13280113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
13380113132SSeungwon Jeon 	u32 clksel;
13480113132SSeungwon Jeon 
13580113132SSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
13680113132SSeungwon Jeon 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
13780113132SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL64);
13880113132SSeungwon Jeon 	else
13980113132SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL);
14080113132SSeungwon Jeon 
14180113132SSeungwon Jeon 	clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing;
14280113132SSeungwon Jeon 
14380113132SSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
14480113132SSeungwon Jeon 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
14580113132SSeungwon Jeon 		mci_writel(host, CLKSEL64, clksel);
14680113132SSeungwon Jeon 	else
14780113132SSeungwon Jeon 		mci_writel(host, CLKSEL, clksel);
148aaaaeb7aSJaehoon Chung 
149aaaaeb7aSJaehoon Chung 	/*
150aaaaeb7aSJaehoon Chung 	 * Exynos4412 and Exynos5250 extends the use of CMD register with the
151aaaaeb7aSJaehoon Chung 	 * use of bit 29 (which is reserved on standard MSHC controllers) for
152aaaaeb7aSJaehoon Chung 	 * optionally bypassing the HOLD register for command and data. The
153aaaaeb7aSJaehoon Chung 	 * HOLD register should be bypassed in case there is no phase shift
154aaaaeb7aSJaehoon Chung 	 * applied on CMD/DATA that is sent to the card.
155aaaaeb7aSJaehoon Chung 	 */
15642f989c0SJaehoon Chung 	if (!SDMMC_CLKSEL_GET_DRV_WD3(clksel) && host->slot)
15742f989c0SJaehoon Chung 		set_bit(DW_MMC_CARD_NO_USE_HOLD, &host->slot->flags);
15880113132SSeungwon Jeon }
15980113132SSeungwon Jeon 
160cf5237efSShawn Lin #ifdef CONFIG_PM
161cf5237efSShawn Lin static int dw_mci_exynos_runtime_resume(struct device *dev)
162e2c63599SDoug Anderson {
163e2c63599SDoug Anderson 	struct dw_mci *host = dev_get_drvdata(dev);
164e22842ddSJaehoon Chung 	int ret;
165e22842ddSJaehoon Chung 
166e22842ddSJaehoon Chung 	ret = dw_mci_runtime_resume(dev);
167e22842ddSJaehoon Chung 	if (ret)
168e22842ddSJaehoon Chung 		return ret;
169e2c63599SDoug Anderson 
1705659eeadSJaehoon Chung 	dw_mci_exynos_config_smu(host);
171e22842ddSJaehoon Chung 
172e22842ddSJaehoon Chung 	return ret;
173e2c63599SDoug Anderson }
174ecf7c7c5SMarek Szyprowski #endif /* CONFIG_PM */
175ecf7c7c5SMarek Szyprowski 
176ecf7c7c5SMarek Szyprowski #ifdef CONFIG_PM_SLEEP
177ecf7c7c5SMarek Szyprowski /**
178ecf7c7c5SMarek Szyprowski  * dw_mci_exynos_suspend_noirq - Exynos-specific suspend code
179306c59cbSLee Jones  * @dev: Device to suspend (this device)
180ecf7c7c5SMarek Szyprowski  *
181ecf7c7c5SMarek Szyprowski  * This ensures that device will be in runtime active state in
182ecf7c7c5SMarek Szyprowski  * dw_mci_exynos_resume_noirq after calling pm_runtime_force_resume()
183ecf7c7c5SMarek Szyprowski  */
184ecf7c7c5SMarek Szyprowski static int dw_mci_exynos_suspend_noirq(struct device *dev)
185ecf7c7c5SMarek Szyprowski {
186ecf7c7c5SMarek Szyprowski 	pm_runtime_get_noresume(dev);
187ecf7c7c5SMarek Szyprowski 	return pm_runtime_force_suspend(dev);
188ecf7c7c5SMarek Szyprowski }
189e2c63599SDoug Anderson 
190e2c63599SDoug Anderson /**
191e2c63599SDoug Anderson  * dw_mci_exynos_resume_noirq - Exynos-specific resume code
192306c59cbSLee Jones  * @dev: Device to resume (this device)
193e2c63599SDoug Anderson  *
194e2c63599SDoug Anderson  * On exynos5420 there is a silicon errata that will sometimes leave the
195e2c63599SDoug Anderson  * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate
196e2c63599SDoug Anderson  * that it fired and we can clear it by writing a 1 back.  Clear it to prevent
197e2c63599SDoug Anderson  * interrupts from going off constantly.
198e2c63599SDoug Anderson  *
199e2c63599SDoug Anderson  * We run this code on all exynos variants because it doesn't hurt.
200e2c63599SDoug Anderson  */
201e2c63599SDoug Anderson static int dw_mci_exynos_resume_noirq(struct device *dev)
202e2c63599SDoug Anderson {
203e2c63599SDoug Anderson 	struct dw_mci *host = dev_get_drvdata(dev);
20489ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
205e2c63599SDoug Anderson 	u32 clksel;
206ecf7c7c5SMarek Szyprowski 	int ret;
207ecf7c7c5SMarek Szyprowski 
208ecf7c7c5SMarek Szyprowski 	ret = pm_runtime_force_resume(dev);
209ecf7c7c5SMarek Szyprowski 	if (ret)
210ecf7c7c5SMarek Szyprowski 		return ret;
211e2c63599SDoug Anderson 
21289ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
21389ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
21489ad2be7SAbhilash Kesavan 		clksel = mci_readl(host, CLKSEL64);
21589ad2be7SAbhilash Kesavan 	else
216e2c63599SDoug Anderson 		clksel = mci_readl(host, CLKSEL);
21789ad2be7SAbhilash Kesavan 
21889ad2be7SAbhilash Kesavan 	if (clksel & SDMMC_CLKSEL_WAKEUP_INT) {
21989ad2be7SAbhilash Kesavan 		if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
22089ad2be7SAbhilash Kesavan 			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
22189ad2be7SAbhilash Kesavan 			mci_writel(host, CLKSEL64, clksel);
22289ad2be7SAbhilash Kesavan 		else
223e2c63599SDoug Anderson 			mci_writel(host, CLKSEL, clksel);
22489ad2be7SAbhilash Kesavan 	}
225e2c63599SDoug Anderson 
226ecf7c7c5SMarek Szyprowski 	pm_runtime_put(dev);
227ecf7c7c5SMarek Szyprowski 
228e2c63599SDoug Anderson 	return 0;
229e2c63599SDoug Anderson }
230ecf7c7c5SMarek Szyprowski #endif /* CONFIG_PM_SLEEP */
231e2c63599SDoug Anderson 
23280113132SSeungwon Jeon static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
233c3665006SThomas Abraham {
234c3665006SThomas Abraham 	struct dw_mci_exynos_priv_data *priv = host->priv;
23580113132SSeungwon Jeon 	u32 dqs, strobe;
236c3665006SThomas Abraham 
23780113132SSeungwon Jeon 	/*
23880113132SSeungwon Jeon 	 * Not supported to configure register
23980113132SSeungwon Jeon 	 * related to HS400
24080113132SSeungwon Jeon 	 */
241941a659fSKrzysztof Kozlowski 	if (priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420) {
242941a659fSKrzysztof Kozlowski 		if (timing == MMC_TIMING_MMC_HS400)
243941a659fSKrzysztof Kozlowski 			dev_warn(host->dev,
244941a659fSKrzysztof Kozlowski 				 "cannot configure HS400, unsupported chipset\n");
24580113132SSeungwon Jeon 		return;
246941a659fSKrzysztof Kozlowski 	}
24780113132SSeungwon Jeon 
24880113132SSeungwon Jeon 	dqs = priv->saved_dqs_en;
24980113132SSeungwon Jeon 	strobe = priv->saved_strobe_ctrl;
25080113132SSeungwon Jeon 
25180113132SSeungwon Jeon 	if (timing == MMC_TIMING_MMC_HS400) {
25280113132SSeungwon Jeon 		dqs |= DATA_STROBE_EN;
25380113132SSeungwon Jeon 		strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
25432b64b03SAnand Moon 	} else if (timing == MMC_TIMING_UHS_SDR104) {
25532b64b03SAnand Moon 		dqs &= 0xffffff00;
256c6d9dedaSSeungwon Jeon 	} else {
25780113132SSeungwon Jeon 		dqs &= ~DATA_STROBE_EN;
258c3665006SThomas Abraham 	}
259c3665006SThomas Abraham 
26080113132SSeungwon Jeon 	mci_writel(host, HS400_DQS_EN, dqs);
26180113132SSeungwon Jeon 	mci_writel(host, HS400_DLINE_CTRL, strobe);
26280113132SSeungwon Jeon }
26380113132SSeungwon Jeon 
26480113132SSeungwon Jeon static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted)
26580113132SSeungwon Jeon {
26680113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
26780113132SSeungwon Jeon 	unsigned long actual;
26880113132SSeungwon Jeon 	u8 div;
26980113132SSeungwon Jeon 	int ret;
270a2a1fed8SSeungwon Jeon 	/*
271a2a1fed8SSeungwon Jeon 	 * Don't care if wanted clock is zero or
272a2a1fed8SSeungwon Jeon 	 * ciu clock is unavailable
273a2a1fed8SSeungwon Jeon 	 */
274a2a1fed8SSeungwon Jeon 	if (!wanted || IS_ERR(host->ciu_clk))
275c6d9dedaSSeungwon Jeon 		return;
276c6d9dedaSSeungwon Jeon 
277c6d9dedaSSeungwon Jeon 	/* Guaranteed minimum frequency for cclkin */
278c6d9dedaSSeungwon Jeon 	if (wanted < EXYNOS_CCLKIN_MIN)
279c6d9dedaSSeungwon Jeon 		wanted = EXYNOS_CCLKIN_MIN;
280c6d9dedaSSeungwon Jeon 
28180113132SSeungwon Jeon 	if (wanted == priv->cur_speed)
28280113132SSeungwon Jeon 		return;
28380113132SSeungwon Jeon 
28480113132SSeungwon Jeon 	div = dw_mci_exynos_get_ciu_div(host);
28580113132SSeungwon Jeon 	ret = clk_set_rate(host->ciu_clk, wanted * div);
286c6d9dedaSSeungwon Jeon 	if (ret)
287c6d9dedaSSeungwon Jeon 		dev_warn(host->dev,
288c6d9dedaSSeungwon Jeon 			"failed to set clk-rate %u error: %d\n",
289c6d9dedaSSeungwon Jeon 			wanted * div, ret);
290c6d9dedaSSeungwon Jeon 	actual = clk_get_rate(host->ciu_clk);
291c6d9dedaSSeungwon Jeon 	host->bus_hz = actual / div;
292c6d9dedaSSeungwon Jeon 	priv->cur_speed = wanted;
293c6d9dedaSSeungwon Jeon 	host->current_speed = 0;
294c6d9dedaSSeungwon Jeon }
29580113132SSeungwon Jeon 
29680113132SSeungwon Jeon static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
29780113132SSeungwon Jeon {
29880113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
29980113132SSeungwon Jeon 	unsigned int wanted = ios->clock;
30080113132SSeungwon Jeon 	u32 timing = ios->timing, clksel;
30180113132SSeungwon Jeon 
30280113132SSeungwon Jeon 	switch (timing) {
30380113132SSeungwon Jeon 	case MMC_TIMING_MMC_HS400:
30480113132SSeungwon Jeon 		/* Update tuned sample timing */
30580113132SSeungwon Jeon 		clksel = SDMMC_CLKSEL_UP_SAMPLE(
30680113132SSeungwon Jeon 				priv->hs400_timing, priv->tuned_sample);
30780113132SSeungwon Jeon 		wanted <<= 1;
30880113132SSeungwon Jeon 		break;
30980113132SSeungwon Jeon 	case MMC_TIMING_MMC_DDR52:
31080113132SSeungwon Jeon 		clksel = priv->ddr_timing;
31180113132SSeungwon Jeon 		/* Should be double rate for DDR mode */
31280113132SSeungwon Jeon 		if (ios->bus_width == MMC_BUS_WIDTH_8)
31380113132SSeungwon Jeon 			wanted <<= 1;
31480113132SSeungwon Jeon 		break;
31532b64b03SAnand Moon 	case MMC_TIMING_UHS_SDR104:
31632b64b03SAnand Moon 	case MMC_TIMING_UHS_SDR50:
31732b64b03SAnand Moon 		clksel = (priv->sdr_timing & 0xfff8ffff) |
31832b64b03SAnand Moon 			(priv->ciu_div << 16);
31932b64b03SAnand Moon 		break;
32032b64b03SAnand Moon 	case MMC_TIMING_UHS_DDR50:
32132b64b03SAnand Moon 		clksel = (priv->ddr_timing & 0xfff8ffff) |
32232b64b03SAnand Moon 			(priv->ciu_div << 16);
32332b64b03SAnand Moon 		break;
32480113132SSeungwon Jeon 	default:
32580113132SSeungwon Jeon 		clksel = priv->sdr_timing;
32680113132SSeungwon Jeon 	}
32780113132SSeungwon Jeon 
32880113132SSeungwon Jeon 	/* Set clock timing for the requested speed mode*/
32980113132SSeungwon Jeon 	dw_mci_exynos_set_clksel_timing(host, clksel);
33080113132SSeungwon Jeon 
33180113132SSeungwon Jeon 	/* Configure setting for HS400 */
33280113132SSeungwon Jeon 	dw_mci_exynos_config_hs400(host, timing);
33380113132SSeungwon Jeon 
33480113132SSeungwon Jeon 	/* Configure clock rate */
33580113132SSeungwon Jeon 	dw_mci_exynos_adjust_clock(host, wanted);
336c6d9dedaSSeungwon Jeon }
337c6d9dedaSSeungwon Jeon 
338c3665006SThomas Abraham static int dw_mci_exynos_parse_dt(struct dw_mci *host)
339c3665006SThomas Abraham {
340e6c784edSYuvaraj Kumar C D 	struct dw_mci_exynos_priv_data *priv;
341c3665006SThomas Abraham 	struct device_node *np = host->dev->of_node;
342c3665006SThomas Abraham 	u32 timing[2];
343c3665006SThomas Abraham 	u32 div = 0;
344e6c784edSYuvaraj Kumar C D 	int idx;
345c3665006SThomas Abraham 	int ret;
346c3665006SThomas Abraham 
347e6c784edSYuvaraj Kumar C D 	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
348bf3707eaSBeomho Seo 	if (!priv)
349e6c784edSYuvaraj Kumar C D 		return -ENOMEM;
350e6c784edSYuvaraj Kumar C D 
351e6c784edSYuvaraj Kumar C D 	for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
352e6c784edSYuvaraj Kumar C D 		if (of_device_is_compatible(np, exynos_compat[idx].compatible))
353e6c784edSYuvaraj Kumar C D 			priv->ctrl_type = exynos_compat[idx].ctrl_type;
354e6c784edSYuvaraj Kumar C D 	}
355e6c784edSYuvaraj Kumar C D 
356c6d9dedaSSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
357c6d9dedaSSeungwon Jeon 		priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1;
358c6d9dedaSSeungwon Jeon 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
359c6d9dedaSSeungwon Jeon 		priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1;
360c6d9dedaSSeungwon Jeon 	else {
361c3665006SThomas Abraham 		of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
362c3665006SThomas Abraham 		priv->ciu_div = div;
363c6d9dedaSSeungwon Jeon 	}
364c3665006SThomas Abraham 
365c3665006SThomas Abraham 	ret = of_property_read_u32_array(np,
366c3665006SThomas Abraham 			"samsung,dw-mshc-sdr-timing", timing, 2);
367c3665006SThomas Abraham 	if (ret)
368c3665006SThomas Abraham 		return ret;
369c3665006SThomas Abraham 
3702d9f0bd1SYuvaraj Kumar C D 	priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
3712d9f0bd1SYuvaraj Kumar C D 
372c3665006SThomas Abraham 	ret = of_property_read_u32_array(np,
373c3665006SThomas Abraham 			"samsung,dw-mshc-ddr-timing", timing, 2);
374c3665006SThomas Abraham 	if (ret)
375c3665006SThomas Abraham 		return ret;
376c3665006SThomas Abraham 
377c3665006SThomas Abraham 	priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
37880113132SSeungwon Jeon 
37980113132SSeungwon Jeon 	ret = of_property_read_u32_array(np,
38080113132SSeungwon Jeon 			"samsung,dw-mshc-hs400-timing", timing, 2);
38180113132SSeungwon Jeon 	if (!ret && of_property_read_u32(np,
38280113132SSeungwon Jeon 				"samsung,read-strobe-delay", &priv->dqs_delay))
38380113132SSeungwon Jeon 		dev_dbg(host->dev,
38480113132SSeungwon Jeon 			"read-strobe-delay is not found, assuming usage of default value\n");
38580113132SSeungwon Jeon 
38680113132SSeungwon Jeon 	priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1],
38780113132SSeungwon Jeon 						HS400_FIXED_CIU_CLK_DIV);
388e6c784edSYuvaraj Kumar C D 	host->priv = priv;
389c3665006SThomas Abraham 	return 0;
390c3665006SThomas Abraham }
391c3665006SThomas Abraham 
392c537a1c5SSeungwon Jeon static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host)
393c537a1c5SSeungwon Jeon {
39489ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
39589ad2be7SAbhilash Kesavan 
39689ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
39789ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
39889ad2be7SAbhilash Kesavan 		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL64));
39989ad2be7SAbhilash Kesavan 	else
400c537a1c5SSeungwon Jeon 		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL));
401c537a1c5SSeungwon Jeon }
402c537a1c5SSeungwon Jeon 
403c537a1c5SSeungwon Jeon static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
404c537a1c5SSeungwon Jeon {
405c537a1c5SSeungwon Jeon 	u32 clksel;
40689ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
40789ad2be7SAbhilash Kesavan 
40889ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
40989ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
41089ad2be7SAbhilash Kesavan 		clksel = mci_readl(host, CLKSEL64);
41189ad2be7SAbhilash Kesavan 	else
412c537a1c5SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL);
41380113132SSeungwon Jeon 	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
41489ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
41589ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
41689ad2be7SAbhilash Kesavan 		mci_writel(host, CLKSEL64, clksel);
41789ad2be7SAbhilash Kesavan 	else
418c537a1c5SSeungwon Jeon 		mci_writel(host, CLKSEL, clksel);
419c537a1c5SSeungwon Jeon }
420c537a1c5SSeungwon Jeon 
421c537a1c5SSeungwon Jeon static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
422c537a1c5SSeungwon Jeon {
42389ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
424c537a1c5SSeungwon Jeon 	u32 clksel;
425c537a1c5SSeungwon Jeon 	u8 sample;
426c537a1c5SSeungwon Jeon 
42789ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
42889ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
42989ad2be7SAbhilash Kesavan 		clksel = mci_readl(host, CLKSEL64);
43089ad2be7SAbhilash Kesavan 	else
431c537a1c5SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL);
43280113132SSeungwon Jeon 
433c537a1c5SSeungwon Jeon 	sample = (clksel + 1) & 0x7;
43480113132SSeungwon Jeon 	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
43580113132SSeungwon Jeon 
43689ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
43789ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
43889ad2be7SAbhilash Kesavan 		mci_writel(host, CLKSEL64, clksel);
43989ad2be7SAbhilash Kesavan 	else
440c537a1c5SSeungwon Jeon 		mci_writel(host, CLKSEL, clksel);
44180113132SSeungwon Jeon 
442c537a1c5SSeungwon Jeon 	return sample;
443c537a1c5SSeungwon Jeon }
444c537a1c5SSeungwon Jeon 
445c537a1c5SSeungwon Jeon static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates)
446c537a1c5SSeungwon Jeon {
447c537a1c5SSeungwon Jeon 	const u8 iter = 8;
448c537a1c5SSeungwon Jeon 	u8 __c;
449c537a1c5SSeungwon Jeon 	s8 i, loc = -1;
450c537a1c5SSeungwon Jeon 
451c537a1c5SSeungwon Jeon 	for (i = 0; i < iter; i++) {
452c537a1c5SSeungwon Jeon 		__c = ror8(candiates, i);
453c537a1c5SSeungwon Jeon 		if ((__c & 0xc7) == 0xc7) {
454c537a1c5SSeungwon Jeon 			loc = i;
455c537a1c5SSeungwon Jeon 			goto out;
456c537a1c5SSeungwon Jeon 		}
457c537a1c5SSeungwon Jeon 	}
458c537a1c5SSeungwon Jeon 
459c537a1c5SSeungwon Jeon 	for (i = 0; i < iter; i++) {
460c537a1c5SSeungwon Jeon 		__c = ror8(candiates, i);
461c537a1c5SSeungwon Jeon 		if ((__c & 0x83) == 0x83) {
462c537a1c5SSeungwon Jeon 			loc = i;
463c537a1c5SSeungwon Jeon 			goto out;
464c537a1c5SSeungwon Jeon 		}
465c537a1c5SSeungwon Jeon 	}
466c537a1c5SSeungwon Jeon 
467c537a1c5SSeungwon Jeon out:
468c537a1c5SSeungwon Jeon 	return loc;
469c537a1c5SSeungwon Jeon }
470c537a1c5SSeungwon Jeon 
4719979dbe5SChaotian Jing static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
472c537a1c5SSeungwon Jeon {
473c537a1c5SSeungwon Jeon 	struct dw_mci *host = slot->host;
47480113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
475c537a1c5SSeungwon Jeon 	struct mmc_host *mmc = slot->mmc;
476c537a1c5SSeungwon Jeon 	u8 start_smpl, smpl, candiates = 0;
477479cb7cfSColin Ian King 	s8 found;
478c537a1c5SSeungwon Jeon 	int ret = 0;
479c537a1c5SSeungwon Jeon 
480c537a1c5SSeungwon Jeon 	start_smpl = dw_mci_exynos_get_clksmpl(host);
481c537a1c5SSeungwon Jeon 
482c537a1c5SSeungwon Jeon 	do {
483c537a1c5SSeungwon Jeon 		mci_writel(host, TMOUT, ~0);
484c537a1c5SSeungwon Jeon 		smpl = dw_mci_exynos_move_next_clksmpl(host);
485c537a1c5SSeungwon Jeon 
4869979dbe5SChaotian Jing 		if (!mmc_send_tuning(mmc, opcode, NULL))
487c537a1c5SSeungwon Jeon 			candiates |= (1 << smpl);
4886c2c6506SUlf Hansson 
489c537a1c5SSeungwon Jeon 	} while (start_smpl != smpl);
490c537a1c5SSeungwon Jeon 
491c537a1c5SSeungwon Jeon 	found = dw_mci_exynos_get_best_clksmpl(candiates);
49280113132SSeungwon Jeon 	if (found >= 0) {
493c537a1c5SSeungwon Jeon 		dw_mci_exynos_set_clksmpl(host, found);
49480113132SSeungwon Jeon 		priv->tuned_sample = found;
49580113132SSeungwon Jeon 	} else {
496c537a1c5SSeungwon Jeon 		ret = -EIO;
49780113132SSeungwon Jeon 	}
498c537a1c5SSeungwon Jeon 
499c537a1c5SSeungwon Jeon 	return ret;
500c537a1c5SSeungwon Jeon }
501c537a1c5SSeungwon Jeon 
502c22f5e1bSWu Fengguang static int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host,
50380113132SSeungwon Jeon 					struct mmc_ios *ios)
50480113132SSeungwon Jeon {
50580113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
50680113132SSeungwon Jeon 
50780113132SSeungwon Jeon 	dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing);
50880113132SSeungwon Jeon 	dw_mci_exynos_adjust_clock(host, (ios->clock) << 1);
50980113132SSeungwon Jeon 
51080113132SSeungwon Jeon 	return 0;
51180113132SSeungwon Jeon }
51280113132SSeungwon Jeon 
5130f6e73d0SDongjin Kim /* Common capabilities of Exynos4/Exynos5 SoC */
5140f6e73d0SDongjin Kim static unsigned long exynos_dwmmc_caps[4] = {
515cab3a802SSeungwon Jeon 	MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
516c3665006SThomas Abraham 	MMC_CAP_CMD23,
517c3665006SThomas Abraham 	MMC_CAP_CMD23,
518c3665006SThomas Abraham 	MMC_CAP_CMD23,
519c3665006SThomas Abraham };
520c3665006SThomas Abraham 
5210f6e73d0SDongjin Kim static const struct dw_mci_drv_data exynos_drv_data = {
5220f6e73d0SDongjin Kim 	.caps			= exynos_dwmmc_caps,
5230d84b9e5SShawn Lin 	.num_caps		= ARRAY_SIZE(exynos_dwmmc_caps),
524c3665006SThomas Abraham 	.init			= dw_mci_exynos_priv_init,
525c3665006SThomas Abraham 	.set_ios		= dw_mci_exynos_set_ios,
526c3665006SThomas Abraham 	.parse_dt		= dw_mci_exynos_parse_dt,
527c537a1c5SSeungwon Jeon 	.execute_tuning		= dw_mci_exynos_execute_tuning,
52880113132SSeungwon Jeon 	.prepare_hs400_tuning	= dw_mci_exynos_prepare_hs400_tuning,
529c3665006SThomas Abraham };
530c3665006SThomas Abraham 
531c3665006SThomas Abraham static const struct of_device_id dw_mci_exynos_match[] = {
5320f6e73d0SDongjin Kim 	{ .compatible = "samsung,exynos4412-dw-mshc",
5330f6e73d0SDongjin Kim 			.data = &exynos_drv_data, },
534c3665006SThomas Abraham 	{ .compatible = "samsung,exynos5250-dw-mshc",
5350f6e73d0SDongjin Kim 			.data = &exynos_drv_data, },
53600fd041bSYuvaraj Kumar C D 	{ .compatible = "samsung,exynos5420-dw-mshc",
53700fd041bSYuvaraj Kumar C D 			.data = &exynos_drv_data, },
5386bce431cSYuvaraj Kumar C D 	{ .compatible = "samsung,exynos5420-dw-mshc-smu",
5396bce431cSYuvaraj Kumar C D 			.data = &exynos_drv_data, },
54089ad2be7SAbhilash Kesavan 	{ .compatible = "samsung,exynos7-dw-mshc",
54189ad2be7SAbhilash Kesavan 			.data = &exynos_drv_data, },
54289ad2be7SAbhilash Kesavan 	{ .compatible = "samsung,exynos7-dw-mshc-smu",
54389ad2be7SAbhilash Kesavan 			.data = &exynos_drv_data, },
544c3665006SThomas Abraham 	{},
545c3665006SThomas Abraham };
546517cb9f1SArnd Bergmann MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
547c3665006SThomas Abraham 
5489665f7f2SSachin Kamat static int dw_mci_exynos_probe(struct platform_device *pdev)
549c3665006SThomas Abraham {
5508e2b36eaSArnd Bergmann 	const struct dw_mci_drv_data *drv_data;
551c3665006SThomas Abraham 	const struct of_device_id *match;
5529b93d392SJoonyoung Shim 	int ret;
553c3665006SThomas Abraham 
554c3665006SThomas Abraham 	match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
555c3665006SThomas Abraham 	drv_data = match->data;
5569b93d392SJoonyoung Shim 
5579b93d392SJoonyoung Shim 	pm_runtime_get_noresume(&pdev->dev);
5589b93d392SJoonyoung Shim 	pm_runtime_set_active(&pdev->dev);
5599b93d392SJoonyoung Shim 	pm_runtime_enable(&pdev->dev);
5609b93d392SJoonyoung Shim 
5619b93d392SJoonyoung Shim 	ret = dw_mci_pltfm_register(pdev, drv_data);
5629b93d392SJoonyoung Shim 	if (ret) {
5639b93d392SJoonyoung Shim 		pm_runtime_disable(&pdev->dev);
5649b93d392SJoonyoung Shim 		pm_runtime_set_suspended(&pdev->dev);
5659b93d392SJoonyoung Shim 		pm_runtime_put_noidle(&pdev->dev);
5669b93d392SJoonyoung Shim 
5679b93d392SJoonyoung Shim 		return ret;
5689b93d392SJoonyoung Shim 	}
5699b93d392SJoonyoung Shim 
5709b93d392SJoonyoung Shim 	return 0;
5719b93d392SJoonyoung Shim }
5729b93d392SJoonyoung Shim 
5739b93d392SJoonyoung Shim static int dw_mci_exynos_remove(struct platform_device *pdev)
5749b93d392SJoonyoung Shim {
5759b93d392SJoonyoung Shim 	pm_runtime_disable(&pdev->dev);
5769b93d392SJoonyoung Shim 	pm_runtime_set_suspended(&pdev->dev);
5779b93d392SJoonyoung Shim 	pm_runtime_put_noidle(&pdev->dev);
5789b93d392SJoonyoung Shim 
5799b93d392SJoonyoung Shim 	return dw_mci_pltfm_remove(pdev);
580c3665006SThomas Abraham }
581c3665006SThomas Abraham 
58215a2e2abSSachin Kamat static const struct dev_pm_ops dw_mci_exynos_pmops = {
583ecf7c7c5SMarek Szyprowski 	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend_noirq,
584ecf7c7c5SMarek Szyprowski 				      dw_mci_exynos_resume_noirq)
585cf5237efSShawn Lin 	SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
586cf5237efSShawn Lin 			   dw_mci_exynos_runtime_resume,
587cf5237efSShawn Lin 			   NULL)
588e2c63599SDoug Anderson };
589e2c63599SDoug Anderson 
590c3665006SThomas Abraham static struct platform_driver dw_mci_exynos_pltfm_driver = {
591c3665006SThomas Abraham 	.probe		= dw_mci_exynos_probe,
5929b93d392SJoonyoung Shim 	.remove		= dw_mci_exynos_remove,
593c3665006SThomas Abraham 	.driver		= {
594c3665006SThomas Abraham 		.name		= "dwmmc_exynos",
595*21b2cec6SDouglas Anderson 		.probe_type	= PROBE_PREFER_ASYNCHRONOUS,
59620183d50SSachin Kamat 		.of_match_table	= dw_mci_exynos_match,
597e2c63599SDoug Anderson 		.pm		= &dw_mci_exynos_pmops,
598c3665006SThomas Abraham 	},
599c3665006SThomas Abraham };
600c3665006SThomas Abraham 
601c3665006SThomas Abraham module_platform_driver(dw_mci_exynos_pltfm_driver);
602c3665006SThomas Abraham 
603c3665006SThomas Abraham MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
604c3665006SThomas Abraham MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
605c3665006SThomas Abraham MODULE_LICENSE("GPL v2");
6062fc546fdSZhangfei Gao MODULE_ALIAS("platform:dwmmc_exynos");
607