Currently the expression ~nic_conf1 is always true because nic_conf1
is a u16 and according to 6.5.3.3 of the C standard the ~ operator
promotes the u16 to an integer before flipping all the bits. Thus
the top 16 bits of the integer result are all set so the expression
is always true. If the intention was to flip all the bits of nic_conf1
then casting the integer result back to a u16 is a suitabel fix.
Interestingly static analyzers seem to thing a bitwise ! should be
used instead of ~ for this scenario, so I think the original intent
of the expression may need some extra consideration.
Addresses-Coverity: ("Logical vs. bitwise operator")
Fixes: c869f77d6a
("add mt7601u driver")
Signed-off-by: Colin Ian King <colin.king@canonical.com>
Acked-by: Jakub Kicinski <kubakici@wp.pl>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
Link: https://lore.kernel.org/r/20210225183241.1002129-1-colin.king@canonical.com
390 lines
9.4 KiB
C
390 lines
9.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org>
|
|
* Copyright (C) 2015 Jakub Kicinski <kubakici@wp.pl>
|
|
*/
|
|
|
|
#include <linux/of.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/partitions.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <asm/unaligned.h>
|
|
#include "mt7601u.h"
|
|
#include "eeprom.h"
|
|
#include "mac.h"
|
|
|
|
static bool
|
|
field_valid(u8 val)
|
|
{
|
|
return val != 0xff;
|
|
}
|
|
|
|
static s8
|
|
field_validate(u8 val)
|
|
{
|
|
if (!field_valid(val))
|
|
return 0;
|
|
|
|
return val;
|
|
}
|
|
|
|
static int
|
|
mt7601u_efuse_read(struct mt7601u_dev *dev, u16 addr, u8 *data,
|
|
enum mt7601u_eeprom_access_modes mode)
|
|
{
|
|
u32 val;
|
|
int i;
|
|
|
|
val = mt76_rr(dev, MT_EFUSE_CTRL);
|
|
val &= ~(MT_EFUSE_CTRL_AIN |
|
|
MT_EFUSE_CTRL_MODE);
|
|
val |= FIELD_PREP(MT_EFUSE_CTRL_AIN, addr & ~0xf) |
|
|
FIELD_PREP(MT_EFUSE_CTRL_MODE, mode) |
|
|
MT_EFUSE_CTRL_KICK;
|
|
mt76_wr(dev, MT_EFUSE_CTRL, val);
|
|
|
|
if (!mt76_poll(dev, MT_EFUSE_CTRL, MT_EFUSE_CTRL_KICK, 0, 1000))
|
|
return -ETIMEDOUT;
|
|
|
|
val = mt76_rr(dev, MT_EFUSE_CTRL);
|
|
if ((val & MT_EFUSE_CTRL_AOUT) == MT_EFUSE_CTRL_AOUT) {
|
|
/* Parts of eeprom not in the usage map (0x80-0xc0,0xf0)
|
|
* will not return valid data but it's ok.
|
|
*/
|
|
memset(data, 0xff, 16);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
val = mt76_rr(dev, MT_EFUSE_DATA(i));
|
|
put_unaligned_le32(val, data + 4 * i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mt7601u_efuse_physical_size_check(struct mt7601u_dev *dev)
|
|
{
|
|
const int map_reads = DIV_ROUND_UP(MT_EFUSE_USAGE_MAP_SIZE, 16);
|
|
u8 data[round_up(MT_EFUSE_USAGE_MAP_SIZE, 16)];
|
|
int ret, i;
|
|
u32 start = 0, end = 0, cnt_free;
|
|
|
|
for (i = 0; i < map_reads; i++) {
|
|
ret = mt7601u_efuse_read(dev, MT_EE_USAGE_MAP_START + i * 16,
|
|
data + i * 16, MT_EE_PHYSICAL_READ);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < MT_EFUSE_USAGE_MAP_SIZE; i++)
|
|
if (!data[i]) {
|
|
if (!start)
|
|
start = MT_EE_USAGE_MAP_START + i;
|
|
end = MT_EE_USAGE_MAP_START + i;
|
|
}
|
|
cnt_free = end - start + 1;
|
|
|
|
if (MT_EFUSE_USAGE_MAP_SIZE - cnt_free < 5) {
|
|
dev_err(dev->dev, "Error: your device needs default EEPROM file and this driver doesn't support it!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool
|
|
mt7601u_has_tssi(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
u16 nic_conf1 = get_unaligned_le16(eeprom + MT_EE_NIC_CONF_1);
|
|
|
|
return (u16)~nic_conf1 && (nic_conf1 & MT_EE_NIC_CONF_1_TX_ALC_EN);
|
|
}
|
|
|
|
static void
|
|
mt7601u_set_chip_cap(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
u16 nic_conf0 = get_unaligned_le16(eeprom + MT_EE_NIC_CONF_0);
|
|
u16 nic_conf1 = get_unaligned_le16(eeprom + MT_EE_NIC_CONF_1);
|
|
|
|
if (!field_valid(nic_conf1 & 0xff))
|
|
nic_conf1 &= 0xff00;
|
|
|
|
dev->ee->tssi_enabled = mt7601u_has_tssi(dev, eeprom) &&
|
|
!(nic_conf1 & MT_EE_NIC_CONF_1_TEMP_TX_ALC);
|
|
|
|
if (nic_conf1 & MT_EE_NIC_CONF_1_HW_RF_CTRL)
|
|
dev_err(dev->dev,
|
|
"Error: this driver does not support HW RF ctrl\n");
|
|
|
|
if (!field_valid(nic_conf0 >> 8))
|
|
return;
|
|
|
|
if (FIELD_GET(MT_EE_NIC_CONF_0_RX_PATH, nic_conf0) > 1 ||
|
|
FIELD_GET(MT_EE_NIC_CONF_0_TX_PATH, nic_conf0) > 1)
|
|
dev_err(dev->dev,
|
|
"Error: device has more than 1 RX/TX stream!\n");
|
|
}
|
|
|
|
static void mt7601u_set_channel_target_power(struct mt7601u_dev *dev,
|
|
u8 *eeprom, u8 max_pwr)
|
|
{
|
|
u8 trgt_pwr = eeprom[MT_EE_TX_TSSI_TARGET_POWER];
|
|
|
|
if (trgt_pwr > max_pwr || !trgt_pwr) {
|
|
dev_warn(dev->dev, "Error: EEPROM trgt power invalid %hhx!\n",
|
|
trgt_pwr);
|
|
trgt_pwr = 0x20;
|
|
}
|
|
|
|
memset(dev->ee->chan_pwr, trgt_pwr, sizeof(dev->ee->chan_pwr));
|
|
}
|
|
|
|
static void
|
|
mt7601u_set_channel_power(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
u32 i, val;
|
|
u8 max_pwr;
|
|
|
|
val = mt7601u_rr(dev, MT_TX_ALC_CFG_0);
|
|
max_pwr = FIELD_GET(MT_TX_ALC_CFG_0_LIMIT_0, val);
|
|
|
|
if (mt7601u_has_tssi(dev, eeprom)) {
|
|
mt7601u_set_channel_target_power(dev, eeprom, max_pwr);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 14; i++) {
|
|
s8 power = field_validate(eeprom[MT_EE_TX_POWER_OFFSET + i]);
|
|
|
|
if (power > max_pwr || power < 0)
|
|
power = MT7601U_DEFAULT_TX_POWER;
|
|
|
|
dev->ee->chan_pwr[i] = power;
|
|
}
|
|
}
|
|
|
|
static void
|
|
mt7601u_set_country_reg(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
/* Note: - region 31 is not valid for mt7601u (see rtmp_init.c)
|
|
* - comments in rtmp_def.h are incorrect (see rt_channel.c)
|
|
*/
|
|
static const struct reg_channel_bounds chan_bounds[] = {
|
|
/* EEPROM country regions 0 - 7 */
|
|
{ 1, 11 }, { 1, 13 }, { 10, 2 }, { 10, 4 },
|
|
{ 14, 1 }, { 1, 14 }, { 3, 7 }, { 5, 9 },
|
|
/* EEPROM country regions 32 - 33 */
|
|
{ 1, 11 }, { 1, 14 }
|
|
};
|
|
u8 val = eeprom[MT_EE_COUNTRY_REGION];
|
|
int idx = -1;
|
|
|
|
if (val < 8)
|
|
idx = val;
|
|
if (val > 31 && val < 33)
|
|
idx = val - 32 + 8;
|
|
|
|
if (idx != -1)
|
|
dev_info(dev->dev,
|
|
"EEPROM country region %02hhx (channels %hhd-%hhd)\n",
|
|
val, chan_bounds[idx].start,
|
|
chan_bounds[idx].start + chan_bounds[idx].num - 1);
|
|
else
|
|
idx = 5; /* channels 1 - 14 */
|
|
|
|
dev->ee->reg = chan_bounds[idx];
|
|
|
|
/* TODO: country region 33 is special - phy should be set to B-mode
|
|
* before entering channel 14 (see sta/connect.c)
|
|
*/
|
|
}
|
|
|
|
static void
|
|
mt7601u_set_rf_freq_off(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
u8 comp;
|
|
|
|
dev->ee->rf_freq_off = field_validate(eeprom[MT_EE_FREQ_OFFSET]);
|
|
comp = field_validate(eeprom[MT_EE_FREQ_OFFSET_COMPENSATION]);
|
|
|
|
if (comp & BIT(7))
|
|
dev->ee->rf_freq_off -= comp & 0x7f;
|
|
else
|
|
dev->ee->rf_freq_off += comp;
|
|
}
|
|
|
|
static void
|
|
mt7601u_set_rssi_offset(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
int i;
|
|
s8 *rssi_offset = dev->ee->rssi_offset;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
rssi_offset[i] = eeprom[MT_EE_RSSI_OFFSET + i];
|
|
|
|
if (rssi_offset[i] < -10 || rssi_offset[i] > 10) {
|
|
dev_warn(dev->dev,
|
|
"Warning: EEPROM RSSI is invalid %02hhx\n",
|
|
rssi_offset[i]);
|
|
rssi_offset[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
mt7601u_extra_power_over_mac(struct mt7601u_dev *dev)
|
|
{
|
|
u32 val;
|
|
|
|
val = ((mt7601u_rr(dev, MT_TX_PWR_CFG_1) & 0x0000ff00) >> 8);
|
|
val |= ((mt7601u_rr(dev, MT_TX_PWR_CFG_2) & 0x0000ff00) << 8);
|
|
mt7601u_wr(dev, MT_TX_PWR_CFG_7, val);
|
|
|
|
val = ((mt7601u_rr(dev, MT_TX_PWR_CFG_4) & 0x0000ff00) >> 8);
|
|
mt7601u_wr(dev, MT_TX_PWR_CFG_9, val);
|
|
}
|
|
|
|
static void
|
|
mt7601u_set_power_rate(struct power_per_rate *rate, s8 delta, u8 value)
|
|
{
|
|
/* Invalid? Note: vendor driver does not handle this */
|
|
if (value == 0xff)
|
|
return;
|
|
|
|
rate->raw = s6_validate(value);
|
|
rate->bw20 = s6_to_int(value);
|
|
/* Note: vendor driver does cap the value to s6 right away */
|
|
rate->bw40 = rate->bw20 + delta;
|
|
}
|
|
|
|
static void
|
|
mt7601u_save_power_rate(struct mt7601u_dev *dev, s8 delta, u32 val, int i)
|
|
{
|
|
struct mt7601u_rate_power *t = &dev->ee->power_rate_table;
|
|
|
|
switch (i) {
|
|
case 0:
|
|
mt7601u_set_power_rate(&t->cck[0], delta, (val >> 0) & 0xff);
|
|
mt7601u_set_power_rate(&t->cck[1], delta, (val >> 8) & 0xff);
|
|
/* Save cck bw20 for fixups of channel 14 */
|
|
dev->ee->real_cck_bw20[0] = t->cck[0].bw20;
|
|
dev->ee->real_cck_bw20[1] = t->cck[1].bw20;
|
|
|
|
mt7601u_set_power_rate(&t->ofdm[0], delta, (val >> 16) & 0xff);
|
|
mt7601u_set_power_rate(&t->ofdm[1], delta, (val >> 24) & 0xff);
|
|
break;
|
|
case 1:
|
|
mt7601u_set_power_rate(&t->ofdm[2], delta, (val >> 0) & 0xff);
|
|
mt7601u_set_power_rate(&t->ofdm[3], delta, (val >> 8) & 0xff);
|
|
mt7601u_set_power_rate(&t->ht[0], delta, (val >> 16) & 0xff);
|
|
mt7601u_set_power_rate(&t->ht[1], delta, (val >> 24) & 0xff);
|
|
break;
|
|
case 2:
|
|
mt7601u_set_power_rate(&t->ht[2], delta, (val >> 0) & 0xff);
|
|
mt7601u_set_power_rate(&t->ht[3], delta, (val >> 8) & 0xff);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static s8
|
|
get_delta(u8 val)
|
|
{
|
|
s8 ret;
|
|
|
|
if (!field_valid(val) || !(val & BIT(7)))
|
|
return 0;
|
|
|
|
ret = val & 0x1f;
|
|
if (ret > 8)
|
|
ret = 8;
|
|
if (val & BIT(6))
|
|
ret = -ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
mt7601u_config_tx_power_per_rate(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
u32 val;
|
|
s8 bw40_delta;
|
|
int i;
|
|
|
|
bw40_delta = get_delta(eeprom[MT_EE_TX_POWER_DELTA_BW40]);
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
val = get_unaligned_le32(eeprom + MT_EE_TX_POWER_BYRATE(i));
|
|
|
|
mt7601u_save_power_rate(dev, bw40_delta, val, i);
|
|
|
|
if (~val)
|
|
mt7601u_wr(dev, MT_TX_PWR_CFG_0 + i * 4, val);
|
|
}
|
|
|
|
mt7601u_extra_power_over_mac(dev);
|
|
}
|
|
|
|
static void
|
|
mt7601u_init_tssi_params(struct mt7601u_dev *dev, u8 *eeprom)
|
|
{
|
|
struct tssi_data *d = &dev->ee->tssi_data;
|
|
|
|
if (!dev->ee->tssi_enabled)
|
|
return;
|
|
|
|
d->slope = eeprom[MT_EE_TX_TSSI_SLOPE];
|
|
d->tx0_delta_offset = eeprom[MT_EE_TX_TSSI_OFFSET] * 1024;
|
|
d->offset[0] = eeprom[MT_EE_TX_TSSI_OFFSET_GROUP];
|
|
d->offset[1] = eeprom[MT_EE_TX_TSSI_OFFSET_GROUP + 1];
|
|
d->offset[2] = eeprom[MT_EE_TX_TSSI_OFFSET_GROUP + 2];
|
|
}
|
|
|
|
int
|
|
mt7601u_eeprom_init(struct mt7601u_dev *dev)
|
|
{
|
|
u8 *eeprom;
|
|
int i, ret;
|
|
|
|
ret = mt7601u_efuse_physical_size_check(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dev->ee = devm_kzalloc(dev->dev, sizeof(*dev->ee), GFP_KERNEL);
|
|
if (!dev->ee)
|
|
return -ENOMEM;
|
|
|
|
eeprom = kmalloc(MT7601U_EEPROM_SIZE, GFP_KERNEL);
|
|
if (!eeprom)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i + 16 <= MT7601U_EEPROM_SIZE; i += 16) {
|
|
ret = mt7601u_efuse_read(dev, i, eeprom + i, MT_EE_READ);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
if (eeprom[MT_EE_VERSION_EE] > MT7601U_EE_MAX_VER)
|
|
dev_warn(dev->dev,
|
|
"Warning: unsupported EEPROM version %02hhx\n",
|
|
eeprom[MT_EE_VERSION_EE]);
|
|
dev_info(dev->dev, "EEPROM ver:%02hhx fae:%02hhx\n",
|
|
eeprom[MT_EE_VERSION_EE], eeprom[MT_EE_VERSION_FAE]);
|
|
|
|
mt7601u_set_macaddr(dev, eeprom + MT_EE_MAC_ADDR);
|
|
mt7601u_set_chip_cap(dev, eeprom);
|
|
mt7601u_set_channel_power(dev, eeprom);
|
|
mt7601u_set_country_reg(dev, eeprom);
|
|
mt7601u_set_rf_freq_off(dev, eeprom);
|
|
mt7601u_set_rssi_offset(dev, eeprom);
|
|
dev->ee->ref_temp = eeprom[MT_EE_REF_TEMP];
|
|
dev->ee->lna_gain = eeprom[MT_EE_LNA_GAIN];
|
|
|
|
mt7601u_config_tx_power_per_rate(dev, eeprom);
|
|
|
|
mt7601u_init_tssi_params(dev, eeprom);
|
|
out:
|
|
kfree(eeprom);
|
|
return ret;
|
|
}
|