Commit 588eb93e authored by Gao Pan's avatar Gao Pan Committed by Wolfram Sang

i2c: imx: add runtime pm support to improve the performance

In our former i2c driver, i2c clk is enabled and disabled in
xfer function, which contributes to power saving. However,
the clk enable process brings a busy wait delay until the core
is stable. As a result, the performance is sacrificed.

To weigh the power consumption and i2c bus performance, runtime
pm is the good solution for it. The clk is enabled when a i2c
transfer starts, and disabled after a specifically defined delay.

If CONFIG_PM is disabled the net result of this patch is that the
clock is never disabled.

Without the patch the test case (many eeprom reads) executes with approx:
real 1m7.735s
user 0m0.488s
sys 0m20.040s

With the patch the same test case (many eeprom reads) executes with approx:
real 0m54.241s
user 0m0.440s
sys 0m5.920s
Signed-off-by: default avatarFugang Duan <B38611@freescale.com>
Signed-off-by: default avatarGao Pan <b54642@freescale.com>
Acked-by: default avatarUwe Kleine-König <u.kleine-koenig@pengutronix.de>
[wsa: sorted includes]
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
parent a5f65018
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
#include <linux/pinctrl/consumer.h> #include <linux/pinctrl/consumer.h>
#include <linux/platform_data/i2c-imx.h> #include <linux/platform_data/i2c-imx.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -120,6 +121,8 @@ ...@@ -120,6 +121,8 @@
#define I2CR_IEN_OPCODE_0 0x0 #define I2CR_IEN_OPCODE_0 0x0
#define I2CR_IEN_OPCODE_1 I2CR_IEN #define I2CR_IEN_OPCODE_1 I2CR_IEN
#define I2C_PM_TIMEOUT 10 /* ms */
/** Variables ****************************************************************** /** Variables ******************************************************************
*******************************************************************************/ *******************************************************************************/
...@@ -527,9 +530,6 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx) ...@@ -527,9 +530,6 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
i2c_imx_set_clk(i2c_imx); i2c_imx_set_clk(i2c_imx);
result = clk_prepare_enable(i2c_imx->clk);
if (result)
return result;
imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR); imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR);
/* Enable I2C controller */ /* Enable I2C controller */
imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR); imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
...@@ -582,7 +582,6 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx) ...@@ -582,7 +582,6 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
/* Disable I2C controller */ /* Disable I2C controller */
temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN, temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
clk_disable_unprepare(i2c_imx->clk);
} }
static irqreturn_t i2c_imx_isr(int irq, void *dev_id) static irqreturn_t i2c_imx_isr(int irq, void *dev_id)
...@@ -901,6 +900,10 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, ...@@ -901,6 +900,10 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
result = pm_runtime_get_sync(i2c_imx->adapter.dev.parent);
if (result < 0)
goto out;
/* Start I2C transfer */ /* Start I2C transfer */
result = i2c_imx_start(i2c_imx); result = i2c_imx_start(i2c_imx);
if (result) { if (result) {
...@@ -964,6 +967,10 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, ...@@ -964,6 +967,10 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter,
/* Stop I2C transfer */ /* Stop I2C transfer */
i2c_imx_stop(i2c_imx); i2c_imx_stop(i2c_imx);
pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent);
pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent);
out:
dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__, dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,
(result < 0) ? "error" : "success msg", (result < 0) ? "error" : "success msg",
(result < 0) ? result : num); (result < 0) ? result : num);
...@@ -1083,7 +1090,7 @@ static int i2c_imx_probe(struct platform_device *pdev) ...@@ -1083,7 +1090,7 @@ static int i2c_imx_probe(struct platform_device *pdev)
ret = clk_prepare_enable(i2c_imx->clk); ret = clk_prepare_enable(i2c_imx->clk);
if (ret) { if (ret) {
dev_err(&pdev->dev, "can't enable I2C clock\n"); dev_err(&pdev->dev, "can't enable I2C clock, ret=%d\n", ret);
return ret; return ret;
} }
...@@ -1107,6 +1114,18 @@ static int i2c_imx_probe(struct platform_device *pdev) ...@@ -1107,6 +1114,18 @@ static int i2c_imx_probe(struct platform_device *pdev)
/* Set up adapter data */ /* Set up adapter data */
i2c_set_adapdata(&i2c_imx->adapter, i2c_imx); i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
/* Set up platform driver data */
platform_set_drvdata(pdev, i2c_imx);
pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
ret = pm_runtime_get_sync(&pdev->dev);
if (ret < 0)
goto rpm_disable;
/* Set up clock divider */ /* Set up clock divider */
i2c_imx->bitrate = IMX_I2C_BIT_RATE; i2c_imx->bitrate = IMX_I2C_BIT_RATE;
ret = of_property_read_u32(pdev->dev.of_node, ret = of_property_read_u32(pdev->dev.of_node,
...@@ -1125,12 +1144,11 @@ static int i2c_imx_probe(struct platform_device *pdev) ...@@ -1125,12 +1144,11 @@ static int i2c_imx_probe(struct platform_device *pdev)
ret = i2c_add_numbered_adapter(&i2c_imx->adapter); ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
if (ret < 0) { if (ret < 0) {
dev_err(&pdev->dev, "registration failed\n"); dev_err(&pdev->dev, "registration failed\n");
goto clk_disable; goto rpm_disable;
} }
/* Set up platform driver data */ pm_runtime_mark_last_busy(&pdev->dev);
platform_set_drvdata(pdev, i2c_imx); pm_runtime_put_autosuspend(&pdev->dev);
clk_disable_unprepare(i2c_imx->clk);
dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq); dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);
dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res); dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);
...@@ -1143,6 +1161,12 @@ static int i2c_imx_probe(struct platform_device *pdev) ...@@ -1143,6 +1161,12 @@ static int i2c_imx_probe(struct platform_device *pdev)
return 0; /* Return OK */ return 0; /* Return OK */
rpm_disable:
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_dont_use_autosuspend(&pdev->dev);
clk_disable: clk_disable:
clk_disable_unprepare(i2c_imx->clk); clk_disable_unprepare(i2c_imx->clk);
return ret; return ret;
...@@ -1151,6 +1175,11 @@ static int i2c_imx_probe(struct platform_device *pdev) ...@@ -1151,6 +1175,11 @@ static int i2c_imx_probe(struct platform_device *pdev)
static int i2c_imx_remove(struct platform_device *pdev) static int i2c_imx_remove(struct platform_device *pdev)
{ {
struct imx_i2c_struct *i2c_imx = platform_get_drvdata(pdev); struct imx_i2c_struct *i2c_imx = platform_get_drvdata(pdev);
int ret;
ret = pm_runtime_get_sync(&pdev->dev);
if (ret < 0)
return ret;
/* remove adapter */ /* remove adapter */
dev_dbg(&i2c_imx->adapter.dev, "adapter removed\n"); dev_dbg(&i2c_imx->adapter.dev, "adapter removed\n");
...@@ -1165,14 +1194,51 @@ static int i2c_imx_remove(struct platform_device *pdev) ...@@ -1165,14 +1194,51 @@ static int i2c_imx_remove(struct platform_device *pdev)
imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR); imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR);
imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR); imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR);
clk_disable_unprepare(i2c_imx->clk);
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_PM
static int i2c_imx_runtime_suspend(struct device *dev)
{
struct imx_i2c_struct *i2c_imx = dev_get_drvdata(dev);
clk_disable_unprepare(i2c_imx->clk);
return 0; return 0;
} }
static int i2c_imx_runtime_resume(struct device *dev)
{
struct imx_i2c_struct *i2c_imx = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(i2c_imx->clk);
if (ret)
dev_err(dev, "can't enable I2C clock, ret=%d\n", ret);
return ret;
}
static const struct dev_pm_ops i2c_imx_pm_ops = {
SET_RUNTIME_PM_OPS(i2c_imx_runtime_suspend,
i2c_imx_runtime_resume, NULL)
};
#define I2C_IMX_PM_OPS (&i2c_imx_pm_ops)
#else
#define I2C_IMX_PM_OPS NULL
#endif /* CONFIG_PM */
static struct platform_driver i2c_imx_driver = { static struct platform_driver i2c_imx_driver = {
.probe = i2c_imx_probe, .probe = i2c_imx_probe,
.remove = i2c_imx_remove, .remove = i2c_imx_remove,
.driver = { .driver = {
.name = DRIVER_NAME, .name = DRIVER_NAME,
.pm = I2C_IMX_PM_OPS,
.of_match_table = i2c_imx_dt_ids, .of_match_table = i2c_imx_dt_ids,
}, },
.id_table = imx_i2c_devtype, .id_table = imx_i2c_devtype,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment