[PATCH char-misc-next v2 2/4] issei: add runtime pm
From: Alexander Usyskin
Date: Sun Oct 18 2026 - 05:31:55 EST
Add support for runtime power management.
ISSE communication is based on send-receive transaction.
Allow autosuspend with 60 seconds grace as firmware required
to answer inside this timeframe.
Signed-off-by: Alexander Usyskin <alexander.usyskin@xxxxxxxxx>
---
drivers/misc/issei/host_client.c | 4 +
drivers/misc/issei/hw_heci.c | 21 +++-
drivers/misc/issei/main.c | 229 +++++++++++++++++++++------------------
drivers/misc/issei/pci_heci.c | 21 ++++
4 files changed, 168 insertions(+), 107 deletions(-)
diff --git a/drivers/misc/issei/host_client.c b/drivers/misc/issei/host_client.c
index 8f36d1c319ec..d16b5835c619 100644
--- a/drivers/misc/issei/host_client.c
+++ b/drivers/misc/issei/host_client.c
@@ -7,6 +7,7 @@
#include <linux/kobject.h>
#include <linux/list.h>
#include <linux/overflow.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/wait.h>
@@ -73,6 +74,7 @@ static void __issei_cl_clean_all_wbuf(struct issei_device *idev, struct issei_ho
}
}
cl->write_in_progress = false;
+ pm_runtime_put_autosuspend(idev->parent);
/* synchronized under host client mutex */
if (waitqueue_active(&cl->write_wait))
wake_up_interruptible(&cl->write_wait);
@@ -309,6 +311,7 @@ ssize_t issei_cl_write(struct issei_host_client *cl, const u8 *buf, size_t buf_s
list_add_tail(&wbuf->list, &idev->write_queue);
cl->write_in_progress = true;
cl_dbg(idev, cl, "Write queued %zu bytes\n", buf_size);
+ pm_runtime_get(idev->parent);
issei_poke_process_thread(idev);
@@ -353,6 +356,7 @@ int issei_cl_write_from_queue(struct issei_device *idev)
if (ret >= 0)
idev->ops->irq_write_generate(idev);
cl->write_in_progress = false;
+ pm_runtime_put_autosuspend(idev->parent);
/* synchronized under host client mutex */
if (waitqueue_active(&cl->write_wait))
wake_up_interruptible(&cl->write_wait);
diff --git a/drivers/misc/issei/hw_heci.c b/drivers/misc/issei/hw_heci.c
index 501774aa95ff..1e39b285d4db 100644
--- a/drivers/misc/issei/hw_heci.c
+++ b/drivers/misc/issei/hw_heci.c
@@ -8,6 +8,7 @@
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pci.h>
+#include <linux/pm_runtime.h>
#include <linux/sizes.h>
#include <linux/spinlock.h>
#include <linux/types.h>
@@ -501,12 +502,20 @@ irqreturn_t issei_heci_irq_quick_handler(int irq, void *dev_id)
{
struct issei_device *idev = dev_id;
struct issei_heci_hw *hw = to_heci_hw(idev);
+ irqreturn_t ret;
+ int active;
u32 reg;
- reg = heci_hcsr_read(idev);
- if (!heci_irq_src(reg))
+ active = pm_runtime_get_if_active(idev->parent);
+ if (!active)
return IRQ_NONE;
+ reg = heci_hcsr_read(idev);
+ if (!heci_irq_src(reg)) {
+ ret = IRQ_NONE;
+ goto out;
+ }
+
scoped_guard(spinlock_irqsave, &hw->access_lock) {
reg = heci_hcsr_read(idev);
heci_irq_clear(idev, reg);
@@ -519,7 +528,13 @@ irqreturn_t issei_heci_irq_quick_handler(int irq, void *dev_id)
issei_poke_process_thread(idev);
- return IRQ_HANDLED;
+ ret = IRQ_HANDLED;
+
+out:
+ if (active > 0)
+ pm_runtime_put_autosuspend(idev->parent);
+
+ return ret;
}
static const struct hw_heci_cfg hw_heci_pch_cfg = {
diff --git a/drivers/misc/issei/main.c b/drivers/misc/issei/main.c
index 8aaf2d47392b..c0b8606b17b3 100644
--- a/drivers/misc/issei/main.c
+++ b/drivers/misc/issei/main.c
@@ -7,6 +7,7 @@
#include <linux/export.h>
#include <linux/jiffies.h>
#include <linux/kthread.h>
+#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/wait.h>
@@ -83,132 +84,152 @@ static int issei_process_write_msg(struct issei_device *idev)
return issei_cl_write_from_queue(idev);
}
-static int issei_process_thread(void *_dev)
+static bool issei_process_thread_worker(struct issei_device *idev, bool power_down,
+ long *timeout, long *old_timeout)
{
- long timeout, old_timeout = MAX_SCHEDULE_TIMEOUT;
- struct issei_device *idev = _dev;
- bool power_down;
int ret;
- while (!kthread_should_stop()) {
- dev_dbg(&idev->dev, "process_work in %d\n", idev->rst_state);
- WRITE_ONCE(idev->has_data, false);
- power_down = idev->power_down; /* to save state */
- if (!idev->ops->hw_is_ready(idev) && idev->rst_state > ISSEI_RST_STATE_HW_READY) {
- if (!power_down)
- dev_dbg(&idev->dev, "HW not ready, resetting\n");
- idev->rst_state = ISSEI_RST_STATE_INIT;
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(idev->parent, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret) {
+ dev_err(&idev->dev, "Failed to acquire pm %d\n", ret);
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ return false;
+ }
+
+ if (!idev->ops->hw_is_ready(idev) && idev->rst_state > ISSEI_RST_STATE_HW_READY) {
+ if (!power_down)
+ dev_dbg(&idev->dev, "HW not ready, resetting\n");
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ }
+ if (power_down)
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ dev_dbg(&idev->dev, "reset_step in %d\n", idev->rst_state);
+ *timeout = MAX_SCHEDULE_TIMEOUT;
+ ret = 0;
+ switch (idev->rst_state) {
+ case ISSEI_RST_STATE_DISABLED:
+ if (power_down) {
+ dev_dbg(&idev->dev, "Interrupt in power down?\n");
+ break;
}
- if (power_down)
- idev->rst_state = ISSEI_RST_STATE_INIT;
- dev_dbg(&idev->dev, "reset_step in %d\n", idev->rst_state);
- timeout = MAX_SCHEDULE_TIMEOUT;
- ret = 0;
- switch (idev->rst_state) {
- case ISSEI_RST_STATE_DISABLED:
- if (power_down) {
- dev_dbg(&idev->dev, "Interrupt in power down?\n");
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ idev->reset_count = 0;
+ fallthrough;
+
+ case ISSEI_RST_STATE_INIT:
+ idev->ops->irq_clear(idev);
+ idev->ops->irq_sync(idev);
+
+ if (!power_down) {
+ if (!idev->reset_count)
+ pm_runtime_get_noresume(idev->parent);
+ idev->reset_count++;
+ if (idev->reset_count > ISSEI_MAX_CONSEC_RESET) {
+ dev_err(&idev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
+ issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
+ pm_runtime_put_noidle(idev->parent);
break;
}
- idev->rst_state = ISSEI_RST_STATE_INIT;
- fallthrough;
-
- case ISSEI_RST_STATE_INIT:
- idev->ops->irq_clear(idev);
- idev->ops->irq_sync(idev);
-
- if (!power_down) {
- idev->reset_count++;
- if (idev->reset_count > ISSEI_MAX_CONSEC_RESET) {
- dev_err(&idev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
- issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
- break;
- }
- }
-
- ret = issei_reset(idev, power_down);
- if (power_down) {
- dev_dbg(&idev->dev, "Powering down\n");
- return 0;
- }
- if (ret)
- break;
+ }
- idev->rst_state = ISSEI_RST_STATE_HW_READY;
- timeout = msecs_to_jiffies(ISSEI_RST_HW_READY_TIMEOUT_MSEC);
+ ret = issei_reset(idev, power_down);
+ if (power_down) {
+ dev_dbg(&idev->dev, "Powering down\n");
+ return true;
+ }
+ if (ret)
break;
- case ISSEI_RST_STATE_HW_READY:
- if (!idev->ops->hw_is_ready(idev)) {
- dev_dbg(&idev->dev, "HW is not ready?\n");
- timeout = old_timeout;
- break;
- }
-
- dev_dbg(&idev->dev, "HW is ready\n");
- idev->ops->hw_reset_release(idev);
- idev->ops->host_set_ready(idev);
- ret = idev->ops->setup_message_send(idev);
- if (ret)
- break;
+ idev->rst_state = ISSEI_RST_STATE_HW_READY;
+ *timeout = msecs_to_jiffies(ISSEI_RST_HW_READY_TIMEOUT_MSEC);
+ break;
- idev->rst_state = ISSEI_RST_STATE_SETUP;
- timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
+ case ISSEI_RST_STATE_HW_READY:
+ if (!idev->ops->hw_is_ready(idev)) {
+ dev_dbg(&idev->dev, "HW is not ready?\n");
+ *timeout = *old_timeout;
break;
+ }
- case ISSEI_RST_STATE_SETUP:
- ret = idev->ops->setup_message_recv(idev);
- if (ret) {
- if (ret == -ENODATA) {
- ret = 0;
- timeout = old_timeout;
- }
- } else {
- timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
- ret = issei_ham_send_start_req(idev);
- idev->rst_state = ISSEI_RST_STATE_START;
- }
+ dev_dbg(&idev->dev, "HW is ready\n");
+ idev->ops->hw_reset_release(idev);
+ idev->ops->host_set_ready(idev);
+ ret = idev->ops->setup_message_send(idev);
+ if (ret)
break;
- case ISSEI_RST_STATE_START:
- ret = issei_process_read_msg(idev);
- if (!ret) {
- timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
- idev->rst_state = ISSEI_RST_STATE_CLIENT_ENUM;
- } else if (ret == -ENODATA) {
+ idev->rst_state = ISSEI_RST_STATE_SETUP;
+ *timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
+ break;
+
+ case ISSEI_RST_STATE_SETUP:
+ ret = idev->ops->setup_message_recv(idev);
+ if (ret) {
+ if (ret == -ENODATA) {
ret = 0;
- timeout = old_timeout;
+ *timeout = *old_timeout;
}
- break;
+ } else {
+ *timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
+ ret = issei_ham_send_start_req(idev);
+ idev->rst_state = ISSEI_RST_STATE_START;
+ }
+ break;
+
+ case ISSEI_RST_STATE_START:
+ ret = issei_process_read_msg(idev);
+ if (!ret) {
+ *timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
+ idev->rst_state = ISSEI_RST_STATE_CLIENT_ENUM;
+ } else if (ret == -ENODATA) {
+ ret = 0;
+ *timeout = *old_timeout;
+ }
+ break;
- case ISSEI_RST_STATE_CLIENT_ENUM:
- ret = issei_process_read_msg(idev);
- if (ret) {
- if (ret == -ENODATA) {
- ret = 0;
- timeout = old_timeout;
- }
- } else {
- idev->reset_count = 0;
- idev->rst_state = ISSEI_RST_STATE_DONE;
- dev_dbg(&idev->dev, "Reset finished successfully\n");
+ case ISSEI_RST_STATE_CLIENT_ENUM:
+ ret = issei_process_read_msg(idev);
+ if (ret) {
+ if (ret == -ENODATA) {
+ ret = 0;
+ *timeout = *old_timeout;
}
+ } else {
+ idev->reset_count = 0;
+ issei_rst_state_set(idev, ISSEI_RST_STATE_DONE);
+ pm_runtime_put_autosuspend(idev->parent);
+ dev_dbg(&idev->dev, "Reset finished successfully\n");
+ }
+ break;
+
+ case ISSEI_RST_STATE_DONE:
+ ret = issei_process_read_msg(idev);
+ if (ret != 0 && ret != -ENODATA)
break;
- case ISSEI_RST_STATE_DONE:
- ret = issei_process_read_msg(idev);
- if (ret != 0 && ret != -ENODATA)
- break;
+ ret = issei_process_write_msg(idev);
+ break;
+ }
- ret = issei_process_write_msg(idev);
- break;
- }
+ if (ret) {
+ dev_warn(&idev->dev, "Process failed ret = %d\n", ret);
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ }
+ return false;
+}
- if (ret) {
- dev_warn(&idev->dev, "Process failed ret = %d\n", ret);
- idev->rst_state = ISSEI_RST_STATE_INIT;
- continue;
- }
+static int issei_process_thread(void *_dev)
+{
+ long timeout, old_timeout = MAX_SCHEDULE_TIMEOUT;
+ struct issei_device *idev = _dev;
+
+ while (!kthread_should_stop()) {
+ dev_dbg(&idev->dev, "process_work in %d\n", idev->rst_state);
+ WRITE_ONCE(idev->has_data, false);
+
+ if (issei_process_thread_worker(idev, idev->power_down, &timeout, &old_timeout))
+ return 0;
/*
* Every thread that has data to process sets the 'has_data' flag and
diff --git a/drivers/misc/issei/pci_heci.c b/drivers/misc/issei/pci_heci.c
index 86c567a4bf0f..c58b51b3aad3 100644
--- a/drivers/misc/issei/pci_heci.c
+++ b/drivers/misc/issei/pci_heci.c
@@ -8,11 +8,15 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/time.h>
#include "cdev.h"
#include "hw_heci.h"
#include "hw_heci_regs.h"
+#define HW_HECI_RPM_TIMEOUT_MSEC (60 * MSEC_PER_SEC)
+
static int issei_heci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct device *dev = &pdev->dev;
@@ -70,6 +74,18 @@ static int issei_heci_probe(struct pci_dev *pdev, const struct pci_device_id *en
goto free_irq;
}
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, HW_HECI_RPM_TIMEOUT_MSEC);
+
+ /*
+ * ISSEI requires to resume from runtime suspend mode
+ * in order to perform link reset flow upon system suspend.
+ */
+ dev_pm_set_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE);
+
+ pm_runtime_put_noidle(dev);
+ pm_runtime_allow(dev);
+
return 0;
free_irq:
@@ -86,6 +102,11 @@ static void issei_heci_shutdown(struct pci_dev *pdev)
{
struct issei_device *idev = pci_get_drvdata(pdev);
struct issei_heci_hw *hw = to_heci_hw(idev);
+ struct device *dev = &pdev->dev;
+
+ pm_runtime_dont_use_autosuspend(dev);
+ pm_runtime_forbid(dev);
+ pm_runtime_get_noresume(dev);
issei_stop(idev);
--
2.53.0