From: Suneel Garapati <[email protected]>
Adds support for SPI controllers found on Octeon II/III and Octeon TX
TX2 SoC platforms.
Signed-off-by: Aaron Williams <[email protected]>
Signed-off-by: Suneel Garapati <[email protected]>
Signed-off-by: Stefan Roese <[email protected]>
Cc: Daniel Schwierzeck <[email protected]>
Cc: Aaron Williams <[email protected]>
Cc: Chandrakala Chavva <[email protected]>
Cc: Jagan Teki <[email protected]>
---
Changes in v3:
- Removed 2nd ops struct for Octeon TX2 and removed "const" from the
octeon_spi_ops declaration. This makes a more elegant switch to the
Octeon TX2 functions possible, as suggested by Daniel
- Remove driver_data struct, as its not needed. The distinction between
PCI based on non-PCI based probing can be made via a common Octeon TX
& TX2 DT property can be made.
Changes in v2:
- Newly added to this series
- Removed inclusion of "common.h"
- Added "depends on DM_PCI" to Kconfig
- Tested on MIPS Octeon and ARM Octeon TX2
- Fixed issues with Octeon TX2 registration. Now only one driver is
registered and the "ops" is overwritten in the Octeon TX2 case.
- Use dev_get_driver_data() to get the driver data struct
- Removed "struct pci_device_id" definition and U_BOOT_PCI_DEVICE()
as its not needed for the PCI based probing on Octeon TX2
drivers/spi/Kconfig | 8 +
drivers/spi/Makefile | 1 +
drivers/spi/octeon_spi.c | 615 +++++++++++++++++++++++++++++++++++++++
3 files changed, 624 insertions(+)
create mode 100644 drivers/spi/octeon_spi.c
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 30d808d7bb..3fc2d0674a 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -240,6 +240,14 @@ config NXP_FSPI
Enable the NXP FlexSPI (FSPI) driver. This driver can be used to
access the SPI NOR flash on platforms embedding this NXP IP core.
+config OCTEON_SPI
+ bool "Octeon SPI driver"
+ depends on DM_PCI && (ARCH_OCTEON || ARCH_OCTEONTX || ARCH_OCTEONTX2)
+ help
+ Enable the Octeon SPI driver. This driver can be used to
+ access the SPI NOR flash on Octeon II/III and OcteonTX/TX2
+ SoC platforms.
+
config OMAP3_SPI
bool "McSPI driver for OMAP"
help
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 4e7461771f..b5c9ff1af8 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_MXC_SPI) += mxc_spi.o
obj-$(CONFIG_MXS_SPI) += mxs_spi.o
obj-$(CONFIG_NXP_FSPI) += nxp_fspi.o
obj-$(CONFIG_ATCSPI200_SPI) += atcspi200_spi.o
+obj-$(CONFIG_OCTEON_SPI) += octeon_spi.o
obj-$(CONFIG_OMAP3_SPI) += omap3_spi.o
obj-$(CONFIG_PIC32_SPI) += pic32_spi.o
obj-$(CONFIG_PL022_SPI) += pl022_spi.o
diff --git a/drivers/spi/octeon_spi.c b/drivers/spi/octeon_spi.c
new file mode 100644
index 0000000000..868eb1bef5
--- /dev/null
+++ b/drivers/spi/octeon_spi.c
@@ -0,0 +1,615 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Marvell International Ltd.
+ *
+ * https://spdx.org/licenses
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <malloc.h>
+#include <spi.h>
+#include <spi-mem.h>
+#include <watchdog.h>
+#include <asm/io.h>
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/compat.h>
+#include <linux/delay.h>
+
+#define OCTEON_SPI_MAX_BYTES 9
+#define OCTEON_SPI_MAX_CLOCK_HZ 50000000
+
+#define OCTEON_SPI_NUM_CS 4
+
+#define OCTEON_SPI_CS_VALID(cs) ((cs) < OCTEON_SPI_NUM_CS)
+
+#define MPI_CFG 0x0000
+#define MPI_STS 0x0008
+#define MPI_TX 0x0010
+#define MPI_XMIT 0x0018
+#define MPI_WIDE_DAT 0x0040
+#define MPI_IO_CTL 0x0048
+#define MPI_DAT(X) (0x0080 + ((X) << 3))
+#define MPI_WIDE_BUF(X) (0x0800 + ((X) << 3))
+#define MPI_CYA_CFG 0x1000
+#define MPI_CLKEN 0x1080
+
+#define MPI_CFG_ENABLE BIT_ULL(0)
+#define MPI_CFG_IDLELO BIT_ULL(1)
+#define MPI_CFG_CLK_CONT BIT_ULL(2)
+#define MPI_CFG_WIREOR BIT_ULL(3)
+#define MPI_CFG_LSBFIRST BIT_ULL(4)
+#define MPI_CFG_CS_STICKY BIT_ULL(5)
+#define MPI_CFG_CSHI BIT_ULL(7)
+#define MPI_CFG_IDLECLKS GENMASK_ULL(9, 8)
+#define MPI_CFG_TRITX BIT_ULL(10)
+#define MPI_CFG_CSLATE BIT_ULL(11)
+#define MPI_CFG_CSENA0 BIT_ULL(12)
+#define MPI_CFG_CSENA1 BIT_ULL(13)
+#define MPI_CFG_CSENA2 BIT_ULL(14)
+#define MPI_CFG_CSENA3 BIT_ULL(15)
+#define MPI_CFG_CLKDIV GENMASK_ULL(28, 16)
+#define MPI_CFG_LEGACY_DIS BIT_ULL(31)
+#define MPI_CFG_IOMODE GENMASK_ULL(35, 34)
+#define MPI_CFG_TB100_EN BIT_ULL(49)
+
+#define MPI_DAT_DATA GENMASK_ULL(7, 0)
+
+#define MPI_STS_BUSY BIT_ULL(0)
+#define MPI_STS_MPI_INTR BIT_ULL(1)
+#define MPI_STS_RXNUM GENMASK_ULL(12, 8)
+
+#define MPI_TX_TOTNUM GENMASK_ULL(4, 0)
+#define MPI_TX_TXNUM GENMASK_ULL(12, 8)
+#define MPI_TX_LEAVECS BIT_ULL(16)
+#define MPI_TX_CSID GENMASK_ULL(21, 20)
+
+#define MPI_XMIT_TOTNUM GENMASK_ULL(10, 0)
+#define MPI_XMIT_TXNUM GENMASK_ULL(30, 20)
+#define MPI_XMIT_BUF_SEL BIT_ULL(59)
+#define MPI_XMIT_LEAVECS BIT_ULL(60)
+#define MPI_XMIT_CSID GENMASK_ULL(62, 61)
+
+/* Used on Octeon TX2 */
+void board_acquire_flash_arb(bool acquire);
+
+/* Local driver data structure */
+struct octeon_spi {
+ void __iomem *base; /* Register base address */
+ struct clk clk;
+ u32 clkdiv; /* Clock divisor for device speed */
+};
+
+static u64 octeon_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 octeon_spi *priv = dev_get_priv(bus);
+ u64 mpi_cfg;
+ uint max_speed = slave->max_hz;
+ bool cpha, cpol;
+
+ if (!max_speed)
+ max_speed = 12500000;
+ if (max_speed > OCTEON_SPI_MAX_CLOCK_HZ)
+ max_speed = OCTEON_SPI_MAX_CLOCK_HZ;
+
+ debug("\n slave params %d %d %d\n", slave->cs,
+ slave->max_hz, slave->mode);
+ cpha = !!(slave->mode & SPI_CPHA);
+ cpol = !!(slave->mode & SPI_CPOL);
+
+ mpi_cfg = FIELD_PREP(MPI_CFG_CLKDIV, priv->clkdiv & 0x1fff) |
+ FIELD_PREP(MPI_CFG_CSHI, !!(slave->mode & SPI_CS_HIGH)) |
+ FIELD_PREP(MPI_CFG_LSBFIRST, !!(slave->mode & SPI_LSB_FIRST)) |
+ FIELD_PREP(MPI_CFG_WIREOR, !!(slave->mode & SPI_3WIRE)) |
+ FIELD_PREP(MPI_CFG_IDLELO, cpha != cpol) |
+ FIELD_PREP(MPI_CFG_CSLATE, cpha) |
+ MPI_CFG_CSENA0 | MPI_CFG_CSENA1 |
+ MPI_CFG_CSENA2 | MPI_CFG_CSENA1 |
+ MPI_CFG_ENABLE;
+
+ debug("\n mpi_cfg %llx\n", mpi_cfg);
+ return mpi_cfg;
+}
+
+/**
+ * Wait until the SPI bus is ready
+ *
+ * @param dev SPI device to wait for
+ */
+static void octeon_spi_wait_ready(struct udevice *dev)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct octeon_spi *priv = dev_get_priv(bus);
+ void *base = priv->base;
+ u64 mpi_sts;
+
+ do {
+ mpi_sts = readq(base + MPI_STS);
+ WATCHDOG_RESET();
+ } while (mpi_sts & MPI_STS_BUSY);
+
+ debug("%s(%s)\n", __func__, dev->name);
+}
+
+/**
+ * Claim the bus for a slave device
+ *
+ * @param dev SPI bus
+ *
+ * @return 0 for success, -EINVAL if chip select is invalid
+ */
+static int octeon_spi_claim_bus(struct udevice *dev)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct octeon_spi *priv = dev_get_priv(bus);
+ void *base = priv->base;
+ u64 mpi_cfg;
+
+ debug("\n\n%s(%s)\n", __func__, dev->name);
+ if (!OCTEON_SPI_CS_VALID(spi_chip_select(dev)))
+ return -EINVAL;
+
+ if (IS_ENABLED(CONFIG_ARCH_OCTEONTX2))
+ board_acquire_flash_arb(true);
+
+ mpi_cfg = readq(base + MPI_CFG);
+ mpi_cfg &= ~MPI_CFG_TRITX;
+ mpi_cfg |= MPI_CFG_ENABLE;
+ writeq(mpi_cfg, base + MPI_CFG);
+ mpi_cfg = readq(base + MPI_CFG);
+ udelay(5); /** Wait for bus to settle */
+
+ 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 octeon_spi_release_bus(struct udevice *dev)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct octeon_spi *priv = dev_get_priv(bus);
+ void *base = priv->base;
+ u64 mpi_cfg;
+
+ debug("%s(%s)\n\n", __func__, dev->name);
+ if (!OCTEON_SPI_CS_VALID(spi_chip_select(dev)))
+ return -EINVAL;
+
+ if (IS_ENABLED(CONFIG_ARCH_OCTEONTX2))
+ board_acquire_flash_arb(false);
+
+ mpi_cfg = readq(base + MPI_CFG);
+ mpi_cfg &= ~MPI_CFG_ENABLE;
+ writeq(mpi_cfg, base + MPI_CFG);
+ mpi_cfg = readq(base + MPI_CFG);
+ udelay(1);
+
+ return 0;
+}
+
+static int octeon_spi_xfer(struct udevice *dev, unsigned int bitlen,
+ const void *dout, void *din, unsigned long flags)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct octeon_spi *priv = dev_get_priv(bus);
+ void *base = priv->base;
+ u64 mpi_tx;
+ u64 mpi_cfg;
+ u64 wide_dat = 0;
+ int len = bitlen / 8;
+ int i;
+ const u8 *tx_data = dout;
+ u8 *rx_data = din;
+ int cs = spi_chip_select(dev);
+
+ if (!OCTEON_SPI_CS_VALID(cs))
+ return -EINVAL;
+
+ debug("\n %s(%s, %u, %p, %p, 0x%lx), cs: %d\n",
+ __func__, dev->name, bitlen, dout, din, flags, cs);
+
+ mpi_cfg = octeon_spi_set_mpicfg(dev);
+ if (mpi_cfg != readq(base + MPI_CFG)) {
+ writeq(mpi_cfg, base + MPI_CFG);
+ mpi_cfg = readq(base + MPI_CFG);
+ udelay(10);
+ }
+
+ debug("\n mpi_cfg upd %llx\n", 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((u64 *)tx_data);
+ debug(" tx: %016llx \t", (unsigned long long)wide_dat);
+ tx_data += 8;
+ writeq(wide_dat, base + MPI_WIDE_DAT);
+ }
+
+ mpi_tx = FIELD_PREP(MPI_TX_CSID, cs) |
+ FIELD_PREP(MPI_TX_LEAVECS, 1) |
+ FIELD_PREP(MPI_TX_TXNUM, tx_data ? 8 : 0) |
+ FIELD_PREP(MPI_TX_TOTNUM, 8);
+ writeq(mpi_tx, base + MPI_TX);
+
+ octeon_spi_wait_ready(dev);
+
+ debug("\n ");
+
+ if (rx_data) {
+ wide_dat = readq(base + MPI_WIDE_DAT);
+ debug(" rx: %016llx\t", (unsigned long long)wide_dat);
+ *(u64 *)rx_data = wide_dat;
+ rx_data += 8;
+ }
+ len -= 8;
+ }
+
+ debug("\n ");
+
+ /* 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++, base + MPI_DAT(i));
+ }
+ }
+
+ mpi_tx = FIELD_PREP(MPI_TX_CSID, cs) |
+ FIELD_PREP(MPI_TX_LEAVECS, !(flags & SPI_XFER_END)) |
+ FIELD_PREP(MPI_TX_TXNUM, tx_data ? len : 0) |
+ FIELD_PREP(MPI_TX_TOTNUM, len);
+ writeq(mpi_tx, base + MPI_TX);
+
+ octeon_spi_wait_ready(dev);
+
+ debug("\n ");
+
+ if (rx_data) {
+ for (i = 0; i < len; i++) {
+ *rx_data = readq(base + MPI_DAT(i)) & 0xff;
+ debug(" rx: %02x\n", *rx_data);
+ rx_data++;
+ }
+ }
+
+ return 0;
+}
+
+static int octeontx2_spi_xfer(struct udevice *dev, unsigned int bitlen,
+ const void *dout, void *din, unsigned long flags)
+{
+ struct udevice *bus = dev_get_parent(dev);
+ struct octeon_spi *priv = dev_get_priv(bus);
+ void *base = priv->base;
+ u64 mpi_xmit;
+ u64 mpi_cfg;
+ u64 wide_dat = 0;
+ int len = bitlen / 8;
+ int rem;
+ int i;
+ const u8 *tx_data = dout;
+ u8 *rx_data = din;
+ int cs = spi_chip_select(dev);
+
+ if (!OCTEON_SPI_CS_VALID(cs))
+ return -EINVAL;
+
+ debug("\n %s(%s, %u, %p, %p, 0x%lx), cs: %d\n",
+ __func__, dev->name, bitlen, dout, din, flags, cs);
+
+ mpi_cfg = octeon_spi_set_mpicfg(dev);
+
+ mpi_cfg |= MPI_CFG_TRITX | MPI_CFG_LEGACY_DIS | MPI_CFG_CS_STICKY |
+ MPI_CFG_TB100_EN;