<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ps2chiper]]></title><description><![CDATA[ps2chiper]]></description><link>https://ps2chiper.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 28 Oct 2026 22:51:35 GMT</lastBuildDate><atom:link href="https://ps2chiper.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[ASUS ExpertWiFi EBG15: the OpenWrt patch series]]></title><description><![CDATA[Complete ASUS EBG15 OpenWrt patches with an exact baseline, SHA-256 checksums, extraction script, application instructions, and test scope.]]></description><link>https://ps2chiper.hashnode.dev/asus-expertwifi-ebg15-the-openwrt-patch-series</link><guid isPermaLink="true">https://ps2chiper.hashnode.dev/asus-expertwifi-ebg15-the-openwrt-patch-series</guid><category><![CDATA[OpenWRT]]></category><dc:creator><![CDATA[jason duhamell]]></dc:creator><pubDate>Sun, 25 Oct 2026 00:55:28 GMT</pubDate><content:encoded><![CDATA[<p>This is the complete eight-part patch set accompanying <a href="https://ps2chiper.hashnode.dev/bringing-openwrt-to-the-asus-expertwifi-ebg15">Bringing OpenWrt to the ASUS ExpertWiFi EBG15</a>.</p>
<p>The patches are published here for inspection and reproduction. They are unsigned OpenWrt-tree diffs, not an accepted upstream series. The separate Linux submissions and OpenWrt pull request have not been filed. No contributor sign-off has been invented.</p>
<h2>Exact baseline and scope</h2>
<p>Apply these patches to OpenWrt commit <code>814759483bbcfecd7b2741838e897693b07433f4</code> from <code>https://git.openwrt.org/openwrt/openwrt.git</code>. This is the tested local baseline, not a claim that the patches apply to today's upstream HEAD. The eight patches reconstruct 20 submission files. Diagnostic patch 702, credentials, device backups, and firmware binaries are excluded.</p>
<p>The kernel and U-Boot patches inside these diffs are OpenWrt quilt inputs. These outer patches are applied to the OpenWrt repository; they are not patches to apply directly to a Linux source checkout.</p>
<p>The candidate was installed into bank 2 with bank 1 retained. The latest hardware run passed configuration preservation, LAN2 traffic, visual power/LAN2 LED checks, persistent-write tests, and warm reboot. Cold boot and the remaining physical ports have not been rechecked on that exact image. Router transmit retransmissions and a previously observed VLAN egress asymmetry remain documented limitations. See the accompanying article for the measured results and full test scope.</p>
<h2>Saving the patches</h2>
<p>Save each <code>diff</code> block below using the filename in its heading. Hashnode also exposes this entire post as Markdown through its <strong>View as Markdown</strong> link. If you save that Markdown as <code>ebg15-patches.md</code>, the following local script extracts and verifies all eight blocks:</p>
<pre><code class="language-python">from pathlib import Path
import hashlib
import re

expected = {'0001-unimac.patch': 'd5525c9f72215e35d8c688f419af6650a313e83d6d38df0d938c55a992463038', '0002-pmb.patch': '99f90d0412b18a58597c26d65ef4b2d99e71213f26ff24053e8a736a979dfb0c', '0003-enet.patch': '340344383f94646fa4e3324331793350413976ad9a3cc31497e4975f87e9ea5e', '0004-leds.patch': 'e8304d865e2907f999f30b5f616f7f0b89e23ab5d3066046fc1f9bfe42ef9222', '0005-pinctrl.patch': '83e8896d18352961f93d48d142b73ff301b13db4c2d4f327c17192aefc6df43c', '0006-image.patch': '035276a666fa79e1776ff3252d1c21caa61f390559ce8dccaee208a5636bb85e', '0007-board.patch': '0c421e4322dbdfd31cdcd874a98d63be95c8b3ac85bca39ceb42132a9b950c9e', '0008-uboot.patch': '837fe08ebff00f1c452dd23f96906f6ab5c08a659af280324f6e1e73ae1c8f82'}
text = Path("ebg15-patches.md").read_text()
blocks = re.findall(r"### `(\d{4}-[^`]+\.patch)`\s*```diff\r?\n(.*?)```", text, re.S)
assert len(blocks) == 8, "Expected exactly eight patches"
out = Path("ebg15-patches")
out.mkdir(exist_ok=True)
for name, patch in blocks:
    data = patch.replace("\r\n", "\n").encode()
    assert hashlib.sha256(data).hexdigest() == expected[name], name
    (out / name).write_bytes(data)
print("Extracted and verified eight patches")
</code></pre>
<h2>Applying the series</h2>
<p>From a clean OpenWrt checkout, with the extracted directory alongside it:</p>
<pre><code class="language-sh">git switch -c asus-ebg15-port 814759483bbcfecd7b2741838e897693b07433f4
for patch in ../ebg15-patches/*.patch; do
    git apply --check "$patch" || exit 1
    git apply "$patch" || exit 1
done
</code></pre>
<p><code>git apply</code> does not create commits or sign-offs. To prepare a formal submission, split the changes into their documented commits and add the contributor’s authorized identity and sign-offs. Refresh kernel/package quilt patches with the appropriate OpenWrt <code>make .../refresh</code> target when editing their hunks.</p>
<h2>Build recipe: configuration, feeds, and provenance</h2>
<p>This section records the missing build inputs. It provides a configuration recipe for the published source baseline and patches. It does <strong>not</strong> claim a successful rebuild with an empty download cache and a newly built toolchain, or a byte-identical reproduction of the reference image.</p>
<h3>Host prerequisites</h3>
<p>The reference host was x86-64 Ubuntu 24.04, with GCC 13.3 and GNU Make 4.3. The existing build selected Python 3.13.5 from the host environment; the pinned build system accepts Python 3.8 or newer. Use a case-sensitive filesystem, build as a normal user, and use <code>umask 022</code>.</p>
<p>For an Ubuntu 24.04 x86-64 host, this is a prerequisite installation starting point:</p>
<pre><code class="language-sh">sudo apt update
sudo apt install build-essential clang flex bison gawk gcc-multilib \
    g++-multilib gettext git libncurses-dev libssl-dev python3 \
    python3-setuptools rsync swig unzip zlib1g-dev file wget curl \
    subversion patch diffutils findutils coreutils util-linux time \
    xsltproc zstd
</code></pre>
<p>Package names vary by host distribution. Run <code>make prereq</code> in the prepared tree to check the pinned build system's requirements. The repository's <a href="https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=include/prereq-build.mk;hb=814759483bbcfecd7b2741838e897693b07433f4">prerequisite checks</a> are the version-specific reference. Prerequisite checks have passed on the existing host; the package-install command above has not been validated on a fresh OS installation.</p>
<h3><code>feeds.conf</code></h3>
<p>Save this beside your future <code>openwrt/</code> checkout as <code>feeds.conf</code>. These revisions are recorded in the built image's <code>feeds.buildinfo</code> and match the current feed repository HEADs. The <code>^</code> suffix pins a feed to a commit rather than following a moving branch.</p>
<pre><code class="language-text">src-git packages https://git.openwrt.org/feed/packages.git^68f46edd7a74e308f9a14e448f4464cf332c7a2b
src-git luci https://git.openwrt.org/project/luci.git^b0cead459cfb434f2b2066e03f36c5bf39a583e7
src-git routing https://git.openwrt.org/feed/routing.git^4b9891b9136259f93294a424507ed24c5e8c1cbd
src-git telephony https://git.openwrt.org/feed/telephony.git^5d68d53c160a325ea9d03fce393e051573bcc736
src-git video https://github.com/openwrt/video.git^6917907d4d6c670cf85be525d23216595655061d
</code></pre>
<h3><code>config.seed</code></h3>
<p>Save this as <code>config.seed</code> beside the checkout. It is the configuration seed saved as <code>config.buildinfo</code> with the tested image, not an assumed minimal profile. It includes LuCI, BlueZ and USB tools. The lab setting <code>CONFIG_KERNEL_DEVMEM=y</code> is retained to document the tested configuration; it is not needed simply to select the EBG15 device and may be omitted for a different build.</p>
<pre><code class="language-text">CONFIG_TARGET_bcm4908=y
CONFIG_TARGET_bcm4908_generic=y
CONFIG_TARGET_bcm4908_generic_DEVICE_asus_ebg15=y
CONFIG_KERNEL_DEVMEM=y
CONFIG_LIBNCURSES_TERMINFO=""
CONFIG_OPENSSL_ENGINE=y
CONFIG_OPENSSL_WITH_ASM=y
CONFIG_OPENSSL_WITH_CHACHA_POLY1305=y
CONFIG_OPENSSL_WITH_CMS=y
CONFIG_OPENSSL_WITH_DEPRECATED=y
CONFIG_OPENSSL_WITH_ERROR_MESSAGES=y
CONFIG_OPENSSL_WITH_IDEA=y
CONFIG_OPENSSL_WITH_MDC2=y
CONFIG_OPENSSL_WITH_PSK=y
CONFIG_OPENSSL_WITH_SEED=y
CONFIG_OPENSSL_WITH_SRP=y
CONFIG_OPENSSL_WITH_TLS13=y
CONFIG_OPENSSL_WITH_WHIRLPOOL=y
CONFIG_PACKAGE_bluez-daemon=y
CONFIG_PACKAGE_bluez-libs=y
CONFIG_PACKAGE_bluez-utils=y
CONFIG_PACKAGE_bluez-utils-btmon=y
CONFIG_PACKAGE_bluez-utils-extra=y
CONFIG_PACKAGE_boost=y
CONFIG_PACKAGE_boost-atomic=y
CONFIG_PACKAGE_boost-chrono=y
CONFIG_PACKAGE_boost-container=y
CONFIG_PACKAGE_boost-date_time=y
CONFIG_PACKAGE_boost-filesystem=y
CONFIG_PACKAGE_boost-log=y
CONFIG_PACKAGE_boost-regex=y
CONFIG_PACKAGE_boost-serialization=y
CONFIG_PACKAGE_boost-thread=y
CONFIG_PACKAGE_cgi-io=y
CONFIG_PACKAGE_dbus=y
CONFIG_PACKAGE_glib2=y
CONFIG_PACKAGE_glib2-core=y
CONFIG_PACKAGE_glib2-gio=y
CONFIG_PACKAGE_glib2-gmodule=y
CONFIG_PACKAGE_glib2-gobject=y
CONFIG_PACKAGE_glib2-gthread=y
CONFIG_PACKAGE_icu=y
CONFIG_PACKAGE_libattr=y
CONFIG_PACKAGE_libdbus=y
CONFIG_PACKAGE_libexpat=y
CONFIG_PACKAGE_libffi=y
CONFIG_PACKAGE_libical=y
CONFIG_PACKAGE_libiwinfo=y
CONFIG_PACKAGE_libiwinfo-data=y
CONFIG_PACKAGE_liblucihttp=y
CONFIG_PACKAGE_liblucihttp-ucode=y
CONFIG_PACKAGE_libncurses=y
CONFIG_PACKAGE_libopenssl=y
CONFIG_PACKAGE_libpcre2=y
CONFIG_PACKAGE_libpthread=y
CONFIG_PACKAGE_libreadline=y
CONFIG_PACKAGE_librt=y
CONFIG_PACKAGE_libstdcpp=y
CONFIG_PACKAGE_libudev-zero=y
CONFIG_PACKAGE_libusb-1.0=y
CONFIG_PACKAGE_luci=y
CONFIG_PACKAGE_luci-app-firewall=y
CONFIG_PACKAGE_luci-app-package-manager=y
CONFIG_PACKAGE_luci-base=y
CONFIG_PACKAGE_luci-lib-uqr=y
CONFIG_PACKAGE_luci-light=y
CONFIG_PACKAGE_luci-mod-admin-full=y
CONFIG_PACKAGE_luci-mod-network=y
CONFIG_PACKAGE_luci-mod-status=y
CONFIG_PACKAGE_luci-mod-system=y
CONFIG_PACKAGE_luci-proto-ipv6=y
CONFIG_PACKAGE_luci-proto-ppp=y
CONFIG_PACKAGE_luci-theme-bootstrap=y
CONFIG_PACKAGE_rpcd=y
CONFIG_PACKAGE_rpcd-mod-file=y
CONFIG_PACKAGE_rpcd-mod-iwinfo=y
CONFIG_PACKAGE_rpcd-mod-luci=y
CONFIG_PACKAGE_rpcd-mod-rrdns=y
CONFIG_PACKAGE_rpcd-mod-ucode=y
CONFIG_PACKAGE_terminfo=y
CONFIG_PACKAGE_ucode-mod-html=y
CONFIG_PACKAGE_ucode-mod-log=y
CONFIG_PACKAGE_uhttpd=y
CONFIG_PACKAGE_uhttpd-mod-ubus=y
CONFIG_PACKAGE_usbids=y
CONFIG_PACKAGE_usbutils=y
CONFIG_PCRE2_JIT_ENABLED=y
CONFIG_TARGET_INITRAMFS_COMPRESSION_NONE=y
CONFIG_TARGET_ROOTFS_INITRAMFS=y
CONFIG_boost-compile-visibility-hidden=y
CONFIG_boost-runtime-shared=y
CONFIG_boost-static-and-shared-libs=y
CONFIG_boost-variant-release=y
</code></pre>
<h3>Optional lab UI change: <code>luci-wan-first.patch</code></h3>
<p>The tested image also contained an uncommitted LuCI change that sorts the WAN port before LAN ports in the status page. Inspection of the candidate rootfs confirms this change was packaged. It was outside the eight core OpenWrt port patches, so it is disclosed separately here. It changes display order, not Ethernet interface assignments.</p>
<p>To match that UI, save this as <code>luci-wan-first.patch</code> and apply it inside the pinned <code>feeds/luci</code> checkout. Omit it if you prefer the unmodified LuCI display order. The eight original patch blocks and checksums remain unchanged.</p>
<pre><code class="language-text">diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/include/29_ports.js b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/include/29_ports.js
index 9ff30bb..c736253 100644
--- a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/include/29_ports.js
+++ b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/include/29_ports.js
@@ -463,7 +463,9 @@ return baseclass.extend({
 		}
 
 		known_ports.sort(function(a, b) {
-			return L.naturalCompare(a.device, b.device);
+			const isWan = function(d) { return d == 'wan'; };
+
+			return (isWan(b.device) - isWan(a.device)) || L.naturalCompare(a.device, b.device);
 		});
 
 		return E('div', { 'style': 'display:grid;grid-template-columns:repeat(auto-fit, minmax(70px, 1fr));margin-bottom:1em' }, known_ports.map(function(port) {
</code></pre>
<h3>Source and build commands</h3>
<p>Start in a directory containing <code>config.seed</code>, <code>feeds.conf</code>, the optional LuCI patch, and the <code>ebg15-patches/</code> directory extracted earlier in this post. Use a <strong>new</strong> checkout; these commands intentionally avoid an existing working tree containing unrelated changes or diagnostic patch 702.</p>
<pre><code class="language-sh">set -eu
git clone https://git.openwrt.org/openwrt/openwrt.git openwrt
cd openwrt
git switch --detach 814759483bbcfecd7b2741838e897693b07433f4

for patch in ../ebg15-patches/*.patch; do
    git apply --check "$patch" || exit 1
    git apply "$patch" || exit 1
done

cp ../feeds.conf feeds.conf
cp ../config.seed .config
umask 022
./scripts/feeds update -a

# Import the seed's selected feed packages and their dependencies.
sed -nE 's/^CONFIG_PACKAGE_([^=]+)=[ym]$/\1/p' ../config.seed \
    | xargs -r ./scripts/feeds install

# Optional: reproduce the status-page port order of the lab image.
git -C feeds/luci apply --check "$PWD/../luci-wan-first.patch"
git -C feeds/luci apply "$PWD/../luci-wan-first.patch"

make defconfig
make prereq
make download -j2
make -j2 V=s
</code></pre>
<p>Stop if any command fails; do not continue past a failed feed update, patch check, or configuration step. The optional LuCI commands require the supplementary patch saved above; leave both out if omitting that UI change. Increase build parallelism only to fit your available RAM and CPU resources. For a failure, rerun <code>make -j1 V=s</code> to get a readable error.</p>
<p>The selective feed import is deliberate. Regenerating configuration with all feed packages linked in the original tree exposed dependency cycles in unrelated Qt5, WeeChat, and Squeezelite options. In an isolated tree, importing the seed's selected packages and dependencies allowed <code>make defconfig</code> to complete and retained <strong>every seed symbol exactly</strong>, including the EBG15 profile, <code>kmod-ath3k</code>, <code>ar3k-firmware</code>, LuCI, BlueZ and USB tools. It still emitted warnings about optional dependencies of unselected recipes; this is not a claim of a warning-free all-package configuration.</p>
<p>The configuration validation used a new source checkout and pinned feed checkouts populated from local Git repositories, without copying build, staging, or download caches. It validates the source-application, feed-import and configuration stages on the existing host. It does not validate a fresh remote source download, all source-mirror availability, or a full toolchain and firmware build from empty caches.</p>
<h3>Output and reproducibility limits</h3>
<p>The expected main output is:</p>
<pre><code class="language-text">bin/targets/bcm4908/generic/openwrt-bcm4908-generic-asus_ebg15-squashfs.pkgtb
</code></pre>
<p>The seed also requests an initramfs output. Its presence is not a claim that the generated initramfs artifact has been independently boot-tested with these instructions.</p>
<p>The image that was tested on hardware had SHA-256 <code>ee7e1d21846ce3167e6889b3bb09230cc9863689a21733382f63b8c2f8335b0c</code>. That identifies the existing candidate; it is <strong>not</strong> a promised checksum for a new build. Its cached version label was <code>r0-2caedb4</code>; use the actual source baseline above rather than treating that label as the source pin.</p>
<p>The base commit timestamp is <code>1792274553</code>. Build-system-generated signing keys, host tools, package caches and generated metadata also affect output identity. Private signing keys and device configuration backups are not published. A fully verified byte-for-byte reproduction requires more evidence than this recipe supplies.</p>
<p>This is a build recipe. The earlier first-install limits still apply: the UART pad mapping and power-adapter details are incomplete, and a clean build does not establish that an image is safe to flash on a different unit.</p>
<h2>Where the Bluetooth support lives</h2>
<p>Bluetooth support is already present in the published patch contents; it is split across three parts of the series:</p>
<table>
<thead>
<tr>
<th>Outer patch</th>
<th>Bluetooth-related change</th>
</tr>
</thead>
<tbody><tr>
<td><code>0005-pinctrl.patch</code></td>
<td>Adds the bank-A GPIO pinmux function needed by the controller's two control lines</td>
</tr>
<tr>
<td><code>0006-image.patch</code></td>
<td>Adds <code>kmod-ath3k</code> to the EBG15 device packages; its dependencies include USB Bluetooth support and <code>ar3k-firmware</code></td>
</tr>
<tr>
<td><code>0007-board.patch</code></td>
<td>Selects GPIO pinmux for A15/A25 and adds <code>bt-disable-hog</code> / <code>bt-reset-hog</code>, both output-high</td>
</tr>
</tbody></table>
<p>The controller is an internal <strong>USB Atheros AR3012</strong>, USB ID <code>0cf3:3004</code>, on the OHCI bus. A15 high deasserts disable, and A25 high releases reset. The <code>ath3k</code> firmware-loading path allows it to re-enumerate as an HCI adapter. BlueZ userspace is needed for scanning, pairing, and services; adding the kernel package alone does not provide all of those tools.</p>
<p>Earlier bench testing completed scanning and a BR/EDR pairing/bonding round-trip. The latest candidate's recorded Bluetooth check is <code>hci0</code> enumeration; profile operation and repeated cold starts remain outside that latest test scope. See the main article's Bluetooth section for the complete explanation.</p>
<p>This clarification changes the post text only. The eight patch blocks and their checksums are unchanged.</p>
<h2>Patch manifest</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>SHA-256</th>
</tr>
</thead>
<tbody><tr>
<td><code>0001-unimac.patch</code></td>
<td><code>d5525c9f72215e35d8c688f419af6650a313e83d6d38df0d938c55a992463038</code></td>
</tr>
<tr>
<td><code>0002-pmb.patch</code></td>
<td><code>99f90d0412b18a58597c26d65ef4b2d99e71213f26ff24053e8a736a979dfb0c</code></td>
</tr>
<tr>
<td><code>0003-enet.patch</code></td>
<td><code>340344383f94646fa4e3324331793350413976ad9a3cc31497e4975f87e9ea5e</code></td>
</tr>
<tr>
<td><code>0004-leds.patch</code></td>
<td><code>e8304d865e2907f999f30b5f616f7f0b89e23ab5d3066046fc1f9bfe42ef9222</code></td>
</tr>
<tr>
<td><code>0005-pinctrl.patch</code></td>
<td><code>83e8896d18352961f93d48d142b73ff301b13db4c2d4f327c17192aefc6df43c</code></td>
</tr>
<tr>
<td><code>0006-image.patch</code></td>
<td><code>035276a666fa79e1776ff3252d1c21caa61f390559ce8dccaee208a5636bb85e</code></td>
</tr>
<tr>
<td><code>0007-board.patch</code></td>
<td><code>0c421e4322dbdfd31cdcd874a98d63be95c8b3ac85bca39ceb42132a9b950c9e</code></td>
</tr>
<tr>
<td><code>0008-uboot.patch</code></td>
<td><code>837fe08ebff00f1c452dd23f96906f6ab5c08a659af280324f6e1e73ae1c8f82</code></td>
</tr>
</tbody></table>
<h2>Complete patch contents</h2>
<h3><code>0001-unimac.patch</code></h3>
<pre><code class="language-diff">diff --git a/target/linux/bcm4908/patches-6.18/303-net-mdio-bcm-unimac-enable-bcm4908-gphys.patch b/target/linux/bcm4908/patches-6.18/303-net-mdio-bcm-unimac-enable-bcm4908-gphys.patch
new file mode 100644
index 0000000..e0271c7
--- /dev/null
+++ b/target/linux/bcm4908/patches-6.18/303-net-mdio-bcm-unimac-enable-bcm4908-gphys.patch
@@ -0,0 +1,133 @@
+Subject: [PATCH net-next] net: mdio: bcm-unimac: select MDIO master before PHY discovery
+
+BCA firmware on ASUS EBG15 leaves the switch indirect MDIO master
+selected. Clear the selector before discovery when the optional
+switchreg resource is supplied, and read it back to flush the write.
+
+The BCM4908 switch control register is at offset zero of this window;
+bit 0 selects the indirect master. This register window is shared with
+the SF2 driver, so map it without requesting exclusive ownership. The
+new write is limited to MDIO bus reset during registration. Runtime
+indirect accesses remain managed by the existing SF2 MDIO path.
+
+Document the optional resource for the generic UniMAC compatible and
+retain the existing indirect-access window for other users.
+---
+--- a/drivers/net/mdio/mdio-bcm-unimac.c
++++ b/drivers/net/mdio/mdio-bcm-unimac.c
+@@ -35,15 +35,31 @@
+ #define  MDIO_CLK_DIV_MASK	0x3F
+ #define  MDIO_SUPP_PREAMBLE	(1 &lt;&lt; 12)
+ 
++/* Bit 0 of the BCM4908 switch control window: MDIO master selector. */
++#define UNIMAC_SWITCHREG_MASTER_SEL	BIT(0)
++
+ struct unimac_mdio_priv {
+ 	struct mii_bus		*mii_bus;
+ 	void __iomem		*base;
++	void __iomem		*switch_base;
+ 	int (*wait_func)	(void *wait_func_data);
+ 	void			*wait_func_data;
+ 	struct clk		*clk;
+ 	u32			clk_freq;
+ };
+ 
++static void unimac_mdio_select_master(struct unimac_mdio_priv *priv)
++{
++	u32 val;
++
++	if (!priv-&gt;switch_base)
++		return;
++
++	val = readl(priv-&gt;switch_base);
++	writel(val &amp; ~UNIMAC_SWITCHREG_MASTER_SEL, priv-&gt;switch_base);
++	readl(priv-&gt;switch_base);
++}
++
+ static inline u32 unimac_mdio_readl(struct unimac_mdio_priv *priv, u32 offset)
+ {
+ 	/* MIPS chips strapped for BE will automagically configure the
+@@ -168,11 +184,17 @@ static int unimac_mdio_write(struct mii_
+  */
+ static int unimac_mdio_reset(struct mii_bus *bus)
+ {
++	struct unimac_mdio_priv *priv = bus-&gt;priv;
+ 	struct device_node *np = bus-&gt;dev.of_node;
+ 	struct device_node *child;
+ 	u32 read_mask = 0;
+ 	int addr;
+ 
++	/* BCA firmware may leave the switch's indirect MDIO master selected.
++	 * Select the external UniMAC master before discovering internal PHYs.
++	 */
++	unimac_mdio_select_master(priv);
++
+ 	if (!np) {
+ 		read_mask = ~bus-&gt;phy_mask;
+ 	} else {
+@@ -262,6 +284,14 @@ static int unimac_mdio_probe(struct plat
+ 		return -ENOMEM;
+ 	}
+ 
++	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "switchreg");
++	if (r) {
++		priv-&gt;switch_base = devm_ioremap(&amp;pdev-&gt;dev, r-&gt;start,
++						 resource_size(r));
++		if (!priv-&gt;switch_base)
++			return -ENOMEM;
++	}
++
+ 	if (of_property_read_u32(np, "clock-frequency", &amp;priv-&gt;clk_freq))
+ 		priv-&gt;clk_freq = 0;
+ 
+--- a/Documentation/devicetree/bindings/net/brcm,unimac-mdio.yaml
++++ b/Documentation/devicetree/bindings/net/brcm,unimac-mdio.yaml
+@@ -14,6 +14,27 @@ maintainers:
+ allOf:
+   - $ref: mdio.yaml#
+ 
++  - if:
++      properties:
++        compatible:
++          const: brcm,unimac-mdio
++      required:
++        - compatible
++    then:
++      properties:
++        reg-names:
++          minItems: 1
++          items:
++            - const: mdio
++            - enum: [mdio_indir_rw, switchreg]
++    else:
++      properties:
++        reg-names:
++          minItems: 1
++          items:
++            - const: mdio
++            - const: mdio_indir_rw
++
+ properties:
+   compatible:
+     enum:
+@@ -32,13 +53,18 @@ properties:
+     minItems: 1
+     items:
+       - description: base register
+-      - description: indirect accesses to larger than 16-bits MDIO transactions
++      - description: |
++          Indirect access to larger than 16-bit MDIO transactions. For
++          brcm,unimac-mdio on BCM4908 this may instead cover switch
++          control registers: offset zero holds the control register,
++          whose bit 0 is the MDIO master selector (1 = indirect switch
++          master, 0 = external UniMAC master).
+ 
+   reg-names:
+     minItems: 1
+     items:
+       - const: mdio
+-      - const: mdio_indir_rw
++      - enum: [mdio_indir_rw, switchreg]
+ 
+   interrupts:
+     oneOf:
</code></pre>
<h3><code>0002-pmb.patch</code></h3>
<pre><code class="language-diff">diff --git a/target/linux/bcm4908/patches-6.18/304-pmdomain-bcm-pmb-add-gmac-domain.patch b/target/linux/bcm4908/patches-6.18/304-pmdomain-bcm-pmb-add-gmac-domain.patch
new file mode 100644
index 0000000..9f98ad7
--- /dev/null
+++ b/target/linux/bcm4908/patches-6.18/304-pmdomain-bcm-pmb-add-gmac-domain.patch
@@ -0,0 +1,61 @@
+Subject: [PATCH] pmdomain: bcm-pmb: add BCM4908 GMAC power domain
+
+BCA U-Boot powers down GMAC at handoff. Add the bus 1, device 21 domain
+so an Ethernet consumer can power it on through the existing locked PMB
+operations. This prevents an external abort when accessing UniMAC.
+
+Keep the domain ID and its implementation together so the consumer can
+use the new identifier with the provider that implements it.
+
+The bus 1 / device 21 identity is verified on the ASUS EBG15; its
+applicability to other BCM4908 revisions advertising this PMB
+compatible is unverified.
+
+---
+--- a/drivers/pmdomain/bcm/bcm-pmb.c
++++ b/drivers/pmdomain/bcm/bcm-pmb.c
+@@ -241,6 +241,7 @@ static int bcm_pmb_power_on(struct gener
+ 	case BCM_PMB_PCIE1:
+ 	case BCM_PMB_PCIE2:
+ 		return bcm_pmb_power_on_zone(pmb, data-&gt;bus, data-&gt;device, 0);
++	case BCM_PMB_GMAC:
+ 	case BCM_PMB_HOST_USB:
+ 		return bcm_pmb_power_on_device(pmb, data-&gt;bus, data-&gt;device);
+ 	case BCM_PMB_SATA:
+@@ -262,6 +263,7 @@ static int bcm_pmb_power_off(struct gene
+ 	case BCM_PMB_PCIE1:
+ 	case BCM_PMB_PCIE2:
+ 		return bcm_pmb_power_off_zone(pmb, data-&gt;bus, data-&gt;device, 0);
++	case BCM_PMB_GMAC:
+ 	case BCM_PMB_HOST_USB:
+ 		return bcm_pmb_power_off_device(pmb, data-&gt;bus, data-&gt;device);
+ 	default:
+@@ -338,6 +340,7 @@ static const struct bcm_pmb_pd_data bcm_
+ 	{ .name = "pcie0", .id = BCM_PMB_PCIE0, .bus = 1, .device = 14, },
+ 	{ .name = "pcie1", .id = BCM_PMB_PCIE1, .bus = 1, .device = 15, },
+ 	{ .name = "usb", .id = BCM_PMB_HOST_USB, .bus = 1, .device = 17, },
++	{ .name = "gmac", .id = BCM_PMB_GMAC, .bus = 1, .device = 21, },
+ 	{ },
+ };
+ 
+--- a/include/dt-bindings/soc/bcm-pmb.h
++++ b/include/dt-bindings/soc/bcm-pmb.h
+@@ -8,5 +8,6 @@
+ #define BCM_PMB_PCIE2				0x03
+ #define BCM_PMB_HOST_USB			0x04
+ #define BCM_PMB_SATA				0x05
++#define BCM_PMB_GMAC				0x06
+ 
+ #endif
+--- a/Documentation/devicetree/bindings/net/brcm,bcm4908-enet.yaml
++++ b/Documentation/devicetree/bindings/net/brcm,bcm4908-enet.yaml
+@@ -21,6 +21,9 @@ properties:
+   reg:
+     maxItems: 1
+ 
++  power-domains:
++    maxItems: 1
++
+   interrupts:
+     minItems: 1
+     items:
</code></pre>
<h3><code>0003-enet.patch</code></h3>
<pre><code class="language-diff">diff --git a/target/linux/bcm4908/patches-6.18/305-net-bcm4908-enet-allow-dsa-tag-mtu.patch b/target/linux/bcm4908/patches-6.18/305-net-bcm4908-enet-allow-dsa-tag-mtu.patch
new file mode 100644
index 0000000..16dbfe7
--- /dev/null
+++ b/target/linux/bcm4908/patches-6.18/305-net-bcm4908-enet-allow-dsa-tag-mtu.patch
@@ -0,0 +1,45 @@
+Subject: [PATCH] net: bcm4908_enet: allow and report DSA conduit MTU
+
+The BCM4908 Ethernet controller is a conduit for the integrated switch.
+Its 1500-byte limit rejects the 1504-byte MTU requested by DSA for a
+standard Ethernet frame plus its four-byte Broadcom tag.
+
+Use the driver's existing six-byte maximum Broadcom tag allowance as
+headroom above ETH_DATA_LEN. This shared controller limit accommodates
+the supported tag sizes across BCM4908 boards without increasing the
+user-port MTU. Receive buffer sizing follows ENET_MTU_MAX; the existing
+frame overhead allowance remains conservative.
+
+Record successful MTU changes in netdev-&gt;mtu so DSA sees the applied size.
+EBG15 RAM tests verify conduit MTU 1504 and full-size traffic both ways;
+sibling hardware validation remains outstanding.
+
+Verified on EBG15: conduit MTU 1504 accepted, user ports remain at 1500,
+and full-size frames with the DSA conduit tag (Broadcom tag) pass in
+both directions. End-to-end 802.1Q VLAN traffic is not covered by this
+test. The advertised controller maximum becomes 1506; the 1506
+maximum and up/down transitions after MTU changes remain distinct
+runtime test items. Runtime validation on other BCM4908 boards is
+still outstanding; builds alone do not cover it.
+
+---
+--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
++++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
+@@ -31,8 +31,8 @@
+ 						 ENET_DMA_CH_CFG_INT_BUFF_DONE)
+ #define ENET_DMA_MAX_BURST_LEN			8 /* in 64 bit words */
+ 
+-#define ENET_MTU_MAX				ETH_DATA_LEN /* Is it possible to support 2044? */
+ #define BRCM_MAX_TAG_LEN			6
++#define ENET_MTU_MAX				(ETH_DATA_LEN + BRCM_MAX_TAG_LEN)
+ #define ENET_MAX_ETH_OVERHEAD			(ETH_HLEN + BRCM_MAX_TAG_LEN + VLAN_HLEN + \
+ 						 ETH_FCS_LEN + 4) /* 32 */
+ 
+@@ -694,6 +694,7 @@ static int bcm4908_enet_change_mtu(struc
+ 	struct bcm4908_enet *enet = netdev_priv(netdev);
+ 
+ 	bcm4908_enet_set_mtu(enet, new_mtu);
++	WRITE_ONCE(netdev-&gt;mtu, new_mtu);
+ 
+ 	return 0;
+ }
</code></pre>
<h3><code>0004-leds.patch</code></h3>
<pre><code class="language-diff">diff --git a/target/linux/bcm4908/patches-6.18/306-leds-bcm63138-add-full-brightness-property.patch b/target/linux/bcm4908/patches-6.18/306-leds-bcm63138-add-full-brightness-property.patch
new file mode 100644
index 0000000..1d158f6
--- /dev/null
+++ b/target/linux/bcm4908/patches-6.18/306-leds-bcm63138-add-full-brightness-property.patch
@@ -0,0 +1,147 @@
+Subject: [PATCH] leds: bcm63138: configure software brightness and default state
+
+Read brcm,full-brightness as a standard DT cell and restrict it to the
+existing supported range 1..9. Initialize software brightness and output
+before registering the LED so default triggers take precedence. Serialize
+shared register updates with already registered channels.
+
+Preserve steady software-controlled LEDs for default-state keep, without
+XORing the logical SW_DATA enable with electrical pad polarity. Do not
+clear controller-wide channel ownership or polarity during probe. Reject
+unsupported hardware/blinking, polarity-mismatched, or out-of-range boot
+states for keep rather than guessing their output. Such channels remain
+unregistered; their per-channel registers are left untouched. Pinctrl is
+still selected through the registered LED device; no output writes follow
+registration. Hardware KEEP and serial boot-state preservation require
+further hardware evidence and are not claimed by this patch.
+---
+--- a/drivers/leds/blink/leds-bcm63138.c
++++ b/drivers/leds/blink/leds-bcm63138.c
+@@ -60,6 +60,7 @@ struct bcm63138_led {
+ 	struct led_classdev cdev;
+ 	u32 pin;
+ 	bool active_low;
++	u8 full_brightness;
+ };
+ 
+ /*
+@@ -201,7 +202,8 @@ static void bcm63138_leds_create_led(str
+ 	struct device *dev = leds-&gt;dev;
+ 	struct bcm63138_led *led;
+ 	struct pinctrl *pinctrl;
+-	u32 bit;
++	enum led_default_state state;
++	u32 bit, value;
+ 	int err;
+ 
+ 	led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+@@ -224,9 +226,68 @@ static void bcm63138_leds_create_led(str
+ 
+ 	led-&gt;active_low = of_property_read_bool(np, "active-low");
+ 
+-	led-&gt;cdev.max_brightness = BCM63138_MAX_BRIGHTNESS;
++	led-&gt;full_brightness = BCM63138_MAX_BRIGHTNESS;
++	if (of_find_property(np, "brcm,full-brightness", NULL)) {
++		err = of_property_read_u32(np, "brcm,full-brightness", &amp;value);
++		if (err || !value || value &gt; BCM63138_MAX_BRIGHTNESS) {
++			dev_err(dev, "Invalid brcm,full-brightness in %pOF\n", np);
++			goto err_free;
++		}
++		led-&gt;full_brightness = value;
++	}
++
++	led-&gt;cdev.max_brightness = led-&gt;full_brightness;
+ 	led-&gt;cdev.brightness_set = bcm63138_leds_brightness_set;
+ 	led-&gt;cdev.blink_set = bcm63138_leds_blink_set;
++	state = led_init_default_state_get(init_data.fwnode);
++	bit = BIT(led-&gt;pin);
++
++	/* Initialize before registration can activate a default trigger.
++	 * Registered siblings may already be updating shared registers.
++	 */
++	scoped_guard(spinlock_irqsave, &amp;leds-&gt;lock) {
++		if (state == LEDS_DEFSTATE_KEEP) {
++			u32 shift = (led-&gt;pin % BCM63138_LEDS_PER_REG) *
++				    BCM63138_LED_BITS;
++			u32 offset = (led-&gt;pin / BCM63138_LEDS_PER_REG) * 4;
++			u32 hw, flash, polarity;
++
++			hw = bcm63138_leds_read(leds, BCM63138_HW_LED_EN);
++			flash = bcm63138_leds_read(leds,
++					BCM63138_FLASH_RATE_CTRL1 + offset);
++			polarity = bcm63138_leds_read(leds,
++					   BCM63138_PARALLEL_LED_POLARITY);
++			/* Hardware/blink status decoding is not established. Do
++			 * not guess or modify those bootloader-owned channels.
++			 */
++			if ((hw &amp; bit) || ((flash &gt;&gt; shift) &amp; BCM63138_LED_MASK) ||
++			    (!!(polarity &amp; bit) == led-&gt;active_low)) {
++				dev_err(dev, "Cannot keep hardware/blinking or mismatched-polarity LED %pOF\n",
++					np);
++				goto err_free;
++			}
++			/* SW_DATA is a logical enable, independent of pad polarity. */
++			if (bcm63138_leds_read(leds, BCM63138_SW_DATA) &amp; bit) {
++				value = bcm63138_leds_read(leds,
++						 BCM63138_BRIGHT_CTRL1 + offset);
++				value = (value &gt;&gt; shift) &amp; BCM63138_LED_MASK;
++				if (!value || value &gt; led-&gt;full_brightness) {
++					dev_err(dev, "Cannot keep LED brightness in %pOF\n", np);
++					goto err_free;
++				}
++				led-&gt;cdev.brightness = value;
++			}
++		} else {
++			led-&gt;cdev.brightness = state == LEDS_DEFSTATE_ON ?
++				led-&gt;full_brightness : LED_OFF;
++			bcm63138_leds_update_bits(leds, BCM63138_PARALLEL_LED_POLARITY,
++						  bit, led-&gt;active_low ? 0 : bit);
++			bcm63138_leds_update_bits(leds, BCM63138_HW_LED_EN, bit, 0);
++			bcm63138_leds_set_flash_rate(leds, led, 0);
++			bcm63138_leds_set_bright(leds, led, led-&gt;full_brightness);
++			bcm63138_leds_enable_led(leds, led, led-&gt;cdev.brightness);
++		}
++	}
+ 
+ 	err = devm_led_classdev_register_ext(dev, &amp;led-&gt;cdev, &amp;init_data);
+ 	if (err) {
+@@ -240,12 +301,6 @@ static void bcm63138_leds_create_led(str
+ 			 np, PTR_ERR(pinctrl));
+ 	}
+ 
+-	bit = BIT(led-&gt;pin);
+-	bcm63138_leds_update_bits(leds, BCM63138_PARALLEL_LED_POLARITY, bit,
+-				  led-&gt;active_low ? 0 : bit);
+-	bcm63138_leds_update_bits(leds, BCM63138_HW_LED_EN, bit, 0);
+-	bcm63138_leds_set_flash_rate(leds, led, 0);
+-	bcm63138_leds_enable_led(leds, led, led-&gt;cdev.brightness);
+ 
+ 	return;
+ 
+@@ -281,9 +336,6 @@ static int bcm63138_leds_probe(struct pl
+ 	bcm63138_leds_write(leds, BCM63138_GLB_CTRL,
+ 			    BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL |
+ 			    BCM63138_GLB_CTRL_SERIAL_LED_EN_POL);
+-	bcm63138_leds_write(leds, BCM63138_HW_LED_EN, 0);
+-	bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0);
+-	bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0);
+ 
+ 	for_each_available_child_of_node_scoped(np, child) {
+ 		bcm63138_leds_create_led(leds, child);
+--- a/Documentation/devicetree/bindings/leds/leds-bcm63138.yaml
++++ b/Documentation/devicetree/bindings/leds/leds-bcm63138.yaml
+@@ -62,6 +62,14 @@ patternProperties:
+         maxItems: 1
+         description: LED pin number
+ 
++      brcm,full-brightness:
++        $ref: /schemas/types.yaml#/definitions/uint32
++        minimum: 1
++        maximum: 9
++        description:
++          Maximum software brightness, within the range supported by
++          this driver. Defaults to 9.
++
+     required:
+       - reg
+ 
</code></pre>
<h3><code>0005-pinctrl.patch</code></h3>
<pre><code class="language-diff">diff --git a/target/linux/bcm4908/patches-6.18/307-pinctrl-bcm4908-add-gpio-function.patch b/target/linux/bcm4908/patches-6.18/307-pinctrl-bcm4908-add-gpio-function.patch
new file mode 100644
index 0000000..cfeae73
--- /dev/null
+++ b/target/linux/bcm4908/patches-6.18/307-pinctrl-bcm4908-add-gpio-function.patch
@@ -0,0 +1,271 @@
+Subject: [PATCH] pinctrl: bcm4908: add gpio function
+
+The vendor pinmux uses function 4 as GPIO mode (gpio-mux = &lt;4&gt; in the
+stock DTS). Mainline exposes LED, UART, I2C, I2S and NAND functions but
+not GPIO. Some boards (e.g. ASUS EBG15) have GPIO consumers on pins whose
+boot-time mux is not GPIO, so the GPIO function must be selectable from
+DT.
+
+Add per-pin GPIO groups for the GPIO A bank (pins 0-31) and expose them
+through a single "gpio" function.
+
+This covers bank A only and does not automatically mux every GPIO
+request: consumers still need the relevant pinctrl state. The binding's
+groups:maxItems=2 means one state can reference at most two groups, so
+larger sets need separate states; the EBG15's two-line Bluetooth use
+fits this limit.
+
+--- a/Documentation/devicetree/bindings/pinctrl/brcm,bcm4908-pinctrl.yaml
++++ b/Documentation/devicetree/bindings/pinctrl/brcm,bcm4908-pinctrl.yaml
+@@ -32,13 +32,45 @@ patternProperties:
+                 led_17, led_18, led_19, led_20, led_21, led_22, led_23, led_24,
+                 led_25, led_26, led_27, led_28, led_29, led_30, led_31,
+                 hs_uart, i2c, i2s, nand_ctrl, nand_data, emmc_ctrl, usb0_pwr,
+-                usb1_pwr ]
++                usb1_pwr, gpio ]
+ 
+       groups:
+         minItems: 1
+         maxItems: 2
+         items:
+-          enum: [ led_0_grp_a, led_1_grp_a, led_2_grp_a, led_3_grp_a,
++          enum: [ gpio_0_grp_a,
++                  gpio_1_grp_a,
++                  gpio_2_grp_a,
++                  gpio_3_grp_a,
++                  gpio_4_grp_a,
++                  gpio_5_grp_a,
++                  gpio_6_grp_a,
++                  gpio_7_grp_a,
++                  gpio_8_grp_a,
++                  gpio_9_grp_a,
++                  gpio_10_grp_a,
++                  gpio_11_grp_a,
++                  gpio_12_grp_a,
++                  gpio_13_grp_a,
++                  gpio_14_grp_a,
++                  gpio_15_grp_a,
++                  gpio_16_grp_a,
++                  gpio_17_grp_a,
++                  gpio_18_grp_a,
++                  gpio_19_grp_a,
++                  gpio_20_grp_a,
++                  gpio_21_grp_a,
++                  gpio_22_grp_a,
++                  gpio_23_grp_a,
++                  gpio_24_grp_a,
++                  gpio_25_grp_a,
++                  gpio_26_grp_a,
++                  gpio_27_grp_a,
++                  gpio_28_grp_a,
++                  gpio_29_grp_a,
++                  gpio_30_grp_a,
++                  gpio_31_grp_a,
++                  led_0_grp_a, led_1_grp_a, led_2_grp_a, led_3_grp_a,
+                   led_4_grp_a, led_5_grp_a, led_6_grp_a, led_7_grp_a,
+                   led_8_grp_a, led_9_grp_a, led_10_grp_a, led_10_grp_b,
+                   led_11_grp_a, led_11_grp_b, led_12_grp_a, led_12_grp_b,
+--- a/drivers/pinctrl/bcm/pinctrl-bcm4908.c
++++ b/drivers/pinctrl/bcm/pinctrl-bcm4908.c
+@@ -169,6 +169,135 @@ static const struct bcm4908_pinctrl_pin_
+ 	{ 31, 3 },
+ };
+ 
++static const struct bcm4908_pinctrl_pin_setup gpio_0_pins_a[] = {
++	{ 0, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_1_pins_a[] = {
++	{ 1, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_2_pins_a[] = {
++	{ 2, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_3_pins_a[] = {
++	{ 3, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_4_pins_a[] = {
++	{ 4, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_5_pins_a[] = {
++	{ 5, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_6_pins_a[] = {
++	{ 6, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_7_pins_a[] = {
++	{ 7, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_8_pins_a[] = {
++	{ 8, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_9_pins_a[] = {
++	{ 9, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_10_pins_a[] = {
++	{ 10, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_11_pins_a[] = {
++	{ 11, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_12_pins_a[] = {
++	{ 12, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_13_pins_a[] = {
++	{ 13, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_14_pins_a[] = {
++	{ 14, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_15_pins_a[] = {
++	{ 15, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_16_pins_a[] = {
++	{ 16, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_17_pins_a[] = {
++	{ 17, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_18_pins_a[] = {
++	{ 18, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_19_pins_a[] = {
++	{ 19, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_20_pins_a[] = {
++	{ 20, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_21_pins_a[] = {
++	{ 21, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_22_pins_a[] = {
++	{ 22, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_23_pins_a[] = {
++	{ 23, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_24_pins_a[] = {
++	{ 24, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_25_pins_a[] = {
++	{ 25, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_26_pins_a[] = {
++	{ 26, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_27_pins_a[] = {
++	{ 27, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_28_pins_a[] = {
++	{ 28, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_29_pins_a[] = {
++	{ 29, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_30_pins_a[] = {
++	{ 30, 4 },
++};
++
++static const struct bcm4908_pinctrl_pin_setup gpio_31_pins_a[] = {
++	{ 31, 4 },
++};
++
++
+ static const struct bcm4908_pinctrl_pin_setup led_10_pins_b[] = {
+ 	{ 8, 2 },
+ };
+@@ -288,6 +417,38 @@ static const struct bcm4908_pinctrl_grp
+ 	{ "led_29_grp_a", led_29_pins_a, ARRAY_SIZE(led_29_pins_a) },
+ 	{ "led_30_grp_a", led_30_pins_a, ARRAY_SIZE(led_30_pins_a) },
+ 	{ "led_31_grp_a", led_31_pins_a, ARRAY_SIZE(led_31_pins_a) },
++	{ "gpio_0_grp_a", gpio_0_pins_a, ARRAY_SIZE(gpio_0_pins_a) },
++	{ "gpio_1_grp_a", gpio_1_pins_a, ARRAY_SIZE(gpio_1_pins_a) },
++	{ "gpio_2_grp_a", gpio_2_pins_a, ARRAY_SIZE(gpio_2_pins_a) },
++	{ "gpio_3_grp_a", gpio_3_pins_a, ARRAY_SIZE(gpio_3_pins_a) },
++	{ "gpio_4_grp_a", gpio_4_pins_a, ARRAY_SIZE(gpio_4_pins_a) },
++	{ "gpio_5_grp_a", gpio_5_pins_a, ARRAY_SIZE(gpio_5_pins_a) },
++	{ "gpio_6_grp_a", gpio_6_pins_a, ARRAY_SIZE(gpio_6_pins_a) },
++	{ "gpio_7_grp_a", gpio_7_pins_a, ARRAY_SIZE(gpio_7_pins_a) },
++	{ "gpio_8_grp_a", gpio_8_pins_a, ARRAY_SIZE(gpio_8_pins_a) },
++	{ "gpio_9_grp_a", gpio_9_pins_a, ARRAY_SIZE(gpio_9_pins_a) },
++	{ "gpio_10_grp_a", gpio_10_pins_a, ARRAY_SIZE(gpio_10_pins_a) },
++	{ "gpio_11_grp_a", gpio_11_pins_a, ARRAY_SIZE(gpio_11_pins_a) },
++	{ "gpio_12_grp_a", gpio_12_pins_a, ARRAY_SIZE(gpio_12_pins_a) },
++	{ "gpio_13_grp_a", gpio_13_pins_a, ARRAY_SIZE(gpio_13_pins_a) },
++	{ "gpio_14_grp_a", gpio_14_pins_a, ARRAY_SIZE(gpio_14_pins_a) },
++	{ "gpio_15_grp_a", gpio_15_pins_a, ARRAY_SIZE(gpio_15_pins_a) },
++	{ "gpio_16_grp_a", gpio_16_pins_a, ARRAY_SIZE(gpio_16_pins_a) },
++	{ "gpio_17_grp_a", gpio_17_pins_a, ARRAY_SIZE(gpio_17_pins_a) },
++	{ "gpio_18_grp_a", gpio_18_pins_a, ARRAY_SIZE(gpio_18_pins_a) },
++	{ "gpio_19_grp_a", gpio_19_pins_a, ARRAY_SIZE(gpio_19_pins_a) },
++	{ "gpio_20_grp_a", gpio_20_pins_a, ARRAY_SIZE(gpio_20_pins_a) },
++	{ "gpio_21_grp_a", gpio_21_pins_a, ARRAY_SIZE(gpio_21_pins_a) },
++	{ "gpio_22_grp_a", gpio_22_pins_a, ARRAY_SIZE(gpio_22_pins_a) },
++	{ "gpio_23_grp_a", gpio_23_pins_a, ARRAY_SIZE(gpio_23_pins_a) },
++	{ "gpio_24_grp_a", gpio_24_pins_a, ARRAY_SIZE(gpio_24_pins_a) },
++	{ "gpio_25_grp_a", gpio_25_pins_a, ARRAY_SIZE(gpio_25_pins_a) },
++	{ "gpio_26_grp_a", gpio_26_pins_a, ARRAY_SIZE(gpio_26_pins_a) },
++	{ "gpio_27_grp_a", gpio_27_pins_a, ARRAY_SIZE(gpio_27_pins_a) },
++	{ "gpio_28_grp_a", gpio_28_pins_a, ARRAY_SIZE(gpio_28_pins_a) },
++	{ "gpio_29_grp_a", gpio_29_pins_a, ARRAY_SIZE(gpio_29_pins_a) },
++	{ "gpio_30_grp_a", gpio_30_pins_a, ARRAY_SIZE(gpio_30_pins_a) },
++	{ "gpio_31_grp_a", gpio_31_pins_a, ARRAY_SIZE(gpio_31_pins_a) },
+ 	{ "led_10_grp_b", led_10_pins_b, ARRAY_SIZE(led_10_pins_b) },
+ 	{ "led_11_grp_b", led_11_pins_b, ARRAY_SIZE(led_11_pins_b) },
+ 	{ "led_12_grp_b", led_12_pins_b, ARRAY_SIZE(led_12_pins_b) },
+@@ -346,6 +507,18 @@ static const char * const led_28_groups[
+ static const char * const led_29_groups[] = { "led_29_grp_a" };
+ static const char * const led_30_groups[] = { "led_30_grp_a" };
+ static const char * const led_31_groups[] = { "led_31_grp_a", "led_31_grp_b" };
++
++static const char * const gpio_groups[] = {
++	"gpio_0_grp_a", "gpio_1_grp_a", "gpio_2_grp_a", "gpio_3_grp_a",
++	"gpio_4_grp_a", "gpio_5_grp_a", "gpio_6_grp_a", "gpio_7_grp_a",
++	"gpio_8_grp_a", "gpio_9_grp_a", "gpio_10_grp_a", "gpio_11_grp_a",
++	"gpio_12_grp_a", "gpio_13_grp_a", "gpio_14_grp_a", "gpio_15_grp_a",
++	"gpio_16_grp_a", "gpio_17_grp_a", "gpio_18_grp_a", "gpio_19_grp_a",
++	"gpio_20_grp_a", "gpio_21_grp_a", "gpio_22_grp_a", "gpio_23_grp_a",
++	"gpio_24_grp_a", "gpio_25_grp_a", "gpio_26_grp_a", "gpio_27_grp_a",
++	"gpio_28_grp_a", "gpio_29_grp_a", "gpio_30_grp_a", "gpio_31_grp_a"
++};
++
+ static const char * const hs_uart_groups[] = { "hs_uart_grp" };
+ static const char * const i2c_groups[] = { "i2c_grp_a", "i2c_grp_b" };
+ static const char * const i2s_groups[] = { "i2s_grp" };
+@@ -388,6 +561,7 @@ static const struct bcm4908_pinctrl_func
+ 	{ "led_29", led_29_groups, ARRAY_SIZE(led_29_groups) },
+ 	{ "led_30", led_30_groups, ARRAY_SIZE(led_30_groups) },
+ 	{ "led_31", led_31_groups, ARRAY_SIZE(led_31_groups) },
++	{ "gpio", gpio_groups, ARRAY_SIZE(gpio_groups) },
+ 	{ "hs_uart", hs_uart_groups, ARRAY_SIZE(hs_uart_groups) },
+ 	{ "i2c", i2c_groups, ARRAY_SIZE(i2c_groups) },
+ 	{ "i2s", i2s_groups, ARRAY_SIZE(i2s_groups) },
</code></pre>
<h3><code>0006-image.patch</code></h3>
<pre><code class="language-diff">diff --git a/target/linux/bcm4908/image/Makefile b/target/linux/bcm4908/image/Makefile
index 678881f..46d9e10 100644
--- a/target/linux/bcm4908/image/Makefile
+++ b/target/linux/bcm4908/image/Makefile
@@ -18,6 +18,7 @@ define Build/bootfs
 	mv $@.tmp $@
 	sed -i "s=\$${kernel}=$@=" $(KDIR)/bootfs*.its*
 	PATH=$(LINUX_DIR)/scripts/dtc:$(PATH) mkimage -f $(KDIR)/bootfs-bcm4908.its $(KDIR)/bootfs-bcm4908.itb
+	PATH=$(LINUX_DIR)/scripts/dtc:$(PATH) mkimage -f $(KDIR)/bootfs-bcm4906.its $(KDIR)/bootfs-bcm4906.itb
 	PATH=$(LINUX_DIR)/scripts/dtc:$(PATH) mkimage -f $(KDIR)/bootfs-bcm4912.its $(KDIR)/bootfs-bcm4912.itb
 endef
 
@@ -97,6 +98,18 @@ define Device/asus_gt-ax6000
 endef
 # TARGET_DEVICES += asus_gt-ax6000
 
+define Device/asus_ebg15
+  DEVICE_VENDOR := ASUS
+  DEVICE_MODEL := EBG15
+  DEVICE_DTS := broadcom/bcmbca/bcm4906-asus-ebg15
+  KERNEL := kernel-bin | bootfs
+  IMAGES := pkgtb
+  IMAGE/pkgtb := append-rootfs | pkgtb
+  SOC := bcm4906
+  DEVICE_PACKAGES += kmod-ath3k
+endef
+TARGET_DEVICES += asus_ebg15
+
 define Device/netgear_r8000p
   DEVICE_VENDOR := Netgear
   DEVICE_MODEL := R8000P
diff --git a/target/linux/bcm4908/image/bootfs-bcm4906.its b/target/linux/bcm4908/image/bootfs-bcm4906.its
new file mode 100644
index 0000000..a43d045
--- /dev/null
+++ b/target/linux/bcm4908/image/bootfs-bcm4906.its
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/include/ "bootfs.itsi"
+
+/ {
+	images {
+		uboot {
+			data = /incbin/("${images_dir}/u-boot/u-boot-bcm4908.bin");
+		};
+
+		fdt_uboot {
+			data = /incbin/("${images_dir}/u-boot/u-boot-bcm4908.dtb");
+		};
+
+		fdt_linux_EBG15 {
+			description = "dtb";
+			data = /incbin/("${dts_dir}/broadcom/bcmbca/bcm4906-asus-ebg15.dtb");
+			arch = "arm64";
+			type = "flat_dt";
+			compression = "none";
+
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+	};
+
+	configurations {
+		conf_ub_EBG15 {
+			description = "EBG15";
+			fdt = "fdt_uboot";
+			loadables = "atf", "uboot";
+		};
+
+		conf_lx_EBG15 {
+			description = "BRCM 63xxx linux";
+			kernel = "kernel";
+			fdt = "fdt_linux_EBG15";
+		};
+	};
+};
diff --git a/target/linux/bcm4908/image/bootfs.itsi b/target/linux/bcm4908/image/bootfs.itsi
index ceaaf10..8338ba8 100644
--- a/target/linux/bcm4908/image/bootfs.itsi
+++ b/target/linux/bcm4908/image/bootfs.itsi
@@ -42,8 +42,8 @@
 			os = "linux";
 			arch = "arm64";
 			compression = "lzma";
-			load = &lt;0x80000&gt;;
-			entry = &lt;0x80000&gt;;
+			load = &lt;0x200000&gt;;
+			entry = &lt;0x200000&gt;;
 
 			hash-1 {
 				algo = "sha256";
diff --git a/target/linux/bcm4908/image/pkgtb-bcm4906.its b/target/linux/bcm4908/image/pkgtb-bcm4906.its
new file mode 100644
index 0000000..ed60121
--- /dev/null
+++ b/target/linux/bcm4908/image/pkgtb-bcm4906.its
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+/ {
+	description = "Broadcom image upgrade package tree binary";
+	#address-cells = &lt;1&gt;;
+
+	images {
+		bootfs_4908_a0+ {
+			description = "bootfs";
+			data = /incbin/("${bootfs}");
+			type = "multi";
+			compression = "none";
+
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+
+		nand_squashfs {
+			description = "rootfs";
+			data = /incbin/("${rootfs}");
+			type = "filesystem";
+			compression = "none";
+
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+	};
+
+	configurations {
+		default = "conf_4908_a0+_nand_squashfs";
+
+		conf_4908_a0+_nand_squashfs {
+			description = "Brcm Image Bundle";
+			bootfs = "bootfs_4908_a0+";
+			rootfs = "nand_squashfs";
+			/* BCA SDK identifies BCM4906 boards as chip 4908. */
+			compatible = "flash=nand;chip=4908;rev=a0+;ip=ipv6,ipv4;ddr=ddr3;fstype=squashfs";
+		};
+	};
+};
</code></pre>
<h3><code>0007-board.patch</code></h3>
<pre><code class="language-diff">diff --git a/target/linux/bcm4908/base-files/etc/board.d/01_leds b/target/linux/bcm4908/base-files/etc/board.d/01_leds
new file mode 100644
index 0000000..1246d53
--- /dev/null
+++ b/target/linux/bcm4908/base-files/etc/board.d/01_leds
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+
+. /lib/functions/system.sh
+. /lib/functions/uci-defaults.sh
+
+bcm4908_setup_leds()
+{
+	local board="$1"
+
+	case "$board" in
+	asus,ebg15)
+		ucidef_set_led_netdev "lan1" "LAN1" "green:lan1" "lan1" "link"
+		ucidef_set_led_netdev "lan2" "LAN2" "green:lan2" "lan2" "link"
+		ucidef_set_led_netdev "lan3" "LAN3" "green:lan3" "lan3" "link"
+		ucidef_set_led_netdev "lan4" "LAN4" "green:lan4" "lan4" "link"
+		ucidef_set_led_netdev "wan" "WAN" "green:wan" "wan" "link"
+		ucidef_set_led_netdev "lan1act" "LAN1_ACT" "orange:lan1" "lan1" "tx rx"
+		ucidef_set_led_netdev "lan2act" "LAN2_ACT" "orange:lan2" "lan2" "tx rx"
+		ucidef_set_led_netdev "lan3act" "LAN3_ACT" "orange:lan3" "lan3" "tx rx"
+		ucidef_set_led_netdev "lan4act" "LAN4_ACT" "orange:lan4" "lan4" "tx rx"
+		ucidef_set_led_netdev "wanact" "WAN_ACT" "orange:wan" "wan" "tx rx"
+		ucidef_set_led_usbport "usb" "USB" "white:usb" "usb1-port2"
+		ucidef_set_led_default "power" "POWER" "white:power" "1"
+		;;
+	esac
+}
+
+board_config_update
+board=$(board_name)
+bcm4908_setup_leds "$board"
+board_config_flush
+
+exit 0
diff --git a/target/linux/bcm4908/base-files/etc/board.d/02_network b/target/linux/bcm4908/base-files/etc/board.d/02_network
index 3ab22a9..f8e2019 100644
--- a/target/linux/bcm4908/base-files/etc/board.d/02_network
+++ b/target/linux/bcm4908/base-files/etc/board.d/02_network
@@ -11,6 +11,10 @@ bcm4908_setup_interfaces()
 	asus,gt-ac5300)
 		ucidef_set_interface_lan "lan1 lan2 lan5 lan6 sw"
 		;;
+	asus,ebg15)
+		ucidef_set_interface_wan "wan"
+		ucidef_set_interface_lan "lan1 lan2 lan3 lan4"
+		;;
 	*)
 		ucidef_set_interfaces_lan_wan "lan1 lan2 lan3 lan4" "wan"
 		;;
diff --git a/target/linux/bcm4908/base-files/etc/fw_env.config b/target/linux/bcm4908/base-files/etc/fw_env.config
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/target/linux/bcm4908/base-files/etc/fw_env.config
@@ -0,0 +1 @@
+
diff --git a/target/linux/bcm4908/base-files/etc/init.d/fw_env b/target/linux/bcm4908/base-files/etc/init.d/fw_env
new file mode 100755
index 0000000..7e74782
--- /dev/null
+++ b/target/linux/bcm4908/base-files/etc/init.d/fw_env
@@ -0,0 +1,11 @@
+#!/bin/sh /etc/rc.common
+
+START=10
+
+boot() {
+	# The preinit bind mount normally provides a valid config. Refresh
+	# the squashfs placeholder only if it was lost (or /tmp regenerated).
+	[ -s /etc/fw_env.config ] &amp;&amp; return
+	[ -s /tmp/fw_env.config ] || return
+	cp /tmp/fw_env.config /etc/fw_env.config
+}
diff --git a/target/linux/bcm4908/base-files/etc/init.d/overlay-ro b/target/linux/bcm4908/base-files/etc/init.d/overlay-ro
new file mode 100755
index 0000000..21f2f00
--- /dev/null
+++ b/target/linux/bcm4908/base-files/etc/init.d/overlay-ro
@@ -0,0 +1,17 @@
+#!/bin/sh /etc/rc.common
+
+START=99
+STOP=89
+
+shutdown() {
+	# Remount the UBIFS overlay read-only so the journal is closed
+	# cleanly before reboot. Without this every next mount logs
+	# "recovery needed" (verified on hardware: with the remount the
+	# following boot mounts without recovery).
+	grep -q ' /overlay ' /proc/mounts || return 0
+	if mount -o remount,ro /overlay 2&gt;/dev/null; then
+		echo "overlay-ro: remounted read-only" &gt; /dev/kmsg
+	else
+		echo "overlay-ro: remount failed rc=$?" &gt; /dev/kmsg
+	fi
+}
diff --git a/target/linux/bcm4908/base-files/etc/inittab b/target/linux/bcm4908/base-files/etc/inittab
new file mode 100644
index 0000000..b169c82
--- /dev/null
+++ b/target/linux/bcm4908/base-files/etc/inittab
@@ -0,0 +1,3 @@
+::sysinit:/etc/init.d/rcS S boot
+::shutdown:/etc/init.d/rcS K shutdown
+ttyS0::askfirst:/usr/libexec/login.sh
diff --git a/target/linux/bcm4908/base-files/lib/preinit/05_fw_env b/target/linux/bcm4908/base-files/lib/preinit/05_fw_env
new file mode 100644
index 0000000..0f71502
--- /dev/null
+++ b/target/linux/bcm4908/base-files/lib/preinit/05_fw_env
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause
+
+# Generate /etc/fw_env.config for BCM4908 boards with BCA bootloader.
+# Registered as 05_fw_env so it runs before 10_indicate_preinit's
+# preinit_config_board, the earliest /bin/board_detect user that runs
+# /etc/board.d/05_fw_defaults and calls plain fw_printenv.
+#
+# During preinit /etc is still the read-only squashfs, so the config
+# is written to /tmp (tmpfs) and bind-mounted over the placeholder
+# /etc/fw_env.config that ships in the image. On writable root
+# (initramfs) it is copied directly instead.
+
+fw_env_setup() {
+	local size
+
+	[ -e /dev/ubi0_1 ] || return
+
+	size=$(dd if=/dev/ubi0_1 skip=4 bs=1 count=4 2&gt;/dev/null | \
+		hexdump -v -e '1/4 "%u"')
+	[ -n "$size" ] || return
+	[ "$size" -gt 8 ] &amp;&amp; [ "$size" -le 1048576 ] || return
+
+	dd if=/dev/ubi0_1 of=/tmp/env.body bs=1 skip=8 count="$size" 2&gt;/dev/null
+	printf '/tmp/env.body 0x0 0x%x 0x%x\n' "$size" "$size" &gt; /tmp/fw_env.config
+	# As root, [ -w /etc ] reports writable even on the read-only
+	# squashfs, so probe with an actual file creation.
+	if touch /etc/.fw_env_probe 2&gt;/dev/null; then
+		rm -f /etc/.fw_env_probe
+		cp /tmp/fw_env.config /etc/fw_env.config
+	elif [ -f /etc/fw_env.config ] &amp;&amp; ! grep -q ' /etc/fw_env.config ' /proc/mounts; then
+		mount -o bind /tmp/fw_env.config /etc/fw_env.config
+	fi
+}
+
+boot_hook_add preinit_main fw_env_setup
diff --git a/target/linux/bcm4908/patches-6.18/302-arm64-dts-broadcom-bcmbca-add-bcm4906-asus-ebg15.patch b/target/linux/bcm4908/patches-6.18/302-arm64-dts-broadcom-bcmbca-add-bcm4906-asus-ebg15.patch
new file mode 100644
index 0000000..2efd39f
--- /dev/null
+++ b/target/linux/bcm4908/patches-6.18/302-arm64-dts-broadcom-bcmbca-add-bcm4906-asus-ebg15.patch
@@ -0,0 +1,384 @@
+Subject: [PATCH] arm64: dts: broadcom: bcmbca: Add BCM4906 ASUS EBG15
+
+Add the ASUS EBG15 wired router with four LAN ports and WAN on SF2 port 7,
+using PHY addresses 8 through 12. All five ports have been verified with
+full-size traffic. Interface labels follow OpenWrt convention: WAN is
+housing port 1 and the LAN ports are lan1 through lan4.
+
+Unlike the CFE spin-table boot path used by other BCM4908 boards, this
+board's BCA firmware exposes PSCI through SMC. Override both CPU enable
+methods and remove their inherited release addresses. Match the stock
+first-MiB firmware reservation; both CPUs boot with this configuration.
+
+Select the UniMAC MDIO master and request the GMAC power domain required
+after BCA U-Boot handoff. The ethernet0 alias enables factory MAC fixup.
+
+Port LEDs use software channels with netdev triggers: green for link and
+orange for traffic, with GPIO A24 as the shared active-low enable. The
+driver's default full brightness is used. The power and USB LEDs are
+software channels too. The reset button (bank A pin 23, active-low,
+verified on hardware) is exposed as a polled key. GPIOs A15/A25 hold
+the Bluetooth radio enabled.
+
+Wire the USB PHY clocks (sw_usb, sw_usb3, usb0_freerun) to the
+peripheral clock, matching what the brcmstb-usb-phy driver looks up by
+name, and extend the binding to allow the third clock.
+
+---
+--- a/arch/arm64/boot/dts/broadcom/bcmbca/Makefile
++++ b/arch/arm64/boot/dts/broadcom/bcmbca/Makefile
+@@ -1,5 +1,6 @@
+ # SPDX-License-Identifier: GPL-2.0
+ dtb-$(CONFIG_ARCH_BCMBCA) += \
++				bcm4906-asus-ebg15.dtb \
+ 				bcm4906-netgear-r8000p.dtb \
+ 				bcm4906-tplink-archer-c2300-v1.dtb \
+ 				bcm4906-zyxel-ex3510b.dtb \
+--- /dev/null
++++ b/arch/arm64/boot/dts/broadcom/bcmbca/bcm4906-asus-ebg15.dts
+@@ -0,0 +1,293 @@
++// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
++
++#include "bcm4906.dtsi"
++
++#include &lt;dt-bindings/gpio/gpio.h&gt;
++#include &lt;dt-bindings/input/input.h&gt;
++#include &lt;dt-bindings/leds/common.h&gt;
++
++/ {
++	compatible = "asus,ebg15", "brcm,bcm4906", "brcm,bcm4908", "brcm,bcmbca";
++	model = "ASUS EBG15";
++
++	aliases {
++		ethernet0 = &amp;enet;
++	};
++
++	psci {
++		compatible = "arm,psci-0.2";
++		method = "smc";
++	};
++
++	memory@0 {
++		device_type = "memory";
++		reg = &lt;0x00 0x00 0x00 0x20000000&gt;;
++	};
++
++	gpio-keys-polled {
++		compatible = "gpio-keys-polled";
++		poll-interval = &lt;100&gt;;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_gpio_23_a&gt;;
++
++		key-reset {
++			label = "reset";
++			linux,code = &lt;KEY_RESTART&gt;;
++			gpios = &lt;&amp;gpio0 23 GPIO_ACTIVE_LOW&gt;;
++		};
++	};
++
++};
++
++/* Stock firmware reserves the first MiB for firmware. */
++&amp;{/reserved-memory/cfe-stub@0} {
++	reg = &lt;0x0 0x0 0x0 0x100000&gt;;
++};
++
++&amp;cpu0 {
++	enable-method = "psci";
++	/delete-property/ cpu-release-addr;
++};
++
++&amp;cpu1 {
++	enable-method = "psci";
++	/delete-property/ cpu-release-addr;
++};
++
++&amp;ports {
++	port@0 {
++		label = "lan4";
++	};
++
++	port@1 {
++		label = "lan3";
++	};
++
++	port@2 {
++		label = "lan2";
++	};
++
++	port@3 {
++		label = "lan1";
++	};
++
++	port@7 {
++		reg = &lt;7&gt;;
++		label = "wan";
++		phy-mode = "internal";
++		phy-handle = &lt;&amp;phy12&gt;;
++	};
++};
++
++&amp;enet {
++	power-domains = &lt;&amp;pmb BCM_PMB_GMAC&gt;;
++};
++
++&amp;leds {
++	led@24 {
++		reg = &lt;24&gt;;
++		color = &lt;LED_COLOR_ID_WHITE&gt;;
++		function = "ethall";
++		active-low;
++		default-state = "on";
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_24_a&gt;;
++	};
++
++	led@1 {
++		reg = &lt;1&gt;;
++		color = &lt;LED_COLOR_ID_GREEN&gt;;
++		function = "lan4";
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_1_a&gt;;
++	};
++
++	led@3 {
++		reg = &lt;3&gt;;
++		color = &lt;LED_COLOR_ID_GREEN&gt;;
++		function = "lan3";
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_3_a&gt;;
++	};
++
++	led@5 {
++		reg = &lt;5&gt;;
++		color = &lt;LED_COLOR_ID_GREEN&gt;;
++		function = "lan2";
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_5_a&gt;;
++	};
++
++	led@7 {
++		reg = &lt;7&gt;;
++		color = &lt;LED_COLOR_ID_GREEN&gt;;
++		function = "lan1";
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_7_a&gt;;
++	};
++
++	led@11 {
++		reg = &lt;11&gt;;
++		color = &lt;LED_COLOR_ID_GREEN&gt;;
++		function = LED_FUNCTION_WAN;
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_11_a&gt;;
++	};
++
++	led@16 {
++		reg = &lt;16&gt;;
++		color = &lt;LED_COLOR_ID_ORANGE&gt;;
++		function = "lan4";
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_16_a&gt;;
++	};
++
++	led@17 {
++		reg = &lt;17&gt;;
++		color = &lt;LED_COLOR_ID_ORANGE&gt;;
++		function = "lan3";
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_17_a&gt;;
++	};
++
++	led@18 {
++		reg = &lt;18&gt;;
++		color = &lt;LED_COLOR_ID_ORANGE&gt;;
++		function = "lan2";
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_18_a&gt;;
++	};
++
++	led@19 {
++		reg = &lt;19&gt;;
++		color = &lt;LED_COLOR_ID_ORANGE&gt;;
++		function = "lan1";
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_19_a&gt;;
++	};
++
++	led@21 {
++		reg = &lt;21&gt;;
++		color = &lt;LED_COLOR_ID_ORANGE&gt;;
++		function = LED_FUNCTION_WAN;
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_21_a&gt;;
++	};
++
++	led@12 {
++		reg = &lt;12&gt;;
++		color = &lt;LED_COLOR_ID_WHITE&gt;;
++		function = LED_FUNCTION_USB;
++		active-low;
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_12_a&gt;;
++		trigger-sources = &lt;&amp;ehci_port2&gt;, &lt;&amp;ohci_port2&gt;, &lt;&amp;xhci_port2&gt;;
++	};
++
++	led@22 {
++		reg = &lt;22&gt;;
++		color = &lt;LED_COLOR_ID_WHITE&gt;;
++		function = LED_FUNCTION_POWER;
++		active-low;
++		default-state = "on";
++		pinctrl-names = "default";
++		pinctrl-0 = &lt;&amp;pins_led_22_a&gt;;
++	};
++};
++
++&amp;mdio {
++	reg = &lt;0x405c0 0x8&gt;, &lt;0x40000 0x110&gt;;
++	reg-names = "mdio", "switchreg";
++};
++
++&amp;nand_controller {
++	brcm,wp-not-connected;
++	status = "okay";
++};
++
++&amp;nandcs {
++	/* Bootloader configures BCH ECC using straps */
++	brcm,nand-ecc-use-strap;
++	nand-on-flash-bbt;
++
++	#address-cells = &lt;1&gt;;
++	#size-cells = &lt;0&gt;;
++
++	partitions {
++		compatible = "fixed-partitions";
++		#address-cells = &lt;1&gt;;
++		#size-cells = &lt;1&gt;;
++
++		partition@0 {
++			label = "loader";
++			reg = &lt;0x0 0x100000&gt;;
++		};
++
++		partition@100000 {
++			label = "image";
++			reg = &lt;0x100000 0xfd00000&gt;;
++		};
++
++		partition@fe00000 {
++			label = "crashlog";
++			reg = &lt;0xfe00000 0x80000&gt;;
++		};
++	};
++};
++
++&amp;pinctrl {
++	pins_gpio_23_a: gpio_23-a-pins {
++		function = "gpio";
++		groups = "gpio_23_grp_a";
++	};
++
++	pins_bt_control: bt-control-pins {
++		function = "gpio";
++		groups = "gpio_15_grp_a", "gpio_25_grp_a";
++	};
++};
++
++&amp;gpio0 {
++	pinctrl-names = "default";
++	pinctrl-0 = &lt;&amp;pins_bt_control&gt;;
++
++	bt-disable-hog {
++		gpio-hog;
++		gpios = &lt;15 GPIO_ACTIVE_HIGH&gt;;
++		output-high;
++		line-name = "bt-disable-deasserted";
++	};
++
++	bt-reset-hog {
++		gpio-hog;
++		gpios = &lt;25 GPIO_ACTIVE_HIGH&gt;;
++		output-high;
++		line-name = "bt-reset-released";
++	};
++};
++
++&amp;usb_phy {
++	brcm,ioc = &lt;1&gt;;
++	status = "okay";
++};
++
++&amp;ehci {
++	status = "okay";
++};
++
++&amp;ohci {
++	status = "okay";
++};
++
++&amp;xhci {
++	status = "okay";
++};
+--- a/Documentation/devicetree/bindings/phy/brcm,brcmstb-usb-phy.yaml
++++ b/Documentation/devicetree/bindings/phy/brcm,brcmstb-usb-phy.yaml
+@@ -46,13 +46,14 @@ properties:
+ 
+   clocks:
+     minItems: 1
+-    maxItems: 2
++    maxItems: 3
+ 
+   clock-names:
+     minItems: 1
+     items:
+       - const: sw_usb
+       - const: sw_usb3
++      - const: usb0_freerun
+ 
+   interrupts:
+     description: wakeup interrupt
+--- a/Documentation/devicetree/bindings/arm/bcm/brcm,bcmbca.yaml
++++ b/Documentation/devicetree/bindings/arm/bcm/brcm,bcmbca.yaml
+@@ -32,6 +32,7 @@ properties:
+       - description: BCM4906 based boards
+         items:
+           - enum:
++              - asus,ebg15
+               - netgear,r8000p
+               - tplink,archer-c2300-v1
+               - zyxel,ex3510b
+--- a/arch/arm64/boot/dts/broadcom/bcmbca/bcm4908.dtsi
++++ b/arch/arm64/boot/dts/broadcom/bcmbca/bcm4908.dtsi
+@@ -150,6 +150,12 @@
+ 			brcm,has-xhci;
+ 			brcm,has-eohci;
+ 			#phy-cells = &lt;1&gt;;
++
++			/* All USB PHY clocks are derived from the peripheral
++			 * clock domain on BCM4908.
++			 */
++			clocks = &lt;&amp;periph_clk&gt;, &lt;&amp;periph_clk&gt;, &lt;&amp;periph_clk&gt;;
++			clock-names = "sw_usb", "sw_usb3", "usb0_freerun";
+ 			status = "disabled";
+ 		};
+ 
+@@ -448,7 +454,7 @@
+ 			status = "disabled";
+ 		};
+ 
+-		pinctrl@560 {
++		pinctrl: pinctrl@560 {
+ 			compatible = "brcm,bcm4908-pinctrl";
+ 			reg = &lt;0x560 0x10&gt;;
+ 
</code></pre>
<h3><code>0008-uboot.patch</code></h3>
<pre><code class="language-diff">diff --git a/package/boot/uboot-bcm4908/patches/300-reset_button-silently-skip-when-no-button-node.patch b/package/boot/uboot-bcm4908/patches/300-reset_button-silently-skip-when-no-button-node.patch
new file mode 100644
index 0000000..abda51c
--- /dev/null
+++ b/package/boot/uboot-bcm4908/patches/300-reset_button-silently-skip-when-no-button-node.patch
@@ -0,0 +1,33 @@
+Subject: [PATCH] bcmbca: reset_button: silently skip boards without button node
+
+reset_button_init() is called unconditionally from board_init(). On
+boards whose control FDT has no "brcm,buttons" compatible node (e.g.
+ASUS EBG15, which exposes its reset button as gpio-keys-polled to
+Linux), register_button_action_for_event() returns -ENODEV. The code
+then falls back to the hold action registration, which fails with
+-ENODEV as well, printing:
+
+    Failed to register rst_to_dflt action rc -19
+
+There is nothing to register in that case, so return success silently
+instead of falling back and printing an error.
+---
+ board/broadcom/bcmbca/reset_button.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/board/broadcom/bcmbca/reset_button.c
++++ b/board/broadcom/bcmbca/reset_button.c
+@@ -62,6 +62,13 @@ int reset_button_init(void)
+ 
+ 	ret = register_button_action_for_event("reset_button", "release", NULL,
+ 			btn_hook_reset_release);
++	/* -ENODEV means the control FDT has no brcm,buttons compatible
++	 * node or no reset_button subnode, so there is no action to
++	 * register. Do not fall back to the hold action and do not print
++	 * an error in that case.
++	 */
++	if (ret == -ENODEV)
++		return 0;
+ 	/* if board does not use release event for the reset button, fallback
+ 	   default hold event */
+ 	if (ret &lt;= 1) {
diff --git a/package/boot/uboot-bcm4908/patches/301-image-fdt-treat-already-reserved-regions-as-success.patch b/package/boot/uboot-bcm4908/patches/301-image-fdt-treat-already-reserved-regions-as-success.patch
new file mode 100644
index 0000000..62f1231
--- /dev/null
+++ b/package/boot/uboot-bcm4908/patches/301-image-fdt-treat-already-reserved-regions-as-success.patch
@@ -0,0 +1,66 @@
+Subject: [PATCH] image-fdt: reserve uncovered parts of overlapping regions
+
+The vendor LMB rejects partial overlaps. Reserve every uncovered gap
+in a temporary LMB and publish only after the complete range is
+protected. Fully covered reservations are harmless duplicates; partial
+overlap must not leave firmware memory available for relocation.
+
+Keep the original LMB unchanged on failure and retain the error message.
+---
+--- a/common/image-fdt.c
++++ b/common/image-fdt.c
+@@ -71,17 +71,45 @@ static const image_header_t *image_get_f
+ static void boot_fdt_reserve_region(struct lmb *lmb, uint64_t addr,
+ 				    uint64_t size)
+ {
+-	long ret;
++	struct lmb updated = *lmb;
++	uint64_t cursor = addr, end;
++	unsigned long i;
+ 
+-	ret = lmb_reserve(lmb, addr, size);
+-	if (ret &gt;= 0) {
+-		debug("   reserving fdt memory region: addr=%llx size=%llx\n",
+-		      (unsigned long long)addr, (unsigned long long)size);
+-	} else {
+-		puts("ERROR: reserving fdt memory region failed ");
+-		printf("(addr=%llx size=%llx)\n",
+-		       (unsigned long long)addr, (unsigned long long)size);
++	if (!size || addr &gt; (uint64_t)-1 - (size - 1))
++		goto error;
++	end = addr + size - 1;
++
++	/* Reserve uncovered gaps transactionally. The vendor LMB rejects
++	 * overlaps rather than merging them, and its ranges are sorted.
++	 */
++	for (i = 0; i &lt; lmb-&gt;reserved.cnt; i++) {
++		uint64_t base = lmb-&gt;reserved.region[i].base;
++		uint64_t len = lmb-&gt;reserved.region[i].size;
++		uint64_t last;
++
++		if (!len)
++			continue;
++		last = base + len - 1;
++		if (last &lt; cursor)
++			continue;
++		if (base &gt; end)
++			break;
++		if (base &gt; cursor &amp;&amp;
++		    lmb_reserve(&amp;updated, cursor, base - cursor) &lt; 0)
++			goto error;
++		if (last &gt;= end)
++			goto done;
++		cursor = last + 1;
+ 	}
++	if (lmb_reserve(&amp;updated, cursor, end - cursor + 1) &lt; 0)
++		goto error;
++done:
++	*lmb = updated;
++	return;
++error:
++	puts("ERROR: reserving fdt memory region failed ");
++	printf("(addr=%llx size=%llx)\n",
++	       (unsigned long long)addr, (unsigned long long)size);
+ }
+ 
+ /**
diff --git a/package/boot/uboot-bcm4908/patches/302-board_sdk-quietly-skip-mcb-sync-without-memory_controller.patch b/package/boot/uboot-bcm4908/patches/302-board_sdk-quietly-skip-mcb-sync-without-memory_controller.patch
new file mode 100644
index 0000000..5c591b5
--- /dev/null
+++ b/package/boot/uboot-bcm4908/patches/302-board_sdk-quietly-skip-mcb-sync-without-memory_controller.patch
@@ -0,0 +1,30 @@
+Subject: [PATCH] bcmbca: board_sdk: quietly skip MCB sync without memory_controller node
+
+update_memcfg() prints "Can't find /memory_controller node in board
+Device Tree" and fails on every board whose control DT does not carry
+the BCA-SDK /memory_controller node, e.g. ASUS EBG15. The MCB env was
+already set by the DDR initialization stage there, so there is nothing
+to sync. The node is legitimately absent on OpenWrt boards; make that
+case a silent no-op instead of an error.
+
+A node that exists but lacks the memcfg property is a real DT problem
+and keeps its error message.
+---
+ board/broadcom/bcmbca/board_sdk.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/board/broadcom/bcmbca/board_sdk.c
++++ b/board/broadcom/bcmbca/board_sdk.c
+@@ -264,8 +264,10 @@ static int update_memcfg(void)
+ 		int offset = fdt_path_offset(gd-&gt;fdt_blob, "/memory_controller");
+ 		if (offset &lt; 0)
+ 		{
+-			printf("Can't find /memory_controller node in board Device Tree\n");
+-			return -1;
++			/* OpenWrt boards don't carry the BCA SDK node; the MCB
++			 * env was already set by the DDR init stage. */
++			debug("No /memory_controller node in Device Tree, keeping env MCB\n");
++			return 0;
+ 		}
+ 		memcfg = fdt_getprop(gd-&gt;fdt_blob, offset, "memcfg", NULL);
+ 		if (memcfg == NULL)
</code></pre>
<h2>Validation and remaining work</h2>
<p>All eight outer patches were applied sequentially to an isolated copy of the original baseline files, then compared byte-for-byte with the submission inputs. The inner kernel/U-Boot patches previously passed clean source-application checks, and the resulting candidate passed the software and hardware checks described in the main article.</p>
<p>The kernel-driver changes also affect sibling BCM4908 devices; their runtime regression testing remains outstanding. First-install UART pinout and power-adapter details are incomplete. This post does not supply a released firmware image or a complete first-install procedure.</p>
]]></content:encoded></item><item><title><![CDATA[Bringing OpenWrt to the ASUS ExpertWiFi EBG15]]></title><description><![CDATA[Porting OpenWrt to the ASUS EBG15: BCM4906 boot quirks, Ethernet, LEDs, dual-bank upgrades, measured results, and remaining work.]]></description><link>https://ps2chiper.hashnode.dev/bringing-openwrt-to-the-asus-expertwifi-ebg15</link><guid isPermaLink="true">https://ps2chiper.hashnode.dev/bringing-openwrt-to-the-asus-expertwifi-ebg15</guid><category><![CDATA[OpenWRT]]></category><category><![CDATA[linux kernel]]></category><category><![CDATA[embedded linux]]></category><dc:creator><![CDATA[jason duhamell]]></dc:creator><pubDate>Sat, 24 Oct 2026 22:38:12 GMT</pubDate><content:encoded><![CDATA[<p><em>A BCM4906 porting diary: firmware handoff, MDIO selection, DSA headroom, LEDs, and testing a dual-bank upgrade.</em></p>
<p>My ASUS ExpertWiFi EBG15 is now running a development build of OpenWrt. Getting there involved more than adding a device tree: the board's BCA bootloader left hardware in a different state from the other BCM4908-family devices supported by the target.</p>
<p>This post records what the port needed and what has actually been tested. As of October 24, 2026, the local OpenWrt patch series and separate Linux upstream drafts are prepared but have not been submitted. This is a development report, rather than a released firmware installation guide.</p>
<p><strong>Patches:</strong> <a href="https://ps2chiper.hashnode.dev/asus-expertwifi-ebg15-the-openwrt-patch-series">Read and save the complete eight-part OpenWrt patch series</a>, including checksums and application instructions.</p>
<h2>The hardware</h2>
<p>The EBG15 is a wired router with no Wi-Fi radios. The working hardware inventory comes from the stock boot logs, device-tree inspection, and bench testing.</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Hardware</th>
</tr>
</thead>
<tbody><tr>
<td>SoC</td>
<td>Broadcom BCM4906, dual-core Brahma-B53, ARMv8</td>
</tr>
<tr>
<td>RAM</td>
<td>512 MiB DDR3</td>
</tr>
<tr>
<td>Flash</td>
<td>Winbond W29N02KV, 256 MiB SLC NAND</td>
</tr>
<tr>
<td>Ethernet</td>
<td>Five 1 GbE ports on the integrated SF2 switch</td>
</tr>
<tr>
<td>USB</td>
<td>External USB port, with USB 2.0/3.0 controllers</td>
</tr>
<tr>
<td>Bluetooth</td>
<td>Internal USB Atheros AR3012</td>
</tr>
<tr>
<td>LEDs</td>
<td>Green link and orange activity per Ethernet port, plus power and USB LEDs</td>
</tr>
<tr>
<td>Reset</td>
<td>Active-low button on GPIO bank A, pin 23</td>
</tr>
<tr>
<td>Console</td>
<td>Serial UART at 115200 baud, 8N1</td>
</tr>
</tbody></table>
<p>The physical Ethernet mapping matters. Housing port 1 is WAN. Housing ports 2 through 5 become <code>lan1</code> through <code>lan4</code>. The underlying switch-port order is reversed for those four LAN sockets:</p>
<table>
<thead>
<tr>
<th>Housing</th>
<th>OpenWrt interface</th>
<th>Switch port</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><code>wan</code></td>
<td>7</td>
</tr>
<tr>
<td>2</td>
<td><code>lan1</code></td>
<td>3</td>
</tr>
<tr>
<td>3</td>
<td><code>lan2</code></td>
<td>2</td>
</tr>
<tr>
<td>4</td>
<td><code>lan3</code></td>
<td>1</td>
</tr>
<tr>
<td>5</td>
<td><code>lan4</code></td>
<td>0</td>
</tr>
</tbody></table>
<h2>First, understand the bootloader handoff</h2>
<p>The stock firmware uses a Broadcom BCA boot chain, with several vendor boot stages before U-Boot loads a FIT image and Linux mounts its root filesystem from UBI.</p>
<p>Existing BCM4908 device support provided a starting point, but some inherited assumptions were wrong for this board.</p>
<h3>CPU startup uses PSCI</h3>
<p>Other boards in the family use a CFE spin-table startup path. The EBG15's BCA firmware exposes PSCI through SMC instead.</p>
<p>The device tree therefore overrides the two CPU enable methods to <code>psci</code>, removes the inherited CPU release addresses, and adds the PSCI node. It also matches the stock first-MiB firmware reservation. With that configuration, both CPUs come online.</p>
<h3>Ethernet needs power before register access</h3>
<p>The bootloader can hand off with the GMAC powered down. Accessing the controller in that state caused an external abort during development.</p>
<p>A new PMB power domain lets the Ethernet device request the required power through the existing provider. The bus/device identity used here is verified on the EBG15; its applicability across other BCM4908 revisions still needs broader hardware evidence.</p>
<p>This is one reason a successful build for a sibling router is useful but insufficient: it cannot show whether that router's firmware leaves the same hardware state behind.</p>
<h2>The MDIO controller was there, but the wrong master was selected</h2>
<p>PHY discovery was another handoff problem. BCA firmware left the switch's indirect MDIO master selected, while Linux needed the external UniMAC master.</p>
<p>The UniMAC MDIO patch accepts an optional <code>switchreg</code> resource. During bus reset, it clears the selector bit and reads the register back to flush the write before PHY discovery.</p>
<p>The switch driver also uses this register window. The patch consequently maps it without claiming exclusive ownership, and limits the new selection write to bus initialization. Runtime indirect operations continue through the existing SF2 path.</p>
<p>The fix is small; establishing which component owns the register at each stage took more work.</p>
<h2>A 1500-byte user MTU needs a larger DSA conduit</h2>
<p>The Ethernet controller is the DSA conduit for the integrated switch. A normal user port has MTU 1500, but the conduit also carries a Broadcom switch tag.</p>
<p>DSA requested a conduit MTU of 1504. The controller driver's old 1500-byte limit rejected it.</p>
<p>The change uses the driver's existing maximum Broadcom tag allowance to raise the supported limit to 1506, while ordinary user ports remain at 1500. The MTU callback also records successful changes in <code>netdev-&gt;mtu</code>.</p>
<p>On the tested unit, <code>eth0</code> reports MTU 1504, and full-size traffic passes through the connected LAN port. The advertised maximum of 1506 and the complete shared-driver runtime matrix remain separate test items.</p>
<h2>Bluetooth: an internal USB AR3012</h2>
<p>The EBG15 contains an Atheros AR3012 Bluetooth 4.0 controller and an internal antenna. It is attached to the OHCI USB bus, where it enumerates as USB ID <code>0cf3:3004</code> on internal port <code>2-1</code>. Its transport is USB; it does not need a UART <code>hciattach</code> setup.</p>
<p>Loading the driver alone was insufficient during bring-up because two board control lines also needed to be driven:</p>
<table>
<thead>
<tr>
<th>GPIO</th>
<th>Board function</th>
<th>Verified normal level</th>
</tr>
</thead>
<tbody><tr>
<td>A15</td>
<td>Bluetooth disable</td>
<td>High: disable deasserted</td>
</tr>
<tr>
<td>A25</td>
<td>Bluetooth reset</td>
<td>High: reset released</td>
</tr>
</tbody></table>
<p>Holding either line low hides the controller from USB. The board device tree selects GPIO pinmux for both pins and uses <code>bt-disable-hog</code> and <code>bt-reset-hog</code> to hold them high from boot. That GPIO function is supplied by the BCM4908 pinctrl patch. This describes the tested steady-state setup; it does not claim a separately characterized reset-pulse or power-sequencing specification.</p>
<p>The device profile includes <code>kmod-ath3k</code>. In this OpenWrt tree, that package pulls in <code>kmod-btusb</code>, <code>kmod-hci-uart</code>, and <code>ar3k-firmware</code>. The presence of the UART module in that dependency chain does not change the board's USB transport. The <code>ath3k</code> driver loads the patch/configuration firmware, after which the device re-enumerates and exposes <code>hci0</code>.</p>
<p>BlueZ userspace is needed to scan, pair, and use Bluetooth services; kernel enumeration alone is not a complete user-facing Bluetooth setup. Earlier bench builds included BlueZ tools, found a nearby device in a scan, and completed a BR/EDR pairing and bonding round-trip against another BlueZ host, including passkey confirmation and stored link keys. That did not establish working audio, HID, or other profile connections.</p>
<p>On the latest candidate, the recorded check confirms that <code>hci0</code> enumerates after boot. Scanning, pairing, profiles, and repeated cold-boot behavior have not been rerun as a complete matrix on that exact image.</p>
<h2>LEDs needed both initialization and sensible names</h2>
<p>The port uses software netdev triggers for the Ethernet LEDs: green for link, orange for activity. GPIO A24 controls a shared active-low enable. Power and USB LEDs use software channels too.</p>
<p>The LED driver change initializes each channel before registering it with the LED class, so registration and default-trigger activation see the intended initial state. Updates to shared registers use the controller lock.</p>
<p>For <code>default-state = "keep"</code>, the implementation is deliberately limited to steady software-controlled channels whose state can be interpreted. It rejects unsupported hardware/blinking states instead of guessing how to preserve them.</p>
<p>The board uses the driver's default full brightness of 9. An earlier experimental setting above that range was dropped. New LED nodes use <code>color</code> and <code>function</code> properties, with the existing port-specific class names retained so board configuration still selects the correct LEDs.</p>
<p>There are 13 LED-class definitions, including the shared enable; that does not mean the enclosure has 13 visible LEDs.</p>
<h2>Two review findings changed the implementation</h2>
<p>Source review found two problems worth explaining because both could hide behind an otherwise successful boot.</p>
<h3>Partial overlap is not complete memory coverage</h3>
<p>An initial U-Boot cleanup suppressed a reservation error whenever a requested region overlapped an existing reservation. That was too broad.</p>
<p>For example:</p>
<pre><code class="language-text">Already reserved:  [0x80000, 0x100000)
Requested:        [0x00000, 0x100000)
Still unprotected: [0x00000, 0x80000)
</code></pre>
<p>Returning on any intersection leaves part of the requested firmware memory available for later allocation.</p>
<p>The revised helper reserves every uncovered gap in a temporary copy of the vendor's logical memory block map. It commits the result only after the complete request succeeds. A failure leaves the original map unchanged and retains the error message.</p>
<p>The focused test compiles the actual helper against the vendor's <code>lib/lmb.c</code>. It passed 135,168 interval cases, plus capacity-failure, zero-size, overflow, and upper-bound checks. Those tests exercise reservation logic; they are not a substitute for booting the resulting firmware.</p>
<h3>A shutdown operation belongs in <code>shutdown()</code></h3>
<p>The overlay cleanup originally lived in an init script's <code>stop()</code> function. In OpenWrt, <code>stop()</code> also runs for an ordinary service stop and as part of restart.</p>
<p>That meant a service-management action could remount the running router's persistent storage read-only, with no corresponding action to restore it.</p>
<p>Moving the operation to <code>shutdown()</code> fixes the scope. Mocked dispatcher tests verified the distinction, and the installed candidate subsequently passed real stop/restart tests with successful writes under <code>/etc</code>.</p>
<p>During an orderly reboot, the hook still remounts the UBIFS overlay read-only. The following boot mounted it without the previous journal-recovery message. Its shutdown ordering still needs to be considered if later packages introduce persistent writers after this hook.</p>
<h2>Packaging and preserving a fallback</h2>
<p>The BCA bootloader consumes a <code>pkgtb</code> FIT bundle containing bootfs and rootfs payloads. The bootfs contains U-Boot, firmware components, the kernel, and the board device tree. The BCA compatibility string identifies this BCM4906 board as chip 4908.</p>
<p>The board matches the target's existing dual-bank UBI upgrade layout, so the port uses the shared upgrade path: replace the inactive bank's bootfs/rootfs, update bank metadata, and restore configuration.</p>
<p>For the latest validation, configuration, metadata, and the inactive-bank payloads were backed up first. The candidate was hash-checked and accepted by <code>sysupgrade -T</code>, then installed into bank 2. Bank 1's bootfs and rootfs hashes remained unchanged.</p>
<p>The bootfs kernel load/entry address is now <code>0x200000</code>, a 2 MiB-aligned address that removed the earlier kernel-image alignment warning in the observed boot.</p>
<p>Keeping the other bank intact provides a recovery option for this test. It does not preserve a factory image indefinitely: a later upgrade can overwrite whichever bank is inactive.</p>
<h2>What the latest candidate passed</h2>
<p>The latest build uses kernel 6.18.55. Validation included a fresh kernel compilation, both U-Boot variants built by the package, and a full firmware build using the existing toolchain and package caches.</p>
<p>All nine relevant kernel/U-Boot patches passed clean application checks. The board DTB compiled and validated against a freshly processed schema set. Schema generation reported an unrelated existing Realtek binding warning; DT compilation reported existing tree warnings. Inspection of the finished image verified its embedded payload hashes, rebuilt U-Boot, updated device tree, and shutdown hook.</p>
<p>The subsequent hardware run exercised the connected <code>lan2</code> interface:</p>
<table>
<thead>
<tr>
<th>Check</th>
<th>Observed result</th>
</tr>
</thead>
<tbody><tr>
<td>Upgrade</td>
<td>Candidate installed into bank 2; bank 1 retained</td>
</tr>
<tr>
<td>Configuration</td>
<td>Network and system configuration hashes preserved</td>
</tr>
<tr>
<td>CPUs</td>
<td>Both CPUs online</td>
</tr>
<tr>
<td>Ethernet</td>
<td>LAN2 linked at 1 Gb/s; conduit MTU 1504</td>
</tr>
<tr>
<td>Full-size ping</td>
<td>Five 1472-byte payload pings with DF set; zero loss</td>
</tr>
<tr>
<td>Host → router TCP</td>
<td>About 941 Mb/s received over 20 seconds; zero sender retransmissions</td>
</tr>
<tr>
<td>Router → host TCP</td>
<td>About 926 Mb/s over 20 seconds; 30,927 sender retransmissions</td>
</tr>
<tr>
<td>Storage</td>
<td>Writes succeed after service stop/restart and after reboot</td>
</tr>
<tr>
<td>LEDs</td>
<td>Power, green LAN2 link, and orange activity visually confirmed</td>
</tr>
<tr>
<td>Bluetooth</td>
<td><code>hci0</code> enumerates</td>
</tr>
<tr>
<td>Warm reboot</td>
<td>Successful; no observed panic, alignment/reservation error, or UBIFS recovery message</td>
</tr>
</tbody></table>
<p>The TCP results need the qualification shown in the table. Router-to-host throughput was high, but retransmissions remain present. Similar behavior appeared in earlier testing; this run does not establish its cause or certify long-term performance.</p>
<p>Earlier bench sessions exercised all five Ethernet ports, reset-button events, USB storage, Bluetooth scanning/pairing, and upgrades in both bank directions. Those results belong to earlier builds. The latest candidate has not repeated that entire matrix.</p>
<h2>What remains before calling the port finished</h2>
<p>The candidate is left running on bank 2. Cold-boot testing of this exact image was deferred, and the latest run covered only the connected LAN2 port.</p>
<p>The remaining work includes:</p>
<ul>
<li>Cold boot and the rest of the physical-port/peripheral test matrix on the exact candidate.</li>
<li>Further investigation of router transmit retransmissions and previously observed VLAN egress asymmetry.</li>
<li>Runtime regression testing on sibling BCM4908-family boards affected by shared driver changes.</li>
<li>Completing the UART pinout and power-adapter details in the installation documentation.</li>
<li>Adding the contributor's authorized identity and sign-offs, and splitting binding/driver changes appropriately for the separate Linux submissions.</li>
</ul>
<p>The device now boots, upgrades with preserved configuration, moves gigabit-class traffic on the tested port, and shuts down cleanly. The next step is to put the patch series in front of OpenWrt reviewers, carrying the test evidence and remaining limits with it.</p>
]]></content:encoded></item></channel></rss>