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
Subject: Re: [PATCH v2 2/3] pwm: rp1: Add RP1 PWM controller driver
Date: Thu, 16 Apr 2026 12:30:43 +0200 [thread overview]
Message-ID: <aeC6U7D6TfWm8JPx@apocalypse> (raw)
In-Reply-To: <adkrHkANCzxO8KUP@monoceros>
Hi Uwe,
On 19:31 Fri 10 Apr , Uwe Kleine-König wrote:
> Hello Andrea,
>
> nice work for a v2!
Thanks!
>
> On Fri, Apr 10, 2026 at 04:09:58PM +0200, Andrea della Porta wrote:
<...snip...>
> > +#define RP1_PWM_GLOBAL_CTRL 0x000
> > +#define RP1_PWM_CHANNEL_CTRL(x) (0x014 + ((x) * 0x10))
> > +#define RP1_PWM_RANGE(x) (0x018 + ((x) * 0x10))
> > +#define RP1_PWM_PHASE(x) (0x01C + ((x) * 0x10))
> > +#define RP1_PWM_DUTY(x) (0x020 + ((x) * 0x10))
> > +
> > +/* 8:FIFO_POP_MASK + 0:Trailing edge M/S modulation */
> > +#define RP1_PWM_CHANNEL_DEFAULT (BIT(8) + BIT(0))
>
> Please add a #define for BIT(8) and then use that and
> FIELD_PREP(RP1_PWM_MODE, RP1_PWM_MODE_SOMENICENAME) to define the
> constant. Also I would define it below the register defines.
Ack.
>
> > +#define RP1_PWM_CHANNEL_ENABLE(x) BIT(x)
> > +#define RP1_PWM_POLARITY BIT(3)
> > +#define RP1_PWM_SET_UPDATE BIT(31)
> > +#define RP1_PWM_MODE_MASK GENMASK(1, 0)
>
> s/_MASK// please
>
> It would be great if the bitfield's names started with the register
> name.
Ack.
>
> > +
> > +#define RP1_PWM_NUM_PWMS 4
> > +
> > +struct rp1_pwm {
> > + struct regmap *regmap;
> > + struct clk *clk;
> > + unsigned long clk_rate;
> > + bool clk_enabled;
> > +};
> > +
> > +struct rp1_pwm_waveform {
> > + u32 period_ticks;
> > + u32 duty_ticks;
> > + bool enabled;
> > + bool inverted_polarity;
> > +};
> > +
> > +static const struct regmap_config rp1_pwm_regmap_config = {
> > + .reg_bits = 32,
> > + .val_bits = 32,
> > + .reg_stride = 4,
> > + .max_register = 0x60,
>
> I'm not a fan of aligning the = in a struct, still more if it fails like
> here. Please consistently align all =s, or even better, use a single
> space before each =. (Same for the struct definitions above, but I won't
> insist.)
Let's use the single space.
>
> > +};
> > +
> > +static void rp1_pwm_apply_config(struct pwm_chip *chip, struct pwm_device *pwm)
> > +{
> > + struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> > + u32 value;
> > +
> > + /* update the changed registers on the next strobe to avoid glitches */
> > + regmap_read(rp1->regmap, RP1_PWM_GLOBAL_CTRL, &value);
> > + value |= RP1_PWM_SET_UPDATE;
> > + regmap_write(rp1->regmap, RP1_PWM_GLOBAL_CTRL, value);
>
> I assume there is a glitch if I update two channels and the old
> configuration of the first channel ends while I'm in the middle of
> configuring the second?
The configuration registers are per-channel but the update flag is global.
I don't have details of the hw insights, my best guess is that anything that
you set in the registers before updating the flag will take effect, so there
should be no glitches.
>
> > +}
> > +
> > +static int rp1_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> > +{
> > + struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> > +
> > + /* init channel to reset defaults */
> > + regmap_write(rp1->regmap, RP1_PWM_CHANNEL_CTRL(pwm->hwpwm), RP1_PWM_CHANNEL_DEFAULT);
> > + return 0;
> > +}
> > +
> > +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);
> > + struct rp1_pwm_waveform *wfhw = _wfhw;
> > + u64 clk_rate = rp1->clk_rate;
> > + u64 ticks;
>
> if (!wf->period_length_ns)
> wfhw->enabled = false
> return 0;
>
> > + ticks = mul_u64_u64_div_u64(wf->period_length_ns, clk_rate, NSEC_PER_SEC);
>
> To ensure this doesn't overflow please fail to probe the driver if
> clk_rate > 1 GHz with an explaining comment. (Or alternatively calculate
> the length of period_ticks = U32_MAX and skip the calculation if
> wf->period_length_ns is bigger.)
Ack.
>
> > + if (ticks > U32_MAX)
> > + ticks = U32_MAX;
> > + wfhw->period_ticks = ticks;
>
> What happens if wf->period_length_ns > 0 but ticks == 0?
I've added a check, returning 1 to signal teh round-up, and a minimum tick of 1
in this case.
>
> > + if (wf->duty_offset_ns + wf->duty_length_ns >= wf->period_length_ns) {
>
> The maybe surprising effect here is that in the two cases
>
> wf->duty_offset_ns == wf->period_length_ns and wf->duty_length_ns == 0
>
> and
>
> wf->duty_length_ns == wf->period_length_ns and wf->duty_offset_ns == 0
>
> you're configuring inverted polarity. I doesn't matter technically
> because the result is the same, but for consumers still using pwm_state
> this is irritating. That's why pwm-stm32 uses inverted polarity only if
> also wf->duty_length_ns and wf->duty_offset_ns are non-zero.
Ack.
>
> > + ticks = mul_u64_u64_div_u64(wf->period_length_ns - wf->duty_length_ns,
> > + clk_rate, NSEC_PER_SEC);
>
> The rounding is wrong here. You should pick the biggest duty_length not
> bigger than wf->duty_length_ns, so you have to use
>
> ticks = wfhw->period_ticks - mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC):
>
> . I see this is a hole in the pwmtestperf coverage.
Ack.
>
> > + wfhw->inverted_polarity = true;
> > + } else {
> > + ticks = mul_u64_u64_div_u64(wf->duty_length_ns, clk_rate, NSEC_PER_SEC);
> > + wfhw->inverted_polarity = false;
> > + }
> > +
> > + if (ticks > wfhw->period_ticks)
> > + ticks = wfhw->period_ticks;
>
> You can and should assume that wf->duty_length_ns <=
> wf->period_length_ns. Then the if condition can never become true.
Ack.
>
> > + wfhw->duty_ticks = ticks;
> > +
> > + wfhw->enabled = !!wfhw->duty_ticks;
> > +
> > + return 0;
> > +}
> > +
> > +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;
> > + u32 ticks;
> > +
> > + memset(wf, 0, sizeof(*wf));
>
> wf = (struct pwm_waveform){ };
>
> is usually more efficient.
Ack.
>
> > + if (!wfhw->enabled)
> > + return 0;
> > +
> > + wf->period_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->period_ticks * NSEC_PER_SEC, clk_rate);
> > +
> > + if (wfhw->inverted_polarity) {
> > + wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->duty_ticks * NSEC_PER_SEC,
> > + clk_rate);
> > + } else {
> > + wf->duty_offset_ns = DIV_ROUND_UP_ULL((u64)wfhw->duty_ticks * NSEC_PER_SEC,
> > + clk_rate);
> > + ticks = wfhw->period_ticks - wfhw->duty_ticks;
> > + wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)ticks * NSEC_PER_SEC, clk_rate);
> > + }
>
> This needs adaption after the rounding issue in tohw is fixed.
Ack.
>
> > + 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;
> > +
> > + /* 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);
> > +
> > + /* set polarity */
> > + regmap_read(rp1->regmap, RP1_PWM_CHANNEL_CTRL(pwm->hwpwm), &value);
> > + if (!wfhw->inverted_polarity)
> > + value &= ~RP1_PWM_POLARITY;
> > + else
> > + value |= RP1_PWM_POLARITY;
> > + regmap_write(rp1->regmap, RP1_PWM_CHANNEL_CTRL(pwm->hwpwm), value);
> > +
> > + /* enable/disable */
> > + regmap_read(rp1->regmap, RP1_PWM_GLOBAL_CTRL, &value);
> > + if (wfhw->enabled)
> > + value |= RP1_PWM_CHANNEL_ENABLE(pwm->hwpwm);
> > + else
> > + value &= ~RP1_PWM_CHANNEL_ENABLE(pwm->hwpwm);
> > + regmap_write(rp1->regmap, RP1_PWM_GLOBAL_CTRL, value);
>
> You can exit early if wfhw->enabled is false after clearing the channel
> enable bit.
Ack.
>
> > + rp1_pwm_apply_config(chip, pwm);
> > +
> > + return 0;
> > +}
> > +
<,...snip...>
> > + }
> > +
> > + return 0;
> > +
> > +err_disable_clk:
> > + clk_disable_unprepare(rp1->clk);
> > +
> > + return ret;
> > +}
>
> On remove you miss to balance the call to clk_prepare_enable() (if no
> failed call to clk_prepare_enable() in rp1_pwm_resume() happend).
Since this driver now exports a syscon, it's only builtin (=Y) so
it cannot be unloaded.
I've also avoided the .remove callback via .suppress_bind_attrs.
>
> > +
> > +static int rp1_pwm_suspend(struct device *dev)
> > +{
> > + struct rp1_pwm *rp1 = dev_get_drvdata(dev);
> > +
> > + if (rp1->clk_enabled) {
> > + clk_disable_unprepare(rp1->clk);
> > + rp1->clk_enabled = false;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int rp1_pwm_resume(struct device *dev)
> > +{
> > + struct rp1_pwm *rp1 = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = clk_prepare_enable(rp1->clk);
> > + if (ret) {
> > + dev_err(dev, "Failed to enable clock on resume: %d\n", ret);
>
> Please use %pe for error codes.
Ack.
Best regards,
Andrea
>
> > + return ret;
> > + }
> > +
> > + rp1->clk_enabled = true;
> > +
> > + return 0;
> > +}
>
> Best regards
> Uwe
next prev parent reply other threads:[~2026-04-16 10:27 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-10 14:09 [PATCH v2 0/3] Add RP1 PWM controller support Andrea della Porta
2026-04-10 14:09 ` [PATCH v2 1/3] dt-bindings: pwm: Add Raspberry Pi RP1 PWM controller Andrea della Porta
2026-04-12 9:20 ` Krzysztof Kozlowski
2026-04-10 14:09 ` [PATCH v2 2/3] pwm: rp1: Add RP1 PWM controller driver Andrea della Porta
2026-04-10 17:31 ` Uwe Kleine-König
2026-04-16 10:30 ` Andrea della Porta [this message]
2026-04-16 13:48 ` Uwe Kleine-König
2026-04-17 9:05 ` Andrea della Porta
2026-04-17 10:50 ` Uwe Kleine-König
2026-04-10 14:09 ` [PATCH v2 3/3] arm64: dts: broadcom: rpi-5: Add RP1 PWM node Andrea della Porta
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=aeC6U7D6TfWm8JPx@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=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=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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox