// SPDX-License-Identifier: GPL-2.0-or-later
/*
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>
*/
#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)