accel/qaic: Add support for PM callbacks

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
This commit is contained in:
Youssef Samir
2025-10-29 11:18:12 -07:00
committed by Jeff Hugo
parent 3a0ff7b98a
commit 3301ef0a72
4 changed files with 103 additions and 0 deletions

View 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 */

View 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)

View 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);

View 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__ */