[PATCH 10/11] net: wwan: t9xx: Add power management support

Jagielski, Jedrzej jedrzej.jagielski at intel.com
Mon Jun 1 05:26:37 PDT 2026


From: Jack Wu via B4 Relay <devnull+jackbb_wu.compal.com at kernel.org> 
Sent: Friday, May 29, 2026 12:32 PM

>From: Jack Wu <jackbb_wu at compal.com>
>
>Add s2idle (S0ix) power management support for the t9xx WWAN driver.
>
>In s2idle the modem remains powered. The driver must quiesce host-side
>DMA engines and service threads before the platform enters low-power
>state, then restore them on resume.
>
>- Suspend: park TRB service threads, stop CLDMA TX/RX queues,
>  disable DPMAIF data path, mask MHCCIF and MSIX interrupts,
>  save PCIe state
>- Resume: restore PCIe state, re-initialize ATR, unmask MHCCIF,
>  resume CLDMA queues, re-enable DPMAIF data path, unpark TRB
>  service threads
>
>Signed-off-by: Jack Wu <jackbb_wu at compal.com>
>---
> drivers/net/wwan/t9xx/pcie/mtk_cldma.c  | 94 +++++++++++++++++++++++++++++++++
> drivers/net/wwan/t9xx/pcie/mtk_cldma.h  |  3 ++
> drivers/net/wwan/t9xx/pcie/mtk_dpmaif.c | 35 +++++++++++-
> drivers/net/wwan/t9xx/pcie/mtk_dpmaif.h |  2 +
> drivers/net/wwan/t9xx/pcie/mtk_pci.c    | 85 ++++++++++++++++++++++++++++-
> 5 files changed, 216 insertions(+), 3 deletions(-)
>
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
>index aacb4177d914..a5227eb546f4 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
>@@ -1113,6 +1113,100 @@ int mtk_cldma_exit(struct mtk_ctrl_trans *trans)
> 	return 0;
> }
> 
>+void mtk_cldma_pm_suspend(struct mtk_md_dev *mdev)
>+{
>+	struct mtk_ctrl_trans *trans = ((struct mtk_ctrl_blk *)mdev->ctrl_blk)->ctrl_hw_priv;
>+	struct cldma_dev *cd = trans->dev;
>+	struct cldma_drv_info *drv_info;
>+	struct cldma_drv_ops *drv_ops;
>+	struct rxq *rxq;
>+	struct txq *txq;
>+	int i, q;
>+
>+	for (i = 0; i < NR_CLDMA; i++) {
>+		drv_info = cd->cldma_drv_info[i];
>+		if (!drv_info)
>+			continue;
>+
>+		drv_ops = drv_info->drv_ops;
>+
>+		/* Stop TX queues and flush pending tx_done_work (suspend phase) */
>+		drv_ops->cldma_stop_queue(drv_info, DIR_TX, ALLQ);
>+		for (q = 0; q < HW_QUEUE_NUM; q++) {
>+			txq = drv_info->txq[q];
>+			if (txq)
>+				flush_work(&txq->tx_done_work);
>+		}
>+
>+		for (q = 0; q < HW_QUEUE_NUM; q++) {
>+			rxq = drv_info->rxq[q];
>+			if (!rxq)
>+				continue;
>+			atomic_set(&rxq->need_exit, 1);
>+			drv_ops->cldma_stop_queue(drv_info, DIR_RX, q);
>+			flush_work(&rxq->rx_done_work);
>+		}
>+		mtk_pci_mask_irq(mdev, drv_info->pci_ext_irq_id);
>+	}
>+}
>+
>+void mtk_cldma_pm_resume_early(struct mtk_md_dev *mdev)
>+{
>+	struct mtk_ctrl_trans *trans = ((struct mtk_ctrl_blk *)mdev->ctrl_blk)->ctrl_hw_priv;
>+	struct cldma_dev *cd = trans->dev;
>+	struct cldma_drv_info *drv_info;
>+	struct cldma_drv_ops *drv_ops;
>+	struct rxq *rxq;
>+	int i, q;
>+
>+	for (i = 0; i < NR_CLDMA; i++) {
>+		drv_info = cd->cldma_drv_info[i];
>+		if (!drv_info)
>+			continue;
>+
>+		drv_ops = drv_info->drv_ops;
>+
>+		/* Resume RX queues from current HW ring position (no addr reset) */
>+		for (q = 0; q < HW_QUEUE_NUM; q++) {
>+			rxq = drv_info->rxq[q];
>+			if (!rxq)
>+				continue;
>+			atomic_set(&rxq->need_exit, 0);
>+			drv_ops->cldma_resume_queue(drv_info, DIR_RX, q);
>+		}
>+
>+		/* Unmask CLDMA L1 interrupt */
>+		mtk_pci_unmask_irq(mdev, drv_info->pci_ext_irq_id);
>+	}
>+}
>+
>+void mtk_cldma_pm_resume(struct mtk_md_dev *mdev)
>+{
>+	struct mtk_ctrl_trans *trans = ((struct mtk_ctrl_blk *)mdev->ctrl_blk)->ctrl_hw_priv;
>+	struct cldma_dev *cd = trans->dev;
>+	struct cldma_drv_info *drv_info;
>+	struct cldma_drv_ops *drv_ops;
>+	struct txq *txq;
>+	int i, q;
>+
>+	for (i = 0; i < NR_CLDMA; i++) {
>+		drv_info = cd->cldma_drv_info[i];
>+		if (!drv_info)
>+			continue;
>+
>+		drv_ops = drv_info->drv_ops;
>+
>+		/* Restart TX queues that have pending descriptors */
>+		for (q = 0; q < HW_QUEUE_NUM; q++) {
>+			txq = drv_info->txq[q];
>+			if (!txq)
>+				continue;
>+			if (atomic_read(&txq->req_budget) < txq->nr_gpds)
>+				mtk_cldma_start_xfer(drv_info, q);
>+		}
>+	}
>+}
>+
> static int mtk_cldma_open(struct cldma_dev *cd, struct sk_buff *skb)
> {
> 	struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
>index 04f83ff0e37d..fd39985f75e7 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
>@@ -163,6 +163,9 @@ int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno);
> int mtk_cldma_trb_process(void *dev, struct sk_buff *skb);
> void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans);
> int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que);
>+void mtk_cldma_pm_suspend(struct mtk_md_dev *mdev);
>+void mtk_cldma_pm_resume_early(struct mtk_md_dev *mdev);
>+void mtk_cldma_pm_resume(struct mtk_md_dev *mdev);
> 
> #define drv_ops_name(NAME) cldma_drv_ops_##NAME
> #define cldma_regs_name(NAME) mtk_cldma_regs_##NAME
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.c b/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.c
>index 43803587bfc3..63273a85e532 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.c
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.c
>@@ -205,6 +205,7 @@ struct mtk_dpmaif_ctlb {
> 	struct dpmaif_irq_param *irq_params;
> 
> 	bool dpmaif_sw_reset;
>+	bool trans_enabled;
> 	unsigned char rxq_cnt;
> 	unsigned char txq_cnt;
> };
>@@ -1687,10 +1688,16 @@ static void mtk_dpmaif_trans_disable(struct mtk_dpmaif_ctlb *dcb)
> static void mtk_dpmaif_trans_ctl(struct mtk_dpmaif_ctlb *dcb, bool enable)
> {
> 	if (enable) {
>-		if (dcb->dpmaif_state == DPMAIF_STATE_PWRON)
>+		if (!dcb->trans_enabled &&
>+		    dcb->dpmaif_state == DPMAIF_STATE_PWRON) {
>+			dcb->trans_enabled = true;
> 			mtk_dpmaif_trans_enable(dcb);
>+		}
> 	} else {
>-		mtk_dpmaif_trans_disable(dcb);
>+		if (dcb->trans_enabled) {

should it be done unconditionally from DPMAIF_STATE_PWRON?

>+			dcb->trans_enabled = false;
>+			mtk_dpmaif_trans_disable(dcb);
>+		}
> 	}
> }
> 
>@@ -2060,6 +2067,30 @@ static int mtk_dpmaif_stop(struct mtk_md_dev *mdev)
> 	return 0;
> }
> 
>+void mtk_dpmaif_pm_suspend(struct mtk_md_dev *mdev)
>+{
>+	struct mtk_dpmaif_ctlb *dcb = ((struct mtk_data_blk *)(mdev->data_blk))->dcb;
>+
>+	if (!dcb)
>+		return;
>+
>+	mutex_lock(&dcb->trans_ctl_lock);
>+	mtk_dpmaif_trans_ctl(dcb, false);
>+	mutex_unlock(&dcb->trans_ctl_lock);
>+}
>+
>+void mtk_dpmaif_pm_resume(struct mtk_md_dev *mdev)
>+{
>+	struct mtk_dpmaif_ctlb *dcb = ((struct mtk_data_blk *)(mdev->data_blk))->dcb;
>+
>+	if (!dcb)
>+		return;
>+
>+	mutex_lock(&dcb->trans_ctl_lock);
>+	mtk_dpmaif_trans_ctl(dcb, true);
>+	mutex_unlock(&dcb->trans_ctl_lock);
>+}
>+
> static void mtk_dpmaif_clear(struct mtk_md_dev *mdev)
> {
> 	struct mtk_dpmaif_ctlb *dcb = ((struct mtk_data_blk *)(mdev->data_blk))->dcb;
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.h b/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.h
>index e7e2f333141c..20fd53fd44b5 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.h
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_dpmaif.h
>@@ -10,5 +10,7 @@
> 
> int mtk_pcie_data_init(struct mtk_md_dev *mdev);
> int mtk_pcie_data_exit(struct mtk_md_dev *mdev);
>+void mtk_dpmaif_pm_suspend(struct mtk_md_dev *mdev);
>+void mtk_dpmaif_pm_resume(struct mtk_md_dev *mdev);
> 
> #endif /* __MTK_DPMAIF_H__ */
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>index baac3692f1e3..f659c9a7aa96 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>@@ -11,8 +11,10 @@
> #include <linux/device.h>
> #include <linux/dma-mapping.h>
> #include <linux/kernel.h>
>+#include <linux/kthread.h>
> #include <linux/module.h>
> 
>+#include "mtk_cldma.h"
> #include "mtk_dev.h"
> #include "mtk_dpmaif.h"
> #include "mtk_trans_ctrl.h"
>@@ -199,7 +201,6 @@ int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
> 	}
> 	priv->irq_cb_list[irq_id] = irq_cb;
> 	priv->irq_cb_data[irq_id] = data;
>-
> 	return 0;
> }
> 
>@@ -970,11 +971,93 @@ static const struct pci_error_handlers mtk_pci_err_handler = {
> 	.error_detected = mtk_pci_error_detected,
> };
> 
>+static void mtk_pci_pm_trb_park(struct mtk_md_dev *mdev)
>+{
>+	struct mtk_ctrl_trans *trans = ((struct mtk_ctrl_blk *)mdev->ctrl_blk)->ctrl_hw_priv;
>+	int i;
>+
>+	for (i = 0; i < trans->trb_srv_num; i++)
>+		kthread_park(trans->trb_srv[i]->trb_thread);
>+}
>+
>+static void mtk_pci_pm_trb_unpark(struct mtk_md_dev *mdev)
>+{
>+	struct mtk_ctrl_trans *trans = ((struct mtk_ctrl_blk *)mdev->ctrl_blk)->ctrl_hw_priv;
>+	int i;
>+
>+	for (i = 0; i < trans->trb_srv_num; i++)
>+		kthread_unpark(trans->trb_srv[i]->trb_thread);
>+}
>+
>+static int __maybe_unused mtk_pci_pm_suspend(struct device *dev)
>+{
>+	struct pci_dev *pdev = to_pci_dev(dev);
>+	struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
>+	struct mtk_pci_priv *priv = mdev->hw_priv;

RCT

>+
>+	mtk_pci_pm_trb_park(mdev);
>+
>+	mtk_cldma_pm_suspend(mdev);
>+
>+	mtk_dpmaif_pm_suspend(mdev);
>+
>+	/* Mask MHCCIF interrupt */
>+	mtk_pci_mask_irq(mdev, priv->mhccif_irq_id);
>+
>+	/* Mask all MSI-X interrupts at the device level */
>+	mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, U32_MAX);
>+
>+	/* Save PCI configuration space */
>+	pci_save_state(pdev);
>+
>+	return 0;
>+}
>+
>+static int __maybe_unused mtk_pci_pm_resume(struct device *dev)
>+{
>+	struct pci_dev *pdev = to_pci_dev(dev);
>+	struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
>+	struct mtk_pci_priv *priv = mdev->hw_priv;
>+	int ret;
>+
>+	/* Restore PCIe configuration space (including MSI-X enable bits) */

these comments are rather obvious

>+	pci_restore_state(pdev);
>+
>+	/* Re-enable bus mastering for DMA */
>+	pci_set_master(pdev);
>+
>+	/* Restore ATR (address translation registers in MMIO BAR space) */
>+	ret = priv->cfg->atr_init(mdev);
>+	if (ret) {
>+		dev_err(mdev->dev, "PM: failed to re-init ATR on resume\n");
>+		return ret;
>+	}
>+
>+	/* Unmask MHCCIF interrupt */
>+	mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
>+
>+	mtk_cldma_pm_resume_early(mdev);
>+
>+	/* Restart CLDMA TX queues that have pending descriptors */
>+	mtk_cldma_pm_resume(mdev);
>+
>+	mtk_dpmaif_pm_resume(mdev);
>+
>+	mtk_pci_pm_trb_unpark(mdev);
>+
>+	return 0;
>+}
>+
>+static const struct dev_pm_ops mtk_pci_pm_ops = {
>+	SET_SYSTEM_SLEEP_PM_OPS(mtk_pci_pm_suspend, mtk_pci_pm_resume)
>+};
>+
> static struct pci_driver mtk_pci_drv = {
> 	.name = "mtk_pci_drv",
> 	.id_table = t9xx_pci_table,
> 	.probe = mtk_pci_probe,
> 	.remove = mtk_pci_remove,
>+	.driver.pm = &mtk_pci_pm_ops,
> 	.err_handler = &mtk_pci_err_handler
> };
> 
>
>-- 
>2.34.1





More information about the linux-arm-kernel mailing list