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