| // 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); |