OpenWrt – Rev 4

Subversion Repositories:
Rev:
From 5e67d4f8a46d19748b501c2ef86de3f50d3cfd51 Mon Sep 17 00:00:00 2001
From: Gabor Juhos <juhosg@openwrt.org>
Date: Sun, 24 Mar 2013 19:26:27 +0100
Subject: [PATCH] rt2x00: rt2800mmio: add a workaround for spurious
 TX_FIFO_STATUS interrupts

Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
---
 drivers/net/wireless/ralink/rt2x00/rt2800mmio.c |   72 +++++++++++++++++++++++++-----
 drivers/net/wireless/ralink/rt2x00/rt2x00.h     |    5 +++
 2 files changed, 65 insertions(+), 12 deletions(-)

--- a/drivers/net/wireless/ralink/rt2x00/rt2800mmio.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800mmio.c
@@ -424,9 +424,9 @@ void rt2800mmio_autowake_tasklet(unsigne
 }
 EXPORT_SYMBOL_GPL(rt2800mmio_autowake_tasklet);
 
-static void rt2800mmio_txstatus_interrupt(struct rt2x00_dev *rt2x00dev)
+static void rt2800mmio_txstatus_interrupt(struct rt2x00_dev *rt2x00dev,
+                                         u32 status)
 {
-       u32 status;
        int i;
 
        /*
@@ -447,29 +447,77 @@ static void rt2800mmio_txstatus_interrup
         * Since we have only one producer and one consumer we don't
         * need to lock the kfifo.
         */
-       for (i = 0; i < rt2x00dev->tx->limit; i++) {
-               status = rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO);
-
-               if (!rt2x00_get_field32(status, TX_STA_FIFO_VALID))
-                       break;
-
+       i = 0;
+       do {
                if (!kfifo_put(&rt2x00dev->txstatus_fifo, status)) {
-                       rt2x00_warn(rt2x00dev, "TX status FIFO overrun, drop tx status report\n");
+                       rt2x00_warn(rt2x00dev,
+                                   "TX status FIFO overrun, drop TX status report\n");
                        break;
                }
-       }
+
+               if (++i >= rt2x00dev->tx->limit)
+                       break;
+
+               status = rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO);
+       } while (rt2x00_get_field32(status, TX_STA_FIFO_VALID));
 
        /* Schedule the tasklet for processing the tx status. */
        tasklet_schedule(&rt2x00dev->txstatus_tasklet);
 }
 
+#define RT2800MMIO_TXSTATUS_IRQ_MAX_RETRIES    4
+
+static bool rt2800mmio_txstatus_is_spurious(struct rt2x00_dev *rt2x00dev,
+                                          u32 txstatus)
+{
+       if (likely(rt2x00_get_field32(txstatus, TX_STA_FIFO_VALID))) {
+               rt2x00dev->txstatus_irq_retries = 0;
+               return false;
+       }
+
+       rt2x00dev->txstatus_irq_retries++;
+
+       /* Ensure that we don't go into an infinite IRQ loop. */
+       if (rt2x00dev->txstatus_irq_retries >=
+           RT2800MMIO_TXSTATUS_IRQ_MAX_RETRIES) {
+               rt2x00_warn(rt2x00dev,
+                           "%u spurious TX_FIFO_STATUS interrupt(s)\n",
+                           rt2x00dev->txstatus_irq_retries);
+               rt2x00dev->txstatus_irq_retries = 0;
+               return false;
+       }
+
+       return true;
+}
+
 irqreturn_t rt2800mmio_interrupt(int irq, void *dev_instance)
 {
        struct rt2x00_dev *rt2x00dev = dev_instance;
        u32 reg, mask;
+       u32 txstatus = 0;
 
-       /* Read status and ACK all interrupts */
+       /* Read status */
        reg = rt2x00mmio_register_read(rt2x00dev, INT_SOURCE_CSR);
+
+       if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS)) {
+               /* Due to unknown reason the hardware generates a
+                * TX_FIFO_STATUS interrupt before the TX_STA_FIFO
+                * register contain valid data. Read the TX status
+                * here to see if we have to process the actual
+                * request.
+                */
+               txstatus = rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO);
+               if (rt2800mmio_txstatus_is_spurious(rt2x00dev, txstatus)) {
+                       /* Remove the TX_FIFO_STATUS bit so it won't be
+                        * processed in this turn. The hardware will
+                        * generate another IRQ for us.
+                        */
+                       rt2x00_set_field32(&reg,
+                                          INT_SOURCE_CSR_TX_FIFO_STATUS, 0);
+               }
+       }
+
+       /* ACK interrupts */
        rt2x00mmio_register_write(rt2x00dev, INT_SOURCE_CSR, reg);
 
        if (!reg)
@@ -486,7 +534,7 @@ irqreturn_t rt2800mmio_interrupt(int irq
        mask = ~reg;
 
        if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS)) {
-               rt2800mmio_txstatus_interrupt(rt2x00dev);
+               rt2800mmio_txstatus_interrupt(rt2x00dev, txstatus);
                /*
                 * Never disable the TX_FIFO_STATUS interrupt.
                 */
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h
@@ -1000,6 +1000,11 @@ struct rt2x00_dev {
        int rf_channel;
 
        /*
+        * Counter for tx status irq retries (rt2800pci).
+        */
+       unsigned int txstatus_irq_retries;
+
+       /*
         * Protect the interrupt mask register.
         */
        spinlock_t irqmask_lock;