Commit 1d8efc74 authored by Sean Wang's avatar Sean Wang Committed by Felix Fietkau

mt76: mt7921: introduce Runtime PM support

Introduce runtime PM to mt7921 driver
Co-developed-by: default avatarLorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: default avatarLorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: default avatarSean Wang <sean.wang@mediatek.com>
Signed-off-by: default avatarFelix Fietkau <nbd@nbd.name>
parent 022159b0
......@@ -159,6 +159,60 @@ mt7921_queues_read(struct seq_file *s, void *data)
return 0;
}
static int
mt7921_pm_set(void *data, u64 val)
{
struct mt7921_dev *dev = data;
struct mt76_phy *mphy = dev->phy.mt76;
int ret = 0;
mt7921_mutex_acquire(dev);
dev->pm.enable = val;
ieee80211_iterate_active_interfaces(mphy->hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7921_pm_interface_iter, mphy->priv);
mt7921_mutex_release(dev);
return ret;
}
static int
mt7921_pm_get(void *data, u64 *val)
{
struct mt7921_dev *dev = data;
*val = dev->pm.enable;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_pm, mt7921_pm_get, mt7921_pm_set, "%lld\n");
static int
mt7921_pm_idle_timeout_set(void *data, u64 val)
{
struct mt7921_dev *dev = data;
dev->pm.idle_timeout = msecs_to_jiffies(val);
return 0;
}
static int
mt7921_pm_idle_timeout_get(void *data, u64 *val)
{
struct mt7921_dev *dev = data;
*val = jiffies_to_msecs(dev->pm.idle_timeout);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_pm_idle_timeout, mt7921_pm_idle_timeout_get,
mt7921_pm_idle_timeout_set, "%lld\n");
int mt7921_init_debugfs(struct mt7921_dev *dev)
{
struct dentry *dir;
......@@ -173,6 +227,9 @@ int mt7921_init_debugfs(struct mt7921_dev *dev)
mt7921_queues_acq);
debugfs_create_file("tx_stats", 0400, dir, dev, &fops_tx_stats);
debugfs_create_file("fw_debug", 0600, dir, dev, &fops_fw_debug);
debugfs_create_file("runtime-pm", 0600, dir, dev, &fops_pm);
debugfs_create_file("idle-timeout", 0600, dir, dev,
&fops_pm_idle_timeout);
return 0;
}
......@@ -201,6 +201,12 @@ int mt7921_register_device(struct mt7921_dev *dev)
dev->phy.dev = dev;
dev->phy.mt76 = &dev->mt76.phy;
dev->mt76.phy.priv = &dev->phy;
INIT_DELAYED_WORK(&dev->pm.ps_work, mt7921_pm_power_save_work);
INIT_WORK(&dev->pm.wake_work, mt7921_pm_wake_work);
init_completion(&dev->pm.wake_cmpl);
spin_lock_init(&dev->pm.txq_lock);
set_bit(MT76_STATE_PM, &dev->mphy.state);
INIT_LIST_HEAD(&dev->phy.stats_list);
INIT_DELAYED_WORK(&dev->mphy.mac_work, mt7921_mac_work);
INIT_DELAYED_WORK(&dev->phy.scan_work, mt7921_scan_work);
......@@ -216,6 +222,7 @@ int mt7921_register_device(struct mt7921_dev *dev)
return ret;
mt7921_init_wiphy(hw);
dev->pm.idle_timeout = MT7921_PM_TIMEOUT;
dev->mphy.sband_2g.sband.ht_cap.cap |=
IEEE80211_HT_CAP_LDPC_CODING |
IEEE80211_HT_CAP_MAX_AMSDU;
......
......@@ -1001,22 +1001,27 @@ void mt7921_mac_tx_free(struct mt7921_dev *dev, struct sk_buff *skb)
mt76_put_txwi(mdev, txwi);
}
mt7921_mac_sta_poll(dev);
if (wake) {
spin_lock_bh(&dev->token_lock);
mt7921_set_tx_blocked(dev, false);
spin_unlock_bh(&dev->token_lock);
}
mt76_worker_schedule(&dev->mt76.tx_worker);
napi_consume_skb(skb, 1);
list_for_each_entry_safe(skb, tmp, &free_list, list) {
skb_list_del_init(skb);
napi_consume_skb(skb, 1);
}
if (test_bit(MT76_STATE_PM, &dev->phy.mt76->state))
return;
mt7921_mac_sta_poll(dev);
mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
mt76_worker_schedule(&dev->mt76.tx_worker);
}
void mt7921_tx_complete_skb(struct mt76_dev *mdev, struct mt76_queue_entry *e)
......@@ -1166,9 +1171,14 @@ void mt7921_update_channel(struct mt76_dev *mdev)
{
struct mt7921_dev *dev = container_of(mdev, struct mt7921_dev, mt76);
if (mt76_connac_pm_wake(&dev->mphy, &dev->pm))
return;
mt7921_phy_update_channel(&mdev->phy, 0);
/* reset obss airtime */
mt76_set(dev, MT_WF_RMAC_MIB_TIME0(0), MT_WF_RMAC_MIB_RXTIME_CLR);
mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
}
static bool
......@@ -1257,7 +1267,7 @@ void mt7921_mac_reset_work(struct work_struct *work)
napi_disable(&dev->mt76.napi[2]);
napi_disable(&dev->mt76.tx_napi);
mutex_lock(&dev->mt76.mutex);
mt7921_mutex_acquire(dev);
mt76_wr(dev, MT_MCU_INT_EVENT, MT_MCU_INT_EVENT_DMA_STOPPED);
......@@ -1292,7 +1302,7 @@ void mt7921_mac_reset_work(struct work_struct *work)
mt76_wr(dev, MT_MCU_INT_EVENT, MT_MCU_INT_EVENT_RESET_DONE);
mt7921_wait_reset_state(dev, MT_MCU_CMD_NORMAL_STATE);
mutex_unlock(&dev->mt76.mutex);
mt7921_mutex_release(dev);
ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mphy.mac_work,
MT7921_WATCHDOG_TIME);
......@@ -1373,7 +1383,10 @@ void mt7921_mac_work(struct work_struct *work)
mac_work.work);
phy = mphy->priv;
mutex_lock(&mphy->dev->mutex);
if (test_bit(MT76_STATE_PM, &mphy->state))
goto out;
mt7921_mutex_acquire(phy->dev);
mt76_update_survey(mphy->dev);
if (++mphy->mac_work_count == 5) {
......@@ -1386,8 +1399,75 @@ void mt7921_mac_work(struct work_struct *work)
mt7921_mac_sta_stats_work(phy);
};
mutex_unlock(&mphy->dev->mutex);
mt7921_mutex_release(phy->dev);
ieee80211_queue_delayed_work(mphy->hw, &mphy->mac_work,
out:
ieee80211_queue_delayed_work(phy->mt76->hw, &mphy->mac_work,
MT7921_WATCHDOG_TIME);
}
void mt7921_pm_wake_work(struct work_struct *work)
{
struct mt7921_dev *dev;
struct mt76_phy *mphy;
dev = (struct mt7921_dev *)container_of(work, struct mt7921_dev,
pm.wake_work);
mphy = dev->phy.mt76;
if (!mt7921_mcu_drv_pmctrl(dev))
mt76_connac_pm_dequeue_skbs(mphy, &dev->pm);
else
dev_err(mphy->dev->dev, "failed to wake device\n");
ieee80211_wake_queues(mphy->hw);
complete_all(&dev->pm.wake_cmpl);
}
void mt7921_pm_power_save_work(struct work_struct *work)
{
struct mt7921_dev *dev;
unsigned long delta;
dev = (struct mt7921_dev *)container_of(work, struct mt7921_dev,
pm.ps_work.work);
delta = dev->pm.idle_timeout;
if (time_is_after_jiffies(dev->pm.last_activity + delta)) {
delta = dev->pm.last_activity + delta - jiffies;
goto out;
}
if (!mt7921_mcu_fw_pmctrl(dev))
return;
out:
queue_delayed_work(dev->mt76.wq, &dev->pm.ps_work, delta);
}
int mt7921_mac_set_beacon_filter(struct mt7921_phy *phy,
struct ieee80211_vif *vif,
bool enable)
{
struct mt7921_dev *dev = phy->dev;
bool ext_phy = phy != &dev->phy;
int err;
if (!dev->pm.enable)
return -EOPNOTSUPP;
err = mt7921_mcu_set_bss_pm(dev, vif, enable);
if (err)
return err;
if (enable) {
vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER;
mt76_set(dev, MT_WF_RFCR(ext_phy),
MT_WF_RFCR_DROP_OTHER_BEACON);
} else {
vif->driver_flags &= ~IEEE80211_VIF_BEACON_FILTER;
mt76_clear(dev, MT_WF_RFCR(ext_phy),
MT_WF_RFCR_DROP_OTHER_BEACON);
}
return 0;
}
......@@ -896,6 +896,8 @@ static int mt7921_load_firmware(struct mt7921_dev *dev)
dev->mt76.hw->wiphy->wowlan = &mt76_connac_wowlan_support;
#endif /* CONFIG_PM */
clear_bit(MT76_STATE_PM, &dev->mphy.state);
dev_err(dev->mt76.dev, "Firmware init done\n");
return 0;
......@@ -1232,3 +1234,70 @@ int mt7921_mcu_set_bss_pm(struct mt7921_dev *dev, struct ieee80211_vif *vif,
return mt76_mcu_send_msg(&dev->mt76, MCU_CMD_SET_BSS_CONNECTED, &req,
sizeof(req), false);
}
int mt7921_mcu_drv_pmctrl(struct mt7921_dev *dev)
{
struct mt76_phy *mphy = &dev->mt76.phy;
int i;
if (!test_and_clear_bit(MT76_STATE_PM, &mphy->state))
goto out;
for (i = 0; i < MT7921_DRV_OWN_RETRY_COUNT; i++) {
mt76_wr(dev, MT_CONN_ON_LPCTL, PCIE_LPCR_HOST_CLR_OWN);
if (mt76_poll_msec(dev, MT_CONN_ON_LPCTL,
PCIE_LPCR_HOST_OWN_SYNC, 0, 50))
break;
}
if (i == MT7921_DRV_OWN_RETRY_COUNT) {
dev_err(dev->mt76.dev, "driver own failed\n");
return -EIO;
}
out:
dev->pm.last_activity = jiffies;
return 0;
}
int mt7921_mcu_fw_pmctrl(struct mt7921_dev *dev)
{
struct mt76_phy *mphy = &dev->mt76.phy;
int i;
if (test_and_set_bit(MT76_STATE_PM, &mphy->state))
return 0;
for (i = 0; i < MT7921_DRV_OWN_RETRY_COUNT; i++) {
mt76_wr(dev, MT_CONN_ON_LPCTL, PCIE_LPCR_HOST_SET_OWN);
if (mt76_poll_msec(dev, MT_CONN_ON_LPCTL,
PCIE_LPCR_HOST_OWN_SYNC, 4, 50))
break;
}
if (i == MT7921_DRV_OWN_RETRY_COUNT) {
dev_err(dev->mt76.dev, "firmware own failed\n");
return -EIO;
}
return 0;
}
void
mt7921_pm_interface_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
{
struct mt7921_phy *phy = priv;
struct mt7921_dev *dev = phy->dev;
if (mt7921_mcu_set_bss_pm(dev, vif, dev->pm.enable))
return;
if (dev->pm.enable) {
vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER;
mt76_set(dev, MT_WF_RFCR(0), MT_WF_RFCR_DROP_OTHER_BEACON);
} else {
vif->driver_flags &= ~IEEE80211_VIF_BEACON_FILTER;
mt76_clear(dev, MT_WF_RFCR(0), MT_WF_RFCR_DROP_OTHER_BEACON);
}
}
......@@ -6,7 +6,7 @@
#include <linux/interrupt.h>
#include <linux/ktime.h>
#include "../mt76.h"
#include "../mt76_connac.h"
#include "regs.h"
#define MT7921_MAX_INTERFACES 4
......@@ -16,6 +16,7 @@
#define MT7921_WTBL_STA (MT7921_WTBL_RESERVED - \
MT7921_MAX_INTERFACES)
#define MT7921_PM_TIMEOUT (HZ / 12)
#define MT7921_HW_SCAN_TIMEOUT (HZ / 10)
#define MT7921_WATCHDOG_TIME (HZ / 10)
#define MT7921_RESET_TIMEOUT (30 * HZ)
......@@ -27,6 +28,8 @@
#define MT7921_RX_RING_SIZE 1536
#define MT7921_RX_MCU_RING_SIZE 512
#define MT7921_DRV_OWN_RETRY_COUNT 10
#define MT7921_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7961_1.bin"
#define MT7921_ROM_PATCH "mediatek/WIFI_MT7961_patch_mcu_1_2_hdr.bin"
......@@ -159,6 +162,8 @@ struct mt7921_dev {
struct idr token;
u8 fw_debug;
struct mt76_connac_pm pm;
};
enum {
......@@ -187,6 +192,11 @@ mt7921_hw_dev(struct ieee80211_hw *hw)
return container_of(phy->dev, struct mt7921_dev, mt76);
}
#define mt7921_mutex_acquire(dev) \
mt76_connac_mutex_acquire(&(dev)->mt76, &(dev)->pm)
#define mt7921_mutex_release(dev) \
mt76_connac_mutex_release(&(dev)->mt76, &(dev)->pm)
static inline u8 mt7921_lmac_mapping(struct mt7921_dev *dev, u8 ac)
{
/* LMAC uses the reverse order of mac80211 AC indexes */
......@@ -318,4 +328,13 @@ int mt7921_mcu_set_bss_pm(struct mt7921_dev *dev, struct ieee80211_vif *vif,
int mt7921_mcu_update_arp_filter(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info);
int mt7921_mcu_drv_pmctrl(struct mt7921_dev *dev);
int mt7921_mcu_fw_pmctrl(struct mt7921_dev *dev);
void mt7921_pm_wake_work(struct work_struct *work);
void mt7921_pm_power_save_work(struct work_struct *work);
bool mt7921_wait_for_mcu_init(struct mt7921_dev *dev);
int mt7921_mac_set_beacon_filter(struct mt7921_phy *phy,
struct ieee80211_vif *vif,
bool enable);
void mt7921_pm_interface_iter(void *priv, u8 *mac, struct ieee80211_vif *vif);
#endif
......@@ -176,6 +176,10 @@ static int mt7921_pci_suspend(struct pci_dev *pdev, pm_message_t state)
bool hif_suspend;
int i, err;
err = mt76_connac_pm_wake(&dev->mphy, &dev->pm);
if (err < 0)
return err;
hif_suspend = !test_bit(MT76_STATE_SUSPEND, &dev->mphy.state);
if (hif_suspend) {
err = mt76_connac_mcu_set_hif_suspend(mdev, true);
......@@ -210,6 +214,10 @@ static int mt7921_pci_suspend(struct pci_dev *pdev, pm_message_t state)
if (err)
goto restore;
err = mt7921_mcu_drv_pmctrl(dev);
if (err)
goto restore;
return 0;
restore:
......@@ -229,6 +237,10 @@ static int mt7921_pci_resume(struct pci_dev *pdev)
struct mt7921_dev *dev = container_of(mdev, struct mt7921_dev, mt76);
int i, err;
err = mt7921_mcu_fw_pmctrl(dev);
if (err < 0)
return err;
err = pci_set_power_state(pdev, PCI_D0);
if (err)
return err;
......
......@@ -408,6 +408,11 @@
#define MT_DMASHDL_SCHED_SET(_n) MT_DMA_SHDL(0x070 + ((_n) << 2))
#define MT_CONN_ON_LPCTL 0x7c060010
#define PCIE_LPCR_HOST_OWN_SYNC BIT(2)
#define PCIE_LPCR_HOST_CLR_OWN BIT(1)
#define PCIE_LPCR_HOST_SET_OWN BIT(0)
#define MT_CONN_ON_MISC 0x7c0600f0
#define MT_TOP_MISC2_FW_N9_RDY GENMASK(1, 0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment