
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@denx.de wrote:
From: Suneel Garapati sgarapati@marvell.com
Adds support for SPI controllers found on Octeon II/III and Octeon TX TX2 SoC platforms.
Signed-off-by: Aaron Williams awilliams@marvell.com Signed-off-by: Suneel Garapati sgarapati@marvell.com Signed-off-by: Stefan Roese sr@denx.de Cc: Daniel Schwierzeck daniel.schwierzeck@gmail.com Cc: Aaron Williams awilliams@marvell.com Cc: Chandrakala Chavva cchavva@marvell.com Cc: Jagan Teki jagan@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.
- */
+#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