]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pwm: sifive: Fix rounding and idempotency issues in apply and get_state
authorNylon Chen <nylon.chen@sifive.com>
Thu, 29 May 2025 03:53:41 +0000 (11:53 +0800)
committerUwe Kleine-König <ukleinek@kernel.org>
Mon, 7 Jul 2025 06:39:34 +0000 (08:39 +0200)
This fix ensures consistent rounding and avoids mismatches
between applied and reported PWM values that could trigger false
idempotency failures in debug checks

This change ensures:
- real_period is now calculated using DIV_ROUND_UP_ULL() to avoid underestimation.
- duty_cycle is rounded up to match the fractional computation in apply()
- apply() truncates the result to compensate for get_state's rounding up logic

These fixes resolve issues like:
.apply is supposed to round down duty_cycle (requested: 360/504000, applied: 361/504124)
.apply is not idempotent (ena=1 pol=0 1739692/4032985) -> (ena=1 pol=0 1739630/4032985)

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202505080303.dBfU5YMS-lkp@intel.com/
Co-developed-by: Zong Li <zong.li@sifive.com>
Signed-off-by: Zong Li <zong.li@sifive.com>
Signed-off-by: Nylon Chen <nylon.chen@sifive.com>
Link: https://lore.kernel.org/r/20250529035341.51736-4-nylon.chen@sifive.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-sifive.c

index f3694801d3eed9d03da4824356f8f3a625c00456..4a07315b07442349c1926ef2e1fabd2617e9a58e 100644 (file)
@@ -118,7 +118,7 @@ static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,
 
        /* As scale <= 15 the shift operation cannot overflow. */
        num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale);
-       ddata->real_period = div64_ul(num, rate);
+       ddata->real_period = DIV_ROUND_UP_ULL(num, rate);
        dev_dbg(ddata->parent,
                "New real_period = %u ns\n", ddata->real_period);
 }
@@ -143,8 +143,8 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
                state->enabled = false;
 
        state->period = ddata->real_period;
-       state->duty_cycle =
-               (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH;
+       state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty * ddata->real_period,
+                                            (1U << PWM_SIFIVE_CMPWIDTH));
        state->polarity = PWM_POLARITY_NORMAL;
 
        return 0;
@@ -159,7 +159,8 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
        unsigned long long num;
        bool enabled;
        int ret = 0;
-       u32 frac, inactive;
+       u64 frac;
+       u32 inactive;
 
        if (state->polarity != PWM_POLARITY_NORMAL)
                return -EINVAL;
@@ -178,9 +179,11 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
         * consecutively
         */
        num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH);
-       frac = DIV64_U64_ROUND_CLOSEST(num, state->period);
+       frac = num;
+       do_div(frac, state->period);
        /* The hardware cannot generate a 0% duty cycle */
-       frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
+       frac = min(frac, (u64)(1U << PWM_SIFIVE_CMPWIDTH) - 1);
+       /* pwmcmp register must be loaded with the inactive(invert the duty) */
        inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
 
        mutex_lock(&ddata->lock);