--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * spi-synquacer.c - Socionext Synquacer SPI driver
+ * Copyright 2021 Linaro Ltd.
+ * Copyright 2021 Socionext, Inc.
+ */
+
+#include <clk.h>
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <time.h>
+#include <dm/device_compat.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <spi.h>
+#include <wait_bit.h>
+
+#define MCTRL 0x0
+#define MEN 0
+#define CSEN 1
+#define IPCLK 3
+#define MES 4
+#define SYNCON 5
+
+#define PCC0 0x4
+#define PCC(n) (PCC0 + (n) * 4)
+#define RTM 3
+#define ACES 2
+#define SAFESYNC 16
+#define CPHA 0
+#define CPOL 1
+#define SSPOL 4
+#define SDIR 7
+#define SS2CD 5
+#define SENDIAN 8
+#define CDRS_SHIFT 9
+#define CDRS_MASK 0x7f
+
+#define TXF 0x14
+#define TXE 0x18
+#define TXC 0x1c
+#define RXF 0x20
+#define RXE 0x24
+#define RXC 0x28
+#define TFLETE 4
+#define RFMTE 5
+
+#define FAULTF 0x2c
+#define FAULTC 0x30
+
+#define DMCFG 0x34
+#define SSDC 1
+#define MSTARTEN 2
+
+#define DMSTART 0x38
+#define TRIGGER 0
+#define DMSTOP 8
+#define CS_MASK 3
+#define CS_SHIFT 16
+#define DATA_TXRX 0
+#define DATA_RX 1
+#define DATA_TX 2
+#define DATA_MASK 3
+#define DATA_SHIFT 26
+#define BUS_WIDTH 24
+
+#define DMBCC 0x3c
+#define DMSTATUS 0x40
+#define RX_DATA_MASK 0x1f
+#define RX_DATA_SHIFT 8
+#define TX_DATA_MASK 0x1f
+#define TX_DATA_SHIFT 16
+
+#define TXBITCNT 0x44
+
+#define FIFOCFG 0x4c
+#define BPW_MASK 0x3
+#define BPW_SHIFT 8
+#define RX_FLUSH 11
+#define TX_FLUSH 12
+#define RX_TRSHLD_MASK 0xf
+#define RX_TRSHLD_SHIFT 0
+#define TX_TRSHLD_MASK 0xf
+#define TX_TRSHLD_SHIFT 4
+
+#define TXFIFO 0x50
+#define RXFIFO 0x90
+#define MID 0xfc
+
+#define FIFO_DEPTH 16
+#define TX_TRSHLD 4
+#define RX_TRSHLD (FIFO_DEPTH - TX_TRSHLD)
+
+#define TXBIT 1
+#define RXBIT 2
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct synquacer_spi_plat {
+ void __iomem *base;
+ bool aces, rtm;
+};
+
+struct synquacer_spi_priv {
+ void __iomem *base;
+ bool aces, rtm;
+ int speed, cs, mode, rwflag;
+ void *rx_buf;
+ const void *tx_buf;
+ unsigned int tx_words, rx_words;
+};
+
+static void read_fifo(struct synquacer_spi_priv *priv)
+{
+ u32 len = readl(priv->base + DMSTATUS);
+ u8 *buf = priv->rx_buf;
+ int i;
+
+ len = (len >> RX_DATA_SHIFT) & RX_DATA_MASK;
+ len = min_t(unsigned int, len, priv->rx_words);
+
+ for (i = 0; i < len; i++)
+ *buf++ = readb(priv->base + RXFIFO);
+
+ priv->rx_buf = buf;
+ priv->rx_words -= len;
+}
+
+static void write_fifo(struct synquacer_spi_priv *priv)
+{
+ u32 len = readl(priv->base + DMSTATUS);
+ const u8 *buf = priv->tx_buf;
+ int i;
+
+ len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK;
+ len = min_t(unsigned int, FIFO_DEPTH - len, priv->tx_words);
+
+ for (i = 0; i < len; i++)
+ writeb(*buf++, priv->base + TXFIFO);
+
+ priv->tx_buf = buf;
+ priv->tx_words -= len;
+}
+
+static void synquacer_cs_set(struct synquacer_spi_priv *priv, bool active)
+{
+ u32 val;
+
+ val = readl(priv->base + DMSTART);
+ val &= ~(CS_MASK << CS_SHIFT);
+ val |= priv->cs << CS_SHIFT;
+
+ if (active) {
+ writel(val, priv->base + DMSTART);
+
+ val = readl(priv->base + DMSTART);
+ val &= ~BIT(DMSTOP);
+ writel(val, priv->base + DMSTART);
+ } else {
+ val |= BIT(DMSTOP);
+ writel(val, priv->base + DMSTART);
+
+ if (priv->rx_buf) {
+ u32 buf[16];
+
+ priv->rx_buf = buf;
+ priv->rx_words = 16;
+ read_fifo(priv);
+ }
+ }
+}
+
+static void synquacer_spi_config(struct udevice *dev, void *rx, const void *tx)
+{
+ struct udevice *bus = dev->parent;
+ struct synquacer_spi_priv *priv = dev_get_priv(bus);
+ struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+ u32 val, div, bus_width;
+ int rwflag;
+
+ rwflag = (rx ? 1 : 0) | (tx ? 2 : 0);
+
+ /* if nothing to do */
+ if (slave_plat->mode == priv->mode &&
+ rwflag == priv->rwflag &&
+ slave_plat->cs == priv->cs &&
+ slave_plat->max_hz == priv->speed)
+ return;
+
+ priv->rwflag = rwflag;
+ priv->cs = slave_plat->cs;
+ priv->mode = slave_plat->mode;
+ priv->speed = slave_plat->max_hz;
+
+ if (priv->mode & SPI_TX_BYTE)
+ bus_width = 1;
+ else if (priv->mode & SPI_TX_DUAL)
+ bus_width = 2;
+ else if (priv->mode & SPI_TX_QUAD)
+ bus_width = 4;
+ else if (priv->mode & SPI_TX_OCTAL)
+ bus_width = 8;
+
+ div = DIV_ROUND_UP(125000000, priv->speed);
+
+ val = readl(priv->base + PCC(priv->cs));
+ val &= ~BIT(RTM);
+ val &= ~BIT(ACES);
+ val &= ~BIT(SAFESYNC);
+ if ((priv->mode & (SPI_TX_DUAL | SPI_RX_DUAL)) && div < 3)
+ val |= BIT(SAFESYNC);
+ if ((priv->mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 6)
+ val |= BIT(SAFESYNC);
+
+ if (priv->mode & SPI_CPHA)
+ val |= BIT(CPHA);
+ else
+ val &= ~BIT(CPHA);
+
+ if (priv->mode & SPI_CPOL)
+ val |= BIT(CPOL);
+ else
+ val &= ~BIT(CPOL);
+
+ if (priv->mode & SPI_CS_HIGH)
+ val |= BIT(SSPOL);
+ else
+ val &= ~BIT(SSPOL);
+
+ if (priv->mode & SPI_LSB_FIRST)
+ val |= BIT(SDIR);
+ else
+ val &= ~BIT(SDIR);
+
+ if (priv->aces)
+ val |= BIT(ACES);
+
+ if (priv->rtm)
+ val |= BIT(RTM);
+
+ val |= (3 << SS2CD);
+ val |= BIT(SENDIAN);
+
+ val &= ~(CDRS_MASK << CDRS_SHIFT);
+ val |= ((div >> 1) << CDRS_SHIFT);
+
+ writel(val, priv->base + PCC(priv->cs));
+
+ val = readl(priv->base + FIFOCFG);
+ val &= ~(BPW_MASK << BPW_SHIFT);
+ val |= (0 << BPW_SHIFT);
+ writel(val, priv->base + FIFOCFG);
+
+ val = readl(priv->base + DMSTART);
+ val &= ~(DATA_MASK << DATA_SHIFT);
+
+ if (tx && rx)
+ val |= (DATA_TXRX << DATA_SHIFT);
+ else if (rx)
+ val |= (DATA_RX << DATA_SHIFT);
+ else
+ val |= (DATA_TX << DATA_SHIFT);
+
+ val &= ~(3 << BUS_WIDTH);
+ val |= ((bus_width >> 1) << BUS_WIDTH);
+ writel(val, priv->base + DMSTART);
+}
+
+static int synquacer_spi_xfer(struct udevice *dev, unsigned int bitlen,
+ const void *tx_buf, void *rx_buf,
+ unsigned long flags)
+{
+ struct udevice *bus = dev->parent;
+ struct synquacer_spi_priv *priv = dev_get_priv(bus);
+ u32 val, words, busy;
+
+ val = readl(priv->base + FIFOCFG);
+ val |= (1 << RX_FLUSH);
+ val |= (1 << TX_FLUSH);
+ writel(val, priv->base + FIFOCFG);
+
+ synquacer_spi_config(dev, rx_buf, tx_buf);
+
+ priv->tx_buf = tx_buf;
+ priv->rx_buf = rx_buf;
+
+ words = bitlen / 8;
+
+ if (tx_buf) {
+ busy |= BIT(TXBIT);
+ priv->tx_words = words;
+ } else {
+ busy &= ~BIT(TXBIT);
+ priv->tx_words = 0;
+ }
+
+ if (rx_buf) {
+ busy |= BIT(RXBIT);
+ priv->rx_words = words;
+ } else {
+ busy &= ~BIT(RXBIT);
+ priv->rx_words = 0;
+ }
+
+ if (flags & SPI_XFER_BEGIN)
+ synquacer_cs_set(priv, true);
+
+ if (tx_buf)
+ write_fifo(priv);
+
+ if (rx_buf) {
+ val = readl(priv->base + FIFOCFG);
+ val &= ~(RX_TRSHLD_MASK << RX_TRSHLD_SHIFT);
+ val |= ((priv->rx_words > FIFO_DEPTH ?
+ RX_TRSHLD : priv->rx_words) << RX_TRSHLD_SHIFT);
+ writel(val, priv->base + FIFOCFG);
+ }
+
+ writel(~0, priv->base + TXC);
+ writel(~0, priv->base + RXC);
+
+ /* Trigger */
+ val = readl(priv->base + DMSTART);
+ val |= BIT(TRIGGER);
+ writel(val, priv->base + DMSTART);
+
+ while (busy & (BIT(RXBIT) | BIT(TXBIT))) {
+ if (priv->rx_words)
+ read_fifo(priv);
+ else
+ busy &= ~BIT(RXBIT);
+
+ if (priv->tx_words) {
+ write_fifo(priv);
+ } else {
+ u32 len;
+
+ do { /* wait for shifter to empty out */
+ cpu_relax();
+ len = readl(priv->base + DMSTATUS);
+ len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK;
+ } while (tx_buf && len);
+ busy &= ~BIT(TXBIT);
+ }
+ }
+
+ if (flags & SPI_XFER_END)
+ synquacer_cs_set(priv, false);
+
+ return 0;
+}
+
+static int synquacer_spi_set_speed(struct udevice *bus, uint speed)
+{
+ return 0;
+}
+
+static int synquacer_spi_set_mode(struct udevice *bus, uint mode)
+{
+ return 0;
+}
+
+static int synquacer_spi_claim_bus(struct udevice *dev)
+{
+ return 0;
+}
+
+static int synquacer_spi_release_bus(struct udevice *dev)
+{
+ return 0;
+}
+
+static void synquacer_spi_disable_module(struct synquacer_spi_priv *priv)
+{
+ writel(0, priv->base + MCTRL);
+ while (readl(priv->base + MCTRL) & BIT(MES))
+ cpu_relax();
+}
+
+static void synquacer_spi_init(struct synquacer_spi_priv *priv)
+{
+ u32 val;
+
+ synquacer_spi_disable_module(priv);
+
+ writel(0, priv->base + TXE);
+ writel(0, priv->base + RXE);
+ val = readl(priv->base + TXF);
+ writel(val, priv->base + TXC);
+ val = readl(priv->base + RXF);
+ writel(val, priv->base + RXC);
+ val = readl(priv->base + FAULTF);
+ writel(val, priv->base + FAULTC);
+
+ val = readl(priv->base + DMCFG);
+ val &= ~BIT(SSDC);
+ val &= ~BIT(MSTARTEN);
+ writel(val, priv->base + DMCFG);
+
+ /* Enable module with direct mode */
+ val = readl(priv->base + MCTRL);
+ val &= ~BIT(IPCLK);
+ val &= ~BIT(CSEN);
+ val |= BIT(MEN);
+ val |= BIT(SYNCON);
+ writel(val, priv->base + MCTRL);
+}
+
+static void synquacer_spi_exit(struct synquacer_spi_priv *priv)
+{
+ u32 val;
+
+ synquacer_spi_disable_module(priv);
+
+ /* Enable module with command sequence mode */
+ val = readl(priv->base + MCTRL);
+ val &= ~BIT(IPCLK);
+ val |= BIT(CSEN);
+ val |= BIT(MEN);
+ val |= BIT(SYNCON);
+ writel(val, priv->base + MCTRL);
+
+ while (!(readl(priv->base + MCTRL) & BIT(MES)))
+ cpu_relax();
+}
+
+static int synquacer_spi_probe(struct udevice *bus)
+{
+ struct synquacer_spi_plat *plat = dev_get_plat(bus);
+ struct synquacer_spi_priv *priv = dev_get_priv(bus);
+
+ priv->base = plat->base;
+ priv->aces = plat->aces;
+ priv->rtm = plat->rtm;
+
+ synquacer_spi_init(priv);
+ return 0;
+}
+
+static int synquacer_spi_remove(struct udevice *bus)
+{
+ struct synquacer_spi_priv *priv = dev_get_priv(bus);
+
+ synquacer_spi_exit(priv);
+ return 0;
+}
+
+static int synquacer_spi_of_to_plat(struct udevice *bus)
+{
+ struct synquacer_spi_plat *plat = dev_get_plat(bus);
+ struct clk clk;
+
+ plat->base = dev_read_addr_ptr(bus);
+
+ plat->aces = dev_read_bool(bus, "socionext,set-aces");
+ plat->rtm = dev_read_bool(bus, "socionext,use-rtm");
+
+ clk_get_by_name(bus, "iHCLK", &clk);
+ clk_enable(&clk);
+
+ return 0;
+}
+
+static const struct dm_spi_ops synquacer_spi_ops = {
+ .claim_bus = synquacer_spi_claim_bus,
+ .release_bus = synquacer_spi_release_bus,
+ .xfer = synquacer_spi_xfer,
+ .set_speed = synquacer_spi_set_speed,
+ .set_mode = synquacer_spi_set_mode,
+};
+
+static const struct udevice_id synquacer_spi_ids[] = {
+ { .compatible = "socionext,synquacer-spi" },
+ { /* Sentinel */ }
+};
+
+U_BOOT_DRIVER(synquacer_spi) = {
+ .name = "synquacer_spi",
+ .id = UCLASS_SPI,
+ .of_match = synquacer_spi_ids,
+ .ops = &synquacer_spi_ops,
+ .of_to_plat = synquacer_spi_of_to_plat,
+ .plat_auto = sizeof(struct synquacer_spi_plat),
+ .priv_auto = sizeof(struct synquacer_spi_priv),
+ .probe = synquacer_spi_probe,
+ .flags = DM_FLAG_OS_PREPARE,
+ .remove = synquacer_spi_remove,
+};