alistair23-linux/drivers/net/wireless/mediatek/mt7601u/eeprom.c
Thomas Gleixner 1802d0beec treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 174
Based on 1 normalized pattern(s):

  this program is free software you can redistribute it and or modify
  it under the terms of the gnu general public license version 2 as
  published by the free software foundation 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

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-only

has been chosen to replace the boilerplate/reference in 655 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Allison Randal <allison@lohutok.net>
Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org>
Reviewed-by: Richard Fontana <rfontana@redhat.com>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190527070034.575739538@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-30 11:26:41 -07:00

391 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 ~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;
}