mirror of
https://github.com/outbackdingo/mt76.git
synced 2026-10-24 13:26:48 +00:00
Disable the interrupt and synchronze for the pending irq handlers to ensure
the irq tasklet is not being scheduled after the suspend to avoid the
possible invalid register access acts when the host pcie controller is
suspended.
[17932.910534] mt7921e 0000:01:00.0: pci_pm_suspend+0x0/0x22c returned 0 after 21375 usecs
[17932.910590] pcieport 0000:00:00.0: calling pci_pm_suspend+0x0/0x22c @ 18565, parent: pci0000:00
[17932.910602] pcieport 0000:00:00.0: pci_pm_suspend+0x0/0x22c returned 0 after 8 usecs
[17932.910671] mtk-pcie 11230000.pcie: calling platform_pm_suspend+0x0/0x60 @ 22783, parent: soc
[17932.910674] mtk-pcie 11230000.pcie: platform_pm_suspend+0x0/0x60 returned 0 after 0 usecs
...
17933.615352] x1 : 00000000000d4200 x0 : ffffff8269ca2300
[17933.620666] Call trace:
[17933.623127] mt76_mmio_rr+0x28/0xf0 [mt76]
[17933.627234] mt7921_rr+0x38/0x44 [mt7921e]
[17933.631339] mt7921_irq_tasklet+0x54/0x1d8 [mt7921e]
[17933.636309] tasklet_action_common+0x12c/0x16c
[17933.640754] tasklet_action+0x24/0x2c
[17933.644418] __do_softirq+0x16c/0x344
[17933.648082] irq_exit+0xa8/0xac
[17933.651224] scheduler_ipi+0xd4/0x148
[17933.654890] handle_IPI+0x164/0x2d4
[17933.658379] gic_handle_irq+0x140/0x178
[17933.662216] el1_irq+0xb8/0x180
[17933.665361] cpuidle_enter_state+0xf8/0x204
[17933.669544] cpuidle_enter+0x38/0x4c
[17933.673122] do_idle+0x1a4/0x2a8
[17933.676352] cpu_startup_entry+0x24/0x28
[17933.680276] rest_init+0xd4/0xe0
[17933.683508] arch_call_rest_init+0x10/0x18
[17933.687606] start_kernel+0x340/0x3b4
[17933.691279] Code: aa0003f5 d503201f f953eaa8 8b344108 (b9400113)
[17933.697373] ---[ end trace a24b8e26ffbda3c5 ]---
[17933.767846] Kernel panic - not syncing: Fatal exception in interrupt
Fixes: ffa1bf97425b ("mt76: mt7921: introduce PM support")
Signed-off-by: Sean Wang <[email protected]>
Signed-off-by: Felix Fietkau <[email protected]>
322 lines
7.7 KiB
C
322 lines
7.7 KiB
C
// SPDX-License-Identifier: ISC
|
|
/* Copyright (C) 2020 MediaTek Inc.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include "mt7921.h"
|
|
#include "mac.h"
|
|
#include "mcu.h"
|
|
#include "../trace.h"
|
|
|
|
static const struct pci_device_id mt7921_pci_device_table[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7961) },
|
|
{ },
|
|
};
|
|
|
|
static void
|
|
mt7921_rx_poll_complete(struct mt76_dev *mdev, enum mt76_rxq_id q)
|
|
{
|
|
struct mt7921_dev *dev = container_of(mdev, struct mt7921_dev, mt76);
|
|
|
|
if (q == MT_RXQ_MAIN)
|
|
mt7921_irq_enable(dev, MT_INT_RX_DONE_DATA);
|
|
else if (q == MT_RXQ_MCU_WA)
|
|
mt7921_irq_enable(dev, MT_INT_RX_DONE_WM2);
|
|
else
|
|
mt7921_irq_enable(dev, MT_INT_RX_DONE_WM);
|
|
}
|
|
|
|
static irqreturn_t mt7921_irq_handler(int irq, void *dev_instance)
|
|
{
|
|
struct mt7921_dev *dev = dev_instance;
|
|
|
|
mt76_wr(dev, MT_WFDMA0_HOST_INT_ENA, 0);
|
|
|
|
if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state))
|
|
return IRQ_NONE;
|
|
|
|
tasklet_schedule(&dev->irq_tasklet);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void mt7921_irq_tasklet(unsigned long data)
|
|
{
|
|
struct mt7921_dev *dev = (struct mt7921_dev *)data;
|
|
u32 intr, mask = 0;
|
|
|
|
mt76_wr(dev, MT_WFDMA0_HOST_INT_ENA, 0);
|
|
|
|
intr = mt76_rr(dev, MT_WFDMA0_HOST_INT_STA);
|
|
intr &= dev->mt76.mmio.irqmask;
|
|
mt76_wr(dev, MT_WFDMA0_HOST_INT_STA, intr);
|
|
|
|
trace_dev_irq(&dev->mt76, intr, dev->mt76.mmio.irqmask);
|
|
|
|
mask |= intr & MT_INT_RX_DONE_ALL;
|
|
if (intr & MT_INT_TX_DONE_MCU)
|
|
mask |= MT_INT_TX_DONE_MCU;
|
|
|
|
if (intr & MT_INT_MCU_CMD) {
|
|
u32 intr_sw;
|
|
|
|
intr_sw = mt76_rr(dev, MT_MCU_CMD);
|
|
/* ack MCU2HOST_SW_INT_STA */
|
|
mt76_wr(dev, MT_MCU_CMD, intr_sw);
|
|
if (intr_sw & MT_MCU_CMD_WAKE_RX_PCIE) {
|
|
mask |= MT_INT_RX_DONE_DATA;
|
|
intr |= MT_INT_RX_DONE_DATA;
|
|
}
|
|
}
|
|
|
|
mt76_set_irq_mask(&dev->mt76, MT_WFDMA0_HOST_INT_ENA, mask, 0);
|
|
|
|
if (intr & MT_INT_TX_DONE_ALL)
|
|
napi_schedule(&dev->mt76.tx_napi);
|
|
|
|
if (intr & MT_INT_RX_DONE_WM)
|
|
napi_schedule(&dev->mt76.napi[MT_RXQ_MCU]);
|
|
|
|
if (intr & MT_INT_RX_DONE_WM2)
|
|
napi_schedule(&dev->mt76.napi[MT_RXQ_MCU_WA]);
|
|
|
|
if (intr & MT_INT_RX_DONE_DATA)
|
|
napi_schedule(&dev->mt76.napi[MT_RXQ_MAIN]);
|
|
}
|
|
|
|
static int mt7921_pci_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *id)
|
|
{
|
|
static const struct mt76_driver_ops drv_ops = {
|
|
/* txwi_size = txd size + txp size */
|
|
.txwi_size = MT_TXD_SIZE + sizeof(struct mt7921_txp_common),
|
|
.drv_flags = MT_DRV_TXWI_NO_FREE | MT_DRV_HW_MGMT_TXQ |
|
|
MT_DRV_AMSDU_OFFLOAD,
|
|
.survey_flags = SURVEY_INFO_TIME_TX |
|
|
SURVEY_INFO_TIME_RX |
|
|
SURVEY_INFO_TIME_BSS_RX,
|
|
.tx_prepare_skb = mt7921_tx_prepare_skb,
|
|
.tx_complete_skb = mt7921_tx_complete_skb,
|
|
.rx_skb = mt7921_queue_rx_skb,
|
|
.rx_poll_complete = mt7921_rx_poll_complete,
|
|
.sta_ps = mt7921_sta_ps,
|
|
.sta_add = mt7921_mac_sta_add,
|
|
.sta_remove = mt7921_mac_sta_remove,
|
|
.update_survey = mt7921_update_channel,
|
|
};
|
|
struct mt7921_dev *dev;
|
|
struct mt76_dev *mdev;
|
|
int ret;
|
|
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev));
|
|
if (ret)
|
|
return ret;
|
|
|
|
pci_set_master(pdev);
|
|
|
|
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
|
|
if (ret)
|
|
goto err_free_pci_vec;
|
|
|
|
mt76_pci_disable_aspm(pdev);
|
|
|
|
mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), &mt7921_ops,
|
|
&drv_ops);
|
|
if (!mdev) {
|
|
ret = -ENOMEM;
|
|
goto err_free_pci_vec;
|
|
}
|
|
|
|
dev = container_of(mdev, struct mt7921_dev, mt76);
|
|
|
|
mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]);
|
|
tasklet_init(&dev->irq_tasklet, mt7921_irq_tasklet, (unsigned long)dev);
|
|
mdev->rev = (mt7921_l1_rr(dev, MT_HW_CHIPID) << 16) |
|
|
(mt7921_l1_rr(dev, MT_HW_REV) & 0xff);
|
|
dev_err(mdev->dev, "ASIC revision: %04x\n", mdev->rev);
|
|
|
|
mt76_wr(dev, MT_WFDMA0_HOST_INT_ENA, 0);
|
|
|
|
mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff);
|
|
|
|
ret = devm_request_irq(mdev->dev, pdev->irq, mt7921_irq_handler,
|
|
IRQF_SHARED, KBUILD_MODNAME, dev);
|
|
if (ret)
|
|
goto err_free_dev;
|
|
|
|
ret = mt7921_register_device(dev);
|
|
if (ret)
|
|
goto err_free_irq;
|
|
|
|
return 0;
|
|
|
|
err_free_irq:
|
|
devm_free_irq(&pdev->dev, pdev->irq, dev);
|
|
err_free_dev:
|
|
mt76_free_device(&dev->mt76);
|
|
err_free_pci_vec:
|
|
pci_free_irq_vectors(pdev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void mt7921_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct mt76_dev *mdev = pci_get_drvdata(pdev);
|
|
struct mt7921_dev *dev = container_of(mdev, struct mt7921_dev, mt76);
|
|
|
|
mt7921_unregister_device(dev);
|
|
devm_free_irq(&pdev->dev, pdev->irq, dev);
|
|
pci_free_irq_vectors(pdev);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int mt7921_pci_suspend(struct pci_dev *pdev, pm_message_t state)
|
|
{
|
|
struct mt76_dev *mdev = pci_get_drvdata(pdev);
|
|
struct mt7921_dev *dev = container_of(mdev, struct mt7921_dev, mt76);
|
|
bool hif_suspend;
|
|
int i, err;
|
|
|
|
err = mt76_connac_pm_wake(&dev->mphy, &dev->pm);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
hif_suspend = !test_bit(MT76_STATE_SUSPEND, &dev->mphy.state);
|
|
if (hif_suspend) {
|
|
err = mt76_connac_mcu_set_hif_suspend(mdev, true);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
if (!dev->pm.enable)
|
|
mt76_connac_mcu_set_deep_sleep(&dev->mt76, true);
|
|
|
|
napi_disable(&mdev->tx_napi);
|
|
mt76_worker_disable(&mdev->tx_worker);
|
|
|
|
mt76_for_each_q_rx(mdev, i) {
|
|
napi_disable(&mdev->napi[i]);
|
|
}
|
|
|
|
pci_enable_wake(pdev, pci_choose_state(pdev, state), true);
|
|
|
|
/* wait until dma is idle */
|
|
mt76_poll(dev, MT_WFDMA0_GLO_CFG,
|
|
MT_WFDMA0_GLO_CFG_TX_DMA_BUSY |
|
|
MT_WFDMA0_GLO_CFG_RX_DMA_BUSY, 0, 1000);
|
|
|
|
/* put dma disabled */
|
|
mt76_clear(dev, MT_WFDMA0_GLO_CFG,
|
|
MT_WFDMA0_GLO_CFG_TX_DMA_EN | MT_WFDMA0_GLO_CFG_RX_DMA_EN);
|
|
|
|
/* disable interrupt */
|
|
mt76_wr(dev, MT_WFDMA0_HOST_INT_ENA, 0);
|
|
mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0x0);
|
|
synchronize_irq(pdev->irq);
|
|
tasklet_kill(&dev->irq_tasklet);
|
|
|
|
err = mt7921_mcu_fw_pmctrl(dev);
|
|
if (err)
|
|
goto restore;
|
|
|
|
pci_save_state(pdev);
|
|
err = pci_set_power_state(pdev, pci_choose_state(pdev, state));
|
|
if (err)
|
|
goto restore;
|
|
|
|
return 0;
|
|
|
|
restore:
|
|
mt76_for_each_q_rx(mdev, i) {
|
|
napi_enable(&mdev->napi[i]);
|
|
}
|
|
napi_enable(&mdev->tx_napi);
|
|
|
|
if (!dev->pm.enable)
|
|
mt76_connac_mcu_set_deep_sleep(&dev->mt76, false);
|
|
|
|
if (hif_suspend)
|
|
mt76_connac_mcu_set_hif_suspend(mdev, false);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mt7921_pci_resume(struct pci_dev *pdev)
|
|
{
|
|
struct mt76_dev *mdev = pci_get_drvdata(pdev);
|
|
struct mt7921_dev *dev = container_of(mdev, struct mt7921_dev, mt76);
|
|
int i, err;
|
|
|
|
err = pci_set_power_state(pdev, PCI_D0);
|
|
if (err)
|
|
return err;
|
|
|
|
pci_restore_state(pdev);
|
|
|
|
err = mt7921_mcu_drv_pmctrl(dev);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
mt7921_wpdma_reinit_cond(dev);
|
|
|
|
/* enable interrupt */
|
|
mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff);
|
|
mt7921_irq_enable(dev, MT_INT_RX_DONE_ALL | MT_INT_TX_DONE_ALL |
|
|
MT_INT_MCU_CMD);
|
|
mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE);
|
|
|
|
/* put dma enabled */
|
|
mt76_set(dev, MT_WFDMA0_GLO_CFG,
|
|
MT_WFDMA0_GLO_CFG_TX_DMA_EN | MT_WFDMA0_GLO_CFG_RX_DMA_EN);
|
|
|
|
mt76_worker_enable(&mdev->tx_worker);
|
|
mt76_for_each_q_rx(mdev, i) {
|
|
napi_enable(&mdev->napi[i]);
|
|
napi_schedule(&mdev->napi[i]);
|
|
}
|
|
napi_enable(&mdev->tx_napi);
|
|
napi_schedule(&mdev->tx_napi);
|
|
|
|
if (!dev->pm.enable)
|
|
mt76_connac_mcu_set_deep_sleep(&dev->mt76, false);
|
|
|
|
if (!test_bit(MT76_STATE_SUSPEND, &dev->mphy.state))
|
|
err = mt76_connac_mcu_set_hif_suspend(mdev, false);
|
|
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
|
|
struct pci_driver mt7921_pci_driver = {
|
|
.name = KBUILD_MODNAME,
|
|
.id_table = mt7921_pci_device_table,
|
|
.probe = mt7921_pci_probe,
|
|
.remove = mt7921_pci_remove,
|
|
#ifdef CONFIG_PM
|
|
.suspend = mt7921_pci_suspend,
|
|
.resume = mt7921_pci_resume,
|
|
#endif /* CONFIG_PM */
|
|
};
|
|
|
|
module_pci_driver(mt7921_pci_driver);
|
|
|
|
MODULE_DEVICE_TABLE(pci, mt7921_pci_device_table);
|
|
MODULE_FIRMWARE(MT7921_FIRMWARE_WM);
|
|
MODULE_FIRMWARE(MT7921_ROM_PATCH);
|
|
MODULE_AUTHOR("Sean Wang <[email protected]>");
|
|
MODULE_AUTHOR("Lorenzo Bianconi <[email protected]>");
|
|
MODULE_LICENSE("Dual BSD/GPL");
|