pwm: renesas-tpu: Improve maths to compute register settings
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>
Wed, 20 Apr 2022 12:12:39 +0000 (14:12 +0200)
committerThierry Reding <thierry.reding@gmail.com>
Fri, 20 May 2022 14:18:05 +0000 (16:18 +0200)
The newly computed register values are intended to exactly match the
previously computed values. The main improvement is that the prescaler
is computed without a loop that involves two divisions in each step.
This uses the fact, that prescalers[i] = 1 << (2 * i).

Assuming a moderately smart compiler, the needed number of divisions for
the case where the requested period is too big, is reduced from 5 to 2.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
drivers/pwm/pwm-renesas-tpu.c

index 5bced2050ece794927803c4e841c01f1c2b7fc6f..4aff3870010c49bb0049b7e75538e6cce877368e 100644 (file)
@@ -244,7 +244,6 @@ static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
 static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                          int duty_ns, int period_ns, bool enabled)
 {
-       static const unsigned int prescalers[] = { 1, 4, 16, 64 };
        struct tpu_pwm_device *tpd = pwm_get_chip_data(pwm);
        struct tpu_device *tpu = to_tpu_device(chip);
        unsigned int prescaler;
@@ -254,26 +253,47 @@ static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
        u32 duty;
        int ret;
 
+       clk_rate = clk_get_rate(tpu->clk);
+
+       period = clk_rate / (NSEC_PER_SEC / period_ns);
+
        /*
-        * Pick a prescaler to avoid overflowing the counter.
-        * TODO: Pick the highest acceptable prescaler.
+        * Find the minimal prescaler in [0..3] such that
+        *
+        *     period >> (2 * prescaler) < 0x10000
+        *
+        * This could be calculated using something like:
+        *
+        *     prescaler = max(ilog2(period) / 2, 7) - 7;
+        *
+        * but given there are only four allowed results and that ilog2 isn't
+        * cheap on all platforms using a switch statement is more effective.
         */
-       clk_rate = clk_get_rate(tpu->clk);
+       switch (period) {
+       case 1 ... 0xffff:
+               prescaler = 0;
+               break;
 
-       for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
-               period = clk_rate / prescalers[prescaler]
-                      / (NSEC_PER_SEC / period_ns);
-               if (period <= 0xffff)
-                       break;
-       }
+       case 0x10000 ... 0x3ffff:
+               prescaler = 1;
+               break;
 
-       if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
-               dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
-               return -ENOTSUPP;
+       case 0x40000 ... 0xfffff:
+               prescaler = 2;
+               break;
+
+       case 0x100000 ... 0x3fffff:
+               prescaler = 3;
+               break;
+
+       default:
+               return -EINVAL;
        }
 
+       period >>= 2 * prescaler;
+
        if (duty_ns) {
-               duty = clk_rate / prescalers[prescaler]
+               duty = (clk_rate >> 2 * prescaler)
                     / (NSEC_PER_SEC / duty_ns);
                if (duty > period)
                        return -EINVAL;
@@ -283,7 +303,7 @@ static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
 
        dev_dbg(&tpu->pdev->dev,
                "rate %u, prescaler %u, period %u, duty %u\n",
-               clk_rate, prescalers[prescaler], period, duty);
+               clk_rate, 1 << (2 * prescaler), period, duty);
 
        if (tpd->prescaler == prescaler && tpd->period == period)
                duty_only = true;