diff options
| author | Martin Blumenstingl <martin.blumenstingl@googlemail.com> | 2019-06-12 21:59:07 +0200 | 
|---|---|---|
| committer | Thierry Reding <thierry.reding@gmail.com> | 2019-06-26 11:39:09 +0200 | 
| commit | fb2081e870e9d59a0e6d076989e04c932c3ba23d (patch) | |
| tree | 946a8c6e6389b3ba87a5a771253c11bc77c3f14d /drivers/pwm/pwm-meson.c | |
| parent | 1064c6bacedd70edfcea6b1b6af529f621e681ae (diff) | |
pwm: meson: Simplify the calculation of the pre-divider and count
Replace the loop to calculate the pre-divider and count with two
separate div64_u64() calculations. This makes the code easier to read
and improves the precision.
Three example cases:
1) 32.768kHz LPO clock for the SDIO wifi chip on Khadas VIM
   clock input: 500MHz (FCLK_DIV4)
   period: 30518ns
   duty cycle: 15259ns
old algorithm: pre_div=0, cnt=15259
new algorithm: pre_div=0, cnt=15259
(no difference in calculated values)
2) PWM LED on Khadas VIM
   clock input: 24MHz (XTAL)
   period: 7812500ns
   duty cycle: 7812500ns
old algorithm: pre_div=2, cnt=62004
new algorithm: pre_div=2, cnt=62500
Using a scope (24MHz sampling rate) shows the actual difference:
- old: 7753000ns, off by -59500ns (0.7616%)
- new: 7815000ns, off by +2500ns (0.032%)
3) Theoretical case where pre_div is different
   clock input: 24MHz (XTAL)
   period: 2730624ns
   duty cycle: 1365312ns
old algorithm: pre_div=1, cnt=32768
new algorithm: pre_div=0, cnt=65534
Using a scope (24MHz sampling rate) shows the actual difference:
- old: 2731000ns
- new: 2731000ns
(my scope is not precise enough to measure the difference if there's
any)
Suggested-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Acked-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
Diffstat (limited to 'drivers/pwm/pwm-meson.c')
| -rw-r--r-- | drivers/pwm/pwm-meson.c | 25 | 
1 files changed, 10 insertions, 15 deletions
| diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 8a5658f2b947..99815b26f6b2 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -12,6 +12,7 @@  #include <linux/err.h>  #include <linux/io.h>  #include <linux/kernel.h> +#include <linux/math64.h>  #include <linux/module.h>  #include <linux/of.h>  #include <linux/of_device.h> @@ -145,7 +146,6 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm,  	struct meson_pwm_channel *channel = pwm_get_chip_data(pwm);  	unsigned int duty, period, pre_div, cnt, duty_cnt;  	unsigned long fin_freq = -1; -	u64 fin_ps;  	duty = state->duty_cycle;  	period = state->period; @@ -164,24 +164,19 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm,  	}  	dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); -	fin_ps = (u64)NSEC_PER_SEC * 1000; -	do_div(fin_ps, fin_freq); - -	/* Calc pre_div with the period */ -	for (pre_div = 0; pre_div <= MISC_CLK_DIV_MASK; pre_div++) { -		cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000, -					    fin_ps * (pre_div + 1)); -		dev_dbg(meson->chip.dev, "fin_ps=%llu pre_div=%u cnt=%u\n", -			fin_ps, pre_div, cnt); -		if (cnt <= 0xffff) -			break; -	} +	pre_div = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * 0xffffLL);  	if (pre_div > MISC_CLK_DIV_MASK) {  		dev_err(meson->chip.dev, "unable to get period pre_div\n");  		return -EINVAL;  	} +	cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1)); +	if (cnt > 0xffff) { +		dev_err(meson->chip.dev, "unable to get period cnt\n"); +		return -EINVAL; +	} +  	dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period,  		pre_div, cnt); @@ -195,8 +190,8 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm,  		channel->lo = cnt;  	} else {  		/* Then check is we can have the duty with the same pre_div */ -		duty_cnt = DIV_ROUND_CLOSEST_ULL((u64)duty * 1000, -						 fin_ps * (pre_div + 1)); +		duty_cnt = div64_u64(fin_freq * (u64)duty, +				     NSEC_PER_SEC * (pre_div + 1));  		if (duty_cnt > 0xffff) {  			dev_err(meson->chip.dev, "unable to get duty cycle\n");  			return -EINVAL; | 
