Commit | Line | Data |
---|---|---|
1cac41cb MB |
1 | /* |
2 | * BCMSDH Function Driver for the native SDIO/MMC driver in the Linux Kernel | |
3 | * | |
4 | * Copyright (C) 1999-2019, Broadcom. | |
5 | * | |
6 | * Unless you and Broadcom execute a separate written software license | |
7 | * agreement governing use of this software, this software is licensed to you | |
8 | * under the terms of the GNU General Public License version 2 (the "GPL"), | |
9 | * available at http://www.broadcom.com/licenses/GPLv2.php, with the | |
10 | * following added to such license: | |
11 | * | |
12 | * As a special exception, the copyright holders of this software give you | |
13 | * permission to link this software with independent modules, and to copy and | |
14 | * distribute the resulting executable under terms of your choice, provided that | |
15 | * you also meet, for each linked independent module, the terms and conditions of | |
16 | * the license of that module. An independent module is a module which is not | |
17 | * derived from this software. The special exception does not apply to any | |
18 | * modifications of the software. | |
19 | * | |
20 | * Notwithstanding the above, under no circumstances may you combine this | |
21 | * software in any way with any other Broadcom software provided under a license | |
22 | * other than the GPL, without Broadcom's express prior written consent. | |
23 | * | |
24 | * | |
25 | * <<Broadcom-WL-IPTag/Proprietary,Open:>> | |
26 | * | |
5a068558 | 27 | * $Id: bcmsdh_sdmmc_linux.c 801673 2019-01-29 04:29:43Z $ |
1cac41cb MB |
28 | */ |
29 | ||
30 | #include <typedefs.h> | |
31 | #include <bcmutils.h> | |
32 | #include <sdio.h> /* SDIO Device and Protocol Specs */ | |
33 | #include <bcmsdbus.h> /* bcmsdh to/from specific controller APIs */ | |
34 | #include <sdiovar.h> /* to get msglevel bit values */ | |
35 | ||
36 | #include <linux/sched.h> /* request_irq() */ | |
37 | ||
38 | #include <linux/mmc/core.h> | |
39 | #include <linux/mmc/card.h> | |
40 | #include <linux/mmc/host.h> | |
41 | #include <linux/mmc/sdio_func.h> | |
42 | #include <linux/mmc/sdio_ids.h> | |
43 | #include <dhd_linux.h> | |
44 | #include <bcmsdh_sdmmc.h> | |
45 | #include <dhd_dbg.h> | |
46 | ||
47 | #if !defined(SDIO_VENDOR_ID_BROADCOM) | |
48 | #define SDIO_VENDOR_ID_BROADCOM 0x02d0 | |
49 | #endif /* !defined(SDIO_VENDOR_ID_BROADCOM) */ | |
50 | ||
51 | #define SDIO_DEVICE_ID_BROADCOM_DEFAULT 0x0000 | |
52 | ||
53 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) | |
54 | #define SDIO_DEVICE_ID_BROADCOM_4325_SDGWB 0x0492 /* BCM94325SDGWB */ | |
55 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) */ | |
56 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4325) | |
57 | #define SDIO_DEVICE_ID_BROADCOM_4325 0x0493 | |
58 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325) */ | |
59 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4329) | |
60 | #define SDIO_DEVICE_ID_BROADCOM_4329 0x4329 | |
61 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4329) */ | |
62 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4319) | |
63 | #define SDIO_DEVICE_ID_BROADCOM_4319 0x4319 | |
64 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4319) */ | |
65 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4330) | |
66 | #define SDIO_DEVICE_ID_BROADCOM_4330 0x4330 | |
67 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4330) */ | |
68 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4334) | |
69 | #define SDIO_DEVICE_ID_BROADCOM_4334 0x4334 | |
70 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4334) */ | |
71 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4324) | |
72 | #define SDIO_DEVICE_ID_BROADCOM_4324 0x4324 | |
73 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4324) */ | |
74 | #if !defined(SDIO_DEVICE_ID_BROADCOM_43239) | |
75 | #define SDIO_DEVICE_ID_BROADCOM_43239 43239 | |
76 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_43239) */ | |
77 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4345) | |
78 | #define SDIO_DEVICE_ID_BROADCOM_4345 0x4345 | |
79 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4345) */ | |
80 | ||
81 | #if !defined(SDIO_DEVICE_ID_BROADCOM_4362) | |
82 | #define SDIO_DEVICE_ID_BROADCOM_4362 0x4362 | |
5a068558 MB |
83 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4362) */ |
84 | #if !defined(SDIO_DEVICE_ID_BROADCOM_43430) | |
85 | #define SDIO_DEVICE_ID_BROADCOM_43430 43430 | |
86 | #endif /* !defined(SDIO_DEVICE_ID_BROADCOM_43430) */ | |
1cac41cb MB |
87 | |
88 | extern void wl_cfg80211_set_parent_dev(void *dev); | |
89 | extern void sdioh_sdmmc_devintr_off(sdioh_info_t *sd); | |
90 | extern void sdioh_sdmmc_devintr_on(sdioh_info_t *sd); | |
91 | extern void* bcmsdh_probe(osl_t *osh, void *dev, void *sdioh, void *adapter_info, uint bus_type, | |
92 | uint bus_num, uint slot_num); | |
93 | extern int bcmsdh_remove(bcmsdh_info_t *bcmsdh); | |
94 | ||
95 | int sdio_function_init(void); | |
96 | void sdio_function_cleanup(void); | |
97 | ||
98 | #define DESCRIPTION "bcmsdh_sdmmc Driver" | |
99 | #define AUTHOR "Broadcom Corporation" | |
100 | ||
101 | /* module param defaults */ | |
102 | static int clockoverride = 0; | |
103 | ||
104 | module_param(clockoverride, int, 0644); | |
105 | MODULE_PARM_DESC(clockoverride, "SDIO card clock override"); | |
106 | ||
107 | /* Maximum number of bcmsdh_sdmmc devices supported by driver */ | |
108 | #define BCMSDH_SDMMC_MAX_DEVICES 1 | |
109 | ||
110 | extern volatile bool dhd_mmc_suspend; | |
111 | ||
112 | static int sdioh_probe(struct sdio_func *func) | |
113 | { | |
114 | int host_idx = func->card->host->index; | |
115 | uint32 rca = func->card->rca; | |
116 | wifi_adapter_info_t *adapter; | |
117 | osl_t *osh = NULL; | |
118 | sdioh_info_t *sdioh = NULL; | |
119 | ||
120 | sd_err(("bus num (host idx)=%d, slot num (rca)=%d\n", host_idx, rca)); | |
121 | adapter = dhd_wifi_platform_get_adapter(SDIO_BUS, host_idx, rca); | |
122 | if (adapter != NULL) | |
123 | sd_err(("found adapter info '%s'\n", adapter->name)); | |
124 | else | |
125 | sd_err(("can't find adapter info for this chip\n")); | |
126 | ||
127 | #ifdef WL_CFG80211 | |
128 | wl_cfg80211_set_parent_dev(&func->dev); | |
129 | #endif // endif | |
130 | ||
131 | /* allocate SDIO Host Controller state info */ | |
132 | osh = osl_attach(&func->dev, SDIO_BUS, TRUE); | |
133 | if (osh == NULL) { | |
134 | sd_err(("%s: osl_attach failed\n", __FUNCTION__)); | |
135 | goto fail; | |
136 | } | |
137 | osl_static_mem_init(osh, adapter); | |
138 | sdioh = sdioh_attach(osh, func); | |
139 | if (sdioh == NULL) { | |
140 | sd_err(("%s: sdioh_attach failed\n", __FUNCTION__)); | |
141 | goto fail; | |
142 | } | |
143 | sdioh->bcmsdh = bcmsdh_probe(osh, &func->dev, sdioh, adapter, SDIO_BUS, host_idx, rca); | |
144 | if (sdioh->bcmsdh == NULL) { | |
145 | sd_err(("%s: bcmsdh_probe failed\n", __FUNCTION__)); | |
146 | goto fail; | |
147 | } | |
148 | ||
149 | sdio_set_drvdata(func, sdioh); | |
150 | return 0; | |
151 | ||
152 | fail: | |
153 | if (sdioh != NULL) | |
154 | sdioh_detach(osh, sdioh); | |
155 | if (osh != NULL) | |
156 | osl_detach(osh); | |
157 | return -ENOMEM; | |
158 | } | |
159 | ||
160 | static void sdioh_remove(struct sdio_func *func) | |
161 | { | |
162 | sdioh_info_t *sdioh; | |
163 | osl_t *osh; | |
164 | ||
165 | sdioh = sdio_get_drvdata(func); | |
166 | if (sdioh == NULL) { | |
167 | sd_err(("%s: error, no sdioh handler found\n", __FUNCTION__)); | |
168 | return; | |
169 | } | |
170 | ||
171 | osh = sdioh->osh; | |
172 | bcmsdh_remove(sdioh->bcmsdh); | |
173 | sdioh_detach(osh, sdioh); | |
174 | osl_detach(osh); | |
175 | } | |
176 | ||
177 | static int bcmsdh_sdmmc_probe(struct sdio_func *func, | |
178 | const struct sdio_device_id *id) | |
179 | { | |
180 | int ret = 0; | |
181 | ||
182 | if (func == NULL) | |
183 | return -EINVAL; | |
184 | ||
185 | sd_err(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); | |
186 | sd_info(("sdio_bcmsdh: func->class=%x\n", func->class)); | |
187 | sd_info(("sdio_vendor: 0x%04x\n", func->vendor)); | |
188 | sd_info(("sdio_device: 0x%04x\n", func->device)); | |
189 | sd_info(("Function#: 0x%04x\n", func->num)); | |
190 | ||
191 | /* 4318 doesn't have function 2 */ | |
192 | if ((func->num == 2) || (func->num == 1 && func->device == 0x4)) | |
193 | ret = sdioh_probe(func); | |
194 | ||
195 | return ret; | |
196 | } | |
197 | ||
198 | static void bcmsdh_sdmmc_remove(struct sdio_func *func) | |
199 | { | |
200 | if (func == NULL) { | |
201 | sd_err(("%s is called with NULL SDIO function pointer\n", __FUNCTION__)); | |
202 | return; | |
203 | } | |
204 | ||
205 | sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__)); | |
206 | sd_info(("sdio_bcmsdh: func->class=%x\n", func->class)); | |
207 | sd_info(("sdio_vendor: 0x%04x\n", func->vendor)); | |
208 | sd_info(("sdio_device: 0x%04x\n", func->device)); | |
209 | sd_info(("Function#: 0x%04x\n", func->num)); | |
210 | ||
211 | if ((func->num == 2) || (func->num == 1 && func->device == 0x4)) | |
212 | sdioh_remove(func); | |
213 | } | |
214 | ||
215 | /* devices we support, null terminated */ | |
216 | static const struct sdio_device_id bcmsdh_sdmmc_ids[] = { | |
217 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT) }, | |
218 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT) }, | |
219 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) }, | |
220 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325) }, | |
221 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4329) }, | |
222 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4319) }, | |
223 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4330) }, | |
224 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4334) }, | |
225 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4324) }, | |
226 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43239) }, | |
227 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4345) }, | |
5a068558 | 228 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43430) }, |
1cac41cb MB |
229 | { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4362) }, |
230 | { SDIO_DEVICE_CLASS(SDIO_CLASS_NONE) }, | |
231 | { 0, 0, 0, 0 /* end: all zeroes */ | |
232 | }, | |
233 | }; | |
234 | ||
235 | MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids); | |
236 | ||
237 | #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) | |
238 | static int bcmsdh_sdmmc_suspend(struct device *pdev) | |
239 | { | |
240 | int err; | |
241 | sdioh_info_t *sdioh; | |
242 | struct sdio_func *func = dev_to_sdio_func(pdev); | |
243 | mmc_pm_flag_t sdio_flags; | |
244 | ||
245 | sd_err(("%s Enter\n", __FUNCTION__)); | |
246 | if (func->num != 2) | |
247 | return 0; | |
248 | ||
249 | dhd_mmc_suspend = TRUE; | |
250 | sdioh = sdio_get_drvdata(func); | |
251 | err = bcmsdh_suspend(sdioh->bcmsdh); | |
252 | if (err) { | |
253 | dhd_mmc_suspend = FALSE; | |
254 | return err; | |
255 | } | |
256 | ||
257 | sdio_flags = sdio_get_host_pm_caps(func); | |
258 | if (!(sdio_flags & MMC_PM_KEEP_POWER)) { | |
259 | sd_err(("%s: can't keep power while host is suspended\n", __FUNCTION__)); | |
260 | dhd_mmc_suspend = FALSE; | |
261 | return -EINVAL; | |
262 | } | |
263 | ||
264 | /* keep power while host suspended */ | |
265 | err = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); | |
266 | if (err) { | |
267 | sd_err(("%s: error while trying to keep power\n", __FUNCTION__)); | |
268 | dhd_mmc_suspend = FALSE; | |
269 | return err; | |
270 | } | |
271 | smp_mb(); | |
272 | ||
273 | return 0; | |
274 | } | |
275 | ||
276 | static int bcmsdh_sdmmc_resume(struct device *pdev) | |
277 | { | |
278 | sdioh_info_t *sdioh; | |
279 | struct sdio_func *func = dev_to_sdio_func(pdev); | |
280 | ||
281 | sd_err(("%s Enter\n", __FUNCTION__)); | |
282 | if (func->num != 2) | |
283 | return 0; | |
284 | ||
285 | sdioh = sdio_get_drvdata(func); | |
286 | dhd_mmc_suspend = FALSE; | |
287 | bcmsdh_resume(sdioh->bcmsdh); | |
288 | ||
289 | smp_mb(); | |
290 | return 0; | |
291 | } | |
292 | ||
293 | static const struct dev_pm_ops bcmsdh_sdmmc_pm_ops = { | |
294 | .suspend = bcmsdh_sdmmc_suspend, | |
295 | .resume = bcmsdh_sdmmc_resume, | |
296 | }; | |
297 | #endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */ | |
298 | ||
299 | #if defined(BCMLXSDMMC) | |
300 | static struct semaphore *notify_semaphore = NULL; | |
301 | ||
302 | static int dummy_probe(struct sdio_func *func, | |
303 | const struct sdio_device_id *id) | |
304 | { | |
305 | if (func) | |
306 | sd_info(("%s: func->num=0x%x; \n", __FUNCTION__, func->num)); | |
307 | if (id) { | |
308 | sd_info(("%s: class=0x%x; vendor=0x%x; device=0x%x\n", __FUNCTION__, | |
309 | id->class, id->vendor, id->device)); | |
310 | if (id->vendor != SDIO_VENDOR_ID_BROADCOM) | |
311 | return -ENODEV; | |
312 | } | |
313 | if (func && (func->num != 2)) { | |
314 | return 0; | |
315 | } | |
316 | ||
317 | if (notify_semaphore) | |
318 | up(notify_semaphore); | |
319 | return 0; | |
320 | } | |
321 | ||
322 | static void dummy_remove(struct sdio_func *func) | |
323 | { | |
324 | } | |
325 | ||
326 | static struct sdio_driver dummy_sdmmc_driver = { | |
327 | .probe = dummy_probe, | |
328 | .remove = dummy_remove, | |
329 | .name = "dummy_sdmmc", | |
330 | .id_table = bcmsdh_sdmmc_ids, | |
331 | }; | |
332 | ||
333 | int sdio_func_reg_notify(void* semaphore) | |
334 | { | |
335 | notify_semaphore = semaphore; | |
336 | return sdio_register_driver(&dummy_sdmmc_driver); | |
337 | } | |
338 | ||
339 | void sdio_func_unreg_notify(void) | |
340 | { | |
341 | OSL_SLEEP(15); | |
342 | sdio_unregister_driver(&dummy_sdmmc_driver); | |
343 | } | |
344 | ||
345 | #endif /* defined(BCMLXSDMMC) */ | |
346 | ||
347 | static struct sdio_driver bcmsdh_sdmmc_driver = { | |
348 | .probe = bcmsdh_sdmmc_probe, | |
349 | .remove = bcmsdh_sdmmc_remove, | |
350 | .name = "bcmsdh_sdmmc", | |
351 | .id_table = bcmsdh_sdmmc_ids, | |
352 | #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) | |
353 | .drv = { | |
354 | .pm = &bcmsdh_sdmmc_pm_ops, | |
355 | }, | |
356 | #endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */ | |
357 | }; | |
358 | ||
359 | struct sdos_info { | |
360 | sdioh_info_t *sd; | |
361 | spinlock_t lock; | |
362 | }; | |
363 | ||
364 | /* Interrupt enable/disable */ | |
365 | SDIOH_API_RC | |
366 | sdioh_interrupt_set(sdioh_info_t *sd, bool enable) | |
367 | { | |
368 | if (!sd) | |
369 | return BCME_BADARG; | |
370 | ||
371 | sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling")); | |
372 | return SDIOH_API_RC_SUCCESS; | |
373 | } | |
374 | ||
375 | #ifdef BCMSDH_MODULE | |
376 | static int __init | |
377 | bcmsdh_module_init(void) | |
378 | { | |
379 | int error = 0; | |
380 | error = sdio_function_init(); | |
381 | return error; | |
382 | } | |
383 | ||
384 | static void __exit | |
385 | bcmsdh_module_cleanup(void) | |
386 | { | |
387 | sdio_function_cleanup(); | |
388 | } | |
389 | ||
390 | module_init(bcmsdh_module_init); | |
391 | module_exit(bcmsdh_module_cleanup); | |
392 | ||
393 | MODULE_LICENSE("GPL v2"); | |
394 | MODULE_DESCRIPTION(DESCRIPTION); | |
395 | MODULE_AUTHOR(AUTHOR); | |
396 | ||
397 | #endif /* BCMSDH_MODULE */ | |
398 | /* | |
399 | * module init | |
400 | */ | |
401 | int bcmsdh_register_client_driver(void) | |
402 | { | |
403 | return sdio_register_driver(&bcmsdh_sdmmc_driver); | |
404 | } | |
405 | ||
406 | /* | |
407 | * module cleanup | |
408 | */ | |
409 | void bcmsdh_unregister_client_driver(void) | |
410 | { | |
411 | sdio_unregister_driver(&bcmsdh_sdmmc_driver); | |
412 | } |