xref: /linux/drivers/mmc/host/dw_mmc-exynos.c (revision 9b93d392fdbd336b0f1558592010c0169801afa5)
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>
17c537a1c5SSeungwon Jeon #include <linux/mmc/mmc.h>
18c3665006SThomas Abraham #include <linux/of.h>
19c3665006SThomas Abraham #include <linux/of_gpio.h>
20cf5237efSShawn Lin #include <linux/pm_runtime.h>
21c537a1c5SSeungwon Jeon #include <linux/slab.h>
22c3665006SThomas Abraham 
23c3665006SThomas Abraham #include "dw_mmc.h"
24c3665006SThomas Abraham #include "dw_mmc-pltfm.h"
250b5fce48SSeungwon Jeon #include "dw_mmc-exynos.h"
26c6d9dedaSSeungwon Jeon 
27c3665006SThomas Abraham /* Variations in Exynos specific dw-mshc controller */
28c3665006SThomas Abraham enum dw_mci_exynos_type {
29c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS4210,
30c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS4412,
31c3665006SThomas Abraham 	DW_MCI_TYPE_EXYNOS5250,
3200fd041bSYuvaraj Kumar C D 	DW_MCI_TYPE_EXYNOS5420,
336bce431cSYuvaraj Kumar C D 	DW_MCI_TYPE_EXYNOS5420_SMU,
3489ad2be7SAbhilash Kesavan 	DW_MCI_TYPE_EXYNOS7,
3589ad2be7SAbhilash Kesavan 	DW_MCI_TYPE_EXYNOS7_SMU,
36c3665006SThomas Abraham };
37c3665006SThomas Abraham 
38c3665006SThomas Abraham /* Exynos implementation specific driver private data */
39c3665006SThomas Abraham struct dw_mci_exynos_priv_data {
40c3665006SThomas Abraham 	enum dw_mci_exynos_type		ctrl_type;
41c3665006SThomas Abraham 	u8				ciu_div;
42c3665006SThomas Abraham 	u32				sdr_timing;
43c3665006SThomas Abraham 	u32				ddr_timing;
4480113132SSeungwon Jeon 	u32				hs400_timing;
4580113132SSeungwon Jeon 	u32				tuned_sample;
46c6d9dedaSSeungwon Jeon 	u32				cur_speed;
4780113132SSeungwon Jeon 	u32				dqs_delay;
4880113132SSeungwon Jeon 	u32				saved_dqs_en;
4980113132SSeungwon Jeon 	u32				saved_strobe_ctrl;
50c3665006SThomas Abraham };
51c3665006SThomas Abraham 
52c3665006SThomas Abraham static struct dw_mci_exynos_compatible {
53c3665006SThomas Abraham 	char				*compatible;
54c3665006SThomas Abraham 	enum dw_mci_exynos_type		ctrl_type;
55c3665006SThomas Abraham } exynos_compat[] = {
56c3665006SThomas Abraham 	{
57c3665006SThomas Abraham 		.compatible	= "samsung,exynos4210-dw-mshc",
58c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4210,
59c3665006SThomas Abraham 	}, {
60c3665006SThomas Abraham 		.compatible	= "samsung,exynos4412-dw-mshc",
61c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4412,
62c3665006SThomas Abraham 	}, {
63c3665006SThomas Abraham 		.compatible	= "samsung,exynos5250-dw-mshc",
64c3665006SThomas Abraham 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5250,
6500fd041bSYuvaraj Kumar C D 	}, {
6600fd041bSYuvaraj Kumar C D 		.compatible	= "samsung,exynos5420-dw-mshc",
6700fd041bSYuvaraj Kumar C D 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420,
686bce431cSYuvaraj Kumar C D 	}, {
696bce431cSYuvaraj Kumar C D 		.compatible	= "samsung,exynos5420-dw-mshc-smu",
706bce431cSYuvaraj Kumar C D 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420_SMU,
7189ad2be7SAbhilash Kesavan 	}, {
7289ad2be7SAbhilash Kesavan 		.compatible	= "samsung,exynos7-dw-mshc",
7389ad2be7SAbhilash Kesavan 		.ctrl_type	= DW_MCI_TYPE_EXYNOS7,
7489ad2be7SAbhilash Kesavan 	}, {
7589ad2be7SAbhilash Kesavan 		.compatible	= "samsung,exynos7-dw-mshc-smu",
7689ad2be7SAbhilash Kesavan 		.ctrl_type	= DW_MCI_TYPE_EXYNOS7_SMU,
77c3665006SThomas Abraham 	},
78c3665006SThomas Abraham };
79c3665006SThomas Abraham 
8080113132SSeungwon Jeon static inline u8 dw_mci_exynos_get_ciu_div(struct dw_mci *host)
8180113132SSeungwon Jeon {
8280113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
8380113132SSeungwon Jeon 
8480113132SSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
8580113132SSeungwon Jeon 		return EXYNOS4412_FIXED_CIU_CLK_DIV;
8680113132SSeungwon Jeon 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
8780113132SSeungwon Jeon 		return EXYNOS4210_FIXED_CIU_CLK_DIV;
8880113132SSeungwon Jeon 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
8980113132SSeungwon Jeon 			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
9080113132SSeungwon Jeon 		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL64)) + 1;
9180113132SSeungwon Jeon 	else
9280113132SSeungwon Jeon 		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL)) + 1;
9380113132SSeungwon Jeon }
9480113132SSeungwon Jeon 
955659eeadSJaehoon Chung static void dw_mci_exynos_config_smu(struct dw_mci *host)
96c3665006SThomas Abraham {
97e6c784edSYuvaraj Kumar C D 	struct dw_mci_exynos_priv_data *priv = host->priv;
98c3665006SThomas Abraham 
995659eeadSJaehoon Chung 	/*
1005659eeadSJaehoon Chung 	 * If Exynos is provided the Security management,
1015659eeadSJaehoon Chung 	 * set for non-ecryption mode at this time.
1025659eeadSJaehoon Chung 	 */
10389ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU ||
10489ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU) {
1056bce431cSYuvaraj Kumar C D 		mci_writel(host, MPSBEGIN0, 0);
1060b5fce48SSeungwon Jeon 		mci_writel(host, MPSEND0, SDMMC_ENDING_SEC_NR_MAX);
1070b5fce48SSeungwon Jeon 		mci_writel(host, MPSCTRL0, SDMMC_MPSCTRL_SECURE_WRITE_BIT |
1080b5fce48SSeungwon Jeon 			   SDMMC_MPSCTRL_NON_SECURE_READ_BIT |
1090b5fce48SSeungwon Jeon 			   SDMMC_MPSCTRL_VALID |
1100b5fce48SSeungwon Jeon 			   SDMMC_MPSCTRL_NON_SECURE_WRITE_BIT);
1116bce431cSYuvaraj Kumar C D 	}
1125659eeadSJaehoon Chung }
1135659eeadSJaehoon Chung 
1145659eeadSJaehoon Chung static int dw_mci_exynos_priv_init(struct dw_mci *host)
1155659eeadSJaehoon Chung {
1165659eeadSJaehoon Chung 	struct dw_mci_exynos_priv_data *priv = host->priv;
1175659eeadSJaehoon Chung 
1185659eeadSJaehoon Chung 	dw_mci_exynos_config_smu(host);
1196bce431cSYuvaraj Kumar C D 
12080113132SSeungwon Jeon 	if (priv->ctrl_type >= DW_MCI_TYPE_EXYNOS5420) {
12180113132SSeungwon Jeon 		priv->saved_strobe_ctrl = mci_readl(host, HS400_DLINE_CTRL);
12280113132SSeungwon Jeon 		priv->saved_dqs_en = mci_readl(host, HS400_DQS_EN);
12380113132SSeungwon Jeon 		priv->saved_dqs_en |= AXI_NON_BLOCKING_WR;
12480113132SSeungwon Jeon 		mci_writel(host, HS400_DQS_EN, priv->saved_dqs_en);
12580113132SSeungwon Jeon 		if (!priv->dqs_delay)
12680113132SSeungwon Jeon 			priv->dqs_delay =
12780113132SSeungwon Jeon 				DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl);
12880113132SSeungwon Jeon 	}
12980113132SSeungwon Jeon 
130a2a1fed8SSeungwon Jeon 	host->bus_hz /= (priv->ciu_div + 1);
131a2a1fed8SSeungwon Jeon 
132c3665006SThomas Abraham 	return 0;
133c3665006SThomas Abraham }
134c3665006SThomas Abraham 
13580113132SSeungwon Jeon static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing)
13680113132SSeungwon Jeon {
13780113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
13880113132SSeungwon Jeon 	u32 clksel;
13980113132SSeungwon Jeon 
14080113132SSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
14180113132SSeungwon Jeon 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
14280113132SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL64);
14380113132SSeungwon Jeon 	else
14480113132SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL);
14580113132SSeungwon Jeon 
14680113132SSeungwon Jeon 	clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing;
14780113132SSeungwon Jeon 
14880113132SSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
14980113132SSeungwon Jeon 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
15080113132SSeungwon Jeon 		mci_writel(host, CLKSEL64, clksel);
15180113132SSeungwon Jeon 	else
15280113132SSeungwon Jeon 		mci_writel(host, CLKSEL, clksel);
153aaaaeb7aSJaehoon Chung 
154aaaaeb7aSJaehoon Chung 	/*
155aaaaeb7aSJaehoon Chung 	 * Exynos4412 and Exynos5250 extends the use of CMD register with the
156aaaaeb7aSJaehoon Chung 	 * use of bit 29 (which is reserved on standard MSHC controllers) for
157aaaaeb7aSJaehoon Chung 	 * optionally bypassing the HOLD register for command and data. The
158aaaaeb7aSJaehoon Chung 	 * HOLD register should be bypassed in case there is no phase shift
159aaaaeb7aSJaehoon Chung 	 * applied on CMD/DATA that is sent to the card.
160aaaaeb7aSJaehoon Chung 	 */
161e5a61353SJaehoon Chung 	if (!SDMMC_CLKSEL_GET_DRV_WD3(clksel) && host->cur_slot)
162aaaaeb7aSJaehoon Chung 		set_bit(DW_MMC_CARD_NO_USE_HOLD, &host->cur_slot->flags);
16380113132SSeungwon Jeon }
16480113132SSeungwon Jeon 
165cf5237efSShawn Lin #ifdef CONFIG_PM
166cf5237efSShawn Lin static int dw_mci_exynos_runtime_resume(struct device *dev)
167e2c63599SDoug Anderson {
168e2c63599SDoug Anderson 	struct dw_mci *host = dev_get_drvdata(dev);
169e2c63599SDoug Anderson 
1705659eeadSJaehoon Chung 	dw_mci_exynos_config_smu(host);
171cf5237efSShawn Lin 	return dw_mci_runtime_resume(dev);
172e2c63599SDoug Anderson }
173e2c63599SDoug Anderson 
174e2c63599SDoug Anderson /**
175e2c63599SDoug Anderson  * dw_mci_exynos_resume_noirq - Exynos-specific resume code
176e2c63599SDoug Anderson  *
177e2c63599SDoug Anderson  * On exynos5420 there is a silicon errata that will sometimes leave the
178e2c63599SDoug Anderson  * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate
179e2c63599SDoug Anderson  * that it fired and we can clear it by writing a 1 back.  Clear it to prevent
180e2c63599SDoug Anderson  * interrupts from going off constantly.
181e2c63599SDoug Anderson  *
182e2c63599SDoug Anderson  * We run this code on all exynos variants because it doesn't hurt.
183e2c63599SDoug Anderson  */
184e2c63599SDoug Anderson 
185e2c63599SDoug Anderson static int dw_mci_exynos_resume_noirq(struct device *dev)
186e2c63599SDoug Anderson {
187e2c63599SDoug Anderson 	struct dw_mci *host = dev_get_drvdata(dev);
18889ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
189e2c63599SDoug Anderson 	u32 clksel;
190e2c63599SDoug Anderson 
19189ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
19289ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
19389ad2be7SAbhilash Kesavan 		clksel = mci_readl(host, CLKSEL64);
19489ad2be7SAbhilash Kesavan 	else
195e2c63599SDoug Anderson 		clksel = mci_readl(host, CLKSEL);
19689ad2be7SAbhilash Kesavan 
19789ad2be7SAbhilash Kesavan 	if (clksel & SDMMC_CLKSEL_WAKEUP_INT) {
19889ad2be7SAbhilash Kesavan 		if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
19989ad2be7SAbhilash Kesavan 			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
20089ad2be7SAbhilash Kesavan 			mci_writel(host, CLKSEL64, clksel);
20189ad2be7SAbhilash Kesavan 		else
202e2c63599SDoug Anderson 			mci_writel(host, CLKSEL, clksel);
20389ad2be7SAbhilash Kesavan 	}
204e2c63599SDoug Anderson 
205e2c63599SDoug Anderson 	return 0;
206e2c63599SDoug Anderson }
207e2c63599SDoug Anderson #else
208e2c63599SDoug Anderson #define dw_mci_exynos_resume_noirq	NULL
209cf5237efSShawn Lin #endif /* CONFIG_PM */
210e2c63599SDoug Anderson 
21180113132SSeungwon Jeon static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
212c3665006SThomas Abraham {
213c3665006SThomas Abraham 	struct dw_mci_exynos_priv_data *priv = host->priv;
21480113132SSeungwon Jeon 	u32 dqs, strobe;
215c3665006SThomas Abraham 
21680113132SSeungwon Jeon 	/*
21780113132SSeungwon Jeon 	 * Not supported to configure register
21880113132SSeungwon Jeon 	 * related to HS400
21980113132SSeungwon Jeon 	 */
220941a659fSKrzysztof Kozlowski 	if (priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420) {
221941a659fSKrzysztof Kozlowski 		if (timing == MMC_TIMING_MMC_HS400)
222941a659fSKrzysztof Kozlowski 			dev_warn(host->dev,
223941a659fSKrzysztof Kozlowski 				 "cannot configure HS400, unsupported chipset\n");
22480113132SSeungwon Jeon 		return;
225941a659fSKrzysztof Kozlowski 	}
22680113132SSeungwon Jeon 
22780113132SSeungwon Jeon 	dqs = priv->saved_dqs_en;
22880113132SSeungwon Jeon 	strobe = priv->saved_strobe_ctrl;
22980113132SSeungwon Jeon 
23080113132SSeungwon Jeon 	if (timing == MMC_TIMING_MMC_HS400) {
23180113132SSeungwon Jeon 		dqs |= DATA_STROBE_EN;
23280113132SSeungwon Jeon 		strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
233c6d9dedaSSeungwon Jeon 	} else {
23480113132SSeungwon Jeon 		dqs &= ~DATA_STROBE_EN;
235c3665006SThomas Abraham 	}
236c3665006SThomas Abraham 
23780113132SSeungwon Jeon 	mci_writel(host, HS400_DQS_EN, dqs);
23880113132SSeungwon Jeon 	mci_writel(host, HS400_DLINE_CTRL, strobe);
23980113132SSeungwon Jeon }
24080113132SSeungwon Jeon 
24180113132SSeungwon Jeon static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted)
24280113132SSeungwon Jeon {
24380113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
24480113132SSeungwon Jeon 	unsigned long actual;
24580113132SSeungwon Jeon 	u8 div;
24680113132SSeungwon Jeon 	int ret;
247a2a1fed8SSeungwon Jeon 	/*
248a2a1fed8SSeungwon Jeon 	 * Don't care if wanted clock is zero or
249a2a1fed8SSeungwon Jeon 	 * ciu clock is unavailable
250a2a1fed8SSeungwon Jeon 	 */
251a2a1fed8SSeungwon Jeon 	if (!wanted || IS_ERR(host->ciu_clk))
252c6d9dedaSSeungwon Jeon 		return;
253c6d9dedaSSeungwon Jeon 
254c6d9dedaSSeungwon Jeon 	/* Guaranteed minimum frequency for cclkin */
255c6d9dedaSSeungwon Jeon 	if (wanted < EXYNOS_CCLKIN_MIN)
256c6d9dedaSSeungwon Jeon 		wanted = EXYNOS_CCLKIN_MIN;
257c6d9dedaSSeungwon Jeon 
25880113132SSeungwon Jeon 	if (wanted == priv->cur_speed)
25980113132SSeungwon Jeon 		return;
26080113132SSeungwon Jeon 
26180113132SSeungwon Jeon 	div = dw_mci_exynos_get_ciu_div(host);
26280113132SSeungwon Jeon 	ret = clk_set_rate(host->ciu_clk, wanted * div);
263c6d9dedaSSeungwon Jeon 	if (ret)
264c6d9dedaSSeungwon Jeon 		dev_warn(host->dev,
265c6d9dedaSSeungwon Jeon 			"failed to set clk-rate %u error: %d\n",
266c6d9dedaSSeungwon Jeon 			wanted * div, ret);
267c6d9dedaSSeungwon Jeon 	actual = clk_get_rate(host->ciu_clk);
268c6d9dedaSSeungwon Jeon 	host->bus_hz = actual / div;
269c6d9dedaSSeungwon Jeon 	priv->cur_speed = wanted;
270c6d9dedaSSeungwon Jeon 	host->current_speed = 0;
271c6d9dedaSSeungwon Jeon }
27280113132SSeungwon Jeon 
27380113132SSeungwon Jeon static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
27480113132SSeungwon Jeon {
27580113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
27680113132SSeungwon Jeon 	unsigned int wanted = ios->clock;
27780113132SSeungwon Jeon 	u32 timing = ios->timing, clksel;
27880113132SSeungwon Jeon 
27980113132SSeungwon Jeon 	switch (timing) {
28080113132SSeungwon Jeon 	case MMC_TIMING_MMC_HS400:
28180113132SSeungwon Jeon 		/* Update tuned sample timing */
28280113132SSeungwon Jeon 		clksel = SDMMC_CLKSEL_UP_SAMPLE(
28380113132SSeungwon Jeon 				priv->hs400_timing, priv->tuned_sample);
28480113132SSeungwon Jeon 		wanted <<= 1;
28580113132SSeungwon Jeon 		break;
28680113132SSeungwon Jeon 	case MMC_TIMING_MMC_DDR52:
28780113132SSeungwon Jeon 		clksel = priv->ddr_timing;
28880113132SSeungwon Jeon 		/* Should be double rate for DDR mode */
28980113132SSeungwon Jeon 		if (ios->bus_width == MMC_BUS_WIDTH_8)
29080113132SSeungwon Jeon 			wanted <<= 1;
29180113132SSeungwon Jeon 		break;
29280113132SSeungwon Jeon 	default:
29380113132SSeungwon Jeon 		clksel = priv->sdr_timing;
29480113132SSeungwon Jeon 	}
29580113132SSeungwon Jeon 
29680113132SSeungwon Jeon 	/* Set clock timing for the requested speed mode*/
29780113132SSeungwon Jeon 	dw_mci_exynos_set_clksel_timing(host, clksel);
29880113132SSeungwon Jeon 
29980113132SSeungwon Jeon 	/* Configure setting for HS400 */
30080113132SSeungwon Jeon 	dw_mci_exynos_config_hs400(host, timing);
30180113132SSeungwon Jeon 
30280113132SSeungwon Jeon 	/* Configure clock rate */
30380113132SSeungwon Jeon 	dw_mci_exynos_adjust_clock(host, wanted);
304c6d9dedaSSeungwon Jeon }
305c6d9dedaSSeungwon Jeon 
306c3665006SThomas Abraham static int dw_mci_exynos_parse_dt(struct dw_mci *host)
307c3665006SThomas Abraham {
308e6c784edSYuvaraj Kumar C D 	struct dw_mci_exynos_priv_data *priv;
309c3665006SThomas Abraham 	struct device_node *np = host->dev->of_node;
310c3665006SThomas Abraham 	u32 timing[2];
311c3665006SThomas Abraham 	u32 div = 0;
312e6c784edSYuvaraj Kumar C D 	int idx;
313c3665006SThomas Abraham 	int ret;
314c3665006SThomas Abraham 
315e6c784edSYuvaraj Kumar C D 	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
316bf3707eaSBeomho Seo 	if (!priv)
317e6c784edSYuvaraj Kumar C D 		return -ENOMEM;
318e6c784edSYuvaraj Kumar C D 
319e6c784edSYuvaraj Kumar C D 	for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
320e6c784edSYuvaraj Kumar C D 		if (of_device_is_compatible(np, exynos_compat[idx].compatible))
321e6c784edSYuvaraj Kumar C D 			priv->ctrl_type = exynos_compat[idx].ctrl_type;
322e6c784edSYuvaraj Kumar C D 	}
323e6c784edSYuvaraj Kumar C D 
324c6d9dedaSSeungwon Jeon 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
325c6d9dedaSSeungwon Jeon 		priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1;
326c6d9dedaSSeungwon Jeon 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
327c6d9dedaSSeungwon Jeon 		priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1;
328c6d9dedaSSeungwon Jeon 	else {
329c3665006SThomas Abraham 		of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
330c3665006SThomas Abraham 		priv->ciu_div = div;
331c6d9dedaSSeungwon Jeon 	}
332c3665006SThomas Abraham 
333c3665006SThomas Abraham 	ret = of_property_read_u32_array(np,
334c3665006SThomas Abraham 			"samsung,dw-mshc-sdr-timing", timing, 2);
335c3665006SThomas Abraham 	if (ret)
336c3665006SThomas Abraham 		return ret;
337c3665006SThomas Abraham 
3382d9f0bd1SYuvaraj Kumar C D 	priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
3392d9f0bd1SYuvaraj Kumar C D 
340c3665006SThomas Abraham 	ret = of_property_read_u32_array(np,
341c3665006SThomas Abraham 			"samsung,dw-mshc-ddr-timing", timing, 2);
342c3665006SThomas Abraham 	if (ret)
343c3665006SThomas Abraham 		return ret;
344c3665006SThomas Abraham 
345c3665006SThomas Abraham 	priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
34680113132SSeungwon Jeon 
34780113132SSeungwon Jeon 	ret = of_property_read_u32_array(np,
34880113132SSeungwon Jeon 			"samsung,dw-mshc-hs400-timing", timing, 2);
34980113132SSeungwon Jeon 	if (!ret && of_property_read_u32(np,
35080113132SSeungwon Jeon 				"samsung,read-strobe-delay", &priv->dqs_delay))
35180113132SSeungwon Jeon 		dev_dbg(host->dev,
35280113132SSeungwon Jeon 			"read-strobe-delay is not found, assuming usage of default value\n");
35380113132SSeungwon Jeon 
35480113132SSeungwon Jeon 	priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1],
35580113132SSeungwon Jeon 						HS400_FIXED_CIU_CLK_DIV);
356e6c784edSYuvaraj Kumar C D 	host->priv = priv;
357c3665006SThomas Abraham 	return 0;
358c3665006SThomas Abraham }
359c3665006SThomas Abraham 
360c537a1c5SSeungwon Jeon static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host)
361c537a1c5SSeungwon Jeon {
36289ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
36389ad2be7SAbhilash Kesavan 
36489ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
36589ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
36689ad2be7SAbhilash Kesavan 		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL64));
36789ad2be7SAbhilash Kesavan 	else
368c537a1c5SSeungwon Jeon 		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL));
369c537a1c5SSeungwon Jeon }
370c537a1c5SSeungwon Jeon 
371c537a1c5SSeungwon Jeon static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
372c537a1c5SSeungwon Jeon {
373c537a1c5SSeungwon Jeon 	u32 clksel;
37489ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
37589ad2be7SAbhilash Kesavan 
37689ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
37789ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
37889ad2be7SAbhilash Kesavan 		clksel = mci_readl(host, CLKSEL64);
37989ad2be7SAbhilash Kesavan 	else
380c537a1c5SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL);
38180113132SSeungwon Jeon 	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
38289ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
38389ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
38489ad2be7SAbhilash Kesavan 		mci_writel(host, CLKSEL64, clksel);
38589ad2be7SAbhilash Kesavan 	else
386c537a1c5SSeungwon Jeon 		mci_writel(host, CLKSEL, clksel);
387c537a1c5SSeungwon Jeon }
388c537a1c5SSeungwon Jeon 
389c537a1c5SSeungwon Jeon static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
390c537a1c5SSeungwon Jeon {
39189ad2be7SAbhilash Kesavan 	struct dw_mci_exynos_priv_data *priv = host->priv;
392c537a1c5SSeungwon Jeon 	u32 clksel;
393c537a1c5SSeungwon Jeon 	u8 sample;
394c537a1c5SSeungwon Jeon 
39589ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
39689ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
39789ad2be7SAbhilash Kesavan 		clksel = mci_readl(host, CLKSEL64);
39889ad2be7SAbhilash Kesavan 	else
399c537a1c5SSeungwon Jeon 		clksel = mci_readl(host, CLKSEL);
40080113132SSeungwon Jeon 
401c537a1c5SSeungwon Jeon 	sample = (clksel + 1) & 0x7;
40280113132SSeungwon Jeon 	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
40380113132SSeungwon Jeon 
40489ad2be7SAbhilash Kesavan 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
40589ad2be7SAbhilash Kesavan 		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
40689ad2be7SAbhilash Kesavan 		mci_writel(host, CLKSEL64, clksel);
40789ad2be7SAbhilash Kesavan 	else
408c537a1c5SSeungwon Jeon 		mci_writel(host, CLKSEL, clksel);
40980113132SSeungwon Jeon 
410c537a1c5SSeungwon Jeon 	return sample;
411c537a1c5SSeungwon Jeon }
412c537a1c5SSeungwon Jeon 
413c537a1c5SSeungwon Jeon static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates)
414c537a1c5SSeungwon Jeon {
415c537a1c5SSeungwon Jeon 	const u8 iter = 8;
416c537a1c5SSeungwon Jeon 	u8 __c;
417c537a1c5SSeungwon Jeon 	s8 i, loc = -1;
418c537a1c5SSeungwon Jeon 
419c537a1c5SSeungwon Jeon 	for (i = 0; i < iter; i++) {
420c537a1c5SSeungwon Jeon 		__c = ror8(candiates, i);
421c537a1c5SSeungwon Jeon 		if ((__c & 0xc7) == 0xc7) {
422c537a1c5SSeungwon Jeon 			loc = i;
423c537a1c5SSeungwon Jeon 			goto out;
424c537a1c5SSeungwon Jeon 		}
425c537a1c5SSeungwon Jeon 	}
426c537a1c5SSeungwon Jeon 
427c537a1c5SSeungwon Jeon 	for (i = 0; i < iter; i++) {
428c537a1c5SSeungwon Jeon 		__c = ror8(candiates, i);
429c537a1c5SSeungwon Jeon 		if ((__c & 0x83) == 0x83) {
430c537a1c5SSeungwon Jeon 			loc = i;
431c537a1c5SSeungwon Jeon 			goto out;
432c537a1c5SSeungwon Jeon 		}
433c537a1c5SSeungwon Jeon 	}
434c537a1c5SSeungwon Jeon 
435c537a1c5SSeungwon Jeon out:
436c537a1c5SSeungwon Jeon 	return loc;
437c537a1c5SSeungwon Jeon }
438c537a1c5SSeungwon Jeon 
4399979dbe5SChaotian Jing static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
440c537a1c5SSeungwon Jeon {
441c537a1c5SSeungwon Jeon 	struct dw_mci *host = slot->host;
44280113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
443c537a1c5SSeungwon Jeon 	struct mmc_host *mmc = slot->mmc;
444c537a1c5SSeungwon Jeon 	u8 start_smpl, smpl, candiates = 0;
445c537a1c5SSeungwon Jeon 	s8 found = -1;
446c537a1c5SSeungwon Jeon 	int ret = 0;
447c537a1c5SSeungwon Jeon 
448c537a1c5SSeungwon Jeon 	start_smpl = dw_mci_exynos_get_clksmpl(host);
449c537a1c5SSeungwon Jeon 
450c537a1c5SSeungwon Jeon 	do {
451c537a1c5SSeungwon Jeon 		mci_writel(host, TMOUT, ~0);
452c537a1c5SSeungwon Jeon 		smpl = dw_mci_exynos_move_next_clksmpl(host);
453c537a1c5SSeungwon Jeon 
4549979dbe5SChaotian Jing 		if (!mmc_send_tuning(mmc, opcode, NULL))
455c537a1c5SSeungwon Jeon 			candiates |= (1 << smpl);
4566c2c6506SUlf Hansson 
457c537a1c5SSeungwon Jeon 	} while (start_smpl != smpl);
458c537a1c5SSeungwon Jeon 
459c537a1c5SSeungwon Jeon 	found = dw_mci_exynos_get_best_clksmpl(candiates);
46080113132SSeungwon Jeon 	if (found >= 0) {
461c537a1c5SSeungwon Jeon 		dw_mci_exynos_set_clksmpl(host, found);
46280113132SSeungwon Jeon 		priv->tuned_sample = found;
46380113132SSeungwon Jeon 	} else {
464c537a1c5SSeungwon Jeon 		ret = -EIO;
46580113132SSeungwon Jeon 	}
466c537a1c5SSeungwon Jeon 
467c537a1c5SSeungwon Jeon 	return ret;
468c537a1c5SSeungwon Jeon }
469c537a1c5SSeungwon Jeon 
470c22f5e1bSWu Fengguang static int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host,
47180113132SSeungwon Jeon 					struct mmc_ios *ios)
47280113132SSeungwon Jeon {
47380113132SSeungwon Jeon 	struct dw_mci_exynos_priv_data *priv = host->priv;
47480113132SSeungwon Jeon 
47580113132SSeungwon Jeon 	dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing);
47680113132SSeungwon Jeon 	dw_mci_exynos_adjust_clock(host, (ios->clock) << 1);
47780113132SSeungwon Jeon 
47880113132SSeungwon Jeon 	return 0;
47980113132SSeungwon Jeon }
48080113132SSeungwon Jeon 
4810f6e73d0SDongjin Kim /* Common capabilities of Exynos4/Exynos5 SoC */
4820f6e73d0SDongjin Kim static unsigned long exynos_dwmmc_caps[4] = {
483cab3a802SSeungwon Jeon 	MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
484c3665006SThomas Abraham 	MMC_CAP_CMD23,
485c3665006SThomas Abraham 	MMC_CAP_CMD23,
486c3665006SThomas Abraham 	MMC_CAP_CMD23,
487c3665006SThomas Abraham };
488c3665006SThomas Abraham 
4890f6e73d0SDongjin Kim static const struct dw_mci_drv_data exynos_drv_data = {
4900f6e73d0SDongjin Kim 	.caps			= exynos_dwmmc_caps,
491c3665006SThomas Abraham 	.init			= dw_mci_exynos_priv_init,
492c3665006SThomas Abraham 	.set_ios		= dw_mci_exynos_set_ios,
493c3665006SThomas Abraham 	.parse_dt		= dw_mci_exynos_parse_dt,
494c537a1c5SSeungwon Jeon 	.execute_tuning		= dw_mci_exynos_execute_tuning,
49580113132SSeungwon Jeon 	.prepare_hs400_tuning	= dw_mci_exynos_prepare_hs400_tuning,
496c3665006SThomas Abraham };
497c3665006SThomas Abraham 
498c3665006SThomas Abraham static const struct of_device_id dw_mci_exynos_match[] = {
4990f6e73d0SDongjin Kim 	{ .compatible = "samsung,exynos4412-dw-mshc",
5000f6e73d0SDongjin Kim 			.data = &exynos_drv_data, },
501c3665006SThomas Abraham 	{ .compatible = "samsung,exynos5250-dw-mshc",
5020f6e73d0SDongjin Kim 			.data = &exynos_drv_data, },
50300fd041bSYuvaraj Kumar C D 	{ .compatible = "samsung,exynos5420-dw-mshc",
50400fd041bSYuvaraj Kumar C D 			.data = &exynos_drv_data, },
5056bce431cSYuvaraj Kumar C D 	{ .compatible = "samsung,exynos5420-dw-mshc-smu",
5066bce431cSYuvaraj Kumar C D 			.data = &exynos_drv_data, },
50789ad2be7SAbhilash Kesavan 	{ .compatible = "samsung,exynos7-dw-mshc",
50889ad2be7SAbhilash Kesavan 			.data = &exynos_drv_data, },
50989ad2be7SAbhilash Kesavan 	{ .compatible = "samsung,exynos7-dw-mshc-smu",
51089ad2be7SAbhilash Kesavan 			.data = &exynos_drv_data, },
511c3665006SThomas Abraham 	{},
512c3665006SThomas Abraham };
513517cb9f1SArnd Bergmann MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
514c3665006SThomas Abraham 
5159665f7f2SSachin Kamat static int dw_mci_exynos_probe(struct platform_device *pdev)
516c3665006SThomas Abraham {
5178e2b36eaSArnd Bergmann 	const struct dw_mci_drv_data *drv_data;
518c3665006SThomas Abraham 	const struct of_device_id *match;
519*9b93d392SJoonyoung Shim 	int ret;
520c3665006SThomas Abraham 
521c3665006SThomas Abraham 	match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
522c3665006SThomas Abraham 	drv_data = match->data;
523*9b93d392SJoonyoung Shim 
524*9b93d392SJoonyoung Shim 	pm_runtime_get_noresume(&pdev->dev);
525*9b93d392SJoonyoung Shim 	pm_runtime_set_active(&pdev->dev);
526*9b93d392SJoonyoung Shim 	pm_runtime_enable(&pdev->dev);
527*9b93d392SJoonyoung Shim 
528*9b93d392SJoonyoung Shim 	ret = dw_mci_pltfm_register(pdev, drv_data);
529*9b93d392SJoonyoung Shim 	if (ret) {
530*9b93d392SJoonyoung Shim 		pm_runtime_disable(&pdev->dev);
531*9b93d392SJoonyoung Shim 		pm_runtime_set_suspended(&pdev->dev);
532*9b93d392SJoonyoung Shim 		pm_runtime_put_noidle(&pdev->dev);
533*9b93d392SJoonyoung Shim 
534*9b93d392SJoonyoung Shim 		return ret;
535*9b93d392SJoonyoung Shim 	}
536*9b93d392SJoonyoung Shim 
537*9b93d392SJoonyoung Shim 	return 0;
538*9b93d392SJoonyoung Shim }
539*9b93d392SJoonyoung Shim 
540*9b93d392SJoonyoung Shim static int dw_mci_exynos_remove(struct platform_device *pdev)
541*9b93d392SJoonyoung Shim {
542*9b93d392SJoonyoung Shim 	pm_runtime_disable(&pdev->dev);
543*9b93d392SJoonyoung Shim 	pm_runtime_set_suspended(&pdev->dev);
544*9b93d392SJoonyoung Shim 	pm_runtime_put_noidle(&pdev->dev);
545*9b93d392SJoonyoung Shim 
546*9b93d392SJoonyoung Shim 	return dw_mci_pltfm_remove(pdev);
547c3665006SThomas Abraham }
548c3665006SThomas Abraham 
54915a2e2abSSachin Kamat static const struct dev_pm_ops dw_mci_exynos_pmops = {
550cf5237efSShawn Lin 	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
551cf5237efSShawn Lin 				pm_runtime_force_resume)
552cf5237efSShawn Lin 	SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend,
553cf5237efSShawn Lin 			   dw_mci_exynos_runtime_resume,
554cf5237efSShawn Lin 			   NULL)
555e2c63599SDoug Anderson 	.resume_noirq = dw_mci_exynos_resume_noirq,
556e2c63599SDoug Anderson 	.thaw_noirq = dw_mci_exynos_resume_noirq,
557e2c63599SDoug Anderson 	.restore_noirq = dw_mci_exynos_resume_noirq,
558e2c63599SDoug Anderson };
559e2c63599SDoug Anderson 
560c3665006SThomas Abraham static struct platform_driver dw_mci_exynos_pltfm_driver = {
561c3665006SThomas Abraham 	.probe		= dw_mci_exynos_probe,
562*9b93d392SJoonyoung Shim 	.remove		= dw_mci_exynos_remove,
563c3665006SThomas Abraham 	.driver		= {
564c3665006SThomas Abraham 		.name		= "dwmmc_exynos",
56520183d50SSachin Kamat 		.of_match_table	= dw_mci_exynos_match,
566e2c63599SDoug Anderson 		.pm		= &dw_mci_exynos_pmops,
567c3665006SThomas Abraham 	},
568c3665006SThomas Abraham };
569c3665006SThomas Abraham 
570c3665006SThomas Abraham module_platform_driver(dw_mci_exynos_pltfm_driver);
571c3665006SThomas Abraham 
572c3665006SThomas Abraham MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
573c3665006SThomas Abraham MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
574c3665006SThomas Abraham MODULE_LICENSE("GPL v2");
5752fc546fdSZhangfei Gao MODULE_ALIAS("platform:dwmmc_exynos");
576