spi: mvebu: fix baudrate calculation for armada variant
authorUwe Kleine-König <uwe@kleine-koenig.org>
Thu, 8 Dec 2016 16:37:08 +0000 (17:37 +0100)
committerMark Brown <broonie@kernel.org>
Thu, 8 Dec 2016 17:54:51 +0000 (17:54 +0000)
The calculation of SPR and SPPR doesn't round correctly at several
places which might result in baud rates that are too big. For example
with tclk_hz = 250000001 and target rate 25000000 it determined a
divider of 10 which is wrong.

Instead of fixing all the corner cases replace the calculation by an
algorithm without a loop which should even be quicker to execute apart
from being correct.

Fixes: df59fa7f4bca ("spi: orion: support armada extended baud rates")
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Mark Brown <broonie@kernel.org>
drivers/spi/spi-orion.c

index ded37025b445834d0367ef41cac7aa80b2c656cf..6b001c4a564086528e9db608f7250305656d5da7 100644 (file)
@@ -138,37 +138,62 @@ static int orion_spi_baudrate_set(struct spi_device *spi, unsigned int speed)
        tclk_hz = clk_get_rate(orion_spi->clk);
 
        if (devdata->typ == ARMADA_SPI) {
-               unsigned int clk, spr, sppr, sppr2, err;
-               unsigned int best_spr, best_sppr, best_err;
-
-               best_err = speed;
-               best_spr = 0;
-               best_sppr = 0;
-
-               /* Iterate over the valid range looking for best fit */
-               for (sppr = 0; sppr < 8; sppr++) {
-                       sppr2 = 0x1 << sppr;
-
-                       spr = tclk_hz / sppr2;
-                       spr = DIV_ROUND_UP(spr, speed);
-                       if ((spr == 0) || (spr > 15))
-                               continue;
-
-                       clk = tclk_hz / (spr * sppr2);
-                       err = speed - clk;
-
-                       if (err < best_err) {
-                               best_spr = spr;
-                               best_sppr = sppr;
-                               best_err = err;
-                       }
-               }
+               /*
+                * Given the core_clk (tclk_hz) and the target rate (speed) we
+                * determine the best values for SPR (in [0 .. 15]) and SPPR (in
+                * [0..7]) such that
+                *
+                *      core_clk / (SPR * 2 ** SPPR)
+                *
+                * is as big as possible but not bigger than speed.
+                */
 
-               if ((best_sppr == 0) && (best_spr == 0))
-                       return -EINVAL;
+               /* best integer divider: */
+               unsigned divider = DIV_ROUND_UP(tclk_hz, speed);
+               unsigned spr, sppr;
+
+               if (divider < 16) {
+                       /* This is the easy case, divider is less than 16 */
+                       spr = divider;
+                       sppr = 0;
+
+               } else {
+                       unsigned two_pow_sppr;
+                       /*
+                        * Find the highest bit set in divider. This and the
+                        * three next bits define SPR (apart from rounding).
+                        * SPPR is then the number of zero bits that must be
+                        * appended:
+                        */
+                       sppr = fls(divider) - 4;
+
+                       /*
+                        * As SPR only has 4 bits, we have to round divider up
+                        * to the next multiple of 2 ** sppr.
+                        */
+                       two_pow_sppr = 1 << sppr;
+                       divider = (divider + two_pow_sppr - 1) & -two_pow_sppr;
+
+                       /*
+                        * recalculate sppr as rounding up divider might have
+                        * increased it enough to change the position of the
+                        * highest set bit. In this case the bit that now
+                        * doesn't make it into SPR is 0, so there is no need to
+                        * round again.
+                        */
+                       sppr = fls(divider) - 4;
+                       spr = divider >> sppr;
+
+                       /*
+                        * Now do range checking. SPR is constructed to have a
+                        * width of 4 bits, so this is fine for sure. So we
+                        * still need to check for sppr to fit into 3 bits:
+                        */
+                       if (sppr > 7)
+                               return -EINVAL;
+               }
 
-               prescale = ((best_sppr & 0x6) << 5) |
-                       ((best_sppr & 0x1) << 4) | best_spr;
+               prescale = ((sppr & 0x6) << 5) | ((sppr & 0x1) << 4) | spr;
        } else {
                /*
                 * the supported rates are: 4,6,8...30