summaryrefslogtreecommitdiff
path: root/drivers/pwm
diff options
context:
space:
mode:
authorPaul Cercueil <paul@crapouillou.net>2020-03-23 15:24:19 +0100
committerThierry Reding <thierry.reding@gmail.com>2020-03-30 18:03:09 +0200
commit485b56f08f3342f353369ee6f9c26053efb2b925 (patch)
treef380328c4159b49761dd8fd50ead4df230d5b0b0 /drivers/pwm
parentce1f9cece057843a03a6b9de361a03eb37dd3fac (diff)
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: Paul Cercueil <paul@crapouillou.net> Tested-by: Mathieu Malaterre <malat@debian.org> Tested-by: Artur Rojek <contact@artur-rojek.eu> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
Diffstat (limited to 'drivers/pwm')
-rw-r--r--drivers/pwm/pwm-jz4740.c44
1 files changed, 29 insertions, 15 deletions
diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c
index 2eb31f2c7717..f4c6c69d0bf6 100644
--- a/drivers/pwm/pwm-jz4740.c
+++ b/drivers/pwm/pwm-jz4740.c
@@ -112,28 +112,42 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
- struct clk *clk = pwm_get_chip_data(pwm),
- *parent_clk = clk_get_parent(clk);
- unsigned long rate, period, duty;
- unsigned long long tmp;
- unsigned int prescaler = 0;
+ unsigned long long tmp = 0xffffull * NSEC_PER_SEC;
+ struct clk *clk = pwm_get_chip_data(pwm);
+ unsigned long period, duty;
uint16_t ctrl;
+ long rate;
int err;
- rate = clk_get_rate(parent_clk);
- tmp = (unsigned long long)rate * state->period;
- do_div(tmp, 1000000000);
- period = tmp;
+ /*
+ * Limit the clock to a maximum rate that still gives us a period value
+ * which fits in 16 bits.
+ */
+ do_div(tmp, state->period);
- while (period > 0xffff && prescaler < 6) {
- period >>= 2;
- rate >>= 2;
- ++prescaler;
+ /*
+ * /!\ IMPORTANT NOTE:
+ * -------------------
+ * 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)
- return -EINVAL;
+ /* Calculate period value */
+ 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;
do_div(tmp, state->period);
duty = period - tmp;