[PATCH 2/2] spi: bcm-mspi: Add support for Broadcom MSPI driver.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




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 devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux