| // SPDX-License-Identifier: ISC |
| /* Copyright (C) 2020 MediaTek Inc. */ |
| |
| #include "mt76_connac.h" |
| |
| int mt76_connac_pm_wake(struct mt76_phy *phy, struct mt76_connac_pm *pm) |
| { |
| struct mt76_dev *dev = phy->dev; |
| |
| if (mt76_is_usb(dev)) |
| return 0; |
| |
| cancel_delayed_work_sync(&pm->ps_work); |
| if (!test_bit(MT76_STATE_PM, &phy->state)) |
| return 0; |
| |
| if (pm->suspended) |
| return 0; |
| |
| queue_work(dev->wq, &pm->wake_work); |
| if (!wait_event_timeout(pm->wait, |
| !test_bit(MT76_STATE_PM, &phy->state), |
| 3 * HZ)) { |
| ieee80211_wake_queues(phy->hw); |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mt76_connac_pm_wake); |
| |
| void mt76_connac_power_save_sched(struct mt76_phy *phy, |
| struct mt76_connac_pm *pm) |
| { |
| struct mt76_dev *dev = phy->dev; |
| |
| if (mt76_is_usb(dev)) |
| return; |
| |
| if (!pm->enable) |
| return; |
| |
| if (pm->suspended) |
| return; |
| |
| pm->last_activity = jiffies; |
| |
| if (!test_bit(MT76_STATE_PM, &phy->state)) { |
| cancel_delayed_work(&phy->mac_work); |
| queue_delayed_work(dev->wq, &pm->ps_work, pm->idle_timeout); |
| } |
| } |
| EXPORT_SYMBOL_GPL(mt76_connac_power_save_sched); |
| |
| void mt76_connac_free_pending_tx_skbs(struct mt76_connac_pm *pm, |
| struct mt76_wcid *wcid) |
| { |
| int i; |
| |
| spin_lock_bh(&pm->txq_lock); |
| for (i = 0; i < IEEE80211_NUM_ACS; i++) { |
| if (wcid && pm->tx_q[i].wcid != wcid) |
| continue; |
| |
| dev_kfree_skb(pm->tx_q[i].skb); |
| pm->tx_q[i].skb = NULL; |
| } |
| spin_unlock_bh(&pm->txq_lock); |
| } |
| EXPORT_SYMBOL_GPL(mt76_connac_free_pending_tx_skbs); |
| |
| void mt76_connac_pm_queue_skb(struct ieee80211_hw *hw, |
| struct mt76_connac_pm *pm, |
| struct mt76_wcid *wcid, |
| struct sk_buff *skb) |
| { |
| int qid = skb_get_queue_mapping(skb); |
| struct mt76_phy *phy = hw->priv; |
| |
| spin_lock_bh(&pm->txq_lock); |
| if (!pm->tx_q[qid].skb) { |
| ieee80211_stop_queues(hw); |
| pm->tx_q[qid].wcid = wcid; |
| pm->tx_q[qid].skb = skb; |
| queue_work(phy->dev->wq, &pm->wake_work); |
| } else { |
| dev_kfree_skb(skb); |
| } |
| spin_unlock_bh(&pm->txq_lock); |
| } |
| EXPORT_SYMBOL_GPL(mt76_connac_pm_queue_skb); |
| |
| void mt76_connac_pm_dequeue_skbs(struct mt76_phy *phy, |
| struct mt76_connac_pm *pm) |
| { |
| int i; |
| |
| spin_lock_bh(&pm->txq_lock); |
| for (i = 0; i < IEEE80211_NUM_ACS; i++) { |
| struct mt76_wcid *wcid = pm->tx_q[i].wcid; |
| struct ieee80211_sta *sta = NULL; |
| |
| if (!pm->tx_q[i].skb) |
| continue; |
| |
| if (wcid && wcid->sta) |
| sta = container_of((void *)wcid, struct ieee80211_sta, |
| drv_priv); |
| |
| mt76_tx(phy, sta, wcid, pm->tx_q[i].skb); |
| pm->tx_q[i].skb = NULL; |
| } |
| spin_unlock_bh(&pm->txq_lock); |
| |
| mt76_worker_schedule(&phy->dev->tx_worker); |
| } |
| EXPORT_SYMBOL_GPL(mt76_connac_pm_dequeue_skbs); |