pwm: A set of fixes for pwm core and various drivers

The first three patches handle clk_get_rate() returning 0 (which might
 happen for example if the CCF is disabled). The first of these was found
 because this triggered a warning with clang, the two others by looking
 for similar issues in other drivers. Similar patches were merged earlier
 for v6.15-rc1 in commit 92b71befc349 ("Merge tag
 'objtool-urgent-2025-04-01' of
 git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip").
 The remaining three fixes address issues in the new waveform pwm API.
 Now that I worked on this a bit more, the finer details and corner cases
 are better understood and the code is fixed accordingly.
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEP4GsaTp6HlmJrf7Tj4D7WH0S/k4FAmf6XjUACgkQj4D7WH0S
 /k5tlgf/dq4lAR0MvJi8XHv2uc8sLSMlMGajCezEiIF2aQ09lqloq68uDD6YzkJV
 IlUCH353GKBNJQuFml15TodhIuAfBKKtUutW9pRgaC20HtE1PItKSk3ebYVL8m6l
 ilLeDWA07jB3gkoDdewGnjSxIQ7jCBrcF+s0OTXibpAdBP+vbbDy/aGPM0E0Byk4
 zfGg39l9g0mwJ5ayGR+ZAncxiJM1bfWWcKpZLvOWpA7VnT7Gve9ibYdONtq+2csk
 b3ZURiwA/MTa3acbYfBxZKjwOJzphy4ZnrXtpAJpko5JjdLiRLWToe5uBDEldjV0
 b1icgiI0kIv8f1JBQ02tHsx0UOh3Uw==
 =V4W8
 -----END PGP SIGNATURE-----

Merge tag 'pwm/for-6.15-rc2-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux

Pull pwm fixes from Uwe Kleine-König:
 "A set of fixes for pwm core and various drivers

  The first three patches handle clk_get_rate() returning 0 (which might
  happen for example if the CCF is disabled). The first of these was
  found because this triggered a warning with clang, the two others by
  looking for similar issues in other drivers.

  The remaining three fixes address issues in the new waveform pwm API.
  Now that I worked on this a bit more, the finer details and corner
  cases are better understood and the code is fixed accordingly"

* tag 'pwm/for-6.15-rc2-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux:
  pwm: axi-pwmgen: Let .round_waveform_tohw() signal when request was rounded up
  pwm: stm32: Search an appropriate duty_cycle if period cannot be modified
  pwm: Let pwm_set_waveform() succeed even if lowlevel driver rounded up
  pwm: fsl-ftm: Handle clk_get_rate() returning 0
  pwm: rcar: Improve register calculation
  pwm: mediatek: Prevent divide-by-zero in pwm_mediatek_config()
This commit is contained in:
Linus Torvalds 2025-04-12 08:11:19 -07:00
commit ecd5d67ad6
6 changed files with 42 additions and 31 deletions

View File

@ -322,7 +322,7 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
const struct pwm_ops *ops = chip->ops;
char wfhw[WFHWSIZE];
struct pwm_waveform wf_rounded;
int err;
int err, ret_tohw;
BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
@ -332,16 +332,16 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
if (!pwm_wf_valid(wf))
return -EINVAL;
err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
if (err)
return err;
ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
if (ret_tohw < 0)
return ret_tohw;
if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) {
err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
if (err)
return err;
if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded))
if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw == 0 && !pwm_check_rounding(wf, &wf_rounded))
dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
@ -382,7 +382,8 @@ static int __pwm_set_waveform(struct pwm_device *pwm,
wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,
wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);
}
return 0;
return ret_tohw;
}
/**

View File

@ -75,6 +75,7 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
{
struct axi_pwmgen_waveform *wfhw = _wfhw;
struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
int ret = 0;
if (wf->period_length_ns == 0) {
*wfhw = (struct axi_pwmgen_waveform){
@ -91,12 +92,15 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
if (wfhw->period_cnt == 0) {
/*
* The specified period is too short for the hardware.
* Let's round .duty_cycle down to 0 to get a (somewhat)
* valid result.
* So round up .period_cnt to 1 (i.e. the smallest
* possible period). With .duty_cycle and .duty_offset
* being less than or equal to .period, their rounded
* value must be 0.
*/
wfhw->period_cnt = 1;
wfhw->duty_cycle_cnt = 0;
wfhw->duty_offset_cnt = 0;
ret = 1;
} else {
wfhw->duty_cycle_cnt = min_t(u64,
mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
@ -111,7 +115,7 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt);
return 0;
return ret;
}
static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,

View File

@ -118,6 +118,9 @@ static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc,
unsigned long long exval;
rate = clk_get_rate(fpc->clk[fpc->period.clk_select]);
if (rate >> fpc->period.clk_ps == 0)
return 0;
exval = ticks;
exval *= 1000000000UL;
do_div(exval, rate >> fpc->period.clk_ps);
@ -190,6 +193,9 @@ static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc,
unsigned int period = fpc->period.mod_period + 1;
unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period);
if (!period_ns)
return 0;
duty = (unsigned long long)duty_ns * period;
do_div(duty, period_ns);

View File

@ -121,21 +121,25 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip);
u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH,
reg_thres = PWMTHRES;
unsigned long clk_rate;
u64 resolution;
int ret;
ret = pwm_mediatek_clk_enable(chip, pwm);
if (ret < 0)
return ret;
clk_rate = clk_get_rate(pc->clk_pwms[pwm->hwpwm]);
if (!clk_rate)
return -EINVAL;
/* Make sure we use the bus clock and not the 26MHz clock */
if (pc->soc->has_ck_26m_sel)
writel(0, pc->regs + PWM_CK_26M_SEL);
/* Using resolution in picosecond gets accuracy higher */
resolution = (u64)NSEC_PER_SEC * 1000;
do_div(resolution, clk_get_rate(pc->clk_pwms[pwm->hwpwm]));
do_div(resolution, clk_rate);
cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
while (cnt_period > 8191) {

View File

@ -8,6 +8,7 @@
* - The hardware cannot generate a 0% duty cycle.
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
@ -102,23 +103,24 @@ static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp,
rcar_pwm_write(rp, value, RCAR_PWMCR);
}
static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns,
int period_ns)
static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, u64 duty_ns,
u64 period_ns)
{
unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */
unsigned long long tmp;
unsigned long clk_rate = clk_get_rate(rp->clk);
u32 cyc, ph;
one_cycle = NSEC_PER_SEC * 100ULL << div;
do_div(one_cycle, clk_rate);
/* div <= 24 == RCAR_PWM_MAX_DIVISION, so the shift doesn't overflow. */
tmp = mul_u64_u64_div_u64(period_ns, clk_rate, (u64)NSEC_PER_SEC << div);
if (tmp > FIELD_MAX(RCAR_PWMCNT_CYC0_MASK))
tmp = FIELD_MAX(RCAR_PWMCNT_CYC0_MASK);
tmp = period_ns * 100ULL;
do_div(tmp, one_cycle);
cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK;
cyc = FIELD_PREP(RCAR_PWMCNT_CYC0_MASK, tmp);
tmp = duty_ns * 100ULL;
do_div(tmp, one_cycle);
ph = tmp & RCAR_PWMCNT_PH0_MASK;
tmp = mul_u64_u64_div_u64(duty_ns, clk_rate, (u64)NSEC_PER_SEC << div);
if (tmp > FIELD_MAX(RCAR_PWMCNT_PH0_MASK))
tmp = FIELD_MAX(RCAR_PWMCNT_PH0_MASK);
ph = FIELD_PREP(RCAR_PWMCNT_PH0_MASK, tmp);
/* Avoid prohibited setting */
if (cyc == 0 || ph == 0)

View File

@ -103,22 +103,16 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip,
if (ret)
goto out;
/*
* calculate the best value for ARR for the given PSC, refuse if
* the resulting period gets bigger than the requested one.
*/
arr = mul_u64_u64_div_u64(wf->period_length_ns, rate,
(u64)NSEC_PER_SEC * (wfhw->psc + 1));
if (arr <= wfhw->arr) {
/*
* requested period is small than the currently
* requested period is smaller than the currently
* configured and unchangable period, report back the smallest
* possible period, i.e. the current state; Initialize
* ccr to anything valid.
* possible period, i.e. the current state and return 1
* to indicate the wrong rounding direction.
*/
wfhw->ccr = 0;
ret = 1;
goto out;
}
} else {