diff mbox

[U-Boot,3/4] i2c: cdns: Support different bus speeds

Message ID a8626f86b7a1ea59a4da707d698f573cbca05a99.1460636146.git.michal.simek@xilinx.com
State Accepted
Commit ad72e7622bd587dc798fbac0351b558fb18caa97
Delegated to: Heiko Schocher
Headers show

Commit Message

Michal Simek April 14, 2016, 12:15 p.m. UTC
400kHz is maximum freq which can be used on Xilinx ZynqMP.
Support it with standard divider calculator.
Input freq is hardcoded to 100MHz input freq till we have clock driver
which can provide this information for exact configuration.

Signed-off-by: Michal Simek <michal.simek@xilinx.com>
---

 drivers/i2c/i2c-cdns.c | 76 +++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 69 insertions(+), 7 deletions(-)

Comments

Heiko Schocher April 18, 2016, 7:08 a.m. UTC | #1
Hello Michal,

Am 14.04.2016 um 14:15 schrieb Michal Simek:
> 400kHz is maximum freq which can be used on Xilinx ZynqMP.
> Support it with standard divider calculator.
> Input freq is hardcoded to 100MHz input freq till we have clock driver
> which can provide this information for exact configuration.
>
> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
> ---
>
>   drivers/i2c/i2c-cdns.c | 76 +++++++++++++++++++++++++++++++++++++++++++++-----
>   1 file changed, 69 insertions(+), 7 deletions(-)

just some nitpick, beside of this:

Reviewed-by: Heiko Schocher <hs@denx.de>

> diff --git a/drivers/i2c/i2c-cdns.c b/drivers/i2c/i2c-cdns.c
> index 0bc6aaaa6f90..5642cd91fe2e 100644
> --- a/drivers/i2c/i2c-cdns.c
> +++ b/drivers/i2c/i2c-cdns.c
> @@ -112,6 +112,7 @@ static void cdns_i2c_debug_status(struct cdns_i2c_regs *cdns_i2c)
>
>   struct i2c_cdns_bus {
>   	int id;
> +	unsigned int input_freq;
>   	struct cdns_i2c_regs __iomem *regs;	/* register base */
>   };
>
> @@ -133,20 +134,79 @@ static u32 cdns_i2c_wait(struct cdns_i2c_regs *cdns_i2c, u32 mask)
>   	return int_status & mask;
>   }
>
> +#define CDNS_I2C_DIVA_MAX	4
> +#define CDNS_I2C_DIVB_MAX	64
> +
> +static int cdns_i2c_calc_divs(unsigned long *f, unsigned long input_clk,
> +		unsigned int *a, unsigned int *b)
> +{
> +	unsigned long fscl = *f, best_fscl = *f, actual_fscl, temp;
> +	unsigned int div_a, div_b, calc_div_a = 0, calc_div_b = 0;
> +	unsigned int last_error, current_error;
> +
> +	/* calculate (divisor_a+1) x (divisor_b+1) */

please add a space before and after the "+"

> +	temp = input_clk / (22 * fscl);
> +
> +	/*
> +	 * If the calculated value is negative or 0CDNS_I2C_DIVA_MAX,
> +	 * the fscl input is out of range. Return error.
> +	 */
> +	if (!temp || (temp > (CDNS_I2C_DIVA_MAX * CDNS_I2C_DIVB_MAX)))
> +		return -EINVAL;
> +
> +	last_error = -1;
> +	for (div_a = 0; div_a < CDNS_I2C_DIVA_MAX; div_a++) {
> +		div_b = DIV_ROUND_UP(input_clk, 22 * fscl * (div_a + 1));
> +
> +		if ((div_b < 1) || (div_b > CDNS_I2C_DIVB_MAX))
> +			continue;
> +		div_b--;
> +
> +		actual_fscl = input_clk / (22 * (div_a + 1) * (div_b + 1));
> +
> +		if (actual_fscl > fscl)
> +			continue;
> +
> +		current_error = ((actual_fscl > fscl) ? (actual_fscl - fscl) :
> +							(fscl - actual_fscl));
> +
> +		if (last_error > current_error) {
> +			calc_div_a = div_a;
> +			calc_div_b = div_b;
> +			best_fscl = actual_fscl;
> +			last_error = current_error;
> +		}
> +	}
> +
> +	*a = calc_div_a;
> +	*b = calc_div_b;
> +	*f = best_fscl;
> +
> +	return 0;
> +}
> +
>   static int cdns_i2c_set_bus_speed(struct udevice *dev, unsigned int speed)
>   {
>   	struct i2c_cdns_bus *bus = dev_get_priv(dev);
> +	u32 div_a = 0, div_b = 0;
> +	unsigned long speed_p = speed;
> +	int ret = 0;
>
> -	if (speed != 100000) {
> -		printf("%s, failed to set clock speed to %u\n", __func__,
> -		       speed);
> +	if (speed > 400000) {
> +		debug("%s, failed to set clock speed to %u\n", __func__,
> +		      speed);
>   		return -EINVAL;
>   	}
>
> -	/* TODO: Calculate dividers based on CPU_CLK_1X */
> -	/* 111MHz / ( (3 * 17) * 22 ) = ~100KHz */
> -	writel((16 << CDNS_I2C_CONTROL_DIV_B_SHIFT) |
> -		(2 << CDNS_I2C_CONTROL_DIV_A_SHIFT), &bus->regs->control);
> +	ret = cdns_i2c_calc_divs(&speed_p, bus->input_freq, &div_a, &div_b);
> +	if (ret)
> +		return ret;
> +
> +	debug("%s: div_a: %d, div_b: %d, input freq: %d, speed: %d/%ld\n",
> +	      __func__, div_a, div_b, bus->input_freq, speed, speed_p);
> +
> +	writel((div_b << CDNS_I2C_CONTROL_DIV_B_SHIFT) |
> +	       (div_a << CDNS_I2C_CONTROL_DIV_A_SHIFT), &bus->regs->control);
>
>   	/* Enable master mode, ack, and 7-bit addressing */
>   	setbits_le32(&bus->regs->control, CDNS_I2C_CONTROL_MS |
> @@ -293,6 +353,8 @@ static int cdns_i2c_ofdata_to_platdata(struct udevice *dev)
>   	if (!i2c_bus->regs)
>   		return -ENOMEM;
>
> +	i2c_bus->input_freq = 100000000; /* TODO hardcode input freq for now */
> +
>   	return 0;
>   }
>
>

bye,
Heiko
Michal Simek April 18, 2016, 11:09 a.m. UTC | #2
On 18.4.2016 09:08, Heiko Schocher wrote:
> Hello Michal,
> 
> Am 14.04.2016 um 14:15 schrieb Michal Simek:
>> 400kHz is maximum freq which can be used on Xilinx ZynqMP.
>> Support it with standard divider calculator.
>> Input freq is hardcoded to 100MHz input freq till we have clock driver
>> which can provide this information for exact configuration.
>>
>> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
>> ---
>>
>>   drivers/i2c/i2c-cdns.c | 76
>> +++++++++++++++++++++++++++++++++++++++++++++-----
>>   1 file changed, 69 insertions(+), 7 deletions(-)
> 
> just some nitpick, beside of this:
> 
> Reviewed-by: Heiko Schocher <hs@denx.de>
> 
>> diff --git a/drivers/i2c/i2c-cdns.c b/drivers/i2c/i2c-cdns.c
>> index 0bc6aaaa6f90..5642cd91fe2e 100644
>> --- a/drivers/i2c/i2c-cdns.c
>> +++ b/drivers/i2c/i2c-cdns.c
>> @@ -112,6 +112,7 @@ static void cdns_i2c_debug_status(struct
>> cdns_i2c_regs *cdns_i2c)
>>
>>   struct i2c_cdns_bus {
>>       int id;
>> +    unsigned int input_freq;
>>       struct cdns_i2c_regs __iomem *regs;    /* register base */
>>   };
>>
>> @@ -133,20 +134,79 @@ static u32 cdns_i2c_wait(struct cdns_i2c_regs
>> *cdns_i2c, u32 mask)
>>       return int_status & mask;
>>   }
>>
>> +#define CDNS_I2C_DIVA_MAX    4
>> +#define CDNS_I2C_DIVB_MAX    64
>> +
>> +static int cdns_i2c_calc_divs(unsigned long *f, unsigned long input_clk,
>> +        unsigned int *a, unsigned int *b)
>> +{
>> +    unsigned long fscl = *f, best_fscl = *f, actual_fscl, temp;
>> +    unsigned int div_a, div_b, calc_div_a = 0, calc_div_b = 0;
>> +    unsigned int last_error, current_error;
>> +
>> +    /* calculate (divisor_a+1) x (divisor_b+1) */
> 
> please add a space before and after the "+"

I have fixed this.

Thanks,
Michal
diff mbox

Patch

diff --git a/drivers/i2c/i2c-cdns.c b/drivers/i2c/i2c-cdns.c
index 0bc6aaaa6f90..5642cd91fe2e 100644
--- a/drivers/i2c/i2c-cdns.c
+++ b/drivers/i2c/i2c-cdns.c
@@ -112,6 +112,7 @@  static void cdns_i2c_debug_status(struct cdns_i2c_regs *cdns_i2c)
 
 struct i2c_cdns_bus {
 	int id;
+	unsigned int input_freq;
 	struct cdns_i2c_regs __iomem *regs;	/* register base */
 };
 
@@ -133,20 +134,79 @@  static u32 cdns_i2c_wait(struct cdns_i2c_regs *cdns_i2c, u32 mask)
 	return int_status & mask;
 }
 
+#define CDNS_I2C_DIVA_MAX	4
+#define CDNS_I2C_DIVB_MAX	64
+
+static int cdns_i2c_calc_divs(unsigned long *f, unsigned long input_clk,
+		unsigned int *a, unsigned int *b)
+{
+	unsigned long fscl = *f, best_fscl = *f, actual_fscl, temp;
+	unsigned int div_a, div_b, calc_div_a = 0, calc_div_b = 0;
+	unsigned int last_error, current_error;
+
+	/* calculate (divisor_a+1) x (divisor_b+1) */
+	temp = input_clk / (22 * fscl);
+
+	/*
+	 * If the calculated value is negative or 0CDNS_I2C_DIVA_MAX,
+	 * the fscl input is out of range. Return error.
+	 */
+	if (!temp || (temp > (CDNS_I2C_DIVA_MAX * CDNS_I2C_DIVB_MAX)))
+		return -EINVAL;
+
+	last_error = -1;
+	for (div_a = 0; div_a < CDNS_I2C_DIVA_MAX; div_a++) {
+		div_b = DIV_ROUND_UP(input_clk, 22 * fscl * (div_a + 1));
+
+		if ((div_b < 1) || (div_b > CDNS_I2C_DIVB_MAX))
+			continue;
+		div_b--;
+
+		actual_fscl = input_clk / (22 * (div_a + 1) * (div_b + 1));
+
+		if (actual_fscl > fscl)
+			continue;
+
+		current_error = ((actual_fscl > fscl) ? (actual_fscl - fscl) :
+							(fscl - actual_fscl));
+
+		if (last_error > current_error) {
+			calc_div_a = div_a;
+			calc_div_b = div_b;
+			best_fscl = actual_fscl;
+			last_error = current_error;
+		}
+	}
+
+	*a = calc_div_a;
+	*b = calc_div_b;
+	*f = best_fscl;
+
+	return 0;
+}
+
 static int cdns_i2c_set_bus_speed(struct udevice *dev, unsigned int speed)
 {
 	struct i2c_cdns_bus *bus = dev_get_priv(dev);
+	u32 div_a = 0, div_b = 0;
+	unsigned long speed_p = speed;
+	int ret = 0;
 
-	if (speed != 100000) {
-		printf("%s, failed to set clock speed to %u\n", __func__,
-		       speed);
+	if (speed > 400000) {
+		debug("%s, failed to set clock speed to %u\n", __func__,
+		      speed);
 		return -EINVAL;
 	}
 
-	/* TODO: Calculate dividers based on CPU_CLK_1X */
-	/* 111MHz / ( (3 * 17) * 22 ) = ~100KHz */
-	writel((16 << CDNS_I2C_CONTROL_DIV_B_SHIFT) |
-		(2 << CDNS_I2C_CONTROL_DIV_A_SHIFT), &bus->regs->control);
+	ret = cdns_i2c_calc_divs(&speed_p, bus->input_freq, &div_a, &div_b);
+	if (ret)
+		return ret;
+
+	debug("%s: div_a: %d, div_b: %d, input freq: %d, speed: %d/%ld\n",
+	      __func__, div_a, div_b, bus->input_freq, speed, speed_p);
+
+	writel((div_b << CDNS_I2C_CONTROL_DIV_B_SHIFT) |
+	       (div_a << CDNS_I2C_CONTROL_DIV_A_SHIFT), &bus->regs->control);
 
 	/* Enable master mode, ack, and 7-bit addressing */
 	setbits_le32(&bus->regs->control, CDNS_I2C_CONTROL_MS |
@@ -293,6 +353,8 @@  static int cdns_i2c_ofdata_to_platdata(struct udevice *dev)
 	if (!i2c_bus->regs)
 		return -ENOMEM;
 
+	i2c_bus->input_freq = 100000000; /* TODO hardcode input freq for now */
+
 	return 0;
 }