Path: blob/main/sys/contrib/dev/mediatek/mt76/mt7915/testmode.c
48526 views
// SPDX-License-Identifier: ISC1/* Copyright (C) 2020 MediaTek Inc. */23#include "mt7915.h"4#include "mac.h"5#include "mcu.h"6#include "testmode.h"78enum {9TM_CHANGED_TXPOWER,10TM_CHANGED_FREQ_OFFSET,1112/* must be last */13NUM_TM_CHANGED14};1516static const u8 tm_change_map[] = {17[TM_CHANGED_TXPOWER] = MT76_TM_ATTR_TX_POWER,18[TM_CHANGED_FREQ_OFFSET] = MT76_TM_ATTR_FREQ_OFFSET,19};2021struct reg_band {22u32 band[2];23};2425#define REG_BAND(_list, _reg) \26{ _list.band[0] = MT_##_reg(0); \27_list.band[1] = MT_##_reg(1); }28#define REG_BAND_IDX(_list, _reg, _idx) \29{ _list.band[0] = MT_##_reg(0, _idx); \30_list.band[1] = MT_##_reg(1, _idx); }3132#define TM_REG_MAX_ID 1733static struct reg_band reg_backup_list[TM_REG_MAX_ID];343536static int37mt7915_tm_set_tx_power(struct mt7915_phy *phy)38{39struct mt7915_dev *dev = phy->dev;40struct mt76_phy *mphy = phy->mt76;41struct cfg80211_chan_def *chandef = &mphy->chandef;42int freq = chandef->center_freq1;43int ret;44struct {45u8 format_id;46u8 band_idx;47s8 tx_power;48u8 ant_idx; /* Only 0 is valid */49u8 center_chan;50u8 rsv[3];51} __packed req = {52.format_id = 0xf,53.band_idx = phy->mt76->band_idx,54.center_chan = ieee80211_frequency_to_channel(freq),55};56u8 *tx_power = NULL;5758if (phy->mt76->test.state != MT76_TM_STATE_OFF)59tx_power = phy->mt76->test.tx_power;6061/* Tx power of the other antennas are the same as antenna 0 */62if (tx_power && tx_power[0])63req.tx_power = tx_power[0];6465ret = mt76_mcu_send_msg(&dev->mt76,66MCU_EXT_CMD(TX_POWER_FEATURE_CTRL),67&req, sizeof(req), false);6869return ret;70}7172static int73mt7915_tm_set_freq_offset(struct mt7915_phy *phy, bool en, u32 val)74{75struct mt7915_dev *dev = phy->dev;76struct mt7915_tm_cmd req = {77.testmode_en = en,78.param_idx = MCU_ATE_SET_FREQ_OFFSET,79.param.freq.band = phy->mt76->band_idx,80.param.freq.freq_offset = cpu_to_le32(val),81};8283return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL), &req,84sizeof(req), false);85}8687static int88mt7915_tm_mode_ctrl(struct mt7915_dev *dev, bool enable)89{90struct {91u8 format_id;92bool enable;93u8 rsv[2];94} __packed req = {95.format_id = 0x6,96.enable = enable,97};9899return mt76_mcu_send_msg(&dev->mt76,100MCU_EXT_CMD(TX_POWER_FEATURE_CTRL),101&req, sizeof(req), false);102}103104static int105mt7915_tm_set_trx(struct mt7915_phy *phy, int type, bool en)106{107struct mt7915_dev *dev = phy->dev;108struct mt7915_tm_cmd req = {109.testmode_en = 1,110.param_idx = MCU_ATE_SET_TRX,111.param.trx.type = type,112.param.trx.enable = en,113.param.trx.band = phy->mt76->band_idx,114};115116return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL), &req,117sizeof(req), false);118}119120static int121mt7915_tm_clean_hwq(struct mt7915_phy *phy, u8 wcid)122{123struct mt7915_dev *dev = phy->dev;124struct mt7915_tm_cmd req = {125.testmode_en = 1,126.param_idx = MCU_ATE_CLEAN_TXQUEUE,127.param.clean.wcid = wcid,128.param.clean.band = phy->mt76->band_idx,129};130131return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL), &req,132sizeof(req), false);133}134135static int136mt7915_tm_set_slot_time(struct mt7915_phy *phy, u8 slot_time, u8 sifs)137{138struct mt7915_dev *dev = phy->dev;139struct mt7915_tm_cmd req = {140.testmode_en = !(phy->mt76->test.state == MT76_TM_STATE_OFF),141.param_idx = MCU_ATE_SET_SLOT_TIME,142.param.slot.slot_time = slot_time,143.param.slot.sifs = sifs,144.param.slot.rifs = 2,145.param.slot.eifs = cpu_to_le16(60),146.param.slot.band = phy->mt76->band_idx,147};148149return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(ATE_CTRL), &req,150sizeof(req), false);151}152153static int154mt7915_tm_set_tam_arb(struct mt7915_phy *phy, bool enable, bool mu)155{156struct mt7915_dev *dev = phy->dev;157u32 op_mode;158159if (!enable)160op_mode = TAM_ARB_OP_MODE_NORMAL;161else if (mu)162op_mode = TAM_ARB_OP_MODE_TEST;163else164op_mode = TAM_ARB_OP_MODE_FORCE_SU;165166return mt7915_mcu_set_muru_ctrl(dev, MURU_SET_ARB_OP_MODE, op_mode);167}168169static int170mt7915_tm_set_wmm_qid(struct mt7915_phy *phy, u8 qid, u8 aifs, u8 cw_min,171u16 cw_max, u16 txop)172{173struct mt7915_vif *mvif = (struct mt7915_vif *)phy->monitor_vif->drv_priv;174struct mt7915_mcu_tx req = { .total = 1 };175struct edca *e = &req.edca[0];176177e->queue = qid + mvif->mt76.wmm_idx * MT76_CONNAC_MAX_WMM_SETS;178e->set = WMM_PARAM_SET;179180e->aifs = aifs;181e->cw_min = cw_min;182e->cw_max = cpu_to_le16(cw_max);183e->txop = cpu_to_le16(txop);184185return mt7915_mcu_update_edca(phy->dev, &req);186}187188static int189mt7915_tm_set_ipg_params(struct mt7915_phy *phy, u32 ipg, u8 mode)190{191#define TM_DEFAULT_SIFS 10192#define TM_MAX_SIFS 127193#define TM_MAX_AIFSN 0xf194#define TM_MIN_AIFSN 0x1195#define BBP_PROC_TIME 1500196struct mt7915_dev *dev = phy->dev;197u8 sig_ext = (mode == MT76_TM_TX_MODE_CCK) ? 0 : 6;198u8 slot_time = 9, sifs = TM_DEFAULT_SIFS;199u8 aifsn = TM_MIN_AIFSN;200u8 band = phy->mt76->band_idx;201u32 i2t_time, tr2t_time, txv_time;202u16 cw = 0;203204if (ipg < sig_ext + slot_time + sifs)205ipg = 0;206207if (!ipg)208goto done;209210ipg -= sig_ext;211212if (ipg <= (TM_MAX_SIFS + slot_time)) {213sifs = ipg - slot_time;214} else {215u32 val = (ipg + slot_time) / slot_time;216217while (val >>= 1)218cw++;219220if (cw > 16)221cw = 16;222223ipg -= ((1 << cw) - 1) * slot_time;224225aifsn = ipg / slot_time;226if (aifsn > TM_MAX_AIFSN)227aifsn = TM_MAX_AIFSN;228229ipg -= aifsn * slot_time;230231if (ipg > TM_DEFAULT_SIFS)232sifs = min_t(u32, ipg, TM_MAX_SIFS);233}234done:235txv_time = mt76_get_field(dev, MT_TMAC_ATCR(band),236MT_TMAC_ATCR_TXV_TOUT);237txv_time *= 50; /* normal clock time */238239i2t_time = (slot_time * 1000 - txv_time - BBP_PROC_TIME) / 50;240tr2t_time = (sifs * 1000 - txv_time - BBP_PROC_TIME) / 50;241242mt76_set(dev, MT_TMAC_TRCR0(band),243FIELD_PREP(MT_TMAC_TRCR0_TR2T_CHK, tr2t_time) |244FIELD_PREP(MT_TMAC_TRCR0_I2T_CHK, i2t_time));245246mt7915_tm_set_slot_time(phy, slot_time, sifs);247248return mt7915_tm_set_wmm_qid(phy,249mt76_connac_lmac_mapping(IEEE80211_AC_BE),250aifsn, cw, cw, 0);251}252253static int254mt7915_tm_set_tx_len(struct mt7915_phy *phy, u32 tx_time)255{256struct mt76_phy *mphy = phy->mt76;257struct mt76_testmode_data *td = &mphy->test;258struct ieee80211_supported_band *sband;259struct rate_info rate = {};260u16 flags = 0, tx_len;261u32 bitrate;262int ret;263264if (!tx_time)265return 0;266267rate.mcs = td->tx_rate_idx;268rate.nss = td->tx_rate_nss;269270switch (td->tx_rate_mode) {271case MT76_TM_TX_MODE_CCK:272case MT76_TM_TX_MODE_OFDM:273if (mphy->chandef.chan->band == NL80211_BAND_5GHZ)274sband = &mphy->sband_5g.sband;275else if (mphy->chandef.chan->band == NL80211_BAND_6GHZ)276sband = &mphy->sband_6g.sband;277else278sband = &mphy->sband_2g.sband;279280rate.legacy = sband->bitrates[rate.mcs].bitrate;281break;282case MT76_TM_TX_MODE_HT:283rate.mcs += rate.nss * 8;284flags |= RATE_INFO_FLAGS_MCS;285286if (td->tx_rate_sgi)287flags |= RATE_INFO_FLAGS_SHORT_GI;288break;289case MT76_TM_TX_MODE_VHT:290flags |= RATE_INFO_FLAGS_VHT_MCS;291292if (td->tx_rate_sgi)293flags |= RATE_INFO_FLAGS_SHORT_GI;294break;295case MT76_TM_TX_MODE_HE_SU:296case MT76_TM_TX_MODE_HE_EXT_SU:297case MT76_TM_TX_MODE_HE_TB:298case MT76_TM_TX_MODE_HE_MU:299rate.he_gi = td->tx_rate_sgi;300flags |= RATE_INFO_FLAGS_HE_MCS;301break;302default:303break;304}305rate.flags = flags;306307switch (mphy->chandef.width) {308case NL80211_CHAN_WIDTH_160:309case NL80211_CHAN_WIDTH_80P80:310rate.bw = RATE_INFO_BW_160;311break;312case NL80211_CHAN_WIDTH_80:313rate.bw = RATE_INFO_BW_80;314break;315case NL80211_CHAN_WIDTH_40:316rate.bw = RATE_INFO_BW_40;317break;318default:319rate.bw = RATE_INFO_BW_20;320break;321}322323bitrate = cfg80211_calculate_bitrate(&rate);324tx_len = bitrate * tx_time / 10 / 8;325326ret = mt76_testmode_alloc_skb(phy->mt76, tx_len);327if (ret)328return ret;329330return 0;331}332333static void334mt7915_tm_reg_backup_restore(struct mt7915_phy *phy)335{336int n_regs = ARRAY_SIZE(reg_backup_list);337struct mt7915_dev *dev = phy->dev;338u32 *b = phy->test.reg_backup;339u8 band = phy->mt76->band_idx;340int i;341342REG_BAND_IDX(reg_backup_list[0], AGG_PCR0, 0);343REG_BAND_IDX(reg_backup_list[1], AGG_PCR0, 1);344REG_BAND_IDX(reg_backup_list[2], AGG_AWSCR0, 0);345REG_BAND_IDX(reg_backup_list[3], AGG_AWSCR0, 1);346REG_BAND_IDX(reg_backup_list[4], AGG_AWSCR0, 2);347REG_BAND_IDX(reg_backup_list[5], AGG_AWSCR0, 3);348REG_BAND(reg_backup_list[6], AGG_MRCR);349REG_BAND(reg_backup_list[7], TMAC_TFCR0);350REG_BAND(reg_backup_list[8], TMAC_TCR0);351REG_BAND(reg_backup_list[9], AGG_ATCR1);352REG_BAND(reg_backup_list[10], AGG_ATCR3);353REG_BAND(reg_backup_list[11], TMAC_TRCR0);354REG_BAND(reg_backup_list[12], TMAC_ICR0);355REG_BAND_IDX(reg_backup_list[13], ARB_DRNGR0, 0);356REG_BAND_IDX(reg_backup_list[14], ARB_DRNGR0, 1);357REG_BAND(reg_backup_list[15], WF_RFCR);358REG_BAND(reg_backup_list[16], WF_RFCR1);359360if (phy->mt76->test.state == MT76_TM_STATE_OFF) {361for (i = 0; i < n_regs; i++)362mt76_wr(dev, reg_backup_list[i].band[band], b[i]);363return;364}365366if (!b) {367b = devm_kzalloc(dev->mt76.dev, 4 * n_regs, GFP_KERNEL);368if (!b)369return;370371phy->test.reg_backup = b;372for (i = 0; i < n_regs; i++)373b[i] = mt76_rr(dev, reg_backup_list[i].band[band]);374}375376mt76_clear(dev, MT_AGG_PCR0(band, 0), MT_AGG_PCR0_MM_PROT |377MT_AGG_PCR0_GF_PROT | MT_AGG_PCR0_ERP_PROT |378MT_AGG_PCR0_VHT_PROT | MT_AGG_PCR0_BW20_PROT |379MT_AGG_PCR0_BW40_PROT | MT_AGG_PCR0_BW80_PROT);380mt76_set(dev, MT_AGG_PCR0(band, 0), MT_AGG_PCR0_PTA_WIN_DIS);381382mt76_wr(dev, MT_AGG_PCR0(band, 1), MT_AGG_PCR1_RTS0_NUM_THRES |383MT_AGG_PCR1_RTS0_LEN_THRES);384385mt76_clear(dev, MT_AGG_MRCR(band), MT_AGG_MRCR_BAR_CNT_LIMIT |386MT_AGG_MRCR_LAST_RTS_CTS_RN | MT_AGG_MRCR_RTS_FAIL_LIMIT |387MT_AGG_MRCR_TXCMD_RTS_FAIL_LIMIT);388389mt76_rmw(dev, MT_AGG_MRCR(band), MT_AGG_MRCR_RTS_FAIL_LIMIT |390MT_AGG_MRCR_TXCMD_RTS_FAIL_LIMIT,391FIELD_PREP(MT_AGG_MRCR_RTS_FAIL_LIMIT, 1) |392FIELD_PREP(MT_AGG_MRCR_TXCMD_RTS_FAIL_LIMIT, 1));393394mt76_wr(dev, MT_TMAC_TFCR0(band), 0);395mt76_clear(dev, MT_TMAC_TCR0(band), MT_TMAC_TCR0_TBTT_STOP_CTRL);396397/* config rx filter for testmode rx */398mt76_wr(dev, MT_WF_RFCR(band), 0xcf70a);399mt76_wr(dev, MT_WF_RFCR1(band), 0);400}401402static void403mt7915_tm_init(struct mt7915_phy *phy, bool en)404{405struct mt7915_dev *dev = phy->dev;406int state;407408if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state))409return;410411mt7915_mcu_set_sku_en(phy, !en);412413mt7915_tm_mode_ctrl(dev, en);414mt7915_tm_reg_backup_restore(phy);415mt7915_tm_set_trx(phy, TM_MAC_TXRX, !en);416417mt7915_mcu_add_bss_info(phy, phy->monitor_vif, en);418state = en ? CONN_STATE_PORT_SECURE : CONN_STATE_DISCONNECT;419mt7915_mcu_add_sta(dev, phy->monitor_vif, NULL, state, true);420421if (!en)422mt7915_tm_set_tam_arb(phy, en, 0);423}424425static void426mt7915_tm_update_channel(struct mt7915_phy *phy)427{428mutex_unlock(&phy->dev->mt76.mutex);429mt76_update_channel(phy->mt76);430mutex_lock(&phy->dev->mt76.mutex);431432mt7915_mcu_set_chan_info(phy, MCU_EXT_CMD(SET_RX_PATH));433}434435static void436mt7915_tm_set_tx_frames(struct mt7915_phy *phy, bool en)437{438struct mt76_testmode_data *td = &phy->mt76->test;439struct mt7915_dev *dev = phy->dev;440struct ieee80211_tx_info *info;441u8 duty_cycle = td->tx_duty_cycle;442u32 tx_time = td->tx_time;443u32 ipg = td->tx_ipg;444445mt7915_tm_set_trx(phy, TM_MAC_RX_RXV, false);446mt7915_tm_clean_hwq(phy, dev->mt76.global_wcid.idx);447448if (en) {449mt7915_tm_update_channel(phy);450451if (td->tx_spe_idx)452phy->test.spe_idx = td->tx_spe_idx;453else454phy->test.spe_idx = mt76_connac_spe_idx(td->tx_antenna_mask);455}456457mt7915_tm_set_tam_arb(phy, en,458td->tx_rate_mode == MT76_TM_TX_MODE_HE_MU);459460/* if all three params are set, duty_cycle will be ignored */461if (duty_cycle && tx_time && !ipg) {462ipg = tx_time * 100 / duty_cycle - tx_time;463} else if (duty_cycle && !tx_time && ipg) {464if (duty_cycle < 100)465tx_time = duty_cycle * ipg / (100 - duty_cycle);466}467468mt7915_tm_set_ipg_params(phy, ipg, td->tx_rate_mode);469mt7915_tm_set_tx_len(phy, tx_time);470471if (ipg)472td->tx_queued_limit = MT76_TM_TIMEOUT * 1000000 / ipg / 2;473474if (!en || !td->tx_skb)475return;476477info = IEEE80211_SKB_CB(td->tx_skb);478info->control.vif = phy->monitor_vif;479480mt7915_tm_set_trx(phy, TM_MAC_TX, en);481}482483static void484mt7915_tm_set_rx_frames(struct mt7915_phy *phy, bool en)485{486mt7915_tm_set_trx(phy, TM_MAC_RX_RXV, false);487488if (en) {489struct mt7915_dev *dev = phy->dev;490491mt7915_tm_update_channel(phy);492493/* read-clear */494mt76_rr(dev, MT_MIB_SDR3(phy->mt76->band_idx));495mt7915_tm_set_trx(phy, TM_MAC_RX_RXV, en);496}497}498499static int500mt7915_tm_rf_switch_mode(struct mt7915_dev *dev, u32 oper)501{502struct mt7915_tm_rf_test req = {503.op.op_mode = cpu_to_le32(oper),504};505506return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(RF_TEST), &req,507sizeof(req), true);508}509510static int511mt7915_tm_set_tx_cont(struct mt7915_phy *phy, bool en)512{513#define TX_CONT_START 0x05514#define TX_CONT_STOP 0x06515struct mt7915_dev *dev = phy->dev;516struct cfg80211_chan_def *chandef = &phy->mt76->chandef;517int freq1 = ieee80211_frequency_to_channel(chandef->center_freq1);518struct mt76_testmode_data *td = &phy->mt76->test;519u32 func_idx = en ? TX_CONT_START : TX_CONT_STOP;520u8 rate_idx = td->tx_rate_idx, mode;521u8 band = phy->mt76->band_idx;522u16 rateval;523struct mt7915_tm_rf_test req = {524.action = 1,525.icap_len = 120,526.op.rf.func_idx = cpu_to_le32(func_idx),527};528struct tm_tx_cont *tx_cont = &req.op.rf.param.tx_cont;529530tx_cont->control_ch = chandef->chan->hw_value;531tx_cont->center_ch = freq1;532tx_cont->tx_ant = td->tx_antenna_mask;533tx_cont->band = band;534535switch (chandef->width) {536case NL80211_CHAN_WIDTH_40:537tx_cont->bw = CMD_CBW_40MHZ;538break;539case NL80211_CHAN_WIDTH_80:540tx_cont->bw = CMD_CBW_80MHZ;541break;542case NL80211_CHAN_WIDTH_80P80:543tx_cont->bw = CMD_CBW_8080MHZ;544break;545case NL80211_CHAN_WIDTH_160:546tx_cont->bw = CMD_CBW_160MHZ;547break;548case NL80211_CHAN_WIDTH_5:549tx_cont->bw = CMD_CBW_5MHZ;550break;551case NL80211_CHAN_WIDTH_10:552tx_cont->bw = CMD_CBW_10MHZ;553break;554case NL80211_CHAN_WIDTH_20:555tx_cont->bw = CMD_CBW_20MHZ;556break;557case NL80211_CHAN_WIDTH_20_NOHT:558tx_cont->bw = CMD_CBW_20MHZ;559break;560default:561return -EINVAL;562}563564if (!en) {565req.op.rf.param.func_data = cpu_to_le32(band);566goto out;567}568569if (td->tx_rate_mode <= MT76_TM_TX_MODE_OFDM) {570struct ieee80211_supported_band *sband;571u8 idx = rate_idx;572573if (chandef->chan->band == NL80211_BAND_5GHZ)574sband = &phy->mt76->sband_5g.sband;575else if (chandef->chan->band == NL80211_BAND_6GHZ)576sband = &phy->mt76->sband_6g.sband;577else578sband = &phy->mt76->sband_2g.sband;579580if (td->tx_rate_mode == MT76_TM_TX_MODE_OFDM)581idx += 4;582rate_idx = sband->bitrates[idx].hw_value & 0xff;583}584585switch (td->tx_rate_mode) {586case MT76_TM_TX_MODE_CCK:587mode = MT_PHY_TYPE_CCK;588break;589case MT76_TM_TX_MODE_OFDM:590mode = MT_PHY_TYPE_OFDM;591break;592case MT76_TM_TX_MODE_HT:593mode = MT_PHY_TYPE_HT;594break;595case MT76_TM_TX_MODE_VHT:596mode = MT_PHY_TYPE_VHT;597break;598case MT76_TM_TX_MODE_HE_SU:599mode = MT_PHY_TYPE_HE_SU;600break;601case MT76_TM_TX_MODE_HE_EXT_SU:602mode = MT_PHY_TYPE_HE_EXT_SU;603break;604case MT76_TM_TX_MODE_HE_TB:605mode = MT_PHY_TYPE_HE_TB;606break;607case MT76_TM_TX_MODE_HE_MU:608mode = MT_PHY_TYPE_HE_MU;609break;610default:611return -EINVAL;612}613614rateval = mode << 6 | rate_idx;615tx_cont->rateval = cpu_to_le16(rateval);616617out:618if (!en) {619int ret;620621ret = mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(RF_TEST), &req,622sizeof(req), true);623if (ret)624return ret;625626return mt7915_tm_rf_switch_mode(dev, RF_OPER_NORMAL);627}628629mt7915_tm_rf_switch_mode(dev, RF_OPER_RF_TEST);630mt7915_tm_update_channel(phy);631632return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(RF_TEST), &req,633sizeof(req), true);634}635636static void637mt7915_tm_update_params(struct mt7915_phy *phy, u32 changed)638{639struct mt76_testmode_data *td = &phy->mt76->test;640bool en = phy->mt76->test.state != MT76_TM_STATE_OFF;641642if (changed & BIT(TM_CHANGED_FREQ_OFFSET))643mt7915_tm_set_freq_offset(phy, en, en ? td->freq_offset : 0);644if (changed & BIT(TM_CHANGED_TXPOWER))645mt7915_tm_set_tx_power(phy);646}647648static int649mt7915_tm_set_state(struct mt76_phy *mphy, enum mt76_testmode_state state)650{651struct mt76_testmode_data *td = &mphy->test;652struct mt7915_phy *phy = mphy->priv;653enum mt76_testmode_state prev_state = td->state;654655mphy->test.state = state;656657if (prev_state == MT76_TM_STATE_TX_FRAMES ||658state == MT76_TM_STATE_TX_FRAMES)659mt7915_tm_set_tx_frames(phy, state == MT76_TM_STATE_TX_FRAMES);660else if (prev_state == MT76_TM_STATE_RX_FRAMES ||661state == MT76_TM_STATE_RX_FRAMES)662mt7915_tm_set_rx_frames(phy, state == MT76_TM_STATE_RX_FRAMES);663else if (prev_state == MT76_TM_STATE_TX_CONT ||664state == MT76_TM_STATE_TX_CONT)665mt7915_tm_set_tx_cont(phy, state == MT76_TM_STATE_TX_CONT);666else if (prev_state == MT76_TM_STATE_OFF ||667state == MT76_TM_STATE_OFF)668mt7915_tm_init(phy, !(state == MT76_TM_STATE_OFF));669670if ((state == MT76_TM_STATE_IDLE &&671prev_state == MT76_TM_STATE_OFF) ||672(state == MT76_TM_STATE_OFF &&673prev_state == MT76_TM_STATE_IDLE)) {674u32 changed = 0;675int i;676677for (i = 0; i < ARRAY_SIZE(tm_change_map); i++) {678u16 cur = tm_change_map[i];679680if (td->param_set[cur / 32] & BIT(cur % 32))681changed |= BIT(i);682}683684mt7915_tm_update_params(phy, changed);685}686687return 0;688}689690static int691mt7915_tm_set_params(struct mt76_phy *mphy, struct nlattr **tb,692enum mt76_testmode_state new_state)693{694struct mt76_testmode_data *td = &mphy->test;695struct mt7915_phy *phy = mphy->priv;696struct mt7915_dev *dev = phy->dev;697u32 chainmask = mphy->chainmask, changed = 0;698bool ext_phy = phy != &dev->phy;699int i;700701BUILD_BUG_ON(NUM_TM_CHANGED >= 32);702703if (new_state == MT76_TM_STATE_OFF ||704td->state == MT76_TM_STATE_OFF)705return 0;706707chainmask = ext_phy ? chainmask >> dev->chainshift : chainmask;708if (td->tx_antenna_mask > chainmask)709return -EINVAL;710711for (i = 0; i < ARRAY_SIZE(tm_change_map); i++) {712if (tb[tm_change_map[i]])713changed |= BIT(i);714}715716mt7915_tm_update_params(phy, changed);717718return 0;719}720721static int722mt7915_tm_dump_stats(struct mt76_phy *mphy, struct sk_buff *msg)723{724struct mt7915_phy *phy = mphy->priv;725struct mt7915_dev *dev = phy->dev;726enum mt76_rxq_id q;727void *rx, *rssi;728u16 fcs_err;729int i;730u32 cnt;731732rx = nla_nest_start(msg, MT76_TM_STATS_ATTR_LAST_RX);733if (!rx)734return -ENOMEM;735736if (nla_put_s32(msg, MT76_TM_RX_ATTR_FREQ_OFFSET, phy->test.last_freq_offset))737return -ENOMEM;738739rssi = nla_nest_start(msg, MT76_TM_RX_ATTR_RCPI);740if (!rssi)741return -ENOMEM;742743for (i = 0; i < ARRAY_SIZE(phy->test.last_rcpi); i++)744if (nla_put_u8(msg, i, phy->test.last_rcpi[i]))745return -ENOMEM;746747nla_nest_end(msg, rssi);748749rssi = nla_nest_start(msg, MT76_TM_RX_ATTR_IB_RSSI);750if (!rssi)751return -ENOMEM;752753for (i = 0; i < ARRAY_SIZE(phy->test.last_ib_rssi); i++)754if (nla_put_s8(msg, i, phy->test.last_ib_rssi[i]))755return -ENOMEM;756757nla_nest_end(msg, rssi);758759rssi = nla_nest_start(msg, MT76_TM_RX_ATTR_WB_RSSI);760if (!rssi)761return -ENOMEM;762763for (i = 0; i < ARRAY_SIZE(phy->test.last_wb_rssi); i++)764if (nla_put_s8(msg, i, phy->test.last_wb_rssi[i]))765return -ENOMEM;766767nla_nest_end(msg, rssi);768769if (nla_put_u8(msg, MT76_TM_RX_ATTR_SNR, phy->test.last_snr))770return -ENOMEM;771772nla_nest_end(msg, rx);773774cnt = mt76_rr(dev, MT_MIB_SDR3(phy->mt76->band_idx));775fcs_err = is_mt7915(&dev->mt76) ? FIELD_GET(MT_MIB_SDR3_FCS_ERR_MASK, cnt) :776FIELD_GET(MT_MIB_SDR3_FCS_ERR_MASK_MT7916, cnt);777778q = phy->mt76->band_idx ? MT_RXQ_BAND1 : MT_RXQ_MAIN;779mphy->test.rx_stats.packets[q] += fcs_err;780mphy->test.rx_stats.fcs_error[q] += fcs_err;781782return 0;783}784785const struct mt76_testmode_ops mt7915_testmode_ops = {786.set_state = mt7915_tm_set_state,787.set_params = mt7915_tm_set_params,788.dump_stats = mt7915_tm_dump_stats,789};790791792