ath9k: Add Tx EDMA support
[safe/jmp/linux-2.6] / drivers / net / wireless / ath / ath9k / xmit.c
index f9f7445..c2b4503 100644 (file)
@@ -92,7 +92,6 @@ static int ath_max_4ms_framelen[3][16] = {
        }
 };
 
-
 /*********************/
 /* Aggregation logic */
 /*********************/
@@ -379,7 +378,8 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
                        }
                }
 
-               if (bf_next == NULL) {
+               if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) &&
+                   bf_next == NULL) {
                        /*
                         * Make sure the last desc is reclaimed if it
                         * not a holding desc.
@@ -413,36 +413,43 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
                                !txfail, sendbar);
                } else {
                        /* retry the un-acked ones */
-                       if (bf->bf_next == NULL && bf_last->bf_stale) {
-                               struct ath_buf *tbf;
-
-                               tbf = ath_clone_txbuf(sc, bf_last);
-                               /*
-                                * Update tx baw and complete the frame with
-                                * failed status if we run out of tx buf
-                                */
-                               if (!tbf) {
-                                       spin_lock_bh(&txq->axq_lock);
-                                       ath_tx_update_baw(sc, tid,
-                                                         bf->bf_seqno);
-                                       spin_unlock_bh(&txq->axq_lock);
-
-                                       bf->bf_state.bf_type |= BUF_XRETRY;
-                                       ath_tx_rc_status(bf, ts, nbad,
-                                                        0, false);
-                                       ath_tx_complete_buf(sc, bf, txq,
-                                                           &bf_head, ts, 0, 0);
-                                       break;
+                       if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)) {
+                               if (bf->bf_next == NULL && bf_last->bf_stale) {
+                                       struct ath_buf *tbf;
+
+                                       tbf = ath_clone_txbuf(sc, bf_last);
+                                       /*
+                                        * Update tx baw and complete the
+                                        * frame with failed status if we
+                                        * run out of tx buf.
+                                        */
+                                       if (!tbf) {
+                                               spin_lock_bh(&txq->axq_lock);
+                                               ath_tx_update_baw(sc, tid,
+                                                               bf->bf_seqno);
+                                               spin_unlock_bh(&txq->axq_lock);
+
+                                               bf->bf_state.bf_type |=
+                                                       BUF_XRETRY;
+                                               ath_tx_rc_status(bf, ts, nbad,
+                                                               0, false);
+                                               ath_tx_complete_buf(sc, bf, txq,
+                                                                   &bf_head,
+                                                                   ts, 0, 0);
+                                               break;
+                                       }
+
+                                       ath9k_hw_cleartxdesc(sc->sc_ah,
+                                                            tbf->bf_desc);
+                                       list_add_tail(&tbf->list, &bf_head);
+                               } else {
+                                       /*
+                                        * Clear descriptor status words for
+                                        * software retry
+                                        */
+                                       ath9k_hw_cleartxdesc(sc->sc_ah,
+                                                            bf->bf_desc);
                                }
-
-                               ath9k_hw_cleartxdesc(sc->sc_ah, tbf->bf_desc);
-                               list_add_tail(&tbf->list, &bf_head);
-                       } else {
-                               /*
-                                * Clear descriptor status words for
-                                * software retry
-                                */
-                               ath9k_hw_cleartxdesc(sc->sc_ah, bf->bf_desc);
                        }
 
                        /*
@@ -855,7 +862,7 @@ struct ath_txq *ath_txq_setup(struct ath_softc *sc, int qtype, int subtype)
        struct ath_hw *ah = sc->sc_ah;
        struct ath_common *common = ath9k_hw_common(ah);
        struct ath9k_tx_queue_info qi;
-       int qnum;
+       int qnum, i;
 
        memset(&qi, 0, sizeof(qi));
        qi.tqi_subtype = subtype;
@@ -910,6 +917,11 @@ struct ath_txq *ath_txq_setup(struct ath_softc *sc, int qtype, int subtype)
                txq->axq_depth = 0;
                txq->axq_tx_inprogress = false;
                sc->tx.txqsetup |= 1<<qnum;
+
+               txq->txq_headidx = txq->txq_tailidx = 0;
+               for (i = 0; i < ATH_TXFIFO_DEPTH; i++)
+                       INIT_LIST_HEAD(&txq->txq_fifo[i]);
+               INIT_LIST_HEAD(&txq->txq_fifo_pending);
        }
        return &sc->tx.txq[qnum];
 }
@@ -1042,30 +1054,49 @@ void ath_draintxq(struct ath_softc *sc, struct ath_txq *txq, bool retry_tx)
        for (;;) {
                spin_lock_bh(&txq->axq_lock);
 
-               if (list_empty(&txq->axq_q)) {
-                       txq->axq_link = NULL;
-                       spin_unlock_bh(&txq->axq_lock);
-                       break;
-               }
-
-               bf = list_first_entry(&txq->axq_q, struct ath_buf, list);
+               if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) {
+                       if (list_empty(&txq->txq_fifo[txq->txq_tailidx])) {
+                               txq->txq_headidx = txq->txq_tailidx = 0;
+                               spin_unlock_bh(&txq->axq_lock);
+                               break;
+                       } else {
+                               bf = list_first_entry(&txq->txq_fifo[txq->txq_tailidx],
+                                                     struct ath_buf, list);
+                       }
+               } else {
+                       if (list_empty(&txq->axq_q)) {
+                               txq->axq_link = NULL;
+                               spin_unlock_bh(&txq->axq_lock);
+                               break;
+                       }
+                       bf = list_first_entry(&txq->axq_q, struct ath_buf,
+                                             list);
 
-               if (bf->bf_stale) {
-                       list_del(&bf->list);
-                       spin_unlock_bh(&txq->axq_lock);
+                       if (bf->bf_stale) {
+                               list_del(&bf->list);
+                               spin_unlock_bh(&txq->axq_lock);
 
-                       spin_lock_bh(&sc->tx.txbuflock);
-                       list_add_tail(&bf->list, &sc->tx.txbuf);
-                       spin_unlock_bh(&sc->tx.txbuflock);
-                       continue;
+                               spin_lock_bh(&sc->tx.txbuflock);
+                               list_add_tail(&bf->list, &sc->tx.txbuf);
+                               spin_unlock_bh(&sc->tx.txbuflock);
+                               continue;
+                       }
                }
 
                lastbf = bf->bf_lastbf;
                if (!retry_tx)
                        lastbf->bf_tx_aborted = true;
 
-               /* remove ath_buf's of the same mpdu from txq */
-               list_cut_position(&bf_head, &txq->axq_q, &lastbf->list);
+               if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) {
+                       list_cut_position(&bf_head,
+                                         &txq->txq_fifo[txq->txq_tailidx],
+                                         &lastbf->list);
+                       INCR(txq->txq_tailidx, ATH_TXFIFO_DEPTH);
+               } else {
+                       /* remove ath_buf's of the same mpdu from txq */
+                       list_cut_position(&bf_head, &txq->axq_q, &lastbf->list);
+               }
+
                txq->axq_depth--;
 
                spin_unlock_bh(&txq->axq_lock);
@@ -1088,6 +1119,27 @@ void ath_draintxq(struct ath_softc *sc, struct ath_txq *txq, bool retry_tx)
                        spin_unlock_bh(&txq->axq_lock);
                }
        }
+
+       if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) {
+               spin_lock_bh(&txq->axq_lock);
+               while (!list_empty(&txq->txq_fifo_pending)) {
+                       bf = list_first_entry(&txq->txq_fifo_pending,
+                                             struct ath_buf, list);
+                       list_cut_position(&bf_head,
+                                         &txq->txq_fifo_pending,
+                                         &bf->bf_lastbf->list);
+                       spin_unlock_bh(&txq->axq_lock);
+
+                       if (bf_isampdu(bf))
+                               ath_tx_complete_aggr(sc, txq, bf, &bf_head,
+                                                    &ts, 0);
+                       else
+                               ath_tx_complete_buf(sc, bf, txq, &bf_head,
+                                                   &ts, 0, 0);
+                       spin_lock_bh(&txq->axq_lock);
+               }
+               spin_unlock_bh(&txq->axq_lock);
+       }
 }
 
 void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx)
@@ -1225,25 +1277,47 @@ static void ath_tx_txqaddbuf(struct ath_softc *sc, struct ath_txq *txq,
 
        bf = list_first_entry(head, struct ath_buf, list);
 
-       list_splice_tail_init(head, &txq->axq_q);
-       txq->axq_depth++;
-
        ath_print(common, ATH_DBG_QUEUE,
                  "qnum: %d, txq depth: %d\n", txq->axq_qnum, txq->axq_depth);
 
-       if (txq->axq_link == NULL) {
+       if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) {
+               if (txq->axq_depth >= ATH_TXFIFO_DEPTH) {
+                       list_splice_tail_init(head, &txq->txq_fifo_pending);
+                       return;
+               }
+               if (!list_empty(&txq->txq_fifo[txq->txq_headidx]))
+                       ath_print(common, ATH_DBG_XMIT,
+                                 "Initializing tx fifo %d which "
+                                 "is non-empty\n",
+                                 txq->txq_headidx);
+               INIT_LIST_HEAD(&txq->txq_fifo[txq->txq_headidx]);
+               list_splice_init(head, &txq->txq_fifo[txq->txq_headidx]);
+               INCR(txq->txq_headidx, ATH_TXFIFO_DEPTH);
                ath9k_hw_puttxbuf(ah, txq->axq_qnum, bf->bf_daddr);
                ath_print(common, ATH_DBG_XMIT,
                          "TXDP[%u] = %llx (%p)\n",
                          txq->axq_qnum, ito64(bf->bf_daddr), bf->bf_desc);
        } else {
-               *txq->axq_link = bf->bf_daddr;
-               ath_print(common, ATH_DBG_XMIT, "link[%u] (%p)=%llx (%p)\n",
-                         txq->axq_qnum, txq->axq_link,
-                         ito64(bf->bf_daddr), bf->bf_desc);
+               list_splice_tail_init(head, &txq->axq_q);
+
+               if (txq->axq_link == NULL) {
+                       ath9k_hw_puttxbuf(ah, txq->axq_qnum, bf->bf_daddr);
+                       ath_print(common, ATH_DBG_XMIT,
+                                       "TXDP[%u] = %llx (%p)\n",
+                                       txq->axq_qnum, ito64(bf->bf_daddr),
+                                       bf->bf_desc);
+               } else {
+                       *txq->axq_link = bf->bf_daddr;
+                       ath_print(common, ATH_DBG_XMIT,
+                                       "link[%u] (%p)=%llx (%p)\n",
+                                       txq->axq_qnum, txq->axq_link,
+                                       ito64(bf->bf_daddr), bf->bf_desc);
+               }
+               ath9k_hw_get_desc_link(ah, bf->bf_lastbf->bf_desc,
+                                      &txq->axq_link);
+               ath9k_hw_txstart(ah, txq->axq_qnum);
        }
-       ath9k_hw_get_desc_link(ah, bf->bf_lastbf->bf_desc, &txq->axq_link);
-       ath9k_hw_txstart(ah, txq->axq_qnum);
+       txq->axq_depth++;
 }
 
 static struct ath_buf *ath_tx_get_buffer(struct ath_softc *sc)
@@ -2140,6 +2214,80 @@ void ath_tx_tasklet(struct ath_softc *sc)
        }
 }
 
+void ath_tx_edma_tasklet(struct ath_softc *sc)
+{
+       struct ath_tx_status txs;
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_txq *txq;
+       struct ath_buf *bf, *lastbf;
+       struct list_head bf_head;
+       int status;
+       int txok;
+
+       for (;;) {
+               status = ath9k_hw_txprocdesc(ah, NULL, (void *)&txs);
+               if (status == -EINPROGRESS)
+                       break;
+               if (status == -EIO) {
+                       ath_print(common, ATH_DBG_XMIT,
+                                 "Error processing tx status\n");
+                       break;
+               }
+
+               /* Skip beacon completions */
+               if (txs.qid == sc->beacon.beaconq)
+                       continue;
+
+               txq = &sc->tx.txq[txs.qid];
+
+               spin_lock_bh(&txq->axq_lock);
+               if (list_empty(&txq->txq_fifo[txq->txq_tailidx])) {
+                       spin_unlock_bh(&txq->axq_lock);
+                       return;
+               }
+
+               bf = list_first_entry(&txq->txq_fifo[txq->txq_tailidx],
+                                     struct ath_buf, list);
+               lastbf = bf->bf_lastbf;
+
+               INIT_LIST_HEAD(&bf_head);
+               list_cut_position(&bf_head, &txq->txq_fifo[txq->txq_tailidx],
+                                 &lastbf->list);
+               INCR(txq->txq_tailidx, ATH_TXFIFO_DEPTH);
+               txq->axq_depth--;
+               txq->axq_tx_inprogress = false;
+               spin_unlock_bh(&txq->axq_lock);
+
+               txok = !(txs.ts_status & ATH9K_TXERR_MASK);
+
+               if (!bf_isampdu(bf)) {
+                       bf->bf_retries = txs.ts_longretry;
+                       if (txs.ts_status & ATH9K_TXERR_XRETRY)
+                               bf->bf_state.bf_type |= BUF_XRETRY;
+                       ath_tx_rc_status(bf, &txs, 0, txok, true);
+               }
+
+               if (bf_isampdu(bf))
+                       ath_tx_complete_aggr(sc, txq, bf, &bf_head, &txs, txok);
+               else
+                       ath_tx_complete_buf(sc, bf, txq, &bf_head,
+                                           &txs, txok, 0);
+
+               spin_lock_bh(&txq->axq_lock);
+               if (!list_empty(&txq->txq_fifo_pending)) {
+                       INIT_LIST_HEAD(&bf_head);
+                       bf = list_first_entry(&txq->txq_fifo_pending,
+                               struct ath_buf, list);
+                       list_cut_position(&bf_head, &txq->txq_fifo_pending,
+                               &bf->bf_lastbf->list);
+                       ath_tx_txqaddbuf(sc, txq, &bf_head);
+               } else if (sc->sc_flags & SC_OP_TXAGGR)
+                       ath_txq_schedule(sc, txq);
+               spin_unlock_bh(&txq->axq_lock);
+       }
+}
+
 /*****************/
 /* Init, Cleanup */
 /*****************/