Some BMIPS xDSL SoCs (BCM6362, BCM63268) integrate a Broadcom 802.11 backplane that is reachable through bcma but differs from the BCM47xx SoCs the host_soc driver was originally written for in two ways: 1. The AXI backplane sits on a big-endian peripheral bus on a big-endian CPU. readl()/writel() perform an asymmetric byte swap in this configuration and land each bit in the wrong position inside the peripheral; 2. Per-core DMP wrappers (NMW/NSW != 0) do not exist for the cores that bcma needs to gate (ChipCommon, the 802.11 core, and the SHIM core itself). Clock and reset control instead lives in a small SoC-level "SHIM" Control register peephole. BCMA_IOCTL and BCMA_RESET_CTL accesses on those cores must be synthesized against the SHIM Control register. The brcm,bus-axi DT path is unchanged. SoCs with those constraints instantiate bcma-host-soc programmatically from a parent bridge driver (e.g. the BCM6362 WLAN SHIM bridge, added in a later patch), supplying a struct bcma_host_soc_pdata that selects big-endian accessors and provides an already-mapped pointer to the SHIM Control register peephole. Internal changes in this patch: - Add include/linux/platform_data/bcma_host_soc.h carrying the new pdata structure (big_endian, shim_attached, shim_iomem). - Add big_endian, shim_attached and shim_iomem fields to struct bcma_bus. - In drivers/bcma/host_soc.c, add a parallel set of BE accessors (read16/read32/write16/write32 and aread32/awrite32) plus a synth path that routes BCMA_IOCTL and BCMA_RESET_CTL accesses on wrapper-less cores through the SHIM Control register. The new bcma_host_soc_ops_brcm_shim ops table groups them. - bcma_host_soc_probe() now reads pdata (when present) and selects big-endian ops + SHIM routing accordingly. bus->mmio is mapped via devm_platform_ioremap_resource() so the same code path works whether the platform_device came from DT (resource via reg property) or was synthesized by a parent (resource passed in platform_device_info::res). - In drivers/bcma/scan.c, bcma_scan_read32() and bcma_erom_get_ent() honour bus->big_endian. Assisted-by: Claude:claude-4.8-opus Signed-off-by: Alessio Ferri --- drivers/bcma/host_soc.c | 224 ++++++++++++++++++++++++++-- drivers/bcma/scan.c | 4 +- include/linux/bcma/bcma.h | 14 ++ include/linux/platform_data/bcma_host_soc.h | 31 ++++ 4 files changed, 260 insertions(+), 13 deletions(-) diff --git a/drivers/bcma/host_soc.c b/drivers/bcma/host_soc.c index 20b1816c570b..f39129fb9cf2 100644 --- a/drivers/bcma/host_soc.c +++ b/drivers/bcma/host_soc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -165,6 +166,195 @@ static const struct bcma_host_ops bcma_host_soc_ops = { .awrite32 = bcma_host_soc_awrite32, }; +/* SHIM peephole layout, subset of the OEM "WlanShimRegs" struct: only + * the per-core Control registers are needed for IOCTL / RESET_CTL + * routing. The low 16 bits of each Control register map bit-for-bit to + * BCMA_IOCTL; bit 16 (SICF_WOC_CORE_RESET) is the per-core wrapper + * BCMA_RESET_CTL bit 0 promoted into the SHIM Control register. + */ +#define BCMA_SHIM_CC_CONTROL 0x08 +#define BCMA_SHIM_MAC_CONTROL 0x10 +#define SICF_WOC_CORE_RESET 0x10000 + +/* Resolve the SHIM Control register for a given core: ChipCommon and + * the IEEE 802.11 core. Returns NULL for any other core, including the + * SHIM core itself - the SHIM has been running since boot and needs no + * gating from bcma_core_enable(). + */ +static void __iomem *bcma_host_soc_shim_ctrl_reg(struct bcma_device *core) +{ + void __iomem *shim = core->bus->shim_iomem; + + if (!shim) + return NULL; + + switch (core->id.id) { + case BCMA_CORE_CHIPCOMMON: + return shim + BCMA_SHIM_CC_CONTROL; + case BCMA_CORE_80211: + return shim + BCMA_SHIM_MAC_CONTROL; + } + return NULL; +} + +/* Synthesize wrapper-register responses for cores whose DMP wrapper + * space does not exist in the standard bcma layout. On SoCs that + * publish a SHIM-style mini-EROM (BMIPS xDSL family: BCM6362, ...) + * ChipCommon and the 802.11 core report NMW=NSW=0; clock and reset + * gating happens in the SHIM's per-core Control register, which is + * where this synth routes BCMA_IOCTL and BCMA_RESET_CTL accesses. + */ +static u32 bcma_host_soc_synth_aread32(struct bcma_device *core, u16 offset) +{ + void __iomem *ctrl_reg = bcma_host_soc_shim_ctrl_reg(core); + + switch (offset) { + case BCMA_IOCTL: + /* Low 16 bits of the SHIM Control register map bit-for-bit + * to BCMA_IOCTL. Returning the live value lets + * bcma_core_is_enabled() observe a prior disable that + * cleared CLOCK_EN/FGC. For cores not in the SHIM map + * (e.g. the SHIM core itself) return BCMA_IOCTL_CLK so + * the core is treated as already-up; the SHIM has been + * running since boot and has nothing to enable. + */ + if (ctrl_reg) + return ioread32be(ctrl_reg) & 0xFFFF; + return BCMA_IOCTL_CLK; + + case BCMA_IOST: + /* IOST is synthesized rather than read from the SHIM + * Status register: while the d11 is in reset, MacStatus's + * SISF_CORE_BITS field is unreliable (observed: 0x1008 on + * a disabled d11, where the "2G_PHY" indicator bit 0 is + * clear, which would steer b43 down a nonexistent 5 GHz + * path on a 2.4 GHz-only single-die part). + * + * Synthesize a stable IOST for the 802.11 core: + * bit 0 (2G_PHY) = 1 single-die 2.4 GHz + * bit 1 (5G_PHY) = 0 no 5 GHz radio wired + * bit 12 (BCMA_IOST_DMA64)= 1 corerev 22 is DMA64 + * + * Other cores have no defined IOST bits of interest. + */ + if (core->id.id == BCMA_CORE_80211) + return 0x01 | BCMA_IOST_DMA64; + return 0; + + case BCMA_RESET_CTL: + /* SICF_WOC_CORE_RESET is the wrapper RESET_CTL bit 0 in + * the SHIM Control register. + */ + if (ctrl_reg) + return (ioread32be(ctrl_reg) & SICF_WOC_CORE_RESET) ? 1 : 0; + return 0; + + case BCMA_RESET_ST: + /* No "reset pending" semantics in the SHIM Control reg. */ + return 0; + + default: + pr_info("bcma: synth aread32 unhandled offset 0x%03x on core idx=%u id=0x%x\n", + offset, core->core_index, core->id.id); + return 0; + } +} + +static void bcma_host_soc_synth_awrite32(struct bcma_device *core, + u16 offset, u32 value) +{ + void __iomem *ctrl_reg = bcma_host_soc_shim_ctrl_reg(core); + u32 cur, new_val; + + if (ctrl_reg) { + switch (offset) { + case BCMA_IOCTL: + /* SICF low 16 bits == BCMA_IOCTL. Preserve + * SICF_WOC_CORE_RESET (the RESET_CTL view) so an + * IOCTL write does not accidentally release reset. + */ + cur = ioread32be(ctrl_reg); + new_val = (value & 0xFFFF) | + (cur & SICF_WOC_CORE_RESET); + iowrite32be(new_val, ctrl_reg); + pr_debug("bcma: synth IOCTL core=0x%x SHIM %08x->%08x (req %08x)\n", + core->id.id, cur, new_val, value); + return; + case BCMA_RESET_CTL: + cur = ioread32be(ctrl_reg); + if (value & 1) + new_val = cur | SICF_WOC_CORE_RESET; + else + new_val = cur & ~SICF_WOC_CORE_RESET; + iowrite32be(new_val, ctrl_reg); + pr_debug("bcma: synth RESET_CTL core=0x%x SHIM %08x->%08x (req %08x)\n", + core->id.id, cur, new_val, value); + return; + } + } + + pr_info("bcma: synth awrite32 dropped on core idx=%u id=0x%x offset=0x%03x value=0x%08x\n", + core->core_index, core->id.id, offset, value); +} + +/* Big-endian accessor variants for SoCs whose AXI backplane sits on a + * big-endian peripheral bus (BMIPS xDSL family). read8/write8 are + * endian-agnostic byte accesses and reuse the LE helpers above. + * CONFIG_BCMA_BLOCKIO block_read/write are intentionally omitted: those + * targets do not enable block I/O. aread32/awrite32 dispatch to the + * synthesizer when core->io_wrap is NULL (legitimate on SHIM-attached + * cores; that NULL state is allow-listed in scan.c). + */ +static u32 bcma_host_soc_read32_be(struct bcma_device *core, u16 offset) +{ + return ioread32be(core->io_addr + offset); +} + +static u16 bcma_host_soc_read16_be(struct bcma_device *core, u16 offset) +{ + return ioread16be(core->io_addr + offset); +} + +static void bcma_host_soc_write32_be(struct bcma_device *core, u16 offset, + u32 value) +{ + iowrite32be(value, core->io_addr + offset); +} + +static void bcma_host_soc_write16_be(struct bcma_device *core, u16 offset, + u16 value) +{ + iowrite16be(value, core->io_addr + offset); +} + +static u32 bcma_host_soc_aread32_be(struct bcma_device *core, u16 offset) +{ + if (likely(core->io_wrap)) + return ioread32be(core->io_wrap + offset); + return bcma_host_soc_synth_aread32(core, offset); +} + +static void bcma_host_soc_awrite32_be(struct bcma_device *core, u16 offset, + u32 value) +{ + if (likely(core->io_wrap)) { + iowrite32be(value, core->io_wrap + offset); + return; + } + bcma_host_soc_synth_awrite32(core, offset, value); +} + +static const struct bcma_host_ops bcma_host_soc_ops_brcm_shim = { + .read8 = bcma_host_soc_read8, + .read16 = bcma_host_soc_read16_be, + .read32 = bcma_host_soc_read32_be, + .write8 = bcma_host_soc_write8, + .write16 = bcma_host_soc_write16_be, + .write32 = bcma_host_soc_write32_be, + .aread32 = bcma_host_soc_aread32_be, + .awrite32 = bcma_host_soc_awrite32_be, +}; + int __init bcma_host_soc_register(struct bcma_soc *soc) { struct bcma_bus *bus = &soc->bus; @@ -202,8 +392,8 @@ int __init bcma_host_soc_init(struct bcma_soc *soc) #ifdef CONFIG_OF static int bcma_host_soc_probe(struct platform_device *pdev) { + struct bcma_host_soc_pdata *pdata = dev_get_platdata(&pdev->dev); struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct bcma_bus *bus; int err; @@ -214,14 +404,26 @@ static int bcma_host_soc_probe(struct platform_device *pdev) bus->dev = dev; - /* Map MMIO */ - bus->mmio = of_iomap(np, 0); - if (!bus->mmio) - return -ENOMEM; + /* Map MMIO. devm_platform_ioremap_resource() consumes the first + * IORESOURCE_MEM regardless of whether it came from a DT reg + * property (legacy brcm,bus-axi path) or from a synthesized + * platform_device_info::res (SHIM-attached path). + */ + bus->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bus->mmio)) + return PTR_ERR(bus->mmio); /* Host specific */ bus->hosttype = BCMA_HOSTTYPE_SOC; - bus->ops = &bcma_host_soc_ops; + if (pdata) { + bus->big_endian = pdata->big_endian; + bus->shim_attached = pdata->shim_attached; + bus->shim_iomem = pdata->shim_iomem; + bus->ops = pdata->big_endian ? &bcma_host_soc_ops_brcm_shim + : &bcma_host_soc_ops; + } else { + bus->ops = &bcma_host_soc_ops; + } /* Initialize struct, detect chip */ bcma_init_bus(bus); @@ -229,15 +431,11 @@ static int bcma_host_soc_probe(struct platform_device *pdev) /* Register */ err = bcma_bus_register(bus); if (err) - goto err_unmap_mmio; + return err; platform_set_drvdata(pdev, bus); return err; - -err_unmap_mmio: - iounmap(bus->mmio); - return err; } static void bcma_host_soc_remove(struct platform_device *pdev) @@ -245,7 +443,9 @@ static void bcma_host_soc_remove(struct platform_device *pdev) struct bcma_bus *bus = platform_get_drvdata(pdev); bcma_bus_unregister(bus); - iounmap(bus->mmio); + /* bus->mmio is devm-managed; shim_iomem is borrowed from the + * parent bridge driver and must not be unmapped here. + */ platform_set_drvdata(pdev, NULL); } diff --git a/drivers/bcma/scan.c b/drivers/bcma/scan.c index 84742408a59c..983a62ddeebb 100644 --- a/drivers/bcma/scan.c +++ b/drivers/bcma/scan.c @@ -143,6 +143,8 @@ static const char *bcma_device_name(const struct bcma_device_id *id) static u32 bcma_scan_read32(struct bcma_bus *bus, u16 offset) { + if (bus->big_endian) + return ioread32be(bus->mmio + offset); return readl(bus->mmio + offset); } @@ -155,7 +157,7 @@ static void bcma_scan_switch_core(struct bcma_bus *bus, u32 addr) static u32 bcma_erom_get_ent(struct bcma_bus *bus, u32 __iomem **eromptr) { - u32 ent = readl(*eromptr); + u32 ent = bus->big_endian ? ioread32be(*eromptr) : readl(*eromptr); (*eromptr)++; return ent; } diff --git a/include/linux/bcma/bcma.h b/include/linux/bcma/bcma.h index 60b94b944e9f..aaa6c5674c2a 100644 --- a/include/linux/bcma/bcma.h +++ b/include/linux/bcma/bcma.h @@ -362,6 +362,20 @@ struct bcma_bus { /* We decided to share SPROM struct with SSB as long as we do not need * any hacks for BCMA. This simplifies drivers code. */ struct ssb_sprom sprom; + + /* SoC quirks populated from struct bcma_host_soc_pdata when a + * SHIM-attached parent bridge driver instantiates the bcma-host-soc + * child platform_device. big_endian selects ioread/iowrite *be + * helpers on the scan and host_soc accessor paths; shim_attached + * tells scan.c that wrapper-less cores (NMW=NSW=0) are legitimate + * on this backplane; shim_iomem points at the SoC-level SHIM + * Control register peephole that host_soc.c routes per-core + * BCMA_IOCTL / BCMA_RESET_CTL accesses through. shim_iomem is + * borrowed from the parent and must not be unmapped here. + */ + bool big_endian; + bool shim_attached; + void __iomem *shim_iomem; }; static inline u32 bcma_read8(struct bcma_device *core, u16 offset) diff --git a/include/linux/platform_data/bcma_host_soc.h b/include/linux/platform_data/bcma_host_soc.h new file mode 100644 index 000000000000..e1e7c5acb574 --- /dev/null +++ b/include/linux/platform_data/bcma_host_soc.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _LINUX_PLATFORM_DATA_BCMA_HOST_SOC_H +#define _LINUX_PLATFORM_DATA_BCMA_HOST_SOC_H + +#include + +/** + * struct bcma_host_soc_pdata - SoC-specific configuration for bcma-host-soc. + * + * Used by parent bridge drivers that instantiate bcma-host-soc as a child + * platform_device (e.g. the BCM6362 WLAN SHIM bridge). The legacy + * brcm,bus-axi DT path uses default values and does not supply this. + * + * @big_endian: Backplane registers are big-endian peripherals on a + * big-endian CPU. Selects ioread/iowrite *be helpers for + * all bcma register accesses on this bus. + * @shim_attached: Cores on this backplane do not publish per-core DMP + * wrappers (NMW=NSW=0 in the EROM); clock and reset + * gating instead lives in a SoC-level "SHIM" Control + * register peephole reached through @shim_iomem. + * @shim_iomem: Pre-mapped iomem pointer for the SHIM peephole. + * Lifetime is owned by the parent bridge driver; the + * bcma-host-soc driver must not iounmap it. + */ +struct bcma_host_soc_pdata { + bool big_endian; + bool shim_attached; + void __iomem *shim_iomem; +}; + +#endif /* _LINUX_PLATFORM_DATA_BCMA_HOST_SOC_H */ -- 2.54.0 bcma_get_next_core() rejects with -ENXIO any component whose component_B descriptor reports NMW=NSW=0 unless its core id is in a short allowlist (4706 MAC GBIT, NS_CHIPCOMMON_B, PMU, GCI). On SoCs that publish a SHIM-style mini-EROM (i.e. BCM6362) the WLAN backplane lists three components: ChipCommon, IEEE 802.11 and BCMA_CORE_SHIM. None of the three is in the existing allowlist, so all three are skipped silently, bus->cores stays empty, bcma_find_core(BCMA_CORE_CHIPCOMMON) returns NULL, and a later bcma_chipco_watchdog_register() dereferences cc->core->bus on its first line and oopses mid-probe. Assisted-by: Claude:claude-4.8-opus Signed-off-by: Alessio Ferri --- drivers/bcma/scan.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/bcma/scan.c b/drivers/bcma/scan.c index 983a62ddeebb..782fc53eb6b3 100644 --- a/drivers/bcma/scan.c +++ b/drivers/bcma/scan.c @@ -318,6 +318,21 @@ static int bcma_get_next_core(struct bcma_bus *bus, u32 __iomem **eromptr, case BCMA_CORE_GCI: /* Not used yet: case BCMA_CORE_OOB_ROUTER: */ break; + case BCMA_CORE_CHIPCOMMON: + case BCMA_CORE_80211: + case BCMA_CORE_SHIM: + /* SHIM-style mini-EROM SoCs publish CHIPCOMMON, the + * IEEE 802.11 core and the SHIM core itself with + * NMW=NSW=0 because clock and reset gating happens + * at the SoC level via the SHIM Control register, + * not via per-core DMP wrappers. host_soc.c sets + * bus->shim_attached on those SoCs from pdata; the + * strict NMW=NSW=0 rejection still applies to PCI- + * attached cards and to SoCs without that quirk. + */ + if (bus->shim_attached) + break; + fallthrough; default: bcma_erom_skip_component(bus, eromptr); return -ENXIO; -- 2.54.0 Document the binding for the SHIM bridge that gates the on-chip 2.4 GHz WLAN block of the Broadcom BCM6362 SoC. The bridge owns the SHIM peephole, a single clock for the macro, and two resets (the SHIM macro itself and its ubus side). It is also a bus: it carries one brcm,bus-axi child describing the bcma backplane behind the SHIM, with a standard interrupt-map routing the d11 core's IRQ to the SoC interrupt controller. Assisted-by: Claude:claude-4.8-opus Signed-off-by: Alessio Ferri --- .../devicetree/bindings/bus/brcm,bcm6362-wlan.yaml | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/Documentation/devicetree/bindings/bus/brcm,bcm6362-wlan.yaml b/Documentation/devicetree/bindings/bus/brcm,bcm6362-wlan.yaml new file mode 100644 index 000000000000..c8d49ccdd2c1 --- /dev/null +++ b/Documentation/devicetree/bindings/bus/brcm,bcm6362-wlan.yaml @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/bus/brcm,bcm6362-wlan.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Broadcom BCM6362 on-chip WLAN SHIM bridge + +maintainers: + - Alessio Ferri + +description: | + The BCM6362 SoC integrates a 2.4 GHz Broadcom WLAN block whose + register backplane uses the Broadcom AMBA (bcma) architecture. The + backplane is gated by a small SHIM bridge that holds the WLAN macro + in reset and disables its clocks until released by software. CFE + does not release this block, so software bring-up is required + before bcma can enumerate the backplane. + + This binding describes the SHIM bridge node. The SHIM driver brings + the macro up and then populates the brcm,bus-axi child node, which + describes the bcma backplane behind the SHIM and is bound by the + bcma-host-soc driver. The SoC-specific configuration (big-endian + accessors, SHIM-attached topology, SHIM Control register peephole + pointer) is delivered to bcma via platform_data injected at + populate time, so the brcm,bus-axi child stays SoC-agnostic. + +properties: + compatible: + const: brcm,bcm6362-wlan + + reg: + maxItems: 1 + description: SHIM peephole registers. + + reg-names: + items: + - const: shim + + clocks: + maxItems: 1 + + resets: + items: + - description: SHIM macro reset + - description: SHIM ubus reset + + reset-names: + items: + - const: shim + - const: shim-ubus + + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + + ranges: true + +patternProperties: + "^axi@[0-9a-f]+$": + type: object + description: The bcma AXI backplane behind the SHIM. + $ref: /schemas/types.yaml# + +required: + - compatible + - reg + - reg-names + - clocks + - resets + - reset-names + - '#address-cells' + - '#size-cells' + - ranges + +additionalProperties: false + +examples: + - | + wlan@10007000 { + compatible = "brcm,bcm6362-wlan"; + reg = <0x10007000 0x100>; + reg-names = "shim"; + clocks = <&periph_clk 11>; + resets = <&periph_rst 7>, <&periph_rst 17>; + reset-names = "shim", "shim-ubus"; + + #address-cells = <1>; + #size-cells = <1>; + ranges; + + axi@10004000 { + compatible = "brcm,bus-axi"; + reg = <0x10004000 0x1000>; + ranges = <0x00000000 0x10004000 0x00002000>; + + #address-cells = <1>; + #size-cells = <1>; + #interrupt-cells = <1>; + + interrupt-map-mask = <0x000fffff 0xffff>; + interrupt-map = <0x00005000 0 &periph_intc 0 12>; + }; + }; -- 2.54.0 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