]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
accel/qaic: Add support for PM callbacks
authorYoussef Samir <youssef.abdulrahman@oss.qualcomm.com>
Wed, 29 Oct 2025 18:18:12 +0000 (11:18 -0700)
committerJeff Hugo <jeff.hugo@oss.qualcomm.com>
Wed, 5 Nov 2025 22:41:43 +0000 (15:41 -0700)
Add initial support for suspend and hibernation PM callbacks to QAIC.
The device can be suspended any time in which the data path is not
busy as queued I/O operations are lost on suspension and cannot be
resumed after suspend.

Signed-off-by: Youssef Samir <youssef.abdulrahman@oss.qualcomm.com>
Reviewed-by: Carl Vanderlip <carl.vanderlip@oss.qualcomm.com>
Signed-off-by: Zack McKevitt <zachary.mckevitt@oss.qualcomm.com>
Reviewed-by: Jeff Hugo <jeff.hugo@oss.qualcomm.com>
Signed-off-by: Jeff Hugo <jeff.hugo@oss.qualcomm.com>
Link: https://patch.msgid.link/20251029181808.1216466-1-zachary.mckevitt@oss.qualcomm.com
drivers/accel/qaic/qaic.h
drivers/accel/qaic/qaic_drv.c
drivers/accel/qaic/qaic_timesync.c
drivers/accel/qaic/qaic_timesync.h

index 820d133236dd19ce411a901bb569f6673a0c7d35..2bfc4ce203c5342b67b110f3f44af8e2b4cc2fcc 100644 (file)
@@ -161,6 +161,8 @@ struct qaic_device {
        struct mhi_device       *qts_ch;
        /* Work queue for tasks related to MHI "QAIC_TIMESYNC" channel */
        struct workqueue_struct *qts_wq;
+       /* MHI "QAIC_TIMESYNC_PERIODIC" channel device */
+       struct mhi_device       *mqts_ch;
        /* Head of list of page allocated by MHI bootlog device */
        struct list_head        bootlog;
        /* MHI bootlog channel device */
index e162f4b8a262abca4ac5e7bd8b187ca2061ad191..1b5dc717a6c79ef5bad7bfb990d48c8cd19a4b84 100644 (file)
@@ -660,6 +660,92 @@ static const struct pci_error_handlers qaic_pci_err_handler = {
        .reset_done = qaic_pci_reset_done,
 };
 
+static bool qaic_is_under_reset(struct qaic_device *qdev)
+{
+       int rcu_id;
+       bool ret;
+
+       rcu_id = srcu_read_lock(&qdev->dev_lock);
+       ret = qdev->dev_state != QAIC_ONLINE;
+       srcu_read_unlock(&qdev->dev_lock, rcu_id);
+       return ret;
+}
+
+static bool qaic_data_path_busy(struct qaic_device *qdev)
+{
+       bool ret = false;
+       int dev_rcu_id;
+       int i;
+
+       dev_rcu_id = srcu_read_lock(&qdev->dev_lock);
+       if (qdev->dev_state != QAIC_ONLINE) {
+               srcu_read_unlock(&qdev->dev_lock, dev_rcu_id);
+               return false;
+       }
+       for (i = 0; i < qdev->num_dbc; i++) {
+               struct dma_bridge_chan *dbc = &qdev->dbc[i];
+               unsigned long flags;
+               int ch_rcu_id;
+
+               ch_rcu_id = srcu_read_lock(&dbc->ch_lock);
+               if (!dbc->usr || !dbc->in_use) {
+                       srcu_read_unlock(&dbc->ch_lock, ch_rcu_id);
+                       continue;
+               }
+               spin_lock_irqsave(&dbc->xfer_lock, flags);
+               ret = !list_empty(&dbc->xfer_list);
+               spin_unlock_irqrestore(&dbc->xfer_lock, flags);
+               srcu_read_unlock(&dbc->ch_lock, ch_rcu_id);
+               if (ret)
+                       break;
+       }
+       srcu_read_unlock(&qdev->dev_lock, dev_rcu_id);
+       return ret;
+}
+
+static int qaic_pm_suspend(struct device *dev)
+{
+       struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
+
+       dev_dbg(dev, "Suspending..\n");
+       if (qaic_data_path_busy(qdev)) {
+               dev_dbg(dev, "Device's datapath is busy. Aborting suspend..\n");
+               return -EBUSY;
+       }
+       if (qaic_is_under_reset(qdev)) {
+               dev_dbg(dev, "Device is under reset. Aborting suspend..\n");
+               return -EBUSY;
+       }
+       qaic_mqts_ch_stop_timer(qdev->mqts_ch);
+       qaic_pci_reset_prepare(qdev->pdev);
+       pci_save_state(qdev->pdev);
+       pci_disable_device(qdev->pdev);
+       pci_set_power_state(qdev->pdev, PCI_D3hot);
+       return 0;
+}
+
+static int qaic_pm_resume(struct device *dev)
+{
+       struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
+       int ret;
+
+       dev_dbg(dev, "Resuming..\n");
+       pci_set_power_state(qdev->pdev, PCI_D0);
+       pci_restore_state(qdev->pdev);
+       ret = pci_enable_device(qdev->pdev);
+       if (ret) {
+               dev_err(dev, "pci_enable_device failed on resume %d\n", ret);
+               return ret;
+       }
+       pci_set_master(qdev->pdev);
+       qaic_pci_reset_done(qdev->pdev);
+       return 0;
+}
+
+static const struct dev_pm_ops qaic_pm_ops = {
+       SYSTEM_SLEEP_PM_OPS(qaic_pm_suspend, qaic_pm_resume)
+};
+
 static struct pci_driver qaic_pci_driver = {
        .name = QAIC_NAME,
        .id_table = qaic_ids,
@@ -667,6 +753,9 @@ static struct pci_driver qaic_pci_driver = {
        .remove = qaic_pci_remove,
        .shutdown = qaic_pci_shutdown,
        .err_handler = &qaic_pci_err_handler,
+       .driver = {
+               .pm = pm_sleep_ptr(&qaic_pm_ops),
+       },
 };
 
 static int __init qaic_init(void)
index 3fac540f8e03d776b48076284d839b0f45872b4b..8af2475f4f3692cc0146c39af27cbce804482103 100644 (file)
@@ -171,6 +171,13 @@ mod_timer:
                dev_err(mqtsdev->dev, "%s mod_timer error:%d\n", __func__, ret);
 }
 
+void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev)
+{
+       struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
+
+       timer_delete_sync(&mqtsdev->timer);
+}
+
 static int qaic_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
 {
        struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
@@ -206,6 +213,7 @@ static int qaic_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_devi
        timer->expires = jiffies + msecs_to_jiffies(timesync_delay_ms);
        add_timer(timer);
        dev_set_drvdata(&mhi_dev->dev, mqtsdev);
+       qdev->mqts_ch = mhi_dev;
 
        return 0;
 
@@ -221,6 +229,7 @@ static void qaic_timesync_remove(struct mhi_device *mhi_dev)
 {
        struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
 
+       mqtsdev->qdev->mqts_ch = NULL;
        timer_delete_sync(&mqtsdev->timer);
        mhi_unprepare_from_transfer(mqtsdev->mhi_dev);
        kfree(mqtsdev->sync_msg);
index 851b7acd43bbbdb43a6170792e1dc91db1cfd360..77b9c2b55057cf6e479ba4a24dfffd9999e23a92 100644 (file)
@@ -6,6 +6,9 @@
 #ifndef __QAIC_TIMESYNC_H__
 #define __QAIC_TIMESYNC_H__
 
+#include <linux/mhi.h>
+
 int qaic_timesync_init(void);
 void qaic_timesync_deinit(void);
+void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev);
 #endif /* __QAIC_TIMESYNC_H__ */