spi: orion: support armada extended baud rates
authorGreg Ungerer <gerg@uclinux.org>
Sun, 28 Sep 2014 13:24:04 +0000 (23:24 +1000)
committerMark Brown <broonie@kernel.org>
Wed, 1 Oct 2014 18:21:51 +0000 (19:21 +0100)
The Armada SoC family implementation of this SPI hardware module has
extended the configuration register to allow for a wider range of SPI
clock rates. Specifically the Serial Baud Rate Pre-selection bits in the
SPI Interface Configuration Register now also use bits 6 and 7 as well.

Modify the baud rate calculation to handle these differences for the
Armada case. Potentially a baud rate can be setup using a number of
different pre-scalar and scalar combinations. This code tries all
possible pre-scalar divisors (8 in total) to try and find the most
accurate set.

This change introduces (and documents) a new device tree compatible
device name "armada-370-spi" to support this.

Signed-off-by: Greg Ungerer <gerg@uclinux.org>
Tested-by: Ezequiel Garcia <ezequiel.garcia@free-electrons.com>
Reviewed-by: Ezequiel Garcia <ezequiel.garcia@free-electrons.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
Documentation/devicetree/bindings/spi/spi-orion.txt
drivers/spi/spi-orion.c

index a3ff50fc76fb077d00d2ae25e394be2d35d72da7..50c3a3de61c1b9c1fe1cce1d4e697875af8392d6 100644 (file)
@@ -1,7 +1,7 @@
 Marvell Orion SPI device
 
 Required properties:
-- compatible : should be "marvell,orion-spi".
+- compatible : should be "marvell,orion-spi" or "marvell,armada-370-spi".
 - reg : offset and length of the register set for the device
 - cell-index : Which of multiple SPI controllers is this.
 Optional properties:
index c4675fa8b64552afac670212b3dc8b002c430f7b..acf8e48db16cdd6b77b691fc2593c092af63a5e0 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
 #include <linux/of.h>
+#include <linux/of_device.h>
 #include <linux/clk.h>
 #include <linux/sizes.h>
 #include <asm/unaligned.h>
 #define ORION_SPI_MODE_CPHA            (1 << 12)
 #define ORION_SPI_IF_8_16_BIT_MODE     (1 << 5)
 #define ORION_SPI_CLK_PRESCALE_MASK    0x1F
+#define ARMADA_SPI_CLK_PRESCALE_MASK   0xDF
 #define ORION_SPI_MODE_MASK            (ORION_SPI_MODE_CPOL | \
                                         ORION_SPI_MODE_CPHA)
 
+enum orion_spi_type {
+       ORION_SPI,
+       ARMADA_SPI,
+};
+
+struct orion_spi_dev {
+       enum orion_spi_type     typ;
+       unsigned int            min_divisor;
+       unsigned int            max_divisor;
+       u32                     prescale_mask;
+};
+
 struct orion_spi {
        struct spi_master       *master;
        void __iomem            *base;
        struct clk              *clk;
+       const struct orion_spi_dev *devdata;
 };
 
 static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg)
@@ -83,30 +98,66 @@ static int orion_spi_baudrate_set(struct spi_device *spi, unsigned int speed)
        u32 prescale;
        u32 reg;
        struct orion_spi *orion_spi;
+       const struct orion_spi_dev *devdata;
 
        orion_spi = spi_master_get_devdata(spi->master);
+       devdata = orion_spi->devdata;
 
        tclk_hz = clk_get_rate(orion_spi->clk);
 
-       /*
-        * the supported rates are: 4,6,8...30
-        * round up as we look for equal or less speed
-        */
-       rate = DIV_ROUND_UP(tclk_hz, speed);
-       rate = roundup(rate, 2);
+       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;
 
-       /* check if requested speed is too small */
-       if (rate > 30)
-               return -EINVAL;
+                       clk = tclk_hz / (spr * sppr2);
+                       err = speed - clk;
 
-       if (rate < 4)
-               rate = 4;
+                       if (err < best_err) {
+                               best_spr = spr;
+                               best_sppr = sppr;
+                               best_err = err;
+                       }
+               }
+
+               if ((best_sppr == 0) && (best_spr == 0))
+                       return -EINVAL;
+
+               prescale = ((best_sppr & 0x6) << 5) |
+                       ((best_sppr & 0x1) << 4) | best_spr;
+       } else {
+               /*
+                * the supported rates are: 4,6,8...30
+                * round up as we look for equal or less speed
+                */
+               rate = DIV_ROUND_UP(tclk_hz, speed);
+               rate = roundup(rate, 2);
+
+               /* check if requested speed is too small */
+               if (rate > 30)
+                       return -EINVAL;
 
-       /* Convert the rate to SPI clock divisor value. */
-       prescale = 0x10 + rate/2;
+               if (rate < 4)
+                       rate = 4;
+
+               /* Convert the rate to SPI clock divisor value. */
+               prescale = 0x10 + rate/2;
+       }
 
        reg = readl(spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG));
-       reg = ((reg & ~ORION_SPI_CLK_PRESCALE_MASK) | prescale);
+       reg = ((reg & ~devdata->prescale_mask) | prescale);
        writel(reg, spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG));
 
        return 0;
@@ -342,8 +393,31 @@ static int orion_spi_reset(struct orion_spi *orion_spi)
        return 0;
 }
 
+static const struct orion_spi_dev orion_spi_dev_data = {
+       .typ = ORION_SPI,
+       .min_divisor = 4,
+       .max_divisor = 30,
+       .prescale_mask = ORION_SPI_CLK_PRESCALE_MASK,
+};
+
+static const struct orion_spi_dev armada_spi_dev_data = {
+       .typ = ARMADA_SPI,
+       .min_divisor = 1,
+       .max_divisor = 1920,
+       .prescale_mask = ARMADA_SPI_CLK_PRESCALE_MASK,
+};
+
+static const struct of_device_id orion_spi_of_match_table[] = {
+       { .compatible = "marvell,orion-spi", .data = &orion_spi_dev_data, },
+       { .compatible = "marvell,armada-370-spi", .data = &armada_spi_dev_data, },
+       {}
+};
+MODULE_DEVICE_TABLE(of, orion_spi_of_match_table);
+
 static int orion_spi_probe(struct platform_device *pdev)
 {
+       const struct of_device_id *of_id;
+       const struct orion_spi_dev *devdata;
        struct spi_master *master;
        struct orion_spi *spi;
        struct resource *r;
@@ -378,6 +452,10 @@ static int orion_spi_probe(struct platform_device *pdev)
        spi = spi_master_get_devdata(master);
        spi->master = master;
 
+       of_id = of_match_device(orion_spi_of_match_table, &pdev->dev);
+       devdata = of_id->data;
+       spi->devdata = devdata;
+
        spi->clk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(spi->clk)) {
                status = PTR_ERR(spi->clk);
@@ -389,8 +467,8 @@ static int orion_spi_probe(struct platform_device *pdev)
                goto out;
 
        tclk_hz = clk_get_rate(spi->clk);
-       master->max_speed_hz = DIV_ROUND_UP(tclk_hz, 4);
-       master->min_speed_hz = DIV_ROUND_UP(tclk_hz, 30);
+       master->max_speed_hz = DIV_ROUND_UP(tclk_hz, devdata->min_divisor);
+       master->min_speed_hz = DIV_ROUND_UP(tclk_hz, devdata->max_divisor);
 
        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        spi->base = devm_ioremap_resource(&pdev->dev, r);
@@ -469,12 +547,6 @@ static const struct dev_pm_ops orion_spi_pm_ops = {
                           NULL)
 };
 
-static const struct of_device_id orion_spi_of_match_table[] = {
-       { .compatible = "marvell,orion-spi", },
-       {}
-};
-MODULE_DEVICE_TABLE(of, orion_spi_of_match_table);
-
 static struct platform_driver orion_spi_driver = {
        .driver = {
                .name   = DRIVER_NAME,