From 704d918341c378c5f9505dfdf32d315e256d3846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 28 Jul 2025 18:00:17 +0200 Subject: [PATCH 1/3] pwm: mediatek: Handle hardware enable and clock enable separately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop handling the clocks in pwm_mediatek_enable() and pwm_mediatek_disable(). This is a preparing change for the next commit that requires that clocks and the enable bit are handled separately. Also move these two functions a bit further up in the source file to make them usable in pwm_mediatek_config(), which is needed in the next commit, too. Signed-off-by: Uwe Kleine-König Reviewed-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/55c94fe2917ece152ee1e998f4675642a7716f13.1753717973.git.u.kleine-koenig@baylibre.com Cc: stable@vger.kernel.org Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-mediatek.c | 60 ++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index 6777c511622a..b6560e52c803 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -121,6 +121,26 @@ static inline void pwm_mediatek_writel(struct pwm_mediatek_chip *chip, writel(value, chip->regs + chip->soc->reg_offset[num] + offset); } +static void pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); + u32 value; + + value = readl(pc->regs); + value |= BIT(pwm->hwpwm); + writel(value, pc->regs); +} + +static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); + u32 value; + + value = readl(pc->regs); + value &= ~BIT(pwm->hwpwm); + writel(value, pc->regs); +} + static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -183,35 +203,6 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, return ret; } -static int pwm_mediatek_enable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); - u32 value; - int ret; - - ret = pwm_mediatek_clk_enable(chip, pwm); - if (ret < 0) - return ret; - - value = readl(pc->regs); - value |= BIT(pwm->hwpwm); - writel(value, pc->regs); - - return 0; -} - -static void pwm_mediatek_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); - u32 value; - - value = readl(pc->regs); - value &= ~BIT(pwm->hwpwm); - writel(value, pc->regs); - - pwm_mediatek_clk_disable(chip, pwm); -} - static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { @@ -221,8 +212,10 @@ static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm, return -EINVAL; if (!state->enabled) { - if (pwm->state.enabled) + if (pwm->state.enabled) { pwm_mediatek_disable(chip, pwm); + pwm_mediatek_clk_disable(chip, pwm); + } return 0; } @@ -231,8 +224,11 @@ static int pwm_mediatek_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (err) return err; - if (!pwm->state.enabled) - err = pwm_mediatek_enable(chip, pwm); + if (!pwm->state.enabled) { + err = pwm_mediatek_clk_enable(chip, pwm); + if (!err) + pwm_mediatek_enable(chip, pwm); + } return err; } From f21d136caf8171f94159d975ea4620c164431bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 28 Jul 2025 18:00:18 +0200 Subject: [PATCH 2/3] pwm: mediatek: Fix duty and period setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: AngeloGioacchino Del Regno 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 --- drivers/pwm/pwm-mediatek.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/pwm/pwm-mediatek.c b/drivers/pwm/pwm-mediatek.c index b6560e52c803..e4b595fc5a5e 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -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; } From 65c6f742ab14ab1a2679fba72b82dcc0289d96f1 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Mon, 28 Jul 2025 15:41:44 -0400 Subject: [PATCH 3/3] pwm: imx-tpm: Reset counter if CMOD is 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per the i.MX93 TRM, section 67.3.2.1 "MOD register update", the value of the TPM counter does NOT get updated when writing MOD.MOD unless SC.CMOD != 0. Therefore, with the current code, assuming the following sequence: 1) pwm_disable() 2) pwm_apply_might_sleep() /* period is changed here */ 3) pwm_enable() and assuming only one channel is active, if CNT.COUNT is higher than the MOD.MOD value written during the pwm_apply_might_sleep() call then, when re-enabling the PWM during pwm_enable(), the counter will end up resetting after UINT32_MAX - CNT.COUNT + MOD.MOD cycles instead of MOD.MOD cycles as normally expected. Fix this problem by forcing a reset of the TPM counter before MOD.MOD is written. Fixes: 738a1cfec2ed ("pwm: Add i.MX TPM PWM driver support") Cc: stable@vger.kernel.org Signed-off-by: Laurentiu Mihalcea Link: https://lore.kernel.org/r/20250728194144.22884-1-laurentiumihalcea111@gmail.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-imx-tpm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c index 7ee7b65b9b90..5b399de16d60 100644 --- a/drivers/pwm/pwm-imx-tpm.c +++ b/drivers/pwm/pwm-imx-tpm.c @@ -204,6 +204,15 @@ static int pwm_imx_tpm_apply_hw(struct pwm_chip *chip, val |= FIELD_PREP(PWM_IMX_TPM_SC_PS, p->prescale); writel(val, tpm->base + PWM_IMX_TPM_SC); + /* + * if the counter is disabled (CMOD == 0), programming the new + * period length (MOD) will not reset the counter (CNT). If + * CNT.COUNT happens to be bigger than the new MOD value then + * the counter will end up being reset way too late. Therefore, + * manually reset it to 0. + */ + if (!cmod) + writel(0x0, tpm->base + PWM_IMX_TPM_CNT); /* * set period count: * if the PWM is disabled (CMOD[1:0] = 2b00), then MOD register