mirror of
https://github.com/outbackdingo/mt76.git
synced 2026-10-24 13:26:48 +00:00
mt7663 firmware v2 is used for embedded devices since it has more completed features in AP mode. Add the capability to specify in mt7615 Kconfig which firmware load first (v3 or v2) and fallback to the other one if the selected firmware fails to load Signed-off-by: Lorenzo Bianconi <[email protected]> Signed-off-by: Felix Fietkau <[email protected]>
412 lines
10 KiB
C
412 lines
10 KiB
C
// SPDX-License-Identifier: ISC
|
|
/* Copyright (C) 2019 MediaTek Inc.
|
|
*
|
|
* Author: Felix Fietkau <[email protected]>
|
|
* Lorenzo Bianconi <[email protected]>
|
|
* Sean Wang <[email protected]>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/usb.h>
|
|
|
|
#include "mt7615.h"
|
|
#include "mac.h"
|
|
#include "mcu.h"
|
|
#include "regs.h"
|
|
|
|
static const u32 mt7663u_reg_map[] = {
|
|
[MT_TOP_CFG_BASE] = 0x80020000,
|
|
[MT_HW_BASE] = 0x80000000,
|
|
[MT_DMA_SHDL_BASE] = 0x5000a000,
|
|
[MT_HIF_BASE] = 0x50000000,
|
|
[MT_CSR_BASE] = 0x40000000,
|
|
[MT_EFUSE_ADDR_BASE] = 0x78011000,
|
|
[MT_TOP_MISC_BASE] = 0x81020000,
|
|
[MT_PHY_BASE] = 0x82070000,
|
|
[MT_WTBL_BASE_ADDR] = 0x820e0000,
|
|
[MT_CFG_BASE] = 0x820f0000,
|
|
[MT_AGG_BASE] = 0x820f2000,
|
|
[MT_ARB_BASE] = 0x820f3000,
|
|
[MT_TMAC_BASE] = 0x820f4000,
|
|
[MT_RMAC_BASE] = 0x820f5000,
|
|
[MT_DMA_BASE] = 0x820f7000,
|
|
[MT_PF_BASE] = 0x820f8000,
|
|
[MT_WTBL_BASE_ON] = 0x820f9000,
|
|
[MT_WTBL_BASE_OFF] = 0x820f9800,
|
|
[MT_LPON_BASE] = 0x820fb000,
|
|
[MT_MIB_BASE] = 0x820fd000,
|
|
};
|
|
|
|
static const struct usb_device_id mt7615_device_table[] = {
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7663, 0xff, 0xff, 0xff) },
|
|
{ },
|
|
};
|
|
|
|
static void mt7663u_stop(struct ieee80211_hw *hw)
|
|
{
|
|
struct mt7615_phy *phy = mt7615_hw_phy(hw);
|
|
struct mt7615_dev *dev = hw->priv;
|
|
|
|
clear_bit(MT76_STATE_RUNNING, &dev->mphy.state);
|
|
cancel_delayed_work_sync(&phy->scan_work);
|
|
cancel_delayed_work_sync(&phy->mac_work);
|
|
mt76u_stop_tx(&dev->mt76);
|
|
}
|
|
|
|
static void mt7663u_cleanup(struct mt7615_dev *dev)
|
|
{
|
|
clear_bit(MT76_STATE_INITIALIZED, &dev->mphy.state);
|
|
mt76u_queues_deinit(&dev->mt76);
|
|
}
|
|
|
|
static void
|
|
mt7663u_mac_write_txwi(struct mt7615_dev *dev, struct mt76_wcid *wcid,
|
|
enum mt76_txq_id qid, struct ieee80211_sta *sta,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
__le32 *txwi;
|
|
int pid;
|
|
|
|
if (!wcid)
|
|
wcid = &dev->mt76.global_wcid;
|
|
|
|
pid = mt76_tx_status_skb_add(&dev->mt76, wcid, skb);
|
|
|
|
txwi = (__le32 *)(skb->data - MT_USB_TXD_SIZE);
|
|
memset(txwi, 0, MT_USB_TXD_SIZE);
|
|
mt7615_mac_write_txwi(dev, txwi, skb, wcid, sta,
|
|
pid, info->control.hw_key, false);
|
|
skb_push(skb, MT_USB_TXD_SIZE);
|
|
}
|
|
|
|
static int
|
|
__mt7663u_mac_set_rates(struct mt7615_dev *dev,
|
|
struct mt7615_wtbl_desc *wd)
|
|
{
|
|
struct mt7615_rate_desc *rate = &wd->rate;
|
|
struct mt7615_sta *sta = wd->sta;
|
|
u32 w5, w27, addr, val;
|
|
|
|
lockdep_assert_held(&dev->mt76.mutex);
|
|
|
|
if (!sta)
|
|
return -EINVAL;
|
|
|
|
if (!mt76_poll(dev, MT_WTBL_UPDATE, MT_WTBL_UPDATE_BUSY, 0, 5000))
|
|
return -ETIMEDOUT;
|
|
|
|
addr = mt7615_mac_wtbl_addr(dev, sta->wcid.idx);
|
|
|
|
w27 = mt76_rr(dev, addr + 27 * 4);
|
|
w27 &= ~MT_WTBL_W27_CC_BW_SEL;
|
|
w27 |= FIELD_PREP(MT_WTBL_W27_CC_BW_SEL, rate->bw);
|
|
|
|
w5 = mt76_rr(dev, addr + 5 * 4);
|
|
w5 &= ~(MT_WTBL_W5_BW_CAP | MT_WTBL_W5_CHANGE_BW_RATE |
|
|
MT_WTBL_W5_MPDU_OK_COUNT |
|
|
MT_WTBL_W5_MPDU_FAIL_COUNT |
|
|
MT_WTBL_W5_RATE_IDX);
|
|
w5 |= FIELD_PREP(MT_WTBL_W5_BW_CAP, rate->bw) |
|
|
FIELD_PREP(MT_WTBL_W5_CHANGE_BW_RATE,
|
|
rate->bw_idx ? rate->bw_idx - 1 : 7);
|
|
|
|
mt76_wr(dev, MT_WTBL_RIUCR0, w5);
|
|
|
|
mt76_wr(dev, MT_WTBL_RIUCR1,
|
|
FIELD_PREP(MT_WTBL_RIUCR1_RATE0, rate->probe_val) |
|
|
FIELD_PREP(MT_WTBL_RIUCR1_RATE1, rate->val[0]) |
|
|
FIELD_PREP(MT_WTBL_RIUCR1_RATE2_LO, rate->val[1]));
|
|
|
|
mt76_wr(dev, MT_WTBL_RIUCR2,
|
|
FIELD_PREP(MT_WTBL_RIUCR2_RATE2_HI, rate->val[1] >> 8) |
|
|
FIELD_PREP(MT_WTBL_RIUCR2_RATE3, rate->val[1]) |
|
|
FIELD_PREP(MT_WTBL_RIUCR2_RATE4, rate->val[2]) |
|
|
FIELD_PREP(MT_WTBL_RIUCR2_RATE5_LO, rate->val[2]));
|
|
|
|
mt76_wr(dev, MT_WTBL_RIUCR3,
|
|
FIELD_PREP(MT_WTBL_RIUCR3_RATE5_HI, rate->val[2] >> 4) |
|
|
FIELD_PREP(MT_WTBL_RIUCR3_RATE6, rate->val[3]) |
|
|
FIELD_PREP(MT_WTBL_RIUCR3_RATE7, rate->val[3]));
|
|
|
|
mt76_wr(dev, MT_WTBL_UPDATE,
|
|
FIELD_PREP(MT_WTBL_UPDATE_WLAN_IDX, sta->wcid.idx) |
|
|
MT_WTBL_UPDATE_RATE_UPDATE |
|
|
MT_WTBL_UPDATE_TX_COUNT_CLEAR);
|
|
|
|
mt76_wr(dev, addr + 27 * 4, w27);
|
|
|
|
mt76_set(dev, MT_LPON_T0CR, MT_LPON_T0CR_MODE); /* TSF read */
|
|
val = mt76_rr(dev, MT_LPON_UTTR0);
|
|
sta->rate_set_tsf = (val & ~BIT(0)) | rate->rateset;
|
|
|
|
if (!(sta->wcid.tx_info & MT_WCID_TX_INFO_SET))
|
|
mt76_poll(dev, MT_WTBL_UPDATE, MT_WTBL_UPDATE_BUSY, 0, 5000);
|
|
|
|
sta->rate_count = 2 * MT7615_RATE_RETRY * sta->n_rates;
|
|
sta->wcid.tx_info |= MT_WCID_TX_INFO_SET;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
__mt7663u_mac_set_key(struct mt7615_dev *dev,
|
|
struct mt7615_wtbl_desc *wd)
|
|
{
|
|
struct mt7615_key_desc *key = &wd->key;
|
|
struct mt7615_sta *sta = wd->sta;
|
|
enum mt7615_cipher_type cipher;
|
|
struct mt76_wcid *wcid;
|
|
int err;
|
|
|
|
lockdep_assert_held(&dev->mt76.mutex);
|
|
|
|
if (!sta)
|
|
return -EINVAL;
|
|
|
|
cipher = mt7615_mac_get_cipher(key->cipher);
|
|
if (cipher == MT_CIPHER_NONE)
|
|
return -EOPNOTSUPP;
|
|
|
|
wcid = &wd->sta->wcid;
|
|
|
|
mt7615_mac_wtbl_update_cipher(dev, wcid, cipher, key->cmd);
|
|
err = mt7615_mac_wtbl_update_key(dev, wcid, key->key, key->keylen,
|
|
cipher, key->cmd);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = mt7615_mac_wtbl_update_pk(dev, wcid, cipher, key->keyidx,
|
|
key->cmd);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (key->cmd == SET_KEY)
|
|
wcid->cipher |= BIT(cipher);
|
|
else
|
|
wcid->cipher &= ~BIT(cipher);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mt7663u_wtbl_work(struct work_struct *work)
|
|
{
|
|
struct mt7615_wtbl_desc *wd, *wd_next;
|
|
struct mt7615_dev *dev;
|
|
|
|
dev = (struct mt7615_dev *)container_of(work, struct mt7615_dev,
|
|
wtbl_work);
|
|
|
|
list_for_each_entry_safe(wd, wd_next, &dev->wd_head, node) {
|
|
spin_lock_bh(&dev->mt76.lock);
|
|
list_del(&wd->node);
|
|
spin_unlock_bh(&dev->mt76.lock);
|
|
|
|
mutex_lock(&dev->mt76.mutex);
|
|
switch (wd->type) {
|
|
case MT7615_WTBL_RATE_DESC:
|
|
__mt7663u_mac_set_rates(dev, wd);
|
|
break;
|
|
case MT7615_WTBL_KEY_DESC:
|
|
__mt7663u_mac_set_key(dev, wd);
|
|
break;
|
|
}
|
|
mutex_unlock(&dev->mt76.mutex);
|
|
|
|
kfree(wd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mt7663u_tx_complete_skb(struct mt76_dev *mdev, enum mt76_txq_id qid,
|
|
struct mt76_queue_entry *e)
|
|
{
|
|
skb_pull(e->skb, MT_USB_HDR_SIZE + MT_USB_TXD_SIZE);
|
|
mt76_tx_complete_skb(mdev, e->skb);
|
|
}
|
|
|
|
static int
|
|
mt7663u_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
|
|
enum mt76_txq_id qid, struct mt76_wcid *wcid,
|
|
struct ieee80211_sta *sta,
|
|
struct mt76_tx_info *tx_info)
|
|
{
|
|
struct mt7615_dev *dev = container_of(mdev, struct mt7615_dev, mt76);
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_info->skb);
|
|
|
|
if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) {
|
|
struct mt7615_sta *msta;
|
|
|
|
msta = container_of(wcid, struct mt7615_sta, wcid);
|
|
spin_lock_bh(&dev->mt76.lock);
|
|
mt7615_mac_set_rates(&dev->phy, msta, &info->control.rates[0],
|
|
msta->rates);
|
|
msta->rate_probe = true;
|
|
spin_unlock_bh(&dev->mt76.lock);
|
|
}
|
|
mt7663u_mac_write_txwi(dev, wcid, qid, sta, tx_info->skb);
|
|
|
|
return mt76u_skb_dma_info(tx_info->skb, tx_info->skb->len);
|
|
}
|
|
|
|
static bool mt7663u_tx_status_data(struct mt76_dev *mdev, u8 *update)
|
|
{
|
|
struct mt7615_dev *dev = container_of(mdev, struct mt7615_dev, mt76);
|
|
|
|
mutex_lock(&dev->mt76.mutex);
|
|
mt7615_mac_sta_poll(dev);
|
|
mutex_unlock(&dev->mt76.mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt7663u_probe(struct usb_interface *usb_intf,
|
|
const struct usb_device_id *id)
|
|
{
|
|
static const struct mt76_driver_ops drv_ops = {
|
|
.txwi_size = MT_USB_TXD_SIZE,
|
|
.drv_flags = MT_DRV_RX_DMA_HDR,
|
|
.tx_prepare_skb = mt7663u_tx_prepare_skb,
|
|
.tx_complete_skb = mt7663u_tx_complete_skb,
|
|
.tx_status_data = mt7663u_tx_status_data,
|
|
.rx_skb = mt7615_queue_rx_skb,
|
|
.sta_ps = mt7615_sta_ps,
|
|
.sta_add = mt7615_mac_sta_add,
|
|
.sta_remove = mt7615_mac_sta_remove,
|
|
.update_survey = mt7615_update_channel,
|
|
};
|
|
struct usb_device *udev = interface_to_usbdev(usb_intf);
|
|
struct ieee80211_ops *ops;
|
|
struct mt7615_dev *dev;
|
|
struct mt76_dev *mdev;
|
|
int ret;
|
|
|
|
ops = devm_kmemdup(&usb_intf->dev, &mt7615_ops, sizeof(mt7615_ops),
|
|
GFP_KERNEL);
|
|
if (!ops)
|
|
return -ENOMEM;
|
|
|
|
ops->stop = mt7663u_stop;
|
|
|
|
mdev = mt76_alloc_device(&usb_intf->dev, sizeof(*dev), ops, &drv_ops);
|
|
if (!mdev)
|
|
return -ENOMEM;
|
|
|
|
dev = container_of(mdev, struct mt7615_dev, mt76);
|
|
udev = usb_get_dev(udev);
|
|
usb_reset_device(udev);
|
|
|
|
usb_set_intfdata(usb_intf, dev);
|
|
|
|
dev->reg_map = mt7663u_reg_map;
|
|
dev->ops = ops;
|
|
ret = mt76u_init(mdev, usb_intf, true);
|
|
if (ret < 0)
|
|
goto error;
|
|
|
|
mdev->rev = (mt76_rr(dev, MT_HW_CHIPID) << 16) |
|
|
(mt76_rr(dev, MT_HW_REV) & 0xff);
|
|
dev_dbg(mdev->dev, "ASIC revision: %04x\n", mdev->rev);
|
|
|
|
if (mt76_poll_msec(dev, MT_CONN_ON_MISC, MT_TOP_MISC2_FW_PWR_ON,
|
|
FW_STATE_PWR_ON << 1, 500)) {
|
|
dev_dbg(dev->mt76.dev, "Usb device already powered on\n");
|
|
set_bit(MT76_STATE_POWER_OFF, &dev->mphy.state);
|
|
goto alloc_queues;
|
|
}
|
|
|
|
ret = mt76u_vendor_request(&dev->mt76, MT_VEND_POWER_ON,
|
|
USB_DIR_OUT | USB_TYPE_VENDOR,
|
|
0x0, 0x1, NULL, 0);
|
|
if (ret)
|
|
goto error;
|
|
|
|
if (!mt76_poll_msec(dev, MT_CONN_ON_MISC, MT_TOP_MISC2_FW_PWR_ON,
|
|
FW_STATE_PWR_ON << 1, 500)) {
|
|
dev_err(dev->mt76.dev, "Timeout for power on\n");
|
|
return -EIO;
|
|
}
|
|
|
|
alloc_queues:
|
|
ret = mt76u_alloc_mcu_queue(&dev->mt76);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ret = mt76u_alloc_queues(&dev->mt76);
|
|
if (ret)
|
|
goto error;
|
|
|
|
ret = mt7663u_register_device(dev);
|
|
if (ret)
|
|
goto error_freeq;
|
|
|
|
return 0;
|
|
|
|
error_freeq:
|
|
mt76u_queues_deinit(&dev->mt76);
|
|
error:
|
|
mt76u_deinit(&dev->mt76);
|
|
usb_set_intfdata(usb_intf, NULL);
|
|
usb_put_dev(interface_to_usbdev(usb_intf));
|
|
|
|
ieee80211_free_hw(mdev->hw);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void mt7663u_disconnect(struct usb_interface *usb_intf)
|
|
{
|
|
struct mt7615_dev *dev = usb_get_intfdata(usb_intf);
|
|
|
|
if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state))
|
|
return;
|
|
|
|
ieee80211_unregister_hw(dev->mt76.hw);
|
|
mt7663u_cleanup(dev);
|
|
|
|
usb_set_intfdata(usb_intf, NULL);
|
|
usb_put_dev(interface_to_usbdev(usb_intf));
|
|
|
|
mt76u_deinit(&dev->mt76);
|
|
ieee80211_free_hw(dev->mt76.hw);
|
|
}
|
|
|
|
static int __maybe_unused
|
|
mt7663u_suspend(struct usb_interface *intf,
|
|
pm_message_t state)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused
|
|
mt7663u_resume(struct usb_interface *intf)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
MODULE_DEVICE_TABLE(usb, mt7615_device_table);
|
|
MODULE_FIRMWARE(MT7663_OFFLOAD_FIRMWARE_N9);
|
|
MODULE_FIRMWARE(MT7663_OFFLOAD_ROM_PATCH);
|
|
MODULE_FIRMWARE(MT7663_FIRMWARE_N9);
|
|
MODULE_FIRMWARE(MT7663_ROM_PATCH);
|
|
|
|
static struct usb_driver mt7663u_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.id_table = mt7615_device_table,
|
|
.probe = mt7663u_probe,
|
|
.disconnect = mt7663u_disconnect,
|
|
#ifdef CONFIG_PM
|
|
.suspend = mt7663u_suspend,
|
|
.resume = mt7663u_resume,
|
|
.reset_resume = mt7663u_resume,
|
|
#endif /* CONFIG_PM */
|
|
.soft_unbind = 1,
|
|
.disable_hub_initiated_lpm = 1,
|
|
};
|
|
module_usb_driver(mt7663u_driver);
|
|
|
|
MODULE_AUTHOR("Sean Wang <[email protected]>");
|
|
MODULE_AUTHOR("Lorenzo Bianconi <[email protected]>");
|
|
MODULE_LICENSE("Dual BSD/GPL");
|