diff options
Diffstat (limited to 'drivers/net/wireless/mediatek/mt76/mt76x02_mcu.c')
-rw-r--r-- | drivers/net/wireless/mediatek/mt76/mt76x02_mcu.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mcu.c new file mode 100644 index 0000000000..75978820a2 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mcu.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * Copyright (C) 2018 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com> + */ + +#include <linux/kernel.h> +#include <linux/firmware.h> +#include <linux/delay.h> + +#include "mt76x02_mcu.h" + +int mt76x02_mcu_parse_response(struct mt76_dev *mdev, int cmd, + struct sk_buff *skb, int seq) +{ + struct mt76x02_dev *dev = container_of(mdev, struct mt76x02_dev, mt76); + u32 *rxfce; + + if (!skb) { + dev_err(mdev->dev, "MCU message %02x (seq %d) timed out\n", + abs(cmd), seq); + dev->mcu_timeout = 1; + return -ETIMEDOUT; + } + + rxfce = (u32 *)skb->cb; + if (seq != FIELD_GET(MT_RX_FCE_INFO_CMD_SEQ, *rxfce)) + return -EAGAIN; + + return 0; +} +EXPORT_SYMBOL_GPL(mt76x02_mcu_parse_response); + +int mt76x02_mcu_msg_send(struct mt76_dev *mdev, int cmd, const void *data, + int len, bool wait_resp) +{ + struct mt76x02_dev *dev = container_of(mdev, struct mt76x02_dev, mt76); + unsigned long expires = jiffies + HZ; + struct sk_buff *skb; + u32 tx_info; + int ret; + u8 seq; + + if (dev->mcu_timeout) + return -EIO; + + skb = mt76_mcu_msg_alloc(mdev, data, len); + if (!skb) + return -ENOMEM; + + mutex_lock(&mdev->mcu.mutex); + + seq = ++mdev->mcu.msg_seq & 0xf; + if (!seq) + seq = ++mdev->mcu.msg_seq & 0xf; + + tx_info = MT_MCU_MSG_TYPE_CMD | + FIELD_PREP(MT_MCU_MSG_CMD_TYPE, cmd) | + FIELD_PREP(MT_MCU_MSG_CMD_SEQ, seq) | + FIELD_PREP(MT_MCU_MSG_PORT, CPU_TX_PORT) | + FIELD_PREP(MT_MCU_MSG_LEN, skb->len); + + ret = mt76_tx_queue_skb_raw(dev, mdev->q_mcu[MT_MCUQ_WM], skb, tx_info); + if (ret) + goto out; + + while (wait_resp) { + skb = mt76_mcu_get_response(&dev->mt76, expires); + ret = mt76x02_mcu_parse_response(mdev, cmd, skb, seq); + dev_kfree_skb(skb); + if (ret != -EAGAIN) + break; + } + +out: + mutex_unlock(&mdev->mcu.mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(mt76x02_mcu_msg_send); + +int mt76x02_mcu_function_select(struct mt76x02_dev *dev, enum mcu_function func, + u32 val) +{ + struct { + __le32 id; + __le32 value; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(func), + .value = cpu_to_le32(val), + }; + bool wait = false; + + if (func != Q_SELECT) + wait = true; + + return mt76_mcu_send_msg(&dev->mt76, CMD_FUN_SET_OP, &msg, + sizeof(msg), wait); +} +EXPORT_SYMBOL_GPL(mt76x02_mcu_function_select); + +int mt76x02_mcu_set_radio_state(struct mt76x02_dev *dev, bool on) +{ + struct { + __le32 mode; + __le32 level; + } __packed __aligned(4) msg = { + .mode = cpu_to_le32(on ? RADIO_ON : RADIO_OFF), + .level = cpu_to_le32(0), + }; + + return mt76_mcu_send_msg(&dev->mt76, CMD_POWER_SAVING_OP, &msg, + sizeof(msg), false); +} +EXPORT_SYMBOL_GPL(mt76x02_mcu_set_radio_state); + +int mt76x02_mcu_calibrate(struct mt76x02_dev *dev, int type, u32 param) +{ + struct { + __le32 id; + __le32 value; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(type), + .value = cpu_to_le32(param), + }; + bool is_mt76x2e = mt76_is_mmio(&dev->mt76) && is_mt76x2(dev); + int ret; + + if (is_mt76x2e) + mt76_rmw(dev, MT_MCU_COM_REG0, BIT(31), 0); + + ret = mt76_mcu_send_msg(&dev->mt76, CMD_CALIBRATION_OP, &msg, + sizeof(msg), true); + if (ret) + return ret; + + if (is_mt76x2e && + WARN_ON(!mt76_poll_msec(dev, MT_MCU_COM_REG0, + BIT(31), BIT(31), 100))) + return -ETIMEDOUT; + + return 0; +} +EXPORT_SYMBOL_GPL(mt76x02_mcu_calibrate); + +int mt76x02_mcu_cleanup(struct mt76x02_dev *dev) +{ + struct sk_buff *skb; + + mt76_wr(dev, MT_MCU_INT_LEVEL, 1); + usleep_range(20000, 30000); + + while ((skb = skb_dequeue(&dev->mt76.mcu.res_q)) != NULL) + dev_kfree_skb(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(mt76x02_mcu_cleanup); + +void mt76x02_set_ethtool_fwver(struct mt76x02_dev *dev, + const struct mt76x02_fw_header *h) +{ + u16 bld = le16_to_cpu(h->build_ver); + u16 ver = le16_to_cpu(h->fw_ver); + + snprintf(dev->mt76.hw->wiphy->fw_version, + sizeof(dev->mt76.hw->wiphy->fw_version), + "%d.%d.%02d-b%x", + (ver >> 12) & 0xf, (ver >> 8) & 0xf, ver & 0xf, bld); +} +EXPORT_SYMBOL_GPL(mt76x02_set_ethtool_fwver); |