Add the bridge driver that brings up the BCM6362 on-chip WLAN SHIM and then populates a brcm,bus-axi child whose backplane is enumerated by drivers/bcma/host_soc.c. Add myself as MANTAINER for this shim. After mapping the SHIM peephole, preparing the clock, and toggling the SHIM and ubus resets, the driver runs the macro-enable sequence taken from the OEM BCM6362 SDK setup.c. It then constructs a struct bcma_host_soc_pdata - with big_endian and shim_attached set, and shim_iomem pointing at the already-mapped SHIM peephole - and hands it to the brcm,bus-axi child by registering an of_dev_auxdata entry keyed on the "brcm,bus-axi" compatible. of_platform_populate() then creates the child platform_device with the pdata attached, and bcma-host-soc consumes it during its probe. The auxdata-based handoff was chosen over a second per-SoC bcma-host-soc DT compatible to keep the SoC-specific knowledge in the SHIM driver, where the SHIM register layout already lives, and to avoid duplicating the SHIM base address between the DT and the bcma driver. Using of_platform_populate() rather than a synthesized platform_device preserves the DT IRQ machinery: the bcma child's of_node carries the standard interrupt-map that bcma_of_get_irq() walks to resolve per-core IRQs. Assisted-by: Claude:claude-4.8-opus Signed-off-by: Alessio Ferri --- MAINTAINERS | 7 ++ drivers/bus/Kconfig | 13 +++ drivers/bus/Makefile | 1 + drivers/bus/bcm6362-wlan-shim.c | 252 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 273 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 461a3eed6129..4032bd6b9cfa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5109,6 +5109,13 @@ L: linux-usb@vger.kernel.org S: Maintained F: drivers/usb/gadget/udc/bcm63xx_udc.* +BROADCOM BCM6362 WLAN SHIM BRIDGE DRIVER +M: Alessio Ferri +L: linux-wireless@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/bus/brcm,bcm6362-wlan.yaml +F: drivers/bus/bcm6362-wlan-shim.c + BROADCOM BCM7XXX ARM ARCHITECTURE M: Florian Fainelli R: Broadcom internal kernel review list diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 3181d8aa32a3..e992a34c5230 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -29,6 +29,19 @@ config ARM_INTEGRATOR_LM Say y here to enable support for the ARM Logic Module bus found on the ARM Integrator AP (Application Platform) +config BCM6362_WLAN_SHIM + tristate "BCM6362 on-chip WLAN SHIM bridge" + depends on BMIPS_GENERIC || COMPILE_TEST + depends on OF + select BCMA + select BCMA_HOST_SOC + help + Bring-up driver for the SHIM bridge that gates the integrated + 2.4 GHz WLAN block of the BCM6362 SoC. The driver releases the + SHIM from reset, configures clocks, and then instantiates a + bcma-host-soc child platform device whose bcma backplane is + enumerated by the bcma driver. + config BRCMSTB_GISB_ARB tristate "Broadcom STB GISB bus arbiter" depends on ARCH_BRCMSTB || BMIPS_GENERIC diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index a01f97fef3e8..4b24ce0137fc 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_ARM_CCI) += arm-cci.o obj-$(CONFIG_ARM_INTEGRATOR_LM) += arm-integrator-lm.o obj-$(CONFIG_HISILICON_LPC) += hisi_lpc.o obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o +obj-$(CONFIG_BCM6362_WLAN_SHIM) += bcm6362-wlan-shim.o obj-$(CONFIG_MOXTET) += moxtet.o # DPAA2 fsl-mc bus diff --git a/drivers/bus/bcm6362-wlan-shim.c b/drivers/bus/bcm6362-wlan-shim.c new file mode 100644 index 000000000000..a2de03cf8ff7 --- /dev/null +++ b/drivers/bus/bcm6362-wlan-shim.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BCM6362 on-chip WLAN SHIM bridge driver. + * + * The BCM6362 integrates a Broadcom 2.4 GHz WLAN block whose register + * backplane is a Broadcom AMBA (AXI/OCP) - what the bcma driver calls + * "brcm,bus-axi". The backplane sits on the SoC ubus, behind a small + * "SHIM" bridge that gates clocks and holds the WLAN macro in reset + * until released by software. CFE does not bring this block up. + * + * ubus ─┬─► WLAN SHIM ─► AXI backplane ┬─► ChipCommon + * │ @ 0x10007000 @ 0x10004000 ├─► d11 MAC core + * │ └─► (PMU, GPIO live in + * │ ChipCommon) + * └─► rest of the SoC + * + * This driver brings the SHIM up (clocks, resets, the OEM enable + * sequence) and then calls of_platform_populate() on its DT node. The + * "brcm,bus-axi" child is bound by drivers/bcma/host_soc.c, and the + * SoC-specific configuration that bcma needs (big-endian backplane, + * SHIM-attached topology, and an already-mapped pointer to the SHIM + * Control register peephole) is delivered to it via of_dev_auxdata + * platform_data injected at populate time. + * + * Bring-up sequence and SHIM register layout match the OEM source + * arch/mips/bcm963xx/setup.c and the WlanShimRegs struct in + * shared/opensource/include/bcm963xx/6362_map_part.h. The fake-PCI + * dance the OEM kernel does after bring-up is intentionally absent + * here: bcma host_soc.c speaks to the backplane natively, in + * big-endian, via the pdata-supplied configuration. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SHIM register layout (struct WlanShimRegs in 6362_map_part.h). */ +#define SHIM_MISC 0x00 +#define SHIM_FORCE_CLK_ON BIT(2) +#define SHIM_MACRO_DISABLE BIT(1) +#define SHIM_MACRO_SOFT_RESET BIT(0) +#define SHIM_STATUS 0x04 +#define SHIM_CC_CONTROL 0x08 +#define SHIM_CC_STATUS 0x0c +#define SHIM_MAC_CONTROL 0x10 +#define SICF_FGC BIT(1) /* force gated clock */ +#define SICF_CLOCK_EN BIT(0) +#define SHIM_MAC_STATUS 0x14 +#define SHIM_CC_ID_A 0x18 +#define SHIM_MAC_ID_A 0x24 + +struct bcm6362_wlan { + struct device *dev; + void __iomem *shim; + struct clk *clk; + struct reset_control *rst_shim; + struct reset_control *rst_shim_ubus; + + /* Storage for the pdata pointer handed to bcma via of_dev_auxdata. + * of_platform_device_create_pdata() stores a pointer to this + * struct on the bcma child device's platform_data field, so it + * must outlive the child. devm_kzalloc on priv guarantees this: + * the child is depopulated in remove() before devres frees priv. + */ + struct bcma_host_soc_pdata pdata; +}; + +static int bcm6362_wlan_bringup(struct bcm6362_wlan *priv) +{ + int ret; + + dev_info(priv->dev, "bring-up: start\n"); + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(priv->dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + dev_info(priv->dev, "bring-up: clock enabled, rate=%lu Hz\n", + clk_get_rate(priv->clk)); + mdelay(10); + + /* Reset toggle (brcm,bcm6345-reset hides the active-low softResetB + * encoding, so assert/deassert read naturally here). + */ + reset_control_assert(priv->rst_shim_ubus); + reset_control_assert(priv->rst_shim); + mdelay(1); + reset_control_deassert(priv->rst_shim_ubus); + reset_control_deassert(priv->rst_shim); + mdelay(1); + dev_info(priv->dev, "bring-up: reset toggled\n"); + + /* The SHIM and the AXI backplane behind it are big-endian + * peripherals on a big-endian MIPS CPU. The asymmetric-endian + * writel() in this configuration byte-swaps the value (it + * assumes a little-endian bus, typical for PCI), landing each + * bit in the wrong position. iowrite32be() is a no-op transform + * here (BE-to-BE) and writes the value the bring-up sequence + * intends. Same story for the read-back diagnostics: readl() + * would byte-swap on the way back. + * + * Force clocks on + hold WLAN macro in soft reset. + */ + iowrite32be(SHIM_FORCE_CLK_ON | SHIM_MACRO_SOFT_RESET, + priv->shim + SHIM_MISC); + mdelay(1); + + /* MAC core: force gated clock + clock enable (with reset held). */ + iowrite32be(SICF_FGC | SICF_CLOCK_EN, priv->shim + SHIM_MAC_CONTROL); + + /* Release macro soft reset, keep clocks forced. */ + iowrite32be(SHIM_FORCE_CLK_ON, priv->shim + SHIM_MISC); + + /* Drop the force, let normal gating take over. */ + iowrite32be(0, priv->shim + SHIM_MISC); + iowrite32be(SICF_CLOCK_EN, priv->shim + SHIM_MAC_CONTROL); + + /* Read-back diagnostics: if the backplane is alive these reflect + * the values we just wrote (MISC=0, MAC_CONTROL=SICF_CLOCK_EN) and + * the STATUS regs report sane non-zero core ids. + */ + dev_info(priv->dev, + "bring-up: post-shim MISC=%08x STATUS=%08x CC_CTRL=%08x CC_STAT=%08x MAC_CTRL=%08x MAC_STAT=%08x\n", + ioread32be(priv->shim + SHIM_MISC), + ioread32be(priv->shim + SHIM_STATUS), + ioread32be(priv->shim + SHIM_CC_CONTROL), + ioread32be(priv->shim + SHIM_CC_STATUS), + ioread32be(priv->shim + SHIM_MAC_CONTROL), + ioread32be(priv->shim + SHIM_MAC_STATUS)); + dev_info(priv->dev, + "bring-up: CcIdA=%08x MacIdA=%08x (non-zero = backplane responsive)\n", + ioread32be(priv->shim + SHIM_CC_ID_A), + ioread32be(priv->shim + SHIM_MAC_ID_A)); + + return 0; +} + +static void bcm6362_wlan_teardown(struct bcm6362_wlan *priv) +{ + iowrite32be(0, priv->shim + SHIM_MAC_CONTROL); + iowrite32be(SHIM_MACRO_DISABLE | SHIM_MACRO_SOFT_RESET, + priv->shim + SHIM_MISC); + reset_control_assert(priv->rst_shim); + reset_control_assert(priv->rst_shim_ubus); + clk_disable_unprepare(priv->clk); +} + +static int bcm6362_wlan_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct of_dev_auxdata auxdata[2]; + struct bcm6362_wlan *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = dev; + + priv->shim = devm_platform_ioremap_resource_byname(pdev, "shim"); + if (IS_ERR(priv->shim)) + return PTR_ERR(priv->shim); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->rst_shim = devm_reset_control_get_exclusive(dev, "shim"); + if (IS_ERR(priv->rst_shim)) + return PTR_ERR(priv->rst_shim); + + priv->rst_shim_ubus = devm_reset_control_get_exclusive(dev, + "shim-ubus"); + if (IS_ERR(priv->rst_shim_ubus)) + return PTR_ERR(priv->rst_shim_ubus); + + ret = bcm6362_wlan_bringup(priv); + if (ret) { + dev_err(dev, "WLAN bring-up failed: %d\n", ret); + return ret; + } + + /* Configure pdata in storage owned by priv. Used by + * of_platform_populate() below and dereferenced by bcma at + * runtime via dev_get_platdata(). + */ + priv->pdata.big_endian = true; + priv->pdata.shim_attached = true; + priv->pdata.shim_iomem = priv->shim; + + /* Inject pdata into the brcm,bus-axi child at populate time. + * phys_addr 0 matches by compatible only; there is exactly one + * brcm,bus-axi child under this node. of_platform_populate() + * triggers the bcma probe synchronously - if bcma is built-in + * (or already loaded as a module - see MODULE_SOFTDEP below) + * it has matched and configured itself before we return here. + */ + auxdata[0] = (struct of_dev_auxdata) + OF_DEV_AUXDATA("brcm,bus-axi", 0, NULL, &priv->pdata); + memset(&auxdata[1], 0, sizeof(auxdata[1])); + + ret = of_platform_populate(dev->of_node, NULL, auxdata, dev); + if (ret) { + dev_err(dev, "failed to populate bcma child: %d\n", ret); + bcm6362_wlan_teardown(priv); + return ret; + } + + platform_set_drvdata(pdev, priv); + return 0; +} + +static void bcm6362_wlan_remove(struct platform_device *pdev) +{ + struct bcm6362_wlan *priv = platform_get_drvdata(pdev); + + /* Tear bcma down first: the bcma child uses priv->shim through + * pdata->shim_iomem and its lifetime is owned here. + * of_platform_depopulate() is synchronous - by the time it + * returns, bcma has released the SHIM mapping. + */ + of_platform_depopulate(&pdev->dev); + bcm6362_wlan_teardown(priv); +} + +static const struct of_device_id bcm6362_wlan_match[] = { + { .compatible = "brcm,bcm6362-wlan", }, + { } +}; +MODULE_DEVICE_TABLE(of, bcm6362_wlan_match); + +static struct platform_driver bcm6362_wlan_driver = { + .probe = bcm6362_wlan_probe, + .remove = bcm6362_wlan_remove, + .driver = { + .name = "bcm6362-wlan", + .of_match_table = bcm6362_wlan_match, + }, +}; +module_platform_driver(bcm6362_wlan_driver); + +MODULE_SOFTDEP("pre: bcma"); +MODULE_AUTHOR("Alessio Ferri "); +MODULE_DESCRIPTION("BCM6362 on-chip WLAN SHIM bridge driver"); +MODULE_LICENSE("GPL"); -- 2.54.0