[PATCH v7 8/9] i2c: rk3x: add i2c support for rk3399 soc

David.Wu david.wu at rock-chips.com
Fri May 6 02:32:37 PDT 2016


Hi Doug,

在 2016/5/6 7:00, Doug Anderson 写道:
> David,
>
> On Wed, May 4, 2016 at 7:36 AM, David Wu <david.wu at rock-chips.com> wrote:
>> +/**
>> + * Calculate timing values for desired SCL frequency
>> + *
>> + * @clk_rate: I2C input clock rate
>> + * @t: Known I2C timing information
>> + * @t_calc: Caculated rk3x private timings that would be written into regs
>> +
>> + * Returns: 0 on success, -EINVAL if the goal SCL rate is too slow. In that case
>> + * a best-effort divider value is returned in divs. If the target rate is
>> + * too high, we silently use the highest possible rate.
>> + * The following formulas are v1's method to calculate timings.
>> + *
>> + * l = divl + 1;
>> + * h = divh + 1;
>> + * s = sda_update_config + 1;
>> + * u = start_setup_config + 1;
>> + * p = stop_setup_config + 1;
>> + * T = Tclk_i2c;
>> +
>> + * tHigh = 8 * h * T;
>> + * tLow = 8 * l * T;
>> +
>> + * tHD;sda = (l * s + 1) * T;
>> + * tSU;sda = [(8 - s) * l + 1] * T;
>> + * tI2C = 8 * (l + h) * T;
>> +
>> + * tSU;sta = (8h * u + 1) * T;
>> + * tHD;sta = [8h * (u + 1) - 1] * T;
>> + * tSU;sto = (8h * p + 1) * T;
>> + */
>> +static int rk3x_i2c_v1_calc_timings(unsigned long clk_rate,
>> +                                   struct i2c_timings *t,
>> +                                   struct rk3x_i2c_calced_timings *t_calc)
>> +{
>
> I don't think I'm going to try to understand all the math here.  I'll
> trust you that this does something sane.
>
>
>> +       /* Final divh and divl must be greater than 0, otherwise the
>> +        * hardware would not output the i2c clk.
>> +        */
>
> nit: multiline comment style doesn't match rest of file.
>
>
>>   static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate)
>>   {
>>          struct i2c_timings *t = &i2c->t;
>> -       struct rk3x_i2c_calced_timings calc;
>> +       struct rk3x_i2c_calced_timings *calc = &i2c->t_calc;
>>          u64 t_low_ns, t_high_ns;
>>          int ret;
>>
>> -       ret = rk3x_i2c_calc_divs(clk_rate, t, &calc);
>> +       ret = i2c->soc_data->calc_timings(clk_rate, t, calc);
>>          WARN_ONCE(ret != 0, "Could not reach SCL freq %u", t->bus_freq_hz);
>>
>> -       clk_enable(i2c->clk);
>> -       i2c_writel(i2c, (calc.div_high << 16) | (calc.div_low & 0xffff),
>> +       if (i2c->pclk)
>> +               clk_enable(i2c->pclk);
>> +       else
>> +               clk_enable(i2c->clk);
>> +       i2c_writel(i2c, (calc->div_high << 16) | (calc->div_low & 0xffff),
>>                     REG_CLKDIV);
>
> There is a subtle bug here, though it likely doesn't manifest in any
> current hardware configurations.
>
> Specifically if you get a clock change on a device with a "v1"
> controller while an i2c transaction is happening then you will likely
> get i2c errors.
>
> The clock change notifications work like this:
> * Before the clock change, adjust the timings based on the faster of
> the old/new clock.
> * Let the clock change happen.
> * If we didn't adjust the timings before, adjust them now.
>
> With the logic above there will be a period where the i2c transaction
> is happening slower than ideal, but that should be OK.  ...and you can
> imagine the speed of the transaction changing midway through the
> transaction--even midway through a single byte.
>
>
> With v1 some of the timing information is _not_updated by
> rk3x_i2c_adapt_div()--it's only set at the start of a transaction.
> That breaks all the above assumptions.
>
> So you should probably be updating the the RKI2C_CON register here by
> doing a read-modify-write, like:
>
> ctrl = i2c_readl(i2c, REG_CON);
> ctrl &= ~REG_CON_TUNING_MASK;
> ctrl |= i2c->t_calc.tuning;
> i2c_writel(i2c, ctrl, REG_CON);
>
>

Yeap, it seems it is a bug when a clock changes, but not update the 
regs, it might make transfer failed. It was not enough to just store 
tuning value.

> Also (optional): once you do that, there becomes much less of a reason
> to store "t_calc" in "struct rk3x_i2c".  Already you're never using
> the "div_low" and "div_high" that you store in the "struct rk3x_i2c".
> Of course, to do that you've got to change other places not to clobber
> these bits in REG_CON.
>

So, I only just need to store tuning value in the "struct rk3x_i2c", but 
not to store the "div_low" and "div_high"?
>
>> @@ -728,11 +910,11 @@ static int rk3x_i2c_clk_notifier_cb(struct notifier_block *nb, unsigned long
>>   {
>>          struct clk_notifier_data *ndata = data;
>>          struct rk3x_i2c *i2c = container_of(nb, struct rk3x_i2c, clk_rate_nb);
>> -       struct rk3x_i2c_calced_timings calc;
>>
>>          switch (event) {
>>          case PRE_RATE_CHANGE:
>> -               if (rk3x_i2c_calc_divs(ndata->new_rate, &i2c->t, &calc) != 0)
>> +               if (i2c->soc_data->calc_timings(ndata->new_rate, &i2c->t,
>> +                                               &i2c->t_calc) != 0)
>
> This change is incorrect.  Please change it back to being calculated
> in a local variable.  Optionally also add a comment that says:
>
> /*
>   * Try the calculation (but don't store the result) ahead of
>   * time to see if we need to block the clock change.  Timings
>   * shouldn't actually take effect until rk3x_i2c_adapt_div().
>   */
>
> Specifically in the case that we return NOTIFY_STOP here we _don't_
> want to have modified our internal timings.  We also _don't_ want to
> have modified our internal timings in the case that the old_rate > the
> new_rate.

Okay, use &i2c->t_calc is an error here, timings shouldn't actually take 
effect until rk3x_i2c_adapt_div().

>
> BTW: Did you manage to find anyone using an old Rockchip SoC that can
> test your patches?
>

The patches we have already used in our projects, they are verified by 
the basic tests. I would ask them to do more tests. Because we didn't 
change the clock rate now, it was a fixed value when clk inited, so we 
could not find the bug here.

>
>> @@ -1042,17 +1236,38 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
>>
>>          platform_set_drvdata(pdev, i2c);
>>
>> +       i2c->clk = devm_clk_get(&pdev->dev, NULL);
>> +       if (IS_ERR(i2c->clk)) {
>> +               dev_err(&pdev->dev, "cannot get clock\n");
>> +               return PTR_ERR(i2c->clk);
>> +       }
>> +
>>          ret = clk_prepare(i2c->clk);
>>          if (ret < 0) {
>>                  dev_err(&pdev->dev, "Could not prepare clock\n");
>>                  return ret;
>>          }
>>
>> +       if (i2c->soc_data->calc_timings == rk3x_i2c_v1_calc_timings) {
>> +               i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
>> +               if (IS_ERR(i2c->pclk)) {
>> +                       dev_err(i2c->dev, "Could not get i2c pclk\n");
>> +                       ret = PTR_ERR(i2c->pclk);
>> +                       goto err_clk;
>> +               }
>> +
>> +               ret = clk_prepare(i2c->pclk);
>> +               if (ret) {
>> +                       dev_err(i2c->dev, "Could not prepare pclk\n");
>> +                       goto err_clk;
>> +               }
>> +       }
>
> This is not matching the bindings.  You are still assuming that "i2c"
> clock is the first clock and "pclk" is the one named "pclk".  Said
> another way, if you had the following in your device tree:
>
>    clocks =  <&pmucru PCLK_I2C0_PMU>, <&pmucru SCLK_I2C0_PMU>;
>    clock-names = "pclk", "i2c";
>
> ...you'll find that you'll get back "pclk" twice.  The first time
> you'll get it because you asked for the first clock listed, the second
> time because you asked for the clock named "pclk".
>
> I'd also say that your life will probably be easier if you always
> setup both "clk" and "pclk", even on old CPUs.  It's OK to call
> "clk_prepare" twice and OK to call "clk_enable" twice.
>
> Thus, I'd probably write all the code as this (untested):
>
>    if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
>      /* Only one clock to use for bus clock and peripheral clock */
>      i2c->clk = devm_clk_get(&pdev->dev, NULL);
>      i2c->pclk = i2c->clk;
>    } else {
>      i2c->clk = devm_clk_get(&pdev->dev, "i2c");
>      i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
>    }
>    if (IS_ERR(i2c->clk)) {
>      ret = PTR_ERR(i2c->clk);
>      if (ret != -EPROBE_DEFER)
>        dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
>      return ret;
>    }
>    if (IS_ERR(i2c->pclk)) {
>      ret = PTR_ERR(i2c->pclk);
>      if (ret != -EPROBE_DEFER)
>        dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
>      return ret;
>    }
>    ret = clk_prepare(i2c->clk);
>    if (ret < 0) {
>      dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
>      return ret;
>    }
>    ret = clk_prepare(i2c->pclk);
>    if (ret < 0) {
>      dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
>      goto err_clk;
>    }
>
> If you take that advice, you can get rid of all of the "if
> (i2c->pclk)" statements in your code.
>

It would make i2c->clk to be enabled and prepared twice when uses 
rk3x_i2c_v0_calc_timings for old hardware. But if do the opposite
disabled and unprepated twice, that is okay.

>
> -Doug
>
>
>




More information about the Linux-rockchip mailing list