aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pwm/pwm-meson.c
diff options
context:
space:
mode:
authorMartin Blumenstingl <martin.blumenstingl@googlemail.com>2019-06-12 15:59:07 -0400
committerThierry Reding <thierry.reding@gmail.com>2019-06-26 05:39:09 -0400
commitfb2081e870e9d59a0e6d076989e04c932c3ba23d (patch)
tree946a8c6e6389b3ba87a5a771253c11bc77c3f14d /drivers/pwm/pwm-meson.c
parent1064c6bacedd70edfcea6b1b6af529f621e681ae (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.c25
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;