[PATCH v2 06/10] drivers: spi: Add SPI controller driver for Octeon

Stefan Roese sr at denx.de
Tue Aug 4 14:54:20 CEST 2020


Hi Suneel,

On 04.08.20 01:20, Suneel Garapati wrote:
> Hi Stefan,
> 
> 
> On Thu, Jul 30, 2020 at 4:58 AM Stefan Roese <sr at denx.de> wrote:
>>
>> From: Suneel Garapati <sgarapati at marvell.com>
>>
>> Adds support for SPI controllers found on Octeon II/III and Octeon TX
>> TX2 SoC platforms.
>>
>> Signed-off-by: Aaron Williams <awilliams at marvell.com>
>> Signed-off-by: Suneel Garapati <sgarapati at marvell.com>
>> Signed-off-by: Stefan Roese <sr at denx.de>
>> Cc: Daniel Schwierzeck <daniel.schwierzeck at gmail.com>
>> Cc: Aaron Williams <awilliams at marvell.com>
>> Cc: Chandrakala Chavva <cchavva at marvell.com>
>> Cc: Jagan Teki <jagan at amarulasolutions.com>
>>
>> ---
>>
>> 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


More information about the U-Boot mailing list