@@ -694,6 +694,50 @@ void Phy8733b::spur_cancellation() {
694694 set_bb (0x1908 , 0xf0u , 9 );
695695}
696696
697+ /* Channel-keyed constants shared by switch_channel (which writes them on
698+ * every full set) and fast_retune (which writes them only when their bucket
699+ * changes across the hop). One copy, so full-vs-fast register parity cannot
700+ * silently diverge. */
701+ namespace {
702+
703+ constexpr uint32_t kScoFc2a38_2g [15 ] = {
704+ 0 , 0x1cfea , 0x1d0e1 , 0x1d1d7 , 0x1d2cd , 0x1d3c3 , 0x1d4b9 ,
705+ 0x1d5b0 , 0x1d6a6 , 0x1d79c , 0x1d892 , 0x1d988 , 0x1da7f ,
706+ 0x1db75 , 0x1ddc4 };
707+ constexpr uint32_t kScoFc2a3c_2g [15 ] = {
708+ 0 , 0x27de3 , 0x27f35 , 0x28088 , 0x281da , 0x2832d , 0x2847f ,
709+ 0x285d2 , 0x28724 , 0x28877 , 0x289c9 , 0x28b1c , 0x28c6e ,
710+ 0x28dc1 , 0x290ed };
711+
712+ constexpr uint32_t agc_bucket_5g (uint8_t center) {
713+ return center <= 64 ? 6u : center <= 144 ? 7u : 0u ;
714+ }
715+
716+ constexpr uint32_t sco_bucket_0c30 (uint8_t center) {
717+ return center >= 173 ? 0x411u
718+ : center >= 120 ? 0x412u
719+ : center >= 112 ? 0x452u
720+ : center >= 56 ? 0x453u
721+ : center >= 52 ? 0x493u
722+ : center >= 16 ? 0x494u
723+ : center >= 13 ? 0x969u
724+ : center >= 11 ? 0x96au
725+ : 0x9aau ;
726+ }
727+
728+ constexpr std::array<uint32_t , 8 > tx_shape_2g (uint8_t center) {
729+ return {center == 14 ? 0x452484u : 0x7847cfu ,
730+ center == 14 ? 0x0fe3c8u : 0x57a6b1u ,
731+ center == 14 ? 0u : 0x1f2af412u ,
732+ center == 14 ? 0u : 0x09717du ,
733+ center == 14 ? 0u : 0xfb9003u ,
734+ center == 14 ? 0u : 0xfb1fa5u ,
735+ center == 14 ? 0u : 0xfe2fcau ,
736+ center == 14 ? 0u : 0xffcff3u };
737+ }
738+
739+ } // namespace
740+
697741bool Phy8733b::switch_channel (const ChannelPlan8733b &plan) {
698742 uint32_t rf18 = read_rf (0 , 0x18 );
699743 uint32_t rf19 = read_rf (0 , 0x19 );
@@ -722,44 +766,18 @@ bool Phy8733b::switch_channel(const ChannelPlan8733b &plan) {
722766 set_bb (0x18ac , 0x000001f0u , 4 );
723767 } else {
724768 set_bb (0x1ea8 , 1u << 7 , 0 );
725- const uint32_t agc = plan.center <= 64 ? 6 : plan.center <= 144 ? 7 : 0 ;
726- set_bb (0x18ac , 0x000001f0u , agc);
769+ set_bb (0x18ac , 0x000001f0u , agc_bucket_5g (plan.center ));
727770 }
728771
729- static constexpr uint32_t sco38[15 ] = {
730- 0 , 0x1cfea , 0x1d0e1 , 0x1d1d7 , 0x1d2cd , 0x1d3c3 , 0x1d4b9 ,
731- 0x1d5b0 , 0x1d6a6 , 0x1d79c , 0x1d892 , 0x1d988 , 0x1da7f ,
732- 0x1db75 , 0x1ddc4 };
733- static constexpr uint32_t sco3c[15 ] = {
734- 0 , 0x27de3 , 0x27f35 , 0x28088 , 0x281da , 0x2832d , 0x2847f ,
735- 0x285d2 , 0x28724 , 0x28877 , 0x289c9 , 0x28b1c , 0x28c6e ,
736- 0x28dc1 , 0x290ed };
737772 if (plan.center <= 14 ) {
738773 set_bb (0x2a38 , 1u << 27 , 0 );
739- set_bb (0x2a38 , 0x07ffff00u , sco38 [plan.center ]);
740- set_bb (0x2a3c , 0x000fffffu , sco3c [plan.center ]);
774+ set_bb (0x2a38 , 0x07ffff00u , kScoFc2a38_2g [plan.center ]);
775+ set_bb (0x2a3c , 0x000fffffu , kScoFc2a3c_2g [plan.center ]);
741776 }
742- const uint32_t sco = plan.center >= 173 ? 0x411
743- : plan.center >= 120 ? 0x412
744- : plan.center >= 112 ? 0x452
745- : plan.center >= 56 ? 0x453
746- : plan.center >= 52 ? 0x493
747- : plan.center >= 16 ? 0x494
748- : plan.center >= 13 ? 0x969
749- : plan.center >= 11 ? 0x96a
750- : 0x9aa ;
751- set_bb (0x0c30 , 0xfffu , sco);
777+ set_bb (0x0c30 , 0xfffu , sco_bucket_0c30 (plan.center ));
752778
753779 if (plan.is_2g ) {
754- const uint32_t shape[8 ] = {
755- plan.center == 14 ? 0x452484u : 0x7847cfu ,
756- plan.center == 14 ? 0x0fe3c8u : 0x57a6b1u ,
757- plan.center == 14 ? 0u : 0x1f2af412u ,
758- plan.center == 14 ? 0u : 0x09717du ,
759- plan.center == 14 ? 0u : 0xfb9003u ,
760- plan.center == 14 ? 0u : 0xfb1fa5u ,
761- plan.center == 14 ? 0u : 0xfe2fcau ,
762- plan.center == 14 ? 0u : 0xffcff3u };
780+ const auto shape = tx_shape_2g (plan.center );
763781 for (unsigned i = 0 ; i < 8 ; ++i)
764782 set_bb (static_cast <uint16_t >(0x1a00 + i * 4 ),
765783 i == 2 ? kDwordMask : 0x00ffffffu , shape[i]);
@@ -1315,6 +1333,11 @@ bool Phy8733b::enable_tssi_tracking(SelectedChannel channel,
13151333 _tssi_digital_snapshot = digital_snapshot;
13161334 _tssi_analog_snapshot = analog_snapshot;
13171335 _tssi_is_2g = channel_cfg->is_2g ;
1336+ /* fast_retune bookkeeping: the offsets the chip now carries and the RF
1337+ * path bit (RFE routing — static per session), for the in-place
1338+ * per-channel rewrite. */
1339+ _fr_tssi_offsets = capped->rate_offsets ;
1340+ _fr_tssi_path = path;
13181341 _logger->info (
13191342 " RTL8733B TSSI tracking enabled: ch={} path={} ceiling={} "
13201343 " rates={:08x}/{:08x}/{:08x}/{:08x}/{:08x}" ,
@@ -1365,6 +1388,9 @@ bool Phy8733b::disable_tssi_tracking() {
13651388 _tssi_digital_snapshot.reset ();
13661389 _tssi_analog_snapshot.reset ();
13671390 }
1391+ _fr_tssi_offsets.reset ();
1392+ _fr_tssi_de.reset ();
1393+ _fr_tssi_power.reset ();
13681394 _logger->info (" RTL8733B TSSI tracking disabled: rollback={}" , ok);
13691395 devourer::Ev (_logger->events (), " rtl8733b.tssi_tracking" )
13701396 .f (" enabled" , false )
@@ -1428,6 +1454,13 @@ bool Phy8733b::prepare_tssi_offsets(SelectedChannel channel,
14281454
14291455 const TssiDeState8733b state = read_tssi_de_state ();
14301456 const bool ok = state.matches_disabled (*de);
1457+ if (ok) {
1458+ /* fast_retune bookkeeping: the DE plan the chip now carries and the
1459+ * EFUSE calibration that derives it, for the in-place per-channel
1460+ * rewrite on bucket-crossing hops. */
1461+ _fr_tssi_de = *de;
1462+ _fr_tssi_power = efuse.tssi_power ;
1463+ }
14311464 _logger->info (
14321465 " RTL8733B TSSI-DE: ready={} enabled={} ch={} cck={}/{} ht40={}/{} "
14331466 " ofdm={}/{} ht20={}/{}" ,
@@ -1489,6 +1522,15 @@ bool Phy8733b::set_channel(SelectedChannel channel) {
14891522
14901523 const ChannelState8733b state = read_channel_state ();
14911524 const bool ok = state.matches (*plan);
1525+ /* fast_retune bookkeeping: record where the radio is, and invalidate the
1526+ * RF compose cache — the switch functions above rewrote both words, so a
1527+ * cached copy is stale until the next fast hop re-primes it. */
1528+ if (ok)
1529+ _fr_plan = *plan;
1530+ else
1531+ _fr_plan.reset ();
1532+ _fr_rf18.reset ();
1533+ _fr_rf19.reset ();
14921534 _logger->info (
14931535 " RTL8733B channel: ready={} primary={} center={} width={} offset={} "
14941536 " RF18={:05x}/{:05x} RF19={:05x}/{:05x} BB9B0={:08x} "
@@ -1513,6 +1555,146 @@ bool Phy8733b::set_channel(SelectedChannel channel) {
15131555 return ok;
15141556}
15151557
1558+ bool Phy8733b::fast_retune (SelectedChannel channel, bool tssi_live,
1559+ uint8_t max_target_qdbm, bool cache_rf) {
1560+ const auto plan = channel_plan (channel);
1561+ if (!_initialized || !plan || !_fr_plan)
1562+ return false ;
1563+ const ChannelPlan8733b cur = *_fr_plan;
1564+ if (plan->is_2g != cur.is_2g || plan->width != cur.width ||
1565+ plan->offset != cur.offset )
1566+ return false ;
1567+ if (plan->center == cur.center && plan->primary == cur.primary )
1568+ return true ;
1569+
1570+ /* Everything that can refuse is computed BEFORE the first chip write, so a
1571+ * declined hop leaves the radio untouched for the caller's full-path
1572+ * fallback rather than half-hopped. */
1573+ std::optional<TssiBbPlan8733b> tssi;
1574+ std::optional<TssiDePlan8733b> de;
1575+ if (tssi_live && _fr_tssi_offsets) {
1576+ tssi = tssi_bb_plan (_tx_power_targets, channel.Channel , _rfe_type,
1577+ _fr_tssi_path, max_target_qdbm);
1578+ if (!tssi)
1579+ return false ;
1580+ }
1581+ if (tssi_live && _fr_tssi_de && _fr_tssi_power) {
1582+ de = tssi_de_plan (*_fr_tssi_power, channel.Channel );
1583+ if (!de)
1584+ return false ;
1585+ }
1586+ if (!cache_rf || !_fr_rf18) {
1587+ const uint32_t rf18 = read_rf (0 , 0x18 );
1588+ if (rf18 == 0xffffffffu )
1589+ return false ;
1590+ _fr_rf18 = rf18;
1591+ }
1592+ if (!cache_rf || !_fr_rf19) {
1593+ const uint32_t rf19 = read_rf (0 , 0x19 );
1594+ if (rf19 == 0xffffffffu )
1595+ return false ;
1596+ _fr_rf19 = rf19;
1597+ }
1598+
1599+ uint32_t rf18 = *_fr_rf18;
1600+ uint32_t rf19 = *_fr_rf19;
1601+ if (plan->is_2g ) {
1602+ rf18 &= ~((3u << 16 ) | (3u << 8 ) | 0xffu );
1603+ rf18 |= plan->center ;
1604+ } else {
1605+ rf18 &= ~((1u << 17 ) | (1u << 9 ) | 0xffu );
1606+ rf18 |= (1u << 16 ) | (1u << 8 ) | plan->center ;
1607+ rf19 &= ~((1u << 19 ) | (1u << 18 ));
1608+ if (plan->center > 144 )
1609+ rf19 |= 1u << 19 ;
1610+ else if (plan->center > 80 )
1611+ rf19 |= 1u << 18 ;
1612+ }
1613+ /* From the first chip write onward a transport failure leaves the radio
1614+ * part-hopped, so the catch drops the fast-path bookkeeping: the next call
1615+ * declines (stale _fr_plan gone) and the caller's full-path fallback
1616+ * reprograms everything, instead of a later fast hop bucket-comparing
1617+ * against a baseline the chip no longer holds. */
1618+ try {
1619+ if (!program_synth (rf18)) {
1620+ _fr_plan.reset ();
1621+ return false ;
1622+ }
1623+ _fr_rf18 = rf18;
1624+ if (rf19 != *_fr_rf19) {
1625+ write_rf (0 , 0x19 , kRfMask , rf19);
1626+ write_rf (1 , 0x19 , kRfMask , rf19);
1627+ _fr_rf19 = rf19;
1628+ }
1629+
1630+ /* Channel-keyed constants from switch_channel, written only when their
1631+ * bucket changes across the hop. Band-keyed constants (0x1ea8, the
1632+ * 2.4 GHz 0x18ac pair, spur cancellation) were set by the last full set
1633+ * at this band and are untouched — that is the fast path's contract. */
1634+ if (plan->is_2g ) {
1635+ if (plan->center <= 14 ) {
1636+ set_bb (0x2a38 , 0x07ffff00u , kScoFc2a38_2g [plan->center ]);
1637+ set_bb (0x2a3c , 0x000fffffu , kScoFc2a3c_2g [plan->center ]);
1638+ }
1639+ if ((plan->center == 14 ) != (cur.center == 14 )) {
1640+ const auto shape = tx_shape_2g (plan->center );
1641+ for (unsigned i = 0 ; i < 8 ; ++i)
1642+ set_bb (static_cast <uint16_t >(0x1a00 + i * 4 ),
1643+ i == 2 ? kDwordMask : 0x00ffffffu , shape[i]);
1644+ }
1645+ } else {
1646+ if (agc_bucket_5g (plan->center ) != agc_bucket_5g (cur.center ))
1647+ set_bb (0x18ac , 0x000001f0u , agc_bucket_5g (plan->center ));
1648+ }
1649+ if (sco_bucket_0c30 (plan->center ) != sco_bucket_0c30 (cur.center ))
1650+ set_bb (0x0c30 , 0xfffu , sco_bucket_0c30 (plan->center ));
1651+ if ((plan->center == 13 ) != (cur.center == 13 ))
1652+ set_bb (0x0808 , 0x7fu , plan->center == 13 ? 0x30 : 0x40 );
1653+
1654+ bb_reset ();
1655+ igi_toggle ();
1656+
1657+ /* TSSI: tracking stays enabled; the per-channel rate-offset dwords and
1658+ * the channel-bucketed DE offsets are rewritten, in place, each only
1659+ * when the new channel's plan differs (the #389 shape). Intra-band the
1660+ * rate offsets are band-keyed and never change — the DE buckets, ~3
1661+ * channels wide at 2.4 GHz, are the calibration a hop actually moves.
1662+ * The rollback snapshots are untouched — they record the pre-enable
1663+ * state, which these rewrites do not change. */
1664+ if (tssi && tssi->rate_offsets != *_fr_tssi_offsets) {
1665+ for (size_t i = 0 ; i < tssi->rate_offsets .size (); ++i)
1666+ set_bb (static_cast <uint16_t >(0x3a00 + i * 4 ), kDwordMask ,
1667+ tssi->rate_offsets [i]);
1668+ _fr_tssi_offsets = tssi->rate_offsets ;
1669+ }
1670+ if (de && !(*de == *_fr_tssi_de)) {
1671+ /* The prepare_tssi_offsets field sequence, minus its 0x4318
1672+ * tracking-disable write — the loop stays live across the rewrite. */
1673+ set_bb (0x433c , 0x0ff00000u , static_cast <uint8_t >(de->cck [0 ]));
1674+ set_bb (0x434c , 0x0ff00000u , static_cast <uint8_t >(de->cck [1 ]));
1675+ set_bb (0x4334 , 0x0ff00000u , static_cast <uint8_t >(de->ht40 [0 ]));
1676+ set_bb (0x4344 , 0x0ff00000u , static_cast <uint8_t >(de->ht40 [1 ]));
1677+ set_bb (0x43b0 , 0x000000ffu , static_cast <uint8_t >(de->ofdm [0 ]));
1678+ set_bb (0x43b0 , 0x0000ff00u , static_cast <uint8_t >(de->ht40 [0 ]));
1679+ set_bb (0x43b0 , 0x00ff0000u , static_cast <uint8_t >(de->ht40 [0 ]));
1680+ set_bb (0x43b4 , 0x0000ff00u , static_cast <uint8_t >(de->ofdm [1 ]));
1681+ set_bb (0x43b4 , 0x000000ffu , static_cast <uint8_t >(de->ht40 [1 ]));
1682+ set_bb (0x43b4 , 0x00ff0000u , static_cast <uint8_t >(de->ht40 [1 ]));
1683+ set_bb (0x43b4 , 0xff000000u , static_cast <uint8_t >(de->ht40 [1 ]));
1684+ set_bb (0x43b0 , 0xff000000u , static_cast <uint8_t >(de->ht20 [0 ]));
1685+ set_bb (0x43b8 , 0x000000ffu , static_cast <uint8_t >(de->ht20 [1 ]));
1686+ _fr_tssi_de = *de;
1687+ }
1688+ _fr_plan = *plan;
1689+ return true ;
1690+ } catch (...) {
1691+ _fr_plan.reset ();
1692+ _fr_rf18.reset ();
1693+ _fr_rf19.reset ();
1694+ throw ;
1695+ }
1696+ }
1697+
15161698bool Phy8733b::initialize (uint8_t cut, const EfuseInfo &efuse) {
15171699 if (!efuse.valid || efuse.rfe_type == 0xff || efuse.xtal == 0xff ) {
15181700 _logger->error (" RTL8733B PHY: refusing initialization without EFUSE data" );
0 commit comments