[PATCH 2/2] spi: bcm-mspi: Add support for Broadcom MSPI driver.
From: Jonathan Richardson
Date: Tue May 12 2015 - 13:35:46 EST
The MSPI controller is a SPI controller found on various Broadcom
SoC's such as Cygnus.
Signed-off-by: Jonathan Richardson <jonathar@xxxxxxxxxxxx>
---
drivers/spi/Kconfig | 5 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-bcm-mspi.c | 505 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 511 insertions(+)
create mode 100644 drivers/spi/spi-bcm-mspi.c
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index ab8dfbe..23622c2 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -121,6 +121,11 @@ config SPI_BCM53XX
help
Enable support for the SPI controller on Broadcom BCM53xx ARM SoCs.
+config SPI_BCM_MSPI
+ tristate "Broadcom MSPI controller"
+ help
+ Enable support for the Broadcom MSPI controller.
+
config SPI_BCM63XX
tristate "Broadcom BCM63xx SPI controller"
depends on BCM63XX
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index d8cbf65..5ebecba 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_SPI_BCM2835) += spi-bcm2835.o
obj-$(CONFIG_SPI_BCM53XX) += spi-bcm53xx.o
obj-$(CONFIG_SPI_BCM63XX) += spi-bcm63xx.o
obj-$(CONFIG_SPI_BCM63XX_HSSPI) += spi-bcm63xx-hsspi.o
+obj-$(CONFIG_SPI_BCM_MSPI) += spi-bcm-mspi.o
obj-$(CONFIG_SPI_BFIN5XX) += spi-bfin5xx.o
obj-$(CONFIG_SPI_ADI_V3) += spi-adi-v3.o
obj-$(CONFIG_SPI_BFIN_SPORT) += spi-bfin-sport.o
diff --git a/drivers/spi/spi-bcm-mspi.c b/drivers/spi/spi-bcm-mspi.c
new file mode 100644
index 0000000..9f6cc03
--- /dev/null
+++ b/drivers/spi/spi-bcm-mspi.c
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/of_irq.h>
+
+#define MSPI_SPCR0_LSB_OFFSET 0x0
+#define MSPI_SPCR0_LSB_SHIFT 0
+#define SPBR_MIN 8U
+#define SPBR_MAX 255U
+#define MSPI_NEWQP_OFFSET 0x10
+#define MSPI_ENDQP_OFFSET 0x14
+#define MSPI_SPCR2_OFFSET 0x18
+#define MSPI_SPCR2_SPE_SHIFT 6
+#define MSPI_SPCR2_SPIFIE_SHIFT 5
+#define MSPI_SPCR2_CONT_AFTER_CMD_SHIFT 7
+#define MSPI_STATUS_OFFSET 0x20
+#define MSPI_TXRAM_OFFSET 0x40
+#define MSPI_RXRAM_OFFSET 0xc0
+#define MSPI_CDRAM_OFFSET 0x140
+#define MSPI_CDRAM_CONT_SHIFT 7
+#define MSPI_CDRAM_BITSE_SHIFT 6
+#define MSPI_CDRAM_PCS_SHIFT 0
+#define MSPI_WRITE_LOCK_OFFSET 0x180
+#define INTERRUPT_MSPI_DONE_OFFSET 0x14
+
+#define NUM_SLOTS 16
+
+struct bcm_mspi {
+ struct platform_device *pdev;
+ void __iomem *base;
+ /* Base of interrupt control regs. */
+ void __iomem *int_base;
+ struct spi_master *master;
+ struct clk *clk;
+ u32 spbr;
+ struct completion xfer_done;
+ int rx_slot;
+};
+
+/*
+ * Calculate TXRAM offset for the requested slot.
+ */
+static inline u32 tx_offset(int slot)
+{
+ BUG_ON(slot > NUM_SLOTS);
+ return 4 * (slot * 2);
+}
+
+/*
+ * Calculate RXRAM offset for the requested slot.
+ */
+static inline u32 rx_offset(int slot)
+{
+ BUG_ON(slot > NUM_SLOTS);
+ return 4 * (1 + slot * 2);
+}
+
+/*
+ * Calculate CDRAM offset for the requested slot.
+ */
+static inline u32 cdram_offset(int slot)
+{
+ BUG_ON(slot > NUM_SLOTS);
+ return MSPI_CDRAM_OFFSET + (4 * slot);
+}
+
+/*
+ * Start tx or rx SPI transfer and wait until complete. The queue start pointer
+ * is always 0. The end queue pointer is passed in to end_slot.
+ *
+ * @set_cont Set the CONT bit if true, clear if false. The CONT bit must be set
+ * if all slots are filled and there is more data to be transferred.
+ * @end_slot The end queue pointer.
+ * Returns 0 if successful, -EIO if transfer timed out.
+ */
+static int bcm_mspi_start_transfer(struct bcm_mspi *mspi, bool set_cont,
+ int end_slot)
+{
+ int val;
+ int err = 0;
+
+ /* Set queue pointers for transmit. */
+ writel(0, mspi->base + MSPI_NEWQP_OFFSET);
+ writel(end_slot, mspi->base + MSPI_ENDQP_OFFSET);
+ dev_dbg(&mspi->pdev->dev, "NEWQP: %d ENDQP: %d\n", 0, end_slot);
+
+ reinit_completion(&mspi->xfer_done);
+
+ writel(1, mspi->base + MSPI_WRITE_LOCK_OFFSET);
+
+ /*
+ * Start the transfer. CONT bit is set if another tx SPI transfer
+ * is required.
+ */
+ val = (1 << MSPI_SPCR2_SPE_SHIFT) | (1 << MSPI_SPCR2_SPIFIE_SHIFT);
+ if (set_cont)
+ val |= (1 << MSPI_SPCR2_CONT_AFTER_CMD_SHIFT);
+
+ writel(val, mspi->base + MSPI_SPCR2_OFFSET);
+ dev_dbg(&mspi->pdev->dev, "SPCR2: %d\n", val);
+
+ /* Wait for interrupt indicating transfer is complete. */
+ if (!wait_for_completion_timeout(&mspi->xfer_done,
+ msecs_to_jiffies(10))) {
+ dev_err(&mspi->pdev->dev,
+ "timeout waiting for tx MSPI interrupt\n");
+ err = -ETIMEDOUT;
+ }
+
+ writel(0, mspi->base + MSPI_WRITE_LOCK_OFFSET);
+
+ return err;
+}
+
+/*
+ * Copies data from tx buffer to the h/w tx buffer (TXRAM). When all tx slots
+ * have been filled, a SPI transfer is initiated to send the data to the device.
+ * Continues until all data has been sent.
+ *
+ * Data is copied into the h/w transmit buffer starting at TXRAM0. The CONT bit
+ * is set in the corresponding command register (CDRAMx) if there is another
+ * byte to send. It is cleared on the last byte.
+ *
+ * The number of bytes sent is saved and is the start of the receive buffer for
+ * the next receive transfer. See bcm_mspi_rx_data().
+ */
+static int bcm_mspi_tx_data(struct spi_master *master,
+ struct spi_device *spi_dev, struct spi_transfer *transfer)
+{
+ struct bcm_mspi *mspi = spi_master_get_devdata(master);
+ int slot = 0;
+ const u8 *buf = transfer->tx_buf;
+ u32 val;
+ int bytes_processed = 0;
+ int err;
+
+ if (!transfer->tx_buf)
+ return 0;
+
+ dev_dbg(&mspi->pdev->dev, "tx %d bytes\n", transfer->len);
+
+ while (bytes_processed < transfer->len) {
+ bool last_slot;
+
+ /*
+ * Write data to slots until all are filled or all
+ * bytes written.
+ */
+ for (slot = 0; slot < NUM_SLOTS; slot++) {
+ u32 txram_offset = MSPI_TXRAM_OFFSET + tx_offset(slot);
+ u32 msb = *buf++;
+
+ val = (spi_dev->chip_select << MSPI_CDRAM_PCS_SHIFT) |
+ (1 << MSPI_CDRAM_CONT_SHIFT);
+
+ writel(msb, mspi->base + txram_offset);
+ dev_dbg(&mspi->pdev->dev, "TXRAM: write 0x%x to 0x%x\n",
+ msb, txram_offset);
+
+ bytes_processed++;
+
+ if (bytes_processed >= transfer->len)
+ last_slot = true;
+ else
+ last_slot = false;
+
+ /*
+ * Update command register. CONT cleared
+ * on last slot.
+ */
+ if (last_slot)
+ val &= ~(1 << MSPI_CDRAM_CONT_SHIFT);
+
+ writel(val, mspi->base + cdram_offset(slot));
+ dev_dbg(&mspi->pdev->dev, "CDRAM: write 0x%x to 0x%x\n",
+ val, cdram_offset(slot));
+
+ /* Stop filling slots if all data written. */
+ if (last_slot)
+ break;
+ }
+
+ /* Start transfer and wait until complete. */
+ err = bcm_mspi_start_transfer(mspi, !last_slot, slot);
+ if (err)
+ return err;
+
+ /* Delay requested amount before next transfer. */
+ udelay(transfer->delay_usecs);
+ }
+
+ /* The rx data will go into RXRAM0/1 + last tx length. */
+ if (slot + 1 >= NUM_SLOTS)
+ mspi->rx_slot = 0;
+ else
+ mspi->rx_slot = slot + 1;
+
+ return 0;
+}
+
+/*
+ * Receives data from the device by configuring the command registers (CDRAMx)
+ * and then initiating a receive transfer. When the transfer is complete, the
+ * data is copied to the receive buffer. Continues until all all data has
+ * been received.
+ *
+ * The received data is copied into the RXRAM buffer starting at RXRAM0 + the
+ * last tx length + 1. After the transfer is complete, the data is received
+ * starting at RXRAM0 again until another tx transfer is done. The CONT bit
+ * is always set on the command register (CDRAMx) corresponding to the RXRAM
+ * slot. The next unused slot is also configured except the CONT bit is cleared.
+ * This quirk applies to rx transfers only.
+ */
+static int bcm_mspi_rx_data(struct spi_master *master,
+ struct spi_device *spi_dev, struct spi_transfer *transfer)
+{
+ struct bcm_mspi *mspi = spi_master_get_devdata(master);
+ int slot, rx_slot, end_slot;
+ int bytes_processed = 0;
+ u8 *buf = transfer->rx_buf;
+ u32 val;
+ int err;
+
+ if (!transfer->rx_buf)
+ return 0;
+
+ dev_dbg(&mspi->pdev->dev, "rx %d bytes\n", transfer->len);
+
+ /* Receive all rx data. */
+ while (bytes_processed < transfer->len) {
+ bool last_slot;
+
+ /* Set command register for each slot. */
+ for (slot = 0; slot < NUM_SLOTS; slot++) {
+ last_slot = (slot + 1 >= transfer->len);
+
+ val = (spi_dev->chip_select << MSPI_CDRAM_PCS_SHIFT) |
+ (1 << MSPI_CDRAM_CONT_SHIFT);
+
+ /* Update command register. */
+ writel(val, mspi->base + cdram_offset(slot));
+ dev_dbg(&mspi->pdev->dev, "CDRAM: write 0x%x to 0x%x\n",
+ val, cdram_offset(slot));
+
+ /* Stop filling slots if all data written. */
+ if (last_slot)
+ break;
+ }
+
+ /* CONT bit is cleared on the next unused slot. */
+ end_slot = slot;
+ if (slot + 1 < NUM_SLOTS) {
+ end_slot += 1;
+ val &= ~(1 << MSPI_CDRAM_CONT_SHIFT);
+ writel(val, mspi->base + cdram_offset(end_slot));
+ dev_dbg(&mspi->pdev->dev, "CDRAM: write 0x%x to 0x%x\n",
+ val, cdram_offset(end_slot));
+ }
+
+ /* Start transfer and wait until complete. */
+ err = bcm_mspi_start_transfer(mspi, !last_slot, end_slot);
+ if (err)
+ return err;
+
+ /* Copy data from rx registers to rx buffer. */
+ for (rx_slot = mspi->rx_slot; rx_slot < NUM_SLOTS; rx_slot++) {
+ u32 rxram_offset = MSPI_RXRAM_OFFSET +
+ rx_offset(rx_slot);
+ u32 msb = readl(mspi->base + rxram_offset);
+
+ dev_dbg(&mspi->pdev->dev, "rxram offset: %x. data = 0x%x\n",
+ rxram_offset, msb);
+
+ *buf++ = (u8)msb;
+ bytes_processed++;
+
+ if (bytes_processed >= transfer->len)
+ break;
+ }
+
+ /*
+ * The read pointer always starts at RXRAM0 after an rx transfer
+ * of any length.
+ */
+ mspi->rx_slot = 0;
+
+ /* Delay requested amount before next transfer. */
+ udelay(transfer->delay_usecs);
+ }
+
+ return 0;
+}
+
+static int bcm_mspi_transfer_one(struct spi_master *master,
+ struct spi_device *spidev, struct spi_transfer *transfer)
+{
+ int err;
+
+ /* 8 bit transfers only are currently supported. */
+ if (transfer->bits_per_word > 8)
+ return -ENOTSUPP;
+
+ err = bcm_mspi_tx_data(master, spidev, transfer);
+ if (err)
+ return err;
+
+ err = bcm_mspi_rx_data(master, spidev, transfer);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/*
+ * The ISR is called when a SPI transfer has completed. SPIF (MSPI finished)
+ * will be set in the status register.
+ */
+static irqreturn_t bcm_mspi_isr(int irq, void *data)
+{
+ struct platform_device *pdev = data;
+ struct bcm_mspi *mspi = platform_get_drvdata(pdev);
+ u32 val;
+
+ val = readl(mspi->base + MSPI_STATUS_OFFSET);
+ if (val & 1) {
+ /* Clear interrupt then signal completion of transfer. */
+ val &= ~1;
+ writel(val, mspi->base + MSPI_STATUS_OFFSET);
+ if (mspi->int_base)
+ writel(1, mspi->int_base + INTERRUPT_MSPI_DONE_OFFSET);
+
+ complete(&mspi->xfer_done);
+
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static void bcm_mspi_hw_init(struct bcm_mspi *mspi)
+{
+ /* Set SPBR (serial clock baud rate). */
+ if (mspi->spbr)
+ writel(mspi->spbr << MSPI_SPCR0_LSB_SHIFT,
+ mspi->base + MSPI_SPCR0_LSB_OFFSET);
+}
+
+static const struct of_device_id bcm_mspi_dt[] = {
+ { .compatible = "brcm,mspi" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bcm_mspi_dt);
+
+static int bcm_mspi_probe(struct platform_device *pdev)
+{
+ struct bcm_mspi *data;
+ struct spi_master *master;
+ struct device *dev = &pdev->dev;
+ int err;
+ struct resource *res;
+ unsigned int irq;
+
+ dev_info(dev, "Initializing BCM MSPI\n");
+
+ master = spi_alloc_master(dev, sizeof(*data));
+ if (!master) {
+ dev_err(dev, "error allocating spi_master\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, master);
+
+ data = spi_master_get_devdata(master);
+ data->master = master;
+ data->pdev = pdev;
+ platform_set_drvdata(pdev, data);
+ init_completion(&data->xfer_done);
+
+ /* SPI master will always use the SPI device(s) from DT. */
+ master->dev.of_node = dev->of_node;
+ master->transfer_one = bcm_mspi_transfer_one;
+
+ /*
+ * Enable clock if provided. The frequency can be changed by setting
+ * SPBR (serial clock baud rate) based on the desired 'clock-frequency'.
+ *
+ * Baud rate is calculated as: mspi_clk / (2 * SPBR) where SPBR is a
+ * value between 1-255. If not set then it is left at the h/w default.
+ */
+ data->clk = devm_clk_get(dev, "mspi_clk");
+ if (!IS_ERR(data->clk)) {
+ u32 desired_rate = 0;
+
+ err = clk_prepare_enable(data->clk);
+ if (err < 0) {
+ dev_err(dev, "failed to enable clock: %d\n", err);
+ goto out;
+ }
+
+ /* Calculate SPBR if clock-frequency provided. */
+ of_property_read_u32(dev->of_node, "clock-frequency",
+ &desired_rate);
+ if (desired_rate > 0) {
+ u32 spbr = clk_get_rate(data->clk) / (2 * desired_rate);
+
+ if (spbr > 0) {
+ data->spbr = clamp_val(spbr, SPBR_MIN,
+ SPBR_MAX);
+ } else {
+ dev_err(dev, "failed to get clock rate: %d\n",
+ spbr);
+ err = spbr;
+ goto out;
+ }
+ }
+ } else {
+ /* Don't report error if clock not specified - it's optional. */
+ if (PTR_ERR(data->clk) != -ENOENT) {
+ err = PTR_ERR(data->clk);
+ dev_err(dev, "failed to get clock: %d\n", err);
+ goto out;
+ }
+ }
+
+ /* Map base memory address. */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(data->base)) {
+ dev_err(&pdev->dev, "unable to map base address\n");
+ err = PTR_ERR(data->base);
+ goto out;
+ }
+
+ /* Map interrupt control base memory address. Not used on all chips. */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ data->int_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(data->int_base)) {
+ dev_err(&pdev->dev, "unable to map base address\n");
+ err = PTR_ERR(data->base);
+ goto out;
+ }
+ }
+
+ /* Get IRQ. */
+ irq = platform_get_irq(pdev, 0);
+ if (irq == 0) {
+ dev_err(&pdev->dev, "could not get IRQ\n");
+ err = -EIO;
+ goto out;
+ }
+
+ /* Initialize SPI controller. */
+ bcm_mspi_hw_init(data);
+
+ err = devm_request_irq(&pdev->dev, irq, bcm_mspi_isr,
+ IRQF_SHARED, "bcm-mspi", pdev);
+ if (err)
+ goto out;
+
+ err = devm_spi_register_master(dev, data->master);
+ if (err)
+ goto out;
+
+ dev_info(dev, "BCM MSPI initialized successfully\n");
+
+ return 0;
+
+out:
+ spi_master_put(data->master);
+ return err;
+}
+
+static struct platform_driver bcm_mspi_driver = {
+ .driver = {
+ .name = "brcm,mspi-v0",
+ .of_match_table = bcm_mspi_dt,
+ },
+ .probe = bcm_mspi_probe,
+};
+
+module_platform_driver(bcm_mspi_driver);
+
+MODULE_DESCRIPTION("Broadcom MSPI SPI Controller driver");
+MODULE_AUTHOR("Broadcom");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/