1/*
2 * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver
3 *
4 * Copyright (C) 2012, Samsung Electronics Co., Ltd.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 */
11
12#include <linux/module.h>
13#include <linux/platform_device.h>
14#include <linux/clk.h>
15#include <linux/mmc/host.h>
16#include <linux/mmc/dw_mmc.h>
17#include <linux/mmc/mmc.h>
18#include <linux/of.h>
19#include <linux/of_gpio.h>
20#include <linux/slab.h>
21
22#include "dw_mmc.h"
23#include "dw_mmc-pltfm.h"
24#include "dw_mmc-exynos.h"
25
26/* Variations in Exynos specific dw-mshc controller */
27enum dw_mci_exynos_type {
28	DW_MCI_TYPE_EXYNOS4210,
29	DW_MCI_TYPE_EXYNOS4412,
30	DW_MCI_TYPE_EXYNOS5250,
31	DW_MCI_TYPE_EXYNOS5420,
32	DW_MCI_TYPE_EXYNOS5420_SMU,
33	DW_MCI_TYPE_EXYNOS7,
34	DW_MCI_TYPE_EXYNOS7_SMU,
35};
36
37/* Exynos implementation specific driver private data */
38struct dw_mci_exynos_priv_data {
39	enum dw_mci_exynos_type		ctrl_type;
40	u8				ciu_div;
41	u32				sdr_timing;
42	u32				ddr_timing;
43	u32				hs400_timing;
44	u32				tuned_sample;
45	u32				cur_speed;
46	u32				dqs_delay;
47	u32				saved_dqs_en;
48	u32				saved_strobe_ctrl;
49};
50
51static struct dw_mci_exynos_compatible {
52	char				*compatible;
53	enum dw_mci_exynos_type		ctrl_type;
54} exynos_compat[] = {
55	{
56		.compatible	= "samsung,exynos4210-dw-mshc",
57		.ctrl_type	= DW_MCI_TYPE_EXYNOS4210,
58	}, {
59		.compatible	= "samsung,exynos4412-dw-mshc",
60		.ctrl_type	= DW_MCI_TYPE_EXYNOS4412,
61	}, {
62		.compatible	= "samsung,exynos5250-dw-mshc",
63		.ctrl_type	= DW_MCI_TYPE_EXYNOS5250,
64	}, {
65		.compatible	= "samsung,exynos5420-dw-mshc",
66		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420,
67	}, {
68		.compatible	= "samsung,exynos5420-dw-mshc-smu",
69		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420_SMU,
70	}, {
71		.compatible	= "samsung,exynos7-dw-mshc",
72		.ctrl_type	= DW_MCI_TYPE_EXYNOS7,
73	}, {
74		.compatible	= "samsung,exynos7-dw-mshc-smu",
75		.ctrl_type	= DW_MCI_TYPE_EXYNOS7_SMU,
76	},
77};
78
79static inline u8 dw_mci_exynos_get_ciu_div(struct dw_mci *host)
80{
81	struct dw_mci_exynos_priv_data *priv = host->priv;
82
83	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
84		return EXYNOS4412_FIXED_CIU_CLK_DIV;
85	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
86		return EXYNOS4210_FIXED_CIU_CLK_DIV;
87	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
88			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
89		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL64)) + 1;
90	else
91		return SDMMC_CLKSEL_GET_DIV(mci_readl(host, CLKSEL)) + 1;
92}
93
94static int dw_mci_exynos_priv_init(struct dw_mci *host)
95{
96	struct dw_mci_exynos_priv_data *priv = host->priv;
97
98	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU ||
99		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU) {
100		mci_writel(host, MPSBEGIN0, 0);
101		mci_writel(host, MPSEND0, SDMMC_ENDING_SEC_NR_MAX);
102		mci_writel(host, MPSCTRL0, SDMMC_MPSCTRL_SECURE_WRITE_BIT |
103			   SDMMC_MPSCTRL_NON_SECURE_READ_BIT |
104			   SDMMC_MPSCTRL_VALID |
105			   SDMMC_MPSCTRL_NON_SECURE_WRITE_BIT);
106	}
107
108	if (priv->ctrl_type >= DW_MCI_TYPE_EXYNOS5420) {
109		priv->saved_strobe_ctrl = mci_readl(host, HS400_DLINE_CTRL);
110		priv->saved_dqs_en = mci_readl(host, HS400_DQS_EN);
111		priv->saved_dqs_en |= AXI_NON_BLOCKING_WR;
112		mci_writel(host, HS400_DQS_EN, priv->saved_dqs_en);
113		if (!priv->dqs_delay)
114			priv->dqs_delay =
115				DQS_CTRL_GET_RD_DELAY(priv->saved_strobe_ctrl);
116	}
117
118	return 0;
119}
120
121static int dw_mci_exynos_setup_clock(struct dw_mci *host)
122{
123	struct dw_mci_exynos_priv_data *priv = host->priv;
124
125	host->bus_hz /= (priv->ciu_div + 1);
126
127	return 0;
128}
129
130static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing)
131{
132	struct dw_mci_exynos_priv_data *priv = host->priv;
133	u32 clksel;
134
135	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
136		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
137		clksel = mci_readl(host, CLKSEL64);
138	else
139		clksel = mci_readl(host, CLKSEL);
140
141	clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing;
142
143	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
144		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
145		mci_writel(host, CLKSEL64, clksel);
146	else
147		mci_writel(host, CLKSEL, clksel);
148}
149
150#ifdef CONFIG_PM_SLEEP
151static int dw_mci_exynos_suspend(struct device *dev)
152{
153	struct dw_mci *host = dev_get_drvdata(dev);
154
155	return dw_mci_suspend(host);
156}
157
158static int dw_mci_exynos_resume(struct device *dev)
159{
160	struct dw_mci *host = dev_get_drvdata(dev);
161
162	dw_mci_exynos_priv_init(host);
163	return dw_mci_resume(host);
164}
165
166/**
167 * dw_mci_exynos_resume_noirq - Exynos-specific resume code
168 *
169 * On exynos5420 there is a silicon errata that will sometimes leave the
170 * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate
171 * that it fired and we can clear it by writing a 1 back.  Clear it to prevent
172 * interrupts from going off constantly.
173 *
174 * We run this code on all exynos variants because it doesn't hurt.
175 */
176
177static int dw_mci_exynos_resume_noirq(struct device *dev)
178{
179	struct dw_mci *host = dev_get_drvdata(dev);
180	struct dw_mci_exynos_priv_data *priv = host->priv;
181	u32 clksel;
182
183	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
184		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
185		clksel = mci_readl(host, CLKSEL64);
186	else
187		clksel = mci_readl(host, CLKSEL);
188
189	if (clksel & SDMMC_CLKSEL_WAKEUP_INT) {
190		if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
191			priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
192			mci_writel(host, CLKSEL64, clksel);
193		else
194			mci_writel(host, CLKSEL, clksel);
195	}
196
197	return 0;
198}
199#else
200#define dw_mci_exynos_suspend		NULL
201#define dw_mci_exynos_resume		NULL
202#define dw_mci_exynos_resume_noirq	NULL
203#endif /* CONFIG_PM_SLEEP */
204
205static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr)
206{
207	struct dw_mci_exynos_priv_data *priv = host->priv;
208	/*
209	 * Exynos4412 and Exynos5250 extends the use of CMD register with the
210	 * use of bit 29 (which is reserved on standard MSHC controllers) for
211	 * optionally bypassing the HOLD register for command and data. The
212	 * HOLD register should be bypassed in case there is no phase shift
213	 * applied on CMD/DATA that is sent to the card.
214	 */
215	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
216		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU) {
217		if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL64)))
218			*cmdr |= SDMMC_CMD_USE_HOLD_REG;
219	 } else {
220		if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL)))
221			*cmdr |= SDMMC_CMD_USE_HOLD_REG;
222	}
223}
224
225static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing)
226{
227	struct dw_mci_exynos_priv_data *priv = host->priv;
228	u32 dqs, strobe;
229
230	/*
231	 * Not supported to configure register
232	 * related to HS400
233	 */
234	if (priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420)
235		return;
236
237	dqs = priv->saved_dqs_en;
238	strobe = priv->saved_strobe_ctrl;
239
240	if (timing == MMC_TIMING_MMC_HS400) {
241		dqs |= DATA_STROBE_EN;
242		strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay);
243	} else {
244		dqs &= ~DATA_STROBE_EN;
245	}
246
247	mci_writel(host, HS400_DQS_EN, dqs);
248	mci_writel(host, HS400_DLINE_CTRL, strobe);
249}
250
251static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted)
252{
253	struct dw_mci_exynos_priv_data *priv = host->priv;
254	unsigned long actual;
255	u8 div;
256	int ret;
257	/*
258	 * Don't care if wanted clock is zero or
259	 * ciu clock is unavailable
260	 */
261	if (!wanted || IS_ERR(host->ciu_clk))
262		return;
263
264	/* Guaranteed minimum frequency for cclkin */
265	if (wanted < EXYNOS_CCLKIN_MIN)
266		wanted = EXYNOS_CCLKIN_MIN;
267
268	if (wanted == priv->cur_speed)
269		return;
270
271	div = dw_mci_exynos_get_ciu_div(host);
272	ret = clk_set_rate(host->ciu_clk, wanted * div);
273	if (ret)
274		dev_warn(host->dev,
275			"failed to set clk-rate %u error: %d\n",
276			wanted * div, ret);
277	actual = clk_get_rate(host->ciu_clk);
278	host->bus_hz = actual / div;
279	priv->cur_speed = wanted;
280	host->current_speed = 0;
281}
282
283static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
284{
285	struct dw_mci_exynos_priv_data *priv = host->priv;
286	unsigned int wanted = ios->clock;
287	u32 timing = ios->timing, clksel;
288
289	switch (timing) {
290	case MMC_TIMING_MMC_HS400:
291		/* Update tuned sample timing */
292		clksel = SDMMC_CLKSEL_UP_SAMPLE(
293				priv->hs400_timing, priv->tuned_sample);
294		wanted <<= 1;
295		break;
296	case MMC_TIMING_MMC_DDR52:
297		clksel = priv->ddr_timing;
298		/* Should be double rate for DDR mode */
299		if (ios->bus_width == MMC_BUS_WIDTH_8)
300			wanted <<= 1;
301		break;
302	default:
303		clksel = priv->sdr_timing;
304	}
305
306	/* Set clock timing for the requested speed mode*/
307	dw_mci_exynos_set_clksel_timing(host, clksel);
308
309	/* Configure setting for HS400 */
310	dw_mci_exynos_config_hs400(host, timing);
311
312	/* Configure clock rate */
313	dw_mci_exynos_adjust_clock(host, wanted);
314}
315
316static int dw_mci_exynos_parse_dt(struct dw_mci *host)
317{
318	struct dw_mci_exynos_priv_data *priv;
319	struct device_node *np = host->dev->of_node;
320	u32 timing[2];
321	u32 div = 0;
322	int idx;
323	int ret;
324
325	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
326	if (!priv)
327		return -ENOMEM;
328
329	for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
330		if (of_device_is_compatible(np, exynos_compat[idx].compatible))
331			priv->ctrl_type = exynos_compat[idx].ctrl_type;
332	}
333
334	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
335		priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1;
336	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
337		priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1;
338	else {
339		of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
340		priv->ciu_div = div;
341	}
342
343	ret = of_property_read_u32_array(np,
344			"samsung,dw-mshc-sdr-timing", timing, 2);
345	if (ret)
346		return ret;
347
348	priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
349
350	ret = of_property_read_u32_array(np,
351			"samsung,dw-mshc-ddr-timing", timing, 2);
352	if (ret)
353		return ret;
354
355	priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
356
357	ret = of_property_read_u32_array(np,
358			"samsung,dw-mshc-hs400-timing", timing, 2);
359	if (!ret && of_property_read_u32(np,
360				"samsung,read-strobe-delay", &priv->dqs_delay))
361		dev_dbg(host->dev,
362			"read-strobe-delay is not found, assuming usage of default value\n");
363
364	priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1],
365						HS400_FIXED_CIU_CLK_DIV);
366	host->priv = priv;
367	return 0;
368}
369
370static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host)
371{
372	struct dw_mci_exynos_priv_data *priv = host->priv;
373
374	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
375		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
376		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL64));
377	else
378		return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL));
379}
380
381static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
382{
383	u32 clksel;
384	struct dw_mci_exynos_priv_data *priv = host->priv;
385
386	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
387		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
388		clksel = mci_readl(host, CLKSEL64);
389	else
390		clksel = mci_readl(host, CLKSEL);
391	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
392	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
393		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
394		mci_writel(host, CLKSEL64, clksel);
395	else
396		mci_writel(host, CLKSEL, clksel);
397}
398
399static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
400{
401	struct dw_mci_exynos_priv_data *priv = host->priv;
402	u32 clksel;
403	u8 sample;
404
405	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
406		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
407		clksel = mci_readl(host, CLKSEL64);
408	else
409		clksel = mci_readl(host, CLKSEL);
410
411	sample = (clksel + 1) & 0x7;
412	clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample);
413
414	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 ||
415		priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU)
416		mci_writel(host, CLKSEL64, clksel);
417	else
418		mci_writel(host, CLKSEL, clksel);
419
420	return sample;
421}
422
423static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates)
424{
425	const u8 iter = 8;
426	u8 __c;
427	s8 i, loc = -1;
428
429	for (i = 0; i < iter; i++) {
430		__c = ror8(candiates, i);
431		if ((__c & 0xc7) == 0xc7) {
432			loc = i;
433			goto out;
434		}
435	}
436
437	for (i = 0; i < iter; i++) {
438		__c = ror8(candiates, i);
439		if ((__c & 0x83) == 0x83) {
440			loc = i;
441			goto out;
442		}
443	}
444
445out:
446	return loc;
447}
448
449static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot)
450{
451	struct dw_mci *host = slot->host;
452	struct dw_mci_exynos_priv_data *priv = host->priv;
453	struct mmc_host *mmc = slot->mmc;
454	u8 start_smpl, smpl, candiates = 0;
455	s8 found = -1;
456	int ret = 0;
457
458	start_smpl = dw_mci_exynos_get_clksmpl(host);
459
460	do {
461		mci_writel(host, TMOUT, ~0);
462		smpl = dw_mci_exynos_move_next_clksmpl(host);
463
464		if (!mmc_send_tuning(mmc))
465			candiates |= (1 << smpl);
466
467	} while (start_smpl != smpl);
468
469	found = dw_mci_exynos_get_best_clksmpl(candiates);
470	if (found >= 0) {
471		dw_mci_exynos_set_clksmpl(host, found);
472		priv->tuned_sample = found;
473	} else {
474		ret = -EIO;
475	}
476
477	return ret;
478}
479
480static int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host,
481					struct mmc_ios *ios)
482{
483	struct dw_mci_exynos_priv_data *priv = host->priv;
484
485	dw_mci_exynos_set_clksel_timing(host, priv->hs400_timing);
486	dw_mci_exynos_adjust_clock(host, (ios->clock) << 1);
487
488	return 0;
489}
490
491/* Common capabilities of Exynos4/Exynos5 SoC */
492static unsigned long exynos_dwmmc_caps[4] = {
493	MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
494	MMC_CAP_CMD23,
495	MMC_CAP_CMD23,
496	MMC_CAP_CMD23,
497};
498
499static const struct dw_mci_drv_data exynos_drv_data = {
500	.caps			= exynos_dwmmc_caps,
501	.init			= dw_mci_exynos_priv_init,
502	.setup_clock		= dw_mci_exynos_setup_clock,
503	.prepare_command	= dw_mci_exynos_prepare_command,
504	.set_ios		= dw_mci_exynos_set_ios,
505	.parse_dt		= dw_mci_exynos_parse_dt,
506	.execute_tuning		= dw_mci_exynos_execute_tuning,
507	.prepare_hs400_tuning	= dw_mci_exynos_prepare_hs400_tuning,
508};
509
510static const struct of_device_id dw_mci_exynos_match[] = {
511	{ .compatible = "samsung,exynos4412-dw-mshc",
512			.data = &exynos_drv_data, },
513	{ .compatible = "samsung,exynos5250-dw-mshc",
514			.data = &exynos_drv_data, },
515	{ .compatible = "samsung,exynos5420-dw-mshc",
516			.data = &exynos_drv_data, },
517	{ .compatible = "samsung,exynos5420-dw-mshc-smu",
518			.data = &exynos_drv_data, },
519	{ .compatible = "samsung,exynos7-dw-mshc",
520			.data = &exynos_drv_data, },
521	{ .compatible = "samsung,exynos7-dw-mshc-smu",
522			.data = &exynos_drv_data, },
523	{},
524};
525MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
526
527static int dw_mci_exynos_probe(struct platform_device *pdev)
528{
529	const struct dw_mci_drv_data *drv_data;
530	const struct of_device_id *match;
531
532	match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
533	drv_data = match->data;
534	return dw_mci_pltfm_register(pdev, drv_data);
535}
536
537static const struct dev_pm_ops dw_mci_exynos_pmops = {
538	SET_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend, dw_mci_exynos_resume)
539	.resume_noirq = dw_mci_exynos_resume_noirq,
540	.thaw_noirq = dw_mci_exynos_resume_noirq,
541	.restore_noirq = dw_mci_exynos_resume_noirq,
542};
543
544static struct platform_driver dw_mci_exynos_pltfm_driver = {
545	.probe		= dw_mci_exynos_probe,
546	.remove		= dw_mci_pltfm_remove,
547	.driver		= {
548		.name		= "dwmmc_exynos",
549		.of_match_table	= dw_mci_exynos_match,
550		.pm		= &dw_mci_exynos_pmops,
551	},
552};
553
554module_platform_driver(dw_mci_exynos_pltfm_driver);
555
556MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
557MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
558MODULE_LICENSE("GPL v2");
559MODULE_ALIAS("platform:dwmmc-exynos");
560