]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
i2c: imx-lpi2c: Add runtime PM support for IRQ and clock management on i.MX8QXP/8QM
authorCarlos Song <carlos.song@nxp.com>
Tue, 25 Nov 2025 08:47:18 +0000 (16:47 +0800)
committerAndi Shyti <andi.shyti@kernel.org>
Sun, 18 Jan 2026 23:45:24 +0000 (00:45 +0100)
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
drivers/i2c/busses/i2c-imx-lpi2c.c

index 2a0962a0b441754577c15ef3a67a7640d41785cc..be7134eefc2f5411d5297fc3661a08930b6bdaf4 100644 (file)
 #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;
+
+       if (need_request_free_irq)
+               devm_free_irq(dev, lpi2c_imx->irq, lpi2c_imx);
 
-       clk_bulk_disable(lpi2c_imx->num_clks, lpi2c_imx->clks);
+       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;