1/*
2 * wl_glue.c: Broadcom WL support module providing a unified SSB/BCMA handling.
3 * Copyright (C) 2011 Jo-Philipp Wich <jow@openwrt.org>
4 */
5
6#include "wl_glue.h"
7
8#include <linux/kernel.h>
9#include <linux/module.h>
10#include <linux/init.h>
11
12#ifdef CONFIG_BCM47XX
13#include <bcm47xx.h>
14#endif
15
16#ifdef CONFIG_SSB
17#include <linux/ssb/ssb.h>
18#endif
19
20#ifdef CONFIG_BCMA
21#include <linux/bcma/bcma.h>
22#endif
23
24MODULE_AUTHOR("Jo-Philipp Wich (jow@openwrt.org)");
25MODULE_DESCRIPTION("Broadcom WL SSB/BCMA compatibility layer");
26MODULE_LICENSE("GPL");
27
28static wl_glue_attach_cb_t attach_cb = NULL;
29static wl_glue_remove_cb_t remove_cb = NULL;
30static enum wl_glue_bus_type active_bus_type = WL_GLUE_BUS_TYPE_UNSPEC;
31static int wl_glue_attached = 0;
32
33
34#ifdef CONFIG_SSB
35static int wl_glue_ssb_probe(struct ssb_device *dev, const struct ssb_device_id *id)
36{
37	void *mmio;
38	void *wldev;
39
40	if (!attach_cb)
41	{
42		pr_err("No attach callback registered\n");
43		return -ENOSYS;
44	}
45
46	if (dev->bus->bustype != SSB_BUSTYPE_SSB)
47	{
48		pr_err("Attaching to SSB behind PCI is not supported. Please remove the b43 ssb bridge\n");
49		return -EINVAL;
50	}
51
52	mmio = (void *) 0x18000000 + dev->core_index * 0x1000;
53	wldev = attach_cb(id->vendor, id->coreid, (ulong)mmio, dev, dev->irq);
54
55	if (!wldev)
56	{
57		pr_err("The attach callback failed, SSB probe aborted\n");
58		return -ENODEV;
59	}
60
61	ssb_set_drvdata(dev, wldev);
62	return 0;
63}
64
65static void wl_glue_ssb_remove(struct ssb_device *dev)
66{
67	void *wldev = ssb_get_drvdata(dev);
68
69	if (remove_cb)
70		remove_cb(wldev);
71
72	ssb_set_drvdata(dev, NULL);
73}
74
75static const struct ssb_device_id wl_glue_ssb_tbl[] = {
76	SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, SSB_ANY_REV),
77	{},
78};
79
80static struct ssb_driver wl_glue_ssb_driver = {
81	.name     = KBUILD_MODNAME,
82	.id_table = wl_glue_ssb_tbl,
83	.probe    = wl_glue_ssb_probe,
84	.remove   = wl_glue_ssb_remove,
85};
86#endif /* CONFIG_SSB */
87
88#ifdef CONFIG_BCMA
89static int wl_glue_bcma_probe(struct bcma_device *dev)
90{
91	void *wldev;
92
93	if (!attach_cb)
94	{
95		pr_err("No attach callback registered\n");
96		return -ENOSYS;
97	}
98
99	if (dev->bus->hosttype != BCMA_HOSTTYPE_SOC)
100	{
101		pr_err("Unsupported BCMA bus type %d\n", dev->bus->hosttype);
102		return -EINVAL;
103	}
104
105	/*
106	 * NB:
107	 * 0x18000000 = BCMA_ADDR_BASE
108	 * 0x1000     = BCMA_CORE_SIZE
109	 */
110
111	wldev = attach_cb(dev->id.manuf, dev->id.id, (ulong)dev->addr, dev, dev->irq);
112
113	if (!wldev)
114	{
115		pr_err("The attach callback failed, BCMA probe aborted\n");
116		return -ENODEV;
117	}
118
119	bcma_set_drvdata(dev, wldev);
120	return 0;
121}
122
123static void wl_glue_bcma_remove(struct bcma_device *dev)
124{
125	void *wldev = bcma_get_drvdata(dev);
126
127	if (remove_cb)
128		remove_cb(wldev);
129
130	bcma_set_drvdata(dev, NULL);
131}
132
133static const struct bcma_device_id wl_glue_bcma_tbl[] = {
134	BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, BCMA_ANY_REV, BCMA_ANY_CLASS),
135	{},
136};
137
138static struct bcma_driver wl_glue_bcma_driver = {
139	.name     = KBUILD_MODNAME,
140	.id_table = wl_glue_bcma_tbl,
141	.probe    = wl_glue_bcma_probe,
142	.remove   = wl_glue_bcma_remove,
143};
144#endif /* CONFIG_BCMA */
145
146
147void wl_glue_set_attach_callback(wl_glue_attach_cb_t cb)
148{
149	attach_cb = cb;
150}
151EXPORT_SYMBOL(wl_glue_set_attach_callback);
152
153void wl_glue_set_remove_callback(wl_glue_remove_cb_t cb)
154{
155	remove_cb = cb;
156}
157EXPORT_SYMBOL(wl_glue_set_remove_callback);
158
159int wl_glue_register(void)
160{
161	int err;
162
163	switch(active_bus_type)
164	{
165#ifdef CONFIG_SSB
166	case WL_GLUE_BUS_TYPE_SSB:
167		err = ssb_driver_register(&wl_glue_ssb_driver);
168		break;
169#endif /* CONFIG_SSB */
170
171#ifdef CONFIG_BCMA
172	case WL_GLUE_BUS_TYPE_BCMA:
173		err = bcma_driver_register(&wl_glue_bcma_driver);
174		break;
175#endif /* CONFIG_BCMA */
176
177	default:
178		pr_err("Not attaching through glue driver due to unsupported bus\n");
179		err = -ENOSYS;
180		break;
181	}
182
183	if (!err)
184	{
185		pr_info("SSB/BCMA glue driver successfully attached\n");
186		wl_glue_attached = 1;
187	}
188
189	return err;
190}
191EXPORT_SYMBOL(wl_glue_register);
192
193int wl_glue_unregister(void)
194{
195	int err;
196
197	if (!wl_glue_attached)
198		return -ENOSYS;
199
200	switch (active_bus_type)
201	{
202#ifdef CONFIG_SSB
203	case WL_GLUE_BUS_TYPE_SSB:
204		ssb_driver_unregister(&wl_glue_ssb_driver);
205		err = 0;
206		break;
207#endif /* CONFIG_SSB */
208
209#ifdef CONFIG_BCMA
210	case WL_GLUE_BUS_TYPE_BCMA:
211		bcma_driver_unregister(&wl_glue_bcma_driver);
212		err = 0;
213		break;
214#endif /* CONFIG_BCMA */
215
216	default:
217		pr_err("Not removing glue driver due to unsupported bus\n");
218		err = -ENOSYS;
219		break;
220	}
221
222	if (!err)
223	{
224		pr_info("SSB/BCMA glue driver successfully detached\n");
225		wl_glue_attached = 0;
226	}
227
228	return err;
229}
230EXPORT_SYMBOL(wl_glue_unregister);
231
232struct device * wl_glue_get_dmadev(void *dev)
233{
234	struct device *dma_dev;
235
236	switch (active_bus_type)
237	{
238#ifdef CONFIG_SSB
239	case WL_GLUE_BUS_TYPE_SSB:
240		dma_dev = ((struct ssb_device *)dev)->dma_dev;
241		break;
242#endif /* CONFIG_SSB */
243
244#ifdef CONFIG_BCMA
245	case WL_GLUE_BUS_TYPE_BCMA:
246		dma_dev = ((struct bcma_device *)dev)->dma_dev;
247		break;
248#endif /* CONFIG_BCMA */
249
250	default:
251		BUG();
252		dma_dev = NULL;
253		break;
254	}
255
256	return dma_dev;
257}
258EXPORT_SYMBOL(wl_glue_get_dmadev);
259
260
261static int __init wl_glue_init(void)
262{
263#ifdef CONFIG_BCM47XX
264	/*
265	 * BCM47xx currently supports either SSB or BCMA bus,
266	 * determine the used one from the info set by the
267	 * platform setup code.
268	 */
269	switch (bcm47xx_bus_type)
270	{
271#ifdef CONFIG_BCM47XX_SSB
272	case BCM47XX_BUS_TYPE_SSB:
273		active_bus_type = WL_GLUE_BUS_TYPE_SSB;
274		break;
275#endif /* CONFIG_BCM47XX_SSB */
276
277#ifdef CONFIG_BCM47XX_BCMA
278	case BCM47XX_BUS_TYPE_BCMA:
279		active_bus_type = WL_GLUE_BUS_TYPE_BCMA;
280		break;
281#endif /* CONFIG_BCM47XX_BCMA */
282	}
283#endif /* CONFIG_BCM47XX */
284
285#ifdef CONFIG_BCM63XX
286#ifdef CONFIG_SSB
287	/*
288	 * BCM63xx currently only uses SSB, so assume that.
289	 */
290	active_bus_type = WL_GLUE_BUS_TYPE_SSB;
291#endif /* CONFIG_SSB */
292#endif /* CONFIG_BCM63XX */
293
294	/* do not fail here, let wl_glue_register() return -ENOSYS later */
295	if (active_bus_type == WL_GLUE_BUS_TYPE_UNSPEC)
296		pr_err("Unable to determine used system bus type\n");
297
298	return 0;
299}
300
301static void __exit wl_glue_exit(void)
302{
303	if (wl_glue_attached)
304	{
305		if (wl_glue_unregister())
306			pr_err("Failed to unregister glue driver\n");
307
308		wl_glue_attached = 0;
309	}
310
311	return;
312}
313
314module_init(wl_glue_init);
315module_exit(wl_glue_exit);
316