Skip to content

jaguar3(8822e): port kernel eFEM GPIO pinmux — RX path B was antenna-less under the static DPDT route#289

Open
gilankpam wants to merge 1 commit into
OpenIPC:masterfrom
gilankpam:fix/8822e-efem-pinmux
Open

jaguar3(8822e): port kernel eFEM GPIO pinmux — RX path B was antenna-less under the static DPDT route#289
gilankpam wants to merge 1 commit into
OpenIPC:masterfrom
gilankpam:fix/8822e-efem-pinmux

Conversation

@gilankpam

@gilankpam gilankpam commented Jul 13, 2026

Copy link
Copy Markdown

Summary

The 8822E front-end fix (#268 / b5a6df7) routes the DPDT antenna transfer switch with a static two-bit write: REG_LED_CFG 0x4c[24] set, [22] clear. That made MCS4+ TX work — but it parks the switch in a TX-favoring position that disconnects RX path B's antenna on every 8812EU: chain-B pwdb pins at the noise floor (~10 raw / −99 dBm) no matter which antenna is fitted.

Nobody saw it because every RX validation to date — including #268's full-duplex proof and the 0x41e8 desense recheck — counts total frames, and chain A alone carries the stream. The loss is real, though: a dead chain B forfeits MRC diversity (3–5 dB, more in NLOS multipath).

This PR replaces the static write with a port of the kernel's actual mechanism: _efem_pinmux_config (rtl8822e_halinit.c, rfe 21–24) → halmac pinmux_set_func_8822e for RFE_CTRL_3/5/7/8/9/11, transcribed entry-for-entry from halmac_gpio_8822e.c. GPIO13 becomes WL_DPDT_SEL driven by the RFE engine's RFE_CTRL_9, so the transfer switch follows TX/RX in hardware: PA on TX, both LNAs on RX — kernel parity.

Evidence

Two-adapter bench (BL-M8812EU2 ↔ 8812EU, ch149, 20 MHz, MCS5 at 6 Mbps offered unless noted, txagc 30 near-field, per-chain rssi/snr from the phystatus via a per-chain-reporting RX tool). Same card, back-to-back bring-ups, only the DPDT route changing:

DPDT route RX chain A RX chain B TX @ MCS7 (10M offered)
static 0x4c[24]+[22]-clear (current) −68 dBm / 27 dB −99 dBm / dead 9.9M goodput, clean
0x4c[24] only (halmac WL_DPDT_SEL bit alone) −68 / 27 −77 / 19 airs nothing
eFEM pinmux (this PR) −70 / 26 −77 / 19 9.7M goodput, clean
  • The bit-level bisect that found it: with the full merge in place, skipping only the DPDT block brings chain B back; skipping only the 1SS TX mapping does not. Within the block, keeping [22] untouched revives chain B (and breaks TX — hence neither bit combination is sufficient; the pin needs its function claim).
  • Read-back before the write shows 0x4c[22] SET from reset (0x0062e282), i.e. GPIO13 parked as BT_WAKE — which is also why [24]-only TX airs nothing: no one has claimed the pin for the RFE engine. The missing piece is the pin-function claim (0x42[1] on GPIO13 + 0x43[3]/[4] fixups), which only the full pinmux walk performs.
  • Validated with both link ends running this port simultaneously: TX MCS7 9.7M / MCS5 6.0M at 0.0% loss / MCS0 clean, while RX reports both chains live (−70/−77 dBm, snr 26/19) — the combination that was previously impossible.
  • 0x41e8 history (quirks doc updated): the register is exonerated by direct A/B; the "RX desense" it was blamed for was this antenna disconnection all along, and both prior rechecks were blind to it because they counted total frames.

Implementation notes

  • RtlJaguar3Device::efem_pinmux_8822e(): per-pin priority-list walk (halmac semantics — disable every claim above the target: field := ~val & msk; enable the target: field := val & msk; stop) + the WL/BT ownership fixups, as data tables. Kernel call order preserved (RFE_CTRL_3, 5, 7, 8, 9, 11).
  • Runs post-coex in InitWrite — coex GPIO_MUXCFG writes would mask it; same ordering lesson as the existing RFE pad block. The PAD_CTRL1[29:28] post-coex re-assert is kept.
  • The old routes remain selectable for board A/B: DEVOURER_DPDT_MODE=legacy|bit24|skip (default efem).
  • docs/8822e-quirks.md updated: the pin-mux section now describes the hardware-controlled route and its A/B matrix, and adds the honest moral — per-chain RSSI is the only trustworthy RX-health metric on a 2T2R part.

Suggested follow-up (not in this PR)

GetActiveRxPaths/per-chain health could gate CI-style validation so a single-chain regression can't hide behind total-frame counts again.

🤖 Generated with Claude Code

…less under the static DPDT route

The 8822E front-end fix (OpenIPC#268/b5a6df7) routed the DPDT antenna transfer
switch with a static two-bit write (REG_LED_CFG 0x4c[24] set, [22]
clear). That fixed MCS4+ TX — but parked the switch in a TX-favoring
position that disconnects RX path B's antenna on every 8812EU: chain-B
pwdb pins at the noise floor (~10 raw / -99 dBm) regardless of antenna.
Invisible to every total-frame-count validation (incl. the OpenIPC#268
full-duplex proof and the 0x41e8 desense recheck) because chain A
carries the stream; found with per-chain RSSI on a two-adapter bench
(2026-07-14).

Live A/B on the same card, seconds apart (8812EU <-> 8812EU, ch149,
MCS5 6 Mbps offered, per-chain rssi/snr from the phystatus):

  DPDT route            RX chain A   RX chain B    TX @ MCS7
  static (b5a6df7)      -68 / 27dB   -99 / dead    9.9M clean
  0x4c[24] only         -68 / 27dB   -77 / 19dB    airs nothing
  eFEM pinmux (this)    -70 / 26dB   -77 / 19dB    9.7M clean

Fix: port the kernel's _efem_pinmux_config (rtl8822e_halinit.c, rfe
21-24) -> halmac pinmux_set_func_8822e for RFE_CTRL_3/5/7/8/9/11:
per-pin priority-list walk (disable the claims above the target, enable
the target) + WL/BT ownership fixups, transcribed entry-for-entry from
halmac_gpio_8822e.c. GPIO13 becomes WL_DPDT_SEL driven by the RFE
engine's RFE_CTRL_9, so the transfer switch follows TX/RX in hardware:
PA on TX, BOTH LNAs on RX — kernel parity. Runs post-coex in InitWrite
(coex GPIO_MUXCFG writes would mask it; same ordering lesson as the
existing RFE pad block).

Validated with both ends on this port simultaneously: TX MCS7 9.7M
goodput / MCS5 6.0M 0.0% loss / MCS0 clean, while RX reports both
chains live (A/B -70/-77 dBm, snr 26/19). The old routes stay
selectable for board A/B via DEVOURER_DPDT_MODE=legacy|bit24|skip
(default: efem). docs/8822e-quirks.md updated — including the 0x41e8
history: the register is exonerated, the desense it was blamed for was
this antenna disconnection, and per-chain RSSI is the only honest RX
health metric on a 2T2R part.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbMmK39gvTZUeWuAx8ujCH

@josephnef josephnef left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed with the vendor tree open and the local bench running — details per finding inline.

Verified:

  • Vendor-source parity is exact. Diffed every table against reference/rtl88x2eu/.../halmac_gpio_8822e.c: all 6 entry lists match entry-for-entry, correctly truncated at the first switch_func match per pinmux_switch_8822e (GPIO2/14 stop at the _4_5 variant, GPIO4/13 at WL_RFE_CTRL_8/9); walk semantics (byte RMW, ~val & msk disable, enable target, stop) and all post-switch fixups match, address arithmetic checked (0x43 = REG_GPIO_MUXCFG+3, 0x4F = REG_LED_CFG+3 — bit0 ≙ 0x4c[24], so the eFEM path still sets the old TX bit via the proper claim; 0x67 = REG_PAD_CTRL1+3, 0x3F = REG_GPIO_MUXCFG_2). Call order 28..33 → RFE_CTRL_3,5,7,8,9,11 matches _efem_pinmux_config / _gpio_to_func_for_rfe_ctrl.
  • Build clean, ctest 20/20.
  • On-air TX A/B on this rig (regress.py --modes devourer:devourer, 8812EU TX → 8812AU RX, ch36, 10 s cells, two RX cells per mode): legacy 270/200 hits, efem 283/200, bit24 90/10 — efem at TX parity with legacy, bit24 collapsed. Independently confirms the TX side of your matrix. The RX chain-B half isn't locally verifiable here (single 8812EU on the rig), but the mechanism is vendor-exact and the bench evidence is coherent.

Asks before merge: the env-read convention violation (inline) and preferably the one-line rfe_type 21–24 gate (inline). The Init-vs-InitWrite asymmetry can be a follow-up bench check alongside your suggested GetActiveRxPaths CI gating.

Good work — the bisect narrative is convincing and the legacy A/B escape hatch is the right safety net.

Comment on lines +810 to +813
const char *dpdt_env = std::getenv("DEVOURER_DPDT_MODE");
std::string dpdt_mode = dpdt_env ? dpdt_env : "efem";
if (std::getenv("DEVOURER_SKIP_DPDT") != nullptr)
dpdt_mode = "skip";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention: the library reads no environment (CLAUDE.md Configuration). Every existing knob — including apply_replay_wseq just below, which reads _cfg.debug.replay_wseq — routes through DeviceConfig with the env mapping in examples/common/env_config.cpp. Please move this to a debug (or tuning) field with the usual env: doc tag and map DEVOURER_DPDT_MODE there.

Also DEVOURER_SKIP_DPDT is an undocumented second spelling of mode=skip (it appears nowhere in the docs or DeviceConfig) — drop it or fold it into the documented knob.

if (std::getenv("DEVOURER_SKIP_DPDT") != nullptr)
dpdt_mode = "skip";
if (dpdt_mode == "efem") {
efem_pinmux_8822e();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kernel gates _efem_pinmux_config on rfe_type 21–24 (rtl8822e_halinit.c); this applies unconditionally on every 8822E. The gate is cheap kernel parity: _phy_ctx.rfe_type is already read from efuse, and the existing RFE pad block (HalJaguar3.cpp, config_rfe) already gates on 21..24. All benched boards are rfe 21 (and devourer defaults efuse 0/0xff → 21), so low practical risk today — but a non-eFEM 8822E board would get GPIOs claimed for an RFE engine its front end doesn't wire.

Comment on lines +569 to +570
_logger->warn("Jaguar3(8822e): eFEM pin-mux applied (RFE_CTRL 3/5/7/8/9/11 "
"-> RFE engine; DPDT under hardware TX/RX control)");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warn for the default/successful path — info is the house style for applied-config lines (the warns on the legacy/skip branches are right, those should be loud).

* improvised b5a6df7 DPDT write, which took two 0x4c bits in isolation
* (set [24], clear [22]) and thereby parked the DPDT antenna transfer
* switch in a static TX-favoring position: TX MCS4+ worked, but RX
* path B lost its antenna on every 8812EU (bench 2026-07-14: chain-B

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the hardcoded bench date will rot; the fact stands without it (git is the changelog).

* Routes the DPDT antenna transfer switch to the RFE engine (hardware
* TX/RX switching) instead of the b5a6df7 static write that deafened RX
* path B. Called from InitWrite post-coex; see the call site. */
void efem_pinmux_8822e();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kernel runs this in rtl8822e_init_misc during general HAL init (both directions); this port only touches InitWrite, so RX-only Init sessions keep the reset-default pins (GPIO13 parked as BT_WAKE per your own read-back). Worth one bench check: is chain B alive in an RX-only session? If not, the pinmux belongs in the shared bring-up; if it is, a one-line comment on the asymmetry would prevent the next reader from "fixing" it. Fine as a follow-up alongside your suggested per-chain CI gating.

@josephnef josephnef left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking changes requested per the inline review (#289 (review)): (1) move DEVOURER_DPDT_MODE off std::getenv into DeviceConfig + env_config.cpp (the library reads no environment) and drop/fold the undocumented DEVOURER_SKIP_DPDT; (2) gate the eFEM pinmux on rfe_type 21–24 like the kernel. The Init-vs-InitWrite asymmetry and the nits can be follow-ups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants