]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pwm: rcar: Improve register calculation
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Tue, 1 Apr 2025 10:29:00 +0000 (12:29 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Fri, 4 Apr 2025 07:23:39 +0000 (09:23 +0200)
There were several issues in the function rcar_pwm_set_counter():

 - The u64 values period_ns and duty_ns were cast to int on function
   call which might loose bits on 32 bit architectures.
   Fix: Make parameters to rcar_pwm_set_counter() u64
 - The algorithm divided by the result of a division which looses
   precision.
   Fix: Make use of mul_u64_u64_div_u64()
 - The calculated values were just masked to fit the respective register
   fields which again might loose bits.
   Fix: Explicitly check for overlow

Implement the respective fixes.

A side effect of fixing the 2nd issue is that there is no division by 0
if clk_get_rate() returns 0.

Fixes: ed6c1476bf7f ("pwm: Add support for R-Car PWM Timer")
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Link: https://lore.kernel.org/r/ab3dac794b2216cc1cc56d65c93dd164f8bd461b.1743501688.git.u.kleine-koenig@baylibre.com
[ukleinek: Added an explicit #include <linux/bitfield.h> to please the
0day build bot]
Link: https://lore.kernel.org/oe-kbuild-all/202504031354.VJtxScP5-lkp@intel.com/
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-rcar.c

index 2261789cc27dae46a615dfc63df2ae6510a80a23..578dbdd2d5a721bea82d083c66eae2a14dcc5460 100644 (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)