From c19f6f452664a2a967a2be3ba3fa714bb99ebe28 Mon Sep 17 00:00:00 2001 From: Carlos Song Date: Thu, 16 Jul 2026 15:19:56 +0800 Subject: i2c: imx-lpi2c: properly unwind resources on probe failure When probe fails at devm_clk_rate_exclusive_get() or clk_get_rate(), which occur before runtime PM is initialized, the clocks enabled by clk_bulk_prepare_enable() are never disabled. When probe fails after runtime PM is initialized, the previous error path called pm_runtime_put_sync(), which triggers the runtime suspend callback. However, due to different clock management strategies on different SoCs[1] (to avoid deadlocks between the global prepare_lock and runtime PM), the callback may only disable clocks without unpreparing them, causing an incomplete unwind. Introduce a new error label 'clk_disable' to explicitly invoke clk_bulk_disable_unprepare(). Replace pm_runtime_put_sync() with the sequence of pm_runtime_disable(), pm_runtime_set_suspended() and pm_runtime_put_noidle() to bypass the runtime suspend callback during error recovery. During the LPI2C driver probe phase, clock APIs are used exclusively to manage clocks. Once probing succeeds, clock management is handed over to the runtime PM core. [1] https://lore.kernel.org/all/20251125084718.2156168-1-carlos.song@nxp.com/ Signed-off-by: Carlos Song Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260716071957.2670263-2-carlos.song@oss.nxp.com --- drivers/i2c/busses/i2c-imx-lpi2c.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index 1ad7a645ca9a..0081354d91e1 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -1527,14 +1527,18 @@ static int lpi2c_imx_probe(struct platform_device *pdev) * each transfer */ ret = devm_clk_rate_exclusive_get(&pdev->dev, lpi2c_imx->clks[0].clk); - if (ret) - return dev_err_probe(&pdev->dev, ret, - "can't lock I2C peripheral clock rate\n"); + if (ret) { + ret = dev_err_probe(&pdev->dev, ret, + "can't lock I2C peripheral clock rate\n"); + goto clk_disable; + } lpi2c_imx->rate_per = clk_get_rate(lpi2c_imx->clks[0].clk); - if (!lpi2c_imx->rate_per) - return dev_err_probe(&pdev->dev, -EINVAL, - "can't get I2C peripheral clock rate\n"); + if (!lpi2c_imx->rate_per) { + ret = dev_err_probe(&pdev->dev, -EINVAL, + "can't get I2C peripheral clock rate\n"); + goto clk_disable; + } if (lpi2c_imx->hwdata->need_prepare_unprepare_clk) pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_LONG_TIMEOUT_MS); @@ -1576,8 +1580,11 @@ static int lpi2c_imx_probe(struct platform_device *pdev) rpm_disable: pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); +clk_disable: + clk_bulk_disable_unprepare(lpi2c_imx->num_clks, lpi2c_imx->clks); return ret; } -- cgit v1.2.3