Re: [PATCH v6 2/3] pwm: rp1: Add RP1 PWM controller driver
From: Uwe Kleine-König
Date: Thu Jul 16 2026 - 05:24:14 EST
Hello,
On Fri, Jul 03, 2026 at 07:05:25PM +0200, Andrea della Porta wrote:
> +static int rp1_pwm_round_waveform_tohw(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const struct pwm_waveform *wf,
> + void *_wfhw)
> +{
> + struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> + u64 period_ticks, duty_ticks, offset_ticks;
> + struct rp1_pwm_waveform *wfhw = _wfhw;
> + u64 clk_rate = rp1->clk_rate;
> + int ret = 0;
> +
> + if (!wf->period_length_ns) {
> + wfhw->enabled = false;
> + wfhw->inverted_polarity = (pwm_get_polarity(pwm) == PWM_POLARITY_INVERSED);
pwm_get_polarity(pwm) looks wrong here for several reasons. 1st the
polarity is defined in *wf and should not depend on the current state,
2nd for a disabled hardware the polarity doesn't matter anyhow, and 3rd
you should not call pwm API functions from the lowlevel driver (which
might interfere with subsystem locking).
Also please initialize the whole structure, best done using:
*wfhw = (typeof(*wfhw)){
.enabled = false,
}
> + return 0;
> + }
> +
> + period_ticks = mul_u64_u64_div_u64(wf->period_length_ns, clk_rate, NSEC_PER_SEC);
> +
> + /*
> + * The period is limited to U32_MAX, and it will be decremented by one later
> + * to allow 100% duty cycle.
> + */
> + if (period_ticks > U32_MAX) {
> + period_ticks = U32_MAX;
> + } else if (period_ticks < 2) {
> + period_ticks = 2;
> + ret = 1;
> + }
> +
> + duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC);
> + duty_ticks = min(duty_ticks, period_ticks);
> + offset_ticks = mul_u64_u64_div_u64(wf->duty_offset_ns, clk_rate, NSEC_PER_SEC);
> + if (offset_ticks >= period_ticks) {
> + u64 remainder;
> +
> + div64_u64_rem(offset_ticks, period_ticks, &remainder);
> + offset_ticks = remainder;
offset_ticks = period_ticks - 1;
> + }
> + if (duty_ticks && offset_ticks &&
> + duty_ticks + offset_ticks >= period_ticks) {
> + wfhw->duty_ticks = period_ticks - duty_ticks;
> + wfhw->inverted_polarity = true;
> + } else {
> + wfhw->duty_ticks = duty_ticks;
> + wfhw->inverted_polarity = false;
> + }
> + /* Account for the extra tick at the end of the period */
> + wfhw->period_ticks = period_ticks - 1;
> +
> + wfhw->enabled = true;
> +
> + return ret;
> +}
> +
> +static int rp1_pwm_round_waveform_fromhw(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const void *_wfhw,
> + struct pwm_waveform *wf)
> +{
> + struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> + const struct rp1_pwm_waveform *wfhw = _wfhw;
> + u64 clk_rate = rp1->clk_rate;
> + u64 ticks;
> +
> + *wf = (struct pwm_waveform){ };
> +
> + if (!wfhw->enabled)
> + return 0;
> +
> + wf->period_length_ns = DIV_ROUND_UP_ULL(((u64)wfhw->period_ticks + 1) * NSEC_PER_SEC,
> + clk_rate);
> +
> + if (!wfhw->inverted_polarity) {
> + wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->duty_ticks * NSEC_PER_SEC,
> + (u32)clk_rate);
> + } else {
> + if (wfhw->duty_ticks > (u64)wfhw->period_ticks + 1) {
> + /* 100% duty cycle case */
> + ticks = 0;
> + } else {
> + ticks = (u64)wfhw->period_ticks + 1 - wfhw->duty_ticks;
> + }
> + wf->duty_length_ns = DIV_ROUND_UP_ULL(ticks * NSEC_PER_SEC, clk_rate);
> + wf->duty_offset_ns = wf->period_length_ns - wf->duty_length_ns;
The duty_offset_ns calculation is wrong.
Consider clk_rate = 3000000, period_ticks = 8, inverted_polarity = true and
duty_ticks = 4.
Then you have:
.period_length_ns = 2666.6666666666666 ns ~> 2667
.duty_length_ns = 1333.3333333333333 ns -> 1334
.duty_offset_ns = 1333.3333333333333 ns -> 1334
but .period_length_ns - .duty_length_ns is 1333.
To get this right, you have to calculate
wf->duty_offset_ns = DIV_ROUND_UP_ULL((u64)(wfhw->period_ticks + 1 - ticks) * NSEC_PER_SEC, clk_rate);
> + }
> +
> + return 0;
> +}
> +
> +static int rp1_pwm_write_waveform(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const void *_wfhw)
> +{
> + struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> + const struct rp1_pwm_waveform *wfhw = _wfhw;
> + u32 value, ctrl;
> +
> + /* set polarity */
> + regmap_read(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), &value);
> + if (!wfhw->inverted_polarity)
> + value &= ~RP1_PWM_CHAN_CTRL_POLARITY;
> + else
> + value |= RP1_PWM_CHAN_CTRL_POLARITY;
> + regmap_write(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), value);
> +
> + /* early exit if disabled */
> + regmap_read(rp1->regmap, RP1_PWM_GLB_CTRL, &ctrl);
> + if (!wfhw->enabled) {
> + ctrl &= ~RP1_PWM_GLB_CTRL_CHANNEL_ENABLE(pwm->hwpwm);
> + goto exit_disable;
Please don't use goto unless for error handling.
> + }
> +
> + /* set period and duty cycle */
> + regmap_write(rp1->regmap,
> + RP1_PWM_RANGE(pwm->hwpwm), wfhw->period_ticks);
> + regmap_write(rp1->regmap,
> + RP1_PWM_DUTY(pwm->hwpwm), wfhw->duty_ticks);
> +
> + /* enable the channel */
> + ctrl |= RP1_PWM_GLB_CTRL_CHANNEL_ENABLE(pwm->hwpwm);
> +exit_disable:
> + regmap_write(rp1->regmap, RP1_PWM_GLB_CTRL, ctrl);
> +
> + rp1_pwm_apply_config(chip, pwm);
> +
> + return 0;
> +}
> +
> +static int rp1_pwm_read_waveform(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + void *_wfhw)
> +{
> + struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> + struct rp1_pwm_waveform *wfhw = _wfhw;
> + u32 value;
> +
> + regmap_read(rp1->regmap, RP1_PWM_GLB_CTRL, &value);
> + wfhw->enabled = !!(value & RP1_PWM_GLB_CTRL_CHANNEL_ENABLE(pwm->hwpwm));
> +
> + regmap_read(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), &value);
> + wfhw->inverted_polarity = !!(value & RP1_PWM_CHAN_CTRL_POLARITY);
> +
> + if (wfhw->enabled) {
> + regmap_read(rp1->regmap, RP1_PWM_RANGE(pwm->hwpwm), &wfhw->period_ticks);
> + regmap_read(rp1->regmap, RP1_PWM_DUTY(pwm->hwpwm), &wfhw->duty_ticks);
> + } else {
> + wfhw->period_ticks = 0;
> + wfhw->duty_ticks = 0;
> + }
Ideally use *wfhw = (typeof(*wfhw)){ ... } here, too, to make it obvious
that the complete struct is initialized.
> +
> + return 0;
> +}
> [...]
> +static struct platform_driver rp1_pwm_driver = {
> + .probe = rp1_pwm_probe,
> + .remove = rp1_pwm_remove,
> + .driver = {
> + .name = "rp1-pwm",
> + .of_match_table = rp1_pwm_of_match,
> + .pm = pm_ptr(&rp1_pwm_pm_ops),
> + .suppress_bind_attrs = true,
If the driver cannot be removed, .remove() is just dead code. Drop it
and note in a comment that this is needed because a syscon cannot be
removed.
Best regards
Uwe
Attachment:
signature.asc
Description: PGP signature