1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for Synopsys DesignWare Cores Mobile Storage Host Controller |
4 | * |
5 | * Copyright (C) 2018 Synaptics Incorporated |
6 | * |
7 | * Author: Jisheng Zhang <jszhang@kernel.org> |
8 | */ |
9 | |
10 | #include <linux/acpi.h> |
11 | #include <linux/clk.h> |
12 | #include <linux/dma-mapping.h> |
13 | #include <linux/iopoll.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/pm_runtime.h> |
19 | #include <linux/reset.h> |
20 | #include <linux/sizes.h> |
21 | |
22 | #include "sdhci-pltfm.h" |
23 | |
24 | #define SDHCI_DWCMSHC_ARG2_STUFF GENMASK(31, 16) |
25 | |
26 | /* DWCMSHC specific Mode Select value */ |
27 | #define DWCMSHC_CTRL_HS400 0x7 |
28 | |
29 | /* DWC IP vendor area 1 pointer */ |
30 | #define DWCMSHC_P_VENDOR_AREA1 0xe8 |
31 | #define DWCMSHC_AREA1_MASK GENMASK(11, 0) |
32 | /* Offset inside the vendor area 1 */ |
33 | #define DWCMSHC_HOST_CTRL3 0x8 |
34 | #define DWCMSHC_EMMC_CONTROL 0x2c |
35 | #define DWCMSHC_CARD_IS_EMMC BIT(0) |
36 | #define DWCMSHC_ENHANCED_STROBE BIT(8) |
37 | #define DWCMSHC_EMMC_ATCTRL 0x40 |
38 | |
39 | /* Rockchip specific Registers */ |
40 | #define DWCMSHC_EMMC_DLL_CTRL 0x800 |
41 | #define DWCMSHC_EMMC_DLL_RXCLK 0x804 |
42 | #define DWCMSHC_EMMC_DLL_TXCLK 0x808 |
43 | #define DWCMSHC_EMMC_DLL_STRBIN 0x80c |
44 | #define DECMSHC_EMMC_DLL_CMDOUT 0x810 |
45 | #define DWCMSHC_EMMC_DLL_STATUS0 0x840 |
46 | #define DWCMSHC_EMMC_DLL_START BIT(0) |
47 | #define DWCMSHC_EMMC_DLL_LOCKED BIT(8) |
48 | #define DWCMSHC_EMMC_DLL_TIMEOUT BIT(9) |
49 | #define DWCMSHC_EMMC_DLL_RXCLK_SRCSEL 29 |
50 | #define DWCMSHC_EMMC_DLL_START_POINT 16 |
51 | #define DWCMSHC_EMMC_DLL_INC 8 |
52 | #define DWCMSHC_EMMC_DLL_BYPASS BIT(24) |
53 | #define DWCMSHC_EMMC_DLL_DLYENA BIT(27) |
54 | #define DLL_TXCLK_TAPNUM_DEFAULT 0x10 |
55 | #define DLL_TXCLK_TAPNUM_90_DEGREES 0xA |
56 | #define DLL_TXCLK_TAPNUM_FROM_SW BIT(24) |
57 | #define DLL_STRBIN_TAPNUM_DEFAULT 0x8 |
58 | #define DLL_STRBIN_TAPNUM_FROM_SW BIT(24) |
59 | #define DLL_STRBIN_DELAY_NUM_SEL BIT(26) |
60 | #define DLL_STRBIN_DELAY_NUM_OFFSET 16 |
61 | #define DLL_STRBIN_DELAY_NUM_DEFAULT 0x16 |
62 | #define DLL_RXCLK_NO_INVERTER 1 |
63 | #define DLL_RXCLK_INVERTER 0 |
64 | #define DLL_CMDOUT_TAPNUM_90_DEGREES 0x8 |
65 | #define DLL_RXCLK_ORI_GATE BIT(31) |
66 | #define DLL_CMDOUT_TAPNUM_FROM_SW BIT(24) |
67 | #define DLL_CMDOUT_SRC_CLK_NEG BIT(28) |
68 | #define DLL_CMDOUT_EN_SRC_CLK_NEG BIT(29) |
69 | |
70 | #define DLL_LOCK_WO_TMOUT(x) \ |
71 | ((((x) & DWCMSHC_EMMC_DLL_LOCKED) == DWCMSHC_EMMC_DLL_LOCKED) && \ |
72 | (((x) & DWCMSHC_EMMC_DLL_TIMEOUT) == 0)) |
73 | #define RK35xx_MAX_CLKS 3 |
74 | |
75 | #define BOUNDARY_OK(addr, len) \ |
76 | ((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1))) |
77 | |
78 | enum dwcmshc_rk_type { |
79 | DWCMSHC_RK3568, |
80 | DWCMSHC_RK3588, |
81 | }; |
82 | |
83 | struct rk35xx_priv { |
84 | /* Rockchip specified optional clocks */ |
85 | struct clk_bulk_data rockchip_clks[RK35xx_MAX_CLKS]; |
86 | struct reset_control *reset; |
87 | enum dwcmshc_rk_type devtype; |
88 | u8 txclk_tapnum; |
89 | }; |
90 | |
91 | struct dwcmshc_priv { |
92 | struct clk *bus_clk; |
93 | int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA reg */ |
94 | void *priv; /* pointer to SoC private stuff */ |
95 | }; |
96 | |
97 | /* |
98 | * If DMA addr spans 128MB boundary, we split the DMA transfer into two |
99 | * so that each DMA transfer doesn't exceed the boundary. |
100 | */ |
101 | static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc, |
102 | dma_addr_t addr, int len, unsigned int cmd) |
103 | { |
104 | int tmplen, offset; |
105 | |
106 | if (likely(!len || BOUNDARY_OK(addr, len))) { |
107 | sdhci_adma_write_desc(host, desc, addr, len, cmd); |
108 | return; |
109 | } |
110 | |
111 | offset = addr & (SZ_128M - 1); |
112 | tmplen = SZ_128M - offset; |
113 | sdhci_adma_write_desc(host, desc, addr, len: tmplen, cmd); |
114 | |
115 | addr += tmplen; |
116 | len -= tmplen; |
117 | sdhci_adma_write_desc(host, desc, addr, len, cmd); |
118 | } |
119 | |
120 | static unsigned int dwcmshc_get_max_clock(struct sdhci_host *host) |
121 | { |
122 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
123 | |
124 | if (pltfm_host->clk) |
125 | return sdhci_pltfm_clk_get_max_clock(host); |
126 | else |
127 | return pltfm_host->clock; |
128 | } |
129 | |
130 | static unsigned int rk35xx_get_max_clock(struct sdhci_host *host) |
131 | { |
132 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
133 | |
134 | return clk_round_rate(clk: pltfm_host->clk, ULONG_MAX); |
135 | } |
136 | |
137 | static void dwcmshc_check_auto_cmd23(struct mmc_host *mmc, |
138 | struct mmc_request *mrq) |
139 | { |
140 | struct sdhci_host *host = mmc_priv(host: mmc); |
141 | |
142 | /* |
143 | * No matter V4 is enabled or not, ARGUMENT2 register is 32-bit |
144 | * block count register which doesn't support stuff bits of |
145 | * CMD23 argument on dwcmsch host controller. |
146 | */ |
147 | if (mrq->sbc && (mrq->sbc->arg & SDHCI_DWCMSHC_ARG2_STUFF)) |
148 | host->flags &= ~SDHCI_AUTO_CMD23; |
149 | else |
150 | host->flags |= SDHCI_AUTO_CMD23; |
151 | } |
152 | |
153 | static void dwcmshc_request(struct mmc_host *mmc, struct mmc_request *mrq) |
154 | { |
155 | dwcmshc_check_auto_cmd23(mmc, mrq); |
156 | |
157 | sdhci_request(mmc, mrq); |
158 | } |
159 | |
160 | static void dwcmshc_set_uhs_signaling(struct sdhci_host *host, |
161 | unsigned int timing) |
162 | { |
163 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
164 | struct dwcmshc_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
165 | u16 ctrl, ctrl_2; |
166 | |
167 | ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); |
168 | /* Select Bus Speed Mode for host */ |
169 | ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; |
170 | if ((timing == MMC_TIMING_MMC_HS200) || |
171 | (timing == MMC_TIMING_UHS_SDR104)) |
172 | ctrl_2 |= SDHCI_CTRL_UHS_SDR104; |
173 | else if (timing == MMC_TIMING_UHS_SDR12) |
174 | ctrl_2 |= SDHCI_CTRL_UHS_SDR12; |
175 | else if ((timing == MMC_TIMING_UHS_SDR25) || |
176 | (timing == MMC_TIMING_MMC_HS)) |
177 | ctrl_2 |= SDHCI_CTRL_UHS_SDR25; |
178 | else if (timing == MMC_TIMING_UHS_SDR50) |
179 | ctrl_2 |= SDHCI_CTRL_UHS_SDR50; |
180 | else if ((timing == MMC_TIMING_UHS_DDR50) || |
181 | (timing == MMC_TIMING_MMC_DDR52)) |
182 | ctrl_2 |= SDHCI_CTRL_UHS_DDR50; |
183 | else if (timing == MMC_TIMING_MMC_HS400) { |
184 | /* set CARD_IS_EMMC bit to enable Data Strobe for HS400 */ |
185 | ctrl = sdhci_readw(host, reg: priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL); |
186 | ctrl |= DWCMSHC_CARD_IS_EMMC; |
187 | sdhci_writew(host, val: ctrl, reg: priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL); |
188 | |
189 | ctrl_2 |= DWCMSHC_CTRL_HS400; |
190 | } |
191 | |
192 | sdhci_writew(host, val: ctrl_2, SDHCI_HOST_CONTROL2); |
193 | } |
194 | |
195 | static void dwcmshc_hs400_enhanced_strobe(struct mmc_host *mmc, |
196 | struct mmc_ios *ios) |
197 | { |
198 | u32 vendor; |
199 | struct sdhci_host *host = mmc_priv(host: mmc); |
200 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
201 | struct dwcmshc_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
202 | int reg = priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL; |
203 | |
204 | vendor = sdhci_readl(host, reg); |
205 | if (ios->enhanced_strobe) |
206 | vendor |= DWCMSHC_ENHANCED_STROBE; |
207 | else |
208 | vendor &= ~DWCMSHC_ENHANCED_STROBE; |
209 | |
210 | sdhci_writel(host, val: vendor, reg); |
211 | } |
212 | |
213 | static void dwcmshc_rk3568_set_clock(struct sdhci_host *host, unsigned int clock) |
214 | { |
215 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
216 | struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(host: pltfm_host); |
217 | struct rk35xx_priv *priv = dwc_priv->priv; |
218 | u8 txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT; |
219 | u32 , reg; |
220 | int err; |
221 | |
222 | host->mmc->actual_clock = 0; |
223 | |
224 | if (clock == 0) { |
225 | /* Disable interface clock at initial state. */ |
226 | sdhci_set_clock(host, clock); |
227 | return; |
228 | } |
229 | |
230 | /* Rockchip platform only support 375KHz for identify mode */ |
231 | if (clock <= 400000) |
232 | clock = 375000; |
233 | |
234 | err = clk_set_rate(clk: pltfm_host->clk, rate: clock); |
235 | if (err) |
236 | dev_err(mmc_dev(host->mmc), "fail to set clock %d" , clock); |
237 | |
238 | sdhci_set_clock(host, clock); |
239 | |
240 | /* Disable cmd conflict check */ |
241 | reg = dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3; |
242 | extra = sdhci_readl(host, reg); |
243 | extra &= ~BIT(0); |
244 | sdhci_writel(host, val: extra, reg); |
245 | |
246 | if (clock <= 52000000) { |
247 | /* |
248 | * Disable DLL and reset both of sample and drive clock. |
249 | * The bypass bit and start bit need to be set if DLL is not locked. |
250 | */ |
251 | sdhci_writel(host, DWCMSHC_EMMC_DLL_BYPASS | DWCMSHC_EMMC_DLL_START, DWCMSHC_EMMC_DLL_CTRL); |
252 | sdhci_writel(host, DLL_RXCLK_ORI_GATE, DWCMSHC_EMMC_DLL_RXCLK); |
253 | sdhci_writel(host, val: 0, DWCMSHC_EMMC_DLL_TXCLK); |
254 | sdhci_writel(host, val: 0, DECMSHC_EMMC_DLL_CMDOUT); |
255 | /* |
256 | * Before switching to hs400es mode, the driver will enable |
257 | * enhanced strobe first. PHY needs to configure the parameters |
258 | * of enhanced strobe first. |
259 | */ |
260 | extra = DWCMSHC_EMMC_DLL_DLYENA | |
261 | DLL_STRBIN_DELAY_NUM_SEL | |
262 | DLL_STRBIN_DELAY_NUM_DEFAULT << DLL_STRBIN_DELAY_NUM_OFFSET; |
263 | sdhci_writel(host, val: extra, DWCMSHC_EMMC_DLL_STRBIN); |
264 | return; |
265 | } |
266 | |
267 | /* Reset DLL */ |
268 | sdhci_writel(host, BIT(1), DWCMSHC_EMMC_DLL_CTRL); |
269 | udelay(1); |
270 | sdhci_writel(host, val: 0x0, DWCMSHC_EMMC_DLL_CTRL); |
271 | |
272 | /* |
273 | * We shouldn't set DLL_RXCLK_NO_INVERTER for identify mode but |
274 | * we must set it in higher speed mode. |
275 | */ |
276 | extra = DWCMSHC_EMMC_DLL_DLYENA; |
277 | if (priv->devtype == DWCMSHC_RK3568) |
278 | extra |= DLL_RXCLK_NO_INVERTER << DWCMSHC_EMMC_DLL_RXCLK_SRCSEL; |
279 | sdhci_writel(host, val: extra, DWCMSHC_EMMC_DLL_RXCLK); |
280 | |
281 | /* Init DLL settings */ |
282 | extra = 0x5 << DWCMSHC_EMMC_DLL_START_POINT | |
283 | 0x2 << DWCMSHC_EMMC_DLL_INC | |
284 | DWCMSHC_EMMC_DLL_START; |
285 | sdhci_writel(host, val: extra, DWCMSHC_EMMC_DLL_CTRL); |
286 | err = readl_poll_timeout(host->ioaddr + DWCMSHC_EMMC_DLL_STATUS0, |
287 | extra, DLL_LOCK_WO_TMOUT(extra), 1, |
288 | 500 * USEC_PER_MSEC); |
289 | if (err) { |
290 | dev_err(mmc_dev(host->mmc), "DLL lock timeout!\n" ); |
291 | return; |
292 | } |
293 | |
294 | extra = 0x1 << 16 | /* tune clock stop en */ |
295 | 0x3 << 17 | /* pre-change delay */ |
296 | 0x3 << 19; /* post-change delay */ |
297 | sdhci_writel(host, val: extra, reg: dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL); |
298 | |
299 | if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 || |
300 | host->mmc->ios.timing == MMC_TIMING_MMC_HS400) |
301 | txclk_tapnum = priv->txclk_tapnum; |
302 | |
303 | if ((priv->devtype == DWCMSHC_RK3588) && host->mmc->ios.timing == MMC_TIMING_MMC_HS400) { |
304 | txclk_tapnum = DLL_TXCLK_TAPNUM_90_DEGREES; |
305 | |
306 | extra = DLL_CMDOUT_SRC_CLK_NEG | |
307 | DLL_CMDOUT_EN_SRC_CLK_NEG | |
308 | DWCMSHC_EMMC_DLL_DLYENA | |
309 | DLL_CMDOUT_TAPNUM_90_DEGREES | |
310 | DLL_CMDOUT_TAPNUM_FROM_SW; |
311 | sdhci_writel(host, val: extra, DECMSHC_EMMC_DLL_CMDOUT); |
312 | } |
313 | |
314 | extra = DWCMSHC_EMMC_DLL_DLYENA | |
315 | DLL_TXCLK_TAPNUM_FROM_SW | |
316 | DLL_RXCLK_NO_INVERTER << DWCMSHC_EMMC_DLL_RXCLK_SRCSEL | |
317 | txclk_tapnum; |
318 | sdhci_writel(host, val: extra, DWCMSHC_EMMC_DLL_TXCLK); |
319 | |
320 | extra = DWCMSHC_EMMC_DLL_DLYENA | |
321 | DLL_STRBIN_TAPNUM_DEFAULT | |
322 | DLL_STRBIN_TAPNUM_FROM_SW; |
323 | sdhci_writel(host, val: extra, DWCMSHC_EMMC_DLL_STRBIN); |
324 | } |
325 | |
326 | static void rk35xx_sdhci_reset(struct sdhci_host *host, u8 mask) |
327 | { |
328 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
329 | struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(host: pltfm_host); |
330 | struct rk35xx_priv *priv = dwc_priv->priv; |
331 | |
332 | if (mask & SDHCI_RESET_ALL && priv->reset) { |
333 | reset_control_assert(rstc: priv->reset); |
334 | udelay(1); |
335 | reset_control_deassert(rstc: priv->reset); |
336 | } |
337 | |
338 | sdhci_reset(host, mask); |
339 | } |
340 | |
341 | static const struct sdhci_ops sdhci_dwcmshc_ops = { |
342 | .set_clock = sdhci_set_clock, |
343 | .set_bus_width = sdhci_set_bus_width, |
344 | .set_uhs_signaling = dwcmshc_set_uhs_signaling, |
345 | .get_max_clock = dwcmshc_get_max_clock, |
346 | .reset = sdhci_reset, |
347 | .adma_write_desc = dwcmshc_adma_write_desc, |
348 | }; |
349 | |
350 | static const struct sdhci_ops sdhci_dwcmshc_rk35xx_ops = { |
351 | .set_clock = dwcmshc_rk3568_set_clock, |
352 | .set_bus_width = sdhci_set_bus_width, |
353 | .set_uhs_signaling = dwcmshc_set_uhs_signaling, |
354 | .get_max_clock = rk35xx_get_max_clock, |
355 | .reset = rk35xx_sdhci_reset, |
356 | .adma_write_desc = dwcmshc_adma_write_desc, |
357 | }; |
358 | |
359 | static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = { |
360 | .ops = &sdhci_dwcmshc_ops, |
361 | .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, |
362 | .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, |
363 | }; |
364 | |
365 | #ifdef CONFIG_ACPI |
366 | static const struct sdhci_pltfm_data sdhci_dwcmshc_bf3_pdata = { |
367 | .ops = &sdhci_dwcmshc_ops, |
368 | .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, |
369 | .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | |
370 | SDHCI_QUIRK2_ACMD23_BROKEN, |
371 | }; |
372 | #endif |
373 | |
374 | static const struct sdhci_pltfm_data sdhci_dwcmshc_rk35xx_pdata = { |
375 | .ops = &sdhci_dwcmshc_rk35xx_ops, |
376 | .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | |
377 | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, |
378 | .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | |
379 | SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, |
380 | }; |
381 | |
382 | static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) |
383 | { |
384 | int err; |
385 | struct rk35xx_priv *priv = dwc_priv->priv; |
386 | |
387 | priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc)); |
388 | if (IS_ERR(ptr: priv->reset)) { |
389 | err = PTR_ERR(ptr: priv->reset); |
390 | dev_err(mmc_dev(host->mmc), "failed to get reset control %d\n" , err); |
391 | return err; |
392 | } |
393 | |
394 | priv->rockchip_clks[0].id = "axi" ; |
395 | priv->rockchip_clks[1].id = "block" ; |
396 | priv->rockchip_clks[2].id = "timer" ; |
397 | err = devm_clk_bulk_get_optional(mmc_dev(host->mmc), RK35xx_MAX_CLKS, |
398 | clks: priv->rockchip_clks); |
399 | if (err) { |
400 | dev_err(mmc_dev(host->mmc), "failed to get clocks %d\n" , err); |
401 | return err; |
402 | } |
403 | |
404 | err = clk_bulk_prepare_enable(RK35xx_MAX_CLKS, clks: priv->rockchip_clks); |
405 | if (err) { |
406 | dev_err(mmc_dev(host->mmc), "failed to enable clocks %d\n" , err); |
407 | return err; |
408 | } |
409 | |
410 | if (of_property_read_u8(mmc_dev(host->mmc)->of_node, propname: "rockchip,txclk-tapnum" , |
411 | out_value: &priv->txclk_tapnum)) |
412 | priv->txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT; |
413 | |
414 | /* Disable cmd conflict check */ |
415 | sdhci_writel(host, val: 0x0, reg: dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3); |
416 | /* Reset previous settings */ |
417 | sdhci_writel(host, val: 0, DWCMSHC_EMMC_DLL_TXCLK); |
418 | sdhci_writel(host, val: 0, DWCMSHC_EMMC_DLL_STRBIN); |
419 | |
420 | return 0; |
421 | } |
422 | |
423 | static void dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) |
424 | { |
425 | /* |
426 | * Don't support highspeed bus mode with low clk speed as we |
427 | * cannot use DLL for this condition. |
428 | */ |
429 | if (host->mmc->f_max <= 52000000) { |
430 | dev_info(mmc_dev(host->mmc), "Disabling HS200/HS400, frequency too low (%d)\n" , |
431 | host->mmc->f_max); |
432 | host->mmc->caps2 &= ~(MMC_CAP2_HS200 | MMC_CAP2_HS400); |
433 | host->mmc->caps &= ~(MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR); |
434 | } |
435 | } |
436 | |
437 | static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { |
438 | { |
439 | .compatible = "rockchip,rk3588-dwcmshc" , |
440 | .data = &sdhci_dwcmshc_rk35xx_pdata, |
441 | }, |
442 | { |
443 | .compatible = "rockchip,rk3568-dwcmshc" , |
444 | .data = &sdhci_dwcmshc_rk35xx_pdata, |
445 | }, |
446 | { |
447 | .compatible = "snps,dwcmshc-sdhci" , |
448 | .data = &sdhci_dwcmshc_pdata, |
449 | }, |
450 | {}, |
451 | }; |
452 | MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids); |
453 | |
454 | #ifdef CONFIG_ACPI |
455 | static const struct acpi_device_id sdhci_dwcmshc_acpi_ids[] = { |
456 | { |
457 | .id = "MLNXBF30" , |
458 | .driver_data = (kernel_ulong_t)&sdhci_dwcmshc_bf3_pdata, |
459 | }, |
460 | {} |
461 | }; |
462 | MODULE_DEVICE_TABLE(acpi, sdhci_dwcmshc_acpi_ids); |
463 | #endif |
464 | |
465 | static int dwcmshc_probe(struct platform_device *pdev) |
466 | { |
467 | struct device *dev = &pdev->dev; |
468 | struct sdhci_pltfm_host *pltfm_host; |
469 | struct sdhci_host *host; |
470 | struct dwcmshc_priv *priv; |
471 | struct rk35xx_priv *rk_priv = NULL; |
472 | const struct sdhci_pltfm_data *pltfm_data; |
473 | int err; |
474 | u32 ; |
475 | |
476 | pltfm_data = device_get_match_data(dev: &pdev->dev); |
477 | if (!pltfm_data) { |
478 | dev_err(&pdev->dev, "Error: No device match data found\n" ); |
479 | return -ENODEV; |
480 | } |
481 | |
482 | host = sdhci_pltfm_init(pdev, pdata: pltfm_data, |
483 | priv_size: sizeof(struct dwcmshc_priv)); |
484 | if (IS_ERR(ptr: host)) |
485 | return PTR_ERR(ptr: host); |
486 | |
487 | /* |
488 | * extra adma table cnt for cross 128M boundary handling. |
489 | */ |
490 | extra = DIV_ROUND_UP_ULL(dma_get_required_mask(dev), SZ_128M); |
491 | if (extra > SDHCI_MAX_SEGS) |
492 | extra = SDHCI_MAX_SEGS; |
493 | host->adma_table_cnt += extra; |
494 | |
495 | pltfm_host = sdhci_priv(host); |
496 | priv = sdhci_pltfm_priv(host: pltfm_host); |
497 | |
498 | if (dev->of_node) { |
499 | pltfm_host->clk = devm_clk_get(dev, id: "core" ); |
500 | if (IS_ERR(ptr: pltfm_host->clk)) { |
501 | err = PTR_ERR(ptr: pltfm_host->clk); |
502 | dev_err(dev, "failed to get core clk: %d\n" , err); |
503 | goto free_pltfm; |
504 | } |
505 | err = clk_prepare_enable(clk: pltfm_host->clk); |
506 | if (err) |
507 | goto free_pltfm; |
508 | |
509 | priv->bus_clk = devm_clk_get(dev, id: "bus" ); |
510 | if (!IS_ERR(ptr: priv->bus_clk)) |
511 | clk_prepare_enable(clk: priv->bus_clk); |
512 | } |
513 | |
514 | err = mmc_of_parse(host: host->mmc); |
515 | if (err) |
516 | goto err_clk; |
517 | |
518 | sdhci_get_of_property(pdev); |
519 | |
520 | priv->vendor_specific_area1 = |
521 | sdhci_readl(host, DWCMSHC_P_VENDOR_AREA1) & DWCMSHC_AREA1_MASK; |
522 | |
523 | host->mmc_host_ops.request = dwcmshc_request; |
524 | host->mmc_host_ops.hs400_enhanced_strobe = dwcmshc_hs400_enhanced_strobe; |
525 | |
526 | if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) { |
527 | rk_priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct rk35xx_priv), GFP_KERNEL); |
528 | if (!rk_priv) { |
529 | err = -ENOMEM; |
530 | goto err_clk; |
531 | } |
532 | |
533 | if (of_device_is_compatible(device: pdev->dev.of_node, "rockchip,rk3588-dwcmshc" )) |
534 | rk_priv->devtype = DWCMSHC_RK3588; |
535 | else |
536 | rk_priv->devtype = DWCMSHC_RK3568; |
537 | |
538 | priv->priv = rk_priv; |
539 | |
540 | err = dwcmshc_rk35xx_init(host, dwc_priv: priv); |
541 | if (err) |
542 | goto err_clk; |
543 | } |
544 | |
545 | #ifdef CONFIG_ACPI |
546 | if (pltfm_data == &sdhci_dwcmshc_bf3_pdata) |
547 | sdhci_enable_v4_mode(host); |
548 | #endif |
549 | |
550 | host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; |
551 | |
552 | pm_runtime_get_noresume(dev); |
553 | pm_runtime_set_active(dev); |
554 | pm_runtime_enable(dev); |
555 | |
556 | err = sdhci_setup_host(host); |
557 | if (err) |
558 | goto err_rpm; |
559 | |
560 | if (rk_priv) |
561 | dwcmshc_rk35xx_postinit(host, dwc_priv: priv); |
562 | |
563 | err = __sdhci_add_host(host); |
564 | if (err) |
565 | goto err_setup_host; |
566 | |
567 | pm_runtime_put(dev); |
568 | |
569 | return 0; |
570 | |
571 | err_setup_host: |
572 | sdhci_cleanup_host(host); |
573 | err_rpm: |
574 | pm_runtime_disable(dev); |
575 | pm_runtime_put_noidle(dev); |
576 | err_clk: |
577 | clk_disable_unprepare(clk: pltfm_host->clk); |
578 | clk_disable_unprepare(clk: priv->bus_clk); |
579 | if (rk_priv) |
580 | clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, |
581 | clks: rk_priv->rockchip_clks); |
582 | free_pltfm: |
583 | sdhci_pltfm_free(pdev); |
584 | return err; |
585 | } |
586 | |
587 | static void dwcmshc_remove(struct platform_device *pdev) |
588 | { |
589 | struct sdhci_host *host = platform_get_drvdata(pdev); |
590 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
591 | struct dwcmshc_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
592 | struct rk35xx_priv *rk_priv = priv->priv; |
593 | |
594 | sdhci_remove_host(host, dead: 0); |
595 | |
596 | clk_disable_unprepare(clk: pltfm_host->clk); |
597 | clk_disable_unprepare(clk: priv->bus_clk); |
598 | if (rk_priv) |
599 | clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, |
600 | clks: rk_priv->rockchip_clks); |
601 | sdhci_pltfm_free(pdev); |
602 | } |
603 | |
604 | #ifdef CONFIG_PM_SLEEP |
605 | static int dwcmshc_suspend(struct device *dev) |
606 | { |
607 | struct sdhci_host *host = dev_get_drvdata(dev); |
608 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
609 | struct dwcmshc_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
610 | struct rk35xx_priv *rk_priv = priv->priv; |
611 | int ret; |
612 | |
613 | pm_runtime_resume(dev); |
614 | |
615 | ret = sdhci_suspend_host(host); |
616 | if (ret) |
617 | return ret; |
618 | |
619 | clk_disable_unprepare(clk: pltfm_host->clk); |
620 | if (!IS_ERR(ptr: priv->bus_clk)) |
621 | clk_disable_unprepare(clk: priv->bus_clk); |
622 | |
623 | if (rk_priv) |
624 | clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, |
625 | clks: rk_priv->rockchip_clks); |
626 | |
627 | return ret; |
628 | } |
629 | |
630 | static int dwcmshc_resume(struct device *dev) |
631 | { |
632 | struct sdhci_host *host = dev_get_drvdata(dev); |
633 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
634 | struct dwcmshc_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
635 | struct rk35xx_priv *rk_priv = priv->priv; |
636 | int ret; |
637 | |
638 | ret = clk_prepare_enable(clk: pltfm_host->clk); |
639 | if (ret) |
640 | return ret; |
641 | |
642 | if (!IS_ERR(ptr: priv->bus_clk)) { |
643 | ret = clk_prepare_enable(clk: priv->bus_clk); |
644 | if (ret) |
645 | goto disable_clk; |
646 | } |
647 | |
648 | if (rk_priv) { |
649 | ret = clk_bulk_prepare_enable(RK35xx_MAX_CLKS, |
650 | clks: rk_priv->rockchip_clks); |
651 | if (ret) |
652 | goto disable_bus_clk; |
653 | } |
654 | |
655 | ret = sdhci_resume_host(host); |
656 | if (ret) |
657 | goto disable_rockchip_clks; |
658 | |
659 | return 0; |
660 | |
661 | disable_rockchip_clks: |
662 | if (rk_priv) |
663 | clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, |
664 | clks: rk_priv->rockchip_clks); |
665 | disable_bus_clk: |
666 | if (!IS_ERR(ptr: priv->bus_clk)) |
667 | clk_disable_unprepare(clk: priv->bus_clk); |
668 | disable_clk: |
669 | clk_disable_unprepare(clk: pltfm_host->clk); |
670 | return ret; |
671 | } |
672 | #endif |
673 | |
674 | #ifdef CONFIG_PM |
675 | |
676 | static void dwcmshc_enable_card_clk(struct sdhci_host *host) |
677 | { |
678 | u16 ctrl; |
679 | |
680 | ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL); |
681 | if ((ctrl & SDHCI_CLOCK_INT_EN) && !(ctrl & SDHCI_CLOCK_CARD_EN)) { |
682 | ctrl |= SDHCI_CLOCK_CARD_EN; |
683 | sdhci_writew(host, val: ctrl, SDHCI_CLOCK_CONTROL); |
684 | } |
685 | } |
686 | |
687 | static void dwcmshc_disable_card_clk(struct sdhci_host *host) |
688 | { |
689 | u16 ctrl; |
690 | |
691 | ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL); |
692 | if (ctrl & SDHCI_CLOCK_CARD_EN) { |
693 | ctrl &= ~SDHCI_CLOCK_CARD_EN; |
694 | sdhci_writew(host, val: ctrl, SDHCI_CLOCK_CONTROL); |
695 | } |
696 | } |
697 | |
698 | static int dwcmshc_runtime_suspend(struct device *dev) |
699 | { |
700 | struct sdhci_host *host = dev_get_drvdata(dev); |
701 | |
702 | dwcmshc_disable_card_clk(host); |
703 | |
704 | return 0; |
705 | } |
706 | |
707 | static int dwcmshc_runtime_resume(struct device *dev) |
708 | { |
709 | struct sdhci_host *host = dev_get_drvdata(dev); |
710 | |
711 | dwcmshc_enable_card_clk(host); |
712 | |
713 | return 0; |
714 | } |
715 | |
716 | #endif |
717 | |
718 | static const struct dev_pm_ops dwcmshc_pmops = { |
719 | SET_SYSTEM_SLEEP_PM_OPS(dwcmshc_suspend, dwcmshc_resume) |
720 | SET_RUNTIME_PM_OPS(dwcmshc_runtime_suspend, |
721 | dwcmshc_runtime_resume, NULL) |
722 | }; |
723 | |
724 | static struct platform_driver sdhci_dwcmshc_driver = { |
725 | .driver = { |
726 | .name = "sdhci-dwcmshc" , |
727 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
728 | .of_match_table = sdhci_dwcmshc_dt_ids, |
729 | .acpi_match_table = ACPI_PTR(sdhci_dwcmshc_acpi_ids), |
730 | .pm = &dwcmshc_pmops, |
731 | }, |
732 | .probe = dwcmshc_probe, |
733 | .remove_new = dwcmshc_remove, |
734 | }; |
735 | module_platform_driver(sdhci_dwcmshc_driver); |
736 | |
737 | MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC" ); |
738 | MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>" ); |
739 | MODULE_LICENSE("GPL v2" ); |
740 | |