Add initial support for suspend and hibernation PM callbacks to QAIC.

Signed-off-by: Youssef Samir <[email protected]>
---
 drivers/accel/qaic/qaic.h          |  2 +
 drivers/accel/qaic/qaic_drv.c      | 89 ++++++++++++++++++++++++++++++
 drivers/accel/qaic/qaic_timesync.c |  9 +++
 drivers/accel/qaic/qaic_timesync.h |  3 +
 4 files changed, 103 insertions(+)

diff --git a/drivers/accel/qaic/qaic.h b/drivers/accel/qaic/qaic.h
index 820d133236dd..2bfc4ce203c5 100644
--- a/drivers/accel/qaic/qaic.h
+++ b/drivers/accel/qaic/qaic.h
@@ -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 */
diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c
index e162f4b8a262..6cf0f42c9add 100644
--- a/drivers/accel/qaic/qaic_drv.c
+++ b/drivers/accel/qaic/qaic_drv.c
@@ -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)
+{
+       int dev_rcu_id;
+       bool ret;
+       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 = {
+       SET_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 = &qaic_pm_ops,
+       },
 };
 
 static int __init qaic_init(void)
diff --git a/drivers/accel/qaic/qaic_timesync.c 
b/drivers/accel/qaic/qaic_timesync.c
index 3fac540f8e03..8af2475f4f36 100644
--- a/drivers/accel/qaic/qaic_timesync.c
+++ b/drivers/accel/qaic/qaic_timesync.c
@@ -171,6 +171,13 @@ static void qaic_timesync_timer(struct timer_list *t)
                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);
diff --git a/drivers/accel/qaic/qaic_timesync.h 
b/drivers/accel/qaic/qaic_timesync.h
index 851b7acd43bb..77b9c2b55057 100644
--- a/drivers/accel/qaic/qaic_timesync.h
+++ b/drivers/accel/qaic/qaic_timesync.h
@@ -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__ */
-- 
2.43.0

Reply via email to