Commit 485b56f0 authored by Paul Cercueil's avatar Paul Cercueil Committed by Thierry Reding

pwm: jz4740: Improve algorithm of clock calculation

The previous algorithm hardcoded details about how the TCU clocks work.
The new algorithm will use clk_round_rate to find the perfect clock rate
for the PWM channel.

This code relies on the fact that clk_round_rate() will always round
down, which is not a valid assumption given by the clk API, but only
happens to be true with the clk drivers used for Ingenic SoCs.

Right now, there is no alternative as the clk API does not have a
round-down function (and won't have one for a while), but if it ever
comes to light, a round-down function should be used instead.
Signed-off-by: default avatarPaul Cercueil <paul@crapouillou.net>
Tested-by: default avatarMathieu Malaterre <malat@debian.org>
Tested-by: default avatarArtur Rojek <contact@artur-rojek.eu>
Signed-off-by: default avatarThierry Reding <thierry.reding@gmail.com>
parent ce1f9cec
...@@ -112,28 +112,42 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, ...@@ -112,28 +112,42 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state) const struct pwm_state *state)
{ {
struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
struct clk *clk = pwm_get_chip_data(pwm), unsigned long long tmp = 0xffffull * NSEC_PER_SEC;
*parent_clk = clk_get_parent(clk); struct clk *clk = pwm_get_chip_data(pwm);
unsigned long rate, period, duty; unsigned long period, duty;
unsigned long long tmp;
unsigned int prescaler = 0;
uint16_t ctrl; uint16_t ctrl;
long rate;
int err; int err;
rate = clk_get_rate(parent_clk); /*
tmp = (unsigned long long)rate * state->period; * Limit the clock to a maximum rate that still gives us a period value
do_div(tmp, 1000000000); * which fits in 16 bits.
period = tmp; */
do_div(tmp, state->period);
while (period > 0xffff && prescaler < 6) { /*
period >>= 2; * /!\ IMPORTANT NOTE:
rate >>= 2; * -------------------
++prescaler; * This code relies on the fact that clk_round_rate() will always round
* down, which is not a valid assumption given by the clk API, but only
* happens to be true with the clk drivers used for Ingenic SoCs.
*
* Right now, there is no alternative as the clk API does not have a
* round-down function (and won't have one for a while), but if it ever
* comes to light, a round-down function should be used instead.
*/
rate = clk_round_rate(clk, tmp);
if (rate < 0) {
dev_err(chip->dev, "Unable to round rate: %ld", rate);
return rate;
} }
if (prescaler == 6) /* Calculate period value */
return -EINVAL; tmp = (unsigned long long)rate * state->period;
do_div(tmp, NSEC_PER_SEC);
period = (unsigned long)tmp;
/* Calculate duty value */
tmp = (unsigned long long)period * state->duty_cycle; tmp = (unsigned long long)period * state->duty_cycle;
do_div(tmp, state->period); do_div(tmp, state->period);
duty = period - tmp; duty = period - tmp;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment