From d1610c641c99addb2cb7937efc0e0a916e2043cd Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 27 Feb 2016 18:45:37 +0100 Subject: [PATCH] mt7603: add code to recover from MAC/DMA hangs Signed-off-by: Felix Fietkau --- mt76.h | 1 + mt7603.h | 10 +++++ mt7603_beacon.c | 13 +++--- mt7603_dma.c | 1 + mt7603_init.c | 39 ++--------------- mt7603_mac.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ mt7603_main.c | 3 ++ mt7603_regs.h | 8 ++++ tx.c | 3 +- 9 files changed, 149 insertions(+), 42 deletions(-) diff --git a/mt76.h b/mt76.h index ab1f0da0..0990a054 100644 --- a/mt76.h +++ b/mt76.h @@ -145,6 +145,7 @@ enum { MT76_STATE_INITIALIZED, MT76_STATE_RUNNING, MT76_SCANNING, + MT76_RESET, }; struct mt76_hw_cap { diff --git a/mt7603.h b/mt7603.h index 2fb90a34..35e2a44e 100644 --- a/mt7603.h +++ b/mt7603.h @@ -33,6 +33,8 @@ #define MT7603_PRE_TBTT_TIME 5000 /* ms */ +#define MT7603_WATCHDOG_TIME 1000 /* ms */ + enum { MT7603_REV_E1 = 0x00, MT7603_REV_E2 = 0x10 @@ -113,11 +115,17 @@ struct mt7603_dev { u8 slottime; s16 coverage_class; + int beacon_int; + struct mt7603_mcu mcu; struct mt76_queue q_rx; u8 beacon_mask; + u8 tx_check; + u8 rx_check; + + struct delayed_work mac_work; struct tasklet_struct tx_tasklet; struct tasklet_struct pre_tbtt_tasklet; }; @@ -175,8 +183,10 @@ static inline void mt7603_irq_disable(struct mt7603_dev *dev, u32 mask) mt7603_set_irq_mask(dev, mask, 0); } +void mt7603_mac_dma_start(struct mt7603_dev *dev); void mt7603_mac_start(struct mt7603_dev *dev); void mt7603_mac_stop(struct mt7603_dev *dev); +void mt7603_mac_work(struct work_struct *work); void mt7603_mac_set_timing(struct mt7603_dev *dev); void mt7603_beacon_set_timer(struct mt7603_dev *dev, int idx, int intval); int mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb); diff --git a/mt7603_beacon.c b/mt7603_beacon.c index 09aabff9..ce35510d 100644 --- a/mt7603_beacon.c +++ b/mt7603_beacon.c @@ -118,18 +118,21 @@ void mt7603_beacon_set_timer(struct mt7603_dev *dev, int idx, int intval) { u32 pre_tbtt = MT7603_PRE_TBTT_TIME / 64; - if (intval) - dev->beacon_mask |= BIT(idx); - else - dev->beacon_mask &= BIT(idx); + if (idx >= 0) { + if (intval) + dev->beacon_mask |= BIT(idx); + else + dev->beacon_mask &= BIT(idx); + } - if (!dev->beacon_mask) { + if (!dev->beacon_mask || (!intval && idx < 0)) { mt7603_irq_disable(dev, MT_INT_MAC_IRQ3); mt76_clear(dev, MT_ARB_SCR, MT_ARB_SCR_BCNQ_OPMODE_MASK); mt76_wr(dev, MT_HW_INT_MASK(3), 0); return; } + dev->beacon_int = intval; mt76_wr(dev, MT_TBTT, MT76_SET(MT_TBTT_PERIOD, intval) | MT_TBTT_CAL_ENABLE); diff --git a/mt7603_dma.c b/mt7603_dma.c index 38dff368..fcf1dee7 100644 --- a/mt7603_dma.c +++ b/mt7603_dma.c @@ -114,6 +114,7 @@ mt7603_tx_tasklet(unsigned long data) struct mt7603_dev *dev = (struct mt7603_dev *) data; int i; + dev->tx_check = 0; for (i = MT_TXQ_MCU; i >= 0; i--) mt76_queue_tx_cleanup(dev, i, false); diff --git a/mt7603_init.c b/mt7603_init.c index f9dd8f16..bf958978 100644 --- a/mt7603_init.c +++ b/mt7603_init.c @@ -40,15 +40,6 @@ struct mt7603_dev *mt7603_alloc_device(struct device *pdev) return dev; } -static bool -wait_for_wpdma(struct mt7603_dev *dev) -{ - return mt76_poll(dev, MT_WPDMA_GLO_CFG, - MT_WPDMA_GLO_CFG_TX_DMA_BUSY | - MT_WPDMA_GLO_CFG_RX_DMA_BUSY, - 0, 1000); -} - static void mt7603_set_tmac_template(struct mt7603_dev *dev) { @@ -65,29 +56,6 @@ mt7603_set_tmac_template(struct mt7603_dev *dev) mt76_wr(dev, addr + 4 * i, desc[i]); } -static int -mt7603_mac_early_init(struct mt7603_dev *dev) -{ - mt76_wr(dev, MT_WPDMA_GLO_CFG, 0x52000850); - - mt76_clear(dev, MT_ARB_SCR, MT_ARB_SCR_TX_DISABLE | MT_ARB_SCR_RX_DISABLE); - mt76_wr(dev, MT_WF_ARB_TX_START_0, ~0); - mt76_clear(dev, MT_WF_ARB_RQCR, MT_WF_ARB_RQCR_RX_START); - - wait_for_wpdma(dev); - udelay(50); - - mt76_set(dev, MT_WPDMA_GLO_CFG, - (MT_WPDMA_GLO_CFG_TX_DMA_EN | - MT_WPDMA_GLO_CFG_RX_DMA_EN | - MT76_SET(MT_WPDMA_GLO_CFG_DMA_BURST_SIZE, 3) | - MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE)); - - mt7603_irq_enable(dev, MT_INT_RX_DONE_ALL | MT_INT_TX_DONE_ALL); - - return 0; -} - static void mt7603_dma_sched_init(struct mt7603_dev *dev) { @@ -295,10 +263,8 @@ mt7603_init_hardware(struct mt7603_dev *dev) if (ret) return ret; - ret = mt7603_mac_early_init(dev); - if (ret) - return ret; - + mt76_wr(dev, MT_WPDMA_GLO_CFG, 0x52000850); + mt7603_mac_dma_start(dev); dev->rxfilter = mt76_rr(dev, MT_WF_RFCR); set_bit(MT76_STATE_INITIALIZED, &dev->mt76.state); @@ -377,6 +343,7 @@ int mt7603_register_device(struct mt7603_dev *dev) spin_lock_init(&dev->status_lock); INIT_LIST_HEAD(&dev->status_list); + INIT_DELAYED_WORK(&dev->mac_work, mt7603_mac_work); tasklet_init(&dev->pre_tbtt_tasklet, mt7603_pre_tbtt_tasklet, (unsigned long) dev); dev->rx_chains = 2; diff --git a/mt7603_mac.c b/mt7603_mac.c index 30a6d0cd..bc12a64e 100644 --- a/mt7603_mac.c +++ b/mt7603_mac.c @@ -919,6 +919,31 @@ void mt7603_tx_complete_skb(struct mt76_dev *mdev, struct mt76_queue *q, ieee80211_free_txskb(mdev->hw, e->skb); } +static bool +wait_for_wpdma(struct mt7603_dev *dev) +{ + return mt76_poll(dev, MT_WPDMA_GLO_CFG, + MT_WPDMA_GLO_CFG_TX_DMA_BUSY | + MT_WPDMA_GLO_CFG_RX_DMA_BUSY, + 0, 1000); +} + +void mt7603_mac_dma_start(struct mt7603_dev *dev) +{ + mt7603_mac_start(dev); + + wait_for_wpdma(dev); + udelay(50); + + mt76_set(dev, MT_WPDMA_GLO_CFG, + (MT_WPDMA_GLO_CFG_TX_DMA_EN | + MT_WPDMA_GLO_CFG_RX_DMA_EN | + MT76_SET(MT_WPDMA_GLO_CFG_DMA_BURST_SIZE, 3) | + MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE)); + + mt7603_irq_enable(dev, MT_INT_RX_DONE_ALL | MT_INT_TX_DONE_ALL); +} + void mt7603_mac_start(struct mt7603_dev *dev) { mt76_clear(dev, MT_ARB_SCR, MT_ARB_SCR_TX_DISABLE | MT_ARB_SCR_RX_DISABLE); @@ -932,3 +957,91 @@ void mt7603_mac_stop(struct mt7603_dev *dev) mt76_wr(dev, MT_WF_ARB_TX_START_0, 0); mt76_clear(dev, MT_WF_ARB_RQCR, MT_WF_ARB_RQCR_RX_START); } + +static void mt7603_mac_reset(struct mt7603_dev *dev) +{ + int beacon_int = dev->beacon_int; + u32 mask = dev->irqmask; + int i; + + set_bit(MT76_RESET, &dev->mt76.state); + + mt76_clear(dev, MT_WPDMA_GLO_CFG, + MT_WPDMA_GLO_CFG_RX_DMA_EN | MT_WPDMA_GLO_CFG_TX_DMA_EN | + MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE); + msleep(1); + + mt7603_irq_disable(dev, mask); + tasklet_disable(&dev->tx_tasklet); + tasklet_disable(&dev->pre_tbtt_tasklet); + napi_disable(&dev->mt76.napi[0]); + napi_disable(&dev->mt76.napi[1]); + + dev->tx_check = 0; + dev->rx_check = 0; + + mt7603_beacon_set_timer(dev, -1, 0); + + mt76_set(dev, MT_WPDMA_GLO_CFG, MT_WPDMA_GLO_CFG_FORCE_TX_EOF); + + mt76_set(dev, MT_PSE_RESET, MT_PSE_RESET_TX_R_E_1); + mt76_poll_msec(dev, MT_PSE_RESET, MT_PSE_RESET_TX_R_E_1_S, + MT_PSE_RESET_TX_R_E_1_S, 500); + + mt76_set(dev, MT_PSE_RESET, MT_PSE_RESET_TX_R_E_2); + mt76_set(dev, MT_WPDMA_GLO_CFG, MT_WPDMA_GLO_CFG_SW_RESET); + mt76_poll_msec(dev, MT_PSE_RESET, MT_PSE_RESET_TX_R_E_2_S, + MT_PSE_RESET_TX_R_E_2_S, 500); + + mt76_clear(dev, MT_PSE_RESET, + MT_PSE_RESET_TX_R_E_1 | MT_PSE_RESET_TX_R_E_2); + + for (i = 0; i < ARRAY_SIZE(dev->mt76.q_tx); i++) + mt76_queue_tx_cleanup(dev, i, true); + + mt76_clear(dev, MT_WPDMA_GLO_CFG, + MT_WPDMA_GLO_CFG_FORCE_TX_EOF | + MT_WPDMA_GLO_CFG_SW_RESET); + mt7603_mac_dma_start(dev); + mt7603_beacon_set_timer(dev, -1, beacon_int); + + tasklet_enable(&dev->tx_tasklet); + tasklet_enable(&dev->pre_tbtt_tasklet); + napi_enable(&dev->mt76.napi[0]); + napi_enable(&dev->mt76.napi[1]); + mt7603_irq_enable(dev, mask); + clear_bit(MT76_RESET, &dev->mt76.state); +} + +static bool mt7603_tx_dma_busy(struct mt7603_dev *dev) +{ + if (!(mt76_rr(dev, MT_WPDMA_GLO_CFG) & MT_WPDMA_GLO_CFG_TX_DMA_BUSY)) + return false; + + mt76_wr(dev, 0x4244, 0x98000000); + return mt76_rr(dev, 0x4244) & BIT(8); +} + +void mt7603_mac_work(struct work_struct *work) +{ + struct mt7603_dev *dev = container_of(work, struct mt7603_dev, mac_work.work); + int time = MT7603_WATCHDOG_TIME; + + mutex_lock(&dev->mutex); + + if (mt7603_tx_dma_busy(dev)) { + time = MT7603_WATCHDOG_TIME / 10; + + if (++dev->tx_check == 10) { + mt7603_mac_reset(dev); + goto out; + } + } else { + dev->tx_check = 0; + } + +out: + ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mac_work, + msecs_to_jiffies(time)); + mutex_unlock(&dev->mutex); +} diff --git a/mt7603_main.c b/mt7603_main.c index 11b42307..451834c6 100644 --- a/mt7603_main.c +++ b/mt7603_main.c @@ -21,6 +21,8 @@ mt7603_start(struct ieee80211_hw *hw) struct mt7603_dev *dev = hw->priv; mt7603_mac_start(dev); + ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mac_work, + MT7603_WATCHDOG_TIME); set_bit(MT76_STATE_RUNNING, &dev->mt76.state); return 0; @@ -32,6 +34,7 @@ mt7603_stop(struct ieee80211_hw *hw) struct mt7603_dev *dev = hw->priv; clear_bit(MT76_STATE_RUNNING, &dev->mt76.state); + cancel_delayed_work_sync(&dev->mac_work); mt7603_mac_stop(dev); } diff --git a/mt7603_regs.h b/mt7603_regs.h index 53198413..1ab8f592 100644 --- a/mt7603_regs.h +++ b/mt7603_regs.h @@ -56,6 +56,8 @@ #define MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE BIT(6) #define MT_WPDMA_GLO_CFG_BIG_ENDIAN BIT(7) #define MT_WPDMA_GLO_CFG_HDR_SEG_LEN GENMASK(15, 8) +#define MT_WPDMA_GLO_CFG_SW_RESET BIT(24) +#define MT_WPDMA_GLO_CFG_FORCE_TX_EOF BIT(25) #define MT_WPDMA_GLO_CFG_CLK_GATE_DIS BIT(30) #define MT_WPDMA_GLO_CFG_RX_2B_OFFSET BIT(31) @@ -98,6 +100,12 @@ #define MT_PSE_BASE 0x8000 #define MT_PSE(ofs) (MT_PSE_BASE + (ofs)) +#define MT_PSE_RESET MT_PSE(0x070) +#define MT_PSE_RESET_TX_R_E_1 BIT(16) +#define MT_PSE_RESET_TX_R_E_2 BIT(17) +#define MT_PSE_RESET_TX_R_E_1_S BIT(20) +#define MT_PSE_RESET_TX_R_E_2_S BIT(21) + #define MT_PSE_FC_P0 MT_PSE(0x120) #define MT_PSE_FC_P0_MIN_RESERVE GENMASK(11, 0) #define MT_PSE_FC_P0_MAX_QUOTA GENMASK(27, 16) diff --git a/tx.c b/tx.c index 18472dbd..8f9b4ef9 100644 --- a/tx.c +++ b/tx.c @@ -409,7 +409,8 @@ void mt76_txq_schedule(struct mt76_dev *dev, struct mt76_queue *hwq) { int len; - if (test_bit(MT76_SCANNING, &dev->state)) + if (test_bit(MT76_SCANNING, &dev->state) || + test_bit(MT76_RESET, &dev->state)) return; do {