From patchwork Tue Oct 29 21:08:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Suneel Garapati X-Patchwork-Id: 1186319 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (no SPF record) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="uPs4d4cE"; dkim-atps=neutral Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 472kz426Lrz9sCJ for ; Wed, 30 Oct 2019 08:21:04 +1100 (AEDT) Received: by lists.denx.de (Postfix, from userid 105) id B363AC21DD7; Tue, 29 Oct 2019 21:13:09 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=-0.0 required=5.0 tests=FREEMAIL_FROM, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, T_DKIM_INVALID autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 7333BC21BE5; Tue, 29 Oct 2019 21:09:34 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 41883C21DA6; Tue, 29 Oct 2019 21:08:59 +0000 (UTC) Received: from mail-pf1-f196.google.com (mail-pf1-f196.google.com [209.85.210.196]) by lists.denx.de (Postfix) with ESMTPS id 983A6C21DD3 for ; Tue, 29 Oct 2019 21:08:54 +0000 (UTC) Received: by mail-pf1-f196.google.com with SMTP id r4so4852527pfl.7 for ; Tue, 29 Oct 2019 14:08:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=s0z6idmdPOcoajzfynlq5jVufrlQ4qDz7B6pdU2NoyQ=; b=uPs4d4cEvByETFMpnv9db7M0Gw03/cv4Wsm+403R9dGa7RLXuEqEIocs7HVZzPtJgG 2HUeU+PfQ/bwyIcHSJXPYy0HfjxQ0lmvTM/wQRRsTjTbu6SGBWULktm+watHWJsJ5cdQ 4uILEXaHXYHB8g6Fg4r7zYGTULnJEfJme4Phxk98a/WLeLvRourCVk4uW1cNK2dcQURc xzGOhNVWqzVOgYDDWLuCaEzRyYMLaGIPB2sqxBtO2f55quYgwNZvap2uDmSyRaQshMvM GiEytWsdVqR5znOg/PfyQjAKCUatxqmSldBIFVA+IsL94ISjm8QqLKy84jpEXj9+/Gp3 Eieg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=s0z6idmdPOcoajzfynlq5jVufrlQ4qDz7B6pdU2NoyQ=; b=skOrEU5L0iDjmUY7bhbinPooL97csRwtaIiMn8/dRbXrhHFqmR+q756jbEGBmCAR5y hQGmySCQTczO4jc3+4KuGdMG6WvoRuJv3sjEUTUbG0O/d96czRBZD23URYA/jAnCI0kk Z8uI63+2qERqPJUHmTqx1zkIT2mGggBIXnIaXjGdycRYyDhhuHNT9hI7T/0Hk0bLNIxg xS07oldsVdh2dBJ+TV9HCwLcPsjb5G+LWezsg+82iDhLv9AGauyk8RFuJtuvH4YJYHME Q/jPKnfZliavliopKy2QZd9KIW0orZWjQ2bnmx1r50B+IHuJ9u/yl9AkwL8rAqf0biB5 wAsA== X-Gm-Message-State: APjAAAWbt8X+PIx8sQ9nZNhz4fbd1KIj5jCvV9ttEpmf+KaDOb0VFgnD 5HSXcHli2aU6Bk3DMvQCpGegh+N+ X-Google-Smtp-Source: APXvYqyumzLjb4vs29sXJymViZknnZqIXKT4GS80rLrRnbP1qtdhJaMVDakqG2tJVjZLhI6NzQveAg== X-Received: by 2002:a17:90a:77c7:: with SMTP id e7mr9205413pjs.133.1572383332609; Tue, 29 Oct 2019 14:08:52 -0700 (PDT) Received: from suneel.hsd1.ca.comcast.net ([2601:641:4000:c9c0:7044:5eef:7096:2413]) by smtp.gmail.com with ESMTPSA id q3sm131160pgj.54.2019.10.29.14.08.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 29 Oct 2019 14:08:52 -0700 (PDT) From: Suneel Garapati To: u-boot Date: Tue, 29 Oct 2019 14:08:13 -0700 Message-Id: <20191029210821.1954-22-suneelglinux@gmail.com> X-Mailer: git-send-email 2.23.0 In-Reply-To: <20191029210821.1954-1-suneelglinux@gmail.com> References: <20191029210821.1954-1-suneelglinux@gmail.com> MIME-Version: 1.0 Cc: Tom Rini , Matthias Brugger , Joe Hershberger , Prasun Kapoor , Maen Suleiman , Chandrakala Chavva , Zi Shen Lim , Stefan Roese , Chris Packham Subject: [U-Boot] [RFC PATCH 21/29] drivers: spi: add SPI controller driver for OcteonTX X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" From: Suneel Garapati Adds support for SPI controllers found on OcteonTX or OcteonTX2 SoC platforms. Signed-off-by: Aaron Williams Signed-off-by: Suneel Garapati --- drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/octeontx_spi.c | 750 +++++++++++++++++++++++++++++++++++++ 3 files changed, 757 insertions(+) create mode 100644 drivers/spi/octeontx_spi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b8ca2bdedd..e521f10015 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -175,6 +175,12 @@ config MVEBU_A3700_SPI used to access the SPI NOR flash on platforms embedding this Marvell IP core. +config OCTEONTX_SPI + bool "OcteonTX SPI driver" + help + Enable the OcteonTX SPI driver. This driver can be used to + access the SPI NOR flash on OcteonTX or OcteonTX2 SoC platforms. + config PIC32_SPI bool "Microchip PIC32 SPI driver" depends on MACH_PIC32 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index ae4f2958f8..296eb11da8 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_BCM63XX_HSSPI) += bcm63xx_hsspi.o obj-$(CONFIG_BCM63XX_SPI) += bcm63xx_spi.o obj-$(CONFIG_BCMSTB_SPI) += bcmstb_spi.o obj-$(CONFIG_CADENCE_QSPI) += cadence_qspi.o cadence_qspi_apb.o +obj-$(CONFIG_OCTEONTX_SPI) += octeontx_spi.o obj-$(CONFIG_CF_SPI) += cf_spi.o obj-$(CONFIG_DAVINCI_SPI) += davinci_spi.o obj-$(CONFIG_DESIGNWARE_SPI) += designware_spi.o diff --git a/drivers/spi/octeontx_spi.c b/drivers/spi/octeontx_spi.c new file mode 100644 index 0000000000..1fb4989349 --- /dev/null +++ b/drivers/spi/octeontx_spi.c @@ -0,0 +1,750 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell International Ltd. + * + * https://spdx.org/licenses + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_ARCH_OCTEONTX2) +#include +#include +#define USE_TBI_CLK +#endif + +#define OCTEONTX_SPI_MAX_BYTES 9 +#define OCTEONTX_SPI_MAX_CLOCK_HZ 50000000 + +#define OCTEONTX2_TBI_CLK 100000000 + +#define OCTEONTX_SPI_NUM_CS 4 + +#define OCTEONTX_SPI_CS_VALID(cs) ((cs) < OCTEONTX_SPI_NUM_CS) + +#define MPI_CFG 0x1000 +#define MPI_STS 0x1008 +#define MPI_TX 0x1010 +#define MPI_XMIT 0x1018 +#define MPI_WIDE_DAT 0x1040 +#define MPI_IO_CTL 0x1048 +#define MPI_DAT(X) (0x1080 + ((X) << 3)) +#define MPI_WIDE_BUF(X) (0x1800 + ((X) << 3)) +#define MPI_CYA_CFG 0x2000 +#define MPI_CLKEN 0x2080 + +union mpi_cfg { + u64 u; + struct mpi_cfg_s { + /** MPI/SPI enable, 0 = pins are tristated, 1 = pins driven */ + u64 enable :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. + */ + u64 idlelo :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. + */ + u64 clk_cont :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. + */ + u64 wireor :1; + /** 0 = shift MSB first, 1 = shift LSB first */ + u64 lsbfirst :1; + u64 cs_sticky :1; /** cs sticky bit */ + u64 rsvd :1; /** Reserved */ + /** + * SPI_CSn_L high. 1 = SPI_CSn_L is asserted high, + * 0 = SPI_CS_n asserted low. + */ + u64 cshi :1; + /** + * When set, guarantees idle coprocessor-clock cycles between + * commands. + */ + u64 idleclks :2; + /** + * Tristate TX. Set to 1 to tristate SPI_DO when not + * transmitting. + */ + u64 tritx :1; + /** + * 0 = SPI_CSn asserts 1/2 coprocessor-clock cycle before + * transaction + * 1 = SPI_CSn asserts coincident with transaction + */ + u64 cslate :1; + u64 csena0 :1; /** cs enable 0 */ + u64 csena1 :1; /** cs enable 1 */ + u64 csena2 :1; /** cs enable 2 */ + u64 csena3 :1; /** cs enable 3 */ + u64 clkdiv :13; /** clock divisor */ + u64 rsvd1 :2; + u64 legacy_dis :1; /** Disable legacy mode */ + u64 rsvd2 :2; + /** + * I/O Mode (legacy_dis must be 1): + * 0x0 One-lane unidirectional mode. + * 0x1 One-lane bidirectional mode. + * 0x2 Two-lane bidirectional mode. + * 0x3 Four-lane bidirectional mode. + */ + u64 iomode :2; + u64 rsvd3 :8; + /** + * Enable ESPI mode per slave. Each bit corresponds to each + * of the four possible CS's. + * If 0, CRC hardware is disabled, turn-around time is the + * default for SPI and no special parsing in hardware. + * If 1, CRC hardware is enabled and the hardware will + * automatically calculate the CRC for one transaction and then + * apply it to the end of the transaction and then check the + * CRC on the response and if there is an error the + * MPI(0..1)_STS[CRC_ERR] bit will be set. The turn around + * time (TAR in the ESPI spec) is set to two cicles and parsing + * for special state is enabled. + */ + u64 cs_espi_en :4; + u64 rsvd4 :1; + /** + * SPI 100MHz clock enable. + * + * 0 Use the system clock (sclk) as the base frequency. + * This provides higher granularity but may require + * changing clkdiv if the system clock is changed. + * 1 Use a 100MHz clock as the base frequency. This is + * the reset value to enable the boot frequency to be + * sclk agnostic. + */ + u64 tb100_en :1; + u64 rsvd5 :14; + } s; + /* struct mpi_cfg_s cn; */ +}; + +/** + * Register (NCB) mpi_dat# + * + * MPI/SPI Data Registers + */ +union mpi_dat { + u64 u; + struct mpi_datx_s { + u64 data :8; /** Data to transmit/receive. */ + u64 reserved_8_63 :56; + } s; + /* struct mpi_datx_s cn; */ +}; + +/** + * Register (NCB) mpi_sts + * + * MPI/SPI STS Register + */ +union mpi_sts { + u64 u; + struct mpi_sts_s { + u64 busy :1; /** SPI engine busy */ + u64 mpi_intr :1; /** Transaction done int */ + u64 reserved_2_7:6; + u64 rxnum :5; /** ESPI number of rx bytes */ + u64 rsvd :6; + u64 crc_err :1; /** CRC error from ESPI */ + u64 rsvd1 :5; + u64 crc :8; /** ESPI CRC received */ + u64 reserved_40_63 :24; + } s; + /* struct mpi_sts_s cn; */ +}; + +/** + * Register (NCB) mpi_tx + * + * MPI/SPI Transmit Register + */ +union mpi_tx { + u64 u; + struct mpi_tx_s { + u64 totnum :5; /** Total bytes to shift */ + u64 rsvd :3; + u64 txnum :5; /** Number of words to tx */ + u64 rsvd1 :3; + u64 leavecs :1; /** Leave CS asserted */ + u64 rsvd2 :3; + u64 csid :2; /** Which CS to assert */ + u64 rsvd3 :42; + } s; + /* struct mpi_tx_s cn; */ +}; + +#if !defined(CONFIG_ARCH_OCTEONTX) +/** + * Register (NCB) mpi#_xmit + * + * MPI/SPI Transmit Register + */ +union mpi_xmit { + u64 u; + struct mpi_xmit_s { + /** Total number of bytes for transmit and receive. */ + u64 totnum : 11; + u64 reserved_11_19 : 9; + /** Number of bytes to transmit (max 1152) */ + u64 txnum : 11; + u64 reserved_31_59 : 29; + u64 leavecs : 1; /** Leave SPI_CSn_L asserted */ + u64 csid : 2; /** Which CS to assert */ + u64 reserved_63 : 1; + } s; +}; +#endif + +/** Local driver data structure */ +struct octeontx_spi { + void *baseaddr; /** Register base address */ + u32 clkdiv; /** Clock divisor for device speed */ + bool is_otx2; /** Gen 2 SoC */ +}; + +static union mpi_cfg octeontx_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 octeontx_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 > OCTEONTX_SPI_MAX_CLOCK_HZ) + max_speed = OCTEONTX_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.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; + + debug("\n mpi_cfg %llx\n", mpi_cfg.u); + return mpi_cfg; +} + +/** + * Wait until the SPI bus is ready + * + * @param dev SPI device to wait for + */ +static void octeontx_spi_wait_ready(struct udevice *dev) +{ + struct udevice *bus = dev_get_parent(dev); + struct octeontx_spi *priv = dev_get_priv(bus); + void *baseaddr = priv->baseaddr; + union mpi_sts mpi_sts; + + do { + mpi_sts.u = readq(baseaddr + MPI_STS); + WATCHDOG_RESET(); + } while (mpi_sts.s.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 octeontx_spi_claim_bus(struct udevice *dev) +{ + struct udevice *bus = dev_get_parent(dev); + struct octeontx_spi *priv = dev_get_priv(bus); + void *baseaddr = priv->baseaddr; + union mpi_cfg mpi_cfg; + + debug("\n\n%s(%s)\n", __func__, dev->name); + if (!OCTEONTX_SPI_CS_VALID(spi_chip_select(dev))) + return -EINVAL; + +#if !defined(CONFIG_ARCH_OCTEONTX) + acquire_flash_arb(true); +#endif + + mpi_cfg.u = readq(baseaddr + MPI_CFG); + mpi_cfg.s.tritx = 0; + mpi_cfg.s.enable = 1; + writeq(mpi_cfg.u, baseaddr + 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 octeontx_spi_release_bus(struct udevice *dev) +{ + struct udevice *bus = dev_get_parent(dev); + struct octeontx_spi *priv = dev_get_priv(bus); + void *baseaddr = priv->baseaddr; + union mpi_cfg mpi_cfg; + + debug("%s(%s)\n\n", __func__, dev->name); + if (!OCTEONTX_SPI_CS_VALID(spi_chip_select(dev))) + return -EINVAL; + +#if !defined(CONFIG_ARCH_OCTEONTX) + acquire_flash_arb(false); +#endif + + mpi_cfg.u = readq(baseaddr + MPI_CFG); + mpi_cfg.s.enable = 0; + writeq(mpi_cfg.u, baseaddr + MPI_CFG); + udelay(1); + + return 0; +} + +#if defined(CONFIG_ARCH_OCTEONTX) +static int octeontx_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev_get_parent(dev); + struct octeontx_spi *priv = dev_get_priv(bus); + void *baseaddr = priv->baseaddr; + union mpi_tx mpi_tx; + union mpi_cfg 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 (!OCTEONTX_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 = octeontx_spi_set_mpicfg(dev); + + if (mpi_cfg.u != readq(baseaddr + MPI_CFG)) { + writeq(mpi_cfg.u, baseaddr + MPI_CFG); + udelay(10); + } + + debug("\n mpi_cfg upd %llx\n", mpi_cfg.u); + + /* + * 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, 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); + + octeontx_spi_wait_ready(dev); + + debug("\n "); + + if (rx_data) { + wide_dat = readq(baseaddr + 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++, 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); + + octeontx_spi_wait_ready(dev); + + debug("\n "); + + 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; +} + +#else + +static int octeontx_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct udevice *bus = dev_get_parent(dev); + struct octeontx_spi *priv = dev_get_priv(bus); + void *baseaddr = priv->baseaddr; + union mpi_xmit mpi_xmit; + union mpi_cfg 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 (!OCTEONTX_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 = octeontx_spi_set_mpicfg(dev); + + mpi_cfg.s.legacy_dis = 1; + mpi_cfg.s.cs_sticky = 1; +#ifdef USE_TBI_CLK + mpi_cfg.s.tb100_en = 1; +#endif + mpi_cfg.s.iomode = 0; + if (flags & (SPI_TX_DUAL | SPI_RX_DUAL)) + mpi_cfg.s.iomode = 2; + if (flags & (SPI_TX_QUAD | SPI_RX_QUAD)) + mpi_cfg.s.iomode = 3; + + if (mpi_cfg.u != readq(baseaddr + MPI_CFG)) { + writeq(mpi_cfg.u, baseaddr + MPI_CFG); + udelay(10); + } + + debug("\n mpi_cfg upd %llx\n\n", mpi_cfg.u); + + /* Start by writing or reading 1024 bytes at a time. */ + while (len > 1024) { + if (tx_data) { + /* 8 bytes per iteration */ + for (i = 0; i < 128; i++) { + wide_dat = get_unaligned((u64 *)tx_data); + debug(" tx: %016llx \t", + (unsigned long long)wide_dat); + if ((i % 4) == 3) + debug("\n"); + tx_data += 8; + writeq(wide_dat, baseaddr + MPI_WIDE_BUF(i)); + } + } + + mpi_xmit.u = 0; + mpi_xmit.s.csid = cs; + mpi_xmit.s.leavecs = 1; + mpi_xmit.s.txnum = tx_data ? 1024 : 0; + mpi_xmit.s.totnum = 1024; + writeq(mpi_xmit.u, baseaddr + MPI_XMIT); + + octeontx_spi_wait_ready(dev); + + debug("\n "); + + if (rx_data) { + /* 8 bytes per iteration */ + for (i = 0; i < 128; i++) { + wide_dat = readq(baseaddr + MPI_WIDE_BUF(i)); + debug(" rx: %016llx\t", + (unsigned long long)wide_dat); + if ((i % 4) == 3) + debug("\n"); + *(u64 *)rx_data = wide_dat; + rx_data += 8; + } + } + len -= 1024; + } + + if (tx_data) { + rem = len % 8; + /* 8 bytes per iteration */ + for (i = 0; i < len / 8; i++) { + wide_dat = get_unaligned((u64 *)tx_data); + debug(" tx: %016llx \t", + (unsigned long long)wide_dat); + if ((i % 4) == 3) + debug("\n"); + tx_data += 8; + writeq(wide_dat, baseaddr + MPI_WIDE_BUF(i)); + } + if (rem) { + memcpy(&wide_dat, tx_data, rem); + debug(" rtx: %016llx\t", wide_dat); + writeq(wide_dat, baseaddr + MPI_WIDE_BUF(i)); + } + } + + mpi_xmit.u = 0; + mpi_xmit.s.csid = cs; + mpi_xmit.s.leavecs = !(flags & SPI_XFER_END); + mpi_xmit.s.txnum = tx_data ? len : 0; + mpi_xmit.s.totnum = len; + writeq(mpi_xmit.u, baseaddr + MPI_XMIT); + + octeontx_spi_wait_ready(dev); + + debug("\n "); + + if (rx_data) { + rem = len % 8; + /* 8 bytes per iteration */ + for (i = 0; i < len / 8; i++) { + wide_dat = readq(baseaddr + MPI_WIDE_BUF(i)); + debug(" rx: %016llx\t", + (unsigned long long)wide_dat); + if ((i % 4) == 3) + debug("\n"); + *(u64 *)rx_data = wide_dat; + rx_data += 8; + } + if (rem) { + wide_dat = readq(baseaddr + MPI_WIDE_BUF(i)); + debug(" rrx: %016llx\t", + (unsigned long long)wide_dat); + memcpy(rx_data, &wide_dat, rem); + rx_data += rem; + } + } + + return 0; +} + +static bool octeontx_spi_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + /* For now, support only below combinations + * 1-1-1 + * 1-1-2 1-2-2 + * 1-1-4 1-4-4 + */ + if (op->cmd.buswidth != 1) + return false; + return true; +} + +static int octeontx_spi_exec_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + unsigned long flags = SPI_XFER_BEGIN; + const void *tx; + void *rx; + u8 buf[8]; + u8 *addr; + int i, temp, ret; + + if (op->cmd.buswidth != 1) + return -ENOTSUPP; + + /* Send CMD */ + i = 0; + buf[i++] = op->cmd.opcode; + + if (!op->data.nbytes && !op->addr.nbytes && !op->dummy.nbytes) + flags |= SPI_XFER_END; + + ret = octeontx_spi_xfer(slave->dev, (i * 8), (void *)&buf, NULL, + flags); + if (ret < 0) + return ret; + /* Send Address and dummy */ + if (op->addr.nbytes) { + addr = (u8 *)&op->addr.val; + for (temp = 0; temp < op->addr.nbytes; temp++) + buf[i++] = *(u8 *)(addr + op->addr.nbytes - 1 - temp); + for (temp = 0; temp < op->dummy.nbytes; temp++) + buf[i++] = 0xff; + if (op->addr.buswidth == 2) + flags |= SPI_RX_DUAL; + if (op->addr.buswidth == 4) + flags |= SPI_RX_QUAD; + + if (!op->data.nbytes) + flags |= SPI_XFER_END; + ret = octeontx_spi_xfer(slave->dev, (i - 1) * 8, + (void *)&buf[1], NULL, + flags); + if (ret < 0) + return ret; + } + if (!op->data.nbytes) + return 0; + + /* Send/Receive Data */ + flags |= SPI_XFER_END; + if (op->data.buswidth == 2) + flags |= SPI_RX_DUAL; + if (op->data.buswidth == 4) + flags |= SPI_RX_QUAD; + + rx = (op->data.dir == SPI_MEM_DATA_IN) ? op->data.buf.in : NULL; + tx = (op->data.dir == SPI_MEM_DATA_OUT) ? op->data.buf.out : NULL; + + ret = octeontx_spi_xfer(slave->dev, (op->data.nbytes * 8), tx, rx, + flags); + return ret; +} + +static const struct spi_controller_mem_ops octeontx_spi_mem_ops = { + .supports_op = octeontx_spi_supports_op, + .exec_op = octeontx_spi_exec_op, +}; + +#endif + +/** + * Set the speed of the SPI bus + * + * @param bus bus to set + * @param max_hz maximum speed supported + */ +static int octeontx_spi_set_speed(struct udevice *bus, uint max_hz) +{ + struct octeontx_spi *priv = dev_get_priv(bus); + u64 refclk = octeontx_get_io_clock(); + u32 calc_hz; + + debug("%s(%s, %u, %llu)\n", __func__, bus->name, max_hz, refclk); + + if (max_hz > OCTEONTX_SPI_MAX_CLOCK_HZ) + max_hz = OCTEONTX_SPI_MAX_CLOCK_HZ; + +#ifdef USE_TBI_CLK + refclk = OCTEONTX2_TBI_CLK; +#endif + priv->clkdiv = refclk / (2 * max_hz); + while (1) { + calc_hz = refclk / (2 * priv->clkdiv); + if (calc_hz <= max_hz) + break; + priv->clkdiv += 1; + } + if (priv->clkdiv > 8191) + return -1; + + debug("%s %d\n", __func__, priv->clkdiv); + + return 0; +} + +static int octeontx_spi_set_mode(struct udevice *bus, uint mode) +{ + /* We don't set it here */ + return 0; +} + +static int octeontx_pci_spi_probe(struct udevice *dev) +{ + struct octeontx_spi *priv = dev_get_priv(dev); + pci_dev_t bdf = dm_pci_get_bdf(dev); + + debug("SPI PCI device: %x\n", bdf); + dev->req_seq = PCI_FUNC(bdf); + priv->baseaddr = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, + PCI_REGION_MEM); + + debug("SPI bus %s %d at %p\n", dev->name, dev->seq, priv->baseaddr); + + return 0; +} + +static const struct dm_spi_ops octeontx_spi_ops = { + .claim_bus = octeontx_spi_claim_bus, + .release_bus = octeontx_spi_release_bus, + .xfer = octeontx_spi_xfer, + .set_speed = octeontx_spi_set_speed, + .set_mode = octeontx_spi_set_mode, +#if !defined(CONFIG_ARCH_OCTEONTX) + .mem_ops = &octeontx_spi_mem_ops, +#endif +}; + +static const struct udevice_id octeontx_spi_ids[] = { + { .compatible = "cavium,thunder-8890-spi" }, + { .compatible = "cavium,thunder-8190-spi" }, + { } +}; + +U_BOOT_DRIVER(octeontx_pci_spi) = { + .name = "spi_octeontx", + .id = UCLASS_SPI, + .of_match = octeontx_spi_ids, + .probe = octeontx_pci_spi_probe, + .priv_auto_alloc_size = sizeof(struct octeontx_spi), + .ops = &octeontx_spi_ops, +}; + +static struct pci_device_id octeontx_spi_supported[] = { + { PCI_VDEVICE(CAVIUM, 0xa00b) }, + { }, +}; + +U_BOOT_PCI_DEVICE(octeontx_pci_spi, octeontx_spi_supported);