Skip to content

Commit

Permalink
pwm: rcar: Improve register calculation
Browse files Browse the repository at this point in the history
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: ed6c147 ("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>
  • Loading branch information
Uwe Kleine-König authored and Uwe Kleine-König committed Apr 4, 2025
1 parent 7ca5994 commit e7327c1
Showing 1 changed file with 13 additions and 11 deletions.
24 changes: 13 additions & 11 deletions drivers/pwm/pwm-rcar.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit e7327c1

Please sign in to comment.