1
0
mirror of https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2026-01-12 01:20:14 +00:00

pwm: sifive: Fix PWM algorithm and clarify inverted compare behavior

The `frac` variable represents the pulse inactive time, and the result
of this algorithm is the pulse active time. Therefore, we must reverse
the result.

Although the SiFive Reference Manual states "pwms >= pwmcmpX -> HIGH",
the hardware behavior is inverted due to a fixed XNOR with 0. As a result,
the pwmcmp register actually defines the low (inactive) portion of the pulse.

The reference is SiFive FU740-C000 Manual[0]

Link: https://sifive.cdn.prismic.io/sifive/1a82e600-1f93-4f41-b2d8-86ed8b16acba_fu740-c000-manual-v1p6.pdf [0]

Co-developed-by: Zong Li <zong.li@sifive.com>
Signed-off-by: Zong Li <zong.li@sifive.com>
Co-developed-by: Vincent Chen <vincent.chen@sifive.com>
Signed-off-by: Vincent Chen <vincent.chen@sifive.com>
Signed-off-by: Nylon Chen <nylon.chen@sifive.com>
Link: https://lore.kernel.org/r/20250529035341.51736-3-nylon.chen@sifive.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
This commit is contained in:
Nylon Chen 2025-05-29 11:53:40 +08:00 committed by Uwe Kleine-König
parent f4bcf818e5
commit 7dbc4432ea

View File

@ -4,11 +4,28 @@
* For SiFive's PWM IP block documentation please refer Chapter 14 of
* Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf
*
* PWM output inversion: According to the SiFive Reference manual
* the output of each comparator is high whenever the value of pwms is
* greater than or equal to the corresponding pwmcmpX[Reference Manual].
*
* Figure 29 in the same manual shows that the pwmcmpXcenter bit is
* hard-tied to 0 (XNOR), which effectively inverts the comparison so that
* the output goes HIGH when `pwms < pwmcmpX`.
*
* In other words, each pwmcmp register actually defines the **inactive**
* (low) period of the pulse, not the active time exactly opposite to what
* the documentation text implies.
*
* To compensate, this driver always **inverts** the duty value when reading
* or writing pwmcmp registers , so that users interact with a conventional
* **active-high** PWM interface.
*
*
* Limitations:
* - When changing both duty cycle and period, we cannot prevent in
* software that the output might produce a period with mixed
* settings (new period length and old duty cycle).
* - The hardware cannot generate a 100% duty cycle.
* - The hardware cannot generate a 0% duty cycle.
* - The hardware generates only inverted output.
*/
#include <linux/clk.h>
@ -110,9 +127,14 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);
u32 duty, val;
u32 duty, val, inactive;
duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
/*
* PWM hardware uses 'inactive' counts in pwmcmp, so invert to get actual duty.
* Here, 'inactive' is the low time and we compute duty as max_count - inactive.
*/
duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive;
state->enabled = duty > 0;
@ -123,7 +145,7 @@ static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
state->period = ddata->real_period;
state->duty_cycle =
(u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH;
state->polarity = PWM_POLARITY_INVERSED;
state->polarity = PWM_POLARITY_NORMAL;
return 0;
}
@ -137,9 +159,9 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
unsigned long long num;
bool enabled;
int ret = 0;
u32 frac;
u32 frac, inactive;
if (state->polarity != PWM_POLARITY_INVERSED)
if (state->polarity != PWM_POLARITY_NORMAL)
return -EINVAL;
cur_state = pwm->state;
@ -157,8 +179,9 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
*/
num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH);
frac = DIV64_U64_ROUND_CLOSEST(num, state->period);
/* The hardware cannot generate a 100% duty cycle */
/* The hardware cannot generate a 0% duty cycle */
frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac;
mutex_lock(&ddata->lock);
if (state->period != ddata->approx_period) {
@ -190,7 +213,7 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
}
}
writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
writel(inactive, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
if (!state->enabled)
clk_disable(ddata->clk);