1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver |
4 | * |
5 | * Copyright (C) 2012, Samsung Electronics Co., Ltd. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/mmc/host.h> |
12 | #include <linux/mmc/mmc.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_gpio.h> |
15 | #include <linux/pm_runtime.h> |
16 | #include <linux/slab.h> |
17 | |
18 | #include "dw_mmc.h" |
19 | #include "dw_mmc-pltfm.h" |
20 | #include "dw_mmc-exynos.h" |
21 | |
22 | /* Variations in Exynos specific dw-mshc controller */ |
23 | enum dw_mci_exynos_type { |
24 | DW_MCI_TYPE_EXYNOS4210, |
25 | DW_MCI_TYPE_EXYNOS4412, |
26 | DW_MCI_TYPE_EXYNOS5250, |
27 | DW_MCI_TYPE_EXYNOS5420, |
28 | DW_MCI_TYPE_EXYNOS5420_SMU, |
29 | DW_MCI_TYPE_EXYNOS7, |
30 | DW_MCI_TYPE_EXYNOS7_SMU, |
31 | DW_MCI_TYPE_ARTPEC8, |
32 | }; |
33 | |
34 | /* Exynos implementation specific driver private data */ |
35 | struct dw_mci_exynos_priv_data { |
36 | enum dw_mci_exynos_type ctrl_type; |
37 | u8 ciu_div; |
38 | u32 sdr_timing; |
39 | u32 ddr_timing; |
40 | u32 hs400_timing; |
41 | u32 tuned_sample; |
42 | u32 cur_speed; |
43 | u32 dqs_delay; |
44 | u32 saved_dqs_en; |
45 | u32 saved_strobe_ctrl; |
46 | }; |
47 | |
48 | static struct dw_mci_exynos_compatible { |
49 | char *compatible; |
50 | enum dw_mci_exynos_type ctrl_type; |
51 | } exynos_compat[] = { |
52 | { |
53 | .compatible = "samsung,exynos4210-dw-mshc" , |
54 | .ctrl_type = DW_MCI_TYPE_EXYNOS4210, |
55 | }, { |
56 | .compatible = "samsung,exynos4412-dw-mshc" , |
57 | .ctrl_type = DW_MCI_TYPE_EXYNOS4412, |
58 | }, { |
59 | .compatible = "samsung,exynos5250-dw-mshc" , |
60 | .ctrl_type = DW_MCI_TYPE_EXYNOS5250, |
61 | }, { |
62 | .compatible = "samsung,exynos5420-dw-mshc" , |
63 | .ctrl_type = DW_MCI_TYPE_EXYNOS5420, |
64 | }, { |
65 | .compatible = "samsung,exynos5420-dw-mshc-smu" , |
66 | .ctrl_type = DW_MCI_TYPE_EXYNOS5420_SMU, |
67 | }, { |
68 | .compatible = "samsung,exynos7-dw-mshc" , |
69 | .ctrl_type = DW_MCI_TYPE_EXYNOS7, |
70 | }, { |
71 | .compatible = "samsung,exynos7-dw-mshc-smu" , |
72 | .ctrl_type = DW_MCI_TYPE_EXYNOS7_SMU, |
73 | }, { |
74 | .compatible = "axis,artpec8-dw-mshc" , |
75 | .ctrl_type = DW_MCI_TYPE_ARTPEC8, |
76 | }, |
77 | }; |
78 | |
79 | static 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 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
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 | |
95 | static void dw_mci_exynos_config_smu(struct dw_mci *host) |
96 | { |
97 | struct dw_mci_exynos_priv_data *priv = host->priv; |
98 | |
99 | /* |
100 | * If Exynos is provided the Security management, |
101 | * set for non-ecryption mode at this time. |
102 | */ |
103 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU || |
104 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU) { |
105 | mci_writel(host, MPSBEGIN0, 0); |
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); |
111 | } |
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); |
119 | |
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 | |
130 | if (priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) { |
131 | /* Quirk needed for the ARTPEC-8 SoC */ |
132 | host->quirks |= DW_MMC_QUIRK_EXTENDED_TMOUT; |
133 | } |
134 | |
135 | host->bus_hz /= (priv->ciu_div + 1); |
136 | |
137 | return 0; |
138 | } |
139 | |
140 | static void dw_mci_exynos_set_clksel_timing(struct dw_mci *host, u32 timing) |
141 | { |
142 | struct dw_mci_exynos_priv_data *priv = host->priv; |
143 | u32 clksel; |
144 | |
145 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
146 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
147 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
148 | clksel = mci_readl(host, CLKSEL64); |
149 | else |
150 | clksel = mci_readl(host, CLKSEL); |
151 | |
152 | clksel = (clksel & ~SDMMC_CLKSEL_TIMING_MASK) | timing; |
153 | |
154 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
155 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
156 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
157 | mci_writel(host, CLKSEL64, clksel); |
158 | else |
159 | mci_writel(host, CLKSEL, clksel); |
160 | |
161 | /* |
162 | * Exynos4412 and Exynos5250 extends the use of CMD register with the |
163 | * use of bit 29 (which is reserved on standard MSHC controllers) for |
164 | * optionally bypassing the HOLD register for command and data. The |
165 | * HOLD register should be bypassed in case there is no phase shift |
166 | * applied on CMD/DATA that is sent to the card. |
167 | */ |
168 | if (!SDMMC_CLKSEL_GET_DRV_WD3(clksel) && host->slot) |
169 | set_bit(DW_MMC_CARD_NO_USE_HOLD, addr: &host->slot->flags); |
170 | } |
171 | |
172 | #ifdef CONFIG_PM |
173 | static int dw_mci_exynos_runtime_resume(struct device *dev) |
174 | { |
175 | struct dw_mci *host = dev_get_drvdata(dev); |
176 | int ret; |
177 | |
178 | ret = dw_mci_runtime_resume(device: dev); |
179 | if (ret) |
180 | return ret; |
181 | |
182 | dw_mci_exynos_config_smu(host); |
183 | |
184 | return ret; |
185 | } |
186 | #endif /* CONFIG_PM */ |
187 | |
188 | #ifdef CONFIG_PM_SLEEP |
189 | /** |
190 | * dw_mci_exynos_suspend_noirq - Exynos-specific suspend code |
191 | * @dev: Device to suspend (this device) |
192 | * |
193 | * This ensures that device will be in runtime active state in |
194 | * dw_mci_exynos_resume_noirq after calling pm_runtime_force_resume() |
195 | */ |
196 | static int dw_mci_exynos_suspend_noirq(struct device *dev) |
197 | { |
198 | pm_runtime_get_noresume(dev); |
199 | return pm_runtime_force_suspend(dev); |
200 | } |
201 | |
202 | /** |
203 | * dw_mci_exynos_resume_noirq - Exynos-specific resume code |
204 | * @dev: Device to resume (this device) |
205 | * |
206 | * On exynos5420 there is a silicon errata that will sometimes leave the |
207 | * WAKEUP_INT bit in the CLKSEL register asserted. This bit is 1 to indicate |
208 | * that it fired and we can clear it by writing a 1 back. Clear it to prevent |
209 | * interrupts from going off constantly. |
210 | * |
211 | * We run this code on all exynos variants because it doesn't hurt. |
212 | */ |
213 | static int dw_mci_exynos_resume_noirq(struct device *dev) |
214 | { |
215 | struct dw_mci *host = dev_get_drvdata(dev); |
216 | struct dw_mci_exynos_priv_data *priv = host->priv; |
217 | u32 clksel; |
218 | int ret; |
219 | |
220 | ret = pm_runtime_force_resume(dev); |
221 | if (ret) |
222 | return ret; |
223 | |
224 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
225 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
226 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
227 | clksel = mci_readl(host, CLKSEL64); |
228 | else |
229 | clksel = mci_readl(host, CLKSEL); |
230 | |
231 | if (clksel & SDMMC_CLKSEL_WAKEUP_INT) { |
232 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
233 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
234 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
235 | mci_writel(host, CLKSEL64, clksel); |
236 | else |
237 | mci_writel(host, CLKSEL, clksel); |
238 | } |
239 | |
240 | pm_runtime_put(dev); |
241 | |
242 | return 0; |
243 | } |
244 | #endif /* CONFIG_PM_SLEEP */ |
245 | |
246 | static void dw_mci_exynos_config_hs400(struct dw_mci *host, u32 timing) |
247 | { |
248 | struct dw_mci_exynos_priv_data *priv = host->priv; |
249 | u32 dqs, strobe; |
250 | |
251 | /* |
252 | * Not supported to configure register |
253 | * related to HS400 |
254 | */ |
255 | if ((priv->ctrl_type < DW_MCI_TYPE_EXYNOS5420) || |
256 | (priv->ctrl_type == DW_MCI_TYPE_ARTPEC8)) { |
257 | if (timing == MMC_TIMING_MMC_HS400) |
258 | dev_warn(host->dev, |
259 | "cannot configure HS400, unsupported chipset\n" ); |
260 | return; |
261 | } |
262 | |
263 | dqs = priv->saved_dqs_en; |
264 | strobe = priv->saved_strobe_ctrl; |
265 | |
266 | if (timing == MMC_TIMING_MMC_HS400) { |
267 | dqs |= DATA_STROBE_EN; |
268 | strobe = DQS_CTRL_RD_DELAY(strobe, priv->dqs_delay); |
269 | } else if (timing == MMC_TIMING_UHS_SDR104) { |
270 | dqs &= 0xffffff00; |
271 | } else { |
272 | dqs &= ~DATA_STROBE_EN; |
273 | } |
274 | |
275 | mci_writel(host, HS400_DQS_EN, dqs); |
276 | mci_writel(host, HS400_DLINE_CTRL, strobe); |
277 | } |
278 | |
279 | static void dw_mci_exynos_adjust_clock(struct dw_mci *host, unsigned int wanted) |
280 | { |
281 | struct dw_mci_exynos_priv_data *priv = host->priv; |
282 | unsigned long actual; |
283 | u8 div; |
284 | int ret; |
285 | /* |
286 | * Don't care if wanted clock is zero or |
287 | * ciu clock is unavailable |
288 | */ |
289 | if (!wanted || IS_ERR(ptr: host->ciu_clk)) |
290 | return; |
291 | |
292 | /* Guaranteed minimum frequency for cclkin */ |
293 | if (wanted < EXYNOS_CCLKIN_MIN) |
294 | wanted = EXYNOS_CCLKIN_MIN; |
295 | |
296 | if (wanted == priv->cur_speed) |
297 | return; |
298 | |
299 | div = dw_mci_exynos_get_ciu_div(host); |
300 | ret = clk_set_rate(clk: host->ciu_clk, rate: wanted * div); |
301 | if (ret) |
302 | dev_warn(host->dev, |
303 | "failed to set clk-rate %u error: %d\n" , |
304 | wanted * div, ret); |
305 | actual = clk_get_rate(clk: host->ciu_clk); |
306 | host->bus_hz = actual / div; |
307 | priv->cur_speed = wanted; |
308 | host->current_speed = 0; |
309 | } |
310 | |
311 | static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios) |
312 | { |
313 | struct dw_mci_exynos_priv_data *priv = host->priv; |
314 | unsigned int wanted = ios->clock; |
315 | u32 timing = ios->timing, clksel; |
316 | |
317 | switch (timing) { |
318 | case MMC_TIMING_MMC_HS400: |
319 | /* Update tuned sample timing */ |
320 | clksel = SDMMC_CLKSEL_UP_SAMPLE( |
321 | priv->hs400_timing, priv->tuned_sample); |
322 | wanted <<= 1; |
323 | break; |
324 | case MMC_TIMING_MMC_DDR52: |
325 | clksel = priv->ddr_timing; |
326 | /* Should be double rate for DDR mode */ |
327 | if (ios->bus_width == MMC_BUS_WIDTH_8) |
328 | wanted <<= 1; |
329 | break; |
330 | case MMC_TIMING_UHS_SDR104: |
331 | case MMC_TIMING_UHS_SDR50: |
332 | clksel = (priv->sdr_timing & 0xfff8ffff) | |
333 | (priv->ciu_div << 16); |
334 | break; |
335 | case MMC_TIMING_UHS_DDR50: |
336 | clksel = (priv->ddr_timing & 0xfff8ffff) | |
337 | (priv->ciu_div << 16); |
338 | break; |
339 | default: |
340 | clksel = priv->sdr_timing; |
341 | } |
342 | |
343 | /* Set clock timing for the requested speed mode*/ |
344 | dw_mci_exynos_set_clksel_timing(host, timing: clksel); |
345 | |
346 | /* Configure setting for HS400 */ |
347 | dw_mci_exynos_config_hs400(host, timing); |
348 | |
349 | /* Configure clock rate */ |
350 | dw_mci_exynos_adjust_clock(host, wanted); |
351 | } |
352 | |
353 | static int dw_mci_exynos_parse_dt(struct dw_mci *host) |
354 | { |
355 | struct dw_mci_exynos_priv_data *priv; |
356 | struct device_node *np = host->dev->of_node; |
357 | u32 timing[2]; |
358 | u32 div = 0; |
359 | int idx; |
360 | int ret; |
361 | |
362 | priv = devm_kzalloc(dev: host->dev, size: sizeof(*priv), GFP_KERNEL); |
363 | if (!priv) |
364 | return -ENOMEM; |
365 | |
366 | for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) { |
367 | if (of_device_is_compatible(device: np, exynos_compat[idx].compatible)) |
368 | priv->ctrl_type = exynos_compat[idx].ctrl_type; |
369 | } |
370 | |
371 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412) |
372 | priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1; |
373 | else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210) |
374 | priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1; |
375 | else { |
376 | of_property_read_u32(np, propname: "samsung,dw-mshc-ciu-div" , out_value: &div); |
377 | priv->ciu_div = div; |
378 | } |
379 | |
380 | ret = of_property_read_u32_array(np, |
381 | propname: "samsung,dw-mshc-sdr-timing" , out_values: timing, sz: 2); |
382 | if (ret) |
383 | return ret; |
384 | |
385 | priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); |
386 | |
387 | ret = of_property_read_u32_array(np, |
388 | propname: "samsung,dw-mshc-ddr-timing" , out_values: timing, sz: 2); |
389 | if (ret) |
390 | return ret; |
391 | |
392 | priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); |
393 | |
394 | ret = of_property_read_u32_array(np, |
395 | propname: "samsung,dw-mshc-hs400-timing" , out_values: timing, sz: 2); |
396 | if (!ret && of_property_read_u32(np, |
397 | propname: "samsung,read-strobe-delay" , out_value: &priv->dqs_delay)) |
398 | dev_dbg(host->dev, |
399 | "read-strobe-delay is not found, assuming usage of default value\n" ); |
400 | |
401 | priv->hs400_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], |
402 | HS400_FIXED_CIU_CLK_DIV); |
403 | host->priv = priv; |
404 | return 0; |
405 | } |
406 | |
407 | static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host) |
408 | { |
409 | struct dw_mci_exynos_priv_data *priv = host->priv; |
410 | |
411 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
412 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
413 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
414 | return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL64)); |
415 | else |
416 | return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL)); |
417 | } |
418 | |
419 | static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample) |
420 | { |
421 | u32 clksel; |
422 | struct dw_mci_exynos_priv_data *priv = host->priv; |
423 | |
424 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
425 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
426 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
427 | clksel = mci_readl(host, CLKSEL64); |
428 | else |
429 | clksel = mci_readl(host, CLKSEL); |
430 | clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample); |
431 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
432 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
433 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
434 | mci_writel(host, CLKSEL64, clksel); |
435 | else |
436 | mci_writel(host, CLKSEL, clksel); |
437 | } |
438 | |
439 | static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host) |
440 | { |
441 | struct dw_mci_exynos_priv_data *priv = host->priv; |
442 | u32 clksel; |
443 | u8 sample; |
444 | |
445 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
446 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
447 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
448 | clksel = mci_readl(host, CLKSEL64); |
449 | else |
450 | clksel = mci_readl(host, CLKSEL); |
451 | |
452 | sample = (clksel + 1) & 0x7; |
453 | clksel = SDMMC_CLKSEL_UP_SAMPLE(clksel, sample); |
454 | |
455 | if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS7 || |
456 | priv->ctrl_type == DW_MCI_TYPE_EXYNOS7_SMU || |
457 | priv->ctrl_type == DW_MCI_TYPE_ARTPEC8) |
458 | mci_writel(host, CLKSEL64, clksel); |
459 | else |
460 | mci_writel(host, CLKSEL, clksel); |
461 | |
462 | return sample; |
463 | } |
464 | |
465 | static s8 dw_mci_exynos_get_best_clksmpl(u8 candidates) |
466 | { |
467 | const u8 iter = 8; |
468 | u8 __c; |
469 | s8 i, loc = -1; |
470 | |
471 | for (i = 0; i < iter; i++) { |
472 | __c = ror8(word: candidates, shift: i); |
473 | if ((__c & 0xc7) == 0xc7) { |
474 | loc = i; |
475 | goto out; |
476 | } |
477 | } |
478 | |
479 | for (i = 0; i < iter; i++) { |
480 | __c = ror8(word: candidates, shift: i); |
481 | if ((__c & 0x83) == 0x83) { |
482 | loc = i; |
483 | goto out; |
484 | } |
485 | } |
486 | |
487 | /* |
488 | * If there is no cadiates value, then it needs to return -EIO. |
489 | * If there are candidates values and don't find bset clk sample value, |
490 | * then use a first candidates clock sample value. |
491 | */ |
492 | for (i = 0; i < iter; i++) { |
493 | __c = ror8(word: candidates, shift: i); |
494 | if ((__c & 0x1) == 0x1) { |
495 | loc = i; |
496 | goto out; |
497 | } |
498 | } |
499 | out: |
500 | return loc; |
501 | } |
502 | |
503 | static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode) |
504 | { |
505 | struct dw_mci *host = slot->host; |
506 | struct dw_mci_exynos_priv_data *priv = host->priv; |
507 | struct mmc_host *mmc = slot->mmc; |
508 | u8 start_smpl, smpl, candidates = 0; |
509 | s8 found; |
510 | int ret = 0; |
511 | |
512 | start_smpl = dw_mci_exynos_get_clksmpl(host); |
513 | |
514 | do { |
515 | mci_writel(host, TMOUT, ~0); |
516 | smpl = dw_mci_exynos_move_next_clksmpl(host); |
517 | |
518 | if (!mmc_send_tuning(host: mmc, opcode, NULL)) |
519 | candidates |= (1 << smpl); |
520 | |
521 | } while (start_smpl != smpl); |
522 | |
523 | found = dw_mci_exynos_get_best_clksmpl(candidates); |
524 | if (found >= 0) { |
525 | dw_mci_exynos_set_clksmpl(host, sample: found); |
526 | priv->tuned_sample = found; |
527 | } else { |
528 | ret = -EIO; |
529 | dev_warn(&mmc->class_dev, |
530 | "There is no candidates value about clksmpl!\n" ); |
531 | } |
532 | |
533 | return ret; |
534 | } |
535 | |
536 | static int dw_mci_exynos_prepare_hs400_tuning(struct dw_mci *host, |
537 | struct mmc_ios *ios) |
538 | { |
539 | struct dw_mci_exynos_priv_data *priv = host->priv; |
540 | |
541 | dw_mci_exynos_set_clksel_timing(host, timing: priv->hs400_timing); |
542 | dw_mci_exynos_adjust_clock(host, wanted: (ios->clock) << 1); |
543 | |
544 | return 0; |
545 | } |
546 | |
547 | static void dw_mci_exynos_set_data_timeout(struct dw_mci *host, |
548 | unsigned int timeout_ns) |
549 | { |
550 | u32 clk_div, tmout; |
551 | u64 tmp; |
552 | unsigned int tmp2; |
553 | |
554 | clk_div = (mci_readl(host, CLKDIV) & 0xFF) * 2; |
555 | if (clk_div == 0) |
556 | clk_div = 1; |
557 | |
558 | tmp = DIV_ROUND_UP_ULL((u64)timeout_ns * host->bus_hz, NSEC_PER_SEC); |
559 | tmp = DIV_ROUND_UP_ULL(tmp, clk_div); |
560 | |
561 | /* TMOUT[7:0] (RESPONSE_TIMEOUT) */ |
562 | tmout = 0xFF; /* Set maximum */ |
563 | |
564 | /* |
565 | * Extended HW timer (max = 0x6FFFFF2): |
566 | * ((TMOUT[10:8] - 1) * 0xFFFFFF + TMOUT[31:11] * 8) |
567 | */ |
568 | if (!tmp || tmp > 0x6FFFFF2) |
569 | tmout |= (0xFFFFFF << 8); |
570 | else { |
571 | /* TMOUT[10:8] */ |
572 | tmp2 = (((unsigned int)tmp / 0xFFFFFF) + 1) & 0x7; |
573 | tmout |= tmp2 << 8; |
574 | |
575 | /* TMOUT[31:11] */ |
576 | tmp = tmp - ((tmp2 - 1) * 0xFFFFFF); |
577 | tmout |= (tmp & 0xFFFFF8) << 8; |
578 | } |
579 | |
580 | mci_writel(host, TMOUT, tmout); |
581 | dev_dbg(host->dev, "timeout_ns: %u => TMOUT[31:8]: %#08x" , |
582 | timeout_ns, tmout >> 8); |
583 | } |
584 | |
585 | static u32 dw_mci_exynos_get_drto_clks(struct dw_mci *host) |
586 | { |
587 | u32 drto_clks; |
588 | |
589 | drto_clks = mci_readl(host, TMOUT) >> 8; |
590 | |
591 | return (((drto_clks & 0x7) - 1) * 0xFFFFFF) + ((drto_clks & 0xFFFFF8)); |
592 | } |
593 | |
594 | /* Common capabilities of Exynos4/Exynos5 SoC */ |
595 | static unsigned long exynos_dwmmc_caps[4] = { |
596 | MMC_CAP_1_8V_DDR | MMC_CAP_8_BIT_DATA, |
597 | 0, |
598 | 0, |
599 | 0, |
600 | }; |
601 | |
602 | static const struct dw_mci_drv_data exynos_drv_data = { |
603 | .caps = exynos_dwmmc_caps, |
604 | .num_caps = ARRAY_SIZE(exynos_dwmmc_caps), |
605 | .common_caps = MMC_CAP_CMD23, |
606 | .init = dw_mci_exynos_priv_init, |
607 | .set_ios = dw_mci_exynos_set_ios, |
608 | .parse_dt = dw_mci_exynos_parse_dt, |
609 | .execute_tuning = dw_mci_exynos_execute_tuning, |
610 | .prepare_hs400_tuning = dw_mci_exynos_prepare_hs400_tuning, |
611 | }; |
612 | |
613 | static const struct dw_mci_drv_data artpec_drv_data = { |
614 | .common_caps = MMC_CAP_CMD23, |
615 | .init = dw_mci_exynos_priv_init, |
616 | .set_ios = dw_mci_exynos_set_ios, |
617 | .parse_dt = dw_mci_exynos_parse_dt, |
618 | .execute_tuning = dw_mci_exynos_execute_tuning, |
619 | .set_data_timeout = dw_mci_exynos_set_data_timeout, |
620 | .get_drto_clks = dw_mci_exynos_get_drto_clks, |
621 | }; |
622 | |
623 | static const struct of_device_id dw_mci_exynos_match[] = { |
624 | { .compatible = "samsung,exynos4412-dw-mshc" , |
625 | .data = &exynos_drv_data, }, |
626 | { .compatible = "samsung,exynos5250-dw-mshc" , |
627 | .data = &exynos_drv_data, }, |
628 | { .compatible = "samsung,exynos5420-dw-mshc" , |
629 | .data = &exynos_drv_data, }, |
630 | { .compatible = "samsung,exynos5420-dw-mshc-smu" , |
631 | .data = &exynos_drv_data, }, |
632 | { .compatible = "samsung,exynos7-dw-mshc" , |
633 | .data = &exynos_drv_data, }, |
634 | { .compatible = "samsung,exynos7-dw-mshc-smu" , |
635 | .data = &exynos_drv_data, }, |
636 | { .compatible = "axis,artpec8-dw-mshc" , |
637 | .data = &artpec_drv_data, }, |
638 | {}, |
639 | }; |
640 | MODULE_DEVICE_TABLE(of, dw_mci_exynos_match); |
641 | |
642 | static int dw_mci_exynos_probe(struct platform_device *pdev) |
643 | { |
644 | const struct dw_mci_drv_data *drv_data; |
645 | const struct of_device_id *match; |
646 | int ret; |
647 | |
648 | match = of_match_node(matches: dw_mci_exynos_match, node: pdev->dev.of_node); |
649 | drv_data = match->data; |
650 | |
651 | pm_runtime_get_noresume(dev: &pdev->dev); |
652 | pm_runtime_set_active(dev: &pdev->dev); |
653 | pm_runtime_enable(dev: &pdev->dev); |
654 | |
655 | ret = dw_mci_pltfm_register(pdev, drv_data); |
656 | if (ret) { |
657 | pm_runtime_disable(dev: &pdev->dev); |
658 | pm_runtime_set_suspended(dev: &pdev->dev); |
659 | pm_runtime_put_noidle(dev: &pdev->dev); |
660 | |
661 | return ret; |
662 | } |
663 | |
664 | return 0; |
665 | } |
666 | |
667 | static void dw_mci_exynos_remove(struct platform_device *pdev) |
668 | { |
669 | pm_runtime_disable(dev: &pdev->dev); |
670 | pm_runtime_set_suspended(dev: &pdev->dev); |
671 | pm_runtime_put_noidle(dev: &pdev->dev); |
672 | |
673 | dw_mci_pltfm_remove(pdev); |
674 | } |
675 | |
676 | static const struct dev_pm_ops dw_mci_exynos_pmops = { |
677 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend_noirq, |
678 | dw_mci_exynos_resume_noirq) |
679 | SET_RUNTIME_PM_OPS(dw_mci_runtime_suspend, |
680 | dw_mci_exynos_runtime_resume, |
681 | NULL) |
682 | }; |
683 | |
684 | static struct platform_driver dw_mci_exynos_pltfm_driver = { |
685 | .probe = dw_mci_exynos_probe, |
686 | .remove_new = dw_mci_exynos_remove, |
687 | .driver = { |
688 | .name = "dwmmc_exynos" , |
689 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
690 | .of_match_table = dw_mci_exynos_match, |
691 | .pm = &dw_mci_exynos_pmops, |
692 | }, |
693 | }; |
694 | |
695 | module_platform_driver(dw_mci_exynos_pltfm_driver); |
696 | |
697 | MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension" ); |
698 | MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com" ); |
699 | MODULE_LICENSE("GPL v2" ); |
700 | MODULE_ALIAS("platform:dwmmc_exynos" ); |
701 | |