| From 10ff4c5239a137abfc896ec73ef3d15a0f86a16a Mon Sep 17 00:00:00 2001 |
| From: Javier Martinez Canillas <javier@osg.samsung.com> |
| Date: Sat, 16 Apr 2016 21:14:52 -0400 |
| Subject: i2c: exynos5: Fix possible ABBA deadlock by keeping I2C clock prepared |
| |
| From: Javier Martinez Canillas <javier@osg.samsung.com> |
| |
| commit 10ff4c5239a137abfc896ec73ef3d15a0f86a16a upstream. |
| |
| The exynos5 I2C controller driver always prepares and enables a clock |
| before using it and then disables unprepares it when the clock is not |
| used anymore. |
| |
| But this can cause a possible ABBA deadlock in some scenarios since a |
| driver that uses regmap to access its I2C registers, will first grab |
| the regmap lock and then the I2C xfer function will grab the prepare |
| lock when preparing the I2C clock. But since the clock driver also |
| uses regmap for I2C accesses, preparing a clock will first grab the |
| prepare lock and then the regmap lock when using the regmap API. |
| |
| An example of this happens on the Exynos5422 Odroid XU4 board where a |
| s2mps11 PMIC is used and both the s2mps11 regulators and clk drivers |
| share the same I2C regmap. |
| |
| The possible deadlock is reported by the kernel lockdep: |
| |
| Possible unsafe locking scenario: |
| |
| CPU0 CPU1 |
| ---- ---- |
| lock(sec_core:428:(regmap)->lock); |
| lock(prepare_lock); |
| lock(sec_core:428:(regmap)->lock); |
| lock(prepare_lock); |
| |
| *** DEADLOCK *** |
| |
| Fix it by leaving the code prepared on probe and use {en,dis}able in |
| the I2C transfer function. |
| |
| This patch is similar to commit 34e81ad5f0b6 ("i2c: s3c2410: fix ABBA |
| deadlock by keeping clock prepared") that fixes the same bug in other |
| driver for an I2C controller found in Samsung SoCs. |
| |
| Reported-by: Anand Moon <linux.amoon@gmail.com> |
| Signed-off-by: Javier Martinez Canillas <javier@osg.samsung.com> |
| Reviewed-by: Anand Moon <linux.amoon@gmail.com> |
| Reviewed-by: Krzysztof Kozlowski <k.kozlowski@samsung.com> |
| Signed-off-by: Wolfram Sang <wsa@the-dreams.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/i2c/busses/i2c-exynos5.c | 24 +++++++++++++++++++----- |
| 1 file changed, 19 insertions(+), 5 deletions(-) |
| |
| --- a/drivers/i2c/busses/i2c-exynos5.c |
| +++ b/drivers/i2c/busses/i2c-exynos5.c |
| @@ -574,7 +574,9 @@ static int exynos5_i2c_xfer(struct i2c_a |
| return -EIO; |
| } |
| |
| - clk_prepare_enable(i2c->clk); |
| + ret = clk_enable(i2c->clk); |
| + if (ret) |
| + return ret; |
| |
| for (i = 0; i < num; i++, msgs++) { |
| stop = (i == num - 1); |
| @@ -598,7 +600,7 @@ static int exynos5_i2c_xfer(struct i2c_a |
| } |
| |
| out: |
| - clk_disable_unprepare(i2c->clk); |
| + clk_disable(i2c->clk); |
| return ret; |
| } |
| |
| @@ -652,7 +654,9 @@ static int exynos5_i2c_probe(struct plat |
| return -ENOENT; |
| } |
| |
| - clk_prepare_enable(i2c->clk); |
| + ret = clk_prepare_enable(i2c->clk); |
| + if (ret) |
| + return ret; |
| |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| i2c->regs = devm_ioremap_resource(&pdev->dev, mem); |
| @@ -701,6 +705,10 @@ static int exynos5_i2c_probe(struct plat |
| |
| platform_set_drvdata(pdev, i2c); |
| |
| + clk_disable(i2c->clk); |
| + |
| + return 0; |
| + |
| err_clk: |
| clk_disable_unprepare(i2c->clk); |
| return ret; |
| @@ -712,6 +720,8 @@ static int exynos5_i2c_remove(struct pla |
| |
| i2c_del_adapter(&i2c->adap); |
| |
| + clk_unprepare(i2c->clk); |
| + |
| return 0; |
| } |
| |
| @@ -722,6 +732,8 @@ static int exynos5_i2c_suspend_noirq(str |
| |
| i2c->suspended = 1; |
| |
| + clk_unprepare(i2c->clk); |
| + |
| return 0; |
| } |
| |
| @@ -731,7 +743,9 @@ static int exynos5_i2c_resume_noirq(stru |
| struct exynos5_i2c *i2c = platform_get_drvdata(pdev); |
| int ret = 0; |
| |
| - clk_prepare_enable(i2c->clk); |
| + ret = clk_prepare_enable(i2c->clk); |
| + if (ret) |
| + return ret; |
| |
| ret = exynos5_hsi2c_clock_setup(i2c); |
| if (ret) { |
| @@ -740,7 +754,7 @@ static int exynos5_i2c_resume_noirq(stru |
| } |
| |
| exynos5_i2c_init(i2c); |
| - clk_disable_unprepare(i2c->clk); |
| + clk_disable(i2c->clk); |
| i2c->suspended = 0; |
| |
| return 0; |