1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * This file is part of wl1271 |
4 | * |
5 | * Copyright (C) 2009-2010 Nokia Corporation |
6 | * |
7 | * Contact: Luciano Coelho <luciano.coelho@nokia.com> |
8 | */ |
9 | |
10 | #include <linux/irq.h> |
11 | #include <linux/module.h> |
12 | #include <linux/vmalloc.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/mmc/sdio.h> |
15 | #include <linux/mmc/sdio_func.h> |
16 | #include <linux/mmc/sdio_ids.h> |
17 | #include <linux/mmc/card.h> |
18 | #include <linux/mmc/host.h> |
19 | #include <linux/pm_runtime.h> |
20 | #include <linux/printk.h> |
21 | #include <linux/of.h> |
22 | #include <linux/of_irq.h> |
23 | |
24 | #include "wlcore.h" |
25 | #include "wl12xx_80211.h" |
26 | #include "io.h" |
27 | |
28 | static bool dump; |
29 | |
30 | struct wl12xx_sdio_glue { |
31 | struct device *dev; |
32 | struct platform_device *core; |
33 | }; |
34 | |
35 | static const struct sdio_device_id wl1271_devices[] = { |
36 | { SDIO_DEVICE(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271) }, |
37 | {} |
38 | }; |
39 | MODULE_DEVICE_TABLE(sdio, wl1271_devices); |
40 | |
41 | static void wl1271_sdio_set_block_size(struct device *child, |
42 | unsigned int blksz) |
43 | { |
44 | struct wl12xx_sdio_glue *glue = dev_get_drvdata(dev: child->parent); |
45 | struct sdio_func *func = dev_to_sdio_func(glue->dev); |
46 | |
47 | sdio_claim_host(func); |
48 | sdio_set_block_size(func, blksz); |
49 | sdio_release_host(func); |
50 | } |
51 | |
52 | static int __must_check wl12xx_sdio_raw_read(struct device *child, int addr, |
53 | void *buf, size_t len, bool fixed) |
54 | { |
55 | int ret; |
56 | struct wl12xx_sdio_glue *glue = dev_get_drvdata(dev: child->parent); |
57 | struct sdio_func *func = dev_to_sdio_func(glue->dev); |
58 | |
59 | sdio_claim_host(func); |
60 | |
61 | if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG)) { |
62 | ((u8 *)buf)[0] = sdio_f0_readb(func, addr, err_ret: &ret); |
63 | dev_dbg(child->parent, "sdio read 52 addr 0x%x, byte 0x%02x\n" , |
64 | addr, ((u8 *)buf)[0]); |
65 | } else { |
66 | if (fixed) |
67 | ret = sdio_readsb(func, dst: buf, addr, count: len); |
68 | else |
69 | ret = sdio_memcpy_fromio(func, dst: buf, addr, count: len); |
70 | |
71 | dev_dbg(child->parent, "sdio read 53 addr 0x%x, %zu bytes\n" , |
72 | addr, len); |
73 | } |
74 | |
75 | sdio_release_host(func); |
76 | |
77 | if (ret) |
78 | dev_err_ratelimited(child->parent, "sdio read failed (%d)\n" , ret); |
79 | |
80 | if (unlikely(dump)) { |
81 | printk(KERN_DEBUG "wlcore_sdio: READ from 0x%04x\n" , addr); |
82 | print_hex_dump(KERN_DEBUG, prefix_str: "wlcore_sdio: READ " , |
83 | prefix_type: DUMP_PREFIX_OFFSET, rowsize: 16, groupsize: 1, |
84 | buf, len, ascii: false); |
85 | } |
86 | |
87 | return ret; |
88 | } |
89 | |
90 | static int __must_check wl12xx_sdio_raw_write(struct device *child, int addr, |
91 | void *buf, size_t len, bool fixed) |
92 | { |
93 | int ret; |
94 | struct wl12xx_sdio_glue *glue = dev_get_drvdata(dev: child->parent); |
95 | struct sdio_func *func = dev_to_sdio_func(glue->dev); |
96 | |
97 | sdio_claim_host(func); |
98 | |
99 | if (unlikely(dump)) { |
100 | printk(KERN_DEBUG "wlcore_sdio: WRITE to 0x%04x\n" , addr); |
101 | print_hex_dump(KERN_DEBUG, prefix_str: "wlcore_sdio: WRITE " , |
102 | prefix_type: DUMP_PREFIX_OFFSET, rowsize: 16, groupsize: 1, |
103 | buf, len, ascii: false); |
104 | } |
105 | |
106 | if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG)) { |
107 | sdio_f0_writeb(func, b: ((u8 *)buf)[0], addr, err_ret: &ret); |
108 | dev_dbg(child->parent, "sdio write 52 addr 0x%x, byte 0x%02x\n" , |
109 | addr, ((u8 *)buf)[0]); |
110 | } else { |
111 | dev_dbg(child->parent, "sdio write 53 addr 0x%x, %zu bytes\n" , |
112 | addr, len); |
113 | |
114 | if (fixed) |
115 | ret = sdio_writesb(func, addr, src: buf, count: len); |
116 | else |
117 | ret = sdio_memcpy_toio(func, addr, src: buf, count: len); |
118 | } |
119 | |
120 | sdio_release_host(func); |
121 | |
122 | if (ret) |
123 | dev_err_ratelimited(child->parent, "sdio write failed (%d)\n" , ret); |
124 | |
125 | return ret; |
126 | } |
127 | |
128 | static int wl12xx_sdio_power_on(struct wl12xx_sdio_glue *glue) |
129 | { |
130 | int ret; |
131 | struct sdio_func *func = dev_to_sdio_func(glue->dev); |
132 | struct mmc_card *card = func->card; |
133 | |
134 | ret = pm_runtime_resume_and_get(dev: &card->dev); |
135 | if (ret < 0) { |
136 | dev_err(glue->dev, "%s: failed to get_sync(%d)\n" , |
137 | __func__, ret); |
138 | |
139 | return ret; |
140 | } |
141 | |
142 | sdio_claim_host(func); |
143 | /* |
144 | * To guarantee that the SDIO card is power cycled, as required to make |
145 | * the FW programming to succeed, let's do a brute force HW reset. |
146 | */ |
147 | mmc_hw_reset(card); |
148 | |
149 | sdio_enable_func(func); |
150 | sdio_release_host(func); |
151 | |
152 | return 0; |
153 | } |
154 | |
155 | static int wl12xx_sdio_power_off(struct wl12xx_sdio_glue *glue) |
156 | { |
157 | struct sdio_func *func = dev_to_sdio_func(glue->dev); |
158 | struct mmc_card *card = func->card; |
159 | |
160 | sdio_claim_host(func); |
161 | sdio_disable_func(func); |
162 | sdio_release_host(func); |
163 | |
164 | /* Let runtime PM know the card is powered off */ |
165 | pm_runtime_put(dev: &card->dev); |
166 | return 0; |
167 | } |
168 | |
169 | static int wl12xx_sdio_set_power(struct device *child, bool enable) |
170 | { |
171 | struct wl12xx_sdio_glue *glue = dev_get_drvdata(dev: child->parent); |
172 | |
173 | if (enable) |
174 | return wl12xx_sdio_power_on(glue); |
175 | else |
176 | return wl12xx_sdio_power_off(glue); |
177 | } |
178 | |
179 | static struct wl1271_if_operations sdio_ops = { |
180 | .read = wl12xx_sdio_raw_read, |
181 | .write = wl12xx_sdio_raw_write, |
182 | .power = wl12xx_sdio_set_power, |
183 | .set_block_size = wl1271_sdio_set_block_size, |
184 | }; |
185 | |
186 | #ifdef CONFIG_OF |
187 | |
188 | static const struct wilink_family_data wl127x_data = { |
189 | .name = "wl127x" , |
190 | .nvs_name = "ti-connectivity/wl127x-nvs.bin" , |
191 | }; |
192 | |
193 | static const struct wilink_family_data wl128x_data = { |
194 | .name = "wl128x" , |
195 | .nvs_name = "ti-connectivity/wl128x-nvs.bin" , |
196 | }; |
197 | |
198 | static const struct wilink_family_data wl18xx_data = { |
199 | .name = "wl18xx" , |
200 | .cfg_name = "ti-connectivity/wl18xx-conf.bin" , |
201 | .nvs_name = "ti-connectivity/wl1271-nvs.bin" , |
202 | }; |
203 | |
204 | static const struct of_device_id wlcore_sdio_of_match_table[] = { |
205 | { .compatible = "ti,wl1271" , .data = &wl127x_data }, |
206 | { .compatible = "ti,wl1273" , .data = &wl127x_data }, |
207 | { .compatible = "ti,wl1281" , .data = &wl128x_data }, |
208 | { .compatible = "ti,wl1283" , .data = &wl128x_data }, |
209 | { .compatible = "ti,wl1285" , .data = &wl128x_data }, |
210 | { .compatible = "ti,wl1801" , .data = &wl18xx_data }, |
211 | { .compatible = "ti,wl1805" , .data = &wl18xx_data }, |
212 | { .compatible = "ti,wl1807" , .data = &wl18xx_data }, |
213 | { .compatible = "ti,wl1831" , .data = &wl18xx_data }, |
214 | { .compatible = "ti,wl1835" , .data = &wl18xx_data }, |
215 | { .compatible = "ti,wl1837" , .data = &wl18xx_data }, |
216 | { } |
217 | }; |
218 | |
219 | static int wlcore_probe_of(struct device *dev, int *irq, int *wakeirq, |
220 | struct wlcore_platdev_data *pdev_data) |
221 | { |
222 | struct device_node *np = dev->of_node; |
223 | const struct of_device_id *of_id; |
224 | |
225 | of_id = of_match_node(matches: wlcore_sdio_of_match_table, node: np); |
226 | if (!of_id) |
227 | return -ENODEV; |
228 | |
229 | pdev_data->family = of_id->data; |
230 | |
231 | *irq = irq_of_parse_and_map(node: np, index: 0); |
232 | if (!*irq) { |
233 | dev_err(dev, "No irq in platform data\n" ); |
234 | return -EINVAL; |
235 | } |
236 | |
237 | *wakeirq = irq_of_parse_and_map(node: np, index: 1); |
238 | |
239 | /* optional clock frequency params */ |
240 | of_property_read_u32(np, propname: "ref-clock-frequency" , |
241 | out_value: &pdev_data->ref_clock_freq); |
242 | of_property_read_u32(np, propname: "tcxo-clock-frequency" , |
243 | out_value: &pdev_data->tcxo_clock_freq); |
244 | |
245 | return 0; |
246 | } |
247 | #else |
248 | static int wlcore_probe_of(struct device *dev, int *irq, int *wakeirq, |
249 | struct wlcore_platdev_data *pdev_data) |
250 | { |
251 | return -ENODATA; |
252 | } |
253 | #endif |
254 | |
255 | static int wl1271_probe(struct sdio_func *func, |
256 | const struct sdio_device_id *id) |
257 | { |
258 | struct wlcore_platdev_data *pdev_data; |
259 | struct wl12xx_sdio_glue *glue; |
260 | struct resource res[2]; |
261 | mmc_pm_flag_t mmcflags; |
262 | int ret = -ENOMEM; |
263 | int irq, wakeirq, num_irqs; |
264 | const char *chip_family; |
265 | |
266 | /* We are only able to handle the wlan function */ |
267 | if (func->num != 0x02) |
268 | return -ENODEV; |
269 | |
270 | pdev_data = devm_kzalloc(dev: &func->dev, size: sizeof(*pdev_data), GFP_KERNEL); |
271 | if (!pdev_data) |
272 | return -ENOMEM; |
273 | |
274 | pdev_data->if_ops = &sdio_ops; |
275 | |
276 | glue = devm_kzalloc(dev: &func->dev, size: sizeof(*glue), GFP_KERNEL); |
277 | if (!glue) |
278 | return -ENOMEM; |
279 | |
280 | glue->dev = &func->dev; |
281 | |
282 | /* Grab access to FN0 for ELP reg. */ |
283 | func->card->quirks |= MMC_QUIRK_LENIENT_FN0; |
284 | |
285 | /* Use block mode for transferring over one block size of data */ |
286 | func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE; |
287 | |
288 | ret = wlcore_probe_of(dev: &func->dev, irq: &irq, wakeirq: &wakeirq, pdev_data); |
289 | if (ret) |
290 | goto out; |
291 | |
292 | /* if sdio can keep power while host is suspended, enable wow */ |
293 | mmcflags = sdio_get_host_pm_caps(func); |
294 | dev_dbg(glue->dev, "sdio PM caps = 0x%x\n" , mmcflags); |
295 | |
296 | if (mmcflags & MMC_PM_KEEP_POWER) |
297 | pdev_data->pwr_in_suspend = true; |
298 | |
299 | sdio_set_drvdata(func, glue); |
300 | |
301 | /* Tell PM core that we don't need the card to be powered now */ |
302 | pm_runtime_put_noidle(dev: &func->dev); |
303 | |
304 | /* |
305 | * Due to a hardware bug, we can't differentiate wl18xx from |
306 | * wl12xx, because both report the same device ID. The only |
307 | * way to differentiate is by checking the SDIO revision, |
308 | * which is 3.00 on the wl18xx chips. |
309 | */ |
310 | if (func->card->cccr.sdio_vsn == SDIO_SDIO_REV_3_00) |
311 | chip_family = "wl18xx" ; |
312 | else |
313 | chip_family = "wl12xx" ; |
314 | |
315 | glue->core = platform_device_alloc(name: chip_family, PLATFORM_DEVID_AUTO); |
316 | if (!glue->core) { |
317 | dev_err(glue->dev, "can't allocate platform_device" ); |
318 | ret = -ENOMEM; |
319 | goto out; |
320 | } |
321 | |
322 | glue->core->dev.parent = &func->dev; |
323 | |
324 | memset(res, 0x00, sizeof(res)); |
325 | |
326 | res[0].start = irq; |
327 | res[0].flags = IORESOURCE_IRQ | |
328 | irqd_get_trigger_type(d: irq_get_irq_data(irq)); |
329 | res[0].name = "irq" ; |
330 | |
331 | |
332 | if (wakeirq > 0) { |
333 | res[1].start = wakeirq; |
334 | res[1].flags = IORESOURCE_IRQ | |
335 | irqd_get_trigger_type(d: irq_get_irq_data(irq: wakeirq)); |
336 | res[1].name = "wakeirq" ; |
337 | num_irqs = 2; |
338 | } else { |
339 | num_irqs = 1; |
340 | } |
341 | ret = platform_device_add_resources(pdev: glue->core, res, num: num_irqs); |
342 | if (ret) { |
343 | dev_err(glue->dev, "can't add resources\n" ); |
344 | goto out_dev_put; |
345 | } |
346 | |
347 | ret = platform_device_add_data(pdev: glue->core, data: pdev_data, |
348 | size: sizeof(*pdev_data)); |
349 | if (ret) { |
350 | dev_err(glue->dev, "can't add platform data\n" ); |
351 | goto out_dev_put; |
352 | } |
353 | |
354 | ret = platform_device_add(pdev: glue->core); |
355 | if (ret) { |
356 | dev_err(glue->dev, "can't add platform device\n" ); |
357 | goto out_dev_put; |
358 | } |
359 | return 0; |
360 | |
361 | out_dev_put: |
362 | platform_device_put(pdev: glue->core); |
363 | |
364 | out: |
365 | return ret; |
366 | } |
367 | |
368 | static void wl1271_remove(struct sdio_func *func) |
369 | { |
370 | struct wl12xx_sdio_glue *glue = sdio_get_drvdata(func); |
371 | |
372 | /* Undo decrement done above in wl1271_probe */ |
373 | pm_runtime_get_noresume(dev: &func->dev); |
374 | |
375 | platform_device_unregister(glue->core); |
376 | } |
377 | |
378 | #ifdef CONFIG_PM |
379 | static int wl1271_suspend(struct device *dev) |
380 | { |
381 | /* Tell MMC/SDIO core it's OK to power down the card |
382 | * (if it isn't already), but not to remove it completely */ |
383 | struct sdio_func *func = dev_to_sdio_func(dev); |
384 | struct wl12xx_sdio_glue *glue = sdio_get_drvdata(func); |
385 | struct wl1271 *wl = platform_get_drvdata(pdev: glue->core); |
386 | mmc_pm_flag_t sdio_flags; |
387 | int ret = 0; |
388 | |
389 | if (!wl) { |
390 | dev_err(dev, "no wilink module was probed\n" ); |
391 | goto out; |
392 | } |
393 | |
394 | dev_dbg(dev, "wl1271 suspend. wow_enabled: %d\n" , |
395 | wl->wow_enabled); |
396 | |
397 | /* check whether sdio should keep power */ |
398 | if (wl->wow_enabled) { |
399 | sdio_flags = sdio_get_host_pm_caps(func); |
400 | |
401 | if (!(sdio_flags & MMC_PM_KEEP_POWER)) { |
402 | dev_err(dev, "can't keep power while host " |
403 | "is suspended\n" ); |
404 | ret = -EINVAL; |
405 | goto out; |
406 | } |
407 | |
408 | /* keep power while host suspended */ |
409 | ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); |
410 | if (ret) { |
411 | dev_err(dev, "error while trying to keep power\n" ); |
412 | goto out; |
413 | } |
414 | } |
415 | out: |
416 | return ret; |
417 | } |
418 | |
419 | static int wl1271_resume(struct device *dev) |
420 | { |
421 | dev_dbg(dev, "wl1271 resume\n" ); |
422 | |
423 | return 0; |
424 | } |
425 | |
426 | static const struct dev_pm_ops wl1271_sdio_pm_ops = { |
427 | .suspend = wl1271_suspend, |
428 | .resume = wl1271_resume, |
429 | }; |
430 | #endif |
431 | |
432 | static struct sdio_driver wl1271_sdio_driver = { |
433 | .name = "wl1271_sdio" , |
434 | .id_table = wl1271_devices, |
435 | .probe = wl1271_probe, |
436 | .remove = wl1271_remove, |
437 | #ifdef CONFIG_PM |
438 | .drv = { |
439 | .pm = &wl1271_sdio_pm_ops, |
440 | }, |
441 | #endif |
442 | }; |
443 | |
444 | module_sdio_driver(wl1271_sdio_driver); |
445 | |
446 | module_param(dump, bool, 0600); |
447 | MODULE_PARM_DESC(dump, "Enable sdio read/write dumps." ); |
448 | |
449 | MODULE_DESCRIPTION("TI WLAN SDIO helpers" ); |
450 | MODULE_LICENSE("GPL" ); |
451 | MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>" ); |
452 | MODULE_AUTHOR("Juuso Oikarinen <juuso.oikarinen@nokia.com>" ); |
453 | |