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:
parent
f4bcf818e5
commit
7dbc4432ea
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user