pwm: rcar: Improve register calculation

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: ed6c1476bf ("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>
pull/1199/head^2^2
Uwe Kleine-König 2025-04-01 12:29:00 +02:00 committed by Uwe Kleine-König
parent 7ca59947b5
commit e7327c1930
1 changed files with 13 additions and 11 deletions

View File

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