diff --git a/src/jaguar2/CLAUDE.md b/src/jaguar2/CLAUDE.md index 7c3b24ac..5b78ffcf 100644 --- a/src/jaguar2/CLAUDE.md +++ b/src/jaguar2/CLAUDE.md @@ -34,6 +34,21 @@ 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 (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). 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 diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index c81b6c4b..9a287a11 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -1,5 +1,6 @@ #include "HalJaguar2.h" +#include #include #include #include @@ -866,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, @@ -879,18 +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 / 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. */ - if (!_warned_uncharacterized && - ((channel >= 15 && channel <= 35) || channel > 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); - } + 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); @@ -899,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; @@ -922,24 +930,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 +956,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 +1218,52 @@ 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) { @@ -1420,21 +1432,22 @@ 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) 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); @@ -1449,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; @@ -1473,19 +1487,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 +1502,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 +1513,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 +1821,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..996f834b 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -319,9 +319,18 @@ 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, 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..880b0818 100644 --- a/src/jaguar3/RadioManagementJaguar3.cpp +++ b/src/jaguar3/RadioManagementJaguar3.cpp @@ -48,19 +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 / 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. */ - if (!_warned_uncharacterized && - ((channel >= 15 && channel <= 35) || channel > 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); - } + warn_uncharacterized(channel, central); _last_channel = channel; @@ -463,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, @@ -700,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) | @@ -727,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,