/* Broadcom B43 wireless driver G PHY LO (LocalOscillator) Measuring and Control routines Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>, Copyright (c) 2005, 2006 Stefano Brivio <stefano.brivio@polimi.it> Copyright (c) 2005-2007 Michael Buesch <m@bues.ch> Copyright (c) 2005, 2006 Danny van Dyk <kugelfang@gentoo.org> Copyright (c) 2005, 2006 Andreas Jaggi <andreas.jaggi@waterwave.ch> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "b43.h" #include "lo.h" #include "phy_g.h" #include "main.h" #include <linux/delay.h> #include <linux/sched.h> #include <linux/slab.h> static struct b43_lo_calib *b43_find_lo_calib(struct b43_txpower_lo_control *lo, const struct b43_bbatt *bbatt, const struct b43_rfatt *rfatt) { struct b43_lo_calib *c; list_for_each_entry(c, &lo->calib_list, list) { if (!b43_compare_bbatt(&c->bbatt, bbatt)) continue; if (!b43_compare_rfatt(&c->rfatt, rfatt)) continue; return c; } return NULL; } /* Write the LocalOscillator Control (adjust) value-pair. */ static void b43_lo_write(struct b43_wldev *dev, struct b43_loctl *control) { struct b43_phy *phy = &dev->phy; u16 value; if (B43_DEBUG) { if (unlikely(abs(control->i) > 16 || abs(control->q) > 16)) { b43dbg(dev->wl, "Invalid LO control pair " "(I: %d, Q: %d)\n", control->i, control->q); dump_stack(); return; } } B43_WARN_ON(phy->type != B43_PHYTYPE_G); value = (u8) (control->q); value |= ((u8) (control->i)) << 8; b43_phy_write(dev, B43_PHY_LO_CTL, value); } static u16 lo_measure_feedthrough(struct b43_wldev *dev, u16 lna, u16 pga, u16 trsw_rx) { struct b43_phy *phy = &dev->phy; u16 rfover; u16 feedthrough; if (phy->gmode) { lna <<= B43_PHY_RFOVERVAL_LNA_SHIFT; pga <<= B43_PHY_RFOVERVAL_PGA_SHIFT; B43_WARN_ON(lna & ~B43_PHY_RFOVERVAL_LNA); B43_WARN_ON(pga & ~B43_PHY_RFOVERVAL_PGA); /*FIXME This assertion fails B43_WARN_ON(trsw_rx & ~(B43_PHY_RFOVERVAL_TRSWRX | B43_PHY_RFOVERVAL_BW)); */ trsw_rx &= (B43_PHY_RFOVERVAL_TRSWRX | B43_PHY_RFOVERVAL_BW); /* Construct the RF Override Value */ rfover = B43_PHY_RFOVERVAL_UNK; rfover |= pga; rfover |= lna; rfover |= trsw_rx; if ((dev->dev->bus_sprom->boardflags_lo & B43_BFL_EXTLNA) && phy->rev > 6) rfover |= B43_PHY_RFOVERVAL_EXTLNA; b43_phy_write(dev, B43_PHY_PGACTL, 0xE300); b43_phy_write(dev, B43_PHY_RFOVERVAL, rfover); udelay(10); rfover |= B43_PHY_RFOVERVAL_BW_LBW; b43_phy_write(dev, B43_PHY_RFOVERVAL, rfover); udelay(10); rfover |= B43_PHY_RFOVERVAL_BW_LPF; b43_phy_write(dev, B43_PHY_RFOVERVAL, rfover); udelay(10); b43_phy_write(dev, B43_PHY_PGACTL, 0xF300); } else { pga |= B43_PHY_PGACTL_UNKNOWN; b43_phy_write(dev, B43_PHY_PGACTL, pga); udelay(10); pga |= B43_PHY_PGACTL_LOWBANDW; b43_phy_write(dev, B43_PHY_PGACTL, pga); udelay(10); pga |= B43_PHY_PGACTL_LPF; b43_phy_write(dev, B43_PHY_PGACTL, pga); } udelay(21); feedthrough = b43_phy_read(dev, B43_PHY_LO_LEAKAGE); /* This is a good place to check if we need to relax a bit, * as this is the main function called regularly * in the LO calibration. */ cond_resched(); return feedthrough; } /* TXCTL Register and Value Table. * Returns the "TXCTL Register". * "value" is the "TXCTL Value". * "pad_mix_gain" is the PAD Mixer Gain. */ static u16 lo_txctl_register_table(struct b43_wldev *dev, u16 *value, u16 *pad_mix_gain) { struct b43_phy *phy = &dev->phy; u16 reg, v, padmix; if (phy->type == B43_PHYTYPE_B) { v = 0x30; if (phy->radio_rev <= 5) { reg = 0x43; padmix = 0; } else { reg = 0x52; padmix = 5; } } else { if (phy->rev >= 2 && phy->radio_rev == 8) { reg = 0x43; v = 0x10; padmix = 2; } else { reg = 0x52; v = 0x30; padmix = 5; } } if (value) *value = v; if (pad_mix_gain) *pad_mix_gain = padmix; return reg; } static void lo_measure_txctl_values(struct b43_wldev *dev) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_txpower_lo_control *lo = gphy->lo_control; u16 reg, mask; u16 trsw_rx, pga; u16 radio_pctl_reg; static const u8 tx_bias_values[] = { 0x09, 0x08, 0x0A, 0x01, 0x00, 0x02, 0x05, 0x04, 0x06, }; static const u8 tx_magn_values[] = { 0x70, 0x40, }; if (!has_loopback_gain(phy)) { radio_pctl_reg = 6; trsw_rx = 2; pga = 0; } else { int lb_gain; /* Loopback gain (in dB) */ trsw_rx = 0; lb_gain = gphy->max_lb_gain / 2; if (lb_gain > 10) { radio_pctl_reg = 0; pga = abs(10 - lb_gain) / 6; pga = clamp_val(pga, 0, 15); } else { int cmp_val; int tmp; pga = 0; cmp_val = 0x24; if ((phy->rev >= 2) && (phy->radio_ver == 0x2050) && (phy->radio_rev == 8)) cmp_val = 0x3C; tmp = lb_gain; if ((10 - lb_gain) < cmp_val) tmp = (10 - lb_gain); if (tmp < 0) tmp += 6; else tmp += 3; cmp_val /= 4; tmp /= 4; if (tmp >= cmp_val) radio_pctl_reg = cmp_val; else radio_pctl_reg = tmp; } } b43_radio_maskset(dev, 0x43, 0xFFF0, radio_pctl_reg); b43_gphy_set_baseband_attenuation(dev, 2); reg = lo_txctl_register_table(dev, &mask, NULL); mask = ~mask; b43_radio_mask(dev, reg, mask); if (has_tx_magnification(phy)) { int i, j; int feedthrough; int min_feedth = 0xFFFF; u8 tx_magn, tx_bias; for (i = 0; i < ARRAY_SIZE(tx_magn_values); i++) { tx_magn = tx_magn_values[i]; b43_radio_maskset(dev, 0x52, 0xFF0F, tx_magn); for (j = 0; j < ARRAY_SIZE(tx_bias_values); j++) { tx_bias = tx_bias_values[j]; b43_radio_maskset(dev, 0x52, 0xFFF0, tx_bias); feedthrough = lo_measure_feedthrough(dev, 0, pga, trsw_rx); if (feedthrough < min_feedth) { lo->tx_bias = tx_bias; lo->tx_magn = tx_magn; min_feedth = feedthrough; } if (lo->tx_bias == 0) break; } b43_radio_write16(dev, 0x52, (b43_radio_read16(dev, 0x52) & 0xFF00) | lo->tx_bias | lo-> tx_magn); } } else { lo->tx_magn = 0; lo->tx_bias = 0; b43_radio_mask(dev, 0x52, 0xFFF0); /* TX bias == 0 */ } lo->txctl_measured_time = jiffies; } static void lo_read_power_vector(struct b43_wldev *dev) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_txpower_lo_control *lo = gphy->lo_control; int i; u64 tmp; u64 power_vector = 0; for (i = 0; i < 8; i += 2) { tmp = b43_shm_read16(dev, B43_SHM_SHARED, 0x310 + i); power_vector |= (tmp << (i * 8)); /* Clear the vector on the device. */ b43_shm_write16(dev, B43_SHM_SHARED, 0x310 + i, 0); } if (power_vector) lo->power_vector = power_vector; lo->pwr_vec_read_time = jiffies; } /* 802.11/LO/GPHY/MeasuringGains */ static void lo_measure_gain_values(struct b43_wldev *dev, s16 max_rx_gain, int use_trsw_rx) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; u16 tmp; if (max_rx_gain < 0) max_rx_gain = 0; if (has_loopback_gain(phy)) { int trsw_rx_gain; if (use_trsw_rx) { trsw_rx_gain = gphy->trsw_rx_gain / 2; if (max_rx_gain >= trsw_rx_gain) { trsw_rx_gain = max_rx_gain - trsw_rx_gain; } } else trsw_rx_gain = max_rx_gain; if (trsw_rx_gain < 9) { gphy->lna_lod_gain = 0; } else { gphy->lna_lod_gain = 1; trsw_rx_gain -= 8; } trsw_rx_gain = clamp_val(trsw_rx_gain, 0, 0x2D); gphy->pga_gain = trsw_rx_gain / 3; if (gphy->pga_gain >= 5) { gphy->pga_gain -= 5; gphy->lna_gain = 2; } else gphy->lna_gain = 0; } else { gphy->lna_gain = 0; gphy->trsw_rx_gain = 0x20; if (max_rx_gain >= 0x14) { gphy->lna_lod_gain = 1; gphy->pga_gain = 2; } else if (max_rx_gain >= 0x12) { gphy->lna_lod_gain = 1; gphy->pga_gain = 1; } else if (max_rx_gain >= 0xF) { gphy->lna_lod_gain = 1; gphy->pga_gain = 0; } else { gphy->lna_lod_gain = 0; gphy->pga_gain = 0; } } tmp = b43_radio_read16(dev, 0x7A); if (gphy->lna_lod_gain == 0) tmp &= ~0x0008; else tmp |= 0x0008; b43_radio_write16(dev, 0x7A, tmp); } struct lo_g_saved_values { u8 old_channel; /* Core registers */ u16 reg_3F4; u16 reg_3E2; /* PHY registers */ u16 phy_lo_mask; u16 phy_extg_01; u16 phy_dacctl_hwpctl; u16 phy_dacctl; u16 phy_cck_14; u16 phy_hpwr_tssictl; u16 phy_analogover; u16 phy_analogoverval; u16 phy_rfover; u16 phy_rfoverval; u16 phy_classctl; u16 phy_cck_3E; u16 phy_crs0; u16 phy_pgactl; u16 phy_cck_2A; u16 phy_syncctl; u16 phy_cck_30; u16 phy_cck_06; /* Radio registers */ u16 radio_43; u16 radio_7A; u16 radio_52; }; static void lo_measure_setup(struct b43_wldev *dev, struct lo_g_saved_values *sav) { struct ssb_sprom *sprom = dev->dev->bus_sprom; struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_txpower_lo_control *lo = gphy->lo_control; u16 tmp; if (b43_has_hardware_pctl(dev)) { sav->phy_lo_mask = b43_phy_read(dev, B43_PHY_LO_MASK); sav->phy_extg_01 = b43_phy_read(dev, B43_PHY_EXTG(0x01)); sav->phy_dacctl_hwpctl = b43_phy_read(dev, B43_PHY_DACCTL); sav->phy_cck_14 = b43_phy_read(dev, B43_PHY_CCK(0x14)); sav->phy_hpwr_tssictl = b43_phy_read(dev, B43_PHY_HPWR_TSSICTL); b43_phy_set(dev, B43_PHY_HPWR_TSSICTL, 0x100); b43_phy_set(dev, B43_PHY_EXTG(0x01), 0x40); b43_phy_set(dev, B43_PHY_DACCTL, 0x40); b43_phy_set(dev, B43_PHY_CCK(0x14), 0x200); } if (phy->type == B43_PHYTYPE_B && phy->radio_ver == 0x2050 && phy->radio_rev < 6) { b43_phy_write(dev, B43_PHY_CCK(0x16), 0x410); b43_phy_write(dev, B43_PHY_CCK(0x17), 0x820); } if (phy->rev >= 2) { sav->phy_analogover = b43_phy_read(dev, B43_PHY_ANALOGOVER); sav->phy_analogoverval = b43_phy_read(dev, B43_PHY_ANALOGOVERVAL); sav->phy_rfover = b43_phy_read(dev, B43_PHY_RFOVER); sav->phy_rfoverval = b43_phy_read(dev, B43_PHY_RFOVERVAL); sav->phy_classctl = b43_phy_read(dev, B43_PHY_CLASSCTL); sav->phy_cck_3E = b43_phy_read(dev, B43_PHY_CCK(0x3E)); sav->phy_crs0 = b43_phy_read(dev, B43_PHY_CRS0); b43_phy_mask(dev, B43_PHY_CLASSCTL, 0xFFFC); b43_phy_mask(dev, B43_PHY_CRS0, 0x7FFF); b43_phy_set(dev, B43_PHY_ANALOGOVER, 0x0003); b43_phy_mask(dev, B43_PHY_ANALOGOVERVAL, 0xFFFC); if (phy->type == B43_PHYTYPE_G) { if ((phy->rev >= 7) && (sprom->boardflags_lo & B43_BFL_EXTLNA)) { b43_phy_write(dev, B43_PHY_RFOVER, 0x933); } else { b43_phy_write(dev, B43_PHY_RFOVER, 0x133); } } else { b43_phy_write(dev, B43_PHY_RFOVER, 0); } b43_phy_write(dev, B43_PHY_CCK(0x3E), 0); } sav->reg_3F4 = b43_read16(dev, 0x3F4); sav->reg_3E2 = b43_read16(dev, 0x3E2); sav->radio_43 = b43_radio_read16(dev, 0x43); sav->radio_7A = b43_radio_read16(dev, 0x7A); sav->phy_pgactl = b43_phy_read(dev, B43_PHY_PGACTL); sav->phy_cck_2A = b43_phy_read(dev, B43_PHY_CCK(0x2A)); sav->phy_syncctl = b43_phy_read(dev, B43_PHY_SYNCCTL); sav->phy_dacctl = b43_phy_read(dev, B43_PHY_DACCTL); if (!has_tx_magnification(phy)) { sav->radio_52 = b43_radio_read16(dev, 0x52); sav->radio_52 &= 0x00F0; } if (phy->type == B43_PHYTYPE_B) { sav->phy_cck_30 = b43_phy_read(dev, B43_PHY_CCK(0x30)); sav->phy_cck_06 = b43_phy_read(dev, B43_PHY_CCK(0x06)); b43_phy_write(dev, B43_PHY_CCK(0x30), 0x00FF); b43_phy_write(dev, B43_PHY_CCK(0x06), 0x3F3F); } else { b43_write16(dev, 0x3E2, b43_read16(dev, 0x3E2) | 0x8000); } b43_write16(dev, 0x3F4, b43_read16(dev, 0x3F4) & 0xF000); tmp = (phy->type == B43_PHYTYPE_G) ? B43_PHY_LO_MASK : B43_PHY_CCK(0x2E); b43_phy_write(dev, tmp, 0x007F); tmp = sav->phy_syncctl; b43_phy_write(dev, B43_PHY_SYNCCTL, tmp & 0xFF7F); tmp = sav->radio_7A; b43_radio_write16(dev, 0x007A, tmp & 0xFFF0); b43_phy_write(dev, B43_PHY_CCK(0x2A), 0x8A3); if (phy->type == B43_PHYTYPE_G || (phy->type == B43_PHYTYPE_B && phy->radio_ver == 0x2050 && phy->radio_rev >= 6)) { b43_phy_write(dev, B43_PHY_CCK(0x2B), 0x1003); } else b43_phy_write(dev, B43_PHY_CCK(0x2B), 0x0802); if (phy->rev >= 2) b43_dummy_transmission(dev, false, true); b43_gphy_channel_switch(dev, 6, 0); b43_radio_read16(dev, 0x51); /* dummy read */ if (phy->type == B43_PHYTYPE_G) b43_phy_write(dev, B43_PHY_CCK(0x2F), 0); /* Re-measure the txctl values, if needed. */ if (time_before(lo->txctl_measured_time, jiffies - B43_LO_TXCTL_EXPIRE)) lo_measure_txctl_values(dev); if (phy->type == B43_PHYTYPE_G && phy->rev >= 3) { b43_phy_write(dev, B43_PHY_LO_MASK, 0xC078); } else { if (phy->type == B43_PHYTYPE_B) b43_phy_write(dev, B43_PHY_CCK(0x2E), 0x8078); else b43_phy_write(dev, B43_PHY_LO_MASK, 0x8078); } } static void lo_measure_restore(struct b43_wldev *dev, struct lo_g_saved_values *sav) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; u16 tmp; if (phy->rev >= 2) { b43_phy_write(dev, B43_PHY_PGACTL, 0xE300); tmp = (gphy->pga_gain << 8); b43_phy_write(dev, B43_PHY_RFOVERVAL, tmp | 0xA0); udelay(5); b43_phy_write(dev, B43_PHY_RFOVERVAL, tmp | 0xA2); udelay(2); b43_phy_write(dev, B43_PHY_RFOVERVAL, tmp | 0xA3); } else { tmp = (gphy->pga_gain | 0xEFA0); b43_phy_write(dev, B43_PHY_PGACTL, tmp); } if (phy->type == B43_PHYTYPE_G) { if (phy->rev >= 3) b43_phy_write(dev, B43_PHY_CCK(0x2E), 0xC078); else b43_phy_write(dev, B43_PHY_CCK(0x2E), 0x8078); if (phy->rev >= 2) b43_phy_write(dev, B43_PHY_CCK(0x2F), 0x0202); else b43_phy_write(dev, B43_PHY_CCK(0x2F), 0x0101); } b43_write16(dev, 0x3F4, sav->reg_3F4); b43_phy_write(dev, B43_PHY_PGACTL, sav->phy_pgactl); b43_phy_write(dev, B43_PHY_CCK(0x2A), sav->phy_cck_2A); b43_phy_write(dev, B43_PHY_SYNCCTL, sav->phy_syncctl); b43_phy_write(dev, B43_PHY_DACCTL, sav->phy_dacctl); b43_radio_write16(dev, 0x43, sav->radio_43); b43_radio_write16(dev, 0x7A, sav->radio_7A); if (!has_tx_magnification(phy)) { tmp = sav->radio_52; b43_radio_maskset(dev, 0x52, 0xFF0F, tmp); } b43_write16(dev, 0x3E2, sav->reg_3E2); if (phy->type == B43_PHYTYPE_B && phy->radio_ver == 0x2050 && phy->radio_rev <= 5) { b43_phy_write(dev, B43_PHY_CCK(0x30), sav->phy_cck_30); b43_phy_write(dev, B43_PHY_CCK(0x06), sav->phy_cck_06); } if (phy->rev >= 2) { b43_phy_write(dev, B43_PHY_ANALOGOVER, sav->phy_analogover); b43_phy_write(dev, B43_PHY_ANALOGOVERVAL, sav->phy_analogoverval); b43_phy_write(dev, B43_PHY_CLASSCTL, sav->phy_classctl); b43_phy_write(dev, B43_PHY_RFOVER, sav->phy_rfover); b43_phy_write(dev, B43_PHY_RFOVERVAL, sav->phy_rfoverval); b43_phy_write(dev, B43_PHY_CCK(0x3E), sav->phy_cck_3E); b43_phy_write(dev, B43_PHY_CRS0, sav->phy_crs0); } if (b43_has_hardware_pctl(dev)) { tmp = (sav->phy_lo_mask & 0xBFFF); b43_phy_write(dev, B43_PHY_LO_MASK, tmp); b43_phy_write(dev, B43_PHY_EXTG(0x01), sav->phy_extg_01); b43_phy_write(dev, B43_PHY_DACCTL, sav->phy_dacctl_hwpctl); b43_phy_write(dev, B43_PHY_CCK(0x14), sav->phy_cck_14); b43_phy_write(dev, B43_PHY_HPWR_TSSICTL, sav->phy_hpwr_tssictl); } b43_gphy_channel_switch(dev, sav->old_channel, 1); } struct b43_lo_g_statemachine { int current_state; int nr_measured; int state_val_multiplier; u16 lowest_feedth; struct b43_loctl min_loctl; }; /* Loop over each possible value in this state. */ static int lo_probe_possible_loctls(struct b43_wldev *dev, struct b43_loctl *probe_loctl, struct b43_lo_g_statemachine *d) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_loctl test_loctl; struct b43_loctl orig_loctl; struct b43_loctl prev_loctl = { .i = -100, .q = -100, }; int i; int begin, end; int found_lower = 0; u16 feedth; static const struct b43_loctl modifiers[] = { {.i = 1,.q = 1,}, {.i = 1,.q = 0,}, {.i = 1,.q = -1,}, {.i = 0,.q = -1,}, {.i = -1,.q = -1,}, {.i = -1,.q = 0,}, {.i = -1,.q = 1,}, {.i = 0,.q = 1,}, }; if (d->current_state == 0) { begin = 1; end = 8; } else if (d->current_state % 2 == 0) { begin = d->current_state - 1; end = d->current_state + 1; } else { begin = d->current_state - 2; end = d->current_state + 2; } if (begin < 1) begin += 8; if (end > 8) end -= 8; memcpy(&orig_loctl, probe_loctl, sizeof(struct b43_loctl)); i = begin; d->current_state = i; while (1) { B43_WARN_ON(!(i >= 1 && i <= 8)); memcpy(&test_loctl, &orig_loctl, sizeof(struct b43_loctl)); test_loctl.i += modifiers[i - 1].i * d->state_val_multiplier; test_loctl.q += modifiers[i - 1].q * d->state_val_multiplier; if ((test_loctl.i != prev_loctl.i || test_loctl.q != prev_loctl.q) && (abs(test_loctl.i) <= 16 && abs(test_loctl.q) <= 16)) { b43_lo_write(dev, &test_loctl); feedth = lo_measure_feedthrough(dev, gphy->lna_gain, gphy->pga_gain, gphy->trsw_rx_gain); if (feedth < d->lowest_feedth) { memcpy(probe_loctl, &test_loctl, sizeof(struct b43_loctl)); found_lower = 1; d->lowest_feedth = feedth; if ((d->nr_measured < 2) && !has_loopback_gain(phy)) break; } } memcpy(&prev_loctl, &test_loctl, sizeof(prev_loctl)); if (i == end) break; if (i == 8) i = 1; else i++; d->current_state = i; } return found_lower; } static void lo_probe_loctls_statemachine(struct b43_wldev *dev, struct b43_loctl *loctl, int *max_rx_gain) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_lo_g_statemachine d; u16 feedth; int found_lower; struct b43_loctl probe_loctl; int max_repeat = 1, repeat_cnt = 0; d.nr_measured = 0; d.state_val_multiplier = 1; if (has_loopback_gain(phy)) d.state_val_multiplier = 3; memcpy(&d.min_loctl, loctl, sizeof(struct b43_loctl)); if (has_loopback_gain(phy)) max_repeat = 4; do { b43_lo_write(dev, &d.min_loctl); feedth = lo_measure_feedthrough(dev, gphy->lna_gain, gphy->pga_gain, gphy->trsw_rx_gain); if (feedth < 0x258) { if (feedth >= 0x12C) *max_rx_gain += 6; else *max_rx_gain += 3; feedth = lo_measure_feedthrough(dev, gphy->lna_gain, gphy->pga_gain, gphy->trsw_rx_gain); } d.lowest_feedth = feedth; d.current_state = 0; do { B43_WARN_ON(! (d.current_state >= 0 && d.current_state <= 8)); memcpy(&probe_loctl, &d.min_loctl, sizeof(struct b43_loctl)); found_lower = lo_probe_possible_loctls(dev, &probe_loctl, &d); if (!found_lower) break; if ((probe_loctl.i == d.min_loctl.i) && (probe_loctl.q == d.min_loctl.q)) break; memcpy(&d.min_loctl, &probe_loctl, sizeof(struct b43_loctl)); d.nr_measured++; } while (d.nr_measured < 24); memcpy(loctl, &d.min_loctl, sizeof(struct b43_loctl)); if (has_loopback_gain(phy)) { if (d.lowest_feedth > 0x1194) *max_rx_gain -= 6; else if (d.lowest_feedth < 0x5DC) *max_rx_gain += 3; if (repeat_cnt == 0) { if (d.lowest_feedth <= 0x5DC) { d.state_val_multiplier = 1; repeat_cnt++; } else d.state_val_multiplier = 2; } else if (repeat_cnt == 2) d.state_val_multiplier = 1; } lo_measure_gain_values(dev, *max_rx_gain, has_loopback_gain(phy)); } while (++repeat_cnt < max_repeat); } static struct b43_lo_calib *b43_calibrate_lo_setting(struct b43_wldev *dev, const struct b43_bbatt *bbatt, const struct b43_rfatt *rfatt) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_loctl loctl = { .i = 0, .q = 0, }; int max_rx_gain; struct b43_lo_calib *cal; struct lo_g_saved_values uninitialized_var(saved_regs); /* Values from the "TXCTL Register and Value Table" */ u16 txctl_reg; u16 txctl_value; u16 pad_mix_gain; saved_regs.old_channel = phy->channel; b43_mac_suspend(dev); lo_measure_setup(dev, &saved_regs); txctl_reg = lo_txctl_register_table(dev, &txctl_value, &pad_mix_gain); b43_radio_maskset(dev, 0x43, 0xFFF0, rfatt->att); b43_radio_maskset(dev, txctl_reg, ~txctl_value, (rfatt->with_padmix ? txctl_value :0)); max_rx_gain = rfatt->att * 2; max_rx_gain += bbatt->att / 2; if (rfatt->with_padmix) max_rx_gain -= pad_mix_gain; if (has_loopback_gain(phy)) max_rx_gain += gphy->max_lb_gain; lo_measure_gain_values(dev, max_rx_gain, has_loopback_gain(phy)); b43_gphy_set_baseband_attenuation(dev, bbatt->att); lo_probe_loctls_statemachine(dev, &loctl, &max_rx_gain); lo_measure_restore(dev, &saved_regs); b43_mac_enable(dev); if (b43_debug(dev, B43_DBG_LO)) { b43dbg(dev->wl, "LO: Calibrated for BB(%u), RF(%u,%u) " "=> I=%d Q=%d\n", bbatt->att, rfatt->att, rfatt->with_padmix, loctl.i, loctl.q); } cal = kmalloc(sizeof(*cal), GFP_KERNEL); if (!cal) { b43warn(dev->wl, "LO calib: out of memory\n"); return NULL; } memcpy(&cal->bbatt, bbatt, sizeof(*bbatt)); memcpy(&cal->rfatt, rfatt, sizeof(*rfatt)); memcpy(&cal->ctl, &loctl, sizeof(loctl)); cal->calib_time = jiffies; INIT_LIST_HEAD(&cal->list); return cal; } /* Get a calibrated LO setting for the given attenuation values. * Might return a NULL pointer under OOM! */ static struct b43_lo_calib *b43_get_calib_lo_settings(struct b43_wldev *dev, const struct b43_bbatt *bbatt, const struct b43_rfatt *rfatt) { struct b43_txpower_lo_control *lo = dev->phy.g->lo_control; struct b43_lo_calib *c; c = b43_find_lo_calib(lo, bbatt, rfatt); if (c) return c; /* Not in the list of calibrated LO settings. * Calibrate it now. */ c = b43_calibrate_lo_setting(dev, bbatt, rfatt); if (!c) return NULL; list_add(&c->list, &lo->calib_list); return c; } void b43_gphy_dc_lt_init(struct b43_wldev *dev, bool update_all) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_txpower_lo_control *lo = gphy->lo_control; int i; int rf_offset, bb_offset; const struct b43_rfatt *rfatt; const struct b43_bbatt *bbatt; u64 power_vector; bool table_changed = false; BUILD_BUG_ON(B43_DC_LT_SIZE != 32); B43_WARN_ON(lo->rfatt_list.len * lo->bbatt_list.len > 64); power_vector = lo->power_vector; if (!update_all && !power_vector) return; /* Nothing to do. */ /* Suspend the MAC now to avoid continuous suspend/enable * cycles in the loop. */ b43_mac_suspend(dev); for (i = 0; i < B43_DC_LT_SIZE * 2; i++) { struct b43_lo_calib *cal; int idx; u16 val; if (!update_all && !(power_vector & (((u64)1ULL) << i))) continue; /* Update the table entry for this power_vector bit. * The table rows are RFatt entries and columns are BBatt. */ bb_offset = i / lo->rfatt_list.len; rf_offset = i % lo->rfatt_list.len; bbatt = &(lo->bbatt_list.list[bb_offset]); rfatt = &(lo->rfatt_list.list[rf_offset]); cal = b43_calibrate_lo_setting(dev, bbatt, rfatt); if (!cal) { b43warn(dev->wl, "LO: Could not " "calibrate DC table entry\n"); continue; } /*FIXME: Is Q really in the low nibble? */ val = (u8)(cal->ctl.q); val |= ((u8)(cal->ctl.i)) << 4; kfree(cal); /* Get the index into the hardware DC LT. */ idx = i / 2; /* Change the table in memory. */ if (i % 2) { /* Change the high byte. */ lo->dc_lt[idx] = (lo->dc_lt[idx] & 0x00FF) | ((val & 0x00FF) << 8); } else { /* Change the low byte. */ lo->dc_lt[idx] = (lo->dc_lt[idx] & 0xFF00) | (val & 0x00FF); } table_changed = true; } if (table_changed) { /* The table changed in memory. Update the hardware table. */ for (i = 0; i < B43_DC_LT_SIZE; i++) b43_phy_write(dev, 0x3A0 + i, lo->dc_lt[i]); } b43_mac_enable(dev); } /* Fixup the RF attenuation value for the case where we are * using the PAD mixer. */ static inline void b43_lo_fixup_rfatt(struct b43_rfatt *rf) { if (!rf->with_padmix) return; if ((rf->att != 1) && (rf->att != 2) && (rf->att != 3)) rf->att = 4; } void b43_lo_g_adjust(struct b43_wldev *dev) { struct b43_phy_g *gphy = dev->phy.g; struct b43_lo_calib *cal; struct b43_rfatt rf; memcpy(&rf, &gphy->rfatt, sizeof(rf)); b43_lo_fixup_rfatt(&rf); cal = b43_get_calib_lo_settings(dev, &gphy->bbatt, &rf); if (!cal) return; b43_lo_write(dev, &cal->ctl); } void b43_lo_g_adjust_to(struct b43_wldev *dev, u16 rfatt, u16 bbatt, u16 tx_control) { struct b43_rfatt rf; struct b43_bbatt bb; struct b43_lo_calib *cal; memset(&rf, 0, sizeof(rf)); memset(&bb, 0, sizeof(bb)); rf.att = rfatt; bb.att = bbatt; b43_lo_fixup_rfatt(&rf); cal = b43_get_calib_lo_settings(dev, &bb, &rf); if (!cal) return; b43_lo_write(dev, &cal->ctl); } /* Periodic LO maintanance work */ void b43_lo_g_maintanance_work(struct b43_wldev *dev) { struct b43_phy *phy = &dev->phy; struct b43_phy_g *gphy = phy->g; struct b43_txpower_lo_control *lo = gphy->lo_control; unsigned long now; unsigned long expire; struct b43_lo_calib *cal, *tmp; bool current_item_expired = false; bool hwpctl; if (!lo) return; now = jiffies; hwpctl = b43_has_hardware_pctl(dev); if (hwpctl) { /* Read the power vector and update it, if needed. */ expire = now - B43_LO_PWRVEC_EXPIRE; if (time_before(lo->pwr_vec_read_time, expire)) { lo_read_power_vector(dev); b43_gphy_dc_lt_init(dev, 0); } //FIXME Recalc the whole DC table from time to time? } if (hwpctl) return; /* Search for expired LO settings. Remove them. * Recalibrate the current setting, if expired. */ expire = now - B43_LO_CALIB_EXPIRE; list_for_each_entry_safe(cal, tmp, &lo->calib_list, list) { if (!time_before(cal->calib_time, expire)) continue; /* This item expired. */ if (b43_compare_bbatt(&cal->bbatt, &gphy->bbatt) && b43_compare_rfatt(&cal->rfatt, &gphy->rfatt)) { B43_WARN_ON(current_item_expired); current_item_expired = true; } if (b43_debug(dev, B43_DBG_LO)) { b43dbg(dev->wl, "LO: Item BB(%u), RF(%u,%u), " "I=%d, Q=%d expired\n", cal->bbatt.att, cal->rfatt.att, cal->rfatt.with_padmix, cal->ctl.i, cal->ctl.q); } list_del(&cal->list); kfree(cal); } if (current_item_expired || unlikely(list_empty(&lo->calib_list))) { /* Recalibrate currently used LO setting. */ if (b43_debug(dev, B43_DBG_LO)) b43dbg(dev->wl, "LO: Recalibrating current LO setting\n"); cal = b43_calibrate_lo_setting(dev, &gphy->bbatt, &gphy->rfatt); if (cal) { list_add(&cal->list, &lo->calib_list); b43_lo_write(dev, &cal->ctl); } else b43warn(dev->wl, "Failed to recalibrate current LO setting\n"); } } void b43_lo_g_cleanup(struct b43_wldev *dev) { struct b43_txpower_lo_control *lo = dev->phy.g->lo_control; struct b43_lo_calib *cal, *tmp; if (!lo) return; list_for_each_entry_safe(cal, tmp, &lo->calib_list, list) { list_del(&cal->list); kfree(cal); } } /* LO Initialization */ void b43_lo_g_init(struct b43_wldev *dev) { if (b43_has_hardware_pctl(dev)) { lo_read_power_vector(dev); b43_gphy_dc_lt_init(dev, 1); } }