Skip to content

Commit be0d2e7

Browse files
ukleineknunojsa
authored andcommitted
pwm: axi-pwmgen: Improve precision in .apply()
axi_pwmgen_apply() does an integer division first and then divides by the result. With this procedure the rounding error from the first division makes the error of the second division grow considerably. With a typical clk_rate of 166666665 Hz the error is hardly visible because AXI_PWMGEN_PSEC_PER_SEC / rate is nearly integer. But even then it's visible for big period values: With period = 0xf0000000 ns we get: clk_period_ps = 6000; /* exact value: 6000.00006 */ target = 4026531840000 / 6000 = 671088640; The exact value is 671088633.289, so the result is off by more than 6 clock ticks. Improve precision by replacing the double division by a multiplication and a divsion. With the clock rate less than 1 GHz this doesn't loose any precision. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
1 parent a8e3c4e commit be0d2e7

File tree

1 file changed

+25
-11
lines changed

1 file changed

+25
-11
lines changed

drivers/pwm/pwm-axi-pwmgen.c

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
#define AXI_PWMGEN_LOAD_CONIG BIT(1)
4242
#define AXI_PWMGEN_RESET BIT(0)
4343

44-
#define AXI_PWMGEN_PSEC_PER_SEC 1000000000000ULL
4544
#define AXI_PWMGEN_N_MAX_PWMS 16
4645

4746
static const unsigned long long axi_pwmgen_scales[] = {
@@ -88,29 +87,44 @@ static inline struct axi_pwmgen *to_axi_pwmgen(struct pwm_chip *chip)
8887
return container_of(chip, struct axi_pwmgen, chip);
8988
}
9089

90+
#ifndef mul_u64_u64_div_u64_roundclosest
91+
static u64 mul_u64_u64_div_u64_roundclosest(u64 a, u64 b, u64 c)
92+
{
93+
u64 res = mul_u64_u64_div_u64(a, b, c);
94+
/*
95+
* Those multiplications might overflow but after the subtraction the
96+
* error cancels out.
97+
*/
98+
u64 rem = a * b - c * res;
99+
100+
if (rem * 2 >= c)
101+
res += 1;
102+
103+
return res;
104+
}
105+
#endif
106+
91107
static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *pwm,
92108
const struct pwm_state *state)
93109
{
94110
unsigned long rate;
95-
unsigned long long clk_period_ps, target, cnt;
111+
unsigned long long cnt;
96112
unsigned int ch = pwm->hwpwm;
97-
struct axi_pwmgen *pwmgen;
113+
struct axi_pwmgen *pwmgen = to_axi_pwmgen(chip);
98114

99-
pwmgen = to_axi_pwmgen(chip);
100115
rate = clk_get_rate(pwmgen->clk);
101-
clk_period_ps = DIV_ROUND_CLOSEST_ULL(AXI_PWMGEN_PSEC_PER_SEC, rate);
102116

103-
target = state->period * axi_pwmgen_scales[state->time_unit];
104-
cnt = target ? DIV_ROUND_CLOSEST_ULL(target, clk_period_ps) : 0;
117+
cnt = mul_u64_u64_div_u64_roundclosest(state->period * axi_pwmgen_scales[state->time_unit],
118+
rate, PSEC_PER_SEC);
105119
axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_PERIOD(pwmgen, ch),
106120
state->enabled ? cnt : 0);
107121

108-
target = state->duty_cycle * axi_pwmgen_scales[state->time_unit];
109-
cnt = target ? DIV_ROUND_CLOSEST_ULL(target, clk_period_ps) : 0;
122+
cnt = mul_u64_u64_div_u64_roundclosest(state->duty_cycle * axi_pwmgen_scales[state->time_unit],
123+
rate, PSEC_PER_SEC);
110124
axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_DUTY(pwmgen, ch), cnt);
111125

112-
target = state->phase * axi_pwmgen_scales[state->time_unit];
113-
cnt = target ? DIV_ROUND_CLOSEST_ULL(target, clk_period_ps) : 0;
126+
cnt = mul_u64_u64_div_u64_roundclosest(state->phase * axi_pwmgen_scales[state->time_unit],
127+
rate, PSEC_PER_SEC);
114128
axi_pwmgen_write(pwmgen, AXI_PWMGEN_CHX_PHASE(pwmgen, ch), cnt);
115129

116130
/* Apply the new config */

0 commit comments

Comments
 (0)