]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/net/wireless/ath/ath5k/dma.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / net / wireless / ath / ath5k / dma.c
index 923c9ca5c4f0fe424186ff978839eca80c9c2617..21091c26a9a57a68629f702be685ebe1ee7825c8 100644 (file)
@@ -37,6 +37,7 @@
 #include "debug.h"
 #include "base.h"
 
+
 /*********\
 * Receive *
 \*********/
@@ -57,7 +58,7 @@ void ath5k_hw_start_rx_dma(struct ath5k_hw *ah)
  *
  * @ah:        The &struct ath5k_hw
  */
-int ath5k_hw_stop_rx_dma(struct ath5k_hw *ah)
+static int ath5k_hw_stop_rx_dma(struct ath5k_hw *ah)
 {
        unsigned int i;
 
@@ -69,7 +70,11 @@ int ath5k_hw_stop_rx_dma(struct ath5k_hw *ah)
        for (i = 1000; i > 0 &&
                        (ath5k_hw_reg_read(ah, AR5K_CR) & AR5K_CR_RXE) != 0;
                        i--)
-               udelay(10);
+               udelay(100);
+
+       if (!i)
+               ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_DMA,
+                               "failed to stop RX DMA !\n");
 
        return i ? 0 : -EBUSY;
 }
@@ -90,11 +95,18 @@ u32 ath5k_hw_get_rxdp(struct ath5k_hw *ah)
  * @ah: The &struct ath5k_hw
  * @phys_addr: RX descriptor address
  *
- * XXX: Should we check if rx is enabled before setting rxdp ?
+ * Returns -EIO if rx is active
  */
-void ath5k_hw_set_rxdp(struct ath5k_hw *ah, u32 phys_addr)
+int ath5k_hw_set_rxdp(struct ath5k_hw *ah, u32 phys_addr)
 {
+       if (ath5k_hw_reg_read(ah, AR5K_CR) & AR5K_CR_RXE) {
+               ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_DMA,
+                               "tried to set RXDP while rx was active !\n");
+               return -EIO;
+       }
+
        ath5k_hw_reg_write(ah, phys_addr, AR5K_RXDP);
+       return 0;
 }
 
 
@@ -125,7 +137,7 @@ int ath5k_hw_start_tx_dma(struct ath5k_hw *ah, unsigned int queue)
 
        /* Return if queue is declared inactive */
        if (ah->ah_txq[queue].tqi_type == AR5K_TX_QUEUE_INACTIVE)
-               return -EIO;
+               return -EINVAL;
 
        if (ah->ah_version == AR5K_AR5210) {
                tx_queue = ath5k_hw_reg_read(ah, AR5K_CR);
@@ -173,10 +185,10 @@ int ath5k_hw_start_tx_dma(struct ath5k_hw *ah, unsigned int queue)
  *
  * Stop DMA transmit on a specific hw queue and drain queue so we don't
  * have any pending frames. Returns -EBUSY if we still have pending frames,
- * -EINVAL if queue number is out of range.
+ * -EINVAL if queue number is out of range or inactive.
  *
  */
-int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue)
+static int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue)
 {
        unsigned int i = 40;
        u32 tx_queue, pending;
@@ -185,7 +197,7 @@ int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue)
 
        /* Return if queue is declared inactive */
        if (ah->ah_txq[queue].tqi_type == AR5K_TX_QUEUE_INACTIVE)
-               return -EIO;
+               return -EINVAL;
 
        if (ah->ah_version == AR5K_AR5210) {
                tx_queue = ath5k_hw_reg_read(ah, AR5K_CR);
@@ -211,12 +223,31 @@ int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue)
                ath5k_hw_reg_write(ah, tx_queue, AR5K_CR);
                ath5k_hw_reg_read(ah, AR5K_CR);
        } else {
+
+               /*
+                * Enable DCU early termination to quickly
+                * flush any pending frames from QCU
+                */
+               AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue),
+                                       AR5K_QCU_MISC_DCU_EARLY);
+
                /*
                 * Schedule TX disable and wait until queue is empty
                 */
                AR5K_REG_WRITE_Q(ah, AR5K_QCU_TXD, queue);
 
-               /*Check for pending frames*/
+               /* Wait for queue to stop */
+               for (i = 1000; i > 0 &&
+               (AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue) != 0);
+               i--)
+                       udelay(100);
+
+               if (AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue))
+                       ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_DMA,
+                               "queue %i didn't stop !\n", queue);
+
+               /* Check for pending frames */
+               i = 1000;
                do {
                        pending = ath5k_hw_reg_read(ah,
                                AR5K_QUEUE_STATUS(queue)) &
@@ -247,12 +278,12 @@ int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue)
                                        AR5K_DIAG_SW_CHANNEL_IDLE_HIGH);
 
                        /* Wait a while and disable mechanism */
-                       udelay(200);
+                       udelay(400);
                        AR5K_REG_DISABLE_BITS(ah, AR5K_QUIET_CTL1,
                                                AR5K_QUIET_CTL1_QT_EN);
 
                        /* Re-check for pending frames */
-                       i = 40;
+                       i = 100;
                        do {
                                pending = ath5k_hw_reg_read(ah,
                                        AR5K_QUEUE_STATUS(queue)) &
@@ -262,18 +293,53 @@ int ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue)
 
                        AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW_5211,
                                        AR5K_DIAG_SW_CHANNEL_IDLE_HIGH);
+
+                       if (pending)
+                               ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_DMA,
+                                       "quiet mechanism didn't work q:%i !\n",
+                                       queue);
                }
 
+               /*
+                * Disable DCU early termination
+                */
+               AR5K_REG_DISABLE_BITS(ah, AR5K_QUEUE_MISC(queue),
+                                       AR5K_QCU_MISC_DCU_EARLY);
+
                /* Clear register */
                ath5k_hw_reg_write(ah, 0, AR5K_QCU_TXD);
-               if (pending)
+               if (pending) {
+                       ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_DMA,
+                                       "tx dma didn't stop (q:%i, frm:%i) !\n",
+                                       queue, pending);
                        return -EBUSY;
+               }
        }
 
        /* TODO: Check for success on 5210 else return error */
        return 0;
 }
 
+/**
+ * ath5k_hw_stop_beacon_queue - Stop beacon queue
+ *
+ * @ah The &struct ath5k_hw
+ * @queue The queue number
+ *
+ * Returns -EIO if queue didn't stop
+ */
+int ath5k_hw_stop_beacon_queue(struct ath5k_hw *ah, unsigned int queue)
+{
+       int ret;
+       ret = ath5k_hw_stop_tx_dma(ah, queue);
+       if (ret) {
+               ATH5K_DBG(ah->ah_sc, ATH5K_DEBUG_DMA,
+                               "beacon queue didn't stop !\n");
+               return -EIO;
+       }
+       return 0;
+}
+
 /**
  * ath5k_hw_get_txdp - Get TX Descriptor's address for a specific queue
  *
@@ -427,6 +493,7 @@ done:
        return ret;
 }
 
+
 /*******************\
 * Interrupt masking *
 \*******************/
@@ -688,3 +755,92 @@ enum ath5k_int ath5k_hw_set_imr(struct ath5k_hw *ah, enum ath5k_int new_mask)
        return old_mask;
 }
 
+
+/********************\
+ Init/Stop functions
+\********************/
+
+/**
+ * ath5k_hw_dma_init - Initialize DMA unit
+ *
+ * @ah: The &struct ath5k_hw
+ *
+ * Set DMA size and pre-enable interrupts
+ * (driver handles tx/rx buffer setup and
+ * dma start/stop)
+ *
+ * XXX: Save/restore RXDP/TXDP registers ?
+ */
+void ath5k_hw_dma_init(struct ath5k_hw *ah)
+{
+       /*
+        * Set Rx/Tx DMA Configuration
+        *
+        * Set standard DMA size (128). Note that
+        * a DMA size of 512 causes rx overruns and tx errors
+        * on pci-e cards (tested on 5424 but since rx overruns
+        * also occur on 5416/5418 with madwifi we set 128
+        * for all PCI-E cards to be safe).
+        *
+        * XXX: need to check 5210 for this
+        * TODO: Check out tx triger level, it's always 64 on dumps but I
+        * guess we can tweak it and see how it goes ;-)
+        */
+       if (ah->ah_version != AR5K_AR5210) {
+               AR5K_REG_WRITE_BITS(ah, AR5K_TXCFG,
+                       AR5K_TXCFG_SDMAMR, AR5K_DMASIZE_128B);
+               AR5K_REG_WRITE_BITS(ah, AR5K_RXCFG,
+                       AR5K_RXCFG_SDMAMW, AR5K_DMASIZE_128B);
+       }
+
+       /* Pre-enable interrupts on 5211/5212*/
+       if (ah->ah_version != AR5K_AR5210)
+               ath5k_hw_set_imr(ah, ah->ah_imr);
+
+}
+
+/**
+ * ath5k_hw_dma_stop - stop DMA unit
+ *
+ * @ah: The &struct ath5k_hw
+ *
+ * Stop tx/rx DMA and interrupts. Returns
+ * -EBUSY if tx or rx dma failed to stop.
+ *
+ * XXX: Sometimes DMA unit hangs and we have
+ * stuck frames on tx queues, only a reset
+ * can fix that.
+ */
+int ath5k_hw_dma_stop(struct ath5k_hw *ah)
+{
+       int i, qmax, err;
+       err = 0;
+
+       /* Disable interrupts */
+       ath5k_hw_set_imr(ah, 0);
+
+       /* Stop rx dma */
+       err = ath5k_hw_stop_rx_dma(ah);
+       if (err)
+               return err;
+
+       /* Clear any pending interrupts
+        * and disable tx dma */
+       if (ah->ah_version != AR5K_AR5210) {
+               ath5k_hw_reg_write(ah, 0xffffffff, AR5K_PISR);
+               qmax = AR5K_NUM_TX_QUEUES;
+       } else {
+               /* PISR/SISR Not available on 5210 */
+               ath5k_hw_reg_read(ah, AR5K_ISR);
+               qmax = AR5K_NUM_TX_QUEUES_NOQCU;
+       }
+
+       for (i = 0; i < qmax; i++) {
+               err = ath5k_hw_stop_tx_dma(ah, i);
+               /* -EINVAL -> queue inactive */
+               if (err && err != -EINVAL)
+                       return err;
+       }
+
+       return 0;
+}