aboutsummaryrefslogtreecommitdiffstats
path: root/package/kernel/mac80211/patches/600-0034-rt2x00-rt2800mmio-add-a-workaround-for-spurious-TX_F.patch
blob: 71f7bbaee7a8c46c931439ee22ecd93141fdafb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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/rt2x00/rt2800mmio.c |   72 +++++++++++++++++++++++++-----
 drivers/net/wireless/rt2x00/rt2x00.h     |    5 +++
 2 files changed, 65 insertions(+), 12 deletions(-)

--- a/drivers/net/wireless/rt2x00/rt2800mmio.c
+++ b/drivers/net/wireless/rt2x00/rt2800mmio.c
@@ -415,9 +415,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;
 
 	/*
@@ -438,29 +438,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++) {
-		rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO, &status);
-
-		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;
+
+ 		rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO, &status);
+	} 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 */
 	rt2x00mmio_register_read(rt2x00dev, INT_SOURCE_CSR, &reg);
+
+	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.
+		 */
+		rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO, &txstatus);
+		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)
@@ -477,7 +525,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/rt2x00/rt2x00.h
+++ b/drivers/net/wireless/rt2x00/rt2x00.h
@@ -989,6 +989,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;