aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/ralink/rt2x00/rt2800mmio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/ralink/rt2x00/rt2800mmio.c')
-rw-r--r--drivers/net/wireless/ralink/rt2x00/rt2800mmio.c138
1 files changed, 91 insertions, 47 deletions
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2800mmio.c b/drivers/net/wireless/ralink/rt2x00/rt2800mmio.c
index ddb88cfeace2..d1de8e2ff690 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2800mmio.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2800mmio.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright (C) 2009 - 2010 Ivo van Doorn <IvDoorn@gmail.com>
* Copyright (C) 2009 Alban Browaeys <prahal@yahoo.com>
* Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org>
@@ -7,19 +8,6 @@
* Copyright (C) 2009 Xose Vazquez Perez <xose.vazquez@gmail.com>
* Copyright (C) 2009 Bart Zolnierkiewicz <bzolnier@gmail.com>
* <http://rt2x00.serialmonkey.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/* Module: rt2800mmio
@@ -255,26 +243,12 @@ void rt2800mmio_autowake_tasklet(unsigned long data)
}
EXPORT_SYMBOL_GPL(rt2800mmio_autowake_tasklet);
-static void rt2800mmio_txdone(struct rt2x00_dev *rt2x00dev)
-{
- bool timeout = false;
-
- while (!kfifo_is_empty(&rt2x00dev->txstatus_fifo) ||
- (timeout = rt2800_txstatus_timeout(rt2x00dev))) {
-
- rt2800_txdone(rt2x00dev);
-
- if (timeout)
- rt2800_txdone_nostatus(rt2x00dev);
- }
-}
-
-static bool rt2800mmio_fetch_txstatus(struct rt2x00_dev *rt2x00dev)
+static void rt2800mmio_fetch_txstatus(struct rt2x00_dev *rt2x00dev)
{
u32 status;
- bool more = false;
+ unsigned long flags;
- /* FIXEME: rewrite this comment
+ /*
* The TX_FIFO_STATUS interrupt needs special care. We should
* read TX_STA_FIFO but we should do it immediately as otherwise
* the register can overflow and we would lose status reports.
@@ -285,34 +259,32 @@ static bool rt2800mmio_fetch_txstatus(struct rt2x00_dev *rt2x00dev)
* because we can schedule the tasklet multiple times (when the
* interrupt fires again during tx status processing).
*
- * txstatus tasklet is called with INT_SOURCE_CSR_TX_FIFO_STATUS
- * disabled so have only one producer and one consumer - we don't
- * need to lock the kfifo.
+ * We also read statuses from tx status timeout timer, use
+ * lock to prevent concurent writes to fifo.
*/
+
+ spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags);
+
while (!kfifo_is_full(&rt2x00dev->txstatus_fifo)) {
status = rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO);
if (!rt2x00_get_field32(status, TX_STA_FIFO_VALID))
break;
kfifo_put(&rt2x00dev->txstatus_fifo, status);
- more = true;
}
- return more;
+ spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags);
}
void rt2800mmio_txstatus_tasklet(unsigned long data)
{
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
- do {
- rt2800mmio_txdone(rt2x00dev);
+ rt2800_txdone(rt2x00dev, 16);
- } while (rt2800mmio_fetch_txstatus(rt2x00dev));
+ if (!kfifo_is_empty(&rt2x00dev->txstatus_fifo))
+ tasklet_schedule(&rt2x00dev->txstatus_tasklet);
- if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
- rt2800mmio_enable_interrupt(rt2x00dev,
- INT_SOURCE_CSR_TX_FIFO_STATUS);
}
EXPORT_SYMBOL_GPL(rt2800mmio_txstatus_tasklet);
@@ -339,8 +311,10 @@ irqreturn_t rt2800mmio_interrupt(int irq, void *dev_instance)
mask = ~reg;
if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS)) {
+ rt2x00_set_field32(&mask, INT_MASK_CSR_TX_FIFO_STATUS, 1);
rt2800mmio_fetch_txstatus(rt2x00dev);
- tasklet_schedule(&rt2x00dev->txstatus_tasklet);
+ if (!kfifo_is_empty(&rt2x00dev->txstatus_fifo))
+ tasklet_schedule(&rt2x00dev->txstatus_tasklet);
}
if (rt2x00_get_field32(reg, INT_SOURCE_CSR_PRE_TBTT))
@@ -440,6 +414,9 @@ void rt2800mmio_start_queue(struct data_queue *queue)
}
EXPORT_SYMBOL_GPL(rt2800mmio_start_queue);
+/* 200 ms */
+#define TXSTATUS_TIMEOUT 200000000
+
void rt2800mmio_kick_queue(struct data_queue *queue)
{
struct rt2x00_dev *rt2x00dev = queue->rt2x00dev;
@@ -454,6 +431,8 @@ void rt2800mmio_kick_queue(struct data_queue *queue)
entry = rt2x00queue_get_entry(queue, Q_INDEX);
rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX(queue->qid),
entry->entry_idx);
+ hrtimer_start(&rt2x00dev->txstatus_timer,
+ TXSTATUS_TIMEOUT, HRTIMER_MODE_REL);
break;
case QID_MGMT:
entry = rt2x00queue_get_entry(queue, Q_INDEX);
@@ -498,11 +477,8 @@ void rt2800mmio_flush_queue(struct data_queue *queue, bool drop)
* For TX queues schedule completion tasklet to catch
* tx status timeouts, othewise just wait.
*/
- if (tx_queue) {
- tasklet_disable(&rt2x00dev->txstatus_tasklet);
- rt2800mmio_txdone(rt2x00dev);
- tasklet_enable(&rt2x00dev->txstatus_tasklet);
- }
+ if (tx_queue)
+ queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work);
/*
* Wait for a little while to give the driver
@@ -640,6 +616,10 @@ void rt2800mmio_clear_entry(struct queue_entry *entry)
word = rt2x00_desc_read(entry_priv->desc, 1);
rt2x00_set_field32(&word, TXD_W1_DMA_DONE, 1);
rt2x00_desc_write(entry_priv->desc, 1, word);
+
+ /* If last entry stop txstatus timer */
+ if (entry->queue->length == 1)
+ hrtimer_cancel(&rt2x00dev->txstatus_timer);
}
}
EXPORT_SYMBOL_GPL(rt2800mmio_clear_entry);
@@ -772,6 +752,70 @@ int rt2800mmio_enable_radio(struct rt2x00_dev *rt2x00dev)
}
EXPORT_SYMBOL_GPL(rt2800mmio_enable_radio);
+static void rt2800mmio_work_txdone(struct work_struct *work)
+{
+ struct rt2x00_dev *rt2x00dev =
+ container_of(work, struct rt2x00_dev, txdone_work);
+
+ if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
+ return;
+
+ while (!kfifo_is_empty(&rt2x00dev->txstatus_fifo) ||
+ rt2800_txstatus_timeout(rt2x00dev)) {
+
+ tasklet_disable(&rt2x00dev->txstatus_tasklet);
+ rt2800_txdone(rt2x00dev, UINT_MAX);
+ rt2800_txdone_nostatus(rt2x00dev);
+ tasklet_enable(&rt2x00dev->txstatus_tasklet);
+ }
+
+ if (rt2800_txstatus_pending(rt2x00dev))
+ hrtimer_start(&rt2x00dev->txstatus_timer,
+ TXSTATUS_TIMEOUT, HRTIMER_MODE_REL);
+}
+
+static enum hrtimer_restart rt2800mmio_tx_sta_fifo_timeout(struct hrtimer *timer)
+{
+ struct rt2x00_dev *rt2x00dev =
+ container_of(timer, struct rt2x00_dev, txstatus_timer);
+
+ if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
+ goto out;
+
+ if (!rt2800_txstatus_pending(rt2x00dev))
+ goto out;
+
+ rt2800mmio_fetch_txstatus(rt2x00dev);
+ if (!kfifo_is_empty(&rt2x00dev->txstatus_fifo))
+ tasklet_schedule(&rt2x00dev->txstatus_tasklet);
+ else
+ queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work);
+out:
+ return HRTIMER_NORESTART;
+}
+
+int rt2800mmio_probe_hw(struct rt2x00_dev *rt2x00dev)
+{
+ int retval;
+
+ retval = rt2800_probe_hw(rt2x00dev);
+ if (retval)
+ return retval;
+
+ /*
+ * Set txstatus timer function.
+ */
+ rt2x00dev->txstatus_timer.function = rt2800mmio_tx_sta_fifo_timeout;
+
+ /*
+ * Overwrite TX done handler
+ */
+ INIT_WORK(&rt2x00dev->txdone_work, rt2800mmio_work_txdone);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rt2800mmio_probe_hw);
+
MODULE_AUTHOR(DRV_PROJECT);
MODULE_VERSION(DRV_VERSION);
MODULE_DESCRIPTION("rt2800 MMIO library");