blob: 0182187741a68710e48b357063bb8ac48aa9a343 [file] [log] [blame]
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "atenl.h"
#define EEPROM_PART_SIZE 0xFF000
char *eeprom_file;
static int
atenl_create_file(struct atenl *an, bool flash_mode)
{
char fname[64], buf[1024];
ssize_t w, len, max_len, total_len = 0;
int fd_ori, fd, ret;
/* reserve space for pre-cal data in flash mode */
if (flash_mode) {
atenl_dbg("%s: init eeprom with flash mode\n", __func__);
max_len = EEPROM_PART_SIZE;
} else {
atenl_dbg("%s: init eeprom with efuse mode\n", __func__);
max_len = 0x1e00;
}
snprintf(fname, sizeof(fname),
"/sys/kernel/debug/ieee80211/phy%d/mt76/eeprom",
get_band_val(an, 0, phy_idx));
fd_ori = open(fname, O_RDONLY);
if (fd_ori < 0)
return -1;
fd = open(eeprom_file, O_RDWR | O_CREAT | O_EXCL, 00644);
if (fd < 0)
goto out;
while ((len = read(fd_ori, buf, sizeof(buf))) > 0) {
retry:
w = write(fd, buf, len);
if (w > 0) {
total_len += len;
continue;
}
if (errno == EINTR)
goto retry;
perror("write");
unlink(eeprom_file);
close(fd);
fd = -1;
goto out;
}
/* reserve space for pre-cal data in flash mode */
len = sizeof(buf);
memset(buf, 0, len);
while (total_len < max_len) {
w = write(fd, buf, len);
if (w > 0) {
total_len += len;
continue;
}
if (errno != EINTR) {
perror("write");
unlink(eeprom_file);
close(fd);
fd = -1;
goto out;
}
}
ret = lseek(fd, 0, SEEK_SET);
if (ret) {
close(fd_ori);
close(fd);
return ret;
}
out:
close(fd_ori);
return fd;
}
static bool
atenl_eeprom_file_exists(void)
{
struct stat st;
return stat(eeprom_file, &st) == 0;
}
static int
atenl_eeprom_init_file(struct atenl *an, bool flash_mode)
{
int fd;
if (!atenl_eeprom_file_exists())
return atenl_create_file(an, flash_mode);
fd = open(eeprom_file, O_RDWR);
if (fd < 0)
perror("open");
return fd;
}
static void
atenl_eeprom_init_chip_id(struct atenl *an)
{
an->chip_id = *(u16 *)an->eeprom_data;
if (is_mt7915(an)) {
an->adie_id = 0x7975;
} else if (is_mt7916(an)) {
an->adie_id = 0x7976;
} else if (is_mt7986(an)) {
bool is_7975 = false;
u32 val;
u8 sub_id;
atenl_reg_read(an, 0x18050000, &val);
switch (val & 0xf) {
case MT7975_ONE_ADIE_SINGLE_BAND:
is_7975 = true;
/* fallthrough */
case MT7976_ONE_ADIE_SINGLE_BAND:
sub_id = 0xa;
break;
case MT7976_ONE_ADIE_DBDC:
sub_id = 0x7;
break;
case MT7975_DUAL_ADIE_DBDC:
is_7975 = true;
/* fallthrough */
case MT7976_DUAL_ADIE_DBDC:
default:
sub_id = 0xf;
break;
}
an->sub_chip_id = sub_id;
an->adie_id = is_7975 ? 0x7975 : 0x7976;
} else if (is_mt7996(an)) {
/* TODO: parse info if required */
}
}
static void
atenl_eeprom_init_max_size(struct atenl *an)
{
switch (an->chip_id) {
case 0x7915:
an->eeprom_size = 3584;
an->eeprom_prek_offs = 0x62;
break;
case 0x7906:
case 0x7916:
case 0x7986:
an->eeprom_size = 4096;
an->eeprom_prek_offs = 0x19a;
break;
case 0x7990:
an->eeprom_size = 7680;
an->eeprom_prek_offs = 0x1a5;
default:
break;
}
}
static void
atenl_eeprom_init_band_cap(struct atenl *an)
{
#define EAGLE_BAND_SEL(index) MT_EE_WIFI_EAGLE_CONF##index##_BAND_SEL
u8 *eeprom = an->eeprom_data;
if (is_mt7915(an)) {
u8 val = eeprom[MT_EE_WIFI_CONF];
u8 band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
struct atenl_band *anb = &an->anb[0];
/* MT7915A */
if (band_sel == MT_EE_BAND_SEL_DEFAULT) {
anb->valid = true;
anb->cap = BAND_TYPE_2G_5G;
return;
}
/* MT7915D */
if (band_sel == MT_EE_BAND_SEL_2GHZ) {
anb->valid = true;
anb->cap = BAND_TYPE_2G;
}
val = eeprom[MT_EE_WIFI_CONF + 1];
band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
anb++;
if (band_sel == MT_EE_BAND_SEL_5GHZ) {
anb->valid = true;
anb->cap = BAND_TYPE_5G;
}
} else if (is_mt7916(an) || is_mt7986(an)) {
struct atenl_band *anb;
u8 val, band_sel;
int i;
for (i = 0; i < 2; i++) {
val = eeprom[MT_EE_WIFI_CONF + i];
band_sel = FIELD_GET(MT_EE_WIFI_CONF0_BAND_SEL, val);
anb = &an->anb[i];
anb->valid = true;
switch (band_sel) {
case MT_EE_BAND_SEL_2G:
anb->cap = BAND_TYPE_2G;
break;
case MT_EE_BAND_SEL_5G:
anb->cap = BAND_TYPE_5G;
break;
case MT_EE_BAND_SEL_6G:
anb->cap = BAND_TYPE_6G;
break;
case MT_EE_BAND_SEL_5G_6G:
anb->cap = BAND_TYPE_5G_6G;
break;
default:
break;
}
}
} else if (is_mt7996(an)) {
struct atenl_band *anb;
u8 val, band_sel;
u8 band_sel_mask[3] = {EAGLE_BAND_SEL(0), EAGLE_BAND_SEL(1),
EAGLE_BAND_SEL(2)};
int i;
for (i = 0; i < 3; i++) {
val = eeprom[MT_EE_WIFI_CONF + i];
band_sel = FIELD_GET(band_sel_mask[i], val);
anb = &an->anb[i];
anb->valid = true;
switch (band_sel) {
case MT_EE_EAGLE_BAND_SEL_2GHZ:
anb->cap = BAND_TYPE_2G;
break;
case MT_EE_EAGLE_BAND_SEL_5GHZ:
anb->cap = BAND_TYPE_5G;
break;
case MT_EE_EAGLE_BAND_SEL_6GHZ:
anb->cap = BAND_TYPE_6G;
break;
case MT_EE_EAGLE_BAND_SEL_5GHZ_6GHZ:
anb->cap = BAND_TYPE_5G_6G;
break;
default:
break;
}
}
}
}
static void
atenl_eeprom_init_antenna_cap(struct atenl *an)
{
if (is_mt7915(an)) {
if (an->anb[0].cap == BAND_TYPE_2G_5G)
an->anb[0].chainmask = 0xf;
else {
an->anb[0].chainmask = 0x3;
an->anb[1].chainmask = 0xc;
}
} else if (is_mt7916(an)) {
an->anb[0].chainmask = 0x3;
an->anb[1].chainmask = 0x3;
} else if (is_mt7986(an)) {
an->anb[0].chainmask = 0xf;
an->anb[1].chainmask = 0xf;
} else if (is_mt7996(an)) {
an->anb[0].chainmask = 0xf;
an->anb[1].chainmask = 0xf;
an->anb[2].chainmask = 0xf;
}
}
int atenl_eeprom_init(struct atenl *an, u8 phy_idx)
{
bool flash_mode;
int eeprom_fd;
char buf[30];
u8 main_phy_idx = phy_idx;
set_band_val(an, 0, phy_idx, phy_idx);
atenl_nl_check_mtd(an);
flash_mode = an->mtd_part != NULL;
// Get the first main phy index for this chip
if (flash_mode)
main_phy_idx -= an->band_idx;
snprintf(buf, sizeof(buf), "/tmp/atenl-eeprom-phy%u", main_phy_idx);
eeprom_file = strdup(buf);
eeprom_fd = atenl_eeprom_init_file(an, flash_mode);
if (eeprom_fd < 0)
return -1;
an->eeprom_data = mmap(NULL, EEPROM_PART_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, eeprom_fd, 0);
if (!an->eeprom_data) {
perror("mmap");
close(eeprom_fd);
return -1;
}
an->eeprom_fd = eeprom_fd;
atenl_eeprom_init_chip_id(an);
atenl_eeprom_init_max_size(an);
atenl_eeprom_init_band_cap(an);
atenl_eeprom_init_antenna_cap(an);
if (get_band_val(an, 1, valid))
set_band_val(an, 1, phy_idx, phy_idx + 1);
if (get_band_val(an, 2, valid))
set_band_val(an, 2, phy_idx, phy_idx + 2);
return 0;
}
void atenl_eeprom_close(struct atenl *an)
{
msync(an->eeprom_data, EEPROM_PART_SIZE, MS_SYNC);
munmap(an->eeprom_data, EEPROM_PART_SIZE);
close(an->eeprom_fd);
if (!an->cmd_mode) {
if (remove(eeprom_file))
perror("remove");
}
free(eeprom_file);
}
int atenl_eeprom_update_precal(struct atenl *an, int write_offs, int size)
{
u32 offs = an->eeprom_prek_offs;
u8 cal_indicator, *eeprom, *pre_cal;
if (!an->cal && !an->cal_info)
return 0;
eeprom = an->eeprom_data;
pre_cal = eeprom + an->eeprom_size;
cal_indicator = an->cal_info[4];
memcpy(eeprom + offs, &cal_indicator, sizeof(u8));
memcpy(pre_cal, an->cal_info, PRE_CAL_INFO);
pre_cal += (PRE_CAL_INFO + write_offs);
if (an->cal)
memcpy(pre_cal, an->cal, size);
else
memset(pre_cal, 0, size);
return 0;
}
int atenl_eeprom_write_mtd(struct atenl *an)
{
bool flash_mode = an->mtd_part != NULL;
pid_t pid;
char offset[10];
if (!flash_mode)
return 0;
pid = fork();
if (pid < 0) {
perror("Fork");
return EXIT_FAILURE;
} else if (pid == 0) {
int ret;
char *part = strdup(an->mtd_part);
snprintf(offset, sizeof(offset), "%d", an->mtd_offset);
char *cmd[] = {"mtd", "-p", offset, "write", eeprom_file, part, NULL};
ret = execvp("mtd", cmd);
if (ret < 0) {
atenl_err("%s: exec error\n", __func__);
exit(0);
}
} else {
wait(&pid);
}
return 0;
}
/* Directly read values from driver's eeprom.
* It's usally used to get calibrated data from driver.
*/
int atenl_eeprom_read_from_driver(struct atenl *an, u32 offset, int len)
{
u8 *eeprom_data = an->eeprom_data + offset;
char fname[64], buf[1024];
int fd_ori, ret;
ssize_t rd;
snprintf(fname, sizeof(fname),
"/sys/kernel/debug/ieee80211/phy%d/mt76/eeprom",
get_band_val(an, 0, phy_idx));
fd_ori = open(fname, O_RDONLY);
if (fd_ori < 0)
return -1;
ret = lseek(fd_ori, offset, SEEK_SET);
if (ret < 0)
goto out;
while ((rd = read(fd_ori, buf, sizeof(buf))) > 0 && len) {
if (len < rd) {
memcpy(eeprom_data, buf, len);
break;
} else {
memcpy(eeprom_data, buf, rd);
eeprom_data += rd;
len -= rd;
}
}
ret = 0;
out:
close(fd_ori);
return ret;
}
/* Update all eeprom values to driver before writing efuse */
static void
atenl_eeprom_sync_to_driver(struct atenl *an)
{
int i;
for (i = 0; i < an->eeprom_size; i += 16)
atenl_nl_write_eeprom(an, i, &an->eeprom_data[i], 16);
}
void atenl_eeprom_cmd_handler(struct atenl *an, u8 phy_idx, char *cmd)
{
bool flash_mode;
an->cmd_mode = true;
atenl_eeprom_init(an, phy_idx);
flash_mode = an->mtd_part != NULL;
if (!strncmp(cmd, "sync eeprom all", 15)) {
atenl_eeprom_write_mtd(an);
} else if (!strncmp(cmd, "eeprom", 6)) {
char *s = strchr(cmd, ' ');
if (!s) {
atenl_err("eeprom: please type a correct command\n");
return;
}
s++;
if (!strncmp(s, "reset", 5)) {
unlink(eeprom_file);
} else if (!strncmp(s, "file", 4)) {
atenl_info("%s\n", eeprom_file);
atenl_info("Flash mode: %d\n", flash_mode);
} else if (!strncmp(s, "set", 3)) {
u32 offset, val;
s = strchr(s, ' ');
if (!s)
return;
s++;
if (!sscanf(s, "%x=%x", &offset, &val) ||
offset > EEPROM_PART_SIZE)
return;
an->eeprom_data[offset] = val;
atenl_info("set offset 0x%x to 0x%x\n", offset, val);
} else if (!strncmp(s, "update buffermode", 17)) {
atenl_eeprom_sync_to_driver(an);
atenl_nl_update_buffer_mode(an);
} else if (!strncmp(s, "write", 5)) {
s = strchr(s, ' ');
if (!s)
return;
s++;
if (!strncmp(s, "flash", 5)) {
atenl_eeprom_write_mtd(an);
} else if (!strncmp(s, "to efuse", 8)) {
atenl_eeprom_sync_to_driver(an);
atenl_nl_write_efuse_all(an);
}
} else if (!strncmp(s, "read", 4)) {
u32 offset;
s = strchr(s, ' ');
if (!s)
return;
s++;
if (!sscanf(s, "%x", &offset) ||
offset > EEPROM_PART_SIZE)
return;
atenl_info("val = 0x%x (%u)\n", an->eeprom_data[offset],
an->eeprom_data[offset]);
} else if (!strncmp(s, "precal", 6)) {
s = strchr(s, ' ');
if (!s)
return;
s++;
if (!strncmp(s, "sync group", 10)) {
atenl_nl_precal_sync_from_driver(an, PREK_SYNC_GROUP);
} else if (!strncmp(s, "sync dpd 2g", 11)) {
atenl_nl_precal_sync_from_driver(an, PREK_SYNC_DPD_2G);
} else if (!strncmp(s, "sync dpd 5g", 11)) {
atenl_nl_precal_sync_from_driver(an, PREK_SYNC_DPD_5G);
} else if (!strncmp(s, "sync dpd 6g", 11)) {
atenl_nl_precal_sync_from_driver(an, PREK_SYNC_DPD_6G);
} else if (!strncmp(s, "group clean", 11)) {
atenl_nl_precal_sync_from_driver(an, PREK_CLEAN_GROUP);
} else if (!strncmp(s, "dpd clean", 9)) {
atenl_nl_precal_sync_from_driver(an, PREK_CLEAN_DPD);
} else if (!strncmp(s, "sync", 4)) {
atenl_nl_precal_sync_from_driver(an, PREK_SYNC_ALL);
}
} else {
atenl_err("Unknown eeprom command: %s\n", cmd);
}
} else {
atenl_err("Unknown command: %s\n", cmd);
}
}