]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
pwm: rockchip: Round period/duty down on apply, up on get
authorNicolas Frattaroli <nicolas.frattaroli@collabora.com>
Mon, 16 Jun 2025 15:14:17 +0000 (17:14 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Mon, 7 Jul 2025 06:39:35 +0000 (08:39 +0200)
With CONFIG_PWM_DEBUG=y, the rockchip PWM driver produces warnings like
this:

  rockchip-pwm fd8b0010.pwm: .apply is supposed to round down
  duty_cycle (requested: 23529/50000, applied: 23542/50000)

This is because the driver chooses ROUND_CLOSEST for purported
idempotency reasons. However, it's possible to keep idempotency while
always rounding down in .apply().

Do this by making .get_state() always round up, and making .apply()
always round down. This is done with u64 maths, and setting both period
and duty to U32_MAX (the biggest the hardware can support) if they would
exceed their 32 bits confines.

Fixes: 12f9ce4a5198 ("pwm: rockchip: Fix period and duty cycle approximation")
Fixes: 1ebb74cf3537 ("pwm: rockchip: Add support for hardware readout")
Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Link: https://lore.kernel.org/r/20250616-rockchip-pwm-rounding-fix-v2-1-a9c65acad7b6@collabora.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-rockchip.c

index c5f50e5eaf41ac7539f59fa03f427eee6263ca90..67b85bdb491b13cedb67c52de614f4ad9be427c5 100644 (file)
@@ -8,6 +8,8 @@
 
 #include <linux/clk.h>
 #include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/math64.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
@@ -61,6 +63,7 @@ static int rockchip_pwm_get_state(struct pwm_chip *chip,
                                  struct pwm_state *state)
 {
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
+       u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
        u32 enable_conf = pc->data->enable_conf;
        unsigned long clk_rate;
        u64 tmp;
@@ -78,12 +81,12 @@ static int rockchip_pwm_get_state(struct pwm_chip *chip,
        clk_rate = clk_get_rate(pc->clk);
 
        tmp = readl_relaxed(pc->base + pc->data->regs.period);
-       tmp *= pc->data->prescaler * NSEC_PER_SEC;
-       state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
+       tmp *= prescaled_ns;
+       state->period = DIV_U64_ROUND_UP(tmp, clk_rate);
 
        tmp = readl_relaxed(pc->base + pc->data->regs.duty);
-       tmp *= pc->data->prescaler * NSEC_PER_SEC;
-       state->duty_cycle =  DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
+       tmp *= prescaled_ns;
+       state->duty_cycle =  DIV_U64_ROUND_UP(tmp, clk_rate);
 
        val = readl_relaxed(pc->base + pc->data->regs.ctrl);
        state->enabled = (val & enable_conf) == enable_conf;
@@ -103,8 +106,9 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                               const struct pwm_state *state)
 {
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
-       unsigned long period, duty;
-       u64 clk_rate, div;
+       u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC;
+       u64 clk_rate, tmp;
+       u32 period_ticks, duty_ticks;
        u32 ctrl;
 
        clk_rate = clk_get_rate(pc->clk);
@@ -114,12 +118,15 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
         * bits, every possible input period can be obtained using the
         * default prescaler value for all practical clock rate values.
         */
-       div = clk_rate * state->period;
-       period = DIV_ROUND_CLOSEST_ULL(div,
-                                      pc->data->prescaler * NSEC_PER_SEC);
+       tmp = mul_u64_u64_div_u64(clk_rate, state->period, prescaled_ns);
+       if (tmp > U32_MAX)
+               tmp = U32_MAX;
+       period_ticks = tmp;
 
-       div = clk_rate * state->duty_cycle;
-       duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);
+       tmp = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, prescaled_ns);
+       if (tmp > U32_MAX)
+               tmp = U32_MAX;
+       duty_ticks = tmp;
 
        /*
         * Lock the period and duty of previous configuration, then
@@ -131,8 +138,8 @@ static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl);
        }
 
-       writel(period, pc->base + pc->data->regs.period);
-       writel(duty, pc->base + pc->data->regs.duty);
+       writel(period_ticks, pc->base + pc->data->regs.period);
+       writel(duty_ticks, pc->base + pc->data->regs.duty);
 
        if (pc->data->supports_polarity) {
                ctrl &= ~PWM_POLARITY_MASK;