]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
pwm: mediatek: Fix duty and period setting
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Mon, 28 Jul 2025 16:00:18 +0000 (18:00 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Tue, 29 Jul 2025 15:46:29 +0000 (17:46 +0200)
The period generated by the hardware is

(PWMDWIDTH + 1) << CLKDIV) / freq

according to my tests with a signal analyser and also the documentation.

The current algorithm doesn't consider the `+ 1` part and so configures
slightly too high periods. The same issue exists for the duty cycle
setting. So subtract 1 from both the register values for period and
duty cycle. If period is 0, bail out, if duty_cycle is 0, just disable
the PWM which results in a constant low output.

Fixes: caf065f8fd58 ("pwm: Add MediaTek PWM support")
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Link: https://lore.kernel.org/r/6d1fa87a76f8020bfe3171529b8e19baffceab10.1753717973.git.u.kleine-koenig@baylibre.com
Cc: stable@vger.kernel.org
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-mediatek.c

index b6560e52c803135a1cfc0ca871984822b98cd726..e4b595fc5a5e04531f7535a0d635520059b32b41 100644 (file)
@@ -170,7 +170,10 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
        do_div(resolution, clk_rate);
 
        cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution);
-       while (cnt_period > 8191) {
+       if (!cnt_period)
+               return -EINVAL;
+
+       while (cnt_period > 8192) {
                resolution *= 2;
                clkdiv++;
                cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000,
@@ -193,9 +196,16 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm,
        }
 
        cnt_duty = DIV_ROUND_CLOSEST_ULL((u64)duty_ns * 1000, resolution);
+
        pwm_mediatek_writel(pc, pwm->hwpwm, PWMCON, BIT(15) | clkdiv);
-       pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, cnt_period);
-       pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, cnt_duty);
+       pwm_mediatek_writel(pc, pwm->hwpwm, reg_width, cnt_period - 1);
+
+       if (cnt_duty) {
+               pwm_mediatek_writel(pc, pwm->hwpwm, reg_thres, cnt_duty - 1);
+               pwm_mediatek_enable(chip, pwm);
+       } else {
+               pwm_mediatek_disable(chip, pwm);
+       }
 
 out:
        pwm_mediatek_clk_disable(chip, pwm);
@@ -224,11 +234,8 @@ static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm,
        if (err)
                return err;
 
-       if (!pwm->state.enabled) {
+       if (!pwm->state.enabled)
                err = pwm_mediatek_clk_enable(chip, pwm);
-               if (!err)
-                       pwm_mediatek_enable(chip, pwm);
-       }
 
        return err;
 }