From 56684f73cb16f2f7496d45bcd4ea4bed71954b91 Mon Sep 17 00:00:00 2001 From: snokvist Date: Tue, 27 Oct 2026 08:59:39 +0100 Subject: [PATCH 1/5] jaguar2: a defined AGC table, fc and RF 0xBE for every 5 GHz channel The 8822B/8821C channel set only wrote the AGC table index (0x958 / 0xc1c), the CFO-tracking fc (0x860[28:17]) and, on the 8822B, the RF 0xBE VCO band for channels in the vendor buckets (36..64 / 100..144 / 149+). Anything else - the 65..99 gap between the UNII-2 groups, 145..148, the unlisted fc channels 49..51 / 117, and the vendor's unwritten RF 0xBE entries at centrals 50/114/130/163 (a 40 MHz tune at 5255 MHz is central 51) - kept whatever the previous channel left, so a receiver tuned there depended on its tuning history. Measured on one 8812BU (40 MHz, fixed gain), same unit before and after: 5350 / 5450 MHz read 3.6 / 4.5 dB apart (noise floor and a fixed carrier alike, SNR unchanged) depending on whether they were reached from 5180 or from 5510/5745. After: 0.4 / 0.7 dB, inside an in-table control's 0.8 dB. The AGC table was what mattered: a stale RF 0xBE alone (centrals 51, 163) showed nothing beyond noise, so the RF 0xBE values written there are untested either way. - agc_table_5g: the vendor buckets, the channels between them taking the RF18 sub-band they tune in (mid from 80 on the 8822B, 100 on the 8821C). - cfo_fc_5g: the vendor buckets, unlisted channels assigned as the 8822C's contiguous sco table does. - rf_be_for_8822b: 15..148 from the 6-step cycle that all 35 defined entries of the vendor's 36..144 tables follow (only the unwritten ones change); the high table with 163 filled from 151/175. It never returns the 0xff no-write sentinel now, so the sentinel and its guards go. - The full path, the 8821C full path and fast_retune share the helpers. Checked against config_phydm_switch_channel_8822b/_8821c for every cch 15..253: no vendor-defined channel changes value. - A firmware fast hop (fastretune_fw) now invalidates the write-on-change caches on every hop, not only on a band change: the firmware rewrites the same registers, and a later software hop could otherwise skip a write and keep the firmware's value. - The uncharacterized-channel warning tests the central channel and covers 65..99, on Jaguar2 and Jaguar3. The 8821C changes are code-covered only. The 8822C needed no change: its rf18_for / select_agc_tables / sco_for are contiguous, and a measured 8812CU showed no history dependence in the gap. Co-Authored-By: Claude Opus 5.5 --- src/jaguar2/CLAUDE.md | 16 +++ src/jaguar2/HalJaguar2.cpp | 181 +++++++++++-------------- src/jaguar2/HalJaguar2.h | 7 +- src/jaguar3/RadioManagementJaguar3.cpp | 21 +-- 4 files changed, 111 insertions(+), 114 deletions(-) diff --git a/src/jaguar2/CLAUDE.md b/src/jaguar2/CLAUDE.md index 7c3b24ac..d84c9cfb 100644 --- a/src/jaguar2/CLAUDE.md +++ b/src/jaguar2/CLAUDE.md @@ -34,6 +34,22 @@ Jaguar1 (shared `PhyTableLoader`). RF18-edge bug. - The TXAGC block is write-only, so `GetTxPowerState` reports the software shadow (`hw_readback=false`). +- **Every 5G central channel gets a defined AGC table, fc and RF 0xBE** + (`agc_table_5g` / `cfo_fc_5g` / `rf_be_for_8822b`, shared by the full and + fast paths). The vendor buckets stop at 64 and resume at 100, and its RF + 0xBE tables leave centrals 50/114/130/163 unwritten, so those channels + used to inherit whatever the previous channel left. On one 8812BU at + 40 MHz, fixed gain, the stale AGC table was what mattered: 5350 / 5450 MHz + read 3.6 / 4.5 dB apart (floor and a fixed carrier alike, SNR unchanged) + depending on whether they were reached from 5180 or from 5510/5745; after + the fix 0.4 / 0.7 dB, inside an in-table control's 0.8 dB. The AGC split follows the RF18 + sub-band (80 on the 8822B, 100 on the 8821C). RF 0xBE comes from the + 12-channel cycle that all 35 defined entries of the vendor's 36..144 tables + follow. A stale RF 0xBE alone showed no measurable effect (centrals 51 and + 163: spread within the control's), so its values are untested either way. + The 8821C changes are code-covered only. A firmware hop (`fastretune_fw`) + invalidates these write-on-change caches every time, not only on a band + change: the firmware rewrites the same registers. - **Firmware channel switch** (H2C 0x1D `SINGLE_CHANNELSWITCH_V2`, `fw_channel_switch` + the `fastretune_fw` knob; both Jaguar2 dies — the path is variant-generic, 8822B on-air-validated, 8821C code-covered diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index c81b6c4b..d34953ef 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -1,5 +1,6 @@ #include "HalJaguar2.h" +#include #include #include #include @@ -879,17 +880,19 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, _last_cck_key = -1; _fw_sw_pending = 0; /* the full set supersedes any in-flight fw switch */ - /* Extended-synth channels (below-band 15..35 / above 177, freq = - * 5000 + 5*ch up to ch 253): the RF tunes them, but the power tables and - * per-channel constants are clamped from the nearest characterized - * channel. Warn once so an unexpected retune there is visible. */ + /* Extended-synth channels (below-band 15..35 / the 65..99 gap between the + * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central + * channel decides): the RF tunes them, but the power tables and per-channel + * constants are extrapolated from the characterized channels. Warn once so + * an unexpected retune there is visible. */ + const uint8_t wch = central_ch(channel, bw, primary_ch_idx); if (!_warned_uncharacterized && - ((channel >= 15 && channel <= 35) || channel > 177)) { + ((wch >= 15 && wch <= 35) || (wch >= 65 && wch <= 99) || wch > 177)) { _warned_uncharacterized = true; - _logger->warn("channel {} is outside the characterized range — TX power " - "and per-channel constants extrapolated from the nearest " - "table entry", - channel); + _logger->warn("channel {} (central {}) is outside the characterized range " + "— TX power and per-channel constants extrapolated from the " + "nearest table entry", + channel, wch); } if (_variant == ChipVariant::C8821C) { @@ -922,24 +925,10 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, _device.phy_set_bb_reg(0x0a28, 0x0000ffff, 0x1525); } } else { - /* 5G AGC table + fc (config_phydm_switch_channel_8822b 5G branch). - * Extended-range clamps: below-band ch 15..35 rides the low bucket - * (mirrors Jaguar1's explicit 15..35 case) and ch >177 keeps the top - * bucket — the vendor tables end at 177 but the synth tunes past it. */ - if (cch <= 64) - _device.phy_set_bb_reg(0x0958, 0x1f, 0x1); - else if (cch >= 100 && cch <= 144) - _device.phy_set_bb_reg(0x0958, 0x1f, 0x2); - else if (cch >= 149) - _device.phy_set_bb_reg(0x0958, 0x1f, 0x3); - if (cch <= 48) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x494); - else if (cch >= 52 && cch <= 64) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x453); - else if (cch >= 100 && cch <= 116) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x452); - else if (cch >= 118) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x412); + /* 5G AGC table + fc (config_phydm_switch_channel_8822b 5G branch), defined + * for every channel — see agc_table_5g / cfo_fc_5g. */ + _device.phy_set_bb_reg(0x0958, 0x1f, agc_table_5g(cch)); + _device.phy_set_bb_reg(0x0860, 0x1ffe0000, cfo_fc_5g(cch)); } /* config_phydm_switch_band_8822b RxHP-with-SoML block (SoML is enabled at @@ -962,9 +951,7 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, * fast_retune). Omitting it (writing 0 on 5G) leaves the 5G VCO mis-tuned so * 5G RX/TX fail entirely — this was the sole gap blocking 5G. Path A only * (matches the vendor). */ - const uint8_t rf_be = rf_be_for_8822b(cch); - if (rf_be != 0xff) - rf_set(0, 0xbe, (1u << 17) | (1u << 16) | (1u << 15), rf_be); + rf_set(0, 0xbe, (1u << 17) | (1u << 16) | (1u << 15), rf_be_for_8822b(cch)); if (cch == 144) { rf_set(0, 0xdf, (1u << 18), 0x1); @@ -1226,32 +1213,51 @@ void HalJaguar2::DumpCanary() { } /* RF 0xBE[17:15] per-5G-channel phase-noise / VCO-band value - * (config_phydm_switch_channel_8822b low/middle/high-band tables). */ + * (config_phydm_switch_channel_8822b). Every defined entry of the vendor's + * low and middle tables (36..64 / 100..144, 35 of them) follows one 6-step + * cycle repeating every 12 channels, continuous across 64 -> 100, so 15..148 + * take the cycle: identical on the vendor channels, and it also fills the + * vendor's no-write entries (centrals 50/114/130, used by its 160 MHz + * channels) and the 65..99 / 145..148 gaps, so no retune keeps a stale value. + * The high table (odd channels) is kept, its no-write entry at 163 filled + * from 12 channels either side (151 and 175 are both 0x5). Below 36 and above + * 177 clamp to the nearest edge. Outside the vendor tables: extrapolated, + * not characterized. */ uint8_t HalJaguar2::rf_be_for_8822b(uint8_t cch) { - static const uint8_t low_band[15] = {0x7, 0x6, 0x6, 0x5, 0x0, 0x0, 0x7, 0xff, - 0x6, 0x5, 0x0, 0x0, 0x7, 0x6, 0x6}; - static const uint8_t middle_band[23] = { - 0x6, 0x5, 0x0, 0x0, 0x7, 0x6, 0x6, 0xff, 0x0, 0x0, 0x7, 0x6, - 0x6, 0x5, 0x0, 0xff, 0x7, 0x6, 0x6, 0x5, 0x0, 0x0, 0x7}; - static const uint8_t high_band[15] = {0x5, 0x5, 0x0, 0x7, 0x7, 0x6, 0x5, 0xff, + static const uint8_t cycle[6] = {0x7, 0x6, 0x6, 0x5, 0x0, 0x0}; + static const uint8_t high_band[15] = {0x5, 0x5, 0x0, 0x7, 0x7, 0x6, 0x5, 0x5, 0x0, 0x7, 0x7, 0x6, 0x5, 0x5, 0x0}; if (cch <= 14) return 0x0; - /* Extended-range clamps: the vendor tables cover 36..64 / 100..144 / - * 149..177; the synth tunes below 36 and above 177 (chan*5+5000 grid up to - * 253). Clamp to the nearest table edge so the VCO band is still set — - * extrapolated, not characterized. */ - if (cch < 36) - return low_band[0]; - if (cch <= 64) - return low_band[(cch - 36) >> 1]; - if (cch >= 100 && cch <= 144) - return middle_band[(cch - 100) >> 1]; - if (cch >= 149 && cch <= 177) - return high_band[(cch - 149) >> 1]; - if (cch > 177) - return high_band[(177 - 149) >> 1]; - return 0xff; + if (cch >= 149) + return high_band[(std::min(cch, 177) - 149) >> 1]; + return cycle[((std::max(cch, 36) - 36) >> 1) % 6]; +} + +/* AGC table index. The vendor buckets (36..64 low, 100..144 mid, 149+ high) + * are kept; the channels between them take the RF18 sub-band they tune in + * (8822B: mid from cch 80; 8821C: mid from 100) and 145..148 the high table. Below-band 15..35 + * rides the low bucket and >177 the high one (extended-range clamps). */ +uint8_t HalJaguar2::agc_table_5g(uint8_t cch) const { + const uint8_t mid_from = _variant == ChipVariant::C8821C ? 100 : 80; + if (cch < mid_from) + return 0x1; + if (cch <= 144) + return 0x2; + return 0x3; +} + +/* CFO-tracking fc: the vendor's 8822B/8821C buckets (<=48 / 52..64 / 100..116 + * / >=118), with the unlisted channels between them assigned as the 8822C's + * contiguous sco table does (<=51 0x494, <=111 0x453, <=119 0x452). */ +uint32_t HalJaguar2::cfo_fc_5g(uint8_t cch) { + if (cch <= 51) + return 0x494; + if (cch <= 99) + return 0x453; + if (cch <= 117) + return 0x452; + return 0x412; } bool HalJaguar2::fast_set_bandwidth(uint8_t bw) { @@ -1426,15 +1432,15 @@ bool HalJaguar2::fast_retune(uint8_t channel, uint8_t bw, pri = (primary_ch_idx == 2 /*HAL_PRIME_CHNL_OFFSET_UPPER*/) ? 1 : 2; fw_channel_switch(cch, pri, bw); prof.mark("fw"); - if (band_change) { - /* The firmware rewrote the band-keyed state under the sw caches — - * invalidate them all; the next sw hop re-primes. */ - _last_agc_bucket = -1; - _last_fc = 0xffffffff; - _last_rf_be = -1; - _last_df18 = -1; - _last_cck_key = -1; - } + /* The firmware rewrote the channel-keyed state (AGC table, fc, RF 0xBE, + * band) under the sw caches, on a same-band hop too — invalidate them + * all, or a later sw hop that maps to a cached value skips its write and + * keeps the firmware's. The next sw hop re-primes. */ + _last_agc_bucket = -1; + _last_fc = 0xffffffff; + _last_rf_be = -1; + _last_df18 = -1; + _last_cck_key = -1; _last_tuned_ch = channel; DVR_DEBUG(_logger, "Jaguar2: fw fast retune -> ch {} (central {})", channel, cch); @@ -1473,19 +1479,10 @@ bool HalJaguar2::fast_retune(uint8_t channel, uint8_t bw, /* Channel-keyed constants, written only when their bucket moves (the caches * are invalidated by every full set, so the first fast hop writes them once). * AGC table index + CFO-tracking fc share buckets across the variants; only - * the AGC register differs (0x958[4:0] on 8822B, 0xc1c[11:8] on 8821C). The - * bucket gaps (e.g. cch 65..99) match the full path, which leaves the - * registers untouched there. */ - int agc_bucket = -1; - if (g2) - agc_bucket = 0; - else if (cch <= 64) /* incl. below-band 15..35 (extended-range clamp) */ - agc_bucket = 1; - else if (cch >= 100 && cch <= 144) - agc_bucket = 2; - else if (cch >= 149) - agc_bucket = 3; - if (agc_bucket >= 0 && agc_bucket != _last_agc_bucket) { + * the AGC register differs (0x958[4:0] on 8822B, 0xc1c[11:8] on 8821C). + * Both are defined for every channel, as in the full path. */ + const int agc_bucket = g2 ? 0 : agc_table_5g(cch); + if (agc_bucket != _last_agc_bucket) { if (c8821) { _cw_agc = (_cw_agc & ~0x00000F00u) | (static_cast(agc_bucket) << 8); @@ -1497,18 +1494,8 @@ bool HalJaguar2::fast_retune(uint8_t channel, uint8_t bw, _last_agc_bucket = agc_bucket; } - uint32_t fc = 0xffffffff; - if (g2) - fc = 0x96a; - else if (cch <= 48) /* incl. below-band 15..35 (extended-range clamp) */ - fc = 0x494; - else if (cch >= 52 && cch <= 64) - fc = 0x453; - else if (cch >= 100 && cch <= 116) - fc = 0x452; - else if (cch >= 118) /* open-ended: extended channels >177 keep 0x412 */ - fc = 0x412; - if (fc != 0xffffffff && fc != _last_fc) { + const uint32_t fc = g2 ? 0x96a : cfo_fc_5g(cch); + if (fc != _last_fc) { _cw_fc = (_cw_fc & ~0x1ffe0000u) | (fc << 17); _device.rtw_write32(0x0860, _cw_fc); _last_fc = fc; @@ -1518,7 +1505,7 @@ bool HalJaguar2::fast_retune(uint8_t channel, uint8_t bw, /* 8822B: RF 0xBE VCO band (per-5G-channel — omitting it detunes the 5G * VCO), the ch144 RF 0xDF[18] flag, and the 2G spur registers. */ const uint8_t rf_be = rf_be_for_8822b(cch); - if (rf_be != 0xff && rf_be != _last_rf_be) { + if (rf_be != _last_rf_be) { _cw_rfbe = (_cw_rfbe & ~0x38000u) | (static_cast(rf_be) << 15); rf_write(0, 0xbe, _cw_rfbe); @@ -1826,22 +1813,10 @@ void HalJaguar2::set_channel_bw_8821c(uint8_t channel, uint8_t bw, _device.phy_set_bb_reg(0x0aac, 0xffffffff, _cck_aac_8821c); } } else { - /* Extended-range clamps (see the 8822B branch): ch 15..35 rides the low - * bucket, ch >177 keeps the top bucket. */ - if (cch <= 64) - _device.phy_set_bb_reg(0x0c1c, 0x00000F00, 0x1); - else if (cch >= 100 && cch <= 144) - _device.phy_set_bb_reg(0x0c1c, 0x00000F00, 0x2); - else if (cch >= 149) - _device.phy_set_bb_reg(0x0c1c, 0x00000F00, 0x3); - if (cch <= 48) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x494); - else if (cch >= 52 && cch <= 64) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x453); - else if (cch >= 100 && cch <= 116) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x452); - else if (cch >= 118) - _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x412); + /* AGC table + fc defined for every channel (see agc_table_5g / + * cfo_fc_5g). */ + _device.phy_set_bb_reg(0x0c1c, 0x00000F00, agc_table_5g(cch)); + _device.phy_set_bb_reg(0x0860, 0x1ffe0000, cfo_fc_5g(cch)); if (cch >= 100 && cch <= 140) rf18 |= (1u << 17); else if (cch > 140) diff --git a/src/jaguar2/HalJaguar2.h b/src/jaguar2/HalJaguar2.h index 88922c17..fdd1566f 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -320,8 +320,13 @@ class HalJaguar2 { * primary; 40 MHz -> ±2 by primary_ch_idx; 80 MHz -> +6/+2/-2/-6. */ static uint8_t central_ch(uint8_t channel, uint8_t bw, uint8_t primary_ch_idx); /* RF 0xBE[17:15] per-5G-channel phase-noise / VCO-band value (8822B); 0 for - * 2.4 GHz, 0xff = no write (shared full/fast paths). */ + * 2.4 GHz (shared full/fast paths). */ static uint8_t rf_be_for_8822b(uint8_t cch); + /* 5G AGC table index (1 low / 2 mid / 3 high) and CFO-tracking fc + * (0x860[28:17]) for any 5G central channel (shared full/fast paths, both + * variants). Defined for every channel so no retune inherits a stale value. */ + uint8_t agc_table_5g(uint8_t cch) const; + static uint32_t cfo_fc_5g(uint8_t cch); /* fast_retune write-on-change caches. Invalidated by every full * set_channel_bw (either variant); they only ever mirror fast-path writes — diff --git a/src/jaguar3/RadioManagementJaguar3.cpp b/src/jaguar3/RadioManagementJaguar3.cpp index a397ee0a..89d13f05 100644 --- a/src/jaguar3/RadioManagementJaguar3.cpp +++ b/src/jaguar3/RadioManagementJaguar3.cpp @@ -48,18 +48,19 @@ void RadioManagementJaguar3::set_channel_bwmode(uint8_t channel, * fast-path writes (the J1 RadioManagementModule pattern). */ invalidate_fast_caches(); - /* Extended-synth channels (below-band 15..35 / above 177, freq = - * 5000 + 5*ch up to ch 253): the RF tunes them, but the power tables, - * calibration bands and per-channel constants are clamped from the nearest - * characterized channel. Warn once so an unexpected retune there is - * visible. */ + /* Extended-synth channels (below-band 15..35 / the 65..99 gap between the + * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central + * channel decides): the RF tunes them, but the power tables and calibration + * bands are clamped from the nearest characterized channel. Warn once so an + * unexpected retune there is visible. */ if (!_warned_uncharacterized && - ((channel >= 15 && channel <= 35) || channel > 177)) { + ((central >= 15 && central <= 35) || (central >= 65 && central <= 99) || + central > 177)) { _warned_uncharacterized = true; - _logger->warn("channel {} is outside the characterized range — TX power " - "and per-channel constants extrapolated from the nearest " - "table entry", - channel); + _logger->warn("channel {} (central {}) is outside the characterized range " + "— TX power and per-channel constants extrapolated from the " + "nearest table entry", + channel, central); } _last_channel = channel; From d580824b34287788b82595bc34f9bf5310eca2c1 Mon Sep 17 00:00:00 2001 From: snokvist Date: Tue, 27 Oct 2026 09:22:25 +0100 Subject: [PATCH 2/5] jaguar2: gap-channel warning 5 GHz only; notes carry their caveats The uncharacterized-channel warning tests the central channel, which central_ch wraps for out-of-spec 2.4 GHz HT40 configs (ch 13 HT40+ -> 15): gate it on a 5 GHz primary, on Jaguar2 and Jaguar3. The jaguar2 notes state what the after-fix spread can and cannot show, and record the RX-only full-vs-fast parity run (20/40 MHz, gap channels). Co-Authored-By: Claude Opus 5.5 --- src/jaguar2/CLAUDE.md | 16 ++++++++++------ src/jaguar2/HalJaguar2.cpp | 13 +++++++------ src/jaguar3/RadioManagementJaguar3.cpp | 8 ++++---- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/jaguar2/CLAUDE.md b/src/jaguar2/CLAUDE.md index d84c9cfb..79b808cf 100644 --- a/src/jaguar2/CLAUDE.md +++ b/src/jaguar2/CLAUDE.md @@ -42,12 +42,16 @@ Jaguar1 (shared `PhyTableLoader`). 40 MHz, fixed gain, the stale AGC table was what mattered: 5350 / 5450 MHz read 3.6 / 4.5 dB apart (floor and a fixed carrier alike, SNR unchanged) depending on whether they were reached from 5180 or from 5510/5745; after - the fix 0.4 / 0.7 dB, inside an in-table control's 0.8 dB. The AGC split follows the RF18 - sub-band (80 on the 8822B, 100 on the 8821C). RF 0xBE comes from the - 12-channel cycle that all 35 defined entries of the vendor's 36..144 tables - follow. A stale RF 0xBE alone showed no measurable effect (centrals 51 and - 163: spread within the control's), so its values are untested either way. - The 8821C changes are code-covered only. A firmware hop (`fastretune_fw`) + the fix 0.4 / 0.7 dB — but the in-table control spreads 0.8 dB, so that + is "no longer resolvable", not zero, and one unit at one width. The AGC + split follows the RF18 sub-band (80 on the 8822B, 100 on the 8821C). RF + 0xBE comes from the 12-channel cycle that all 35 defined entries of the + vendor's 36..144 tables follow. A stale RF 0xBE alone showed nothing + resolvable (centrals 51 and 163, below ~1.5 dB), so its values are + untested either way. Full-vs-fast canary parity holds on the same unit at + 20 and 40 MHz across the gap (RX-only `DEVOURER_RX_SWEEP` vs + `_SWEEP_FULL`, predecessors from every bucket); 80 MHz and the 8821C + changes are code-covered only. A firmware hop (`fastretune_fw`) invalidates these write-on-change caches every time, not only on a band change: the firmware rewrites the same registers. - **Firmware channel switch** (H2C 0x1D `SINGLE_CHANNELSWITCH_V2`, diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index d34953ef..1cf8125b 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -882,11 +882,11 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, /* Extended-synth channels (below-band 15..35 / the 65..99 gap between the * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central - * channel decides): the RF tunes them, but the power tables and per-channel - * constants are extrapolated from the characterized channels. Warn once so - * an unexpected retune there is visible. */ + * channel decides, 5 GHz only): the RF tunes them, but the power tables and + * per-channel constants are extrapolated from the characterized channels. + * Warn once so an unexpected retune there is visible. */ const uint8_t wch = central_ch(channel, bw, primary_ch_idx); - if (!_warned_uncharacterized && + if (!_warned_uncharacterized && channel > 14 && ((wch >= 15 && wch <= 35) || (wch >= 65 && wch <= 99) || wch > 177)) { _warned_uncharacterized = true; _logger->warn("channel {} (central {}) is outside the characterized range " @@ -1236,8 +1236,9 @@ uint8_t HalJaguar2::rf_be_for_8822b(uint8_t cch) { /* AGC table index. The vendor buckets (36..64 low, 100..144 mid, 149+ high) * are kept; the channels between them take the RF18 sub-band they tune in - * (8822B: mid from cch 80; 8821C: mid from 100) and 145..148 the high table. Below-band 15..35 - * rides the low bucket and >177 the high one (extended-range clamps). */ + * (8822B: mid from cch 80; 8821C: mid from 100) and 145..148 the high table. + * Below-band 15..35 rides the low bucket and >177 the high one + * (extended-range clamps). */ uint8_t HalJaguar2::agc_table_5g(uint8_t cch) const { const uint8_t mid_from = _variant == ChipVariant::C8821C ? 100 : 80; if (cch < mid_from) diff --git a/src/jaguar3/RadioManagementJaguar3.cpp b/src/jaguar3/RadioManagementJaguar3.cpp index 89d13f05..01e33620 100644 --- a/src/jaguar3/RadioManagementJaguar3.cpp +++ b/src/jaguar3/RadioManagementJaguar3.cpp @@ -50,10 +50,10 @@ void RadioManagementJaguar3::set_channel_bwmode(uint8_t channel, /* Extended-synth channels (below-band 15..35 / the 65..99 gap between the * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central - * channel decides): the RF tunes them, but the power tables and calibration - * bands are clamped from the nearest characterized channel. Warn once so an - * unexpected retune there is visible. */ - if (!_warned_uncharacterized && + * channel decides, 5 GHz only): the RF tunes them, but the power tables + * and calibration bands are clamped from the nearest characterized channel. + * Warn once so an unexpected retune there is visible. */ + if (!_warned_uncharacterized && channel > 14 && ((central >= 15 && central <= 35) || (central >= 65 && central <= 99) || central > 177)) { _warned_uncharacterized = true; From bbd44ee256b87ce552b633248f4ad8ef5c67f2a9 Mon Sep 17 00:00:00 2001 From: snokvist Date: Tue, 27 Oct 2026 09:22:56 +0100 Subject: [PATCH 3/5] jaguar2: wrap a header comment at 80 columns Co-Authored-By: Claude Opus 5.5 --- src/jaguar2/HalJaguar2.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jaguar2/HalJaguar2.h b/src/jaguar2/HalJaguar2.h index fdd1566f..97333c70 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -324,7 +324,8 @@ class HalJaguar2 { static uint8_t rf_be_for_8822b(uint8_t cch); /* 5G AGC table index (1 low / 2 mid / 3 high) and CFO-tracking fc * (0x860[28:17]) for any 5G central channel (shared full/fast paths, both - * variants). Defined for every channel so no retune inherits a stale value. */ + * variants). Defined for every channel so no retune inherits a stale + * value. */ uint8_t agc_table_5g(uint8_t cch) const; static uint32_t cfo_fc_5g(uint8_t cch); From 106c210a1f972e27b5b3c9e088da9a453826c34e Mon Sep 17 00:00:00 2001 From: snokvist Date: Tue, 27 Oct 2026 09:52:22 +0100 Subject: [PATCH 4/5] jaguar2/3: warn on fast hops too; one cch; notes current-state - warn_uncharacterized (Jaguar2, Jaguar3): the one-shot extended-channel warning is a helper called by the full channel set and both fast paths (software and firmware), so a fast hop into 65..99 (or below 36 / above 177) warns like a full set does (Qodo). It runs once a destination is accepted; after the first warning it is a bool test. - set_channel_bw computes cch once, before the 8821C dispatch (review). - src/jaguar2/CLAUDE.md: the bullet states what holds now (the gap's assignments, the measured spread with its control, what is untested) instead of the history, at about half the length (review). Co-Authored-By: Claude Opus 5.5 --- src/jaguar2/CLAUDE.md | 31 ++++++++------------- src/jaguar2/HalJaguar2.cpp | 37 +++++++++++++++----------- src/jaguar2/HalJaguar2.h | 3 +++ src/jaguar3/RadioManagementJaguar3.cpp | 37 ++++++++++++++++---------- src/jaguar3/RadioManagementJaguar3.h | 3 +++ 5 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/jaguar2/CLAUDE.md b/src/jaguar2/CLAUDE.md index 79b808cf..39f895e7 100644 --- a/src/jaguar2/CLAUDE.md +++ b/src/jaguar2/CLAUDE.md @@ -34,26 +34,17 @@ Jaguar1 (shared `PhyTableLoader`). RF18-edge bug. - The TXAGC block is write-only, so `GetTxPowerState` reports the software shadow (`hw_readback=false`). -- **Every 5G central channel gets a defined AGC table, fc and RF 0xBE** - (`agc_table_5g` / `cfo_fc_5g` / `rf_be_for_8822b`, shared by the full and - fast paths). The vendor buckets stop at 64 and resume at 100, and its RF - 0xBE tables leave centrals 50/114/130/163 unwritten, so those channels - used to inherit whatever the previous channel left. On one 8812BU at - 40 MHz, fixed gain, the stale AGC table was what mattered: 5350 / 5450 MHz - read 3.6 / 4.5 dB apart (floor and a fixed carrier alike, SNR unchanged) - depending on whether they were reached from 5180 or from 5510/5745; after - the fix 0.4 / 0.7 dB — but the in-table control spreads 0.8 dB, so that - is "no longer resolvable", not zero, and one unit at one width. The AGC - split follows the RF18 sub-band (80 on the 8822B, 100 on the 8821C). RF - 0xBE comes from the 12-channel cycle that all 35 defined entries of the - vendor's 36..144 tables follow. A stale RF 0xBE alone showed nothing - resolvable (centrals 51 and 163, below ~1.5 dB), so its values are - untested either way. Full-vs-fast canary parity holds on the same unit at - 20 and 40 MHz across the gap (RX-only `DEVOURER_RX_SWEEP` vs - `_SWEEP_FULL`, predecessors from every bucket); 80 MHz and the 8821C - changes are code-covered only. A firmware hop (`fastretune_fw`) - invalidates these write-on-change caches every time, not only on a band - change: the firmware rewrites the same registers. +- **Every 5G central channel gets a defined AGC table, fc and (8822B) RF + 0xBE** (`agc_table_5g` / `cfo_fc_5g` / `rf_be_for_8822b`, full and fast + paths). Gap 65..99: AGC table 1, 2 from central 80 on the 8822B (1 + through 99 on the 8821C), fc 0x453, RF 0xBE from the vendor's 12-channel + cycle. One 8812BU, 40 MHz, fixed gain: the floor at 5350 / 5450 MHz + spreads 0.4 / 0.7 dB by predecessor, against 0.8 dB for an in-table + control (5310): unresolved, not zero. The filled RF 0xBE values are + untested (a stale one showed nothing below ~1.5 dB). Full-vs-fast RX + parity holds across the gap at 20/40 MHz on the same unit; 80 MHz and + the 8821C are code-covered only. A firmware hop invalidates these + write-on-change caches, same-band hops included. - **Firmware channel switch** (H2C 0x1D `SINGLE_CHANNELSWITCH_V2`, `fw_channel_switch` + the `fastretune_fw` knob; both Jaguar2 dies — the path is variant-generic, 8822B on-air-validated, 8821C code-covered diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index 1cf8125b..9a287a11 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -867,6 +867,24 @@ uint8_t HalJaguar2::central_ch(uint8_t channel, uint8_t bw, return channel; } +/* Extended-synth channels (below-band 15..35 / the 65..99 gap between the + * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central + * channel decides, 5 GHz only): the RF tunes them, but the power tables and + * per-channel constants are extrapolated from the characterized channels. + * Warn once, from the full and both fast paths, so an unexpected retune there + * is visible. */ +void HalJaguar2::warn_uncharacterized(uint8_t channel, uint8_t cch) { + if (_warned_uncharacterized || channel <= 14) + return; + if ((cch >= 15 && cch <= 35) || (cch >= 65 && cch <= 99) || cch > 177) { + _warned_uncharacterized = true; + _logger->warn("channel {} (central {}) is outside the characterized range " + "— TX power and per-channel constants extrapolated from the " + "nearest table entry", + channel, cch); + } +} + void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, uint8_t primary_ch_idx) { /* Any full channel set rewrites what fast_retune caches (RF18 band/BW bits, @@ -880,20 +898,8 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, _last_cck_key = -1; _fw_sw_pending = 0; /* the full set supersedes any in-flight fw switch */ - /* Extended-synth channels (below-band 15..35 / the 65..99 gap between the - * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central - * channel decides, 5 GHz only): the RF tunes them, but the power tables and - * per-channel constants are extrapolated from the characterized channels. - * Warn once so an unexpected retune there is visible. */ - const uint8_t wch = central_ch(channel, bw, primary_ch_idx); - if (!_warned_uncharacterized && channel > 14 && - ((wch >= 15 && wch <= 35) || (wch >= 65 && wch <= 99) || wch > 177)) { - _warned_uncharacterized = true; - _logger->warn("channel {} (central {}) is outside the characterized range " - "— TX power and per-channel constants extrapolated from the " - "nearest table entry", - channel, wch); - } + const uint8_t cch = central_ch(channel, bw, primary_ch_idx); + warn_uncharacterized(channel, cch); if (_variant == ChipVariant::C8821C) { set_channel_bw_8821c(channel, bw, rfe_type, primary_ch_idx); @@ -902,7 +908,6 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, DumpCanary(); return; } - const uint8_t cch = central_ch(channel, bw, primary_ch_idx); const bool g2 = cch <= 14; const bool r2t2r = _ver.rf_2t2r != 0; @@ -1427,6 +1432,7 @@ bool HalJaguar2::fast_retune(uint8_t channel, uint8_t bw, return false; prof.mark("confirm"); const uint8_t cch = central_ch(channel, bw, primary_ch_idx); + warn_uncharacterized(channel, cch); /* get_pri_ch_id encoding: 20 MHz -> 0; 40 MHz upper -> 1, lower -> 2. */ uint8_t pri = 0; if (bw == 1) @@ -1456,6 +1462,7 @@ bool HalJaguar2::fast_retune(uint8_t channel, uint8_t bw, devourer::HopProf prof(_logger->events(), _cfg.debug.hop_prof, "j2", channel); const uint8_t cch = central_ch(channel, bw, primary_ch_idx); + warn_uncharacterized(channel, cch); const bool g2 = cch <= 14; const bool c8821 = (_variant == ChipVariant::C8821C); const bool r2t2r = _ver.rf_2t2r != 0; diff --git a/src/jaguar2/HalJaguar2.h b/src/jaguar2/HalJaguar2.h index 97333c70..996f834b 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -319,6 +319,9 @@ class HalJaguar2 { /* Central channel of the wide channel (shared full/fast paths): 20 MHz -> * primary; 40 MHz -> ±2 by primary_ch_idx; 80 MHz -> +6/+2/-2/-6. */ static uint8_t central_ch(uint8_t channel, uint8_t bw, uint8_t primary_ch_idx); + /* One-shot warning for a 5 GHz central channel outside the characterized + * range (shared by the full and both fast paths). */ + void warn_uncharacterized(uint8_t channel, uint8_t cch); /* RF 0xBE[17:15] per-5G-channel phase-noise / VCO-band value (8822B); 0 for * 2.4 GHz (shared full/fast paths). */ static uint8_t rf_be_for_8822b(uint8_t cch); diff --git a/src/jaguar3/RadioManagementJaguar3.cpp b/src/jaguar3/RadioManagementJaguar3.cpp index 01e33620..880b0818 100644 --- a/src/jaguar3/RadioManagementJaguar3.cpp +++ b/src/jaguar3/RadioManagementJaguar3.cpp @@ -48,20 +48,7 @@ void RadioManagementJaguar3::set_channel_bwmode(uint8_t channel, * fast-path writes (the J1 RadioManagementModule pattern). */ invalidate_fast_caches(); - /* Extended-synth channels (below-band 15..35 / the 65..99 gap between the - * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central - * channel decides, 5 GHz only): the RF tunes them, but the power tables - * and calibration bands are clamped from the nearest characterized channel. - * Warn once so an unexpected retune there is visible. */ - if (!_warned_uncharacterized && channel > 14 && - ((central >= 15 && central <= 35) || (central >= 65 && central <= 99) || - central > 177)) { - _warned_uncharacterized = true; - _logger->warn("channel {} (central {}) is outside the characterized range " - "— TX power and per-channel constants extrapolated from the " - "nearest table entry", - channel, central); - } + warn_uncharacterized(channel, central); _last_channel = channel; @@ -464,6 +451,26 @@ void RadioManagementJaguar3::DumpCanary() { _logger->info("=== END DEVOURER_DUMP_CANARY ==="); } +/* Extended-synth channels (below-band 15..35 / the 65..99 gap between the + * UNII-2 groups / above 177, freq = 5000 + 5*ch up to ch 253; the central + * channel decides, 5 GHz only): the RF tunes them, but the power tables and + * calibration bands are clamped from the nearest characterized channel. Warn + * once, from the full and both fast paths, so an unexpected retune there is + * visible. */ +void RadioManagementJaguar3::warn_uncharacterized(uint8_t channel, + uint8_t central) { + if (_warned_uncharacterized || channel <= 14) + return; + if ((central >= 15 && central <= 35) || (central >= 65 && central <= 99) || + central > 177) { + _warned_uncharacterized = true; + _logger->warn("channel {} (central {}) is outside the characterized range " + "— TX power and per-channel constants extrapolated from the " + "nearest table entry", + channel, central); + } +} + void RadioManagementJaguar3::central_and_pri(uint8_t channel, uint8_t channel_offset, ChannelWidth_t bwmode, @@ -701,6 +708,7 @@ bool RadioManagementJaguar3::fast_retune(uint8_t channel, prof.mark("confirm"); uint8_t central, pri; central_and_pri(channel, channel_offset, bwmode, central, pri); + warn_uncharacterized(channel, central); const uint8_t bw_code = (bwmode == CHANNEL_WIDTH_40) ? 1 : 0; const uint32_t msg = 0x1du | (static_cast(central) << 8) | @@ -728,6 +736,7 @@ bool RadioManagementJaguar3::fast_retune(uint8_t channel, channel); uint8_t central, pri; central_and_pri(channel, channel_offset, bwmode, central, pri); + warn_uncharacterized(channel, central); /* Compose-cache prime: every masked write costs a read+write round-trip, so * read the full dwords of everything the hop touches ONCE per epoch, then diff --git a/src/jaguar3/RadioManagementJaguar3.h b/src/jaguar3/RadioManagementJaguar3.h index adde455a..7078607a 100644 --- a/src/jaguar3/RadioManagementJaguar3.h +++ b/src/jaguar3/RadioManagementJaguar3.h @@ -168,6 +168,9 @@ class RadioManagementJaguar3 { void write_check_afe_8822e(uint16_t add, uint32_t data); void dack_soft_rst_8822e(); + /* One-shot warning for a 5 GHz central channel outside the characterized + * range (shared by the full and both fast paths). */ + void warn_uncharacterized(uint8_t channel, uint8_t central); /* Shared by the full and fast channel paths so they cannot diverge. */ static void central_and_pri(uint8_t channel, uint8_t channel_offset, ChannelWidth_t bwmode, uint8_t ¢ral, From 394e23b14c08ed50d1ab6229ca930d83a093acb9 Mon Sep 17 00:00:00 2001 From: snokvist Date: Tue, 27 Oct 2026 10:22:25 +0100 Subject: [PATCH 5/5] jaguar2: notes carry the second unit's register checks A second 8812BU-class unit (maintainer's RX-only canary dumps) matches full-path registers at four filled targets across predecessors, and full-vs-fast parity at 20/40/80 MHz except the pre-existing 0x838[7:4] DFS CCA tweak. The firmware hop and the 8821C stay untested. Co-Authored-By: Claude Opus 5.5 --- src/jaguar2/CLAUDE.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/jaguar2/CLAUDE.md b/src/jaguar2/CLAUDE.md index 39f895e7..5b78ffcf 100644 --- a/src/jaguar2/CLAUDE.md +++ b/src/jaguar2/CLAUDE.md @@ -41,10 +41,14 @@ Jaguar1 (shared `PhyTableLoader`). cycle. One 8812BU, 40 MHz, fixed gain: the floor at 5350 / 5450 MHz spreads 0.4 / 0.7 dB by predecessor, against 0.8 dB for an in-table control (5310): unresolved, not zero. The filled RF 0xBE values are - untested (a stale one showed nothing below ~1.5 dB). Full-vs-fast RX - parity holds across the gap at 20/40 MHz on the same unit; 80 MHz and - the 8821C are code-covered only. A firmware hop invalidates these - write-on-change caches, same-band hops included. + untested (a stale one showed nothing below ~1.5 dB). On a second + 8812BU-class unit the full path's canary registers at four filled + targets (20/40 MHz) match across two predecessors each. Full-vs-fast + canary parity (RX-only) holds across the gap at 20/40 MHz on both units + and at 80 MHz on the second, except the 20 MHz DFS CCA tweak in + `0x838[7:4]`, which `fast_retune` never rewrites. A firmware hop + invalidates these write-on-change caches, same-band hops included; that, + the firmware hop itself and the 8821C are untested. - **Firmware channel switch** (H2C 0x1D `SINGLE_CHANNELSWITCH_V2`, `fw_channel_switch` + the `fastretune_fw` knob; both Jaguar2 dies — the path is variant-generic, 8822B on-air-validated, 8821C code-covered