1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * sdhci-brcmstb.c Support for SDHCI on Broadcom BRCMSTB SoC's |
4 | * |
5 | * Copyright (C) 2015 Broadcom Corporation |
6 | */ |
7 | |
8 | #include <linux/io.h> |
9 | #include <linux/mmc/host.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/bitops.h> |
13 | #include <linux/delay.h> |
14 | |
15 | #include "sdhci-cqhci.h" |
16 | #include "sdhci-pltfm.h" |
17 | #include "cqhci.h" |
18 | |
19 | #define SDHCI_VENDOR 0x78 |
20 | #define SDHCI_VENDOR_ENHANCED_STRB 0x1 |
21 | #define SDHCI_VENDOR_GATE_SDCLK_EN 0x2 |
22 | |
23 | #define BRCMSTB_MATCH_FLAGS_NO_64BIT BIT(0) |
24 | #define BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT BIT(1) |
25 | #define BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE BIT(2) |
26 | |
27 | #define BRCMSTB_PRIV_FLAGS_HAS_CQE BIT(0) |
28 | #define BRCMSTB_PRIV_FLAGS_GATE_CLOCK BIT(1) |
29 | |
30 | #define SDHCI_ARASAN_CQE_BASE_ADDR 0x200 |
31 | |
32 | struct sdhci_brcmstb_priv { |
33 | void __iomem *cfg_regs; |
34 | unsigned int flags; |
35 | struct clk *base_clk; |
36 | u32 base_freq_hz; |
37 | }; |
38 | |
39 | struct brcmstb_match_priv { |
40 | void (*hs400es)(struct mmc_host *mmc, struct mmc_ios *ios); |
41 | struct sdhci_ops *ops; |
42 | const unsigned int flags; |
43 | }; |
44 | |
45 | static inline void enable_clock_gating(struct sdhci_host *host) |
46 | { |
47 | u32 reg; |
48 | |
49 | reg = sdhci_readl(host, SDHCI_VENDOR); |
50 | reg |= SDHCI_VENDOR_GATE_SDCLK_EN; |
51 | sdhci_writel(host, val: reg, SDHCI_VENDOR); |
52 | } |
53 | |
54 | static void brcmstb_reset(struct sdhci_host *host, u8 mask) |
55 | { |
56 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
57 | struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
58 | |
59 | sdhci_and_cqhci_reset(host, mask); |
60 | |
61 | /* Reset will clear this, so re-enable it */ |
62 | if (priv->flags & BRCMSTB_PRIV_FLAGS_GATE_CLOCK) |
63 | enable_clock_gating(host); |
64 | } |
65 | |
66 | static void sdhci_brcmstb_hs400es(struct mmc_host *mmc, struct mmc_ios *ios) |
67 | { |
68 | struct sdhci_host *host = mmc_priv(host: mmc); |
69 | |
70 | u32 reg; |
71 | |
72 | dev_dbg(mmc_dev(mmc), "%s(): Setting HS400-Enhanced-Strobe mode\n" , |
73 | __func__); |
74 | reg = readl(addr: host->ioaddr + SDHCI_VENDOR); |
75 | if (ios->enhanced_strobe) |
76 | reg |= SDHCI_VENDOR_ENHANCED_STRB; |
77 | else |
78 | reg &= ~SDHCI_VENDOR_ENHANCED_STRB; |
79 | writel(val: reg, addr: host->ioaddr + SDHCI_VENDOR); |
80 | } |
81 | |
82 | static void sdhci_brcmstb_set_clock(struct sdhci_host *host, unsigned int clock) |
83 | { |
84 | u16 clk; |
85 | |
86 | host->mmc->actual_clock = 0; |
87 | |
88 | clk = sdhci_calc_clk(host, clock, actual_clock: &host->mmc->actual_clock); |
89 | sdhci_writew(host, val: clk, SDHCI_CLOCK_CONTROL); |
90 | |
91 | if (clock == 0) |
92 | return; |
93 | |
94 | sdhci_enable_clk(host, clk); |
95 | } |
96 | |
97 | static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host, |
98 | unsigned int timing) |
99 | { |
100 | u16 ctrl_2; |
101 | |
102 | dev_dbg(mmc_dev(host->mmc), "%s: Setting UHS signaling for %d timing\n" , |
103 | __func__, timing); |
104 | ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); |
105 | /* Select Bus Speed Mode for host */ |
106 | ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; |
107 | if ((timing == MMC_TIMING_MMC_HS200) || |
108 | (timing == MMC_TIMING_UHS_SDR104)) |
109 | ctrl_2 |= SDHCI_CTRL_UHS_SDR104; |
110 | else if (timing == MMC_TIMING_UHS_SDR12) |
111 | ctrl_2 |= SDHCI_CTRL_UHS_SDR12; |
112 | else if (timing == MMC_TIMING_SD_HS || |
113 | timing == MMC_TIMING_MMC_HS || |
114 | timing == MMC_TIMING_UHS_SDR25) |
115 | ctrl_2 |= SDHCI_CTRL_UHS_SDR25; |
116 | else if (timing == MMC_TIMING_UHS_SDR50) |
117 | ctrl_2 |= SDHCI_CTRL_UHS_SDR50; |
118 | else if ((timing == MMC_TIMING_UHS_DDR50) || |
119 | (timing == MMC_TIMING_MMC_DDR52)) |
120 | ctrl_2 |= SDHCI_CTRL_UHS_DDR50; |
121 | else if (timing == MMC_TIMING_MMC_HS400) |
122 | ctrl_2 |= SDHCI_CTRL_HS400; /* Non-standard */ |
123 | sdhci_writew(host, val: ctrl_2, SDHCI_HOST_CONTROL2); |
124 | } |
125 | |
126 | static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc) |
127 | { |
128 | sdhci_dumpregs(host: mmc_priv(host: mmc)); |
129 | } |
130 | |
131 | static void sdhci_brcmstb_cqe_enable(struct mmc_host *mmc) |
132 | { |
133 | struct sdhci_host *host = mmc_priv(host: mmc); |
134 | u32 reg; |
135 | |
136 | reg = sdhci_readl(host, SDHCI_PRESENT_STATE); |
137 | while (reg & SDHCI_DATA_AVAILABLE) { |
138 | sdhci_readl(host, SDHCI_BUFFER); |
139 | reg = sdhci_readl(host, SDHCI_PRESENT_STATE); |
140 | } |
141 | |
142 | sdhci_cqe_enable(mmc); |
143 | } |
144 | |
145 | static const struct cqhci_host_ops sdhci_brcmstb_cqhci_ops = { |
146 | .enable = sdhci_brcmstb_cqe_enable, |
147 | .disable = sdhci_cqe_disable, |
148 | .dumpregs = sdhci_brcmstb_dumpregs, |
149 | }; |
150 | |
151 | static struct sdhci_ops sdhci_brcmstb_ops = { |
152 | .set_clock = sdhci_set_clock, |
153 | .set_bus_width = sdhci_set_bus_width, |
154 | .reset = sdhci_reset, |
155 | .set_uhs_signaling = sdhci_set_uhs_signaling, |
156 | }; |
157 | |
158 | static struct sdhci_ops sdhci_brcmstb_ops_7216 = { |
159 | .set_clock = sdhci_brcmstb_set_clock, |
160 | .set_bus_width = sdhci_set_bus_width, |
161 | .reset = brcmstb_reset, |
162 | .set_uhs_signaling = sdhci_brcmstb_set_uhs_signaling, |
163 | }; |
164 | |
165 | static struct brcmstb_match_priv match_priv_7425 = { |
166 | .flags = BRCMSTB_MATCH_FLAGS_NO_64BIT | |
167 | BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, |
168 | .ops = &sdhci_brcmstb_ops, |
169 | }; |
170 | |
171 | static struct brcmstb_match_priv match_priv_7445 = { |
172 | .flags = BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT, |
173 | .ops = &sdhci_brcmstb_ops, |
174 | }; |
175 | |
176 | static const struct brcmstb_match_priv match_priv_7216 = { |
177 | .flags = BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE, |
178 | .hs400es = sdhci_brcmstb_hs400es, |
179 | .ops = &sdhci_brcmstb_ops_7216, |
180 | }; |
181 | |
182 | static const struct of_device_id __maybe_unused sdhci_brcm_of_match[] = { |
183 | { .compatible = "brcm,bcm7425-sdhci" , .data = &match_priv_7425 }, |
184 | { .compatible = "brcm,bcm7445-sdhci" , .data = &match_priv_7445 }, |
185 | { .compatible = "brcm,bcm7216-sdhci" , .data = &match_priv_7216 }, |
186 | {}, |
187 | }; |
188 | |
189 | static u32 sdhci_brcmstb_cqhci_irq(struct sdhci_host *host, u32 intmask) |
190 | { |
191 | int cmd_error = 0; |
192 | int data_error = 0; |
193 | |
194 | if (!sdhci_cqe_irq(host, intmask, cmd_error: &cmd_error, data_error: &data_error)) |
195 | return intmask; |
196 | |
197 | cqhci_irq(mmc: host->mmc, intmask, cmd_error, data_error); |
198 | |
199 | return 0; |
200 | } |
201 | |
202 | static int sdhci_brcmstb_add_host(struct sdhci_host *host, |
203 | struct sdhci_brcmstb_priv *priv) |
204 | { |
205 | struct cqhci_host *cq_host; |
206 | bool dma64; |
207 | int ret; |
208 | |
209 | if ((priv->flags & BRCMSTB_PRIV_FLAGS_HAS_CQE) == 0) |
210 | return sdhci_add_host(host); |
211 | |
212 | dev_dbg(mmc_dev(host->mmc), "CQE is enabled\n" ); |
213 | host->mmc->caps2 |= MMC_CAP2_CQE | MMC_CAP2_CQE_DCMD; |
214 | ret = sdhci_setup_host(host); |
215 | if (ret) |
216 | return ret; |
217 | |
218 | cq_host = devm_kzalloc(mmc_dev(host->mmc), |
219 | size: sizeof(*cq_host), GFP_KERNEL); |
220 | if (!cq_host) { |
221 | ret = -ENOMEM; |
222 | goto cleanup; |
223 | } |
224 | |
225 | cq_host->mmio = host->ioaddr + SDHCI_ARASAN_CQE_BASE_ADDR; |
226 | cq_host->ops = &sdhci_brcmstb_cqhci_ops; |
227 | |
228 | dma64 = host->flags & SDHCI_USE_64_BIT_DMA; |
229 | if (dma64) { |
230 | dev_dbg(mmc_dev(host->mmc), "Using 64 bit DMA\n" ); |
231 | cq_host->caps |= CQHCI_TASK_DESC_SZ_128; |
232 | } |
233 | |
234 | ret = cqhci_init(cq_host, mmc: host->mmc, dma64); |
235 | if (ret) |
236 | goto cleanup; |
237 | |
238 | ret = __sdhci_add_host(host); |
239 | if (ret) |
240 | goto cleanup; |
241 | |
242 | return 0; |
243 | |
244 | cleanup: |
245 | sdhci_cleanup_host(host); |
246 | return ret; |
247 | } |
248 | |
249 | static int sdhci_brcmstb_probe(struct platform_device *pdev) |
250 | { |
251 | const struct brcmstb_match_priv *match_priv; |
252 | struct sdhci_pltfm_data brcmstb_pdata; |
253 | struct sdhci_pltfm_host *pltfm_host; |
254 | const struct of_device_id *match; |
255 | struct sdhci_brcmstb_priv *priv; |
256 | u32 actual_clock_mhz; |
257 | struct sdhci_host *host; |
258 | struct clk *clk; |
259 | struct clk *base_clk = NULL; |
260 | int res; |
261 | |
262 | match = of_match_node(matches: sdhci_brcm_of_match, node: pdev->dev.of_node); |
263 | match_priv = match->data; |
264 | |
265 | dev_dbg(&pdev->dev, "Probe found match for %s\n" , match->compatible); |
266 | |
267 | clk = devm_clk_get_optional_enabled(dev: &pdev->dev, NULL); |
268 | if (IS_ERR(ptr: clk)) |
269 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: clk), |
270 | fmt: "Failed to get and enable clock from Device Tree\n" ); |
271 | |
272 | memset(&brcmstb_pdata, 0, sizeof(brcmstb_pdata)); |
273 | brcmstb_pdata.ops = match_priv->ops; |
274 | host = sdhci_pltfm_init(pdev, pdata: &brcmstb_pdata, |
275 | priv_size: sizeof(struct sdhci_brcmstb_priv)); |
276 | if (IS_ERR(ptr: host)) |
277 | return PTR_ERR(ptr: host); |
278 | |
279 | pltfm_host = sdhci_priv(host); |
280 | priv = sdhci_pltfm_priv(host: pltfm_host); |
281 | if (device_property_read_bool(dev: &pdev->dev, propname: "supports-cqe" )) { |
282 | priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_CQE; |
283 | match_priv->ops->irq = sdhci_brcmstb_cqhci_irq; |
284 | } |
285 | |
286 | /* Map in the non-standard CFG registers */ |
287 | priv->cfg_regs = devm_platform_get_and_ioremap_resource(pdev, index: 1, NULL); |
288 | if (IS_ERR(ptr: priv->cfg_regs)) { |
289 | res = PTR_ERR(ptr: priv->cfg_regs); |
290 | goto err; |
291 | } |
292 | |
293 | sdhci_get_of_property(pdev); |
294 | res = mmc_of_parse(host: host->mmc); |
295 | if (res) |
296 | goto err; |
297 | |
298 | /* |
299 | * Automatic clock gating does not work for SD cards that may |
300 | * voltage switch so only enable it for non-removable devices. |
301 | */ |
302 | if ((match_priv->flags & BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE) && |
303 | (host->mmc->caps & MMC_CAP_NONREMOVABLE)) |
304 | priv->flags |= BRCMSTB_PRIV_FLAGS_GATE_CLOCK; |
305 | |
306 | /* |
307 | * If the chip has enhanced strobe and it's enabled, add |
308 | * callback |
309 | */ |
310 | if (match_priv->hs400es && |
311 | (host->mmc->caps2 & MMC_CAP2_HS400_ES)) |
312 | host->mmc_host_ops.hs400_enhanced_strobe = match_priv->hs400es; |
313 | |
314 | /* |
315 | * Supply the existing CAPS, but clear the UHS modes. This |
316 | * will allow these modes to be specified by device tree |
317 | * properties through mmc_of_parse(). |
318 | */ |
319 | sdhci_read_caps(host); |
320 | if (match_priv->flags & BRCMSTB_MATCH_FLAGS_NO_64BIT) |
321 | host->caps &= ~SDHCI_CAN_64BIT; |
322 | host->caps1 &= ~(SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_SDR104 | |
323 | SDHCI_SUPPORT_DDR50); |
324 | |
325 | if (match_priv->flags & BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT) |
326 | host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; |
327 | |
328 | /* Change the base clock frequency if the DT property exists */ |
329 | if (device_property_read_u32(dev: &pdev->dev, propname: "clock-frequency" , |
330 | val: &priv->base_freq_hz) != 0) |
331 | goto add_host; |
332 | |
333 | base_clk = devm_clk_get_optional(dev: &pdev->dev, id: "sdio_freq" ); |
334 | if (IS_ERR(ptr: base_clk)) { |
335 | dev_warn(&pdev->dev, "Clock for \"sdio_freq\" not found\n" ); |
336 | goto add_host; |
337 | } |
338 | |
339 | res = clk_prepare_enable(clk: base_clk); |
340 | if (res) |
341 | goto err; |
342 | |
343 | /* set improved clock rate */ |
344 | clk_set_rate(clk: base_clk, rate: priv->base_freq_hz); |
345 | actual_clock_mhz = clk_get_rate(clk: base_clk) / 1000000; |
346 | |
347 | host->caps &= ~SDHCI_CLOCK_V3_BASE_MASK; |
348 | host->caps |= (actual_clock_mhz << SDHCI_CLOCK_BASE_SHIFT); |
349 | /* Disable presets because they are now incorrect */ |
350 | host->quirks2 |= SDHCI_QUIRK2_PRESET_VALUE_BROKEN; |
351 | |
352 | dev_dbg(&pdev->dev, "Base Clock Frequency changed to %dMHz\n" , |
353 | actual_clock_mhz); |
354 | priv->base_clk = base_clk; |
355 | |
356 | add_host: |
357 | res = sdhci_brcmstb_add_host(host, priv); |
358 | if (res) |
359 | goto err; |
360 | |
361 | pltfm_host->clk = clk; |
362 | return res; |
363 | |
364 | err: |
365 | sdhci_pltfm_free(pdev); |
366 | clk_disable_unprepare(clk: base_clk); |
367 | return res; |
368 | } |
369 | |
370 | static void sdhci_brcmstb_shutdown(struct platform_device *pdev) |
371 | { |
372 | sdhci_pltfm_suspend(dev: &pdev->dev); |
373 | } |
374 | |
375 | MODULE_DEVICE_TABLE(of, sdhci_brcm_of_match); |
376 | |
377 | #ifdef CONFIG_PM_SLEEP |
378 | static int sdhci_brcmstb_suspend(struct device *dev) |
379 | { |
380 | struct sdhci_host *host = dev_get_drvdata(dev); |
381 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
382 | struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
383 | |
384 | clk_disable_unprepare(clk: priv->base_clk); |
385 | return sdhci_pltfm_suspend(dev); |
386 | } |
387 | |
388 | static int sdhci_brcmstb_resume(struct device *dev) |
389 | { |
390 | struct sdhci_host *host = dev_get_drvdata(dev); |
391 | struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
392 | struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(host: pltfm_host); |
393 | int ret; |
394 | |
395 | ret = sdhci_pltfm_resume(dev); |
396 | if (!ret && priv->base_freq_hz) { |
397 | ret = clk_prepare_enable(clk: priv->base_clk); |
398 | /* |
399 | * Note: using clk_get_rate() below as clk_get_rate() |
400 | * honors CLK_GET_RATE_NOCACHE attribute, but clk_set_rate() |
401 | * may do implicit get_rate() calls that do not honor |
402 | * CLK_GET_RATE_NOCACHE. |
403 | */ |
404 | if (!ret && |
405 | (clk_get_rate(clk: priv->base_clk) != priv->base_freq_hz)) |
406 | ret = clk_set_rate(clk: priv->base_clk, rate: priv->base_freq_hz); |
407 | } |
408 | |
409 | return ret; |
410 | } |
411 | #endif |
412 | |
413 | static const struct dev_pm_ops sdhci_brcmstb_pmops = { |
414 | SET_SYSTEM_SLEEP_PM_OPS(sdhci_brcmstb_suspend, sdhci_brcmstb_resume) |
415 | }; |
416 | |
417 | static struct platform_driver sdhci_brcmstb_driver = { |
418 | .driver = { |
419 | .name = "sdhci-brcmstb" , |
420 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
421 | .pm = &sdhci_brcmstb_pmops, |
422 | .of_match_table = of_match_ptr(sdhci_brcm_of_match), |
423 | }, |
424 | .probe = sdhci_brcmstb_probe, |
425 | .remove_new = sdhci_pltfm_remove, |
426 | .shutdown = sdhci_brcmstb_shutdown, |
427 | }; |
428 | |
429 | module_platform_driver(sdhci_brcmstb_driver); |
430 | |
431 | MODULE_DESCRIPTION("SDHCI driver for Broadcom BRCMSTB SoCs" ); |
432 | MODULE_AUTHOR("Broadcom" ); |
433 | MODULE_LICENSE("GPL v2" ); |
434 | |