[PATCH 16/17] i3c: mipi-i3c-hci: Add optional Runtime PM support

Frank Li Frank.li at nxp.com
Fri Dec 19 09:16:49 PST 2025


On Fri, Dec 19, 2025 at 04:45:33PM +0200, Adrian Hunter wrote:
> Implement optional Runtime PM support for the MIPI I3C HCI driver.
> Introduce runtime suspend and resume callbacks to manage bus state and
> restore hardware configuration after resume.  Optionally enable autosuspend
> with a default delay of 1 second, and add helper functions to control
> Runtime PM during probe and remove.
>
> Read quirks from i3c_hci_driver_ids[] and set new quirk
> HCI_QUIRK_RPM_ALLOWED for intel-lpss-i3c devices to enable runtime PM for
> them.
>
> Signed-off-by: Adrian Hunter <adrian.hunter at intel.com>
> ---
>  drivers/i3c/master/mipi-i3c-hci/core.c | 96 +++++++++++++++++++++++++-
>  drivers/i3c/master/mipi-i3c-hci/hci.h  |  2 +
>  2 files changed, 96 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
> index 3b0609cb7da7..9a8d34ae4a66 100644
> --- a/drivers/i3c/master/mipi-i3c-hci/core.c
> +++ b/drivers/i3c/master/mipi-i3c-hci/core.c
> @@ -16,6 +16,7 @@
>  #include <linux/module.h>
>  #include <linux/platform_data/mipi-i3c-hci.h>
>  #include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
>
>  #include "hci.h"
>  #include "ext_caps.h"
> @@ -176,6 +177,7 @@ void i3c_hci_sync_irq_inactive(struct i3c_hci *hci)
>  	int irq = platform_get_irq(pdev, 0);
>
>  	reg_write(INTR_SIGNAL_ENABLE, 0x0);
> +	hci->irq_inactive = true;
>  	synchronize_irq(irq);
>  }
>
> @@ -558,6 +560,14 @@ static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
>  	irqreturn_t result = IRQ_NONE;
>  	u32 val;
>
> +	/*
> +	 * The IRQ can be shared, so the handler may be called when the IRQ is
> +	 * due to a different device. That could happen when runtime suspended,
> +	 * so exit immediately if IRQs are not expected for this device.
> +	 */
> +	if (hci->irq_inactive)
> +		return IRQ_NONE;
> +
>  	val = reg_read(INTR_STATUS);
>  	reg_write(INTR_STATUS, val);
>  	dev_dbg(&hci->master.dev, "INTR_STATUS %#x", val);
> @@ -613,6 +623,70 @@ static int i3c_hci_software_reset(struct i3c_hci *hci)
>  	return 0;
>  }
>
> +static int i3c_hci_runtime_suspend(struct device *dev)
> +{
> +	struct i3c_hci *hci = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = i3c_hci_bus_disable(hci);
> +	if (ret)
> +		return ret;
> +
> +	hci->io->suspend(hci);
> +
> +	return 0;
> +}
> +
> +static int i3c_hci_runtime_resume(struct device *dev)
> +{
> +	struct i3c_hci *hci = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = i3c_hci_software_reset(hci);
> +	if (ret)
> +		return -EIO;
> +
> +	reg_write(INTR_SIGNAL_ENABLE, 0x0);
> +	reg_write(INTR_STATUS_ENABLE, GENMASK(31, 10));
> +
> +	reg_clear(HC_CONTROL, HC_CONTROL_PIO_MODE);
> +
> +	reg_write(MASTER_DEVICE_ADDR,
> +		  MASTER_DYNAMIC_ADDR(hci->master.this->info.dyn_addr) |
> +		  MASTER_DYNAMIC_ADDR_VALID);
> +

Is it possible move these register write to one of help function?

> +	mipi_i3c_hci_dat_v1.restore(hci);
> +
> +	hci->irq_inactive = false;
> +
> +	hci->io->resume(hci);
> +
> +	reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE);
> +
> +	return 0;
> +}
> +
> +#define DEFAULT_AUTOSUSPEND_DELAY_MS 1000
> +
> +static void i3c_hci_rpm_enable(struct device *dev)
> +{
> +	struct i3c_hci *hci = dev_get_drvdata(dev);
> +
> +	pm_runtime_set_autosuspend_delay(dev, DEFAULT_AUTOSUSPEND_DELAY_MS);
> +	pm_runtime_use_autosuspend(dev);
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);

use devm_pm_runtime_enable() can simple some code

> +
> +	hci->master.rpm_allowed = true;
> +}
> +
> +static void i3c_hci_rpm_disable(struct device *dev)
> +{
> +	pm_runtime_disable(dev);
> +	pm_runtime_set_suspended(dev);
> +	pm_runtime_dont_use_autosuspend(dev);
> +}
> +
>  static int i3c_hci_init(struct i3c_hci *hci)
>  {
>  	bool size_in_dwords, mode_selector;
> @@ -806,6 +880,8 @@ static int i3c_hci_probe(struct platform_device *pdev)
>  	hci->master.dev.init_name = dev_name(&pdev->dev);
>
>  	hci->quirks = (unsigned long)device_get_match_data(&pdev->dev);
> +	if (!hci->quirks && platform_get_device_id(pdev))
> +		hci->quirks = platform_get_device_id(pdev)->driver_data;
>
>  	ret = i3c_hci_init(hci);
>  	if (ret)
> @@ -817,12 +893,20 @@ static int i3c_hci_probe(struct platform_device *pdev)
>  	if (ret)
>  		return ret;
>
> +	if (hci->quirks & HCI_QUIRK_RPM_ALLOWED)
> +		i3c_hci_rpm_enable(&pdev->dev);
> +
>  	ret = i3c_master_register(&hci->master, &pdev->dev,
>  				  &i3c_hci_ops, false);
>  	if (ret)
> -		return ret;
> +		goto err_rpm_disable;
>
>  	return 0;
> +
> +err_rpm_disable:
> +	if (hci->quirks & HCI_QUIRK_RPM_ALLOWED)
> +		i3c_hci_rpm_disable(&pdev->dev);
> +	return ret;
>  }
>
>  static void i3c_hci_remove(struct platform_device *pdev)
> @@ -830,6 +914,9 @@ static void i3c_hci_remove(struct platform_device *pdev)
>  	struct i3c_hci *hci = platform_get_drvdata(pdev);
>
>  	i3c_master_unregister(&hci->master);
> +
> +	if (hci->quirks & HCI_QUIRK_RPM_ALLOWED)
> +		i3c_hci_rpm_disable(&pdev->dev);
>  }
>
>  static const __maybe_unused struct of_device_id i3c_hci_of_match[] = {
> @@ -845,11 +932,15 @@ static const struct acpi_device_id i3c_hci_acpi_match[] = {
>  MODULE_DEVICE_TABLE(acpi, i3c_hci_acpi_match);
>
>  static const struct platform_device_id i3c_hci_driver_ids[] = {
> -	{ .name = "intel-lpss-i3c" },
> +	{ .name = "intel-lpss-i3c", HCI_QUIRK_RPM_ALLOWED },
>  	{ /* sentinel */ }
>  };
>  MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids);
>
> +static const struct dev_pm_ops i3c_hci_pm_ops = {
> +	SET_RUNTIME_PM_OPS(i3c_hci_runtime_suspend, i3c_hci_runtime_resume, NULL)

new macro RUNTIME_PM_OPS

Frank
> +};
> +
>  static struct platform_driver i3c_hci_driver = {
>  	.probe = i3c_hci_probe,
>  	.remove = i3c_hci_remove,
> @@ -858,6 +949,7 @@ static struct platform_driver i3c_hci_driver = {
>  		.name = "mipi-i3c-hci",
>  		.of_match_table = of_match_ptr(i3c_hci_of_match),
>  		.acpi_match_table = i3c_hci_acpi_match,
> +		.pm = pm_ptr(&i3c_hci_pm_ops),
>  	},
>  };
>  module_platform_driver(i3c_hci_driver);
> diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
> index 38f927685d3b..6339adf4b67a 100644
> --- a/drivers/i3c/master/mipi-i3c-hci/hci.h
> +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
> @@ -51,6 +51,7 @@ struct i3c_hci {
>  	void *io_data;
>  	const struct hci_cmd_ops *cmd;
>  	atomic_t next_cmd_tid;
> +	bool irq_inactive;
>  	u32 caps;
>  	unsigned int quirks;
>  	unsigned int DAT_entries;
> @@ -143,6 +144,7 @@ struct i3c_hci_dev_data {
>  #define HCI_QUIRK_PIO_MODE	BIT(2)  /* Set PIO mode for AMD platforms */
>  #define HCI_QUIRK_OD_PP_TIMING		BIT(3)  /* Set OD and PP timings for AMD platforms */
>  #define HCI_QUIRK_RESP_BUF_THLD		BIT(4)  /* Set resp buf thld to 0 for AMD platforms */
> +#define HCI_QUIRK_RPM_ALLOWED		BIT(5)  /* Runtime PM allowed */
>
>  /* global functions */
>  void mipi_i3c_hci_resume(struct i3c_hci *hci);
> --
> 2.51.0
>
>
> --
> linux-i3c mailing list
> linux-i3c at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-i3c



More information about the linux-i3c mailing list