Re: [PATCH v4 3/3] pwm: Add support for Xilinx AXI Timer

From: Uwe Kleine-König
Date: Fri Jun 25 2021 - 12:57:09 EST


Hello Sean,

On Fri, Jun 25, 2021 at 11:13:33AM -0400, Sean Anderson wrote:
> On 6/25/21 2:19 AM, Uwe Kleine-König wrote:
> > On Fri, May 28, 2021 at 05:45:22PM -0400, Sean Anderson wrote:
> >> + * Hardware limitations:

Please make this "* Limitations:" to match what the other drivers do and
so ease grepping for this info.

> >> + * - When changing both duty cycle and period, we may end up with one cycle
> >> + * with the old duty cycle and the new period.
> >
> > That means it doesn't reset the counter when a new period is set, right?
>
> Correct. The only way to write to the counter is to stop the timer and
> restart it.

ok.

> >> + * - Cannot produce 100% duty cycle.
> >
> > Can it produce a 0% duty cycle? Below you're calling
> > xilinx_timer_tlr_period(..., ..., ..., 0) then which returns -ERANGE.
>
> Yes. This is what you get when you try to specify 100% duty cycle (e.g.
> TLR0 == TLR1).

OK, so the hardware can do it, but your driver doesn't make use of it,
right?

> >> + * - Only produces "normal" output.
> >
> > Does the output emit a low level when it's disabled?
>
> I believe so.

Is there a possibility to be sure? I'd like to know that to complete my
picture about the behaviour of the supported PWMs.

> >> + */
> >> +
> >> [...]
> >> +static int xilinx_pwm_apply(struct pwm_chip *chip, struct pwm_device *unused,
> >> + const struct pwm_state *state)
> >> +{
> >> + int ret;
> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> >> + u32 tlr0, tlr1;
> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> >> + bool enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> >> +
> >> + if (state->polarity != PWM_POLARITY_NORMAL)
> >> + return -EINVAL;
> >> +
> >> + ret = xilinx_timer_tlr_period(priv, &tlr0, tcsr0, state->period);
> >> + if (ret)
> >> + return ret;
> >
> > The implementation of xilinx_timer_tlr_period (in patch 2/3) returns
> > -ERANGE for big periods. The good behaviour to implement is to cap to
> > the biggest period possible in this case.
>
> Ok. Is this documented anywhere?

I tried but Thierry didn't like the result and I didn't retry. The
problem is also that many drivers we already have in the tree don't
behave like this (because for a long time nobody cared). That new
drivers should behave this way is my effort to get some consistent
behaviour.

> And wouldn't this result in the wrong duty cycle? E.g. say the max
> value is 100 and I try to apply a period of 150 and a duty_cycle of 75
> (for a 50% duty cycle). If we cap at 100, then I will instead have a
> 75% duty cycle, and there will be no error.

Yes that is right. That there is no feedback is a problem that we have
for a long time. I have a prototype patch that implements a
pwm_round_state() function that lets a consumer know the result of
applying a certain pwm_state in advance. But we're not there yet.

> So I will silently get the wrong duty cycle, even when that duty cycle
> is probably more important than the period.

It depends on the use case and every policy is wrong for some cases. So
I picked the policy I already explained because it is a) easy to
implement for lowlevel drivers and b) it's easy to work with for
consumers once we have pwm_round_state().

> > Also note that state->period is an u64 but it is casted to unsigned int
> > as this is the type of the forth parameter of xilinx_timer_tlr_period.
>
> Hm, it looks like I immediately cast period to a u64. I will change the
> signature for this function next revision.

Then note that period * clk_get_rate(priv->clk) might overflow.

> >> + ret = xilinx_timer_tlr_period(priv, &tlr1, tcsr1, state->duty_cycle);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + xilinx_timer_write(priv, tlr0, TLR0);
> >> + xilinx_timer_write(priv, tlr1, TLR1);
> >> +
> >> + if (state->enabled) {
> >> + /* Only touch the TCSRs if we aren't already running */
> >> + if (!enabled) {
> >> + /* Load TLR into TCR */
> >> + xilinx_timer_write(priv, tcsr0 | TCSR_LOAD, TCSR0);
> >> + xilinx_timer_write(priv, tcsr1 | TCSR_LOAD, TCSR1);
> >> + /* Enable timers all at once with ENALL */
> >> + tcsr0 = (TCSR_PWM_SET & ~TCSR_ENT) | (tcsr0 & TCSR_UDT);
> >> + tcsr1 = TCSR_PWM_SET | TCSR_ENALL | (tcsr1 & TCSR_UDT);
> >> + xilinx_timer_write(priv, tcsr0, TCSR0);
> >> + xilinx_timer_write(priv, tcsr1, TCSR1);
> >> + }
> >> + } else {
> >> + xilinx_timer_write(priv, 0, TCSR0);
> >> + xilinx_timer_write(priv, 0, TCSR1);
> >> + }
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static void xilinx_pwm_get_state(struct pwm_chip *chip,
> >> + struct pwm_device *unused,
> >> + struct pwm_state *state)
> >> +{
> >> + struct xilinx_timer_priv *priv = xilinx_pwm_chip_to_priv(chip);
> >> + u32 tlr0 = xilinx_timer_read(priv, TLR0);
> >> + u32 tlr1 = xilinx_timer_read(priv, TLR1);
> >> + u32 tcsr0 = xilinx_timer_read(priv, TCSR0);
> >> + u32 tcsr1 = xilinx_timer_read(priv, TCSR1);
> >> +
> >> + state->period = xilinx_timer_get_period(priv, tlr0, tcsr0);
> >> + state->duty_cycle = xilinx_timer_get_period(priv, tlr1, tcsr1);
> >> + state->enabled = xilinx_timer_pwm_enabled(tcsr0, tcsr1);
> >> + state->polarity = PWM_POLARITY_NORMAL;
> >
> > Are the values returned here sensible if the hardware isn't in PWM mode?
>
> Yes. If the hardware isn't in PWM mode, then state->enabled will be
> false.

Ah right. Good enough.

> >> + else if (pwm_cells)
> >> + return dev_err_probe(dev, -EINVAL, "#pwm-cells must be 0\n");
> >
> > What is the rationale here to not support #pwm-cells = <2>?
>
> Only one PWM is supported. But otherwise there is no particular
> reason.

The usual binding is to have 3 additional parameters.
1) chip-local pwm number (which can only be 0 for a pwmchip having
.npwm = 1)
2) the "typical" period
3) some flags (like PWM_POLARITY_*)

I don't care much if you implement it with or without 1), but 2) and 3)
should IMHO be here. If you don't want 1),
http://patchwork.ozlabs.org/project/linux-pwm/patch/20210622030948.966748-1-bjorn.andersson@xxxxxxxxxx/
might be interesting for you. (But note, Thierry didn't give feedback to
this yet, it might be possible he wants 1)-3) for new drivers.)

Best regards
Uwe

--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | https://www.pengutronix.de/ |

Attachment: signature.asc
Description: PGP signature