struct sk_buff *skb)
 {
        struct rtw_dev *rtwdev = hw->priv;
-       struct rtw_tx_pkt_info pkt_info = {0};
 
-       if (!test_bit(RTW_FLAG_RUNNING, rtwdev->flags))
-               goto out;
+       if (!test_bit(RTW_FLAG_RUNNING, rtwdev->flags)) {
+               ieee80211_free_txskb(hw, skb);
+               return;
+       }
 
-       rtw_tx_pkt_info_update(rtwdev, &pkt_info, control, skb);
-       if (rtw_hci_tx(rtwdev, &pkt_info, skb))
-               goto out;
+       rtw_tx(rtwdev, control, skb);
+}
 
-       return;
+static void rtw_ops_wake_tx_queue(struct ieee80211_hw *hw,
+                                 struct ieee80211_txq *txq)
+{
+       struct rtw_dev *rtwdev = hw->priv;
+       struct rtw_txq *rtwtxq = (struct rtw_txq *)txq->drv_priv;
 
-out:
-       ieee80211_free_txskb(hw, skb);
+       if (!test_bit(RTW_FLAG_RUNNING, rtwdev->flags))
+               return;
+
+       spin_lock_bh(&rtwdev->txq_lock);
+       if (list_empty(&rtwtxq->list))
+               list_add_tail(&rtwtxq->list, &rtwdev->txqs);
+       spin_unlock_bh(&rtwdev->txq_lock);
+
+       tasklet_schedule(&rtwdev->tx_tasklet);
 }
 
 static int rtw_ops_start(struct ieee80211_hw *hw)
        rtwvif->stats.rx_cnt = 0;
        rtwvif->in_lps = false;
        rtwvif->conf = &rtw_vif_port[port];
+       rtw_txq_init(rtwdev, vif->txq);
 
        mutex_lock(&rtwdev->mutex);
 
 
        rtw_leave_lps_deep(rtwdev);
 
+       rtw_txq_cleanup(rtwdev, vif->txq);
+
        eth_zero_addr(rtwvif->mac_addr);
        config |= PORT_SET_MAC_ADDR;
        rtwvif->net_type = RTW_NET_NO_LINK;
 {
        struct rtw_dev *rtwdev = hw->priv;
        struct rtw_sta_info *si = (struct rtw_sta_info *)sta->drv_priv;
+       int i;
        int ret = 0;
 
        mutex_lock(&rtwdev->mutex);
        si->vif = vif;
        si->init_ra_lv = 1;
        ewma_rssi_init(&si->avg_rssi);
+       for (i = 0; i < ARRAY_SIZE(sta->txq); i++)
+               rtw_txq_init(rtwdev, sta->txq[i]);
 
        rtw_update_sta_info(rtwdev, si);
        rtw_fw_media_status_report(rtwdev, si->mac_id, true);
 {
        struct rtw_dev *rtwdev = hw->priv;
        struct rtw_sta_info *si = (struct rtw_sta_info *)sta->drv_priv;
+       int i;
 
        mutex_lock(&rtwdev->mutex);
 
        rtw_release_macid(rtwdev, si->mac_id);
        rtw_fw_media_status_report(rtwdev, si->mac_id, false);
 
+       for (i = 0; i < ARRAY_SIZE(sta->txq); i++)
+               rtw_txq_cleanup(rtwdev, sta->txq[i]);
+
        rtwdev->sta_cnt--;
 
        rtw_info(rtwdev, "sta %pM with macid %d left\n",
 
 const struct ieee80211_ops rtw_ops = {
        .tx                     = rtw_ops_tx,
+       .wake_tx_queue          = rtw_ops_wake_tx_queue,
        .start                  = rtw_ops_start,
        .stop                   = rtw_ops_stop,
        .config                 = rtw_ops_config,
 
 #include "phy.h"
 #include "reg.h"
 #include "efuse.h"
+#include "tx.h"
 #include "debug.h"
 
 unsigned int rtw_fw_lps_deep_mode;
        int ret;
 
        INIT_LIST_HEAD(&rtwdev->rsvd_page_list);
+       INIT_LIST_HEAD(&rtwdev->txqs);
 
        timer_setup(&rtwdev->tx_report.purge_timer,
                    rtw_tx_report_purge_timer, 0);
+       tasklet_init(&rtwdev->tx_tasklet, rtw_tx_tasklet,
+                    (unsigned long)rtwdev);
 
        INIT_DELAYED_WORK(&rtwdev->watch_dog_work, rtw_watch_dog_work);
        INIT_DELAYED_WORK(&coex->bt_relink_work, rtw_coex_bt_relink_work);
        spin_lock_init(&rtwdev->dm_lock);
        spin_lock_init(&rtwdev->rf_lock);
        spin_lock_init(&rtwdev->h2c.lock);
+       spin_lock_init(&rtwdev->txq_lock);
        spin_lock_init(&rtwdev->tx_report.q_lock);
 
        mutex_init(&rtwdev->mutex);
        if (fw->firmware)
                release_firmware(fw->firmware);
 
+       tasklet_kill(&rtwdev->tx_tasklet);
        spin_lock_irqsave(&rtwdev->tx_report.q_lock, flags);
        skb_queue_purge(&rtwdev->tx_report.queue);
        spin_unlock_irqrestore(&rtwdev->tx_report.q_lock, flags);
 
        hw->extra_tx_headroom = max_tx_headroom;
        hw->queues = IEEE80211_NUM_ACS;
+       hw->txq_data_size = sizeof(struct rtw_txq);
        hw->sta_data_size = sizeof(struct rtw_sta_info);
        hw->vif_data_size = sizeof(struct rtw_vif);
 
 
        struct timer_list purge_timer;
 };
 
+struct rtw_txq {
+       struct list_head list;
+       unsigned long last_push;
+};
+
 #define RTW_BC_MC_MACID 1
 DECLARE_EWMA(rssi, 10, 16);
 
        struct sk_buff_head c2h_queue;
        struct work_struct c2h_work;
 
+       /* used to protect txqs list */
+       spinlock_t txq_lock;
+       struct list_head txqs;
+       struct tasklet_struct tx_tasklet;
+
        struct rtw_tx_report tx_report;
 
        struct {
        return !!rtwdev->sta_cnt;
 }
 
+static inline struct ieee80211_txq *rtwtxq_to_txq(struct rtw_txq *rtwtxq)
+{
+       void *p = rtwtxq;
+
+       return container_of(p, struct ieee80211_txq, drv_priv);
+}
+
 void rtw_get_channel_params(struct cfg80211_chan_def *chandef,
                            struct rtw_channel_params *ch_param);
 bool check_hw_ready(struct rtw_dev *rtwdev, u32 addr, u32 mask, u32 target);
 
        pkt_info->qsel = TX_DESC_QSEL_MGMT;
        pkt_info->ls = true;
 }
+
+void rtw_tx(struct rtw_dev *rtwdev,
+           struct ieee80211_tx_control *control,
+           struct sk_buff *skb)
+{
+       struct rtw_tx_pkt_info pkt_info = {0};
+
+       rtw_tx_pkt_info_update(rtwdev, &pkt_info, control, skb);
+       if (rtw_hci_tx(rtwdev, &pkt_info, skb))
+               goto out;
+
+       return;
+
+out:
+       ieee80211_free_txskb(rtwdev->hw, skb);
+}
+
+static bool rtw_txq_dequeue(struct rtw_dev *rtwdev,
+                           struct rtw_txq *rtwtxq)
+{
+       struct ieee80211_txq *txq = rtwtxq_to_txq(rtwtxq);
+       struct ieee80211_tx_control control;
+       struct sk_buff *skb;
+
+       skb = ieee80211_tx_dequeue(rtwdev->hw, txq);
+       if (!skb)
+               return false;
+
+       control.sta = txq->sta;
+       rtw_tx(rtwdev, &control, skb);
+       rtwtxq->last_push = jiffies;
+
+       return true;
+}
+
+static void rtw_txq_push(struct rtw_dev *rtwdev,
+                        struct rtw_txq *rtwtxq,
+                        unsigned long frames)
+{
+       int i;
+
+       rcu_read_lock();
+
+       for (i = 0; i < frames; i++)
+               if (!rtw_txq_dequeue(rtwdev, rtwtxq))
+                       break;
+
+       rcu_read_unlock();
+}
+
+void rtw_tx_tasklet(unsigned long data)
+{
+       struct rtw_dev *rtwdev = (void *)data;
+       struct rtw_txq *rtwtxq, *tmp;
+
+       spin_lock_bh(&rtwdev->txq_lock);
+
+       list_for_each_entry_safe(rtwtxq, tmp, &rtwdev->txqs, list) {
+               struct ieee80211_txq *txq = rtwtxq_to_txq(rtwtxq);
+               unsigned long frame_cnt;
+               unsigned long byte_cnt;
+
+               ieee80211_txq_get_depth(txq, &frame_cnt, &byte_cnt);
+               rtw_txq_push(rtwdev, rtwtxq, frame_cnt);
+
+               list_del_init(&rtwtxq->list);
+       }
+
+       spin_unlock_bh(&rtwdev->txq_lock);
+}
+
+void rtw_txq_init(struct rtw_dev *rtwdev, struct ieee80211_txq *txq)
+{
+       struct rtw_txq *rtwtxq;
+
+       if (!txq)
+               return;
+
+       rtwtxq = (struct rtw_txq *)txq->drv_priv;
+       INIT_LIST_HEAD(&rtwtxq->list);
+}
+
+void rtw_txq_cleanup(struct rtw_dev *rtwdev, struct ieee80211_txq *txq)
+{
+       struct rtw_txq *rtwtxq;
+
+       if (!txq)
+               return;
+
+       rtwtxq = (struct rtw_txq *)txq->drv_priv;
+       spin_lock_bh(&rtwdev->txq_lock);
+       if (!list_empty(&rtwtxq->list))
+               list_del_init(&rtwtxq->list);
+       spin_unlock_bh(&rtwdev->txq_lock);
+}
 
        TX_DESC_QSEL_H2C        = 19,
 };
 
+void rtw_tx(struct rtw_dev *rtwdev,
+           struct ieee80211_tx_control *control,
+           struct sk_buff *skb);
+void rtw_txq_init(struct rtw_dev *rtwdev, struct ieee80211_txq *txq);
+void rtw_txq_cleanup(struct rtw_dev *rtwdev, struct ieee80211_txq *txq);
+void rtw_tx_tasklet(unsigned long data);
 void rtw_tx_pkt_info_update(struct rtw_dev *rtwdev,
                            struct rtw_tx_pkt_info *pkt_info,
                            struct ieee80211_tx_control *control,