/* * wl_glue.c: Broadcom WL support module providing a unified SSB/BCMA handling. * Copyright (C) 2011 Jo-Philipp Wich */ #include "wl_glue.h" #include #include #include #ifdef CONFIG_BCM47XX #include #endif #ifdef CONFIG_SSB #include #endif #ifdef CONFIG_BCMA #include #endif MODULE_AUTHOR("Jo-Philipp Wich (jo@mein.io)"); MODULE_DESCRIPTION("Broadcom WL SSB/BCMA compatibility layer"); MODULE_LICENSE("GPL"); static wl_glue_attach_cb_t attach_cb = NULL; static wl_glue_remove_cb_t remove_cb = NULL; static enum wl_glue_bus_type active_bus_type = WL_GLUE_BUS_TYPE_UNSPEC; static int wl_glue_attached = 0; #ifdef CONFIG_SSB static int wl_glue_ssb_probe(struct ssb_device *dev, const struct ssb_device_id *id) { void *mmio; void *wldev; if (!attach_cb) { pr_err("No attach callback registered\n"); return -ENOSYS; } if (dev->bus->bustype != SSB_BUSTYPE_SSB) { pr_err("Attaching to SSB behind PCI is not supported. Please remove the b43 ssb bridge\n"); return -EINVAL; } mmio = (void *) 0x18000000 + dev->core_index * 0x1000; wldev = attach_cb(id->vendor, id->coreid, (ulong)mmio, dev, dev->irq); if (!wldev) { pr_err("The attach callback failed, SSB probe aborted\n"); return -ENODEV; } ssb_set_drvdata(dev, wldev); return 0; } static void wl_glue_ssb_remove(struct ssb_device *dev) { void *wldev = ssb_get_drvdata(dev); if (remove_cb) remove_cb(wldev); ssb_set_drvdata(dev, NULL); } static const struct ssb_device_id wl_glue_ssb_tbl[] = { SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, SSB_ANY_REV), {}, }; static struct ssb_driver wl_glue_ssb_driver = { .name = KBUILD_MODNAME, .id_table = wl_glue_ssb_tbl, .probe = wl_glue_ssb_probe, .remove = wl_glue_ssb_remove, }; #endif /* CONFIG_SSB */ #ifdef CONFIG_BCMA static int wl_glue_bcma_probe(struct bcma_device *dev) { void *wldev; if (!attach_cb) { pr_err("No attach callback registered\n"); return -ENOSYS; } if (dev->bus->hosttype != BCMA_HOSTTYPE_SOC) { pr_err("Unsupported BCMA bus type %d\n", dev->bus->hosttype); return -EINVAL; } /* * NB: * 0x18000000 = BCMA_ADDR_BASE * 0x1000 = BCMA_CORE_SIZE */ wldev = attach_cb(dev->id.manuf, dev->id.id, (ulong)dev->addr, dev, dev->irq); if (!wldev) { pr_err("The attach callback failed, BCMA probe aborted\n"); return -ENODEV; } bcma_set_drvdata(dev, wldev); return 0; } static void wl_glue_bcma_remove(struct bcma_device *dev) { void *wldev = bcma_get_drvdata(dev); if (remove_cb) remove_cb(wldev); bcma_set_drvdata(dev, NULL); } static const struct bcma_device_id wl_glue_bcma_tbl[] = { BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, BCMA_ANY_REV, BCMA_ANY_CLASS), {}, }; static struct bcma_driver wl_glue_bcma_driver = { .name = KBUILD_MODNAME, .id_table = wl_glue_bcma_tbl, .probe = wl_glue_bcma_probe, .remove = wl_glue_bcma_remove, }; #endif /* CONFIG_BCMA */ void wl_glue_set_attach_callback(wl_glue_attach_cb_t cb) { attach_cb = cb; } EXPORT_SYMBOL(wl_glue_set_attach_callback); void wl_glue_set_remove_callback(wl_glue_remove_cb_t cb) { remove_cb = cb; } EXPORT_SYMBOL(wl_glue_set_remove_callback); int wl_glue_register(void) { int err; switch(active_bus_type) { #ifdef CONFIG_SSB case WL_GLUE_BUS_TYPE_SSB: err = ssb_driver_register(&wl_glue_ssb_driver); break; #endif /* CONFIG_SSB */ #ifdef CONFIG_BCMA case WL_GLUE_BUS_TYPE_BCMA: err = bcma_driver_register(&wl_glue_bcma_driver); break; #endif /* CONFIG_BCMA */ default: pr_err("Not attaching through glue driver due to unsupported bus\n"); err = -ENOSYS; break; } if (!err) { pr_info("SSB/BCMA glue driver successfully attached\n"); wl_glue_attached = 1; } return err; } EXPORT_SYMBOL(wl_glue_register); int wl_glue_unregister(void) { int err; if (!wl_glue_attached) return -ENOSYS; switch (active_bus_type) { #ifdef CONFIG_SSB case WL_GLUE_BUS_TYPE_SSB: ssb_driver_unregister(&wl_glue_ssb_driver); err = 0; break; #endif /* CONFIG_SSB */ #ifdef CONFIG_BCMA case WL_GLUE_BUS_TYPE_BCMA: bcma_driver_unregister(&wl_glue_bcma_driver); err = 0; break; #endif /* CONFIG_BCMA */ default: pr_err("Not removing glue driver due to unsupported bus\n"); err = -ENOSYS; break; } if (!err) { pr_info("SSB/BCMA glue driver successfully detached\n"); wl_glue_attached = 0; } return err; } EXPORT_SYMBOL(wl_glue_unregister); struct device * wl_glue_get_dmadev(void *dev) { struct device *dma_dev; switch (active_bus_type) { #ifdef CONFIG_SSB case WL_GLUE_BUS_TYPE_SSB: dma_dev = ((struct ssb_device *)dev)->dma_dev; break; #endif /* CONFIG_SSB */ #ifdef CONFIG_BCMA case WL_GLUE_BUS_TYPE_BCMA: dma_dev = ((struct bcma_device *)dev)->dma_dev; break; #endif /* CONFIG_BCMA */ default: BUG(); dma_dev = NULL; break; } return dma_dev; } EXPORT_SYMBOL(wl_glue_get_dmadev); static int __init wl_glue_init(void) { #ifdef CONFIG_BCM47XX /* * BCM47xx currently supports either SSB or BCMA bus, * determine the used one from the info set by the * platform setup code. */ switch (bcm47xx_bus_type) { #ifdef CONFIG_BCM47XX_SSB case BCM47XX_BUS_TYPE_SSB: active_bus_type = WL_GLUE_BUS_TYPE_SSB; break; #endif /* CONFIG_BCM47XX_SSB */ #ifdef CONFIG_BCM47XX_BCMA case BCM47XX_BUS_TYPE_BCMA: active_bus_type = WL_GLUE_BUS_TYPE_BCMA; break; #endif /* CONFIG_BCM47XX_BCMA */ } #endif /* CONFIG_BCM47XX */ #ifdef CONFIG_BCM63XX #ifdef CONFIG_SSB /* * BCM63xx currently only uses SSB, so assume that. */ active_bus_type = WL_GLUE_BUS_TYPE_SSB; #endif /* CONFIG_SSB */ #endif /* CONFIG_BCM63XX */ /* do not fail here, let wl_glue_register() return -ENOSYS later */ if (active_bus_type == WL_GLUE_BUS_TYPE_UNSPEC) pr_err("Unable to determine used system bus type\n"); return 0; } static void __exit wl_glue_exit(void) { if (wl_glue_attached) { if (wl_glue_unregister()) pr_err("Failed to unregister glue driver\n"); wl_glue_attached = 0; } return; } module_init(wl_glue_init); module_exit(wl_glue_exit);