diff mbox

i2c: imx: update i2c clock divider for each transaction

Message ID 1400552505-22954-1-git-send-email-b38611@freescale.com
State Accepted
Headers show

Commit Message

Nimrod Andy May 20, 2014, 2:21 a.m. UTC
Since IMX serial SOCs support low bus freq mode, some clocks freq
may change to save power. I2C needs to check the clock source and
update the divider.

For example:
i.MX6SL I2C clk is from IPG_PERCLK which is sourced from IPG_CLK.
Under normal operation, IPG_CLK is 66MHz, ipg_perclk is at 22MHz.
In low bus freq mode, IPG_CLK is at 12MHz and IPG_PERCLK is down
to 4MHz. So the I2C driver must update the divider register for
each transaction when the current IPG_PERCLK is not equal to the
clock of previous transaction.

Signed-off-by: Fugang Duan  <B38611@freescale.com>
---
 drivers/i2c/busses/i2c-imx.c |   94 ++++++++++++++++++++++-------------------
 1 files changed, 50 insertions(+), 44 deletions(-)

Comments

Frank Li May 22, 2014, 1:26 p.m. UTC | #1
> -----Original Message-----
> From: Fugang Duan [mailto:b38611@freescale.com]
> Sent: Monday, May 19, 2014 9:22 PM
> To: wsa@the-dreams.de
> Cc: linux-i2c@vger.kernel.org; u.kleine-koenig@pengutronix.de; Duan
> Fugang-B38611; Li Frank-B20596
> Subject: [PATCH] i2c: imx: update i2c clock divider for each transaction
> 
> Since IMX serial SOCs support low bus freq mode, some clocks freq may
> change to save power. I2C needs to check the clock source and update the
> divider.
> 
> For example:
> i.MX6SL I2C clk is from IPG_PERCLK which is sourced from IPG_CLK.
> Under normal operation, IPG_CLK is 66MHz, ipg_perclk is at 22MHz.
> In low bus freq mode, IPG_CLK is at 12MHz and IPG_PERCLK is down to 4MHz.
> So the I2C driver must update the divider register for each transaction
> when the current IPG_PERCLK is not equal to the clock of previous
> transaction.
> 
> Signed-off-by: Fugang Duan  <B38611@freescale.com>

Acked by: Frank Li<frank.li@freescale.com>

> ---
>  drivers/i2c/busses/i2c-imx.c |   94 ++++++++++++++++++++++---------------
> ----
>  1 files changed, 50 insertions(+), 44 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
> index 6c2ac05..2fdff4d 100644
> --- a/drivers/i2c/busses/i2c-imx.c
> +++ b/drivers/i2c/busses/i2c-imx.c
> @@ -183,6 +183,8 @@ struct imx_i2c_struct {
>  	unsigned int 		disable_delay;
>  	int			stopped;
>  	unsigned int		ifdr; /* IMX_I2C_IFDR */
> +	unsigned int		cur_clk;
> +	unsigned int		bitrate;
>  	const struct imx_i2c_hwdata	*hwdata;
>  };
> 
> @@ -305,6 +307,49 @@ static int i2c_imx_acked(struct imx_i2c_struct
> *i2c_imx)
>  	return 0;
>  }
> 
> +static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx) {
> +	struct imx_i2c_clk_pair *i2c_clk_div = i2c_imx->hwdata->clk_div;
> +	unsigned int i2c_clk_rate;
> +	unsigned int div;
> +	int i;
> +
> +	/* Divider value calculation */
> +	i2c_clk_rate = clk_get_rate(i2c_imx->clk);
> +	if (i2c_imx->cur_clk == i2c_clk_rate)
> +		return;
> +	else
> +		i2c_imx->cur_clk = i2c_clk_rate;
> +
> +	div = (i2c_clk_rate + i2c_imx->bitrate - 1) / i2c_imx->bitrate;
> +	if (div < i2c_clk_div[0].div)
> +		i = 0;
> +	else if (div > i2c_clk_div[i2c_imx->hwdata->ndivs - 1].div)
> +		i = i2c_imx->hwdata->ndivs - 1;
> +	else
> +		for (i = 0; i2c_clk_div[i].div < div; i++);
> +
> +	/* Store divider value */
> +	i2c_imx->ifdr = i2c_clk_div[i].val;
> +
> +	/*
> +	 * There dummy delay is calculated.
> +	 * It should be about one I2C clock period long.
> +	 * This delay is used in I2C bus disable function
> +	 * to fix chip hardware bug.
> +	 */
> +	i2c_imx->disable_delay = (500000U * i2c_clk_div[i].div
> +		+ (i2c_clk_rate / 2) - 1) / (i2c_clk_rate / 2);
> +
> +	/* dev_dbg() can't be used, because adapter is not yet registered */
> +#ifdef CONFIG_I2C_DEBUG_BUS
> +	dev_dbg(&i2c_imx->adapter.dev, "<%s> I2C_CLK=%d, REQ DIV=%d\n",
> +		__func__, i2c_clk_rate, div);
> +	dev_dbg(&i2c_imx->adapter.dev, "<%s> IFDR[IC]=0x%x, REAL DIV=%d\n",
> +		__func__, i2c_clk_div[i].val, i2c_clk_div[i].div); #endif }
> +
>  static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)  {
>  	unsigned int temp = 0;
> @@ -312,6 +357,8 @@ static int i2c_imx_start(struct imx_i2c_struct
> *i2c_imx)
> 
>  	dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
> 
> +	i2c_imx_set_clk(i2c_imx);
> +
>  	result = clk_prepare_enable(i2c_imx->clk);
>  	if (result)
>  		return result;
> @@ -367,45 +414,6 @@ static void i2c_imx_stop(struct imx_i2c_struct
> *i2c_imx)
>  	clk_disable_unprepare(i2c_imx->clk);
>  }
> 
> -static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx,
> -							unsigned int rate)
> -{
> -	struct imx_i2c_clk_pair *i2c_clk_div = i2c_imx->hwdata->clk_div;
> -	unsigned int i2c_clk_rate;
> -	unsigned int div;
> -	int i;
> -
> -	/* Divider value calculation */
> -	i2c_clk_rate = clk_get_rate(i2c_imx->clk);
> -	div = (i2c_clk_rate + rate - 1) / rate;
> -	if (div < i2c_clk_div[0].div)
> -		i = 0;
> -	else if (div > i2c_clk_div[i2c_imx->hwdata->ndivs - 1].div)
> -		i = i2c_imx->hwdata->ndivs - 1;
> -	else
> -		for (i = 0; i2c_clk_div[i].div < div; i++);
> -
> -	/* Store divider value */
> -	i2c_imx->ifdr = i2c_clk_div[i].val;
> -
> -	/*
> -	 * There dummy delay is calculated.
> -	 * It should be about one I2C clock period long.
> -	 * This delay is used in I2C bus disable function
> -	 * to fix chip hardware bug.
> -	 */
> -	i2c_imx->disable_delay = (500000U * i2c_clk_div[i].div
> -		+ (i2c_clk_rate / 2) - 1) / (i2c_clk_rate / 2);
> -
> -	/* dev_dbg() can't be used, because adapter is not yet registered */
> -#ifdef CONFIG_I2C_DEBUG_BUS
> -	dev_dbg(&i2c_imx->adapter.dev, "<%s> I2C_CLK=%d, REQ DIV=%d\n",
> -		__func__, i2c_clk_rate, div);
> -	dev_dbg(&i2c_imx->adapter.dev, "<%s> IFDR[IC]=0x%x, REAL DIV=%d\n",
> -		__func__, i2c_clk_div[i].val, i2c_clk_div[i].div);
> -#endif
> -}
> -
>  static irqreturn_t i2c_imx_isr(int irq, void *dev_id)  {
>  	struct imx_i2c_struct *i2c_imx = dev_id; @@ -619,7 +627,6 @@ static
> int i2c_imx_probe(struct platform_device *pdev)
>  	struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
>  	void __iomem *base;
>  	int irq, ret;
> -	u32 bitrate;
> 
>  	dev_dbg(&pdev->dev, "<%s>\n", __func__);
> 
> @@ -683,12 +690,11 @@ static int i2c_imx_probe(struct platform_device
> *pdev)
>  	i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
> 
>  	/* Set up clock divider */
> -	bitrate = IMX_I2C_BIT_RATE;
> +	i2c_imx->bitrate = IMX_I2C_BIT_RATE;
>  	ret = of_property_read_u32(pdev->dev.of_node,
> -				   "clock-frequency", &bitrate);
> +				   "clock-frequency", &i2c_imx->bitrate);
>  	if (ret < 0 && pdata && pdata->bitrate)
> -		bitrate = pdata->bitrate;
> -	i2c_imx_set_clk(i2c_imx, bitrate);
> +		i2c_imx->bitrate = pdata->bitrate;
> 
>  	/* Set up chip registers to defaults */
>  	imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
> --
> 1.7.8

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Wolfram Sang June 2, 2014, 4:03 p.m. UTC | #2
On Tue, May 20, 2014 at 10:21:45AM +0800, Fugang Duan wrote:
> Since IMX serial SOCs support low bus freq mode, some clocks freq
> may change to save power. I2C needs to check the clock source and
> update the divider.
> 
> For example:
> i.MX6SL I2C clk is from IPG_PERCLK which is sourced from IPG_CLK.
> Under normal operation, IPG_CLK is 66MHz, ipg_perclk is at 22MHz.
> In low bus freq mode, IPG_CLK is at 12MHz and IPG_PERCLK is down
> to 4MHz. So the I2C driver must update the divider register for
> each transaction when the current IPG_PERCLK is not equal to the
> clock of previous transaction.
> 
> Signed-off-by: Fugang Duan  <B38611@freescale.com>
> ---

Applied to for-next, thanks!

One change, though:

> +	/* dev_dbg() can't be used, because adapter is not yet registered */

This comment is outdated. I removed it and the __func__ printout.
diff mbox

Patch

diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index 6c2ac05..2fdff4d 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -183,6 +183,8 @@  struct imx_i2c_struct {
 	unsigned int 		disable_delay;
 	int			stopped;
 	unsigned int		ifdr; /* IMX_I2C_IFDR */
+	unsigned int		cur_clk;
+	unsigned int		bitrate;
 	const struct imx_i2c_hwdata	*hwdata;
 };
 
@@ -305,6 +307,49 @@  static int i2c_imx_acked(struct imx_i2c_struct *i2c_imx)
 	return 0;
 }
 
+static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx)
+{
+	struct imx_i2c_clk_pair *i2c_clk_div = i2c_imx->hwdata->clk_div;
+	unsigned int i2c_clk_rate;
+	unsigned int div;
+	int i;
+
+	/* Divider value calculation */
+	i2c_clk_rate = clk_get_rate(i2c_imx->clk);
+	if (i2c_imx->cur_clk == i2c_clk_rate)
+		return;
+	else
+		i2c_imx->cur_clk = i2c_clk_rate;
+
+	div = (i2c_clk_rate + i2c_imx->bitrate - 1) / i2c_imx->bitrate;
+	if (div < i2c_clk_div[0].div)
+		i = 0;
+	else if (div > i2c_clk_div[i2c_imx->hwdata->ndivs - 1].div)
+		i = i2c_imx->hwdata->ndivs - 1;
+	else
+		for (i = 0; i2c_clk_div[i].div < div; i++);
+
+	/* Store divider value */
+	i2c_imx->ifdr = i2c_clk_div[i].val;
+
+	/*
+	 * There dummy delay is calculated.
+	 * It should be about one I2C clock period long.
+	 * This delay is used in I2C bus disable function
+	 * to fix chip hardware bug.
+	 */
+	i2c_imx->disable_delay = (500000U * i2c_clk_div[i].div
+		+ (i2c_clk_rate / 2) - 1) / (i2c_clk_rate / 2);
+
+	/* dev_dbg() can't be used, because adapter is not yet registered */
+#ifdef CONFIG_I2C_DEBUG_BUS
+	dev_dbg(&i2c_imx->adapter.dev, "<%s> I2C_CLK=%d, REQ DIV=%d\n",
+		__func__, i2c_clk_rate, div);
+	dev_dbg(&i2c_imx->adapter.dev, "<%s> IFDR[IC]=0x%x, REAL DIV=%d\n",
+		__func__, i2c_clk_div[i].val, i2c_clk_div[i].div);
+#endif
+}
+
 static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
 {
 	unsigned int temp = 0;
@@ -312,6 +357,8 @@  static int i2c_imx_start(struct imx_i2c_struct *i2c_imx)
 
 	dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
 
+	i2c_imx_set_clk(i2c_imx);
+
 	result = clk_prepare_enable(i2c_imx->clk);
 	if (result)
 		return result;
@@ -367,45 +414,6 @@  static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx)
 	clk_disable_unprepare(i2c_imx->clk);
 }
 
-static void i2c_imx_set_clk(struct imx_i2c_struct *i2c_imx,
-							unsigned int rate)
-{
-	struct imx_i2c_clk_pair *i2c_clk_div = i2c_imx->hwdata->clk_div;
-	unsigned int i2c_clk_rate;
-	unsigned int div;
-	int i;
-
-	/* Divider value calculation */
-	i2c_clk_rate = clk_get_rate(i2c_imx->clk);
-	div = (i2c_clk_rate + rate - 1) / rate;
-	if (div < i2c_clk_div[0].div)
-		i = 0;
-	else if (div > i2c_clk_div[i2c_imx->hwdata->ndivs - 1].div)
-		i = i2c_imx->hwdata->ndivs - 1;
-	else
-		for (i = 0; i2c_clk_div[i].div < div; i++);
-
-	/* Store divider value */
-	i2c_imx->ifdr = i2c_clk_div[i].val;
-
-	/*
-	 * There dummy delay is calculated.
-	 * It should be about one I2C clock period long.
-	 * This delay is used in I2C bus disable function
-	 * to fix chip hardware bug.
-	 */
-	i2c_imx->disable_delay = (500000U * i2c_clk_div[i].div
-		+ (i2c_clk_rate / 2) - 1) / (i2c_clk_rate / 2);
-
-	/* dev_dbg() can't be used, because adapter is not yet registered */
-#ifdef CONFIG_I2C_DEBUG_BUS
-	dev_dbg(&i2c_imx->adapter.dev, "<%s> I2C_CLK=%d, REQ DIV=%d\n",
-		__func__, i2c_clk_rate, div);
-	dev_dbg(&i2c_imx->adapter.dev, "<%s> IFDR[IC]=0x%x, REAL DIV=%d\n",
-		__func__, i2c_clk_div[i].val, i2c_clk_div[i].div);
-#endif
-}
-
 static irqreturn_t i2c_imx_isr(int irq, void *dev_id)
 {
 	struct imx_i2c_struct *i2c_imx = dev_id;
@@ -619,7 +627,6 @@  static int i2c_imx_probe(struct platform_device *pdev)
 	struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
 	void __iomem *base;
 	int irq, ret;
-	u32 bitrate;
 
 	dev_dbg(&pdev->dev, "<%s>\n", __func__);
 
@@ -683,12 +690,11 @@  static int i2c_imx_probe(struct platform_device *pdev)
 	i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
 
 	/* Set up clock divider */
-	bitrate = IMX_I2C_BIT_RATE;
+	i2c_imx->bitrate = IMX_I2C_BIT_RATE;
 	ret = of_property_read_u32(pdev->dev.of_node,
-				   "clock-frequency", &bitrate);
+				   "clock-frequency", &i2c_imx->bitrate);
 	if (ret < 0 && pdata && pdata->bitrate)
-		bitrate = pdata->bitrate;
-	i2c_imx_set_clk(i2c_imx, bitrate);
+		i2c_imx->bitrate = pdata->bitrate;
 
 	/* Set up chip registers to defaults */
 	imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,