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

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Nylon Chen and committed by
Uwe Kleine-König
7dbc4432 f4bcf818

+31 -8
+31 -8
drivers/pwm/pwm-sifive.c
··· 4 4 * For SiFive's PWM IP block documentation please refer Chapter 14 of 5 5 * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf 6 6 * 7 + * PWM output inversion: According to the SiFive Reference manual 8 + * the output of each comparator is high whenever the value of pwms is 9 + * greater than or equal to the corresponding pwmcmpX[Reference Manual]. 10 + * 11 + * Figure 29 in the same manual shows that the pwmcmpXcenter bit is 12 + * hard-tied to 0 (XNOR), which effectively inverts the comparison so that 13 + * the output goes HIGH when `pwms < pwmcmpX`. 14 + * 15 + * In other words, each pwmcmp register actually defines the **inactive** 16 + * (low) period of the pulse, not the active time exactly opposite to what 17 + * the documentation text implies. 18 + * 19 + * To compensate, this driver always **inverts** the duty value when reading 20 + * or writing pwmcmp registers , so that users interact with a conventional 21 + * **active-high** PWM interface. 22 + * 23 + * 7 24 * Limitations: 8 25 * - When changing both duty cycle and period, we cannot prevent in 9 26 * software that the output might produce a period with mixed 10 27 * settings (new period length and old duty cycle). 11 - * - The hardware cannot generate a 100% duty cycle. 28 + * - The hardware cannot generate a 0% duty cycle. 12 29 * - The hardware generates only inverted output. 13 30 */ 14 31 #include <linux/clk.h> ··· 127 110 struct pwm_state *state) 128 111 { 129 112 struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); 130 - u32 duty, val; 113 + u32 duty, val, inactive; 131 114 132 - duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); 115 + inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); 116 + /* 117 + * PWM hardware uses 'inactive' counts in pwmcmp, so invert to get actual duty. 118 + * Here, 'inactive' is the low time and we compute duty as max_count - inactive. 119 + */ 120 + duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive; 133 121 134 122 state->enabled = duty > 0; 135 123 ··· 145 123 state->period = ddata->real_period; 146 124 state->duty_cycle = 147 125 (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH; 148 - state->polarity = PWM_POLARITY_INVERSED; 126 + state->polarity = PWM_POLARITY_NORMAL; 149 127 150 128 return 0; 151 129 } ··· 159 137 unsigned long long num; 160 138 bool enabled; 161 139 int ret = 0; 162 - u32 frac; 140 + u32 frac, inactive; 163 141 164 - if (state->polarity != PWM_POLARITY_INVERSED) 142 + if (state->polarity != PWM_POLARITY_NORMAL) 165 143 return -EINVAL; 166 144 167 145 cur_state = pwm->state; ··· 179 157 */ 180 158 num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH); 181 159 frac = DIV64_U64_ROUND_CLOSEST(num, state->period); 182 - /* The hardware cannot generate a 100% duty cycle */ 160 + /* The hardware cannot generate a 0% duty cycle */ 183 161 frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); 162 + inactive = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - frac; 184 163 185 164 mutex_lock(&ddata->lock); 186 165 if (state->period != ddata->approx_period) { ··· 213 190 } 214 191 } 215 192 216 - writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); 193 + writel(inactive, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm)); 217 194 218 195 if (!state->enabled) 219 196 clk_disable(ddata->clk);