1 /*
2 * BCMSDH Function Driver for the native SDIO/MMC driver in the Linux Kernel
3 *
4 * Copyright (C) 2020, 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 *
21 * <<Broadcom-WL-IPTag/Open:>>
22 *
23 * $Id$
24 */
25
26 #include <typedefs.h>
27 #include <bcmutils.h>
28 #include <sdio.h> /* SDIO Device and Protocol Specs */
29 #include <bcmsdbus.h> /* bcmsdh to/from specific controller APIs */
30 #include <sdiovar.h> /* to get msglevel bit values */
31
32 #include <linux/sched.h> /* request_irq() */
33
34 #include <linux/mmc/core.h>
35 #include <linux/mmc/card.h>
36 #include <linux/mmc/host.h>
37 #include <linux/mmc/sdio_func.h>
38 #include <linux/mmc/sdio_ids.h>
39 #include <dhd_linux.h>
40 #include <bcmsdh_sdmmc.h>
41 #include <dhd_dbg.h>
42 #include <bcmdevs.h>
43
44 #if !defined(SDIO_VENDOR_ID_BROADCOM)
45 #define SDIO_VENDOR_ID_BROADCOM 0x02d0
46 #endif /* !defined(SDIO_VENDOR_ID_BROADCOM) */
47
48 #define SDIO_DEVICE_ID_BROADCOM_DEFAULT 0x0000
49
50 extern void wl_cfg80211_set_parent_dev(void *dev);
51 extern void sdioh_sdmmc_devintr_off(sdioh_info_t *sd);
52 extern void sdioh_sdmmc_devintr_on(sdioh_info_t *sd);
53 extern void* bcmsdh_probe(osl_t *osh, void *dev, void *sdioh, void *adapter_info, uint bus_type,
54 uint bus_num, uint slot_num);
55 extern int bcmsdh_remove(bcmsdh_info_t *bcmsdh);
56
57 int sdio_function_init(void);
58 void sdio_function_cleanup(void);
59
60 #define DESCRIPTION "bcmsdh_sdmmc Driver"
61 #define AUTHOR "Broadcom Corporation"
62
63 /* module param defaults */
64 static int clockoverride = 0;
65
66 module_param(clockoverride, int, 0644);
67 MODULE_PARM_DESC(clockoverride, "SDIO card clock override");
68
69 #ifdef GLOBAL_SDMMC_INSTANCE
70 PBCMSDH_SDMMC_INSTANCE gInstance;
71 #endif
72
73 /* Maximum number of bcmsdh_sdmmc devices supported by driver */
74 #define BCMSDH_SDMMC_MAX_DEVICES 1
75
76 extern volatile bool dhd_mmc_suspend;
77
sdioh_probe(struct sdio_func * func)78 static int sdioh_probe(struct sdio_func *func)
79 {
80 int host_idx = func->card->host->index;
81 uint32 rca = func->card->rca;
82 wifi_adapter_info_t *adapter;
83 osl_t *osh = NULL;
84 sdioh_info_t *sdioh = NULL;
85
86 sd_err(("bus num (host idx)=%d, slot num (rca)=%d\n", host_idx, rca));
87 adapter = dhd_wifi_platform_get_adapter(SDIO_BUS, host_idx, rca);
88 if (adapter != NULL) {
89 sd_err(("found adapter info '%s'\n", adapter->name));
90 adapter->bus_type = SDIO_BUS;
91 adapter->bus_num = host_idx;
92 adapter->slot_num = rca;
93 adapter->sdio_func = func;
94 } else {
95 sd_err(("can't find adapter info for this chip\n"));
96 #ifdef ADAPTER_IDX
97 goto fail;
98 #endif
99 }
100
101 #ifdef WL_CFG80211
102 wl_cfg80211_set_parent_dev(&func->dev);
103 #endif
104
105 /* allocate SDIO Host Controller state info */
106 osh = osl_attach(&func->dev, SDIO_BUS, TRUE);
107 if (osh == NULL) {
108 sd_err(("%s: osl_attach failed\n", __FUNCTION__));
109 goto fail;
110 }
111 osl_static_mem_init(osh, adapter);
112 sdioh = sdioh_attach(osh, func);
113 if (sdioh == NULL) {
114 sd_err(("%s: sdioh_attach failed\n", __FUNCTION__));
115 goto fail;
116 }
117 sdioh->bcmsdh = bcmsdh_probe(osh, &func->dev, sdioh, adapter, SDIO_BUS, host_idx, rca);
118 if (sdioh->bcmsdh == NULL) {
119 sd_err(("%s: bcmsdh_probe failed\n", __FUNCTION__));
120 goto fail;
121 }
122
123 sdio_set_drvdata(func, sdioh);
124 return 0;
125
126 fail:
127 if (sdioh != NULL)
128 sdioh_detach(osh, sdioh);
129 if (osh != NULL)
130 osl_detach(osh);
131 return -ENOMEM;
132 }
133
sdioh_remove(struct sdio_func * func)134 static void sdioh_remove(struct sdio_func *func)
135 {
136 sdioh_info_t *sdioh;
137 osl_t *osh;
138
139 sdioh = sdio_get_drvdata(func);
140 if (sdioh == NULL) {
141 sd_err(("%s: error, no sdioh handler found\n", __FUNCTION__));
142 return;
143 }
144 sd_err(("%s: Enter\n", __FUNCTION__));
145
146 osh = sdioh->osh;
147 bcmsdh_remove(sdioh->bcmsdh);
148 sdioh_detach(osh, sdioh);
149 osl_detach(osh);
150 }
151
bcmsdh_sdmmc_probe(struct sdio_func * func,const struct sdio_device_id * id)152 static int bcmsdh_sdmmc_probe(struct sdio_func *func,
153 const struct sdio_device_id *id)
154 {
155 int ret = 0;
156
157 if (func == NULL)
158 return -EINVAL;
159
160 sd_err(("%s: Enter num=%d\n", __FUNCTION__, func->num));
161 sd_info(("sdio_bcmsdh: func->class=%x\n", func->class));
162 sd_info(("sdio_vendor: 0x%04x\n", func->vendor));
163 sd_info(("sdio_device: 0x%04x\n", func->device));
164 sd_info(("Function#: 0x%04x\n", func->num));
165
166 #ifdef GLOBAL_SDMMC_INSTANCE
167 gInstance->func[func->num] = func;
168 #endif
169
170 /* 4318 doesn't have function 2 */
171 if ((func->num == 2) || (func->num == 1 && func->device == 0x4))
172 ret = sdioh_probe(func);
173
174 return ret;
175 }
176
bcmsdh_sdmmc_remove(struct sdio_func * func)177 static void bcmsdh_sdmmc_remove(struct sdio_func *func)
178 {
179 if (func == NULL) {
180 sd_err(("%s is called with NULL SDIO function pointer\n", __FUNCTION__));
181 return;
182 }
183
184 sd_trace(("bcmsdh_sdmmc: %s Enter\n", __FUNCTION__));
185 sd_info(("sdio_bcmsdh: func->class=%x\n", func->class));
186 sd_info(("sdio_vendor: 0x%04x\n", func->vendor));
187 sd_info(("sdio_device: 0x%04x\n", func->device));
188 sd_info(("Function#: 0x%04x\n", func->num));
189
190 if ((func->num == 2) || (func->num == 1 && func->device == 0x4))
191 sdioh_remove(func);
192 }
193
194 /* devices we support, null terminated */
195 static const struct sdio_device_id bcmsdh_sdmmc_ids[] = {
196 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT) },
197 /* XXX This should not be in the external release, as it will attach to any SDIO
198 * device, even non-WLAN devices.
199 * Need to add IDs for the FALCON-based chips and put this under BCMINTERNAL
200 { SDIO_DEVICE_CLASS(SDIO_CLASS_NONE) },
201 */
202 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM4362_CHIP_ID) },
203 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43751_CHIP_ID) },
204 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43752_CHIP_ID) },
205 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43012_CHIP_ID) },
206 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_CHIP_ID) },
207 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N_ID) },
208 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N2G_ID) },
209 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43014_D11N5G_ID) },
210 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43013_CHIP_ID) },
211 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43013_D11N_ID) },
212 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43013_D11N2G_ID) },
213 { SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, BCM43013_D11N5G_ID) },
214 { SDIO_DEVICE_CLASS(SDIO_CLASS_NONE) },
215 { 0, 0, 0, 0 /* end: all zeroes */
216 },
217 };
218
219 MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids);
220
221 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM)
bcmsdh_sdmmc_suspend(struct device * pdev)222 static int bcmsdh_sdmmc_suspend(struct device *pdev)
223 {
224 int err;
225 sdioh_info_t *sdioh;
226 struct sdio_func *func = dev_to_sdio_func(pdev);
227 mmc_pm_flag_t sdio_flags;
228
229 printf("%s Enter func->num=%d\n", __FUNCTION__, func->num);
230 if (func->num != 2)
231 return 0;
232
233 dhd_mmc_suspend = TRUE;
234 sdioh = sdio_get_drvdata(func);
235 err = bcmsdh_suspend(sdioh->bcmsdh);
236 if (err) {
237 printf("%s bcmsdh_suspend err=%d\n", __FUNCTION__, err);
238 dhd_mmc_suspend = FALSE;
239 return err;
240 }
241
242 sdio_flags = sdio_get_host_pm_caps(func);
243 if (!(sdio_flags & MMC_PM_KEEP_POWER)) {
244 sd_err(("%s: can't keep power while host is suspended\n", __FUNCTION__));
245 dhd_mmc_suspend = FALSE;
246 return -EINVAL;
247 }
248
249 /* keep power while host suspended */
250 err = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
251 if (err) {
252 sd_err(("%s: error while trying to keep power\n", __FUNCTION__));
253 dhd_mmc_suspend = FALSE;
254 return err;
255 }
256 smp_mb();
257
258 printf("%s Exit\n", __FUNCTION__);
259 return 0;
260 }
261
bcmsdh_sdmmc_resume(struct device * pdev)262 static int bcmsdh_sdmmc_resume(struct device *pdev)
263 {
264 sdioh_info_t *sdioh;
265 struct sdio_func *func = dev_to_sdio_func(pdev);
266
267 printf("%s Enter func->num=%d\n", __FUNCTION__, func->num);
268 if (func->num != 2)
269 return 0;
270
271 dhd_mmc_suspend = FALSE;
272 sdioh = sdio_get_drvdata(func);
273 bcmsdh_resume(sdioh->bcmsdh);
274
275 smp_mb();
276 printf("%s Exit\n", __FUNCTION__);
277 return 0;
278 }
279
280 static const struct dev_pm_ops bcmsdh_sdmmc_pm_ops = {
281 .suspend = bcmsdh_sdmmc_suspend,
282 .resume = bcmsdh_sdmmc_resume,
283 };
284 #endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */
285
286 #if defined(BCMLXSDMMC)
287 static struct semaphore *notify_semaphore = NULL;
288
dummy_probe(struct sdio_func * func,const struct sdio_device_id * id)289 static int dummy_probe(struct sdio_func *func,
290 const struct sdio_device_id *id)
291 {
292 sd_err(("%s: enter\n", __FUNCTION__));
293 if (func && (func->num != 2)) {
294 return 0;
295 }
296
297 if (notify_semaphore)
298 up(notify_semaphore);
299 return 0;
300 }
301
dummy_remove(struct sdio_func * func)302 static void dummy_remove(struct sdio_func *func)
303 {
304 }
305
306 static struct sdio_driver dummy_sdmmc_driver = {
307 .probe = dummy_probe,
308 .remove = dummy_remove,
309 .name = "dummy_sdmmc",
310 .id_table = bcmsdh_sdmmc_ids,
311 };
312
sdio_func_reg_notify(void * semaphore)313 int sdio_func_reg_notify(void* semaphore)
314 {
315 notify_semaphore = semaphore;
316 return sdio_register_driver(&dummy_sdmmc_driver);
317 }
318
sdio_func_unreg_notify(void)319 void sdio_func_unreg_notify(void)
320 {
321 OSL_SLEEP(15);
322 sdio_unregister_driver(&dummy_sdmmc_driver);
323 }
324
325 #endif /* defined(BCMLXSDMMC) */
326
327 static struct sdio_driver bcmsdh_sdmmc_driver = {
328 .probe = bcmsdh_sdmmc_probe,
329 .remove = bcmsdh_sdmmc_remove,
330 .name = "bcmsdh_sdmmc",
331 .id_table = bcmsdh_sdmmc_ids,
332 #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM)
333 .drv = {
334 .pm = &bcmsdh_sdmmc_pm_ops,
335 },
336 #endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */
337 };
338
339 struct sdos_info {
340 sdioh_info_t *sd;
341 spinlock_t lock;
342 };
343
344 /* Interrupt enable/disable */
345 SDIOH_API_RC
sdioh_interrupt_set(sdioh_info_t * sd,bool enable)346 sdioh_interrupt_set(sdioh_info_t *sd, bool enable)
347 {
348 if (!sd)
349 return BCME_BADARG;
350
351 sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling"));
352 return SDIOH_API_RC_SUCCESS;
353 }
354
355 #ifdef BCMSDH_MODULE
356 static int __init
bcmsdh_module_init(void)357 bcmsdh_module_init(void)
358 {
359 int error = 0;
360 error = sdio_function_init();
361 return error;
362 }
363
364 static void __exit
bcmsdh_module_cleanup(void)365 bcmsdh_module_cleanup(void)
366 {
367 sdio_function_cleanup();
368 }
369
370 module_init(bcmsdh_module_init);
371 module_exit(bcmsdh_module_cleanup);
372
373 MODULE_LICENSE("GPL v2");
374 MODULE_DESCRIPTION(DESCRIPTION);
375 MODULE_AUTHOR(AUTHOR);
376
377 #endif /* BCMSDH_MODULE */
378 /*
379 * module init
380 */
bcmsdh_register_client_driver(void)381 int bcmsdh_register_client_driver(void)
382 {
383 return sdio_register_driver(&bcmsdh_sdmmc_driver);
384 }
385
386 /*
387 * module cleanup
388 */
bcmsdh_unregister_client_driver(void)389 void bcmsdh_unregister_client_driver(void)
390 {
391 sdio_unregister_driver(&bcmsdh_sdmmc_driver);
392 }
393