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: 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>
This commit is contained in:
Родитель
7d1311b93e
Коммит
df59fa7f4b
|
@ -1,7 +1,7 @@
|
||||||
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 @@
|
||||||
#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 @@
|
||||||
#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)
|
||||||
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);
|
|
||||||
rate = roundup(rate, 2);
|
|
||||||
|
|
||||||
/* check if requested speed is too small */
|
best_err = speed;
|
||||||
if (rate > 30)
|
best_spr = 0;
|
||||||
return -EINVAL;
|
best_sppr = 0;
|
||||||
|
|
||||||
if (rate < 4)
|
/* Iterate over the valid range looking for best fit */
|
||||||
rate = 4;
|
for (sppr = 0; sppr < 8; sppr++) {
|
||||||
|
sppr2 = 0x1 << sppr;
|
||||||
|
|
||||||
/* Convert the rate to SPI clock divisor value. */
|
spr = tclk_hz / sppr2;
|
||||||
prescale = 0x10 + rate/2;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 = 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)
|
||||||
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)
|
||||||
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)
|
||||||
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 = {
|
||||||
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,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче