i2c: imx-lpi2c: Add runtime PM support for IRQ and clock management on i.MX8QXP/8QM

On i.MX8QXP/8QM SoCs, both the lvds/mipi and lvds/mipi-lpi2c power domains
must enter low-power mode during runtime suspend to achieve deep power
savings.

LPI2C resides in the lvds-lpi2c/mipi-lpi2c power domain, while its IRQ is
routed through an irqsteer located in the lvds/mipi power domain. The LPI2C
clock source comes from an LPCG within the lvds-lpi2c domain.

For example, the hierarchy for lvds0 and lvds0-lpi2c0 domains is:
┌───────────────────────┐
│ pm-domain : lvds0     │
│                       │
│    ┌──────────────┐   │
│    │   irqsteer   │   │
│    └───────▲──────┘   │
│            │irq       │
│            │          │
└────────────┼──────────┘
┌────────────┼──────────┐
│        ┌───┼───┐      │
│        │lpi2c0 │      │
│        └───┬───┘clk   │
│   ┌────────┼───────┐  │
│   │       LPCG     │  │
│   └────────────────┘  │
│pm-domain:lvds0-lpi2c0 │
└───────────────────────┘

To allow these domains to power down in system runtime suspend:

- All irqsteer clients must release IRQs.
- All LPCG clients must disable and unprepare clocks.

Thus, LPI2C must:

- Free its IRQ during runtime suspend and re-request it on resume.
- Disable and unprepare all clocks during runtime suspend and prepare
  and rne ble them on resume.

This enables the lvds/mipi domains to enter deep low-power mode,
significantly reducing power consumption compared to active mode.

Signed-off-by: Carlos Song <carlos.song@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20251125084718.2156168-1-carlos.song@nxp.com
This commit is contained in:
Carlos Song
2025-11-25 16:47:18 +08:00
committed by Andi Shyti
parent 7021f6c038
commit 949f647eff

View File

@@ -131,6 +131,7 @@
#define CHUNK_DATA 256
#define I2C_PM_TIMEOUT 10 /* ms */
#define I2C_PM_LONG_TIMEOUT_MS 1000 /* Avoid dead lock caused by big clock prepare lock */
#define I2C_DMA_THRESHOLD 8 /* bytes */
enum lpi2c_imx_mode {
@@ -148,6 +149,11 @@ enum lpi2c_imx_pincfg {
FOUR_PIN_PP,
};
struct imx_lpi2c_hwdata {
bool need_request_free_irq; /* Needed by irqsteer */
bool need_prepare_unprepare_clk; /* Needed by LPCG */
};
struct lpi2c_imx_dma {
bool using_pio_mode;
u8 rx_cmd_buf_len;
@@ -186,6 +192,21 @@ struct lpi2c_imx_struct {
bool can_use_dma;
struct lpi2c_imx_dma *dma;
struct i2c_client *target;
int irq;
const struct imx_lpi2c_hwdata *hwdata;
};
static const struct imx_lpi2c_hwdata imx7ulp_lpi2c_hwdata = {
};
static const struct imx_lpi2c_hwdata imx8qxp_lpi2c_hwdata = {
.need_request_free_irq = true,
.need_prepare_unprepare_clk = true,
};
static const struct imx_lpi2c_hwdata imx8qm_lpi2c_hwdata = {
.need_request_free_irq = true,
.need_prepare_unprepare_clk = true,
};
#define lpi2c_imx_read_msr_poll_timeout(atomic, val, cond) \
@@ -1363,7 +1384,9 @@ static const struct i2c_algorithm lpi2c_imx_algo = {
};
static const struct of_device_id lpi2c_imx_of_match[] = {
{ .compatible = "fsl,imx7ulp-lpi2c" },
{ .compatible = "fsl,imx7ulp-lpi2c", .data = &imx7ulp_lpi2c_hwdata,},
{ .compatible = "fsl,imx8qxp-lpi2c", .data = &imx8qxp_lpi2c_hwdata,},
{ .compatible = "fsl,imx8qm-lpi2c", .data = &imx8qm_lpi2c_hwdata,},
{ }
};
MODULE_DEVICE_TABLE(of, lpi2c_imx_of_match);
@@ -1374,19 +1397,23 @@ static int lpi2c_imx_probe(struct platform_device *pdev)
struct resource *res;
dma_addr_t phy_addr;
unsigned int temp;
int irq, ret;
int ret;
lpi2c_imx = devm_kzalloc(&pdev->dev, sizeof(*lpi2c_imx), GFP_KERNEL);
if (!lpi2c_imx)
return -ENOMEM;
lpi2c_imx->hwdata = of_device_get_match_data(&pdev->dev);
if (!lpi2c_imx->hwdata)
return -ENODEV;
lpi2c_imx->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(lpi2c_imx->base))
return PTR_ERR(lpi2c_imx->base);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
lpi2c_imx->irq = platform_get_irq(pdev, 0);
if (lpi2c_imx->irq < 0)
return lpi2c_imx->irq;
lpi2c_imx->adapter.owner = THIS_MODULE;
lpi2c_imx->adapter.algo = &lpi2c_imx_algo;
@@ -1406,10 +1433,10 @@ static int lpi2c_imx_probe(struct platform_device *pdev)
if (ret)
lpi2c_imx->bitrate = I2C_MAX_STANDARD_MODE_FREQ;
ret = devm_request_irq(&pdev->dev, irq, lpi2c_imx_isr, IRQF_NO_SUSPEND,
ret = devm_request_irq(&pdev->dev, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND,
pdev->name, lpi2c_imx);
if (ret)
return dev_err_probe(&pdev->dev, ret, "can't claim irq %d\n", irq);
return dev_err_probe(&pdev->dev, ret, "can't claim irq %d\n", lpi2c_imx->irq);
i2c_set_adapdata(&lpi2c_imx->adapter, lpi2c_imx);
platform_set_drvdata(pdev, lpi2c_imx);
@@ -1432,7 +1459,11 @@ static int lpi2c_imx_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, -EINVAL,
"can't get I2C peripheral clock rate\n");
pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT);
if (lpi2c_imx->hwdata->need_prepare_unprepare_clk)
pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_LONG_TIMEOUT_MS);
else
pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
@@ -1487,8 +1518,16 @@ static void lpi2c_imx_remove(struct platform_device *pdev)
static int __maybe_unused lpi2c_runtime_suspend(struct device *dev)
{
struct lpi2c_imx_struct *lpi2c_imx = dev_get_drvdata(dev);
bool need_prepare_unprepare_clk = lpi2c_imx->hwdata->need_prepare_unprepare_clk;
bool need_request_free_irq = lpi2c_imx->hwdata->need_request_free_irq;
clk_bulk_disable(lpi2c_imx->num_clks, lpi2c_imx->clks);
if (need_request_free_irq)
devm_free_irq(dev, lpi2c_imx->irq, lpi2c_imx);
if (need_prepare_unprepare_clk)
clk_bulk_disable_unprepare(lpi2c_imx->num_clks, lpi2c_imx->clks);
else
clk_bulk_disable(lpi2c_imx->num_clks, lpi2c_imx->clks);
pinctrl_pm_select_sleep_state(dev);
return 0;
@@ -1497,13 +1536,32 @@ static int __maybe_unused lpi2c_runtime_suspend(struct device *dev)
static int __maybe_unused lpi2c_runtime_resume(struct device *dev)
{
struct lpi2c_imx_struct *lpi2c_imx = dev_get_drvdata(dev);
bool need_prepare_unprepare_clk = lpi2c_imx->hwdata->need_prepare_unprepare_clk;
bool need_request_free_irq = lpi2c_imx->hwdata->need_request_free_irq;
int ret;
pinctrl_pm_select_default_state(dev);
ret = clk_bulk_enable(lpi2c_imx->num_clks, lpi2c_imx->clks);
if (ret) {
dev_err(dev, "failed to enable I2C clock, ret=%d\n", ret);
return ret;
if (need_prepare_unprepare_clk) {
ret = clk_bulk_prepare_enable(lpi2c_imx->num_clks, lpi2c_imx->clks);
if (ret) {
dev_err(dev, "failed to enable I2C clock, ret=%d\n", ret);
return ret;
}
} else {
ret = clk_bulk_enable(lpi2c_imx->num_clks, lpi2c_imx->clks);
if (ret) {
dev_err(dev, "failed to enable clock %d\n", ret);
return ret;
}
}
if (need_request_free_irq) {
ret = devm_request_irq(dev, lpi2c_imx->irq, lpi2c_imx_isr, IRQF_NO_SUSPEND,
dev_name(dev), lpi2c_imx);
if (ret) {
dev_err(dev, "can't claim irq %d\n", lpi2c_imx->irq);
return ret;
}
}
return 0;