mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-03-22 07:27:12 +08:00
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user