diff options
author | Greg Ungerer <gerg@uclinux.org> | 2014-09-28 09:24:04 -0400 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2014-10-01 14:21:51 -0400 |
commit | df59fa7f4bca9658b75f0f5fee225b3a057475c5 (patch) | |
tree | 6c01e397e59715c8043f0de634299f2273bac18b /drivers/spi/spi-orion.c | |
parent | 7d1311b93e58ed55f3a31cc8f94c4b8fe988a2b9 (diff) |
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>
Diffstat (limited to 'drivers/spi/spi-orion.c')
-rw-r--r-- | drivers/spi/spi-orion.c | 116 |
1 files changed, 94 insertions, 22 deletions
diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index c4675fa8b645..acf8e48db16c 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c | |||
@@ -18,6 +18,7 @@ | |||
18 | #include <linux/module.h> | 18 | #include <linux/module.h> |
19 | #include <linux/pm_runtime.h> | 19 | #include <linux/pm_runtime.h> |
20 | #include <linux/of.h> | 20 | #include <linux/of.h> |
21 | #include <linux/of_device.h> | ||
21 | #include <linux/clk.h> | 22 | #include <linux/clk.h> |
22 | #include <linux/sizes.h> | 23 | #include <linux/sizes.h> |
23 | #include <asm/unaligned.h> | 24 | #include <asm/unaligned.h> |
@@ -40,13 +41,27 @@ | |||
40 | #define ORION_SPI_MODE_CPHA (1 << 12) | 41 | #define ORION_SPI_MODE_CPHA (1 << 12) |
41 | #define ORION_SPI_IF_8_16_BIT_MODE (1 << 5) | 42 | #define ORION_SPI_IF_8_16_BIT_MODE (1 << 5) |
42 | #define ORION_SPI_CLK_PRESCALE_MASK 0x1F | 43 | #define ORION_SPI_CLK_PRESCALE_MASK 0x1F |
44 | #define ARMADA_SPI_CLK_PRESCALE_MASK 0xDF | ||
43 | #define ORION_SPI_MODE_MASK (ORION_SPI_MODE_CPOL | \ | 45 | #define ORION_SPI_MODE_MASK (ORION_SPI_MODE_CPOL | \ |
44 | ORION_SPI_MODE_CPHA) | 46 | ORION_SPI_MODE_CPHA) |
45 | 47 | ||
48 | enum orion_spi_type { | ||
49 | ORION_SPI, | ||
50 | ARMADA_SPI, | ||
51 | }; | ||
52 | |||
53 | struct orion_spi_dev { | ||
54 | enum orion_spi_type typ; | ||
55 | unsigned int min_divisor; | ||
56 | unsigned int max_divisor; | ||
57 | u32 prescale_mask; | ||
58 | }; | ||
59 | |||
46 | struct orion_spi { | 60 | struct orion_spi { |
47 | struct spi_master *master; | 61 | struct spi_master *master; |
48 | void __iomem *base; | 62 | void __iomem *base; |
49 | struct clk *clk; | 63 | struct clk *clk; |
64 | const struct orion_spi_dev *devdata; | ||
50 | }; | 65 | }; |
51 | 66 | ||
52 | static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg) | 67 | 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 | u32 prescale; | 98 | u32 prescale; |
84 | u32 reg; | 99 | u32 reg; |
85 | struct orion_spi *orion_spi; | 100 | struct orion_spi *orion_spi; |
101 | const struct orion_spi_dev *devdata; | ||
86 | 102 | ||
87 | orion_spi = spi_master_get_devdata(spi->master); | 103 | orion_spi = spi_master_get_devdata(spi->master); |
104 | devdata = orion_spi->devdata; | ||
88 | 105 | ||
89 | tclk_hz = clk_get_rate(orion_spi->clk); | 106 | tclk_hz = clk_get_rate(orion_spi->clk); |
90 | 107 | ||
91 | /* | 108 | if (devdata->typ == ARMADA_SPI) { |
92 | * the supported rates are: 4,6,8...30 | 109 | unsigned int clk, spr, sppr, sppr2, err; |
93 | * round up as we look for equal or less speed | 110 | unsigned int best_spr, best_sppr, best_err; |
94 | */ | 111 | |
95 | rate = DIV_ROUND_UP(tclk_hz, speed); | 112 | best_err = speed; |
96 | rate = roundup(rate, 2); | 113 | best_spr = 0; |
114 | best_sppr = 0; | ||
115 | |||
116 | /* Iterate over the valid range looking for best fit */ | ||
117 | for (sppr = 0; sppr < 8; sppr++) { | ||
118 | sppr2 = 0x1 << sppr; | ||
119 | |||
120 | spr = tclk_hz / sppr2; | ||
121 | spr = DIV_ROUND_UP(spr, speed); | ||
122 | if ((spr == 0) || (spr > 15)) | ||
123 | continue; | ||
97 | 124 | ||
98 | /* check if requested speed is too small */ | 125 | clk = tclk_hz / (spr * sppr2); |
99 | if (rate > 30) | 126 | err = speed - clk; |
100 | return -EINVAL; | ||
101 | 127 | ||
102 | if (rate < 4) | 128 | if (err < best_err) { |
103 | rate = 4; | 129 | best_spr = spr; |
130 | best_sppr = sppr; | ||
131 | best_err = err; | ||
132 | } | ||
133 | } | ||
134 | |||
135 | if ((best_sppr == 0) && (best_spr == 0)) | ||
136 | return -EINVAL; | ||
137 | |||
138 | prescale = ((best_sppr & 0x6) << 5) | | ||
139 | ((best_sppr & 0x1) << 4) | best_spr; | ||
140 | } else { | ||
141 | /* | ||
142 | * the supported rates are: 4,6,8...30 | ||
143 | * round up as we look for equal or less speed | ||
144 | */ | ||
145 | rate = DIV_ROUND_UP(tclk_hz, speed); | ||
146 | rate = roundup(rate, 2); | ||
147 | |||
148 | /* check if requested speed is too small */ | ||
149 | if (rate > 30) | ||
150 | return -EINVAL; | ||
104 | 151 | ||
105 | /* Convert the rate to SPI clock divisor value. */ | 152 | if (rate < 4) |
106 | prescale = 0x10 + rate/2; | 153 | rate = 4; |
154 | |||
155 | /* Convert the rate to SPI clock divisor value. */ | ||
156 | prescale = 0x10 + rate/2; | ||
157 | } | ||
107 | 158 | ||
108 | reg = readl(spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG)); | 159 | reg = readl(spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG)); |
109 | reg = ((reg & ~ORION_SPI_CLK_PRESCALE_MASK) | prescale); | 160 | reg = ((reg & ~devdata->prescale_mask) | prescale); |
110 | writel(reg, spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG)); | 161 | writel(reg, spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG)); |
111 | 162 | ||
112 | return 0; | 163 | return 0; |
@@ -342,8 +393,31 @@ static int orion_spi_reset(struct orion_spi *orion_spi) | |||
342 | return 0; | 393 | return 0; |
343 | } | 394 | } |
344 | 395 | ||
396 | static const struct orion_spi_dev orion_spi_dev_data = { | ||
397 | .typ = ORION_SPI, | ||
398 | .min_divisor = 4, | ||
399 | .max_divisor = 30, | ||
400 | .prescale_mask = ORION_SPI_CLK_PRESCALE_MASK, | ||
401 | }; | ||
402 | |||
403 | static const struct orion_spi_dev armada_spi_dev_data = { | ||
404 | .typ = ARMADA_SPI, | ||
405 | .min_divisor = 1, | ||
406 | .max_divisor = 1920, | ||
407 | .prescale_mask = ARMADA_SPI_CLK_PRESCALE_MASK, | ||
408 | }; | ||
409 | |||
410 | static const struct of_device_id orion_spi_of_match_table[] = { | ||
411 | { .compatible = "marvell,orion-spi", .data = &orion_spi_dev_data, }, | ||
412 | { .compatible = "marvell,armada-370-spi", .data = &armada_spi_dev_data, }, | ||
413 | {} | ||
414 | }; | ||
415 | MODULE_DEVICE_TABLE(of, orion_spi_of_match_table); | ||
416 | |||
345 | static int orion_spi_probe(struct platform_device *pdev) | 417 | static int orion_spi_probe(struct platform_device *pdev) |
346 | { | 418 | { |
419 | const struct of_device_id *of_id; | ||
420 | const struct orion_spi_dev *devdata; | ||
347 | struct spi_master *master; | 421 | struct spi_master *master; |
348 | struct orion_spi *spi; | 422 | struct orion_spi *spi; |
349 | struct resource *r; | 423 | struct resource *r; |
@@ -378,6 +452,10 @@ static int orion_spi_probe(struct platform_device *pdev) | |||
378 | spi = spi_master_get_devdata(master); | 452 | spi = spi_master_get_devdata(master); |
379 | spi->master = master; | 453 | spi->master = master; |
380 | 454 | ||
455 | of_id = of_match_device(orion_spi_of_match_table, &pdev->dev); | ||
456 | devdata = of_id->data; | ||
457 | spi->devdata = devdata; | ||
458 | |||
381 | spi->clk = devm_clk_get(&pdev->dev, NULL); | 459 | spi->clk = devm_clk_get(&pdev->dev, NULL); |
382 | if (IS_ERR(spi->clk)) { | 460 | if (IS_ERR(spi->clk)) { |
383 | status = PTR_ERR(spi->clk); | 461 | status = PTR_ERR(spi->clk); |
@@ -389,8 +467,8 @@ static int orion_spi_probe(struct platform_device *pdev) | |||
389 | goto out; | 467 | goto out; |
390 | 468 | ||
391 | tclk_hz = clk_get_rate(spi->clk); | 469 | tclk_hz = clk_get_rate(spi->clk); |
392 | master->max_speed_hz = DIV_ROUND_UP(tclk_hz, 4); | 470 | master->max_speed_hz = DIV_ROUND_UP(tclk_hz, devdata->min_divisor); |
393 | master->min_speed_hz = DIV_ROUND_UP(tclk_hz, 30); | 471 | master->min_speed_hz = DIV_ROUND_UP(tclk_hz, devdata->max_divisor); |
394 | 472 | ||
395 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 473 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
396 | spi->base = devm_ioremap_resource(&pdev->dev, r); | 474 | spi->base = devm_ioremap_resource(&pdev->dev, r); |
@@ -469,12 +547,6 @@ static const struct dev_pm_ops orion_spi_pm_ops = { | |||
469 | NULL) | 547 | NULL) |
470 | }; | 548 | }; |
471 | 549 | ||
472 | static const struct of_device_id orion_spi_of_match_table[] = { | ||
473 | { .compatible = "marvell,orion-spi", }, | ||
474 | {} | ||
475 | }; | ||
476 | MODULE_DEVICE_TABLE(of, orion_spi_of_match_table); | ||
477 | |||
478 | static struct platform_driver orion_spi_driver = { | 550 | static struct platform_driver orion_spi_driver = { |
479 | .driver = { | 551 | .driver = { |
480 | .name = DRIVER_NAME, | 552 | .name = DRIVER_NAME, |