Commit df59fa7f authored by Greg Ungerer's avatar Greg Ungerer Committed by Mark Brown

spi: orion: support armada extended baud rates

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: default avatarGreg Ungerer <gerg@uclinux.org>
Tested-by: default avatarEzequiel Garcia <ezequiel.garcia@free-electrons.com>
Reviewed-by: default avatarEzequiel Garcia <ezequiel.garcia@free-electrons.com>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 7d1311b9
Marvell Orion SPI device Marvell Orion SPI device
Required properties: 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 - reg : offset and length of the register set for the device
- cell-index : Which of multiple SPI controllers is this. - cell-index : Which of multiple SPI controllers is this.
Optional properties: Optional properties:
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/sizes.h> #include <linux/sizes.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
...@@ -40,13 +41,27 @@ ...@@ -40,13 +41,27 @@
#define ORION_SPI_MODE_CPHA (1 << 12) #define ORION_SPI_MODE_CPHA (1 << 12)
#define ORION_SPI_IF_8_16_BIT_MODE (1 << 5) #define ORION_SPI_IF_8_16_BIT_MODE (1 << 5)
#define ORION_SPI_CLK_PRESCALE_MASK 0x1F #define ORION_SPI_CLK_PRESCALE_MASK 0x1F
#define ARMADA_SPI_CLK_PRESCALE_MASK 0xDF
#define ORION_SPI_MODE_MASK (ORION_SPI_MODE_CPOL | \ #define ORION_SPI_MODE_MASK (ORION_SPI_MODE_CPOL | \
ORION_SPI_MODE_CPHA) 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 orion_spi {
struct spi_master *master; struct spi_master *master;
void __iomem *base; void __iomem *base;
struct clk *clk; struct clk *clk;
const struct orion_spi_dev *devdata;
}; };
static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg) 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) ...@@ -83,30 +98,66 @@ static int orion_spi_baudrate_set(struct spi_device *spi, unsigned int speed)
u32 prescale; u32 prescale;
u32 reg; u32 reg;
struct orion_spi *orion_spi; struct orion_spi *orion_spi;
const struct orion_spi_dev *devdata;
orion_spi = spi_master_get_devdata(spi->master); orion_spi = spi_master_get_devdata(spi->master);
devdata = orion_spi->devdata;
tclk_hz = clk_get_rate(orion_spi->clk); tclk_hz = clk_get_rate(orion_spi->clk);
/* if (devdata->typ == ARMADA_SPI) {
* the supported rates are: 4,6,8...30 unsigned int clk, spr, sppr, sppr2, err;
* round up as we look for equal or less speed unsigned int best_spr, best_sppr, best_err;
*/
rate = DIV_ROUND_UP(tclk_hz, speed); best_err = speed;
rate = roundup(rate, 2); 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 */ clk = tclk_hz / (spr * sppr2);
if (rate > 30) err = speed - clk;
return -EINVAL;
if (rate < 4) if (err < best_err) {
rate = 4; 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. */ if (rate < 4)
prescale = 0x10 + rate/2; 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 = 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)); writel(reg, spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG));
return 0; return 0;
...@@ -342,8 +393,31 @@ static int orion_spi_reset(struct orion_spi *orion_spi) ...@@ -342,8 +393,31 @@ static int orion_spi_reset(struct orion_spi *orion_spi)
return 0; 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) 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 spi_master *master;
struct orion_spi *spi; struct orion_spi *spi;
struct resource *r; struct resource *r;
...@@ -378,6 +452,10 @@ static int orion_spi_probe(struct platform_device *pdev) ...@@ -378,6 +452,10 @@ static int orion_spi_probe(struct platform_device *pdev)
spi = spi_master_get_devdata(master); spi = spi_master_get_devdata(master);
spi->master = 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); spi->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(spi->clk)) { if (IS_ERR(spi->clk)) {
status = PTR_ERR(spi->clk); status = PTR_ERR(spi->clk);
...@@ -389,8 +467,8 @@ static int orion_spi_probe(struct platform_device *pdev) ...@@ -389,8 +467,8 @@ static int orion_spi_probe(struct platform_device *pdev)
goto out; goto out;
tclk_hz = clk_get_rate(spi->clk); tclk_hz = clk_get_rate(spi->clk);
master->max_speed_hz = DIV_ROUND_UP(tclk_hz, 4); master->max_speed_hz = DIV_ROUND_UP(tclk_hz, devdata->min_divisor);
master->min_speed_hz = DIV_ROUND_UP(tclk_hz, 30); master->min_speed_hz = DIV_ROUND_UP(tclk_hz, devdata->max_divisor);
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
spi->base = devm_ioremap_resource(&pdev->dev, r); spi->base = devm_ioremap_resource(&pdev->dev, r);
...@@ -469,12 +547,6 @@ static const struct dev_pm_ops orion_spi_pm_ops = { ...@@ -469,12 +547,6 @@ static const struct dev_pm_ops orion_spi_pm_ops = {
NULL) 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 = { static struct platform_driver orion_spi_driver = {
.driver = { .driver = {
.name = DRIVER_NAME, .name = DRIVER_NAME,
......
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