blob: b86f51845230d6e20297b7b8d0c8470234ab3a22 [file] [log] [blame]
Thomas Abrahamc3665002012-09-17 18:16:43 +00001/*
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>
Seungwon Jeonc537a1c2013-08-31 00:12:50 +090017#include <linux/mmc/mmc.h>
Thomas Abrahamc3665002012-09-17 18:16:43 +000018#include <linux/of.h>
19#include <linux/of_gpio.h>
Seungwon Jeonc537a1c2013-08-31 00:12:50 +090020#include <linux/slab.h>
Thomas Abrahamc3665002012-09-17 18:16:43 +000021
22#include "dw_mmc.h"
23#include "dw_mmc-pltfm.h"
24
25#define NUM_PINS(x) (x + 2)
26
27#define SDMMC_CLKSEL 0x09C
28#define SDMMC_CLKSEL_CCLK_SAMPLE(x) (((x) & 7) << 0)
29#define SDMMC_CLKSEL_CCLK_DRIVE(x) (((x) & 7) << 16)
30#define SDMMC_CLKSEL_CCLK_DIVIDER(x) (((x) & 7) << 24)
31#define SDMMC_CLKSEL_GET_DRV_WD3(x) (((x) >> 16) & 0x7)
32#define SDMMC_CLKSEL_TIMING(x, y, z) (SDMMC_CLKSEL_CCLK_SAMPLE(x) | \
33 SDMMC_CLKSEL_CCLK_DRIVE(y) | \
34 SDMMC_CLKSEL_CCLK_DIVIDER(z))
Doug Andersone2c63592013-08-31 00:11:21 +090035#define SDMMC_CLKSEL_WAKEUP_INT BIT(11)
Thomas Abrahamc3665002012-09-17 18:16:43 +000036
Thomas Abrahamc3665002012-09-17 18:16:43 +000037#define EXYNOS4210_FIXED_CIU_CLK_DIV 2
38#define EXYNOS4412_FIXED_CIU_CLK_DIV 4
39
Yuvaraj Kumar C D6bce4312013-08-31 00:12:35 +090040/* Block number in eMMC */
41#define DWMCI_BLOCK_NUM 0xFFFFFFFF
42
43#define SDMMC_EMMCP_BASE 0x1000
44#define SDMMC_MPSECURITY (SDMMC_EMMCP_BASE + 0x0010)
45#define SDMMC_MPSBEGIN0 (SDMMC_EMMCP_BASE + 0x0200)
46#define SDMMC_MPSEND0 (SDMMC_EMMCP_BASE + 0x0204)
47#define SDMMC_MPSCTRL0 (SDMMC_EMMCP_BASE + 0x020C)
48
49/* SMU control bits */
50#define DWMCI_MPSCTRL_SECURE_READ_BIT BIT(7)
51#define DWMCI_MPSCTRL_SECURE_WRITE_BIT BIT(6)
52#define DWMCI_MPSCTRL_NON_SECURE_READ_BIT BIT(5)
53#define DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT BIT(4)
54#define DWMCI_MPSCTRL_USE_FUSE_KEY BIT(3)
55#define DWMCI_MPSCTRL_ECB_MODE BIT(2)
56#define DWMCI_MPSCTRL_ENCRYPTION BIT(1)
57#define DWMCI_MPSCTRL_VALID BIT(0)
58
Thomas Abrahamc3665002012-09-17 18:16:43 +000059/* Variations in Exynos specific dw-mshc controller */
60enum dw_mci_exynos_type {
61 DW_MCI_TYPE_EXYNOS4210,
62 DW_MCI_TYPE_EXYNOS4412,
63 DW_MCI_TYPE_EXYNOS5250,
Yuvaraj Kumar C D00fd0412013-05-24 15:34:32 +053064 DW_MCI_TYPE_EXYNOS5420,
Yuvaraj Kumar C D6bce4312013-08-31 00:12:35 +090065 DW_MCI_TYPE_EXYNOS5420_SMU,
Thomas Abrahamc3665002012-09-17 18:16:43 +000066};
67
68/* Exynos implementation specific driver private data */
69struct dw_mci_exynos_priv_data {
70 enum dw_mci_exynos_type ctrl_type;
71 u8 ciu_div;
72 u32 sdr_timing;
73 u32 ddr_timing;
74};
75
76static struct dw_mci_exynos_compatible {
77 char *compatible;
78 enum dw_mci_exynos_type ctrl_type;
79} exynos_compat[] = {
80 {
81 .compatible = "samsung,exynos4210-dw-mshc",
82 .ctrl_type = DW_MCI_TYPE_EXYNOS4210,
83 }, {
84 .compatible = "samsung,exynos4412-dw-mshc",
85 .ctrl_type = DW_MCI_TYPE_EXYNOS4412,
86 }, {
87 .compatible = "samsung,exynos5250-dw-mshc",
88 .ctrl_type = DW_MCI_TYPE_EXYNOS5250,
Yuvaraj Kumar C D00fd0412013-05-24 15:34:32 +053089 }, {
90 .compatible = "samsung,exynos5420-dw-mshc",
91 .ctrl_type = DW_MCI_TYPE_EXYNOS5420,
Yuvaraj Kumar C D6bce4312013-08-31 00:12:35 +090092 }, {
93 .compatible = "samsung,exynos5420-dw-mshc-smu",
94 .ctrl_type = DW_MCI_TYPE_EXYNOS5420_SMU,
Thomas Abrahamc3665002012-09-17 18:16:43 +000095 },
96};
97
98static int dw_mci_exynos_priv_init(struct dw_mci *host)
99{
Yuvaraj Kumar C De6c784e2013-08-31 00:11:57 +0900100 struct dw_mci_exynos_priv_data *priv = host->priv;
Thomas Abrahamc3665002012-09-17 18:16:43 +0000101
Yuvaraj Kumar C D6bce4312013-08-31 00:12:35 +0900102 if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU) {
103 mci_writel(host, MPSBEGIN0, 0);
104 mci_writel(host, MPSEND0, DWMCI_BLOCK_NUM);
105 mci_writel(host, MPSCTRL0, DWMCI_MPSCTRL_SECURE_WRITE_BIT |
106 DWMCI_MPSCTRL_NON_SECURE_READ_BIT |
107 DWMCI_MPSCTRL_VALID |
108 DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT);
109 }
110
Thomas Abrahamc3665002012-09-17 18:16:43 +0000111 return 0;
112}
113
114static int dw_mci_exynos_setup_clock(struct dw_mci *host)
115{
116 struct dw_mci_exynos_priv_data *priv = host->priv;
117
Yuvaraj Kumar C D00fd0412013-05-24 15:34:32 +0530118 if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5250 ||
Yuvaraj Kumar C D6bce4312013-08-31 00:12:35 +0900119 priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420 ||
120 priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU)
Thomas Abrahamc3665002012-09-17 18:16:43 +0000121 host->bus_hz /= (priv->ciu_div + 1);
122 else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412)
123 host->bus_hz /= EXYNOS4412_FIXED_CIU_CLK_DIV;
124 else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210)
125 host->bus_hz /= EXYNOS4210_FIXED_CIU_CLK_DIV;
126
127 return 0;
128}
129
Doug Andersone2c63592013-08-31 00:11:21 +0900130#ifdef CONFIG_PM_SLEEP
131static int dw_mci_exynos_suspend(struct device *dev)
132{
133 struct dw_mci *host = dev_get_drvdata(dev);
134
135 return dw_mci_suspend(host);
136}
137
138static int dw_mci_exynos_resume(struct device *dev)
139{
140 struct dw_mci *host = dev_get_drvdata(dev);
141
Yuvaraj Kumar C D6bce4312013-08-31 00:12:35 +0900142 dw_mci_exynos_priv_init(host);
Doug Andersone2c63592013-08-31 00:11:21 +0900143 return dw_mci_resume(host);
144}
145
146/**
147 * dw_mci_exynos_resume_noirq - Exynos-specific resume code
148 *
149 * On exynos5420 there is a silicon errata that will sometimes leave the
150 * WAKEUP_INT bit in the CLKSEL register asserted. This bit is 1 to indicate
151 * that it fired and we can clear it by writing a 1 back. Clear it to prevent
152 * interrupts from going off constantly.
153 *
154 * We run this code on all exynos variants because it doesn't hurt.
155 */
156
157static int dw_mci_exynos_resume_noirq(struct device *dev)
158{
159 struct dw_mci *host = dev_get_drvdata(dev);
160 u32 clksel;
161
162 clksel = mci_readl(host, CLKSEL);
163 if (clksel & SDMMC_CLKSEL_WAKEUP_INT)
164 mci_writel(host, CLKSEL, clksel);
165
166 return 0;
167}
168#else
169#define dw_mci_exynos_suspend NULL
170#define dw_mci_exynos_resume NULL
171#define dw_mci_exynos_resume_noirq NULL
172#endif /* CONFIG_PM_SLEEP */
173
Thomas Abrahamc3665002012-09-17 18:16:43 +0000174static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr)
175{
176 /*
177 * Exynos4412 and Exynos5250 extends the use of CMD register with the
178 * use of bit 29 (which is reserved on standard MSHC controllers) for
179 * optionally bypassing the HOLD register for command and data. The
180 * HOLD register should be bypassed in case there is no phase shift
181 * applied on CMD/DATA that is sent to the card.
182 */
183 if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL)))
184 *cmdr |= SDMMC_CMD_USE_HOLD_REG;
185}
186
187static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios)
188{
189 struct dw_mci_exynos_priv_data *priv = host->priv;
190
191 if (ios->timing == MMC_TIMING_UHS_DDR50)
192 mci_writel(host, CLKSEL, priv->ddr_timing);
193 else
194 mci_writel(host, CLKSEL, priv->sdr_timing);
195}
196
197static int dw_mci_exynos_parse_dt(struct dw_mci *host)
198{
Yuvaraj Kumar C De6c784e2013-08-31 00:11:57 +0900199 struct dw_mci_exynos_priv_data *priv;
Thomas Abrahamc3665002012-09-17 18:16:43 +0000200 struct device_node *np = host->dev->of_node;
201 u32 timing[2];
202 u32 div = 0;
Yuvaraj Kumar C De6c784e2013-08-31 00:11:57 +0900203 int idx;
Thomas Abrahamc3665002012-09-17 18:16:43 +0000204 int ret;
205
Yuvaraj Kumar C De6c784e2013-08-31 00:11:57 +0900206 priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
207 if (!priv) {
208 dev_err(host->dev, "mem alloc failed for private data\n");
209 return -ENOMEM;
210 }
211
212 for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) {
213 if (of_device_is_compatible(np, exynos_compat[idx].compatible))
214 priv->ctrl_type = exynos_compat[idx].ctrl_type;
215 }
216
Thomas Abrahamc3665002012-09-17 18:16:43 +0000217 of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div);
218 priv->ciu_div = div;
219
220 ret = of_property_read_u32_array(np,
221 "samsung,dw-mshc-sdr-timing", timing, 2);
222 if (ret)
223 return ret;
224
Thomas Abrahamc3665002012-09-17 18:16:43 +0000225 ret = of_property_read_u32_array(np,
226 "samsung,dw-mshc-ddr-timing", timing, 2);
227 if (ret)
228 return ret;
229
Yuvaraj Kumar C De6c784e2013-08-31 00:11:57 +0900230 priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
Thomas Abrahamc3665002012-09-17 18:16:43 +0000231 priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div);
Yuvaraj Kumar C De6c784e2013-08-31 00:11:57 +0900232 host->priv = priv;
Thomas Abrahamc3665002012-09-17 18:16:43 +0000233 return 0;
234}
235
Seungwon Jeonc537a1c2013-08-31 00:12:50 +0900236static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host)
237{
238 return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL));
239}
240
241static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample)
242{
243 u32 clksel;
244 clksel = mci_readl(host, CLKSEL);
245 clksel = (clksel & ~0x7) | SDMMC_CLKSEL_CCLK_SAMPLE(sample);
246 mci_writel(host, CLKSEL, clksel);
247}
248
249static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host)
250{
251 u32 clksel;
252 u8 sample;
253
254 clksel = mci_readl(host, CLKSEL);
255 sample = (clksel + 1) & 0x7;
256 clksel = (clksel & ~0x7) | sample;
257 mci_writel(host, CLKSEL, clksel);
258 return sample;
259}
260
261static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates)
262{
263 const u8 iter = 8;
264 u8 __c;
265 s8 i, loc = -1;
266
267 for (i = 0; i < iter; i++) {
268 __c = ror8(candiates, i);
269 if ((__c & 0xc7) == 0xc7) {
270 loc = i;
271 goto out;
272 }
273 }
274
275 for (i = 0; i < iter; i++) {
276 __c = ror8(candiates, i);
277 if ((__c & 0x83) == 0x83) {
278 loc = i;
279 goto out;
280 }
281 }
282
283out:
284 return loc;
285}
286
287static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode,
288 struct dw_mci_tuning_data *tuning_data)
289{
290 struct dw_mci *host = slot->host;
291 struct mmc_host *mmc = slot->mmc;
292 const u8 *blk_pattern = tuning_data->blk_pattern;
293 u8 *blk_test;
294 unsigned int blksz = tuning_data->blksz;
295 u8 start_smpl, smpl, candiates = 0;
296 s8 found = -1;
297 int ret = 0;
298
299 blk_test = kmalloc(blksz, GFP_KERNEL);
300 if (!blk_test)
301 return -ENOMEM;
302
303 start_smpl = dw_mci_exynos_get_clksmpl(host);
304
305 do {
306 struct mmc_request mrq = {NULL};
307 struct mmc_command cmd = {0};
308 struct mmc_command stop = {0};
309 struct mmc_data data = {0};
310 struct scatterlist sg;
311
312 cmd.opcode = opcode;
313 cmd.arg = 0;
314 cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
315
316 stop.opcode = MMC_STOP_TRANSMISSION;
317 stop.arg = 0;
318 stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
319
320 data.blksz = blksz;
321 data.blocks = 1;
322 data.flags = MMC_DATA_READ;
323 data.sg = &sg;
324 data.sg_len = 1;
325
326 sg_init_one(&sg, blk_test, blksz);
327 mrq.cmd = &cmd;
328 mrq.stop = &stop;
329 mrq.data = &data;
330 host->mrq = &mrq;
331
332 mci_writel(host, TMOUT, ~0);
333 smpl = dw_mci_exynos_move_next_clksmpl(host);
334
335 mmc_wait_for_req(mmc, &mrq);
336
337 if (!cmd.error && !data.error) {
338 if (!memcmp(blk_pattern, blk_test, blksz))
339 candiates |= (1 << smpl);
340 } else {
341 dev_dbg(host->dev,
342 "Tuning error: cmd.error:%d, data.error:%d\n",
343 cmd.error, data.error);
344 }
345 } while (start_smpl != smpl);
346
347 found = dw_mci_exynos_get_best_clksmpl(candiates);
348 if (found >= 0)
349 dw_mci_exynos_set_clksmpl(host, found);
350 else
351 ret = -EIO;
352
353 kfree(blk_test);
354 return ret;
355}
356
Dongjin Kim0f6e73d2013-02-23 00:17:45 +0900357/* Common capabilities of Exynos4/Exynos5 SoC */
358static unsigned long exynos_dwmmc_caps[4] = {
Thomas Abrahamc3665002012-09-17 18:16:43 +0000359 MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR |
360 MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23,
361 MMC_CAP_CMD23,
362 MMC_CAP_CMD23,
363 MMC_CAP_CMD23,
364};
365
Dongjin Kim0f6e73d2013-02-23 00:17:45 +0900366static const struct dw_mci_drv_data exynos_drv_data = {
367 .caps = exynos_dwmmc_caps,
Thomas Abrahamc3665002012-09-17 18:16:43 +0000368 .init = dw_mci_exynos_priv_init,
369 .setup_clock = dw_mci_exynos_setup_clock,
370 .prepare_command = dw_mci_exynos_prepare_command,
371 .set_ios = dw_mci_exynos_set_ios,
372 .parse_dt = dw_mci_exynos_parse_dt,
Seungwon Jeonc537a1c2013-08-31 00:12:50 +0900373 .execute_tuning = dw_mci_exynos_execute_tuning,
Thomas Abrahamc3665002012-09-17 18:16:43 +0000374};
375
376static const struct of_device_id dw_mci_exynos_match[] = {
Dongjin Kim0f6e73d2013-02-23 00:17:45 +0900377 { .compatible = "samsung,exynos4412-dw-mshc",
378 .data = &exynos_drv_data, },
Thomas Abrahamc3665002012-09-17 18:16:43 +0000379 { .compatible = "samsung,exynos5250-dw-mshc",
Dongjin Kim0f6e73d2013-02-23 00:17:45 +0900380 .data = &exynos_drv_data, },
Yuvaraj Kumar C D00fd0412013-05-24 15:34:32 +0530381 { .compatible = "samsung,exynos5420-dw-mshc",
382 .data = &exynos_drv_data, },
Yuvaraj Kumar C D6bce4312013-08-31 00:12:35 +0900383 { .compatible = "samsung,exynos5420-dw-mshc-smu",
384 .data = &exynos_drv_data, },
Thomas Abrahamc3665002012-09-17 18:16:43 +0000385 {},
386};
Arnd Bergmann517cb9f2012-11-06 22:55:30 +0100387MODULE_DEVICE_TABLE(of, dw_mci_exynos_match);
Thomas Abrahamc3665002012-09-17 18:16:43 +0000388
Sachin Kamat9665f7f2013-02-18 14:23:08 +0530389static int dw_mci_exynos_probe(struct platform_device *pdev)
Thomas Abrahamc3665002012-09-17 18:16:43 +0000390{
Arnd Bergmann8e2b36e2012-11-06 22:55:31 +0100391 const struct dw_mci_drv_data *drv_data;
Thomas Abrahamc3665002012-09-17 18:16:43 +0000392 const struct of_device_id *match;
393
394 match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node);
395 drv_data = match->data;
396 return dw_mci_pltfm_register(pdev, drv_data);
397}
398
Doug Andersone2c63592013-08-31 00:11:21 +0900399const struct dev_pm_ops dw_mci_exynos_pmops = {
400 SET_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend, dw_mci_exynos_resume)
401 .resume_noirq = dw_mci_exynos_resume_noirq,
402 .thaw_noirq = dw_mci_exynos_resume_noirq,
403 .restore_noirq = dw_mci_exynos_resume_noirq,
404};
405
Thomas Abrahamc3665002012-09-17 18:16:43 +0000406static struct platform_driver dw_mci_exynos_pltfm_driver = {
407 .probe = dw_mci_exynos_probe,
408 .remove = __exit_p(dw_mci_pltfm_remove),
409 .driver = {
410 .name = "dwmmc_exynos",
Sachin Kamat20183d52013-02-18 14:23:09 +0530411 .of_match_table = dw_mci_exynos_match,
Doug Andersone2c63592013-08-31 00:11:21 +0900412 .pm = &dw_mci_exynos_pmops,
Thomas Abrahamc3665002012-09-17 18:16:43 +0000413 },
414};
415
416module_platform_driver(dw_mci_exynos_pltfm_driver);
417
418MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension");
419MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com");
420MODULE_LICENSE("GPL v2");
421MODULE_ALIAS("platform:dwmmc-exynos");