All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jonathan Richardson <jonathar@broadcom.com>
To: Mark Brown <broonie@kernel.org>,
	Dmitry Torokhov <dtor@google.com>,
	Anatol Pomazau <anatol@google.com>
Cc: Jonathan Richardson <jonathar@broadcom.com>,
	Scott Branden <sbranden@broadcom.com>,
	Rob Herring <robh+dt@kernel.org>, Pawel Moll <pawel.moll@arm.com>,
	Mark Rutland <mark.rutland@arm.com>,
	Ian Campbell <ijc+devicetree@hellion.org.uk>,
	Kumar Gala <galak@codeaurora.org>, <linux-kernel@vger.kernel.org>,
	<linux-spi@vger.kernel.org>,
	bcm-kernel-feedback-list <bcm-kernel-feedback-list@broadcom.com>,
	<devicetree@vger.kernel.org>
Subject: [PATCH 2/2] spi: bcm-mspi: Add support for Broadcom MSPI driver.
Date: Tue, 12 May 2015 10:38:13 -0700	[thread overview]
Message-ID: <1431452293-16697-3-git-send-email-jonathar@broadcom.com> (raw)
In-Reply-To: <1431452293-16697-1-git-send-email-jonathar@broadcom.com>

The MSPI controller is a SPI controller found on various Broadcom
SoC's such as Cygnus.

Signed-off-by: Jonathan Richardson <jonathar@broadcom.com>
---
 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


WARNING: multiple messages have this Message-ID (diff)
From: Jonathan Richardson <jonathar@broadcom.com>
To: Mark Brown <broonie@kernel.org>,
	Dmitry Torokhov <dtor@google.com>,
	Anatol Pomazau <anatol@google.com>
Cc: Jonathan Richardson <jonathar@broadcom.com>,
	Scott Branden <sbranden@broadcom.com>,
	Rob Herring <robh+dt@kernel.org>, Pawel Moll <pawel.moll@arm.com>,
	Mark Rutland <mark.rutland@arm.com>,
	Ian Campbell <ijc+devicetree@hellion.org.uk>,
	Kumar Gala <galak@codeaurora.org>,
	linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org,
	bcm-kernel-feedback-list <bcm-kernel-feedback-list@broadcom.com>,
	devicetree@vger.kernel.org
Subject: [PATCH 2/2] spi: bcm-mspi: Add support for Broadcom MSPI driver.
Date: Tue, 12 May 2015 10:38:13 -0700	[thread overview]
Message-ID: <1431452293-16697-3-git-send-email-jonathar@broadcom.com> (raw)
In-Reply-To: <1431452293-16697-1-git-send-email-jonathar@broadcom.com>

The MSPI controller is a SPI controller found on various Broadcom
SoC's such as Cygnus.

Signed-off-by: Jonathan Richardson <jonathar@broadcom.com>
---
 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

  parent reply	other threads:[~2015-05-12 17:35 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-05-12 17:38 [PATCH 0/2] Add Broadcom MSPI driver Jonathan Richardson
2015-05-12 17:38 ` Jonathan Richardson
2015-05-12 17:38 ` Jonathan Richardson
2015-05-12 17:38 ` [PATCH 1/2] ARM: dts: Add binding for " Jonathan Richardson
2015-05-12 17:38   ` Jonathan Richardson
2015-05-12 18:38   ` Florian Fainelli
2015-05-12 18:38     ` Florian Fainelli
2015-05-12 17:38 ` Jonathan Richardson [this message]
2015-05-12 17:38   ` [PATCH 2/2] spi: bcm-mspi: Add support " Jonathan Richardson
2015-05-12 18:46   ` Florian Fainelli
2015-05-12 18:46     ` Florian Fainelli
2015-05-12 19:17   ` Mark Brown
2015-05-12 19:17     ` Mark Brown
2015-05-13 23:49     ` Jonathan Richardson
2015-05-13 23:49       ` Jonathan Richardson
2015-05-13 23:49       ` Jonathan Richardson
2015-05-14  0:19       ` Scott Branden
2015-05-14  0:19         ` Scott Branden
2015-05-14  0:19         ` Scott Branden
2015-05-14 10:31         ` Mark Brown
2015-05-14 18:19           ` Scott Branden
2015-05-14 18:19             ` Scott Branden
2015-05-14 18:19             ` Scott Branden
2015-05-14 18:28             ` Florian Fainelli
2015-05-14 18:28               ` Florian Fainelli
2015-05-14 18:36               ` Scott Branden
2015-05-14 18:36                 ` Scott Branden
2015-05-14 18:36                 ` Scott Branden
2015-05-14 18:55                 ` Jonathan Richardson
2015-05-14 18:55                   ` Jonathan Richardson
2015-05-14 18:55                   ` Jonathan Richardson
2015-05-14 19:08             ` Mark Brown
2015-05-14 19:08               ` Mark Brown
2015-05-14 19:55               ` Jonathan Richardson
2015-05-14 19:55                 ` Jonathan Richardson
2015-05-14 19:55                 ` Jonathan Richardson
2015-05-14 20:12                 ` Mark Brown
2015-05-14 21:43                   ` Florian Fainelli
2015-05-14 21:43                     ` Florian Fainelli

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1431452293-16697-3-git-send-email-jonathar@broadcom.com \
    --to=jonathar@broadcom.com \
    --cc=anatol@google.com \
    --cc=bcm-kernel-feedback-list@broadcom.com \
    --cc=broonie@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dtor@google.com \
    --cc=galak@codeaurora.org \
    --cc=ijc+devicetree@hellion.org.uk \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-spi@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=pawel.moll@arm.com \
    --cc=robh+dt@kernel.org \
    --cc=sbranden@broadcom.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.