From: Andrea della Porta <andrea.porta@suse.com>
To: "Uwe Kleine-König" <ukleinek@kernel.org>
Cc: Andrea della Porta <andrea.porta@suse.com>,
linux-pwm@vger.kernel.org, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Florian Fainelli <florian.fainelli@broadcom.com>,
Broadcom internal kernel review list
<bcm-kernel-feedback-list@broadcom.com>,
devicetree@vger.kernel.org, linux-rpi-kernel@lists.infradead.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org,
Naushir Patuck <naush@raspberrypi.com>,
Stanimir Varbanov <svarbanov@suse.de>,
mbrugger@suse.com, Sean Young <sean@mess.org>,
Julian Braha <julianbraha@gmail.com>
Subject: Re: [PATCH v6 2/3] pwm: rp1: Add RP1 PWM controller driver
Date: Fri, 17 Jul 2026 11:25:35 +0200 [thread overview]
Message-ID: <aln1D3E5QZULZ2U1@apocalypse> (raw)
In-Reply-To: <alibzwe9Mjf3GxJo@monoceros>
Hi Uwe,
On 11:18 Thu 16 Jul , Uwe Kleine-König wrote:
> 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).
Ok, but in this case what the output of the disabled channel should be?
If it was inverted before the disable, would it make sense to let the output
be high? Or are we allowed to hard code a polarity value on disable?
>
> Also please initialize the whole structure, best done using:
>
> *wfhw = (typeof(*wfhw)){
> .enabled = false,
> }
Ack. In this case inverted_polarity would be falsei though, see above.
>
> > + 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;
Ack.
>
> > + }
> > + 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);
So it will end up as 'duty_length_ns + duty_offset_ns > period_length_ns', which is
timing violation. Is it allowed (or even required) in the PWM subsystem?
>
> > + }
> > +
> > + 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.
Ack.
>
> > + }
> > +
> > + /* 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.
Ack.
>
> > +
> > + 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.
Ack.
Many thanks,
Andrea
>
> Best regards
> Uwe
next prev parent reply other threads:[~2026-07-17 9:22 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-03 17:05 [PATCH v6 0/3] Add RP1 PWM controller support Andrea della Porta
2026-07-03 17:05 ` [PATCH v6 1/3] dt-bindings: pwm: Add Raspberry Pi RP1 PWM controller Andrea della Porta
2026-07-07 12:43 ` Matthias Brugger
2026-07-03 17:05 ` [PATCH v6 2/3] pwm: rp1: Add RP1 PWM controller driver Andrea della Porta
2026-07-03 17:17 ` sashiko-bot
2026-07-06 12:55 ` Andrea della Porta
2026-07-07 12:43 ` Matthias Brugger
2026-07-14 9:25 ` Andrea della Porta
2026-07-16 9:18 ` Uwe Kleine-König
2026-07-17 9:25 ` Andrea della Porta [this message]
2026-07-17 13:49 ` Uwe Kleine-König
2026-07-03 17:05 ` [PATCH v6 3/3] arm64: dts: broadcom: rpi-5: Add RP1 PWM node Andrea della Porta
2026-07-07 12:43 ` Matthias Brugger
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aln1D3E5QZULZ2U1@apocalypse \
--to=andrea.porta@suse.com \
--cc=bcm-kernel-feedback-list@broadcom.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=florian.fainelli@broadcom.com \
--cc=julianbraha@gmail.com \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=linux-rpi-kernel@lists.infradead.org \
--cc=mbrugger@suse.com \
--cc=naush@raspberrypi.com \
--cc=robh@kernel.org \
--cc=sean@mess.org \
--cc=svarbanov@suse.de \
--cc=ukleinek@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.