Hi Addy,
On Monday 13 October 2014 at 10:44:04, Addy Ke wrote:
> As show in I2C specification:
> - Standard-mode: the minimum HIGH period of the scl clock is 4.0us
> the minimum LOW period of the scl clock is 4.7us
> - Fast-mode: the minimum HIGH period of the scl clock is 0.6us
> the minimum LOW period of the scl clock is 1.3us
>
> I have measured i2c SCL waveforms in fast-mode by oscilloscope
> on rk3288-pinky board. the LOW period of the scl clock is 1.3us.
> It is so critical that we must adjust LOW division to increase
> the LOW period of the scl clock.
>
> Thanks Doug for the suggestion about division formulas.
>
> Tested-by: Heiko Stuebner
> Reviewed-by: Doug Anderson
> Tested-by: Doug Anderson
> Signed-off-by: Addy Ke
> ---
> Changes in v2:
> - remove Fast-mode plus and HS-mode
> - use new formulas suggested by Doug
> Changes in V3:
> - use new formulas (sep 30) suggested by Doug
> Changes in V4:
> - fix some wrong style
> - WARN_ONCE if min_low_div > max_low_div
> Changes in V5:
> - round up for i2c_rate and round down for scl_rate, suggested by Max
> - place a WARN_ON if scl_rate < 1000, suggested by Max
> - add div_high and div_low overflow protection, suggested by Max
>
> drivers/i2c/busses/i2c-rk3x.c | 161
> +++--- 1 file changed, 152
> insertions(+), 9 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c
> index b41d979..a30a4fb 100644
> --- a/drivers/i2c/busses/i2c-rk3x.c
> +++ b/drivers/i2c/busses/i2c-rk3x.c
> @@ -24,6 +24,7 @@
> #include
> #include
> #include
> +#include
>
>
> /* Register Map */
> @@ -428,18 +429,158 @@ out:
> return IRQ_HANDLED;
> }
>
> -static void rk3x_i2c_set_scl_rate(struct rk3x_i2c *i2c, unsigned long
> scl_rate) +static int rk3x_i2c_calc_divs(unsigned long i2c_rate, unsigned
> long scl_rate, + unsigned long *div_low, unsigned
> long
*div_high)
> {
> - unsigned long i2c_rate = clk_get_rate(i2c->clk);
> - unsigned int div;
> + unsigned long min_low_ns, min_high_ns;
> + unsigned long max_data_hold_ns;
> + unsigned long data_hold_buffer_ns;
> + unsigned long max_low_ns, min_total_ns;
> +
> + unsigned long i2c_rate_khz, scl_rate_khz;
> +
> + unsigned long min_low_div, min_high_div;
> + unsigned long max_low_div;
> +
> + unsigned long min_div_for_hold, min_total_div;
> + unsigned long extra_div, extra_low_div, ideal_low_div;
> +
> + /* Only support standard-mode and fast-mode */
> + if (WARN_ON(scl_rate > 40))
> + scl_rate = 40;
> +
> + /* prevent scl_rate_khz from becoming 0 */
> + if (WARN_ON(scl_rate < 1000))
> + scl_rate = 1000;
> +
> + /*
> + * min_low_ns: The minimum number of ns we need to hold low
> + * to meet i2c spec
> + * min_high_ns: The minimum number of ns we need to hold high
> + * to meet i2c spec
> + * max_low_ns: The maximum number of ns we can hold low
> + * to meet i2c spec
> + *
> + * Note: max_low_ns should be (max data hold time * 2 - buffer)
> + * This is because the i2c host on Rockchip holds the data line
> + * for half the low time.
> + */
> + if (scl_rate <= 10) {
> + min_low_ns = 4700;
> + min_high_ns = 4000;
> + max_data_hold_ns = 3450;
> + data_hold_buffer_ns = 50;
> + } else {
> + min_low_ns = 1300;
> + min_high_ns = 600;
> + max_data_hold_ns = 900;
> + data_hold_buffer_ns = 50;
> + }
> + max_low_ns = max_data_hold_ns * 2 - data_hold_buffer_ns;
> + min_total_ns = min_low_ns + min_high_ns;
> +
> + /* Adjust to avoid overflow */
> + i2c_rate_khz = DIV_ROUND_UP(i2c_rate, 1000);
> + scl_rate_khz = scl_rate / 1000;
>
> - /* set DIV = DIVH = DIVL
> - * SCL rate = (clk rate) / (8 * (DIVH + 1 + DIVL + 1))
> - * = (clk rate) / (16 * (DIV + 1))
> + /*
> + * We need the total div to be >= this number
> + * so we don't clock too fast.
> + */
> + min_total_div = DIV_ROUND_UP(i2c_rate_khz, scl_rate_khz * 8);
> +
> + /* These are the min dividers needed for min hold times. */
> + min_low_div = DIV_ROUND_UP(i2c_rate_khz * min_low_ns, 8 * 100);
> + min_high_div = DIV_ROUND_UP(i2c_rate_khz * min_high_ns, 8 * 100);
> + min_div_for_hold = (min_low_div + min_high_div);
> +
> + /*
> + * This is the maximum divider so we don't go over the max.
> + * We don't round up here (we round down) since this is a max.
>*/
> - div = DIV_ROUND_UP(i2c_rate, scl_rate * 16) - 1;
> + max_low_div = i2c_rate_khz * max_low_ns / (8 * 100);
> +
> + if (min_low_div > max_low_div) {
> + WARN_ONCE(true,
> + "Conflicting, min_low_div %lu, max_low_div %lu\