/*
* Copyright (c) 2010 Broadcom Corporation
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <brcm_hw_ids.h>
#include <chipcommon.h>
#include <aiutils.h>
#include <d11.h>
#include <phy_shim.h>
#include "phy_hal.h"
#include "phy_int.h"
#include "phy_radio.h"
#include "phy_lcn.h"
#include "phyreg_n.h"
#define VALID_N_RADIO(radioid) ((radioid == BCM2055_ID) || \
(radioid == BCM2056_ID) || \
(radioid == BCM2057_ID))
#define VALID_LCN_RADIO(radioid) (radioid == BCM2064_ID)
#define VALID_RADIO(pi, radioid) ( \
(ISNPHY(pi) ? VALID_N_RADIO(radioid) : false) || \
(ISLCNPHY(pi) ? VALID_LCN_RADIO(radioid) : false))
/* basic mux operation - can be optimized on several architectures */
#define MUX(pred, true, false) ((pred) ? (true) : (false))
/* modulo inc/dec - assumes x E [0, bound - 1] */
#define MODINC(x, bound) MUX((x) == (bound) - 1, 0, (x) + 1)
/* modulo inc/dec, bound = 2^k */
#define MODDEC_POW2(x, bound) (((x) - 1) & ((bound) - 1))
#define MODINC_POW2(x, bound) (((x) + 1) & ((bound) - 1))
struct chan_info_basic {
u16 chan;
u16 freq;
};
static const struct chan_info_basic chan_info_all[] = {
{1, 2412},
{2, 2417},
{3, 2422},
{4, 2427},
{5, 2432},
{6, 2437},
{7, 2442},
{8, 2447},
{9, 2452},
{10, 2457},
{11, 2462},
{12, 2467},
{13, 2472},
{14, 2484},
{34, 5170},
{38, 5190},
{42, 5210},
{46, 5230},
{36, 5180},
{40, 5200},
{44, 5220},
{48, 5240},
{52, 5260},
{56, 5280},
{60, 5300},
{64, 5320},
{100, 5500},
{104, 5520},
{108, 5540},
{112, 5560},
{116, 5580},
{120, 5600},
{124, 5620},
{128, 5640},
{132, 5660},
{136, 5680},
{140, 5700},
{149, 5745},
{153, 5765},
{157, 5785},
{161, 5805},
{165, 5825},
{184, 4920},
{188, 4940},
{192, 4960},
{196, 4980},
{200, 5000},
{204, 5020},
{208, 5040},
{212, 5060},
{216, 5080}
};
static const u8 ofdm_rate_lookup[] = {
BRCM_RATE_48M,
BRCM_RATE_24M,
BRCM_RATE_12M,
BRCM_RATE_6M,
BRCM_RATE_54M,
BRCM_RATE_36M,
BRCM_RATE_18M,
BRCM_RATE_9M
};
#define PHY_WREG_LIMIT 24
void wlc_phyreg_enter(struct brcms_phy_pub *pih)
{
struct brcms_phy *pi = container_of(pih, struct brcms_phy, pubpi_ro);
wlapi_bmac_ucode_wake_override_phyreg_set(pi->sh->physhim);
}
void wlc_phyreg_exit(struct brcms_phy_pub *pih)
{
struct brcms_phy *pi = container_of(pih, struct brcms_phy, pubpi_ro);
wlapi_bmac_ucode_wake_override_phyreg_clear(pi->sh->physhim);
}
void wlc_radioreg_enter(struct brcms_phy_pub *pih)
{
struct brcms_phy *pi = container_of(pih, struct brcms_phy, pubpi_ro);
wlapi_bmac_mctrl(pi->sh->physhim, MCTL_LOCK_RADIO, MCTL_LOCK_RADIO);
udelay(10);
}
void wlc_radioreg_exit(struct brcms_phy_pub *pih)
{
struct brcms_phy *pi = container_of(pih, struct brcms_phy, pubpi_ro);
(void)bcma_read16(pi->d11core, D11REGOFFS(phyversion));
pi->phy_wreg = 0;
wlapi_bmac_mctrl(pi->sh->physhim, MCTL_LOCK_RADIO, 0);
}
u16 read_radio_reg(struct brcms_phy *pi, u16 addr)
{
u16 data;
if ((addr == RADIO_IDCODE))
return 0xffff;
switch (pi->pubpi.phy_type) {
case PHY_TYPE_N:
if (!CONF_HAS(PHYTYPE, PHY_TYPE_N))
break;
if (NREV_GE(pi->pubpi.phy_rev, 7))
addr |= RADIO_2057_READ_OFF;
else
addr |= RADIO_2055_READ_OFF;
break;
case PHY_TYPE_LCN:
if (!CONF_HAS(PHYTYPE, PHY_TYPE_LCN))
break;
addr |= RADIO_2064_READ_OFF;
break;
default:
break;
}
if ((D11REV_GE(pi->sh->corerev, 24)) ||
(D11REV_IS(pi->sh->corerev, 22)
&& (pi->pubpi.phy_type != PHY_TYPE_SSN))) {
bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), addr);
data = bcma_read16(pi->d11core, D11REGOFFS(radioregdata));
} else {
bcma_wflush16(pi->d11core, D11REGOFFS(phy4waddr), addr);
data = bcma_read16(pi->d11core, D11REGOFFS(phy4wdatalo));
}
pi->phy_wreg = 0;
return data;
}
void write_radio_reg(struct brcms_phy *pi, u16 addr, u16 val)
{
if ((D11REV_GE(pi->sh->corerev, 24)) ||
(D11REV_IS(pi->sh->corerev, 22)
&& (pi->pubpi.phy_type != PHY_TYPE_SSN))) {
bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), addr);
bcma_write16(pi->d11core, D11REGOFFS(radioregdata), val);
} else {
bcma_wflush16(pi->d11core, D11REGOFFS(phy4waddr), addr);
bcma_write16(pi->d11core, D11REGOFFS(phy4wdatalo), val);
}
if ((pi->d11core->bus->hosttype == BCMA_HOSTTYPE_PCI) &&
(++pi->phy_wreg >= pi->phy_wreg_limit)) {
(void)bcma_read32(pi->d11core, D11REGOFFS(maccontrol));
pi->phy_wreg = 0;
}
}
static u32 read_radio_id(struct brcms_phy *pi)
{
u32 id;
if (D11REV_GE(pi->sh->corerev, 24)) {
u32 b0, b1, b2;
bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), 0);
b0 = (u32) bcma_read16(pi->d11core, D11REGOFFS(radioregdata));
bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), 1);
b1 = (u32) bcma_read16(pi->d11core, D11REGOFFS(radioregdata));
bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), 2);
b2 = (u32) bcma_read16(pi->d11core, D11REGOFFS(radioregdata));
id = ((b0 & 0xf) << 28) | (((b2 << 8) | b1) << 12) | ((b0 >> 4)
& 0xf);
} else {
bcma_wflush16(pi->d11core, D11REGOFFS(phy4waddr), RADIO_IDCODE);
id = (u32) bcma_read16(pi->d11core, D11REGOFFS(phy4wdatalo));
id |= (u32) bcma_read16(pi->d11core,
D11REGOFFS(phy4wdatahi)) << 16;
}
pi->phy_wreg = 0;
return id;
}
void and_radio_reg(struct brcms_phy *pi, u16 addr, u16 val)
{
u16 rval;
rval = read_radio_reg(pi, addr);
write_radio_reg(pi, addr, (rval & val));
}
void or_radio_reg(struct brcms_phy *pi, u16 addr, u16 val)
{
u16 rval;
rval = read_radio_reg(pi, addr);
write_radio_reg(pi, addr, (rval | val));
}
void xor_radio_reg(struct brcms_phy *pi, u16 addr, u16 mask)
{
u16 rval;
rval = read_radio_reg(pi, addr);
write_radio_reg(pi, addr, (rval ^ mask));
}
void mod_radio_reg(struct brcms_phy *pi, u16 addr, u16 mask, u16 val)
{
u16 rval;
rval = read_radio_reg(pi, addr);
write_radio_reg(pi, addr, (rval & ~mask) | (val & mask));
}
void write_phy_channel_reg(struct brcms_phy *pi, uint val)
{
bcma_write16(pi->d11core, D11REGOFFS(phychannel), val);
}
u16 read_phy_reg(struct brcms_phy *pi, u16 addr)
{
bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr);
pi->phy_wreg = 0;
return bcma_read16(pi->d11core, D11REGOFFS(phyregdata));
}
void write_phy_reg(struct brcms_phy *pi, u16 addr, u16 val)
{
#ifdef CONFIG_BCM47XX
bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr);
bcma_write16(pi->d11core, D11REGOFFS(phyregdata), val);
if (addr == 0x72)
(void)bcma_read16(pi->d11core, D11REGOFFS(phyregdata));
#else
bcma_write32(pi->d11core, D11REGOFFS(phyregaddr), addr | (val << 16));
if ((pi->d11core->bus->hosttype == BCMA_HOSTTYPE_PCI) &&
(++pi->phy_wreg >= pi->phy_wreg_limit)) {
pi->phy_wreg = 0;
(void)bcma_read16(pi->d11core, D11REGOFFS(phyversion));
}
#endif
}
void and_phy_reg(struct brcms_phy *pi, u16 addr, u16 val)
{
bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr);
bcma_mask16(pi->d11core, D11REGOFFS(phyregdata), val);
pi->phy_wreg = 0;
}
void or_phy_reg(struct brcms_phy *pi, u16 addr, u16 val)
{
bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr);
bcma_set16(pi->d11core, D11REGOFFS(phyregdata), val);
pi->phy_wreg = 0;
}
void mod_phy_reg(struct brcms_phy *pi, u16 addr, u16 mask, u16 val)
{
val &= mask;
bcma_wflush16(pi->d11core, D11RE