Unverified Commit 949f647e authored by Carlos Song's avatar Carlos Song Committed by Andi Shyti
Browse files

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: default avatarCarlos Song <carlos.song@nxp.com>
Reviewed-by: default avatarFrank Li <Frank.Li@nxp.com>
Signed-off-by: default avatarAndi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20251125084718.2156168-1-carlos.song@nxp.com
parent 7021f6c0
Loading
Loading
Loading
Loading
+71 −13
Original line number Diff line number Diff line
@@ -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");

	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,7 +1518,15 @@ 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);

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

@@ -1497,14 +1536,33 @@ 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 (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;
}