Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

pwm: rockchip: Round period/duty down on apply, up on get

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>

authored by

Nicolas Frattaroli and committed by
Uwe Kleine-König
0b4d1abe fd0b0697

+20 -13
+20 -13
drivers/pwm/pwm-rockchip.c
··· 8 8 9 9 #include <linux/clk.h> 10 10 #include <linux/io.h> 11 + #include <linux/limits.h> 12 + #include <linux/math64.h> 11 13 #include <linux/module.h> 12 14 #include <linux/of.h> 13 15 #include <linux/platform_device.h> ··· 63 61 struct pwm_state *state) 64 62 { 65 63 struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); 64 + u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC; 66 65 u32 enable_conf = pc->data->enable_conf; 67 66 unsigned long clk_rate; 68 67 u64 tmp; ··· 81 78 clk_rate = clk_get_rate(pc->clk); 82 79 83 80 tmp = readl_relaxed(pc->base + pc->data->regs.period); 84 - tmp *= pc->data->prescaler * NSEC_PER_SEC; 85 - state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); 81 + tmp *= prescaled_ns; 82 + state->period = DIV_U64_ROUND_UP(tmp, clk_rate); 86 83 87 84 tmp = readl_relaxed(pc->base + pc->data->regs.duty); 88 - tmp *= pc->data->prescaler * NSEC_PER_SEC; 89 - state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate); 85 + tmp *= prescaled_ns; 86 + state->duty_cycle = DIV_U64_ROUND_UP(tmp, clk_rate); 90 87 91 88 val = readl_relaxed(pc->base + pc->data->regs.ctrl); 92 89 state->enabled = (val & enable_conf) == enable_conf; ··· 106 103 const struct pwm_state *state) 107 104 { 108 105 struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); 109 - unsigned long period, duty; 110 - u64 clk_rate, div; 106 + u64 prescaled_ns = (u64)pc->data->prescaler * NSEC_PER_SEC; 107 + u64 clk_rate, tmp; 108 + u32 period_ticks, duty_ticks; 111 109 u32 ctrl; 112 110 113 111 clk_rate = clk_get_rate(pc->clk); ··· 118 114 * bits, every possible input period can be obtained using the 119 115 * default prescaler value for all practical clock rate values. 120 116 */ 121 - div = clk_rate * state->period; 122 - period = DIV_ROUND_CLOSEST_ULL(div, 123 - pc->data->prescaler * NSEC_PER_SEC); 117 + tmp = mul_u64_u64_div_u64(clk_rate, state->period, prescaled_ns); 118 + if (tmp > U32_MAX) 119 + tmp = U32_MAX; 120 + period_ticks = tmp; 124 121 125 - div = clk_rate * state->duty_cycle; 126 - duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC); 122 + tmp = mul_u64_u64_div_u64(clk_rate, state->duty_cycle, prescaled_ns); 123 + if (tmp > U32_MAX) 124 + tmp = U32_MAX; 125 + duty_ticks = tmp; 127 126 128 127 /* 129 128 * Lock the period and duty of previous configuration, then ··· 138 131 writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl); 139 132 } 140 133 141 - writel(period, pc->base + pc->data->regs.period); 142 - writel(duty, pc->base + pc->data->regs.duty); 134 + writel(period_ticks, pc->base + pc->data->regs.period); 135 + writel(duty_ticks, pc->base + pc->data->regs.duty); 143 136 144 137 if (pc->data->supports_polarity) { 145 138 ctrl &= ~PWM_POLARITY_MASK;