]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pwm: stm32: Fix rounding issue for requests with inverted polarity
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Wed, 15 Apr 2026 14:50:12 +0000 (16:50 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Thu, 16 Apr 2026 05:25:15 +0000 (07:25 +0200)
The calculation of the number of pwm clk ticks from a time length in
nanoseconds involves a division and thus some rounding. That might
result in

duty_ticks + offset_ticks < period_ticks

despite

duty_length_ns + duty_offset_ns >= period_length_ns

. The stm32 PWM cannot configure offset_ticks freely, it can only select
0 or period_length_ns - duty_length_ns---that is the classic normal and
inverted polarity. The decision to select the hardware polarity must be
done using the ticks values and not the nanoseconds times to adhere to
the rounding rules by the pwm core.

With the pwm clk running at 208900 kHz on my test machine
(stm32mp135f-dk), a test case that was handled wrong is:

# pwmround -P 9999962 -O 24970 -D 9974992
period_length = 9999962
duty_length = 9974840
duty_offset = 25123

With this change applied the rounding is done correctly:

# pwmround -P 9999962 -O 24970 -D 9974992
period_length = 9999962
duty_length = 9974840
duty_offset = 0

Fixes: deaba9cff809 ("pwm: stm32: Implementation of the waveform callbacks")
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Link: https://patch.msgid.link/c5e7767cee821b5f6e00f95bd14a5e13015646fb.1776264104.git.u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-stm32.c

index 2594fb771b04ad715520803adc9c0cacd2880b5a..935257a890b0670f0eeb33e4a3aacb635a3da5f8 100644 (file)
@@ -68,7 +68,7 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip,
        struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
        unsigned int ch = pwm->hwpwm;
        unsigned long rate;
-       u64 ccr, duty;
+       u64 duty_ticks, offset_ticks;
        int ret;
 
        if (wf->period_length_ns == 0) {
@@ -164,23 +164,25 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip,
                wfhw->arr = min_t(u64, arr, priv->max_arr) - 1;
        }
 
-       duty = mul_u64_u64_div_u64(wf->duty_length_ns, rate,
-                                  (u64)NSEC_PER_SEC * (wfhw->psc + 1));
-       duty = min_t(u64, duty, wfhw->arr + 1);
+       duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, rate,
+                                        (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+       duty_ticks = min_t(u64, duty_ticks, wfhw->arr + 1);
 
-       if (wf->duty_length_ns && wf->duty_offset_ns &&
-           wf->duty_length_ns + wf->duty_offset_ns >= wf->period_length_ns) {
+       offset_ticks = mul_u64_u64_div_u64(wf->duty_offset_ns, rate,
+                                          (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+       offset_ticks = min_t(u64, offset_ticks, wfhw->arr + 1);
+
+       if (duty_ticks && offset_ticks &&
+           duty_ticks + offset_ticks >= wfhw->arr + 1) {
                wfhw->ccer |= TIM_CCER_CCxP(ch + 1);
                if (priv->have_complementary_output)
                        wfhw->ccer |= TIM_CCER_CCxNP(ch + 1);
 
-               ccr = wfhw->arr + 1 - duty;
+               wfhw->ccr = wfhw->arr + 1 - duty_ticks;
        } else {
-               ccr = duty;
+               wfhw->ccr = duty_ticks;
        }
 
-       wfhw->ccr = min_t(u64, ccr, wfhw->arr + 1);
-
 out:
        dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x\n",
                pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,