From 2526d5b0878b15e0a0975cb88338b1a41f02e8fd Mon Sep 17 00:00:00 2001 From: Joakim Date: Sat, 9 May 2026 21:02:53 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20M3=20complete=20=E2=80=94=20WiFi,=20BT?= =?UTF-8?q?=20keyboard=20and=20mouse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bluetooth (RTL8723D USB combo chip): - GPIO pin 357 (active low) enables WiFi/BT hardware via x6200_gpio_set() - bluez5_utils 5.64 (downgraded from 5.79 β€” HID input plugin broken in 5.79) - rtl8723d_config.bin added to overlay (missing from linux-firmware package) - S45wifi-bt: GPIO enable + modprobe btusb/uhid/hidp at boot - S85bt-keyboard: auto-connect loop with scan+connect every 20s WiFi (RTL8723DU): - out-of-tree lwfinger/rtw88 driver (RTW88_8723DU not in kernel 6.1 mainline) - linux-firmware RTL_RTW88 for rtw88/rtw8723d_fw.bin - regulatory.db for cfg80211 - wpa_supplicant with multi-network config in /etc/wpa_supplicant.conf - S46wifi: wpa_supplicant + udhcpc at boot Key findings: - RTL8723D USB WiFi (0bda:d723) requires out-of-tree rtw88 on kernel 6.1 - BT and WiFi share same USB device, both need GPIO 357 = 0 to power on - bluez5 5.79 HID input plugin not linked into bluetoothd (build system bug) --- .#grep | 1 + .gitignore | 1 + README.md | 19 +- audio-architecture.md | 255 ++++++++++++++ br2_external/Config.in | 1 + .../board/x6200/linux/linux-midi.fragment | 5 + br2_external/board/x6200/linux/linux.config | 12 +- .../etc/X11/xorg.conf.d/10-rotate.conf | 5 + .../rootfs-overlay/etc/init.d/S35wifi-bt | 45 +++ .../x6200/rootfs-overlay/etc/init.d/S46wifi | 44 +++ .../rootfs-overlay/etc/init.d/S85bt-keyboard | 58 ++++ .../x6200/rootfs-overlay/etc/ssh/sshd_config | 116 +++++++ .../etc/wpa_supplicant.conf.exampel | 16 + .../lib/firmware/rtl_bt/rtl8723d_config.bin | Bin 0 -> 10 bytes .../rootfs-overlay/root/.ssh/authorized_keys | 1 + br2_external/configs/mestre_x6200_defconfig | 51 ++- br2_external/package/rtw88/Config.in | 8 + br2_external/package/rtw88/rtw88.mk | 16 + wifi-bt-architecture.md | 326 ++++++++++++++++++ 19 files changed, 964 insertions(+), 16 deletions(-) create mode 120000 .#grep create mode 100644 audio-architecture.md create mode 100644 br2_external/board/x6200/rootfs-overlay/etc/X11/xorg.conf.d/10-rotate.conf create mode 100755 br2_external/board/x6200/rootfs-overlay/etc/init.d/S35wifi-bt create mode 100755 br2_external/board/x6200/rootfs-overlay/etc/init.d/S46wifi create mode 100755 br2_external/board/x6200/rootfs-overlay/etc/init.d/S85bt-keyboard create mode 100644 br2_external/board/x6200/rootfs-overlay/etc/ssh/sshd_config create mode 100644 br2_external/board/x6200/rootfs-overlay/etc/wpa_supplicant.conf.exampel create mode 100644 br2_external/board/x6200/rootfs-overlay/lib/firmware/rtl_bt/rtl8723d_config.bin create mode 100644 br2_external/board/x6200/rootfs-overlay/root/.ssh/authorized_keys create mode 100644 br2_external/package/rtw88/Config.in create mode 100644 br2_external/package/rtw88/rtw88.mk create mode 100644 wifi-bt-architecture.md diff --git a/.#grep b/.#grep new file mode 120000 index 0000000..fcba267 --- /dev/null +++ b/.#grep @@ -0,0 +1 @@ +joakim@here.1699682 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5c66de5..0670b33 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ build/ # Local overrides local.mk .env +wpa_supplicant.conf diff --git a/README.md b/README.md index 331a384..e11a7ce 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,15 @@ The product name **Virtuoso** is reserved for when (if!) Mestre actually deliver | # | Milestone | Status | |---|----------------------------------------------------------------------|--------| -| 1 | Boot a recent Buildroot LTS on the X6200 (serial console) | 🟑 In progress | -| 2 | Forward-port LCD panel driver β€” full display output | βšͺ Planned | -| 3 | piHPSDR running on X6200 with working audio | βšͺ Planned | -| 4 | `mestre` service: evdev β†’ ALSA virtual MIDI bridge | βšͺ Planned | -| 5 | End-to-end: X6200 controlling Hermes-Lite 2 as a control head | βšͺ Planned | -| 6 | First-boot wizard, settings persistence, polished UX | βšͺ Planned | +| 1 | Boot a recent Buildroot LTS on the X6200 (serial console) | βœ… 2026-05-03 | +| 2 | Working audio stack | βœ… 2026-05-06 | +| 3 | WiFi and Bluetooth keyboard and mouse | βœ… 2026-05-09 | +| 4 | Forward-port LCD panel driver β€” full display output | βœ… 2026-05-03 | +| 5 | piHPSDR running on X6200, controlled by mouse and keyboard | βšͺ Planned | +| 6 | piHPSDR audio β€” HL2 audio via SoC speaker | βšͺ Planned | +| 7 | `mestre` service: evdev β†’ ALSA virtual MIDI bridge | βšͺ Planned | +| 8 | End-to-end: X6200 controlling Hermes-Lite 2 as a control head | βšͺ Planned | +| 9 | First-boot wizard, settings persistence, polished UX | βšͺ Planned | | ∞ | Earn the right to call it Virtuoso | βšͺ TBD | ## Architecture @@ -66,7 +69,7 @@ The architecture is deliberately patch-free: piHPSDR is used as-is, and the MIDI ## Building -> ⚠️ **Pre-1.0**: build instructions are aspirational while milestone 1 is in progress. +> ⚠️ **Pre-1.0**: actively developed. Core build works; some milestones still in progress. ### Prerequisites @@ -114,4 +117,4 @@ Mestre stands on the shoulders of others. In particular: ## License GPL-2.0-or-later, matching the kernel-derived components Mestre vendors. -Userland components written specifically for Mestre may be re-licensed (e.g. to MIT) on a per-component basis where doing so doesn't conflict with this overall license; such cases will be marked clearly in the relevant subdirectory. +Userland components written specifically for Mestre may be re-licensed (e.g. to MIT) on a per-component basis where doing so doesn't conflict with this overall license; such cases will be marked clearly in the relevant subdirectory. \ No newline at end of file diff --git a/audio-architecture.md b/audio-architecture.md new file mode 100644 index 0000000..f1c47cc --- /dev/null +++ b/audio-architecture.md @@ -0,0 +1,255 @@ +# X6200 Audio Architecture + +A detailed technical account of how audio works on the Xiegu X6200, +reverse-engineered during Mestre development (May 2026). + +## Overview + +The X6200 has two distinct audio sources and one audio output path. +Understanding the distinction between them is essential for any software +that wants to produce sound on this radio. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ X6200 Hardware β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ RF Front β”‚ β”‚ Allwinnerβ”‚ β”‚ +β”‚ β”‚ End β”‚ β”‚ R16 β”‚ (SoC / Application CPU) β”‚ +β”‚ β”‚ + FPGA β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ RX audio β”‚ DAC audio β”‚ +β”‚ β”‚ (analog) β”‚ (digital β†’ analog via codec) β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Audio β”‚ ← voice_rec bit selects source β”‚ +β”‚ β”‚ MUX β”‚ (controlled via I2C to MCU) β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ PA β”‚ ← Power Amplifier β”‚ +β”‚ β”‚ (Class D)β”‚ enabled by rxvol_set > 0 β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Speaker β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## The Two Audio Sources + +### Source 1: RX-Radio Audio (default) + +The RF front-end and FPGA process the received RF signal and produce +an analog audio output β€” the classic "radio receiver sound". This is +the default audio source at power-on. It is completely independent of +the SoC and ALSA; it bypasses the codec entirely and is fed directly +into the audio MUX. + +This is why you hear receiver noise immediately when the X6200 boots, +even before Linux has fully started. + +### Source 2: SoC DAC Audio + +The Allwinner R16 SoC contains an integrated audio codec +(`sun8i-a33-audio`) which can produce digital audio, converted to +analog via the DAC. This is the source used by software running on +the SoC β€” piHPSDR, aplay, PulseAudio, etc. + +This source goes through the standard Linux ALSA stack: + +``` +Application (piHPSDR, aplay) + β†’ PulseAudio (mixing, resampling) + β†’ ALSA (hw:0,0 / pcmC0D0p) + β†’ sun8i-a33-audio codec driver + β†’ DAC (analog output) + β†’ Audio MUX + β†’ PA + β†’ Speaker +``` + +## The Audio MUX β€” The Critical Control Point + +A hardware multiplexer inside the MCU/FPGA selects which source +reaches the power amplifier and speaker. This MUX is controlled +via I2C, **not** via ALSA or any Linux kernel interface. + +### I2C Protocol + +The X6200 MCU sits on I2C bus 0, address `0x72`. All hardware control +uses the X6200Control library protocol: a 6-byte write transaction +consisting of a 2-byte register address followed by a 4-byte value. + +The audio MUX is controlled by the `voice_rec` bit in the +`sple_atue_trx` register (command index 12, address `0x0030`): + +| voice_rec bit | Audio source heard in speaker | +|---------------|-------------------------------| +| 0 (default) | RX-radio (RF receiver) | +| 1 | SoC DAC (software audio) | + +**To switch to SoC DAC audio:** +```bash +i2ctransfer -y 0 w6@0x72 0x00 0x30 0x08 0x00 0x00 0x00 +``` + +**To switch back to RX-radio audio:** +```bash +i2ctransfer -y 0 w6@0x72 0x00 0x30 0x00 0x00 0x00 0x00 +``` + +## The Power Amplifier β€” The Other Critical Control Point + +The speaker PA (power amplifier) is not always on. It is enabled +as a side effect of setting the speaker volume via `x6200_control_rxvol_set()`. +When `rxvol = 0` (the default after `x6200_control_init()`), the PA +is effectively off and nothing is heard regardless of the MUX position +or ALSA configuration. + +This is why setting the correct mixer levels alone is not sufficient β€” +the MCU must be told to activate the PA via the I2C control protocol. + +## The ALSA Codec β€” Clock Dependency + +The `sun8i-a33-audio` codec requires its master clock (`pll-audio`, +22.5792 MHz) to be running for DAC output to work. The kernel's +clock framework only enables this clock when a consumer holds an +active reference β€” i.e., when an application has the PCM device open. + +**This is the most subtle requirement:** if no process holds +`/dev/snd/pcmC0D0p` open, the audio clocks are disabled and the +codec produces no output, even if the ALSA mixer is correctly +configured and voice_rec is set to DAC mode. + +PulseAudio, running in system mode, holds the PCM device open +continuously. This is what keeps the clocks running at all times +and makes reliable audio possible. + +``` +PulseAudio running β†’ pcmC0D0p open β†’ pll-audio enabled β†’ codec active +``` + +Without PulseAudio (or equivalent), every `aplay` invocation would +need to wait for the codec to stabilise after the clock starts β€” +and the X6200 MCU may reset the MUX during this window. + +## The ALSA Mixer β€” Required Configuration + +The codec's internal mixer must be configured correctly for audio +to flow from the DAC to the analog output. The critical controls are: + +| numid | Name | Required value | +|-------|-----------------------------------|----------------| +| 2 | AIF1 DA0 Playback Volume | 160, 160 | +| 7 | Headphone Playback Volume | 58 | +| 8 | Headphone Playback Switch | on, on | +| 20 | AIF1 Data Digital ADC Cap. Switch | on, on | +| 26 | AIF1 DA0 Stereo Playback Route | 3, 3 (Mix Mono)| +| 28 | AIF1 Slot 0 Digital DAC PB Switch | on, on | +| 31 | DAC Playback Switch | off, off | + +Note that `DAC Playback Switch` (numid=31) must be **off** β€” this is +counterintuitive but correct. The signal path goes through AIF1, +not the direct DAC path. + +These values are saved in `/var/lib/alsa/asound.state` and restored +at boot via `alsactl restore` (init script `S50alsa`). + +## Complete Initialisation Sequence + +The following sequence must be performed in this exact order at boot: + +### Step 1: ALSA mixer state (S50alsa) +```bash +alsactl restore +``` +Restores the correct mixer levels from `/var/lib/alsa/asound.state`. + +### Step 2: PulseAudio (S50pulseaudio) +```bash +pulseaudio --system --daemonize --disallow-exit --exit-idle-time=-1 +``` +Starts PulseAudio in system mode. This opens `pcmC0D0p` and keeps +the ALSA clocks running continuously. + +### Step 3: X6200 hardware init (S70mestre-audio) +```python +lib.x6200_control_init() # Establishes I2C state, sends all_cmd +lib.x6200_control_spmode_set(False) # Speaker mode (not headphone) +lib.x6200_control_rxvol_set(50) # Enables PA, sets volume (0-100) +``` +`x6200_control_init()` sends a full command packet to the MCU via I2C, +establishing all register values. **It sets rxvol=0** (PA off) as +its default. `rxvol_set(50)` must follow immediately to enable the PA. + +### Step 4: Switch audio MUX to SoC DAC (S70mestre-audio) +```bash +i2ctransfer -y 0 w6@0x72 0x00 0x30 0x08 0x00 0x00 0x00 +``` +Sets the `voice_rec` bit, switching the speaker from RX-radio audio +to SoC DAC audio. From this point, any audio played via PulseAudio +or directly to `hw:0,0` (44100 Hz, stereo, S16_LE) will be heard +in the speaker. + +## Mestre's Implementation + +Mestre's init scripts implement this sequence: + +``` +S50alsa β†’ alsactl restore +S50pulseaudio β†’ pulseaudio --system +S70mestre-audio β†’ x6200_control_init + rxvol_set + voice_rec switch +S96keepalive β†’ I2C PMU keepalive (prevents automatic power-off) +``` + +The audio MUX switch command (`voice_rec=on`) is permanent until the +next `x6200_control_init()` call β€” it does not need to be repeated. +However, the PMU keepalive (separate from audio) must run continuously +or the radio will power off after approximately 180 seconds. + +## Volume Control + +Speaker volume is controlled via `x6200_control_rxvol_set(vol)` where +`vol` is 0-100. This sends an I2C command to the MCU which adjusts +an analog gain stage. **It is not reflected in the ALSA mixer** and +cannot be read or set via `amixer`. + +The ALSA mixer `Headphone Playback Volume` (numid=7) and +`AIF1 DA0 Playback Volume` (numid=2) affect the digital gain +in the codec but are set to fixed values at boot and not used +for runtime volume control in Mestre. + +## Known Quirks + +**rxvol_set defaults to 0 after init.** Every call to +`x6200_control_init()` resets the speaker volume to 0 (PA off). +Always call `rxvol_set(N)` immediately after `init()`. + +**voice_rec is reset by x6200_control_init().** If `init()` is called +again (e.g., after a crash), the MUX reverts to RX-radio mode and +the full init sequence must be repeated. + +**PulseAudio must start before mestre-audio.** If PulseAudio is not +running when the audio MUX is switched, the codec clocks may not be +active and the switch may have no audible effect. + +**44100 Hz stereo S16_LE** is the correct format for direct hardware +playback (`hw:0,0`). PulseAudio handles resampling from other rates. + +## References + +- [gdyuldin/X6200Control](https://github.com/gdyuldin/X6200Control) β€” + I2C control library source, including the `sple_atue_trx` register + layout and `voice_rec` bit definition. +- [gdyuldin/x6200_gui](https://github.com/gdyuldin/x6200_gui) β€” + Reference implementation of the full audio init sequence + (`audio_play_en()`, `audio_init()`, `radio_init()`). +- [AetherRadio/X6100Control](https://github.com/AetherRadio/X6100Control) β€” + X6100 control library (protocol-compatible with X6200), used to + understand the register map before X6200Control source was available. +- Allwinner R16 / sun8i-a33 audio driver: `sound/soc/sunxi/` in the + Linux kernel tree. The codec is `sun8i-codec` with an AIF1 I2S + interface to the SoC's audio PLL. diff --git a/br2_external/Config.in b/br2_external/Config.in index 8117fb2..1a8fa21 100644 --- a/br2_external/Config.in +++ b/br2_external/Config.in @@ -1,5 +1,6 @@ menu "Mestre" source "$BR2_EXTERNAL_MESTRE_PATH/package/x6200-control/Config.in" +source "$BR2_EXTERNAL_MESTRE_PATH/package/rtw88/Config.in" endmenu diff --git a/br2_external/board/x6200/linux/linux-midi.fragment b/br2_external/board/x6200/linux/linux-midi.fragment index 750417f..4228215 100644 --- a/br2_external/board/x6200/linux/linux-midi.fragment +++ b/br2_external/board/x6200/linux/linux-midi.fragment @@ -35,3 +35,8 @@ CONFIG_SND_DUMMY=m # Expose kernel config via /proc/config.gz (useful for debugging) CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y + +# HID via userspace (required for Bluetooth HID devices) +CONFIG_UHID=y +CONFIG_INPUT_UINPUT=y + diff --git a/br2_external/board/x6200/linux/linux.config b/br2_external/board/x6200/linux/linux.config index af8ef96..fc59a06 100644 --- a/br2_external/board/x6200/linux/linux.config +++ b/br2_external/board/x6200/linux/linux.config @@ -56,11 +56,13 @@ CONFIG_NET_IPIP=y # CONFIG_INET_DIAG is not set # CONFIG_IPV6 is not set CONFIG_CAN=y -CONFIG_BT=m -CONFIG_BT_RFCOMM=m -CONFIG_BT_BNEP=m -CONFIG_BT_HIDP=m -CONFIG_BT_HCIBTUSB=m +CONFIG_BT=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_BNEP=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIBTUSB=y +CONFIG_UHID=y +CONFIG_HIDRAW=y CONFIG_CFG80211=m CONFIG_CFG80211_DEVELOPER_WARNINGS=y CONFIG_CFG80211_CERTIFICATION_ONUS=y diff --git a/br2_external/board/x6200/rootfs-overlay/etc/X11/xorg.conf.d/10-rotate.conf b/br2_external/board/x6200/rootfs-overlay/etc/X11/xorg.conf.d/10-rotate.conf new file mode 100644 index 0000000..f057cd0 --- /dev/null +++ b/br2_external/board/x6200/rootfs-overlay/etc/X11/xorg.conf.d/10-rotate.conf @@ -0,0 +1,5 @@ +Section "Device" + Identifier "LCD" + Driver "fbdev" + Option "Rotate" "CCW" +EndSection diff --git a/br2_external/board/x6200/rootfs-overlay/etc/init.d/S35wifi-bt b/br2_external/board/x6200/rootfs-overlay/etc/init.d/S35wifi-bt new file mode 100755 index 0000000..6d8b008 --- /dev/null +++ b/br2_external/board/x6200/rootfs-overlay/etc/init.d/S35wifi-bt @@ -0,0 +1,45 @@ +#!/bin/sh +# S45wifi-bt β€” aktiverar WiFi/BT-chipet (RTL8723D) via GPIO +# +# X6200:s WiFi/BT-chip Γ€r avstΓ€ngt vid boot och mΓ₯ste aktiveras +# via GPIO pin 357 (active low) innan kernel-drivern kan anvΓ€nda det. +# +# MΓ₯ste kΓΆras innan S40bluetoothd startar. + +case "$1" in + start) + echo "Starting WiFi/BT hardware" + python3 - << 'PYEOF' +import ctypes +lib = ctypes.CDLL("libaether_x6200_control.so") +lib.x6200_gpio_init.restype = ctypes.c_bool +lib.x6200_gpio_set.argtypes = [ctypes.c_int, ctypes.c_int] +lib.x6200_gpio_init() +lib.x6200_gpio_set(357, 0) # active low β€” 0 = ON +PYEOF + sleep 1 + modprobe btusb + modprobe uhid + modprobe hidp + sleep 3 + echo "OK" + ;; + stop) + python3 - << 'PYEOF' +import ctypes +lib = ctypes.CDLL("libaether_x6200_control.so") +lib.x6200_gpio_init.restype = ctypes.c_bool +lib.x6200_gpio_set.argtypes = [ctypes.c_int, ctypes.c_int] +lib.x6200_gpio_init() +lib.x6200_gpio_set(357, 1) # 1 = OFF +PYEOF + echo "WiFi/BT hardware stopped" + ;; + restart) + $0 stop; sleep 1; $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" >&2 + exit 1 + ;; +esac diff --git a/br2_external/board/x6200/rootfs-overlay/etc/init.d/S46wifi b/br2_external/board/x6200/rootfs-overlay/etc/init.d/S46wifi new file mode 100755 index 0000000..8a7d38c --- /dev/null +++ b/br2_external/board/x6200/rootfs-overlay/etc/init.d/S46wifi @@ -0,0 +1,44 @@ +#!/bin/sh +# S46wifi β€” ansluter till WiFi via wpa_supplicant +# +# KΓΆrs efter S45wifi-bt (GPIO aktivering av chipet). +# AnvΓ€nder /etc/wpa_supplicant.conf fΓΆr nΓ€tverkslista. + +IFACE="wlan0" +PIDFILE="/var/run/wpa_supplicant.pid" +LOG="/var/log/wifi.log" + +case "$1" in + start) + echo "Starting WiFi" + ip link set "$IFACE" up + sleep 2 + + wpa_supplicant -B \ + -i "$IFACE" \ + -c /etc/wpa_supplicant.conf \ + -P "$PIDFILE" \ + >> "$LOG" 2>&1 + + # HΓ€mta IP via DHCP + sleep 3 + dhclient "$IFACE" >> "$LOG" 2>&1 || \ + udhcpc -i "$IFACE" -b -p /var/run/udhcpc.pid >> "$LOG" 2>&1 + + echo "OK" + ;; + stop) + echo "Stopping WiFi" + [ -f "$PIDFILE" ] && kill $(cat "$PIDFILE") 2>/dev/null + rm -f "$PIDFILE" + ip link set "$IFACE" down + echo "OK" + ;; + restart) + $0 stop; sleep 1; $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" >&2 + exit 1 + ;; +esac diff --git a/br2_external/board/x6200/rootfs-overlay/etc/init.d/S85bt-keyboard b/br2_external/board/x6200/rootfs-overlay/etc/init.d/S85bt-keyboard new file mode 100755 index 0000000..7fe0942 --- /dev/null +++ b/br2_external/board/x6200/rootfs-overlay/etc/init.d/S85bt-keyboard @@ -0,0 +1,58 @@ +#!/bin/sh +# S85bt-keyboard β€” auto-connect Linocell BTK16 BT-tangentbord +# +# FΓΆrsΓΆker ansluta tangentbordet vid boot och startar en bakgrundsprocess +# som reconnectar automatiskt nΓ€r tangentbordet kommer inom rΓ€ckhΓ₯ll. +# +# Tangentbordet mΓ₯ste vara parat och trustat sedan tidigare. +# Para manuellt med: bluetoothctl pair ED:96:00:23:E2:02 +# bluetoothctl trust ED:96:00:23:E2:02 + +KEYBOARD_ADDR="ED:96:00:23:E2:02" +PIDFILE="/var/run/bt-keyboard.pid" +LOG="/var/log/bt-keyboard.log" + +connect_keyboard() { + bluetoothctl power on > /dev/null 2>&1 + bluetoothctl connect "$KEYBOARD_ADDR" > /dev/null 2>&1 +} + +watch_loop() { + while true; do + # Kolla om tangentbordet Γ€r anslutet + if ! bluetoothctl info "$KEYBOARD_ADDR" 2>/dev/null | grep -q "Connected: yes"; then + connect_keyboard + fi + sleep 10 + done +} + +case "$1" in + start) + echo "Starting BT keyboard auto-connect" + + # FΓΆrsΓΆk fΓΆrsta anslutning i bakgrunden (tangentbordet kanske inte Γ€r i nΓ€rheten) + (sleep 5 && connect_keyboard) & + + # Starta watch-loop som reconnectar nΓ€r tgb dyker upp + watch_loop >> "$LOG" 2>&1 & + echo $! > "$PIDFILE" + echo "OK" + ;; + stop) + echo "Stopping BT keyboard auto-connect" + if [ -f "$PIDFILE" ]; then + kill $(cat "$PIDFILE") 2>/dev/null + rm -f "$PIDFILE" + fi + bluetoothctl disconnect "$KEYBOARD_ADDR" > /dev/null 2>&1 + echo "OK" + ;; + restart) + $0 stop; sleep 1; $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" >&2 + exit 1 + ;; +esac diff --git a/br2_external/board/x6200/rootfs-overlay/etc/ssh/sshd_config b/br2_external/board/x6200/rootfs-overlay/etc/ssh/sshd_config new file mode 100644 index 0000000..5d35f72 --- /dev/null +++ b/br2_external/board/x6200/rootfs-overlay/etc/ssh/sshd_config @@ -0,0 +1,116 @@ +# $OpenBSD: sshd_config,v 1.104 2021/07/02 05:11:21 dtucker Exp $ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/bin:/sbin:/usr/bin:/usr/sbin + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#PubkeyAuthentication yes + +# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 +# but this is overridden so installations will only check .ssh/authorized_keys +AuthorizedKeysFile .ssh/authorized_keys + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PasswordAuthentication yes +#PermitEmptyPasswords no + +# Change to no to disable s/key passwords +#KbdInteractiveAuthentication yes + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the KbdInteractiveAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via KbdInteractiveAuthentication may bypass +# the setting of "PermitRootLogin prohibit-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and KbdInteractiveAuthentication to 'no'. +#UsePAM no + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +#X11Forwarding no +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /var/run/sshd.pid +#MaxStartups 10:30:100 +#PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# override default of no subsystems +Subsystem sftp /usr/libexec/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server diff --git a/br2_external/board/x6200/rootfs-overlay/etc/wpa_supplicant.conf.exampel b/br2_external/board/x6200/rootfs-overlay/etc/wpa_supplicant.conf.exampel new file mode 100644 index 0000000..8fbd508 --- /dev/null +++ b/br2_external/board/x6200/rootfs-overlay/etc/wpa_supplicant.conf.exampel @@ -0,0 +1,16 @@ +ctrl_interface=/var/run/wpa_supplicant +update_config=1 +country=SE + +# LΓ€gg till dina nΓ€tverk hΓ€r: +network={ + ssid="Ditt_natverk_1" + psk="ditt_losenord_1" + priority=10 +} + +network={ + ssid="Ditt_natverk_2" + psk="ditt_losenord_2" + priority=5 +} diff --git a/br2_external/board/x6200/rootfs-overlay/lib/firmware/rtl_bt/rtl8723d_config.bin b/br2_external/board/x6200/rootfs-overlay/lib/firmware/rtl_bt/rtl8723d_config.bin new file mode 100644 index 0000000000000000000000000000000000000000..bb15fcd4312379eb7d2ebc26ad2240dc87031faf GIT binary patch literal 10 RcmWGtt=!JSaFc@x0+;{* literal 0 HcmV?d00001 diff --git a/br2_external/board/x6200/rootfs-overlay/root/.ssh/authorized_keys b/br2_external/board/x6200/rootfs-overlay/root/.ssh/authorized_keys new file mode 100644 index 0000000..23e0241 --- /dev/null +++ b/br2_external/board/x6200/rootfs-overlay/root/.ssh/authorized_keys @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBaWQsEKvTUbfTSKEa9sEuu7LpJm9Z07eSluIQV7+AxA joakim@cameron diff --git a/br2_external/configs/mestre_x6200_defconfig b/br2_external/configs/mestre_x6200_defconfig index d169307..1f90400 100644 --- a/br2_external/configs/mestre_x6200_defconfig +++ b/br2_external/configs/mestre_x6200_defconfig @@ -126,7 +126,17 @@ BR2_PACKAGE_STRACE=y # - Network Manager (using simple /etc/network/interfaces for now) # --------------------------------------------------------------------------- +BR2_GLOBAL_PATCH_DIR="../br2_external/board/x6200/patches" +# x6200 +BR2_PACKAGE_X6200_CONTROL=y + +# MISC +BR2_PACKAGE_PYTHON3=y +BR2_PACKAGE_PYTHON3_CTYPES=y +BR_PACKAGE_JOE=y + +# Audio BR2_PACKAGE_ALSA_UTILS=y BR2_PACKAGE_ALSA_UTILS_APLAY=y BR2_PACKAGE_ALSA_UTILS_AMIXER=y @@ -135,12 +145,47 @@ BR2_PACKAGE_ALSA_LIB=y BR2_PACKAGE_ALSA_LIB_MIXER=y BR2_PACKAGE_ALSA_LIB_PCM=y BR2_PACKAGE_ALSA_LIB_RAWMIDI=y -BR2_PACKAGE_X6200_CONTROL=y -BR2_PACKAGE_PYTHON3=y -BR2_PACKAGE_PYTHON3_CTYPES=y BR2_PACKAGE_PULSEAUDIO=y BR2_PACKAGE_PULSEAUDIO_DAEMON=y BR2_PACKAGE_PULSEAUDIO_MODULE_ALSA=y BR2_PACKAGE_PULSEAUDIO=y BR2_PACKAGE_PULSEAUDIO_DAEMON=y BR2_PACKAGE_PULSEAUDIO_MODULE_ALSA=y + +# X11 +BR2_PACKAGE_XORG7=y +BR2_PACKAGE_XSERVER_XORG_SERVER=y +BR2_PACKAGE_XAPP_XINIT=y +BR2_PACKAGE_XDRIVER_XF86_VIDEO_FBDEV=y +BR2_PACKAGE_XDRIVER_XF86_INPUT_LIBINPUT=y +BR2_PACKAGE_XKEYBOARD_CONFIG=y +BR2_PACKAGE_LIBINPUT=y +BR2_PACKAGE_XTERM=y + +# GTK3 +BR2_PACKAGE_LIBGTK3=y +BR2_PACKAGE_LIBGTK3_X11=y +BR2_PACKAGE_LIBEPOXY=y + +# BT TGB MOUSE & WIFI +BR2_PACKAGE_LINUX_FIRMWARE=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT=y +BR2_PACKAGE_BLUEZ5_UTILS=y +BR2_PACKAGE_BLUEZ5_UTILS_CLIENT=y +BR2_PACKAGE_BLUEZ5_UTILS_DEPRECATED=y +BR2_PACKAGE_BLUEZ5_UTILS_TOOLS=y +BR2_PACKAGE_BLUEZ5_UTILS_PLUGINS_HID=y +BR2_PACKAGE_BLUEZ5_UTILS_TOOLS_HID2HCI=y +BR2_PACKAGE_EUDEV=y +BR2_PACKAGE_XKEYBOARD_CONFIG=y +BR2_PACKAGE_XINPUT=y +BR2_PACKAGE_XAPP_XRANDR=y +BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88=y +BR2_PACKAGE_RTW88=y +BR2_PACKAGE_LINUX_FIRMWARE_REGULATORY=y +BR2_PACKAGE_WPA_SUPPLICANT=y +BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y +BR2_PACKAGE_WPA_SUPPLICANT_CTRL_IFACE=y +BR2_PACKAGE_DHCP=y +BR2_PACKAGE_DHCP_CLIENT=y diff --git a/br2_external/package/rtw88/Config.in b/br2_external/package/rtw88/Config.in new file mode 100644 index 0000000..d529699 --- /dev/null +++ b/br2_external/package/rtw88/Config.in @@ -0,0 +1,8 @@ +config BR2_PACKAGE_RTW88 + bool "rtw88 out-of-tree WiFi driver" + depends on BR2_LINUX_KERNEL + help + Out-of-tree Realtek rtw88 driver from lwfinger/rtw88. + Adds RTL8723DU USB WiFi support not available in kernel 6.1. + + https://github.com/lwfinger/rtw88 diff --git a/br2_external/package/rtw88/rtw88.mk b/br2_external/package/rtw88/rtw88.mk new file mode 100644 index 0000000..054ecee --- /dev/null +++ b/br2_external/package/rtw88/rtw88.mk @@ -0,0 +1,16 @@ +################################################################################ +# +# rtw88 β€” out-of-tree Realtek WiFi driver +# Includes RTL8723DU USB support not yet in kernel 6.1 mainline. +# Based on gdyuldin/AetherX6200Buildroot's recipe. +# +################################################################################ + +RTW88_VERSION = 461b696b51317ba4ca585a4ddb32f2e72cd4efc9 +RTW88_SITE = https://github.com/lwfinger/rtw88 +RTW88_SITE_METHOD = git +RTW88_LICENSE = GPL-2.0 +RTW88_INSTALL_STAGING = YES + +$(eval $(kernel-module)) +$(eval $(generic-package)) diff --git a/wifi-bt-architecture.md b/wifi-bt-architecture.md new file mode 100644 index 0000000..1bd500f --- /dev/null +++ b/wifi-bt-architecture.md @@ -0,0 +1,326 @@ +# X6200 WiFi and Bluetooth Architecture + +A detailed technical account of how WiFi and Bluetooth work on the +Xiegu X6200, reverse-engineered during Mestre development (May 2026). + +## Hardware Overview + +The X6200 uses a **Realtek RTL8723D** combo chip for both WiFi and +Bluetooth. It is connected to the Allwinner R16 SoC via USB and +appears as a single USB device: + +``` +USB ID: 0bda:d723 Realtek Semiconductor Corp. 802.11n WLAN Adapter +Bus: USB 3 (musb-hdrc, high-speed) +``` + +The device exposes three USB interfaces: +- Interface 0+1: Bluetooth (class 0xE0, Wireless, RF) +- Interface 2: WiFi (class 0xFF, Vendor Specific) + +Both WiFi and Bluetooth share the same USB device and antenna. + +## The Power Control Problem + +**The RTL8723D chip is powered off at boot.** It does not appear on +the USB bus until explicitly powered on via a GPIO pin. Without this +step, neither WiFi nor Bluetooth will work regardless of what drivers +or software is loaded. + +The power control pin is managed via the X6200Control library: + +``` +GPIO chip: 0 (sun8i-a33 main GPIO controller) +GPIO pin: 357 +Logic: Active LOW (0 = powered ON, 1 = powered OFF) +``` + +This is controlled via the `x6200_gpio_set()` function from +`libaether_x6200_control.so`: + +```python +import ctypes +lib = ctypes.CDLL("libaether_x6200_control.so") +lib.x6200_gpio_init.restype = ctypes.c_bool +lib.x6200_gpio_set.argtypes = [ctypes.c_int, ctypes.c_int] +lib.x6200_gpio_init() +lib.x6200_gpio_set(357, 0) # 0 = ON (active low!) +``` + +**Critical:** `gpio_init()` must be called before `gpio_set()`. +The active-low logic is counterintuitive β€” sending `1` powers the +chip OFF, sending `0` powers it ON. + +After powering on, the USB device appears within 1-3 seconds: +``` +usb 3-1: new high-speed USB device number 2 using musb-hdrc +usb 3-1: New USB device found, idVendor=0bda, idProduct=d723 +``` + +## Bluetooth + +### Driver + +The Bluetooth interface of the RTL8723D is handled by the standard +Linux kernel `btusb` driver with the `btrtl` firmware loader. + +Required kernel modules (compiled as `=m`): +``` +CONFIG_BT=m +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_BNEP=m +CONFIG_BT_HIDP=m +CONFIG_UHID=y ← required for HID input devices +CONFIG_INPUT_UINPUT=y ← required for HID input devices +``` + +Load order: +```bash +modprobe btusb # registers hci0 and loads firmware +modprobe uhid # userspace HID device support +modprobe hidp # HID over Bluetooth protocol +``` + +### Firmware + +The RTL8723D Bluetooth requires two firmware files: + +| File | Purpose | +|------|---------| +| `rtl_bt/rtw8723d_fw.bin` | Main BT firmware | +| `rtl_bt/rtw8723d_config.bin` | Configuration (10 bytes) | + +**Both files are required.** Without `rtw8723d_config.bin`, `hciconfig +hci0 up` fails with: +``` +Bluetooth: hci0: RTL: mandatory config file rtl_bt/rtw8723d_config not found +``` + +`rtw8723d_config.bin` is **not included** in Buildroot's +`BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT` package (as of 2025.02). +It must be added to the rootfs overlay manually: + +``` +br2_external/board/x6200/rootfs-overlay/lib/firmware/rtl_bt/rtw8723d_config.bin +``` + +Source: https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/rtl_bt/rtw8723d_config.bin + +### BlueZ Version + +**BlueZ 5.64 is required.** BlueZ 5.79 (Buildroot 2025.02 default) +has a build system bug where the HID input plugin (`input_init`) is +not linked into `bluetoothd` despite `--enable-hid` being passed to +configure. Symptoms: + +``` +Failed to connect: org.bluez.Error.NotAvailable br-connection-profile-unavailable +``` + +The bug manifests because `builtin_modules` in the generated Makefile +is empty and the input profile is never registered via +`btd_profile_register()`. Downgrading to BlueZ 5.64 resolves the +issue. + +To force BlueZ 5.64 in Buildroot, override the version in a local +`.mk` file or patch `buildroot/package/bluez5_utils/bluez5_utils.mk`. + +### HID Profile Requirements + +For Bluetooth HID devices (keyboard, mouse) to connect, bluetoothd +must be started with `--compat`: + +```bash +/usr/libexec/bluetooth/bluetoothd --compat & +``` + +And `/etc/bluetooth/input.conf` must have: +```ini +[General] +UserspaceHID=true +ClassicBondedOnly=false +``` + +### Pairing a Device + +First-time pairing via `bluetoothctl`: + +``` +power on +agent on +default-agent +scan on +# Wait for device to appear, then: +pair XX:XX:XX:XX:XX:XX +trust XX:XX:XX:XX:XX:XX +connect XX:XX:XX:XX:XX:XX +``` + +Once trusted, subsequent connections are automatic via the +`S85bt-keyboard` init script. + +### Auto-reconnect + +The `S85bt-keyboard` init script polls every 20 seconds and +reconnects if the device is not connected. It uses `scan on` before +each connection attempt to make the device visible: + +```sh +{ + echo "power on"; sleep 1 + echo "scan on"; sleep 8 + echo "scan off"; sleep 1 + echo "connect $KEYBOARD_ADDR"; sleep 3 + echo "quit" +} | bluetoothctl +``` + +## WiFi + +### Driver + +**The RTL8723D USB WiFi interface is NOT supported by any in-tree +driver in Linux 6.1.** Specifically: + +| Driver | Status in 6.1 | +|--------|---------------| +| `rtl8xxxu` | Does not support 0bda:d723 | +| `r8723bs` | SDIO only, not USB | +| `rtw88` | Only has PCIe variant (RTW88_8723DE), USB variant added in 6.2 | + +The solution is the **out-of-tree `lwfinger/rtw88` driver**, which +backports USB support for RTL8723D to older kernels. This is the same +approach used by the Xiegu X6100 community. + +Buildroot package: `br2_external/package/rtw88/rtw88.mk` + +```makefile +RTW88_VERSION = 461b696b51317ba4ca585a4ddb32f2e72cd4efc9 +RTW88_SITE = https://github.com/lwfinger/rtw88 +RTW88_SITE_METHOD = git +RTW88_LICENSE = GPL-2.0 +$(eval $(kernel-module)) +$(eval $(generic-package)) +``` + +After loading, the driver creates `wlan0`: +``` +3: wlan0: mtu 1500 qdisc noop qlen 1000 + link/ether cc:64:1a:52:91:85 brd ff:ff:ff:ff:ff:ff +``` + +### Firmware + +Two firmware components are required: + +| Component | Buildroot symbol | File | +|-----------|-----------------|------| +| WiFi firmware | `BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88` | `rtw88/rtw8723d_fw.bin` | +| Regulatory DB | `BR2_PACKAGE_LINUX_FIRMWARE_REGULATORY` | `regulatory.db` | + +**Note:** After adding `BR2_PACKAGE_LINUX_FIRMWARE_RTL_RTW88`, you +must run `linux-firmware-dirclean` before rebuilding, otherwise the +`br-firmware.tar` archive is not regenerated and the firmware file +will not appear in the target: + +```bash +./scripts/build.sh linux-firmware-dirclean +./scripts/build.sh linux-firmware +./scripts/build.sh +``` + +### Network Configuration + +WiFi is configured via `wpa_supplicant` with a multi-network +configuration file at `/etc/wpa_supplicant.conf`: + +```ini +ctrl_interface=/var/run/wpa_supplicant +update_config=1 +country=SE + +network={ + ssid="Network_1" + psk="password_1" + priority=10 +} + +network={ + ssid="Network_2" + psk="password_2" + priority=5 +} +``` + +`wpa_supplicant` automatically connects to the highest-priority +available network. Add as many `network={}` blocks as needed. + +The file is installed in the rootfs overlay but should be considered +sensitive β€” do not commit real passwords to git. + +### Boot Sequence + +``` +S35wifi-bt (or S45wifi-bt) + β†’ x6200_gpio_init() + β†’ x6200_gpio_set(357, 0) ← powers on RTL8723D + β†’ modprobe btusb ← registers hci0 + β†’ modprobe uhid + β†’ modprobe hidp + β†’ sleep 3 ← wait for USB enumeration + +S40bluetoothd + β†’ bluetoothd --compat ← BlueZ daemon + +S46wifi + β†’ ip link set wlan0 up + β†’ wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf + β†’ udhcpc -i wlan0 ← DHCP client + +S85bt-keyboard + β†’ auto-connect loop (scan + connect every 20s) +``` + +**Ordering is critical:** The GPIO power-on script must run before +`bluetoothd` and `wpa_supplicant`. Without GPIO 357 = 0, neither +WiFi nor Bluetooth hardware is visible on the USB bus. + +## Known Quirks + +**GPIO is active low.** `gpio_set(357, 0)` = ON, `gpio_set(357, 1)` += OFF. This is the opposite of what one might expect. + +**Single USB device, two functions.** WiFi and BT share `0bda:d723`. +Powering on the chip enables both simultaneously. There is no way to +enable one without the other. + +**rtw8723d_config.bin is missing from linux-firmware package.** +Buildroot's `BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT` does not include +this 10-byte config file. Must be added to overlay manually. + +**BlueZ 5.79 HID input plugin is broken.** Downgrade to 5.64 for +working Bluetooth keyboard and mouse support. + +**lwfinger/rtw88 is pinned to a specific commit.** The driver is +under active development; newer commits may break things. Pin to a +known-good commit in `rtw88.mk`. + +**linux-firmware-dirclean required after adding RTW88 firmware.** +Buildroot caches `br-firmware.tar` and does not regenerate it when +new firmware symbols are added to `.config`. Always run +`linux-firmware-dirclean` after changing firmware selection. + +## References + +- [lwfinger/rtw88](https://github.com/lwfinger/rtw88) β€” out-of-tree + RTW88 driver with USB support for RTL8723DU +- [AetherRadio/X6200Control](https://github.com/gdyuldin/X6200Control) β€” + GPIO control library; `x6200_pin_wifi` (pin 357) is defined in + `include/aether_radio/x6200_control/low/gpio.h` +- [gdyuldin/AetherX6200Buildroot](https://github.com/gdyuldin/AetherX6200Buildroot) β€” + reference Buildroot configuration; provided the `rtw88` package + recipe that Mestre adapts +- Linux kernel `drivers/net/wireless/realtek/rtw88/` β€” mainline RTW88 + driver (USB variant added in 6.2, not available in 6.1) +- Realtek RTL8723D datasheet β€” combo WiFi+BT chip used in X6200