diff options
author | Martin Blumenstingl <martin.blumenstingl@googlemail.com> | 2019-06-12 15:59:07 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2019-06-26 05:39:09 -0400 |
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 @@ | |||
12 | #include <linux/err.h> | 12 | #include <linux/err.h> |
13 | #include <linux/io.h> | 13 | #include <linux/io.h> |
14 | #include <linux/kernel.h> | 14 | #include <linux/kernel.h> |
15 | #include <linux/math64.h> | ||
15 | #include <linux/module.h> | 16 | #include <linux/module.h> |
16 | #include <linux/of.h> | 17 | #include <linux/of.h> |
17 | #include <linux/of_device.h> | 18 | #include <linux/of_device.h> |
@@ -145,7 +146,6 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, | |||
145 | struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); | 146 | struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); |
146 | unsigned int duty, period, pre_div, cnt, duty_cnt; | 147 | unsigned int duty, period, pre_div, cnt, duty_cnt; |
147 | unsigned long fin_freq = -1; | 148 | unsigned long fin_freq = -1; |
148 | u64 fin_ps; | ||
149 | 149 | ||
150 | duty = state->duty_cycle; | 150 | duty = state->duty_cycle; |
151 | period = state->period; | 151 | period = state->period; |
@@ -164,24 +164,19 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, | |||
164 | } | 164 | } |
165 | 165 | ||
166 | dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); | 166 | dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); |
167 | fin_ps = (u64)NSEC_PER_SEC * 1000; | ||
168 | do_div(fin_ps, fin_freq); | ||
169 | |||
170 | /* Calc pre_div with the period */ | ||
171 | for (pre_div = 0; pre_div <= MISC_CLK_DIV_MASK; pre_div++) { | ||
172 | cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000, | ||
173 | fin_ps * (pre_div + 1)); | ||
174 | dev_dbg(meson->chip.dev, "fin_ps=%llu pre_div=%u cnt=%u\n", | ||
175 | fin_ps, pre_div, cnt); | ||
176 | if (cnt <= 0xffff) | ||
177 | break; | ||
178 | } | ||
179 | 167 | ||
168 | pre_div = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * 0xffffLL); | ||
180 | if (pre_div > MISC_CLK_DIV_MASK) { | 169 | if (pre_div > MISC_CLK_DIV_MASK) { |
181 | dev_err(meson->chip.dev, "unable to get period pre_div\n"); | 170 | dev_err(meson->chip.dev, "unable to get period pre_div\n"); |
182 | return -EINVAL; | 171 | return -EINVAL; |
183 | } | 172 | } |
184 | 173 | ||
174 | cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1)); | ||
175 | if (cnt > 0xffff) { | ||
176 | dev_err(meson->chip.dev, "unable to get period cnt\n"); | ||
177 | return -EINVAL; | ||
178 | } | ||
179 | |||
185 | dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period, | 180 | dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period, |
186 | pre_div, cnt); | 181 | pre_div, cnt); |
187 | 182 | ||
@@ -195,8 +190,8 @@ static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, | |||
195 | channel->lo = cnt; | 190 | channel->lo = cnt; |
196 | } else { | 191 | } else { |
197 | /* Then check is we can have the duty with the same pre_div */ | 192 | /* Then check is we can have the duty with the same pre_div */ |
198 | duty_cnt = DIV_ROUND_CLOSEST_ULL((u64)duty * 1000, | 193 | duty_cnt = div64_u64(fin_freq * (u64)duty, |
199 | fin_ps * (pre_div + 1)); | 194 | NSEC_PER_SEC * (pre_div + 1)); |
200 | if (duty_cnt > 0xffff) { | 195 | if (duty_cnt > 0xffff) { |
201 | dev_err(meson->chip.dev, "unable to get duty cycle\n"); | 196 | dev_err(meson->chip.dev, "unable to get duty cycle\n"); |
202 | return -EINVAL; | 197 | return -EINVAL; |