[PATCH wireless-next 2/5] b43: N-PHY: fix TX IQ/LO calibration
Luca Casati
luk00casati at gmail.com
Mon Oct 26 11:15:40 PDT 2026
Several bugs kept the TX IQ/LO calibration from working, and on rev7+
it left TX dead afterwards:
- The busy poll on IQLOCAL_CMD was inverted: bits 15:14 are set while a
step runs, so it stopped waiting as soon as the step started.
- The final copy was reversed: it wrote the stale buffer over the best
coefficients (table 15 at 96) and read the active ones (at 80), so the
result was never applied.
- TxCalPhyCleanup lacked the rev7+ part of brcmsmac's
wlc_phy_txcal_phycleanup_nphy(): the 0x80 and 0x8 (PA power-up) RF
overrides set up for the cal were left in place.
- There was no counterpart to b43_nphy_tx_cal_radio_setup_rev7(), so the
TSSI/IQ-cal path stayed switched in. Add one, following
wlc_phy_txcal_radio_cleanup_nphy().
- The TRSW override for the second core used 8, which does not fit the
2-bit field on rev7+; use 2 as wl does.
With these, on a BCM43228 (N-PHY rev 16, radio 2057 rev 9) the per-step
coefficients match the proprietary wl driver's.
Found and fixed with the help of an AI coding assistant (Claude Opus 5.5),
by comparing mmiotraces of b43 and wl on the same card.
Signed-off-by: Luca Casati <luk00casati at gmail.com>
---
drivers/net/wireless/broadcom/b43/phy_n.c | 89 +++++++++++++++++++++--
1 file changed, 84 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/broadcom/b43/phy_n.c b/drivers/net/wireless/broadcom/b43/phy_n.c
index 66565c14e..cabf61870 100644
--- a/drivers/net/wireless/broadcom/b43/phy_n.c
+++ b/drivers/net/wireless/broadcom/b43/phy_n.c
@@ -4775,6 +4775,37 @@ static void b43_nphy_tx_cal_radio_setup_rev7(struct b43_wldev *dev)
}
}
+/*
+ * Undo b43_nphy_tx_cal_radio_setup_rev7(), as brcmsmac's
+ * wlc_phy_txcal_radio_cleanup_nphy() does. Without it the TSSI/IQ cal
+ * path stays switched in after a TX IQ/LO cal.
+ */
+static void b43_nphy_tx_cal_radio_cleanup(struct b43_wldev *dev)
+{
+ struct b43_phy *phy = &dev->phy;
+ u16 *save = dev->phy.n->tx_rx_cal_radio_saveregs;
+ int core, off;
+ u16 r;
+
+ if (phy->rev < 7 || phy->rev >= 19)
+ return;
+
+ for (core = 0; core < 2; core++) {
+ r = core ? 0x20 : 0;
+ off = core * 11;
+
+ b43_radio_write(dev, r + R2057_TX0_TX_SSI_MASTER, save[off + 0]);
+ b43_radio_write(dev, r + R2057_TX0_IQCAL_VCM_HG, save[off + 1]);
+ b43_radio_write(dev, r + R2057_TX0_IQCAL_IDAC, save[off + 2]);
+ b43_radio_write(dev, r + R2057_TX0_TSSI_VCM, save[off + 3]);
+ b43_radio_write(dev, r + R2057_TX0_TX_SSI_MUX, save[off + 5]);
+ if (phy->radio_rev != 5)
+ b43_radio_write(dev, r + R2057_TX0_TSSIA, save[off + 6]);
+ b43_radio_write(dev, r + R2057_TX0_TSSIG, save[off + 7]);
+ b43_radio_write(dev, r + R2057_TX0_TSSI_MISC1, save[off + 8]);
+ }
+}
+
/* https://bcm-v4.sipsolutions.net/802.11/PHY/N/TxCalRadioSetup */
static void b43_nphy_tx_cal_radio_setup(struct b43_wldev *dev)
{
@@ -5033,7 +5064,9 @@ static struct nphy_txgains b43_nphy_get_tx_gains(struct b43_wldev *dev)
/* https://bcm-v4.sipsolutions.net/802.11/PHY/N/TxCalPhyCleanup */
static void b43_nphy_tx_cal_phy_cleanup(struct b43_wldev *dev)
{
- u16 *regs = dev->phy.n->tx_rx_cal_phy_saveregs;
+ struct b43_phy *phy = &dev->phy;
+ struct b43_phy_n *nphy = dev->phy.n;
+ u16 *regs = nphy->tx_rx_cal_phy_saveregs;
if (dev->phy.rev >= 3) {
b43_phy_write(dev, B43_NPHY_AFECTL_C1, regs[0]);
@@ -5047,7 +5080,42 @@ static void b43_nphy_tx_cal_phy_cleanup(struct b43_wldev *dev)
b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, regs[8]);
b43_phy_write(dev, B43_NPHY_PAPD_EN0, regs[9]);
b43_phy_write(dev, B43_NPHY_PAPD_EN1, regs[10]);
+
+ /*
+ * Drop the overrides b43_nphy_tx_cal_phy_setup() set on rev7+
+ * (as brcmsmac's wlc_phy_txcal_phycleanup_nphy()). Leaving
+ * the 0x8 (PA power-up) override in place kills TX.
+ */
+ if (phy->rev >= 19)
+ b43_nphy_rf_ctl_override_rev19(dev, 0x80, 0, 0, true,
+ 1);
+ else if (phy->rev >= 7)
+ b43_nphy_rf_ctl_override_rev7(dev, 0x80, 0, 0, true, 1);
+
b43_nphy_reset_cca(dev);
+
+ if (nphy->use_int_tx_iq_lo_cal) {
+ if (phy->rev >= 19) {
+ b43_nphy_rf_ctl_override_rev19(dev, 0x8, 0, 0x3,
+ true, 0);
+ } else if (phy->rev >= 8) {
+ b43_nphy_rf_ctl_override_rev7(dev, 0x8, 0, 0x3,
+ true, 0);
+ } else if (phy->rev == 7) {
+ bool ghz2 = b43_current_band(dev->wl) ==
+ NL80211_BAND_2GHZ;
+
+ b43_radio_maskset(dev, ghz2 ?
+ R2057_PAD2G_TUNE_PUS_CORE0 :
+ R2057_IPA5G_CASCOFFV_PU_CORE0,
+ ~1, 1);
+ b43_radio_maskset(dev, ghz2 ?
+ R2057_PAD2G_TUNE_PUS_CORE1 :
+ R2057_IPA5G_CASCOFFV_PU_CORE1,
+ ~1, 1);
+ b43_radio_mask(dev, R2057_OVR_REG0, ~(1 << 4));
+ }
+ }
} else {
b43_phy_maskset(dev, B43_NPHY_AFECTL_C1, 0x0FFF, regs[0]);
b43_phy_maskset(dev, B43_NPHY_AFECTL_C2, 0x0FFF, regs[1]);
@@ -5102,7 +5170,12 @@ static void b43_nphy_tx_cal_phy_setup(struct b43_wldev *dev)
b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_PA,
0, 3);
b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW, 2, 1);
- b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW, 8, 2);
+ /*
+ * On rev7+ the TRSW field is 2 bits wide, so the legacy value
+ * 8 for the second core ends up as 0; wl uses 2 there.
+ */
+ b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW,
+ phy->rev >= 7 ? 2 : 8, 2);
regs[9] = b43_phy_read(dev, B43_NPHY_PAPD_EN0);
regs[10] = b43_phy_read(dev, B43_NPHY_PAPD_EN1);
@@ -5478,12 +5551,16 @@ static int b43_nphy_cal_tx_iq_lo(struct b43_wldev *dev,
}
b43_phy_write(dev, B43_NPHY_IQLOCAL_CMD, cmd);
+ /* bits 15:14 stay set while the cal step runs */
for (i = 0; i < 2000; i++) {
tmp = b43_phy_read(dev, B43_NPHY_IQLOCAL_CMD);
- if (tmp & 0xC000)
+ if (!(tmp & 0xC000))
break;
udelay(10);
}
+ if (i == 2000)
+ b43err(dev->wl, "TX IQ/LO cal step 0x%04x timed out\n",
+ cmd);
b43_ntab_read_bulk(dev, B43_NTAB16(15, 96), length,
buffer);
@@ -5500,8 +5577,9 @@ static int b43_nphy_cal_tx_iq_lo(struct b43_wldev *dev,
last = (dev->phy.rev < 3) ? 6 : 7;
if (!mphase || nphy->mphase_cal_phase_id == last) {
- b43_ntab_write_bulk(dev, B43_NTAB16(15, 96), 4, buffer);
- b43_ntab_read_bulk(dev, B43_NTAB16(15, 80), 4, buffer);
+ /* apply the result: best coeffs (96) -> active (80) */
+ b43_ntab_read_bulk(dev, B43_NTAB16(15, 96), 4, buffer);
+ b43_ntab_write_bulk(dev, B43_NTAB16(15, 80), 4, buffer);
if (dev->phy.rev < 3) {
buffer[0] = 0;
buffer[1] = 0;
@@ -5540,6 +5618,7 @@ static int b43_nphy_cal_tx_iq_lo(struct b43_wldev *dev,
b43_nphy_tx_cal_phy_cleanup(dev);
b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x110), 2, save);
+ b43_nphy_tx_cal_radio_cleanup(dev);
if (dev->phy.rev < 2 && (!mphase || nphy->mphase_cal_phase_id == last))
b43_nphy_tx_iq_workaround(dev);
--
2.43.0
More information about the b43-dev
mailing list