[U-Boot] [RFC 13/22] spi: add thunderx SPI driver

Tim Harvey tharvey at gateworks.com
Fri Feb 22 18:03:10 UTC 2019


Signed-off-by: Tim Harvey <tharvey at gateworks.com>
---
 configs/thunderx_81xx_defconfig |   4 +
 drivers/spi/Kconfig             |   6 +
 drivers/spi/Makefile            |   1 +
 drivers/spi/thunderx_spi.c      | 448 ++++++++++++++++++++++++++++++++
 4 files changed, 459 insertions(+)
 create mode 100755 drivers/spi/thunderx_spi.c

diff --git a/configs/thunderx_81xx_defconfig b/configs/thunderx_81xx_defconfig
index e43aa9750d..48f57ecf1b 100644
--- a/configs/thunderx_81xx_defconfig
+++ b/configs/thunderx_81xx_defconfig
@@ -24,6 +24,7 @@ CONFIG_SYS_PROMPT="ThunderX_81XX> "
 CONFIG_CMD_GPIO=y
 CONFIG_CMD_I2C=y
 CONFIG_CMD_PCI=y
+CONFIG_CMD_SPI=y
 # CONFIG_CMD_NET is not set
 CONFIG_DEFAULT_DEVICE_TREE="thunderx-81xx"
 CONFIG_DM=y
@@ -39,3 +40,6 @@ CONFIG_PCI_THUNDERX=y
 CONFIG_DM_SERIAL=y
 CONFIG_DEBUG_UART_PL011=y
 CONFIG_DEBUG_UART_SKIP_INIT=y
+CONFIG_SPI=y
+CONFIG_DM_SPI=y
+CONFIG_THUNDERX_SPI=y
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 516188ea88..d1d5463909 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -233,6 +233,12 @@ config TEGRA210_QSPI
 	  be used to access SPI chips on platforms embedding this
 	  NVIDIA Tegra210 IP core.
 
+config THUNDERX_SPI
+	bool "Cavium ThunderX SPI driver"
+	help
+	  Enable the Cavium ThunderX SPI driver. This driver can be used to
+	  access the SPI NOR flash on ThunderX SoC platforms.
+
 config XILINX_SPI
 	bool "Xilinx SPI driver"
 	help
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 7242ea7e40..ea99775094 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_TEGRA114_SPI) += tegra114_spi.o
 obj-$(CONFIG_TEGRA20_SFLASH) += tegra20_sflash.o
 obj-$(CONFIG_TEGRA20_SLINK) += tegra20_slink.o
 obj-$(CONFIG_TEGRA210_QSPI) += tegra210_qspi.o
+obj-$(CONFIG_THUNDERX_SPI) += thunderx_spi.o
 obj-$(CONFIG_TI_QSPI) += ti_qspi.o
 obj-$(CONFIG_XILINX_SPI) += xilinx_spi.o
 obj-$(CONFIG_ZYNQ_SPI) += zynq_spi.o
diff --git a/drivers/spi/thunderx_spi.c b/drivers/spi/thunderx_spi.c
new file mode 100755
index 0000000000..e165215524
--- /dev/null
+++ b/drivers/spi/thunderx_spi.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier:	GPL-2.0+
+/*
+ * Copyright (C) 2018, Cavium Inc.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <malloc.h>
+#include <spi.h>
+#include <watchdog.h>
+
+#include <asm/io.h>
+#include <asm/arch-thunderx/thunderx.h>
+#include <asm/unaligned.h>
+
+#define THUNDERX_SPI_MAX_BYTES		9
+#define THUNDERX_SPI_MAX_CLOCK_HZ	50000000
+
+#define THUNDERX_SPI_NUM_CS		4
+
+#define THUNDERX_SPI_CS_VALID(cs)	((cs) < THUNDERX_SPI_NUM_CS)
+
+#define MPI_CFG				0x1000
+#define MPI_STS				0x1008
+#define MPI_TX				0x1010
+#define MPI_WIDE_DAT			0x1040
+#define MPI_DAT(X)			(0x1080 + ((X) << 3))
+
+union mpi_cfg {
+	uint64_t u;
+	struct mpi_cfg_s {
+#if __BYTE_ORDER == __BIG_ENDIAN /* Word 0 - Big Endian */
+		uint64_t		:35;
+
+		uint64_t clkdiv		:13;	/** clock divisor */
+		uint64_t csena3		:1;	/** cs enable 3. */
+		uint64_t csena2		:1;	/** cs enable 2 */
+		uint64_t csena1		:1;	/** cs enable 1 */
+		uint64_t csena0		:1;	/** cs enable 0 */
+		/**
+		 * 0 = SPI_CSn asserts 1/2 coprocessor-clock cycle before
+		 *     transaction
+		 * 1 = SPI_CSn asserts coincident with transaction
+		 */
+		uint64_t cslate		:1;
+		/**
+		 * Tristate TX.  Set to 1 to tristate SPI_DO when not
+		 * transmitting.
+		 */
+		uint64_t tritx		:1;
+		/**
+		 * When set, guarantees idle coprocessor-clock cycles between
+		 * commands.
+		 */
+		uint64_t idleclks	:2;
+		/**
+		 * SPI_CSn_L high.  1 = SPI_CSn_L is asserted high,
+		 * 0 = SPI_CS_n asserted low.
+		 */
+		uint64_t cshi		:1;
+		uint64_t 		:2;	/** Reserved */
+		/** 0 = shift MSB first, 1 = shift LSB first */
+		uint64_t lsbfirst	:1;
+		/**
+		 * Wire-or DO and DI.
+		 * 0 = SPI_DO and SPI_DI are separate wires (SPI).  SPI_DO pin
+		 *     is always driven.
+		 * 1 = SPI_DO/DI is all from SPI_DO pin (MPI).  SPI_DO pin is
+		 *     tristated when not transmitting.  If WIREOR = 1, SPI_DI
+		 *     pin is not used by the MPI/SPI engine.
+		 */
+		uint64_t wireor		:1;
+		/**
+		 * Clock control.
+		 * 0 = Clock idles to value given by IDLELO after completion of
+		 *     MPI/SPI transaction.
+		 * 1 = Clock never idles, requires SPI_CSn_L
+		 *     deassertion/assertion between commands.
+		 */
+		uint64_t clk_cont	:1;
+		/**
+		 * Clock idle low/clock invert
+		 * 0 = SPI_CLK idles high, first transition is high-to-low.
+		 *     This correspondes to SPI Block Guide options CPOL = 1,
+		 *     CPHA = 0.
+		 * 1 = SPI_CLK idles low, first transition is low-to-high.  This
+		 *     corresponds to SPI Block Guide options CPOL = 0, CPHA = 0.
+		 */
+		uint64_t idlelo		:1;
+		/** MPI/SPI enable, 0 = pins are tristated, 1 = pins driven */
+		uint64_t enable		:1;
+#else /* Word 0 - Little Endian */
+		uint64_t enable		:1;
+		uint64_t idlelo		:1;
+		uint64_t clk_cont	:1;
+		uint64_t wireor		:1;
+		uint64_t lsbfirst	:1;
+		uint64_t		:2;
+		uint64_t cshi		:1;
+		uint64_t idleclks	:2;
+		uint64_t tritx		:1;
+		uint64_t cslate		:1;
+		uint64_t csena0		:1;
+		uint64_t csena1		:1;
+		uint64_t csena2		:1;
+		uint64_t csena3		:1;
+		uint64_t clkdiv		:13;
+		uint64_t 		:35;	/** Reserved */
+#endif /* Word 0 - End */
+	} s;
+	/* struct mpi_cfg_s cn; */
+};
+
+/**
+ * Register (NCB) mpi_dat#
+ *
+ * MPI/SPI Data Registers
+ */
+union mpi_dat {
+	uint64_t u;
+	struct mpi_datx_s {
+#if __BYTE_ORDER == __BIG_ENDIAN /* Word 0 - Big Endian */
+		uint64_t reserved_8_63	:56;
+		/**< [  7:  0](R/W/H) Data to transmit/receive. */
+		uint64_t data		:8;
+#else /* Word 0 - Little Endian */
+		uint64_t data		:8;
+		uint64_t reserved_8_63	:56;
+#endif /* Word 0 - End */
+	} s;
+	/* struct mpi_datx_s cn; */
+};
+
+/**
+ * Register (NCB) mpi_sts
+ *
+ * MPI/SPI STS Register
+ */
+union mpi_sts {
+	uint64_t u;
+	struct mpi_sts_s {
+#if __BYTE_ORDER == __BIG_ENDIAN /* Word 0 - Big Endian */
+		uint64_t reserved_13_63	:51;
+		uint64_t rxnum		:5;	/** Number of bytes */
+		uint64_t reserved_2_7	:6;
+		uint64_t mpi_intr	:1;	/** Transaction done int */
+		uint64_t busy		:1;	/** SPI engine busy */
+#else /* Word 0 - Little Endian */
+		uint64_t busy		:1;
+		uint64_t mpi_intr	:1;
+		uint64_t reserved_2_7	:6;
+		uint64_t rxnum		:5;
+		uint64_t reserved_13_63	:51;
+#endif /* Word 0 - End */
+	} s;
+	/* struct mpi_sts_s cn; */
+};
+
+/**
+ * Register (NCB) mpi_tx
+ *
+ * MPI/SPI Transmit Register
+ */
+union mpi_tx {
+	uint64_t u;
+	struct mpi_tx_s {
+#if __BYTE_ORDER == __BIG_ENDIAN /* Word 0 - Big Endian */
+		uint64_t		:42;	/* Reserved */
+		uint64_t csid 		:2;	/** Which CS to assert */
+		uint64_t		:3;	/* Reserved */
+		uint64_t leavecs	:1;	/** Leave CSn asserted */
+		uint64_t 		:3;	/* Reserved */
+		uint64_t txnum		:5;	/** Number of words to tx */
+		uint64_t		:3;	/* Reserved */
+		uint64_t totnum		:5;	/** Total bytes to shift */
+#else /* Word 0 - Little Endian */
+		uint64_t totnum		:5;
+		uint64_t 		:3;
+		uint64_t txnum		:5;
+		uint64_t		:3;
+		uint64_t leavecs	:1;
+		uint64_t		:3;
+		uint64_t csid		:2;
+		uint64_t		:42;
+#endif /* Word 0 - End */
+	} s;
+	/* struct mpi_tx_s cn; */
+};
+
+/** Local driver data structure */
+struct thunderx_spi {
+	void *baseaddr;		/** Register base address */
+	u32 clkdiv;		/** Clock divisor for device speed */
+};
+
+void *thunderx_spi_get_baseaddr(struct udevice *dev)
+{
+	struct udevice *bus = dev_get_parent(dev);
+	struct thunderx_spi *priv = dev_get_priv(bus);
+
+	return priv->baseaddr;
+}
+
+static union mpi_cfg thunderx_spi_set_mpicfg(struct udevice *dev)
+{
+	struct dm_spi_slave_platdata *slave = dev_get_parent_platdata(dev);
+	struct udevice *bus = dev_get_parent(dev);
+	struct thunderx_spi *priv = dev_get_priv(bus);
+	union mpi_cfg mpi_cfg;
+	uint max_speed = slave->max_hz;
+	bool cpha, cpol;
+
+	if (!max_speed)
+		max_speed = 12500000;
+	if (max_speed > THUNDERX_SPI_MAX_CLOCK_HZ)
+		max_speed = THUNDERX_SPI_MAX_CLOCK_HZ;
+
+	dev_dbg(dev, "%s: CS%d Hz=%d Mode=%x\n", __func__,
+		slave->cs, slave->max_hz, slave->mode);
+	cpha = !!(slave->mode & SPI_CPHA);
+	cpol = !!(slave->mode & SPI_CPOL);
+
+	mpi_cfg.u = 0;
+	mpi_cfg.s.clkdiv = priv->clkdiv & 0x1fff;
+	mpi_cfg.s.cshi = !!(slave->mode & SPI_CS_HIGH);
+	mpi_cfg.s.lsbfirst = !!(slave->mode & SPI_LSB_FIRST);
+	mpi_cfg.s.wireor = !!(slave->mode & SPI_3WIRE);
+	mpi_cfg.s.idlelo = cpha != cpol;
+	mpi_cfg.s.cslate = cpha;
+	mpi_cfg.s.enable = 1;
+	mpi_cfg.s.csena0 = 1;
+	mpi_cfg.s.csena1 = 1;
+	mpi_cfg.s.csena2 = 1;
+	mpi_cfg.s.csena3 = 1;
+	dev_dbg(dev, "%s: mpi_cfg=%llx\n", __func__, mpi_cfg.u);
+
+	return mpi_cfg;
+}
+
+/**
+ * Wait until the SPI bus is ready
+ *
+ * @param	dev	SPI device to wait for
+ */
+static void thunderx_spi_wait_ready(struct udevice *dev)
+{
+	void *baseaddr = thunderx_spi_get_baseaddr(dev);
+	union mpi_sts mpi_sts;
+
+	do {
+		mpi_sts.u = readq(baseaddr + MPI_STS);
+		WATCHDOG_RESET();
+	} while (mpi_sts.s.busy);
+}
+/**
+ * Claim the bus for a slave device
+ *
+ * @param	dev	SPI bus
+ *
+ * @return	0 for success, -EINVAL if chip select is invalid
+ */
+static int thunderx_spi_claim_bus(struct udevice *dev)
+{
+	void *baseaddr = thunderx_spi_get_baseaddr(dev);
+	union mpi_cfg mpi_cfg;
+
+	if (!THUNDERX_SPI_CS_VALID(spi_chip_select(dev)))
+		return -EINVAL;
+
+	mpi_cfg.u = readq(baseaddr + MPI_CFG);
+	mpi_cfg.s.tritx = 0;
+	mpi_cfg.s.enable = 1;
+	writeq(mpi_cfg.u, baseaddr + MPI_CFG);
+
+	return 0;
+}
+
+/**
+ * Release the bus to a slave device
+ *
+ * @param	dev	SPI bus
+ *
+ * @return	0 for success, -EINVAL if chip select is invalid
+ */
+static int thunderx_spi_release_bus(struct udevice *dev)
+{
+	void *baseaddr = thunderx_spi_get_baseaddr(dev);
+	union mpi_cfg mpi_cfg;
+
+	if (!THUNDERX_SPI_CS_VALID(spi_chip_select(dev)))
+		return -EINVAL;
+
+	mpi_cfg.u = readq(baseaddr + MPI_CFG);
+	mpi_cfg.s.enable = 0;
+	writeq(mpi_cfg.u, baseaddr + MPI_CFG);
+
+	return 0;
+}
+
+static int thunderx_spi_xfer(struct udevice *dev, unsigned int bitlen,
+			     const void *dout, void *din, unsigned long flags)
+{
+	void *baseaddr = thunderx_spi_get_baseaddr(dev);
+	union mpi_tx mpi_tx;
+	union mpi_cfg mpi_cfg;
+	uint64_t wide_dat = 0;
+	int len = bitlen / 8;
+	int i;
+	const uint8_t *tx_data = dout;
+	uint8_t *rx_data = din;
+	int cs = spi_chip_select(dev);
+
+	if (!THUNDERX_SPI_CS_VALID(cs))
+		return -EINVAL;
+
+	dev_dbg(dev, "%s bitlen=%u dout=%p din=%p flags=0x%lx CS%d\n", __func__,
+		bitlen, dout, din, flags, cs);
+
+	mpi_cfg = thunderx_spi_set_mpicfg(dev);
+
+	if (mpi_cfg.u != readq(baseaddr + MPI_CFG))
+		writeq(mpi_cfg.u, baseaddr + MPI_CFG);
+
+	/* Start by writing and reading 8 bytes at a time.  While we can support
+	 * up to 10, it's easier to just use 8 with the MPI_WIDE_DAT register.
+	 */
+	while (len > 8) {
+		if (tx_data) {
+			wide_dat = get_unaligned((uint64_t *)tx_data);
+			debug("  tx: %016llx\n", (unsigned long long)wide_dat);
+			tx_data += 8;
+			writeq(wide_dat, baseaddr + MPI_WIDE_DAT);
+		}
+		mpi_tx.u = 0;
+		mpi_tx.s.csid = cs;
+		mpi_tx.s.leavecs = 1;
+		mpi_tx.s.txnum = tx_data ? 8 : 0;
+		mpi_tx.s.totnum = 8;
+		writeq(mpi_tx.u, baseaddr + MPI_TX);
+
+		thunderx_spi_wait_ready(dev);
+
+		if (rx_data) {
+			wide_dat = readq(baseaddr + MPI_WIDE_DAT);
+			debug("  rx: %016llx\n", (unsigned long long)wide_dat);
+			*(uint64_t *)rx_data = wide_dat;
+			rx_data += 8;
+		}
+		len -= 8;
+	}
+
+	/* Write and read the rest of the data */
+	if (tx_data)
+		for (i = 0; i < len; i++) {
+			debug("  tx: %02x\n", *tx_data);
+			writeq(*tx_data++, baseaddr + MPI_DAT(i));
+		}
+
+	mpi_tx.u = 0;
+	mpi_tx.s.csid = cs;
+	mpi_tx.s.leavecs = !(flags & SPI_XFER_END);
+	mpi_tx.s.txnum = tx_data ? len : 0;
+	mpi_tx.s.totnum = len;
+
+	writeq(mpi_tx.u, baseaddr + MPI_TX);
+
+	thunderx_spi_wait_ready(dev);
+
+	if (rx_data) {
+		for (i = 0; i < len; i++) {
+			*rx_data = readq(baseaddr + MPI_DAT(i)) & 0xff;
+			debug("  rx: %02x\n", *rx_data);
+			rx_data++;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * Set the speed of the SPI bus
+ *
+ * @param	bus	bus to set
+ * @param	max_hz	maximum speed supported
+ */
+static int thunderx_spi_set_speed(struct udevice *bus, uint max_hz)
+{
+	struct thunderx_spi *priv = dev_get_priv(bus);
+
+	dev_dbg(dev, "%s: max_hz=%u io=%llu", __func__, max_hz,
+		thunderx_get_io_clock());
+	if (max_hz > THUNDERX_SPI_MAX_CLOCK_HZ)
+		max_hz = THUNDERX_SPI_MAX_CLOCK_HZ;
+	priv->clkdiv = (thunderx_get_io_clock()) / (2 * max_hz);
+
+	return 0;
+}
+
+static int thunderx_spi_set_mode(struct udevice *bus, uint mode)
+{
+	/* We don't set it here */
+	return 0;
+}
+
+static int thunderx_pci_spi_probe(struct udevice *dev)
+{
+	struct thunderx_spi *priv = dev_get_priv(dev);
+	pci_dev_t bdf = dm_pci_get_bdf(dev);
+	size_t size;
+
+	dev->req_seq = PCI_FUNC(bdf);
+	priv->baseaddr = dm_pci_map_bar(dev, 0, &size, PCI_REGION_MEM);
+	dev_dbg(dev, "%s: SPI PCI device: bdf:%x base:%p\n", __func__,
+		bdf, priv->baseaddr);
+
+	return 0;
+}
+
+static const struct dm_spi_ops thunderx_spi_ops = {
+	.claim_bus	= thunderx_spi_claim_bus,
+	.release_bus	= thunderx_spi_release_bus,
+	.xfer		= thunderx_spi_xfer,
+	.set_speed	= thunderx_spi_set_speed,
+	.set_mode	= thunderx_spi_set_mode,
+};
+
+static const struct udevice_id thunderx_spi_ids[] = {
+	{ .compatible	= "cavium,thunder-8890-spi" },
+	{ .compatible	= "cavium,thunder-8190-spi" },
+	{ }
+};
+
+U_BOOT_DRIVER(thunderx_pci_spi) = {
+	.name			= "spi_thunderx",
+	.id			= UCLASS_SPI,
+	.of_match 		= thunderx_spi_ids,
+	.probe			= thunderx_pci_spi_probe,
+	.priv_auto_alloc_size 	= sizeof(struct thunderx_spi),
+	.ops			= &thunderx_spi_ops,
+};
+
+static struct pci_device_id thunderx_pci_spi_supported[] = {
+	{ PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_THUNDERX_SPI) },
+	{ },
+};
+
+U_BOOT_PCI_DEVICE(thunderx_pci_spi, thunderx_pci_spi_supported);
+
-- 
2.17.1



More information about the U-Boot mailing list