Signed-off-by: Tim Harvey <thar...@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 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot