This ports the Linux spi-bcm2835 driver to barebox.

Signed-off-by: Marco Felsch <[email protected]>
---
Changelog:
v2:
 - no changes

 drivers/spi/Kconfig       |  11 ++
 drivers/spi/Makefile      |   1 +
 drivers/spi/spi-bcm2835.c | 400 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 412 insertions(+)
 create mode 100644 drivers/spi/spi-bcm2835.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 445c756a38a2..8357f7806e3c 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -95,6 +95,17 @@ config DRIVER_SPI_DSPI
          This enables support for the Freescale DSPI controller in master
          mode. VF610 platform uses the controller.
 
+config SPI_BCM2835
+       tristate "BCM2835 SPI controller"
+       depends on ARCH_BCM283X || COMPILE_TEST
+       help
+         This selects a driver for the Broadcom BCM2835 SPI master.
+
+         The BCM2835 contains two types of SPI master controller; the
+         "universal SPI master", and the regular SPI controller. This driver
+         is for the regular SPI controller. Slave mode operation is not also
+         not supported.
+
 config SPI_ZYNQ_QSPI
        tristate "Xilinx Zynq QSPI controller"
        depends on ARCH_ZYNQ
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 68a8c4e675a5..e0f1124090a4 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_FSL_DSPI) += spi-fsl-dspi.o
 obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
 obj-$(CONFIG_DRIVER_SPI_OMAP3) += omap3_spi.o
 obj-$(CONFIG_DRIVER_SPI_DSPI) += dspi_spi.o
+obj-$(CONFIG_SPI_BCM2835) += spi-bcm2835.o
 obj-$(CONFIG_SPI_ZYNQ_QSPI) += zynq_qspi.o
 obj-$(CONFIG_SPI_NXP_FLEXSPI) += spi-nxp-fspi.o
 obj-$(CONFIG_DRIVER_SPI_STM32) += stm32_spi.o
diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c
new file mode 100644
index 000000000000..f0dc76a956f7
--- /dev/null
+++ b/drivers/spi/spi-bcm2835.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Broadcom BCM2835 SPI Controllers
+ *
+ * Copyright (C) 2012 Chris Boot
+ * Copyright (C) 2013 Stephen Warren
+ * Copyright (C) 2015 Martin Sperl
+ *
+ * This driver is inspired by:
+ * spi-ath79.c, Copyright (C) 2009-2011 Gabor Juhos <[email protected]>
+ * spi-atmel.c, Copyright (C) 2006 Atmel Corporation
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <spi/spi.h>
+
+/* SPI register offsets */
+#define BCM2835_SPI_CS                 0x00
+#define BCM2835_SPI_FIFO               0x04
+#define BCM2835_SPI_CLK                        0x08
+#define BCM2835_SPI_DLEN               0x0c
+
+/* Bitfields in CS */
+#define BCM2835_SPI_CS_TXD             0x00040000
+#define BCM2835_SPI_CS_RXD             0x00020000
+#define BCM2835_SPI_CS_DONE            0x00010000
+#define BCM2835_SPI_CS_REN             0x00001000
+#define BCM2835_SPI_CS_ADCS            0x00000800
+#define BCM2835_SPI_CS_INTR            0x00000400
+#define BCM2835_SPI_CS_INTD            0x00000200
+#define BCM2835_SPI_CS_DMAEN           0x00000100
+#define BCM2835_SPI_CS_TA              0x00000080
+#define BCM2835_SPI_CS_CLEAR_RX                0x00000020
+#define BCM2835_SPI_CS_CLEAR_TX                0x00000010
+#define BCM2835_SPI_CS_CPOL            0x00000008
+#define BCM2835_SPI_CS_CPHA            0x00000004
+#define BCM2835_SPI_CS_CS_10           0x00000002
+#define BCM2835_SPI_CS_CS_01           0x00000001
+
+#define BCM2835_SPI_FIFO_SIZE          64
+#define BCM2835_SPI_MODE_BITS  (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \
+                               | SPI_NO_CS | SPI_3WIRE)
+
+#define DRV_NAME       "spi-bcm2835"
+
+/**
+ * struct bcm2835_spi - BCM2835 SPI controller
+ * @regs: base address of register map
+ * @clk: core clock, divided to calculate serial clock
+ * @clk_hz: core clock cached speed
+ * @tfr: SPI transfer currently processed
+ * @ctlr: SPI controller reverse lookup
+ * @tx_buf: pointer whence next transmitted byte is read
+ * @rx_buf: pointer where next received byte is written
+ * @tx_len: remaining bytes to transmit
+ * @rx_len: remaining bytes to receive
+ */
+struct bcm2835_spi {
+       void __iomem *regs;
+       struct clk *clk;
+       unsigned long clk_hz;
+       struct spi_transfer *tfr;
+       struct spi_controller ctlr;
+       const u8 *tx_buf;
+       u8 *rx_buf;
+       int tx_len;
+       int rx_len;
+};
+
+/**
+ * struct bcm2835_spidev - BCM2835 SPI target
+ * @prepare_cs: precalculated CS register value for ->prepare_message()
+ *     (uses target-specific clock polarity and phase settings)
+ */
+struct bcm2835_spidev {
+       u32 prepare_cs;
+};
+
+static inline u32 bcm2835_rd(struct bcm2835_spi *bs, unsigned int reg)
+{
+       return readl(bs->regs + reg);
+}
+
+static inline void bcm2835_wr(struct bcm2835_spi *bs, unsigned int reg, u32 
val)
+{
+       writel(val, bs->regs + reg);
+}
+
+static inline void bcm2835_rd_fifo(struct bcm2835_spi *bs)
+{
+       u8 byte;
+
+       while ((bs->rx_len) &&
+              (bcm2835_rd(bs, BCM2835_SPI_CS) & BCM2835_SPI_CS_RXD)) {
+               byte = bcm2835_rd(bs, BCM2835_SPI_FIFO);
+               if (bs->rx_buf)
+                       *bs->rx_buf++ = byte;
+               bs->rx_len--;
+       }
+}
+
+static inline void bcm2835_wr_fifo(struct bcm2835_spi *bs)
+{
+       u8 byte;
+
+       while ((bs->tx_len) &&
+              (bcm2835_rd(bs, BCM2835_SPI_CS) & BCM2835_SPI_CS_TXD)) {
+               byte = bs->tx_buf ? *bs->tx_buf++ : 0;
+               bcm2835_wr(bs, BCM2835_SPI_FIFO, byte);
+               bs->tx_len--;
+       }
+}
+
+/**
+ * bcm2835_rd_fifo_count() - blindly read exactly @count bytes from RX FIFO
+ * @bs: BCM2835 SPI controller
+ * @count: bytes to read from RX FIFO
+ *
+ * The caller must ensure that @bs->rx_len is greater than or equal to @count,
+ * that the RX FIFO contains at least @count bytes and that the DMA Enable flag
+ * in the CS register is set (such that a read from the FIFO register receives
+ * 32-bit instead of just 8-bit).  Moreover @bs->rx_buf must not be %NULL.
+ */
+static inline void bcm2835_rd_fifo_count(struct bcm2835_spi *bs, int count)
+{
+       u32 val;
+       int len;
+
+       bs->rx_len -= count;
+
+       do {
+               val = bcm2835_rd(bs, BCM2835_SPI_FIFO);
+               len = min(count, 4);
+               memcpy(bs->rx_buf, &val, len);
+               bs->rx_buf += len;
+               count -= 4;
+       } while (count > 0);
+}
+
+/**
+ * bcm2835_wr_fifo_blind() - blindly write up to @count bytes to TX FIFO
+ * @bs: BCM2835 SPI controller
+ * @count: bytes available for writing in TX FIFO
+ */
+static inline void bcm2835_wr_fifo_blind(struct bcm2835_spi *bs, int count)
+{
+       u8 val;
+
+       count = min(count, bs->tx_len);
+       bs->tx_len -= count;
+
+       do {
+               val = bs->tx_buf ? *bs->tx_buf++ : 0;
+               bcm2835_wr(bs, BCM2835_SPI_FIFO, val);
+       } while (--count);
+}
+
+static void bcm2835_spi_reset_hw(struct bcm2835_spi *bs)
+{
+       u32 cs = bcm2835_rd(bs, BCM2835_SPI_CS);
+
+       /* Disable SPI interrupts and transfer */
+       cs &= ~(BCM2835_SPI_CS_INTR |
+               BCM2835_SPI_CS_INTD |
+               BCM2835_SPI_CS_DMAEN |
+               BCM2835_SPI_CS_TA);
+       /*
+        * Transmission sometimes breaks unless the DONE bit is written at the
+        * end of every transfer.  The spec says it's a RO bit.  Either the
+        * spec is wrong and the bit is actually of type RW1C, or it's a
+        * hardware erratum.
+        */
+       cs |= BCM2835_SPI_CS_DONE;
+       /* and reset RX/TX FIFOS */
+       cs |= BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX;
+
+       /* and reset the SPI_HW */
+       bcm2835_wr(bs, BCM2835_SPI_CS, cs);
+       /* as well as DLEN */
+       bcm2835_wr(bs, BCM2835_SPI_DLEN, 0);
+}
+
+static int bcm2835_spi_transfer_one_poll(struct spi_controller *ctlr,
+                                        struct spi_device *spi,
+                                        struct spi_transfer *tfr,
+                                        u32 cs)
+{
+       struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+
+       /* enable HW block without interrupts */
+       bcm2835_wr(bs, BCM2835_SPI_CS, cs | BCM2835_SPI_CS_TA);
+
+       bcm2835_wr_fifo_blind(bs, BCM2835_SPI_FIFO_SIZE);
+
+       /* loop until finished the transfer */
+       while (bs->rx_len) {
+               /* fill in tx fifo with remaining data */
+               bcm2835_wr_fifo(bs);
+
+               /* read from fifo as much as possible */
+               bcm2835_rd_fifo(bs);
+       }
+
+       /* Transfer complete - reset SPI HW */
+       bcm2835_spi_reset_hw(bs);
+       /* and return without waiting for completion */
+       return 0;
+}
+
+static int bcm2835_spi_transfer_one(struct spi_controller *ctlr,
+                                   struct spi_device *spi,
+                                   struct spi_transfer *tfr)
+{
+       struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+       struct bcm2835_spidev *target = spi_get_ctldata(spi);
+       unsigned long spi_hz, cdiv;
+       u32 cs = target->prepare_cs;
+
+       /* set clock */
+       spi_hz = tfr->speed_hz;
+
+       if (spi_hz >= bs->clk_hz / 2) {
+               cdiv = 2; /* clk_hz/2 is the fastest we can go */
+       } else if (spi_hz) {
+               /* CDIV must be a multiple of two */
+               cdiv = DIV_ROUND_UP(bs->clk_hz, spi_hz);
+               cdiv += (cdiv % 2);
+
+               if (cdiv >= 65536)
+                       cdiv = 0; /* 0 is the slowest we can go */
+       } else {
+               cdiv = 0; /* 0 is the slowest we can go */
+       }
+       tfr->effective_speed_hz = cdiv ? (bs->clk_hz / cdiv) : (bs->clk_hz / 
65536);
+       bcm2835_wr(bs, BCM2835_SPI_CLK, cdiv);
+
+       /* handle all the 3-wire mode */
+       if (spi->mode & SPI_3WIRE && tfr->rx_buf)
+               cs |= BCM2835_SPI_CS_REN;
+
+       /* set transmit buffers and length */
+       bs->tx_buf = tfr->tx_buf;
+       bs->rx_buf = tfr->rx_buf;
+       bs->tx_len = tfr->len;
+       bs->rx_len = tfr->len;
+
+       return bcm2835_spi_transfer_one_poll(ctlr, spi, tfr, cs);
+}
+
+static int bcm2835_spi_prepare_message(struct spi_controller *ctlr,
+                                      struct spi_message *msg)
+{
+       struct spi_device *spi = msg->spi;
+       struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+       struct bcm2835_spidev *target = spi_get_ctldata(spi);
+
+       /*
+        * Set up clock polarity before spi_transfer_one_message() asserts
+        * chip select to avoid a gratuitous clock signal edge.
+        */
+       bcm2835_wr(bs, BCM2835_SPI_CS, target->prepare_cs);
+
+       return 0;
+}
+
+static void bcm2835_spi_handle_err(struct spi_controller *ctlr,
+                                  struct spi_message *msg)
+{
+       struct bcm2835_spi *bs = spi_controller_get_devdata(ctlr);
+
+       /* and reset */
+       bcm2835_spi_reset_hw(bs);
+}
+
+
+static void bcm2835_spi_cleanup(struct spi_device *spi)
+{
+       struct bcm2835_spidev *target = spi_get_ctldata(spi);
+
+       kfree(target);
+}
+
+static size_t bcm2835_spi_max_transfer_size(struct spi_device *spi)
+{
+       return SIZE_MAX;
+}
+
+static int bcm2835_spi_setup(struct spi_device *spi)
+{
+       struct bcm2835_spidev *target = spi_get_ctldata(spi);
+       u32 cs;
+
+       if (!target) {
+               target = kzalloc(sizeof(*target), GFP_KERNEL);
+               if (!target)
+                       return -ENOMEM;
+
+               spi_set_ctldata(spi, target);
+       }
+
+       /*
+        * Precalculate SPI target's CS register value for ->prepare_message():
+        * The driver always uses software-controlled GPIO chip select, hence
+        * set the hardware-controlled native chip select to an invalid value
+        * to prevent it from interfering.
+        */
+       cs = BCM2835_SPI_CS_CS_10 | BCM2835_SPI_CS_CS_01;
+       if (spi->mode & SPI_CPOL)
+               cs |= BCM2835_SPI_CS_CPOL;
+       if (spi->mode & SPI_CPHA)
+               cs |= BCM2835_SPI_CS_CPHA;
+       target->prepare_cs = cs;
+
+       /*
+        * sanity checking the native-chipselects
+        */
+       if (spi->mode & SPI_NO_CS)
+               return 0;
+
+       if (!spi->cs_gpiod) {
+               dev_err(&spi->dev, "Driver supports only cs-gpios at the 
moment\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int bcm2835_spi_probe(struct device *dev)
+{
+       struct spi_controller *ctlr;
+       struct resource *iores;
+       struct bcm2835_spi *bs;
+       int err;
+
+       bs = xzalloc(sizeof(*bs));
+
+       ctlr = &bs->ctlr;
+
+       /* ctlr->mode_bits = BCM2835_SPI_MODE_BITS; */
+       ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
+       ctlr->num_chipselect = 3;
+       ctlr->use_gpio_descriptors = true;
+       ctlr->max_transfer_size = bcm2835_spi_max_transfer_size;
+       ctlr->setup = bcm2835_spi_setup;
+       ctlr->cleanup = bcm2835_spi_cleanup;
+       ctlr->transfer_one = bcm2835_spi_transfer_one;
+       ctlr->handle_err = bcm2835_spi_handle_err;
+       ctlr->prepare_message = bcm2835_spi_prepare_message;
+       ctlr->dev = dev;
+
+       spi_controller_set_devdata(ctlr, bs);
+
+       iores = dev_request_mem_resource(dev, 0);
+       if (IS_ERR(iores))
+               return dev_err_probe(dev, PTR_ERR(iores),
+                                    "failed to get io-resource\n");
+       bs->regs = IOMEM(iores->start);
+
+       bs->clk = clk_get_enabled(dev, NULL);
+       if (IS_ERR(bs->clk))
+               return dev_err_probe(dev, PTR_ERR(bs->clk),
+                                    "could not get clk\n");
+
+       ctlr->max_speed_hz = clk_get_rate(bs->clk) / 2;
+
+       bs->clk_hz = clk_get_rate(bs->clk);
+
+       /* initialise the hardware with the default polarities */
+       bcm2835_wr(bs, BCM2835_SPI_CS,
+                  BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX);
+
+       err = spi_register_controller(ctlr);
+       if (err)
+               return dev_err_probe(dev, err,
+                                    "could not register SPI controller\n");
+
+       return 0;
+}
+
+static const struct of_device_id bcm2835_spi_match[] = {
+       { .compatible = "brcm,bcm2835-spi", },
+       {}
+};
+MODULE_DEVICE_TABLE(of, bcm2835_spi_match);
+
+static struct driver bcm2835_spi_driver = {
+       .name           = DRV_NAME,
+       .of_compatible  = DRV_OF_COMPAT(bcm2835_spi_match),
+       .probe          = bcm2835_spi_probe,
+};
+coredevice_platform_driver(bcm2835_spi_driver);
+
+MODULE_DESCRIPTION("SPI controller driver for Broadcom BCM2835");
+MODULE_AUTHOR("Chris Boot <[email protected]>");
+MODULE_LICENSE("GPL");
-- 
2.39.5


Reply via email to