diff options
-rw-r--r-- | Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt | 31 | ||||
-rw-r--r-- | drivers/spi/Kconfig | 76 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-axi-spi-engine.c | 591 | ||||
-rw-r--r-- | drivers/spi/spi-bcm2835.c | 5 | ||||
-rw-r--r-- | drivers/spi/spi-bcm2835aux.c | 72 | ||||
-rw-r--r-- | drivers/spi/spi.c | 19 | ||||
-rw-r--r-- | include/linux/spi/spi.h | 5 |
8 files changed, 737 insertions, 63 deletions
diff --git a/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt new file mode 100644 index 0000000..8a18d71 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/adi,axi-spi-engine.txt @@ -0,0 +1,31 @@ +Analog Devices AXI SPI Engine controller Device Tree Bindings + +Required properties: +- compatible : Must be "adi,axi-spi-engine-1.00.a"" +- reg : Physical base address and size of the register map. +- interrupts : Property with a value describing the interrupt + number. +- clock-names : List of input clock names - "s_axi_aclk", "spi_clk" +- clocks : Clock phandles and specifiers (See clock bindings for + details on clock-names and clocks). +- #address-cells : Must be <1> +- #size-cells : Must be <0> + +Optional subnodes: + Subnodes are use to represent the SPI slave devices connected to the SPI + master. They follow the generic SPI bindings as outlined in spi-bus.txt. + +Example: + + spi@@44a00000 { + compatible = "adi,axi-spi-engine-1.00.a"; + reg = <0x44a00000 0x1000>; + interrupts = <0 56 4>; + clocks = <&clkc 15 &clkc 15>; + clock-names = "s_axi_aclk", "spi_clk"; + + #address-cells = <1>; + #size-cells = <0>; + + /* SPI devices */ + }; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 7706416..77d3202 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -75,11 +75,26 @@ config SPI_ATMEL This selects a driver for the Atmel SPI Controller, present on many AT32 (AVR32) and AT91 (ARM) chips. +config SPI_AU1550 + tristate "Au1550/Au1200/Au1300 SPI Controller" + depends on MIPS_ALCHEMY + select SPI_BITBANG + help + If you say yes to this option, support will be included for the + PSC SPI controller found on Au1550, Au1200 and Au1300 series. + +config SPI_AXI_SPI_ENGINE + tristate "Analog Devices AXI SPI Engine controller" + depends on HAS_IOMEM + help + This enables support for the Analog Devices AXI SPI Engine SPI controller. + It is part of the SPI Engine framework that is used in some Analog Devices + reference designs for FPGAs. + config SPI_BCM2835 tristate "BCM2835 SPI controller" depends on GPIOLIB depends on ARCH_BCM2835 || COMPILE_TEST - depends on GPIOLIB help This selects a driver for the Broadcom BCM2835 SPI master. @@ -90,8 +105,7 @@ config SPI_BCM2835 config SPI_BCM2835AUX tristate "BCM2835 SPI auxiliary controller" - depends on ARCH_BCM2835 || COMPILE_TEST - depends on GPIOLIB + depends on (ARCH_BCM2835 && GPIOLIB) || COMPILE_TEST help This selects a driver for the Broadcom BCM2835 SPI aux master. @@ -118,14 +132,6 @@ config SPI_BFIN_SPORT help Enable support for a SPI bus via the Blackfin SPORT peripheral. -config SPI_AU1550 - tristate "Au1550/Au1200/Au1300 SPI Controller" - depends on MIPS_ALCHEMY - select SPI_BITBANG - help - If you say yes to this option, support will be included for the - PSC SPI controller found on Au1550, Au1200 and Au1300 series. - config SPI_BCM53XX tristate "Broadcom BCM53xx SPI controller" depends on ARCH_BCM_5301X @@ -197,6 +203,23 @@ config SPI_DAVINCI help SPI master controller for DaVinci/DA8x/OMAP-L/AM1x SPI modules. +config SPI_DESIGNWARE + tristate "DesignWare SPI controller core support" + help + general driver for SPI controller core from DesignWare + +config SPI_DW_PCI + tristate "PCI interface driver for DW SPI core" + depends on SPI_DESIGNWARE && PCI + +config SPI_DW_MID_DMA + bool "DMA support for DW SPI controller on Intel MID platform" + depends on SPI_DW_PCI && DW_DMAC_PCI + +config SPI_DW_MMIO + tristate "Memory-mapped io interface driver for DW SPI core" + depends on SPI_DESIGNWARE + config SPI_DLN2 tristate "Diolan DLN-2 USB SPI adapter" depends on MFD_DLN2 @@ -346,6 +369,13 @@ config SPI_MT65XX say Y or M here.If you are not sure, say N. SPI drivers for Mediatek MT65XX and MT81XX series ARM SoCs. +config SPI_NUC900 + tristate "Nuvoton NUC900 series SPI" + depends on ARCH_W90X900 + select SPI_BITBANG + help + SPI driver for Nuvoton NUC900 series ARM SoCs + config SPI_OC_TINY tristate "OpenCores tiny SPI" depends on GPIOLIB || COMPILE_TEST @@ -647,34 +677,10 @@ config SPI_ZYNQMP_GQSPI help Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC. -config SPI_NUC900 - tristate "Nuvoton NUC900 series SPI" - depends on ARCH_W90X900 - select SPI_BITBANG - help - SPI driver for Nuvoton NUC900 series ARM SoCs - # # Add new SPI master controllers in alphabetical order above this line # -config SPI_DESIGNWARE - tristate "DesignWare SPI controller core support" - help - general driver for SPI controller core from DesignWare - -config SPI_DW_PCI - tristate "PCI interface driver for DW SPI core" - depends on SPI_DESIGNWARE && PCI - -config SPI_DW_MID_DMA - bool "DMA support for DW SPI controller on Intel MID platform" - depends on SPI_DW_PCI && DW_DMAC_PCI - -config SPI_DW_MMIO - tristate "Memory-mapped io interface driver for DW SPI core" - depends on SPI_DESIGNWARE - # # There are lots of SPI device types, with sensors and memory # being probably the most widely used ones. diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 8991ffc..7ad8806 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_SPI_ALTERA) += spi-altera.o obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o obj-$(CONFIG_SPI_ATH79) += spi-ath79.o obj-$(CONFIG_SPI_AU1550) += spi-au1550.o +obj-$(CONFIG_SPI_AXI_SPI_ENGINE) += spi-axi-spi-engine.o obj-$(CONFIG_SPI_BCM2835) += spi-bcm2835.o obj-$(CONFIG_SPI_BCM2835AUX) += spi-bcm2835aux.o obj-$(CONFIG_SPI_BCM53XX) += spi-bcm53xx.o diff --git a/drivers/spi/spi-axi-spi-engine.c b/drivers/spi/spi-axi-spi-engine.c new file mode 100644 index 0000000..c968ab2 --- /dev/null +++ b/drivers/spi/spi-axi-spi-engine.c @@ -0,0 +1,591 @@ +/* + * SPI-Engine SPI controller driver + * Copyright 2015 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> + +#define SPI_ENGINE_VERSION_MAJOR(x) ((x >> 16) & 0xff) +#define SPI_ENGINE_VERSION_MINOR(x) ((x >> 8) & 0xff) +#define SPI_ENGINE_VERSION_PATCH(x) (x & 0xff) + +#define SPI_ENGINE_REG_VERSION 0x00 + +#define SPI_ENGINE_REG_RESET 0x40 + +#define SPI_ENGINE_REG_INT_ENABLE 0x80 +#define SPI_ENGINE_REG_INT_PENDING 0x84 +#define SPI_ENGINE_REG_INT_SOURCE 0x88 + +#define SPI_ENGINE_REG_SYNC_ID 0xc0 + +#define SPI_ENGINE_REG_CMD_FIFO_ROOM 0xd0 +#define SPI_ENGINE_REG_SDO_FIFO_ROOM 0xd4 +#define SPI_ENGINE_REG_SDI_FIFO_LEVEL 0xd8 + +#define SPI_ENGINE_REG_CMD_FIFO 0xe0 +#define SPI_ENGINE_REG_SDO_DATA_FIFO 0xe4 +#define SPI_ENGINE_REG_SDI_DATA_FIFO 0xe8 +#define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK 0xec + +#define SPI_ENGINE_INT_CMD_ALMOST_EMPTY BIT(0) +#define SPI_ENGINE_INT_SDO_ALMOST_EMPTY BIT(1) +#define SPI_ENGINE_INT_SDI_ALMOST_FULL BIT(2) +#define SPI_ENGINE_INT_SYNC BIT(3) + +#define SPI_ENGINE_CONFIG_CPHA BIT(0) +#define SPI_ENGINE_CONFIG_CPOL BIT(1) +#define SPI_ENGINE_CONFIG_3WIRE BIT(2) + +#define SPI_ENGINE_INST_TRANSFER 0x0 +#define SPI_ENGINE_INST_ASSERT 0x1 +#define SPI_ENGINE_INST_WRITE 0x2 +#define SPI_ENGINE_INST_MISC 0x3 + +#define SPI_ENGINE_CMD_REG_CLK_DIV 0x0 +#define SPI_ENGINE_CMD_REG_CONFIG 0x1 + +#define SPI_ENGINE_MISC_SYNC 0x0 +#define SPI_ENGINE_MISC_SLEEP 0x1 + +#define SPI_ENGINE_TRANSFER_WRITE 0x1 +#define SPI_ENGINE_TRANSFER_READ 0x2 + +#define SPI_ENGINE_CMD(inst, arg1, arg2) \ + (((inst) << 12) | ((arg1) << 8) | (arg2)) + +#define SPI_ENGINE_CMD_TRANSFER(flags, n) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_TRANSFER, (flags), (n)) +#define SPI_ENGINE_CMD_ASSERT(delay, cs) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_ASSERT, (delay), (cs)) +#define SPI_ENGINE_CMD_WRITE(reg, val) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_WRITE, (reg), (val)) +#define SPI_ENGINE_CMD_SLEEP(delay) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SLEEP, (delay)) +#define SPI_ENGINE_CMD_SYNC(id) \ + SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SYNC, (id)) + +struct spi_engine_program { + unsigned int length; + uint16_t instructions[]; +}; + +struct spi_engine { + struct clk *clk; + struct clk *ref_clk; + + spinlock_t lock; + + void __iomem *base; + + struct spi_message *msg; + struct spi_engine_program *p; + unsigned cmd_length; + const uint16_t *cmd_buf; + + struct spi_transfer *tx_xfer; + unsigned int tx_length; + const uint8_t *tx_buf; + + struct spi_transfer *rx_xfer; + unsigned int rx_length; + uint8_t *rx_buf; + + unsigned int sync_id; + unsigned int completed_id; + + unsigned int int_enable; +}; + +static void spi_engine_program_add_cmd(struct spi_engine_program *p, + bool dry, uint16_t cmd) +{ + if (!dry) + p->instructions[p->length] = cmd; + p->length++; +} + +static unsigned int spi_engine_get_config(struct spi_device *spi) +{ + unsigned int config = 0; + + if (spi->mode & SPI_CPOL) + config |= SPI_ENGINE_CONFIG_CPOL; + if (spi->mode & SPI_CPHA) + config |= SPI_ENGINE_CONFIG_CPHA; + if (spi->mode & SPI_3WIRE) + config |= SPI_ENGINE_CONFIG_3WIRE; + + return config; +} + +static unsigned int spi_engine_get_clk_div(struct spi_engine *spi_engine, + struct spi_device *spi, struct spi_transfer *xfer) +{ + unsigned int clk_div; + + clk_div = DIV_ROUND_UP(clk_get_rate(spi_engine->ref_clk), + xfer->speed_hz * 2); + if (clk_div > 255) + clk_div = 255; + else if (clk_div > 0) + clk_div -= 1; + + return clk_div; +} + +static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry, + struct spi_transfer *xfer) +{ + unsigned int len = xfer->len; + + while (len) { + unsigned int n = min(len, 256U); + unsigned int flags = 0; + + if (xfer->tx_buf) + flags |= SPI_ENGINE_TRANSFER_WRITE; + if (xfer->rx_buf) + flags |= SPI_ENGINE_TRANSFER_READ; + + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_TRANSFER(flags, n - 1)); + len -= n; + } +} + +static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry, + struct spi_engine *spi_engine, unsigned int clk_div, unsigned int delay) +{ + unsigned int spi_clk = clk_get_rate(spi_engine->ref_clk); + unsigned int t; + + if (delay == 0) + return; + + t = DIV_ROUND_UP(delay * spi_clk, (clk_div + 1) * 2); + while (t) { + unsigned int n = min(t, 256U); + + spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_SLEEP(n - 1)); + t -= n; + } +} + +static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry, + struct spi_device *spi, bool assert) +{ + unsigned int mask = 0xff; + + if (assert) + mask ^= BIT(spi->chip_select); + + spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(1, mask)); +} + +static int spi_engine_compile_message(struct spi_engine *spi_engine, + struct spi_message *msg, bool dry, struct spi_engine_program *p) +{ + struct spi_device *spi = msg->spi; + struct spi_transfer *xfer; + int clk_div, new_clk_div; + bool cs_change = true; + + clk_div = -1; + + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG, + spi_engine_get_config(spi))); + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + new_clk_div = spi_engine_get_clk_div(spi_engine, spi, xfer); + if (new_clk_div != clk_div) { + clk_div = new_clk_div; + spi_engine_program_add_cmd(p, dry, + SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV, + clk_div)); + } + + if (cs_change) + spi_engine_gen_cs(p, dry, spi, true); + + spi_engine_gen_xfer(p, dry, xfer); + spi_engine_gen_sleep(p, dry, spi_engine, clk_div, + xfer->delay_usecs); + + cs_change = xfer->cs_change; + if (list_is_last(&xfer->transfer_list, &msg->transfers)) + cs_change = !cs_change; + + if (cs_change) + spi_engine_gen_cs(p, dry, spi, false); + } + + return 0; +} + +static void spi_engine_xfer_next(struct spi_engine *spi_engine, + struct spi_transfer **_xfer) +{ + struct spi_message *msg = spi_engine->msg; + struct spi_transfer *xfer = *_xfer; + + if (!xfer) { + xfer = list_first_entry(&msg->transfers, + struct spi_transfer, transfer_list); + } else if (list_is_last(&xfer->transfer_list, &msg->transfers)) { + xfer = NULL; + } else { + xfer = list_next_entry(xfer, transfer_list); + } + + *_xfer = xfer; +} + +static void spi_engine_tx_next(struct spi_engine *spi_engine) +{ + struct spi_transfer *xfer = spi_engine->tx_xfer; + + do { + spi_engine_xfer_next(spi_engine, &xfer); + } while (xfer && !xfer->tx_buf); + + spi_engine->tx_xfer = xfer; + if (xfer) { + spi_engine->tx_length = xfer->len; + spi_engine->tx_buf = xfer->tx_buf; + } else { + spi_engine->tx_buf = NULL; + } +} + +static void spi_engine_rx_next(struct spi_engine *spi_engine) +{ + struct spi_transfer *xfer = spi_engine->rx_xfer; + + do { + spi_engine_xfer_next(spi_engine, &xfer); + } while (xfer && !xfer->rx_buf); + + spi_engine->rx_xfer = xfer; + if (xfer) { + spi_engine->rx_length = xfer->len; + spi_engine->rx_buf = xfer->rx_buf; + } else { + spi_engine->rx_buf = NULL; + } +} + +static bool spi_engine_write_cmd_fifo(struct spi_engine *spi_engine) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_CMD_FIFO; + unsigned int n, m, i; + const uint16_t *buf; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_CMD_FIFO_ROOM); + while (n && spi_engine->cmd_length) { + m = min(n, spi_engine->cmd_length); + buf = spi_engine->cmd_buf; + for (i = 0; i < m; i++) + writel_relaxed(buf[i], addr); + spi_engine->cmd_buf += m; + spi_engine->cmd_length -= m; + n -= m; + } + + return spi_engine->cmd_length != 0; +} + +static bool spi_engine_write_tx_fifo(struct spi_engine *spi_engine) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDO_DATA_FIFO; + unsigned int n, m, i; + const uint8_t *buf; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDO_FIFO_ROOM); + while (n && spi_engine->tx_length) { + m = min(n, spi_engine->tx_length); + buf = spi_engine->tx_buf; + for (i = 0; i < m; i++) + writel_relaxed(buf[i], addr); + spi_engine->tx_buf += m; + spi_engine->tx_length -= m; + n -= m; + if (spi_engine->tx_length == 0) + spi_engine_tx_next(spi_engine); + } + + return spi_engine->tx_length != 0; +} + +static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine) +{ + void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDI_DATA_FIFO; + unsigned int n, m, i; + uint8_t *buf; + + n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDI_FIFO_LEVEL); + while (n && spi_engine->rx_length) { + m = min(n, spi_engine->rx_length); + buf = spi_engine->rx_buf; + for (i = 0; i < m; i++) + buf[i] = readl_relaxed(addr); + spi_engine->rx_buf += m; + spi_engine->rx_length -= m; + n -= m; + if (spi_engine->rx_length == 0) + spi_engine_rx_next(spi_engine); + } + + return spi_engine->rx_length != 0; +} + +static irqreturn_t spi_engine_irq(int irq, void *devid) +{ + struct spi_master *master = devid; + struct spi_engine *spi_engine = spi_master_get_devdata(master); + unsigned int disable_int = 0; + unsigned int pending; + + pending = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + + if (pending & SPI_ENGINE_INT_SYNC) { + writel_relaxed(SPI_ENGINE_INT_SYNC, + spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + spi_engine->completed_id = readl_relaxed( + spi_engine->base + SPI_ENGINE_REG_SYNC_ID); + } + + spin_lock(&spi_engine->lock); + + if (pending & SPI_ENGINE_INT_CMD_ALMOST_EMPTY) { + if (!spi_engine_write_cmd_fifo(spi_engine)) + disable_int |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; + } + + if (pending & SPI_ENGINE_INT_SDO_ALMOST_EMPTY) { + if (!spi_engine_write_tx_fifo(spi_engine)) + disable_int |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; + } + + if (pending & (SPI_ENGINE_INT_SDI_ALMOST_FULL | SPI_ENGINE_INT_SYNC)) { + if (!spi_engine_read_rx_fifo(spi_engine)) + disable_int |= SPI_ENGINE_INT_SDI_ALMOST_FULL; + } + + if (pending & SPI_ENGINE_INT_SYNC) { + if (spi_engine->msg && + spi_engine->completed_id == spi_engine->sync_id) { + struct spi_message *msg = spi_engine->msg; + + kfree(spi_engine->p); + msg->status = 0; + msg->actual_length = msg->frame_length; + spi_engine->msg = NULL; + spi_finalize_current_message(master); + disable_int |= SPI_ENGINE_INT_SYNC; + } + } + + if (disable_int) { + spi_engine->int_enable &= ~disable_int; + writel_relaxed(spi_engine->int_enable, + spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + } + + spin_unlock(&spi_engine->lock); + + return IRQ_HANDLED; +} + +static int spi_engine_transfer_one_message(struct spi_master *master, + struct spi_message *msg) +{ + struct spi_engine_program p_dry, *p; + struct spi_engine *spi_engine = spi_master_get_devdata(master); + unsigned int int_enable = 0; + unsigned long flags; + size_t size; + + p_dry.length = 0; + spi_engine_compile_message(spi_engine, msg, true, &p_dry); + + size = sizeof(*p->instructions) * (p_dry.length + 1); + p = kzalloc(sizeof(*p) + size, GFP_KERNEL); + if (!p) + return -ENOMEM; + spi_engine_compile_message(spi_engine, msg, false, p); + + spin_lock_irqsave(&spi_engine->lock, flags); + spi_engine->sync_id = (spi_engine->sync_id + 1) & 0xff; + spi_engine_program_add_cmd(p, false, + SPI_ENGINE_CMD_SYNC(spi_engine->sync_id)); + + spi_engine->msg = msg; + spi_engine->p = p; + + spi_engine->cmd_buf = p->instructions; + spi_engine->cmd_length = p->length; + if (spi_engine_write_cmd_fifo(spi_engine)) + int_enable |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY; + + spi_engine_tx_next(spi_engine); + if (spi_engine_write_tx_fifo(spi_engine)) + int_enable |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY; + + spi_engine_rx_next(spi_engine); + if (spi_engine->rx_length != 0) + int_enable |= SPI_ENGINE_INT_SDI_ALMOST_FULL; + + int_enable |= SPI_ENGINE_INT_SYNC; + + writel_relaxed(int_enable, + spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + spi_engine->int_enable = int_enable; + spin_unlock_irqrestore(&spi_engine->lock, flags); + + return 0; +} + +static int spi_engine_probe(struct platform_device *pdev) +{ + struct spi_engine *spi_engine; + struct spi_master *master; + unsigned int version; + struct resource *res; + int irq; + int ret; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return -ENXIO; + + spi_engine = devm_kzalloc(&pdev->dev, sizeof(*spi_engine), GFP_KERNEL); + if (!spi_engine) + return -ENOMEM; + + master = spi_alloc_master(&pdev->dev, 0); + if (!master) + return -ENOMEM; + + spi_master_set_devdata(master, spi_engine); + + spin_lock_init(&spi_engine->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spi_engine->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spi_engine->base)) { + ret = PTR_ERR(spi_engine->base); + goto err_put_master; + } + + version = readl(spi_engine->base + SPI_ENGINE_REG_VERSION); + if (SPI_ENGINE_VERSION_MAJOR(version) != 1) { + dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%c\n", + SPI_ENGINE_VERSION_MAJOR(version), + SPI_ENGINE_VERSION_MINOR(version), + SPI_ENGINE_VERSION_PATCH(version)); + return -ENODEV; + } + + spi_engine->clk = devm_clk_get(&pdev->dev, "s_axi_aclk"); + if (IS_ERR(spi_engine->clk)) { + ret = PTR_ERR(spi_engine->clk); + goto err_put_master; + } + + spi_engine->ref_clk = devm_clk_get(&pdev->dev, "spi_clk"); + if (IS_ERR(spi_engine->ref_clk)) { + ret = PTR_ERR(spi_engine->ref_clk); + goto err_put_master; + } + + ret = clk_prepare_enable(spi_engine->clk); + if (ret) + goto err_put_master; + + ret = clk_prepare_enable(spi_engine->ref_clk); + if (ret) + goto err_clk_disable; + + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET); + writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + + ret = request_irq(irq, spi_engine_irq, 0, pdev->name, master); + if (ret) + goto err_ref_clk_disable; + + master->dev.parent = &pdev->dev; + master->dev.of_node = pdev->dev.of_node; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_3WIRE; + master->bits_per_word_mask = SPI_BPW_MASK(8); + master->max_speed_hz = clk_get_rate(spi_engine->ref_clk) / 2; + master->transfer_one_message = spi_engine_transfer_one_message; + master->num_chipselect = 8; + + ret = spi_register_master(master); + if (ret) + goto err_free_irq; + + platform_set_drvdata(pdev, master); + + return 0; +err_free_irq: + free_irq(irq, master); +err_ref_clk_disable: + clk_disable_unprepare(spi_engine->ref_clk); +err_clk_disable: + clk_disable_unprepare(spi_engine->clk); +err_put_master: + spi_master_put(master); + return ret; +} + +static int spi_engine_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct spi_engine *spi_engine = spi_master_get_devdata(master); + int irq = platform_get_irq(pdev, 0); + + spi_unregister_master(master); + + free_irq(irq, master); + + writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING); + writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE); + writel_relaxed(0x01, spi_engine->base + SPI_ENGINE_REG_RESET); + + clk_disable_unprepare(spi_engine->ref_clk); + clk_disable_unprepare(spi_engine->clk); + + return 0; +} + +static const struct of_device_id spi_engine_match_table[] = { + { .compatible = "adi,axi-spi-engine-1.00.a" }, + { }, +}; + +static struct platform_driver spi_engine_driver = { + .probe = spi_engine_probe, + .remove = spi_engine_remove, + .driver = { + .name = "spi-engine", + .of_match_table = spi_engine_match_table, + }, +}; +module_platform_driver(spi_engine_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices SPI engine peripheral driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index cf04960..f35cc10 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -727,11 +727,6 @@ static int bcm2835_spi_setup(struct spi_device *spi) spi->chip_select, spi->cs_gpio, err); return err; } - /* the implementation of pinctrl-bcm2835 currently does not - * set the GPIO value when using gpio_direction_output - * so we are setting it here explicitly - */ - gpio_set_value(spi->cs_gpio, (spi->mode & SPI_CS_HIGH) ? 0 : 1); return 0; } diff --git a/drivers/spi/spi-bcm2835aux.c b/drivers/spi/spi-bcm2835aux.c index ecc73c0..7428091 100644 --- a/drivers/spi/spi-bcm2835aux.c +++ b/drivers/spi/spi-bcm2835aux.c @@ -64,9 +64,9 @@ #define BCM2835_AUX_SPI_CNTL0_VAR_WIDTH 0x00004000 #define BCM2835_AUX_SPI_CNTL0_DOUTHOLD 0x00003000 #define BCM2835_AUX_SPI_CNTL0_ENABLE 0x00000800 -#define BCM2835_AUX_SPI_CNTL0_CPHA_IN 0x00000400 +#define BCM2835_AUX_SPI_CNTL0_IN_RISING 0x00000400 #define BCM2835_AUX_SPI_CNTL0_CLEARFIFO 0x00000200 -#define BCM2835_AUX_SPI_CNTL0_CPHA_OUT 0x00000100 +#define BCM2835_AUX_SPI_CNTL0_OUT_RISING 0x00000100 #define BCM2835_AUX_SPI_CNTL0_CPOL 0x00000080 #define BCM2835_AUX_SPI_CNTL0_MSBF_OUT 0x00000040 #define BCM2835_AUX_SPI_CNTL0_SHIFTLEN 0x0000003F @@ -92,9 +92,6 @@ #define BCM2835_AUX_SPI_POLLING_LIMIT_US 30 #define BCM2835_AUX_SPI_POLLING_JIFFIES 2 -#define BCM2835_AUX_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \ - | SPI_NO_CS) - struct bcm2835aux_spi { void __iomem *regs; struct clk *clk; @@ -212,9 +209,15 @@ static irqreturn_t bcm2835aux_spi_interrupt(int irq, void *dev_id) ret = IRQ_HANDLED; } - /* and if rx_len is 0 then wake up completion and disable spi */ + if (!bs->tx_len) { + /* disable tx fifo empty interrupt */ + bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1] | + BCM2835_AUX_SPI_CNTL1_IDLE); + } + + /* and if rx_len is 0 then disable interrupts and wake up completion */ if (!bs->rx_len) { - bcm2835aux_spi_reset_hw(bs); + bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1]); complete(&master->xfer_completion); } @@ -307,9 +310,6 @@ static int bcm2835aux_spi_transfer_one_poll(struct spi_master *master, } } - /* Transfer complete - reset SPI HW */ - bcm2835aux_spi_reset_hw(bs); - /* and return without waiting for completion */ return 0; } @@ -330,10 +330,6 @@ static int bcm2835aux_spi_transfer_one(struct spi_master *master, * resulting (potentially) in more interrupts when transferring * more than 12 bytes */ - bs->cntl[0] = BCM2835_AUX_SPI_CNTL0_ENABLE | - BCM2835_AUX_SPI_CNTL0_VAR_WIDTH | - BCM2835_AUX_SPI_CNTL0_MSBF_OUT; - bs->cntl[1] = BCM2835_AUX_SPI_CNTL1_MSBF_IN; /* set clock */ spi_hz = tfr->speed_hz; @@ -348,17 +344,13 @@ static int bcm2835aux_spi_transfer_one(struct spi_master *master, } else { /* the slowest we can go */ speed = BCM2835_AUX_SPI_CNTL0_SPEED_MAX; } + /* mask out old speed from previous spi_transfer */ + bs->cntl[0] &= ~(BCM2835_AUX_SPI_CNTL0_SPEED); + /* set the new speed */ bs->cntl[0] |= speed << BCM2835_AUX_SPI_CNTL0_SPEED_SHIFT; spi_used_hz = clk_hz / (2 * (speed + 1)); - /* handle all the modes */ - if (spi->mode & SPI_CPOL) - bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_CPOL; - if (spi->mode & SPI_CPHA) - bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_CPHA_OUT | - BCM2835_AUX_SPI_CNTL0_CPHA_IN; - /* set transmit buffers and length */ bs->tx_buf = tfr->tx_buf; bs->rx_buf = tfr->rx_buf; @@ -382,6 +374,40 @@ static int bcm2835aux_spi_transfer_one(struct spi_master *master, return bcm2835aux_spi_transfer_one_irq(master, spi, tfr); } +static int bcm2835aux_spi_prepare_message(struct spi_master *master, + struct spi_message *msg) +{ + struct spi_device *spi = msg->spi; + struct bcm2835aux_spi *bs = spi_master_get_devdata(master); + + bs->cntl[0] = BCM2835_AUX_SPI_CNTL0_ENABLE | + BCM2835_AUX_SPI_CNTL0_VAR_WIDTH | + BCM2835_AUX_SPI_CNTL0_MSBF_OUT; + bs->cntl[1] = BCM2835_AUX_SPI_CNTL1_MSBF_IN; + + /* handle all the modes */ + if (spi->mode & SPI_CPOL) { + bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_CPOL; + bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_OUT_RISING; + } else { + bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_IN_RISING; + } + bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1]); + bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL0, bs->cntl[0]); + + return 0; +} + +static int bcm2835aux_spi_unprepare_message(struct spi_master *master, + struct spi_message *msg) +{ + struct bcm2835aux_spi *bs = spi_master_get_devdata(master); + + bcm2835aux_spi_reset_hw(bs); + + return 0; +} + static void bcm2835aux_spi_handle_err(struct spi_master *master, struct spi_message *msg) { @@ -405,11 +431,13 @@ static int bcm2835aux_spi_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, master); - master->mode_bits = BCM2835_AUX_SPI_MODE_BITS; + master->mode_bits = (SPI_CPOL | SPI_CS_HIGH | SPI_NO_CS); master->bits_per_word_mask = SPI_BPW_MASK(8); master->num_chipselect = -1; master->transfer_one = bcm2835aux_spi_transfer_one; master->handle_err = bcm2835aux_spi_handle_err; + master->prepare_message = bcm2835aux_spi_prepare_message; + master->unprepare_message = bcm2835aux_spi_unprepare_message; master->dev.of_node = pdev->dev.of_node; bs = spi_master_get_devdata(master); diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 89158c6..e86148f 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1589,13 +1589,30 @@ static void of_register_spi_devices(struct spi_master *master) { } static int acpi_spi_add_resource(struct acpi_resource *ares, void *data) { struct spi_device *spi = data; + struct spi_master *master = spi->master; if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { struct acpi_resource_spi_serialbus *sb; sb = &ares->data.spi_serial_bus; if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_SPI) { - spi->chip_select = sb->device_selection; + /* + * ACPI DeviceSelection numbering is handled by the + * host controller driver in Windows and can vary + * from driver to driver. In Linux we always expect + * 0 .. max - 1 so we need to ask the driver to + * translate between the two schemes. + */ + if (master->fw_translate_cs) { + int cs = master->fw_translate_cs(master, + sb->device_selection); + if (cs < 0) + return cs; + spi->chip_select = cs; + } else { + spi->chip_select = sb->device_selection; + } + spi->max_speed_hz = sb->connection_speed; if (sb->clock_phase == ACPI_SPI_SECOND_PHASE) diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index ba0dee9..199b042 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -372,6 +372,9 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) * @dma_rx: DMA receive channel * @dummy_rx: dummy receive buffer for full-duplex devices * @dummy_tx: dummy transmit buffer for full-duplex devices + * @fw_translate_cs: If the boot firmware uses different numbering scheme + * what Linux expects, this optional hook can be used to translate + * between the two. * * Each SPI master controller can communicate with one or more @spi_device * children. These make a small bus, sharing MOSI, MISO and SCK signals @@ -542,6 +545,8 @@ struct spi_master { /* dummy data for full duplex devices */ void *dummy_rx; void *dummy_tx; + + int (*fw_translate_cs)(struct spi_master *master, unsigned cs); }; static inline void *spi_master_get_devdata(struct spi_master *master) |