diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 1494c91763..62d9676550 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -481,4 +481,12 @@ config MXC_SPI
 	  Enable the MXC SPI controller driver. This driver can be used
 	  on various i.MX SoCs such as i.MX31/35/51/6/7.
 
+config SYNQUACER_SPI
+	bool "Socionext SynQuacer HS-SPI driver"
+	depends on ARCH_SYNQUACER
+	help
+	  Enable the Socionext HS-SPI driver for SynQuacer. This driver can
+	  be used to access the SPI interface and SPI NOR flash on platforms
+	  embedding this HS-SPI IP core.
+
 endif # menu "SPI Support"
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index cfe4fae1d4..98c95323d1 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_EXYNOS_SPI) += exynos_spi.o
 obj-$(CONFIG_FSL_DSPI) += fsl_dspi.o
 obj-$(CONFIG_FSL_ESPI) += fsl_espi.o
 obj-$(CONFIG_FSL_QSPI) += fsl_qspi.o
+obj-$(CONFIG_SYNQUACER_SPI) += spi-synquacer.o
 obj-$(CONFIG_ICH_SPI) +=  ich.o
 obj-$(CONFIG_KIRKWOOD_SPI) += kirkwood_spi.o
 obj-$(CONFIG_MESON_SPIFC) += meson_spifc.o
diff --git a/drivers/spi/spi-synquacer.c b/drivers/spi/spi-synquacer.c
new file mode 100644
index 0000000000..ce558c4bc0
--- /dev/null
+++ b/drivers/spi/spi-synquacer.c
@@ -0,0 +1,491 @@
+// 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,
+};
