#define HTT_RX_RING_REFILL_RESCHED_MS 5
 
 static int ath10k_htt_rx_get_csum_state(struct sk_buff *skb);
-static void ath10k_htt_txrx_compl_task(unsigned long ptr);
 
 static struct sk_buff *
 ath10k_htt_rx_find_skb_paddr(struct ath10k *ar, u32 paddr)
 void ath10k_htt_rx_free(struct ath10k_htt *htt)
 {
        del_timer_sync(&htt->rx_ring.refill_retry_timer);
-       tasklet_kill(&htt->txrx_compl_task);
 
        skb_queue_purge(&htt->rx_compl_q);
        skb_queue_purge(&htt->rx_in_ord_compl_q);
        skb_queue_head_init(&htt->tx_fetch_ind_q);
        atomic_set(&htt->num_mpdus_ready, 0);
 
-       tasklet_init(&htt->txrx_compl_task, ath10k_htt_txrx_compl_task,
-                    (unsigned long)htt);
-
        ath10k_dbg(ar, ATH10K_DBG_BOOT, "htt rx ring size %d fill_level %d\n",
                   htt->rx_ring.size, htt->rx_ring.fill_level);
        return 0;
        trace_ath10k_rx_hdr(ar, skb->data, skb->len);
        trace_ath10k_rx_payload(ar, skb->data, skb->len);
 
-       ieee80211_rx(ar->hw, skb);
+       ieee80211_rx_napi(ar->hw, NULL, skb, &ar->napi);
 }
 
 static int ath10k_htt_rx_nwifi_hdrlen(struct ath10k *ar,
        struct ath10k *ar = htt->ar;
        static struct ieee80211_rx_status rx_status;
        struct sk_buff_head amsdu;
-       int ret;
+       int ret, num_msdus;
 
        __skb_queue_head_init(&amsdu);
 
                return ret;
        }
 
+       num_msdus = skb_queue_len(&amsdu);
        ath10k_htt_rx_h_ppdu(ar, &amsdu, &rx_status, 0xffff);
        ath10k_htt_rx_h_unchain(ar, &amsdu, ret > 0);
        ath10k_htt_rx_h_filter(ar, &amsdu, &rx_status);
        ath10k_htt_rx_h_mpdu(ar, &amsdu, &rx_status);
        ath10k_htt_rx_h_deliver(ar, &amsdu, &rx_status);
 
-       return 0;
+       return num_msdus;
 }
 
 static void ath10k_htt_rx_proc_rx_ind(struct ath10k_htt *htt,
                mpdu_count += mpdu_ranges[i].mpdu_count;
 
        atomic_add(mpdu_count, &htt->num_mpdus_ready);
-
-       tasklet_schedule(&htt->txrx_compl_task);
-}
-
-static void ath10k_htt_rx_frag_handler(struct ath10k_htt *htt)
-{
-       atomic_inc(&htt->num_mpdus_ready);
-
-       tasklet_schedule(&htt->txrx_compl_task);
 }
 
 static void ath10k_htt_rx_tx_compl_ind(struct ath10k *ar,
                        RX_FLAG_MMIC_STRIPPED;
 }
 
-static void ath10k_htt_rx_h_rx_offload(struct ath10k *ar,
-                                      struct sk_buff_head *list)
+static int ath10k_htt_rx_h_rx_offload(struct ath10k *ar,
+                                     struct sk_buff_head *list)
 {
        struct ath10k_htt *htt = &ar->htt;
        struct ieee80211_rx_status *status = &htt->rx_status;
        struct htt_rx_offload_msdu *rx;
        struct sk_buff *msdu;
        size_t offset;
+       int num_msdu = 0;
 
        while ((msdu = __skb_dequeue(list))) {
                /* Offloaded frames don't have Rx descriptor. Instead they have
                ath10k_htt_rx_h_rx_offload_prot(status, msdu);
                ath10k_htt_rx_h_channel(ar, status, NULL, rx->vdev_id);
                ath10k_process_rx(ar, status, msdu);
+               num_msdu++;
        }
+       return num_msdu;
 }
 
-static void ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb)
+static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb)
 {
        struct ath10k_htt *htt = &ar->htt;
        struct htt_resp *resp = (void *)skb->data;
        u8 tid;
        bool offload;
        bool frag;
-       int ret;
+       int ret, num_msdus = 0;
 
        lockdep_assert_held(&htt->rx_ring.lock);
 
        if (htt->rx_confused)
-               return;
+               return -EIO;
 
        skb_pull(skb, sizeof(resp->hdr));
        skb_pull(skb, sizeof(resp->rx_in_ord_ind));
 
        if (skb->len < msdu_count * sizeof(*resp->rx_in_ord_ind.msdu_descs)) {
                ath10k_warn(ar, "dropping invalid in order rx indication\n");
-               return;
+               return -EINVAL;
        }
 
        /* The event can deliver more than 1 A-MSDU. Each A-MSDU is later
        if (ret < 0) {
                ath10k_warn(ar, "failed to pop paddr list: %d\n", ret);
                htt->rx_confused = true;
-               return;
+               return -EIO;
        }
 
        /* Offloaded frames are very different and need to be handled
         * separately.
         */
        if (offload)
-               ath10k_htt_rx_h_rx_offload(ar, &list);
+               num_msdus = ath10k_htt_rx_h_rx_offload(ar, &list);
 
        while (!skb_queue_empty(&list)) {
                __skb_queue_head_init(&amsdu);
                         * better to report something than nothing though. This
                         * should still give an idea about rx rate to the user.
                         */
+                       num_msdus += skb_queue_len(&amsdu);
                        ath10k_htt_rx_h_ppdu(ar, &amsdu, status, vdev_id);
                        ath10k_htt_rx_h_filter(ar, &amsdu, status);
                        ath10k_htt_rx_h_mpdu(ar, &amsdu, status);
                        ath10k_warn(ar, "failed to extract amsdu: %d\n", ret);
                        htt->rx_confused = true;
                        __skb_queue_purge(&list);
-                       return;
+                       return -EIO;
                }
        }
+       return num_msdus;
 }
 
 static void ath10k_htt_rx_tx_fetch_resp_id_confirm(struct ath10k *ar,
        }
        case HTT_T2H_MSG_TYPE_TX_COMPL_IND:
                ath10k_htt_rx_tx_compl_ind(htt->ar, skb);
-               tasklet_schedule(&htt->txrx_compl_task);
                break;
        case HTT_T2H_MSG_TYPE_SEC_IND: {
                struct ath10k *ar = htt->ar;
        case HTT_T2H_MSG_TYPE_RX_FRAG_IND: {
                ath10k_dbg_dump(ar, ATH10K_DBG_HTT_DUMP, NULL, "htt event: ",
                                skb->data, skb->len);
-               ath10k_htt_rx_frag_handler(htt);
+               atomic_inc(&htt->num_mpdus_ready);
                break;
        }
        case HTT_T2H_MSG_TYPE_TEST:
                break;
        }
        case HTT_T2H_MSG_TYPE_RX_IN_ORD_PADDR_IND: {
-               skb_queue_tail(&htt->rx_in_ord_compl_q, skb);
-               tasklet_schedule(&htt->txrx_compl_task);
+               __skb_queue_tail(&htt->rx_in_ord_compl_q, skb);
                return false;
        }
        case HTT_T2H_MSG_TYPE_TX_CREDIT_UPDATE_IND:
                        break;
                }
                skb_queue_tail(&htt->tx_fetch_ind_q, tx_fetch_ind);
-               tasklet_schedule(&htt->txrx_compl_task);
                break;
        }
        case HTT_T2H_MSG_TYPE_TX_FETCH_CONFIRM:
 }
 EXPORT_SYMBOL(ath10k_htt_rx_pktlog_completion_handler);
 
-static void ath10k_htt_txrx_compl_task(unsigned long ptr)
+int ath10k_htt_txrx_compl_task(struct ath10k *ar, int budget)
 {
-       struct ath10k_htt *htt = (struct ath10k_htt *)ptr;
-       struct ath10k *ar = htt->ar;
+       struct ath10k_htt *htt = &ar->htt;
        struct htt_tx_done tx_done = {};
-       struct sk_buff_head rx_ind_q;
        struct sk_buff_head tx_ind_q;
        struct sk_buff *skb;
        unsigned long flags;
-       int num_mpdus;
+       int quota = 0, done, num_rx_msdus;
+       bool resched_napi = false;
 
-       __skb_queue_head_init(&rx_ind_q);
        __skb_queue_head_init(&tx_ind_q);
 
-       spin_lock_irqsave(&htt->rx_in_ord_compl_q.lock, flags);
-       skb_queue_splice_init(&htt->rx_in_ord_compl_q, &rx_ind_q);
-       spin_unlock_irqrestore(&htt->rx_in_ord_compl_q.lock, flags);
+       /* Since in-ord-ind can deliver more than 1 A-MSDU in single event,
+        * process it first to utilize full available quota.
+        */
+       while (quota < budget) {
+               if (skb_queue_empty(&htt->rx_in_ord_compl_q))
+                       break;
 
-       spin_lock_irqsave(&htt->tx_fetch_ind_q.lock, flags);
-       skb_queue_splice_init(&htt->tx_fetch_ind_q, &tx_ind_q);
-       spin_unlock_irqrestore(&htt->tx_fetch_ind_q.lock, flags);
+               skb = __skb_dequeue(&htt->rx_in_ord_compl_q);
+               if (!skb) {
+                       resched_napi = true;
+                       goto exit;
+               }
+
+               spin_lock_bh(&htt->rx_ring.lock);
+               num_rx_msdus = ath10k_htt_rx_in_ord_ind(ar, skb);
+               spin_unlock_bh(&htt->rx_ring.lock);
+               if (num_rx_msdus < 0) {
+                       resched_napi = true;
+                       goto exit;
+               }
+
+               dev_kfree_skb_any(skb);
+               if (num_rx_msdus > 0)
+                       quota += num_rx_msdus;
+
+               if ((quota > ATH10K_NAPI_QUOTA_LIMIT) &&
+                   !skb_queue_empty(&htt->rx_in_ord_compl_q)) {
+                       resched_napi = true;
+                       goto exit;
+               }
+       }
+
+       while (quota < budget) {
+               /* no more data to receive */
+               if (!atomic_read(&htt->num_mpdus_ready))
+                       break;
+
+               num_rx_msdus = ath10k_htt_rx_handle_amsdu(htt);
+               if (num_rx_msdus < 0) {
+                       resched_napi = true;
+                       goto exit;
+               }
+
+               quota += num_rx_msdus;
+               atomic_dec(&htt->num_mpdus_ready);
+               if ((quota > ATH10K_NAPI_QUOTA_LIMIT) &&
+                   atomic_read(&htt->num_mpdus_ready)) {
+                       resched_napi = true;
+                       goto exit;
+               }
+       }
+
+       /* From NAPI documentation:
+        *  The napi poll() function may also process TX completions, in which
+        *  case if it processes the entire TX ring then it should count that
+        *  work as the rest of the budget.
+        */
+       if ((quota < budget) && !kfifo_is_empty(&htt->txdone_fifo))
+               quota = budget;
 
        /* kfifo_get: called only within txrx_tasklet so it's neatly serialized.
         * From kfifo_get() documentation:
        while (kfifo_get(&htt->txdone_fifo, &tx_done))
                ath10k_txrx_tx_unref(htt, &tx_done);
 
+       spin_lock_irqsave(&htt->tx_fetch_ind_q.lock, flags);
+       skb_queue_splice_init(&htt->tx_fetch_ind_q, &tx_ind_q);
+       spin_unlock_irqrestore(&htt->tx_fetch_ind_q.lock, flags);
+
        while ((skb = __skb_dequeue(&tx_ind_q))) {
                ath10k_htt_rx_tx_fetch_ind(ar, skb);
                dev_kfree_skb_any(skb);
        }
 
-       num_mpdus = atomic_read(&htt->num_mpdus_ready);
-
-       while (num_mpdus) {
-               if (ath10k_htt_rx_handle_amsdu(htt))
-                       break;
-
-               num_mpdus--;
-               atomic_dec(&htt->num_mpdus_ready);
-       }
-
-       while ((skb = __skb_dequeue(&rx_ind_q))) {
-               spin_lock_bh(&htt->rx_ring.lock);
-               ath10k_htt_rx_in_ord_ind(ar, skb);
-               spin_unlock_bh(&htt->rx_ring.lock);
-               dev_kfree_skb_any(skb);
-       }
-
+exit:
        ath10k_htt_rx_msdu_buff_replenish(htt);
+       /* In case of rx failure or more data to read, report budget
+        * to reschedule NAPI poll
+        */
+       done = resched_napi ? budget : quota;
+
+       return done;
 }
+EXPORT_SYMBOL(ath10k_htt_txrx_compl_task);
 
        ath10k_ce_per_engine_service(ar, pipe);
 }
 
-void ath10k_pci_kill_tasklet(struct ath10k *ar)
+static void ath10k_pci_rx_retry_sync(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
 
-       tasklet_kill(&ar_pci->intr_tq);
-
        del_timer_sync(&ar_pci->rx_post_retry);
 }
 
                                                 ul_pipe, dl_pipe);
 }
 
-static void ath10k_pci_irq_msi_fw_mask(struct ath10k *ar)
+void ath10k_pci_irq_msi_fw_mask(struct ath10k *ar)
 {
        u32 val;
 
 
 void ath10k_pci_flush(struct ath10k *ar)
 {
-       ath10k_pci_kill_tasklet(ar);
+       ath10k_pci_rx_retry_sync(ar);
        ath10k_pci_buffer_cleanup(ar);
 }
 
        ath10k_pci_irq_disable(ar);
        ath10k_pci_irq_sync(ar);
        ath10k_pci_flush(ar);
+       napi_synchronize(&ar->napi);
+       napi_disable(&ar->napi);
 
        spin_lock_irqsave(&ar_pci->ps_lock, flags);
        WARN_ON(ar_pci->ps_wake_refcount > 0);
                ath10k_err(ar, "could not wake up target CPU: %d\n", ret);
                goto err_ce;
        }
+       napi_enable(&ar->napi);
 
        return 0;
 
                return IRQ_NONE;
        }
 
-       if (ar_pci->oper_irq_mode == ATH10K_PCI_IRQ_LEGACY) {
-               if (!ath10k_pci_irq_pending(ar))
-                       return IRQ_NONE;
-
-               ath10k_pci_disable_and_clear_legacy_irq(ar);
-       }
+       if ((ar_pci->oper_irq_mode == ATH10K_PCI_IRQ_LEGACY) &&
+           !ath10k_pci_irq_pending(ar))
+               return IRQ_NONE;
 
-       tasklet_schedule(&ar_pci->intr_tq);
+       ath10k_pci_disable_and_clear_legacy_irq(ar);
+       ath10k_pci_irq_msi_fw_mask(ar);
+       napi_schedule(&ar->napi);
 
        return IRQ_HANDLED;
 }
 
-static void ath10k_pci_tasklet(unsigned long data)
+static int ath10k_pci_napi_poll(struct napi_struct *ctx, int budget)
 {
-       struct ath10k *ar = (struct ath10k *)data;
-       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       struct ath10k *ar = container_of(ctx, struct ath10k, napi);
+       int done = 0;
 
        if (ath10k_pci_has_fw_crashed(ar)) {
-               ath10k_pci_irq_disable(ar);
                ath10k_pci_fw_crashed_clear(ar);
                ath10k_pci_fw_crashed_dump(ar);
-               return;
+               napi_complete(ctx);
+               return done;
        }
 
        ath10k_ce_per_engine_service_any(ar);
 
-       /* Re-enable legacy irq that was disabled in the irq handler */
-       if (ar_pci->oper_irq_mode == ATH10K_PCI_IRQ_LEGACY)
+       done = ath10k_htt_txrx_compl_task(ar, budget);
+
+       if (done < budget) {
+               napi_complete(ctx);
+               /* In case of MSI, it is possible that interrupts are received
+                * while NAPI poll is inprogress. So pending interrupts that are
+                * received after processing all copy engine pipes by NAPI poll
+                * will not be handled again. This is causing failure to
+                * complete boot sequence in x86 platform. So before enabling
+                * interrupts safer to check for pending interrupts for
+                * immediate servicing.
+                */
+               if (CE_INTERRUPT_SUMMARY(ar)) {
+                       napi_reschedule(ctx);
+                       goto out;
+               }
                ath10k_pci_enable_legacy_irq(ar);
+               ath10k_pci_irq_msi_fw_unmask(ar);
+       }
+
+out:
+       return done;
 }
 
 static int ath10k_pci_request_irq_msi(struct ath10k *ar)
        free_irq(ar_pci->pdev->irq, ar);
 }
 
-void ath10k_pci_init_irq_tasklets(struct ath10k *ar)
+void ath10k_pci_init_napi(struct ath10k *ar)
 {
-       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-
-       tasklet_init(&ar_pci->intr_tq, ath10k_pci_tasklet, (unsigned long)ar);
+       netif_napi_add(&ar->napi_dev, &ar->napi, ath10k_pci_napi_poll,
+                      ATH10K_NAPI_BUDGET);
 }
 
 static int ath10k_pci_init_irq(struct ath10k *ar)
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
        int ret;
 
-       ath10k_pci_init_irq_tasklets(ar);
+       ath10k_pci_init_napi(ar);
 
        if (ath10k_pci_irq_mode != ATH10K_PCI_IRQ_AUTO)
                ath10k_info(ar, "limiting irq mode to: %d\n",
 
 void ath10k_pci_release_resource(struct ath10k *ar)
 {
-       ath10k_pci_kill_tasklet(ar);
+       ath10k_pci_rx_retry_sync(ar);
+       netif_napi_del(&ar->napi);
        ath10k_pci_ce_deinit(ar);
        ath10k_pci_free_pipes(ar);
 }
 
 err_free_irq:
        ath10k_pci_free_irq(ar);
-       ath10k_pci_kill_tasklet(ar);
+       ath10k_pci_rx_retry_sync(ar);
 
 err_deinit_irq:
        ath10k_pci_deinit_irq(ar);