Hi Suneel,

On 04.08.20 01:20, Suneel Garapati wrote:
Hi Stefan,


On Thu, Jul 30, 2020 at 4:58 AM Stefan Roese <[email protected]> wrote:

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;
For OcteonTX2, TB100_EN is set so a fixed 100MHz clock should be used
in spi_set_speed.

Thanks for the feedback. I'll send a fix, once this driver has reached
mainline - which should be shortly.

Thanks,
Stefan

Reply via email to