diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 741b9140992a..3b1815b6a904 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -171,6 +171,19 @@ config SPI_BCM_QSPI
 	  based platforms. This driver works for both SPI master for spi-nor
 	  flash device as well as MSPI device.
 
+config SPI_BT1_SYS
+	tristate "Baikal-T1 System Boot SPI Controller"
+	depends on (MIPS_BAIKAL_T1 && OF) || COMPILE_TEST
+	imply SPI_MEM
+	help
+	  SPI driver for Baikal-T1 System Boot SPI Controller. It's based on
+	  the DW APB SSI IP block, but with very limited resources available:
+	  no IRQ, no DMA, a single CS, FIFO of just 8 bytes depth. Its primary
+	  usecase is to boot the system from an external SPI-nor flash. So
+	  aside with the poll-based normal SPI interface the memory operations
+	  and read-only direct mapping are supported by the driver from
+	  scratch.
+
 config SPI_BITBANG
 	tristate "Utilities for Bitbanging SPI masters"
 	help
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 28f601327f8c..3c5d40a298e4 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_SPI_BCM63XX)		+= spi-bcm63xx.o
 obj-$(CONFIG_SPI_BCM63XX_HSSPI)		+= spi-bcm63xx-hsspi.o
 obj-$(CONFIG_SPI_BCM_QSPI)		+= spi-iproc-qspi.o spi-brcmstb-qspi.o spi-bcm-qspi.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi-bitbang.o
+obj-$(CONFIG_SPI_BT1_SYS)		+= spi-bt1-sys.o
 obj-$(CONFIG_SPI_BUTTERFLY)		+= spi-butterfly.o
 obj-$(CONFIG_SPI_CADENCE)		+= spi-cadence.o
 obj-$(CONFIG_SPI_CLPS711X)		+= spi-clps711x.o
diff --git a/drivers/spi/spi-bt1-sys.c b/drivers/spi/spi-bt1-sys.c
new file mode 100644
index 000000000000..f7ab0eb4c247
--- /dev/null
+++ b/drivers/spi/spi-bt1-sys.c
@@ -0,0 +1,873 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
+ *
+ * Authors:
+ *   Ramil Zaripov <Ramil.Zaripov@baikalelectronics.ru>
+ *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
+ *
+ * Baikal-T1 System Boot SPI Controller driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/string.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/irqflags.h>
+#include <linux/preempt.h>
+#include <linux/debugfs.h>
+#include <linux/cpumask.h>
+#include <linux/seq_file.h>
+
+#include "spi-bt1-sys.h"
+
+static void bs_set_cfg(struct bt1_spi *bs, const struct bt1_spi_cfg *req)
+{
+	u32 ctrl0, ctrl1, baudr, min_sckdv, sckdv;
+
+	ctrl0 = FIELD_PREP(BC_SPI_CTRL0_DFS_MASK, req->dfs - 1) |
+		FIELD_PREP(BC_SPI_CTRL0_FRF_MASK, BC_SPI_CTRL0_FRF_SPI) |
+		(req->mode & SPI_CPHA ? BC_SPI_CTRL0_SCPH : 0) |
+		(req->mode & SPI_CPOL ? BC_SPI_CTRL0_SCPOL : 0) |
+		(req->mode & SPI_LOOP ? BC_SPI_CTRL0_SRL : 0) |
+		FIELD_PREP(BC_SPI_CTRL0_TMOD_MASK, req->tmode);
+	ctrl1 = FIELD_PREP(BC_SPI_CTRL1_NDF_MASK, req->ndf ? req->ndf - 1 : 0);
+
+	/*
+	 * There is a very tricky race condition hidden inside Baikal-T1 SoC
+	 * MMIO implementation. Simultaneous accesses to any registers via
+	 * the APB bus are serialized even if they are executed to different
+	 * addresses of even different controllers. This may cause
+	 * IO-operations delays (two, three, four, etc times the normal IO
+	 * operation latency), which in our case may lead to the data loss if
+	 * SPI-bus is fast enough and APB traffic is too heavy (either
+	 * generated by sibling CPU or DMA). So in order to fix this we have
+	 * no other way but to increase the SPI-bus clock divider lower limit.
+	 * Note this limitation isn't applicable to the SPI flash direct
+	 * mapping mode, since IO operations are performed by the Boot
+	 * Controller itself while CPU just reads the requested data.
+	 */
+	sckdv = DIV_ROUND_UP(bs->ref_freq, req->freq);
+	if (!req->dirmap && !req->cs_gpiod && num_possible_cpus() > 1)
+		min_sckdv = BC_SPI_MIN_SCKDV;
+	else
+		min_sckdv = 2;
+	sckdv = clamp_val(sckdv, min_sckdv, BC_SPI_BAUDR_SCKDV_MASK);
+	baudr = FIELD_PREP(BC_SPI_BAUDR_SCKDV_MASK, sckdv);
+
+	/*
+	 * Enable DW APB SSI controller registers access by means of the
+	 * RDA mode control bit. Only then we'll be able to read/write
+	 * from/to the controller memory mapped config-space.
+	 */
+	writel(BC_CSR_SPI_RDA, bs->regs + BC_CSR);
+
+	/*
+	 * Update the SPI controller configuration registers and enable
+	 * the requested slave for further communications.
+	 */
+	writel(0, bs->regs + BC_SPI_SSIENR);
+	writel(ctrl0, bs->regs + BC_SPI_CTRL0);
+	writel(ctrl1, bs->regs + BC_SPI_CTRL1);
+	writel(baudr, bs->regs + BC_SPI_BAUDR);
+	writel(BC_SPI_SSIENR_EN, bs->regs + BC_SPI_SSIENR);
+
+	bs->cfg = *req;
+
+	/*
+	 * DW APB SSI controller has a nasty peculiarity. It doesn't provide
+	 * a direct way to set and clear the native chip-select signal. Instead
+	 * the controller asserts the CS pin if Tx FIFO isn't empty and a
+	 * transmission is going on. When Tx FIFO doesn't have anything to be
+	 * pushed out the chip-select pin will be automatically de-asserted
+	 * back to the high level. Needless to say that the sudden CS
+	 * de-assertion in the middle of the message transfer most likely will
+	 * cause the data loss. So in order to make sure the executed transfers
+	 * are CS-atomic we have to either keep the controller FIFO non-empty
+	 * during the whole series of transfers or use GPIO-based chip-select
+	 * signal. The last case availability depends on the platform
+	 * configuration, while the former one can be achieved by making sure
+	 * nothing preempt the transfer execution context. It can be done by
+	 * disabling the local interrupts while the data being pushed and
+	 * pulled to/from the controller FIFOs. Note we don't have to worry
+	 * about this problem in case of SPI flash transparent access, because
+	 * IO operations are performed by the Boot Controller.
+	 */
+	writel(BIT(req->cs), bs->regs + BC_SPI_SER);
+	if (req->cs_gpiod) {
+		gpiod_set_value_cansleep(req->cs_gpiod,
+					 !!(bs->cfg.mode & SPI_CS_HIGH));
+	} else if (!req->dirmap) {
+		local_irq_save(bs->cfg.flags);
+		preempt_disable();
+	}
+
+	/*
+	 * If direct mapping is requested from now the DW APB SSI registers
+	 * are getting unavailable.
+	 */
+	if (req->dirmap)
+		writel(0, bs->regs + BC_CSR);
+}
+
+/*
+ * Note the next method is applicable only in non-direct mapping case. So
+ * we don't need to disable the transparent mode to access the DW APB SSI
+ * registers since they are already available after bs_set_cfg() actions.
+ */
+static void bs_update_cfg(struct bt1_spi *bs, struct spi_transfer *xfer)
+{
+	u32 ctrl0, baudr, min_sckdv, sckdv;
+
+	sckdv = DIV_ROUND_UP(bs->ref_freq, xfer->speed_hz);
+	if (!bs->cfg.cs_gpiod && num_possible_cpus() > 1)
+		min_sckdv = BC_SPI_MIN_SCKDV;
+	else
+		min_sckdv = 2;
+	sckdv = clamp_val(sckdv, min_sckdv, BC_SPI_BAUDR_SCKDV_MASK);
+	xfer->effective_speed_hz = bs->ref_freq / sckdv;
+
+	if (xfer->bits_per_word == bs->cfg.dfs &&
+	    xfer->speed_hz == bs->cfg.freq)
+		return;
+
+	ctrl0 = readl(bs->regs + BC_SPI_CTRL0) & ~BC_SPI_CTRL0_DFS_MASK;
+	ctrl0 |= FIELD_PREP(BC_SPI_CTRL0_DFS_MASK, xfer->bits_per_word - 1);
+	baudr = FIELD_PREP(BC_SPI_BAUDR_SCKDV_MASK, sckdv);
+
+	writel(0, bs->regs + BC_SPI_SSIENR);
+	writel(ctrl0, bs->regs + BC_SPI_CTRL0);
+	writel(baudr, bs->regs + BC_SPI_BAUDR);
+	writel(BC_SPI_SSIENR_EN, bs->regs + BC_SPI_SSIENR);
+
+	bs->cfg.dfs = xfer->bits_per_word;
+	bs->cfg.freq = xfer->speed_hz;
+}
+
+static void bs_clear_cfg(struct bt1_spi *bs)
+{
+	if (bs->cfg.cs_gpiod) {
+		gpiod_set_value_cansleep(bs->cfg.cs_gpiod,
+					 !(bs->cfg.mode & SPI_CS_HIGH));
+	} else if (!bs->cfg.dirmap) {
+		local_irq_restore(bs->cfg.flags);
+		preempt_enable();
+	}
+
+	/* Disable direct mapping if has been enabled in bs_set_cfg(). */
+	if (bs->cfg.dirmap)
+		writel(BC_CSR_SPI_RDA, bs->regs + BC_CSR);
+
+	writel(0, bs->regs + BC_SPI_SER);
+}
+
+static int bs_check_status(struct bt1_spi *bs)
+{
+	int ret = 0;
+	u32 data;
+
+	/* Check the bogus bits of the controller status register. */
+	data = readl(bs->regs + BC_SPI_RISR);
+	if (data & BC_SPI_RISR_RXO) {
+		dev_err(bs->dev, "RX FIFO overrun detected\n");
+		ret = -EIO;
+	}
+
+	if (data & BC_SPI_RISR_RXU) {
+		dev_err(bs->dev, "RX FIFO underrun detected\n");
+		ret = -EIO;
+	}
+
+	if (data & BC_SPI_RISR_TXO) {
+		dev_err(bs->dev, "TX FIFO overrun detected\n");
+		ret = -EIO;
+	}
+
+	readl(bs->regs + BC_SPI_ICR);
+
+	/*
+	 * Spin around the BUSY-state bit waiting for the controller to finish
+	 * all the requested IO-operations.
+	 */
+	do {
+		data = readl(bs->regs + BC_SPI_SR);
+	} while ((data & BC_SPI_SR_BUSY) || !(data & BC_SPI_SR_TFE));
+
+	return ret;
+}
+
+static int bs_pull_bytes(struct bt1_spi *bs, u8 *dst, unsigned int len)
+{
+	const u8 *end = dst + len;
+	const u8 *next = dst;
+	int retry = BC_SPI_RETRY;
+
+	/* Write dummy-data to the outbound FIFO to trigger the RO-transfer. */
+	if (bs->cfg.tmode == BC_SPI_CTRL0_TMOD_RO)
+		writel(GENMASK(bs->cfg.dfs - 1, 0), bs->regs + BC_SPI_DR);
+
+	while (dst < end) {
+		next += __raw_readl(bs->regs + BC_SPI_RXFLR);
+		if (dst < next) {
+			do {
+				*dst++ = __raw_readl(bs->regs + BC_SPI_DR);
+			} while (dst < next);
+			retry = BC_SPI_RETRY;
+		} else if (!retry--) {
+			return -EIO;
+		}
+	}
+
+	return 0;
+}
+
+static void bs_push_bytes(struct bt1_spi *bs, const u8 *src, unsigned int len)
+{
+	const u8 *end = src + len;
+	const u8 *next = src;
+
+	while (src < end) {
+		next += BC_SPI_FIFO_LEN - __raw_readl(bs->regs + BC_SPI_TXFLR);
+		if (src < next) {
+			if (next > end)
+				next = end;
+			do {
+				__raw_writel(*src++, bs->regs + BC_SPI_DR);
+			} while (src < next);
+		}
+	}
+}
+
+static void bs_pp_bytes(struct bt1_spi *bs, const u8 *src, u8 *dst,
+			unsigned int len)
+{
+	unsigned int txlen = len, rxlen = len;
+	u32 data, cnt;
+
+	/*
+	 * Note we have to keep the Tx FIFO only half full in order to prevent
+	 * the Rx FIFO overrun in case of the task rescheduling in the middle
+	 * of the loop.
+	 */
+	while (txlen || rxlen) {
+		cnt = BC_SPI_FIFO_LVL - __raw_readl(bs->regs + BC_SPI_TXFLR);
+		cnt = min(cnt, txlen);
+		txlen -= cnt;
+		while (cnt--) {
+			data = src ? *src++ : 0xFF;
+			__raw_writel(data, bs->regs + BC_SPI_DR);
+		}
+		cnt = __raw_readl(bs->regs + BC_SPI_RXFLR);
+		cnt = min(cnt, rxlen);
+		rxlen -= cnt;
+		while (cnt--) {
+			data = __raw_readl(bs->regs + BC_SPI_DR);
+			if (dst)
+				*dst++ = data;
+		}
+	}
+}
+
+static void bs_pp_words(struct bt1_spi *bs, const u16 *src, u16 *dst,
+			unsigned int len)
+{
+	unsigned int txlen = len, rxlen = len;
+	u32 data, cnt;
+
+	while (txlen || rxlen) {
+		cnt = BC_SPI_FIFO_LVL - __raw_readl(bs->regs + BC_SPI_TXFLR);
+		cnt = min(cnt, txlen);
+		txlen -= cnt;
+		while (cnt--) {
+			data = src ? *src++ : 0xFFFF;
+			__raw_writel(data, bs->regs + BC_SPI_DR);
+		}
+		cnt = __raw_readl(bs->regs + BC_SPI_RXFLR);
+		cnt = min(cnt, rxlen);
+		rxlen -= cnt;
+		while (cnt--) {
+			data = __raw_readl(bs->regs + BC_SPI_DR);
+			if (dst)
+				*dst++ = data;
+		}
+	}
+}
+
+static u8 *bs_create_cmd(struct bt1_spi *bs, const struct spi_mem_op *op,
+			 unsigned int *len)
+{
+	unsigned int i, j;
+	const u8 *data;
+	u8 *cmd;
+
+	/*
+	 * Calculate the total length of the EEPROM command transfer and
+	 * either use the pre-allocated buffer or create a temporary one.
+	 */
+	*len = 1 + op->addr.nbytes + op->dummy.nbytes;
+	if (op->data.dir == SPI_MEM_DATA_OUT)
+		*len += op->data.nbytes;
+
+	if (*len <= BC_SPI_BUF_SIZE) {
+		cmd = bs->buf;
+	} else {
+		cmd = kzalloc(*len, GFP_KERNEL);
+		if (!cmd)
+			return ERR_PTR(-ENOMEM);
+	}
+
+	/*
+	 * Collect the operation code, address and dummy bytes into the single
+	 * buffer.
+	 */
+	cmd[0] = op->cmd.opcode;
+	for (i = 1; i <= op->addr.nbytes; ++i) {
+		cmd[i] = op->addr.val >>
+			 (BITS_PER_BYTE * (op->addr.nbytes - i));
+	}
+	for (; i <= op->addr.nbytes + op->dummy.nbytes; ++i)
+		cmd[i] = 0xFF;
+
+	/*
+	 * If it's a transfer with data to be sent, also copy it into the
+	 * single buffer in order to speed up the push-data loop. Otherwise
+	 * in case of high-frequency SPI transfers we may have an occasional
+	 * output data loss.
+	 */
+	if (op->data.dir == SPI_MEM_DATA_OUT) {
+		data = op->data.buf.out;
+		for (j = 0; j < op->data.nbytes; ++i, ++j)
+			cmd[i] = data[j];
+	}
+
+	return cmd;
+}
+
+static void bs_clear_cmd(struct bt1_spi *bs, u8 *cmd)
+{
+	if (cmd != bs->buf)
+		kfree(cmd);
+}
+
+static int bs_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op)
+{
+	if (op->data.dir == SPI_MEM_DATA_IN) {
+		if (op->data.nbytes > (BC_SPI_CTRL1_NDF_MASK + 1))
+			op->data.nbytes = BC_SPI_CTRL1_NDF_MASK + 1;
+	}
+
+	return 0;
+}
+
+static bool bs_supports_mem_op(struct spi_mem *mem,
+			       const struct spi_mem_op *op)
+{
+	if (op->data.buswidth > 1 || op->addr.buswidth > 1 ||
+	    op->dummy.buswidth > 1 || op->cmd.buswidth > 1)
+		return false;
+
+	return spi_mem_default_supports_op(mem, op);
+}
+
+static int bs_exec_mem_op(struct spi_mem *mem,
+			  const struct spi_mem_op *op)
+{
+	struct bt1_spi *bs = spi_controller_get_devdata(mem->spi->controller);
+	struct bt1_spi_cfg req;
+	unsigned int len;
+	int ret;
+	u8 *cmd;
+
+	/* Collect the controller configuration required by the operation. */
+	req.dirmap = false;
+	req.cs = mem->spi->chip_select;
+	req.cs_gpiod = mem->spi->cs_gpiod;
+	req.flags = 0;
+	req.mode = mem->spi->mode;
+	if (req.cs_gpiod) {
+		req.tmode = BC_SPI_CTRL0_TMOD_TR;
+		req.ndf = 0;
+	} else if (op->data.dir == SPI_MEM_DATA_IN) {
+		req.tmode = BC_SPI_CTRL0_TMOD_READ_EEPROM;
+		req.ndf = op->data.nbytes;
+	} else {
+		req.tmode = BC_SPI_CTRL0_TMOD_TO;
+		req.ndf = 0;
+	}
+	req.dfs = BITS_PER_BYTE;
+	req.freq = mem->spi->max_speed_hz;
+
+	/*
+	 * Collect the command data into a single output buffer to speed the
+	 * transmission up.
+	 */
+	cmd = bs_create_cmd(bs, op, &len);
+	if (IS_ERR(cmd))
+		return PTR_ERR(cmd);
+
+	bs_set_cfg(bs, &req);
+
+	/*
+	 * In the boosted mode we have to push and pull the data as soon as
+	 * possible otherwise it might be lost, due to either native CS
+	 * getting toggled or Rx FIFO getting overrun. In non-boosted mode we
+	 * can relax and push-pull data synchronously with no worries of losing
+	 * any data or breaking the transfers due to the native CS toggle.
+	 */
+	if (!bs->cfg.cs_gpiod) {
+		bs_push_bytes(bs, cmd, len);
+
+		if (op->data.dir == SPI_MEM_DATA_IN)
+			ret = bs_pull_bytes(bs, op->data.buf.in,
+					    op->data.nbytes);
+	} else {
+		bs_pp_bytes(bs, cmd, NULL, len);
+
+		if (op->data.dir == SPI_MEM_DATA_IN)
+			bs_pp_bytes(bs, NULL, op->data.buf.in,
+				    op->data.nbytes);
+	}
+
+	ret = bs_check_status(bs);
+
+	bs_clear_cfg(bs);
+
+	bs_clear_cmd(bs, cmd);
+
+	return ret;
+}
+
+static int bs_dirmap_create(struct spi_mem_dirmap_desc *desc)
+{
+	struct spi_controller *ctlr = desc->mem->spi->controller;
+	struct bt1_spi *bs = spi_controller_get_devdata(ctlr);
+
+	if (!bs->map || !bs_supports_mem_op(desc->mem, &desc->info.op_tmpl))
+		return -ENOTSUPP;
+
+	/*
+	 * Make sure the requested region doesn't go out of the physically
+	 * mapped flash memory bounds and the operation is read-only.
+	 */
+	if (desc->info.offset + desc->info.length > bs->map_len ||
+	    desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN)
+		return -ENOTSUPP;
+
+	return 0;
+}
+
+static void bs_copy_from_map(void *to, void __iomem *from, size_t len)
+{
+	size_t shift, chunk;
+	u32 data;
+
+	/*
+	 * We split the copying up into the next three stages: unaligned head,
+	 * aligned body, unaligned tail.
+	 */
+	shift = (size_t)from & 0x3;
+	if (shift) {
+		chunk = min_t(size_t, 4 - shift, len);
+		data = readl_relaxed(from - shift);
+		memcpy(to, &data + shift, chunk);
+		from += chunk;
+		to += chunk;
+		len -= chunk;
+	}
+
+	while (len >= 4) {
+		data = readl_relaxed(from);
+		memcpy(to, &data, 4);
+		from += 4;
+		to += 4;
+		len -= 4;
+	}
+
+	if (len) {
+		data = readl_relaxed(from);
+		memcpy(to, &data, len);
+	}
+}
+
+static ssize_t bs_dirmap_read(struct spi_mem_dirmap_desc *desc,
+			      u64 offs, size_t len, void *buf)
+{
+	struct spi_mem *mem = desc->mem;
+	struct bt1_spi *bs = spi_controller_get_devdata(mem->spi->controller);
+	struct bt1_spi_cfg req;
+
+	/*
+	 * Make sure the requested operation length is valid. Truncate the
+	 * length if it's greater than the length of the MMIO region.
+	 */
+	if (offs >= bs->map_len || !len)
+		return 0;
+
+	len = min_t(size_t, len, bs->map_len - offs);
+
+	/* Collect the controller configuration required by the operation. */
+	req.dirmap = true;
+	req.cs = mem->spi->chip_select;
+	req.cs_gpiod = mem->spi->cs_gpiod;
+	req.flags = 0;
+	req.mode = mem->spi->mode;
+	req.tmode = BC_SPI_CTRL0_TMOD_READ_EEPROM;
+	req.ndf = 4;
+	req.dfs = BITS_PER_BYTE;
+	req.freq = mem->spi->max_speed_hz;
+
+	bs_set_cfg(bs, &req);
+
+	bs_copy_from_map(buf, bs->map + offs, len);
+
+	bs_clear_cfg(bs);
+
+	return len;
+}
+
+static const struct spi_controller_mem_ops bs_mem_ops = {
+	.adjust_op_size = bs_adjust_mem_op_size,
+	.supports_op = bs_supports_mem_op,
+	.exec_op = bs_exec_mem_op,
+	.dirmap_create = bs_dirmap_create,
+	.dirmap_read = bs_dirmap_read
+};
+
+static int bs_prepare_message(struct spi_controller *ctrl,
+			      struct spi_message *msg)
+{
+	struct bt1_spi *bs = spi_controller_get_devdata(ctrl);
+	struct spi_transfer *xfer;
+	struct bt1_spi_cfg req;
+
+	/*
+	 * Currently the driver doesn't support the generic messages-based
+	 * SPI interface with pure native chip-select signal. This is due
+	 * to the automatic native chip-select toggle peculiarity described
+	 * in the comment of the bs_set_cfg() method. Alas we can't use the
+	 * IRQ-disable-based boost approach here since the native queue-based
+	 * SPI messages transfer method can sleep waiting for the
+	 * transfers/CS-change delays.
+	 */
+	if (!msg->spi->cs_gpiod)
+		return -ENOTSUPP;
+
+	/*
+	 * Collect the controller configuration common for all transfers and
+	 * specific to the very first one.
+	 */
+	xfer = list_first_entry(&msg->transfers, typeof(*xfer), transfer_list);
+	req.dirmap = false;
+	req.cs = msg->spi->chip_select;
+	req.cs_gpiod = msg->spi->cs_gpiod;
+	req.flags = 0;
+	req.mode = msg->spi->mode;
+	req.tmode = BC_SPI_CTRL0_TMOD_TR;
+	req.ndf = 0;
+	req.dfs = xfer->bits_per_word;
+	req.freq = xfer->speed_hz;
+
+	bs_set_cfg(bs, &req);
+
+	return 0;
+}
+
+static int bs_unprepare_message(struct spi_controller *ctrl,
+				struct spi_message *msg)
+{
+	struct bt1_spi *bs = spi_controller_get_devdata(ctrl);
+
+	bs_clear_cfg(bs);
+
+	return 0;
+}
+
+static int bs_transfer_one(struct spi_controller *ctrl, struct spi_device *spi,
+			   struct spi_transfer *xfer)
+{
+	struct bt1_spi *bs = spi_controller_get_devdata(ctrl);
+
+	bs_update_cfg(bs, xfer);
+
+	if (bs->cfg.dfs <= 8)
+		bs_pp_bytes(bs, xfer->tx_buf, xfer->rx_buf, xfer->len);
+	else
+		bs_pp_words(bs, xfer->tx_buf, xfer->rx_buf, xfer->len / 2);
+
+	return bs_check_status(bs);
+}
+
+static struct bt1_spi *bs_create_data(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct bt1_spi *bs;
+
+	bs = devm_kzalloc(dev, sizeof(*bs), GFP_KERNEL);
+	if (!bs)
+		return ERR_PTR(-ENOMEM);
+
+	bs->dev = dev;
+	bs->pdev = pdev;
+
+	return bs;
+}
+
+static int bs_request_regs(struct bt1_spi *bs)
+{
+	struct resource *res;
+
+	bs->regs = devm_platform_ioremap_resource_byname(bs->pdev, "config");
+	if (IS_ERR(bs->regs)) {
+		dev_err(bs->dev, "Couldn't get the controller registers\n");
+		return PTR_ERR(bs->regs);
+	}
+
+	/*
+	 * Direct mapping is optional feature, which in fact doesn't really
+	 * give much of the performance gain. But the driver supports it
+	 * anyway.
+	 */
+	bs->map = devm_platform_ioremap_resource_byname(bs->pdev, "map");
+	if (IS_ERR(bs->map)) {
+		bs->map = NULL;
+		bs->map_len = 0;
+		dev_warn(bs->dev, "No direct mapping memory detected\n");
+	} else {
+		res = platform_get_resource_byname(bs->pdev, IORESOURCE_MEM,
+						   "map");
+		bs->map_len = resource_size(res);
+	}
+
+	return 0;
+}
+
+static void bs_disable_clk(void *data)
+{
+	struct bt1_spi *bs = data;
+
+	clk_disable_unprepare(bs->ref);
+}
+
+static int bs_request_clks(struct bt1_spi *bs)
+{
+	int ret;
+
+	/*
+	 * SSI reference and APB clocks are synchronous so we only need a
+	 * single clock source requested and enabled.
+	 */
+	bs->ref = devm_clk_get(bs->dev, NULL);
+	if (IS_ERR(bs->ref)) {
+		dev_err(bs->dev, "Couldn't get the ref clock descriptor\n");
+		return PTR_ERR(bs->ref);
+	}
+
+	ret = clk_prepare_enable(bs->ref);
+	if (ret) {
+		dev_err(bs->dev, "Couldn't enable the reference clock\n");
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(bs->dev, bs_disable_clk, bs);
+	if (ret) {
+		dev_err(bs->dev, "Can't add the clocks disable action\n");
+		return ret;
+	}
+
+	bs->ref_freq = clk_get_rate(bs->ref);
+	if (!bs->ref_freq) {
+		dev_err(bs->dev, "Invalid reference clock frequency\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bs_create_spi_controller(struct bt1_spi *bs)
+{
+	int ret;
+
+	bs->ctrl = spi_alloc_master(bs->dev, 0);
+	if (!bs->ctrl) {
+		dev_err(bs->dev, "No memory for SPI controller descriptor\n");
+		return -ENOMEM;
+	}
+
+	spi_controller_set_devdata(bs->ctrl, bs);
+
+	bs->ctrl->use_gpio_descriptors = true;
+	bs->ctrl->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;
+	bs->ctrl->bits_per_word_mask =  SPI_BPW_RANGE_MASK(4, 16);
+	bs->ctrl->bus_num = bs->pdev->id;
+	bs->ctrl->prepare_message = bs_prepare_message;
+	bs->ctrl->unprepare_message = bs_unprepare_message;
+	bs->ctrl->transfer_one = bs_transfer_one;
+	bs->ctrl->mem_ops = &bs_mem_ops;
+	bs->ctrl->min_speed_hz = bs->ref_freq / (BC_SPI_BAUDR_SCKDV_MASK - 1);
+	bs->ctrl->max_speed_hz = bs->ref_freq / 2;
+	bs->ctrl->dev.of_node = bs->dev->of_node;
+	bs->ctrl->dev.fwnode = bs->dev->fwnode;
+	bs->ctrl->flags = SPI_MASTER_GPIO_SS;
+
+	ret = devm_spi_register_controller(bs->dev, bs->ctrl);
+	if (ret) {
+		spi_controller_put(bs->ctrl);
+		dev_err(bs->dev, "Failed to register the SPI controller\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define BC_SPI_DBGFS_REG(_name, _off)	\
+{					\
+	.name = _name,			\
+	.offset = _off			\
+}
+
+static const struct debugfs_reg32 bs_dbgfs_regs[] = {
+	BC_SPI_DBGFS_REG("CSR", BC_CSR),
+	BC_SPI_DBGFS_REG("MAR", BC_MAR),
+	BC_SPI_DBGFS_REG("DRID", BC_DRID),
+	BC_SPI_DBGFS_REG("VID", BC_VID),
+	BC_SPI_DBGFS_REG("CTRL0", BC_SPI_CTRL0),
+	BC_SPI_DBGFS_REG("CTRL1", BC_SPI_CTRL1),
+	BC_SPI_DBGFS_REG("SSIENR", BC_SPI_SSIENR),
+	BC_SPI_DBGFS_REG("SER", BC_SPI_SER),
+	BC_SPI_DBGFS_REG("BAUDR", BC_SPI_BAUDR),
+	BC_SPI_DBGFS_REG("TXFLR", BC_SPI_TXFLR),
+	BC_SPI_DBGFS_REG("RXFLR", BC_SPI_RXFLR),
+	BC_SPI_DBGFS_REG("SR", BC_SPI_SR),
+	BC_SPI_DBGFS_REG("RISR", BC_SPI_RISR),
+	BC_SPI_DBGFS_REG("IDR", BC_SPI_IDR),
+	BC_SPI_DBGFS_REG("VERSION", BC_SPI_VERSION)
+};
+
+static int bs_dbgfs_show_regs(struct seq_file *s, void *data)
+{
+	const struct debugfs_reg32 *regs = bs_dbgfs_regs;
+	struct bt1_spi *bs = s->private;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bs_dbgfs_regs); i++, regs++) {
+		seq_printf(s, "%s = 0x%08x\n", regs->name,
+			   readl(bs->regs + regs->offset));
+		if (seq_has_overflowed(s))
+			break;
+	}
+
+	return 0;
+}
+
+static int bs_dbgfs_open_regs(struct inode *inode, struct file *file)
+{
+	struct bt1_spi *bs = inode->i_private;
+	int ret;
+
+	ret = single_open(file, bs_dbgfs_show_regs, bs);
+	if (!ret) {
+		mutex_lock(&bs->ctrl->io_mutex);
+		writel(BC_CSR_SPI_RDA, bs->regs + BC_CSR);
+	}
+
+	return ret;
+}
+
+static int bs_dbgfs_release_regs(struct inode *inode, struct file *file)
+{
+	struct bt1_spi *bs = inode->i_private;
+
+	mutex_unlock(&bs->ctrl->io_mutex);
+
+	return single_release(inode, file);
+}
+
+static const struct file_operations bs_fops_regs = {
+	.open = bs_dbgfs_open_regs,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = bs_dbgfs_release_regs
+};
+
+static void bs_dbgfs_remove(void *data)
+{
+	struct bt1_spi *bs = data;
+
+	debugfs_remove_recursive(bs->dbgfs);
+}
+
+static void bs_dbgfs_init(struct bt1_spi *bs)
+{
+	char name[32];
+	int ret;
+
+	snprintf(name, 32, "bt1-sys-ssi%d", bs->ctrl->bus_num);
+	bs->dbgfs = debugfs_create_dir(name, NULL);
+
+	ret = devm_add_action_or_reset(bs->dev, bs_dbgfs_remove, bs);
+	if (ret)
+		return;
+
+	debugfs_create_file("registers", 0400, bs->dbgfs, bs, &bs_fops_regs);
+}
+
+#else
+
+static inline int bs_dbgfs_init(struct bt1_spi *bs)
+{
+	return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int bs_probe(struct platform_device *pdev)
+{
+	struct bt1_spi *bs;
+	int ret;
+
+	bs = bs_create_data(pdev);
+	if (IS_ERR(bs))
+		return PTR_ERR(bs);
+
+	ret = bs_request_regs(bs);
+	if (ret)
+		return ret;
+
+	ret = bs_request_clks(bs);
+	if (ret)
+		return ret;
+
+	ret = bs_create_spi_controller(bs);
+	if (ret)
+		return ret;
+
+	bs_dbgfs_init(bs);
+
+	return 0;
+}
+
+static const struct of_device_id bs_of_match[] = {
+	{ .compatible = "baikal,bt1-sys-ssi" },
+	{ }
+};
+
+static struct platform_driver bs_driver = {
+	.probe = bs_probe,
+	.driver = {
+		.name = "bt1-sys-ssi",
+		.of_match_table = bs_of_match
+	}
+};
+module_platform_driver(bs_driver);
+
+MODULE_AUTHOR("Serge Semin <Sergey.Semin@baikalelectronics.ru>");
+MODULE_DESCRIPTION("Baikal-T1 System Boot SPI Controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/spi/spi-bt1-sys.h b/drivers/spi/spi-bt1-sys.h
new file mode 100644
index 000000000000..a45c0ef9ff4d
--- /dev/null
+++ b/drivers/spi/spi-bt1-sys.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
+ *
+ * Baikal-T1 System Boot SPI Controller driver
+ */
+#ifndef __SPI_BT1_SYS_H__
+#define __SPI_BT1_SYS_H__
+
+#include <linux/kernel.h>
+#include <linux/bits.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+
+/*
+ * Baikal-T1 System Boot SPI controller registers. In addition to the Vendor-
+ * specific settings these are normal Synopsys DW APB SSI controller registers.
+ * Exception is that the IP core is synthesized with no IRQ/DMA, with very
+ * small FIFO of just 8 words and have a specific IP block implementing
+ * physically mapped SPI flash found at CS0.
+ */
+#define BC_CSR				0x000
+#define BC_CSR_MODE_FLD			0
+#define BC_CSR_MODE_MASK		GENMASK(1, BC_CSR_MODE_FLD)
+#define BC_CSR_MODE_ROM			0
+#define BC_CSR_MODE_SRAM		1
+#define BC_CSR_MODE_FLASH		2
+#define BC_CSR_SPI_RDA			BIT(8)
+#define BC_MAR				0x004
+#define BC_MAR_BSAB			BIT(0)
+#define BC_DRID				0x008
+#define BC_VID				0x00C
+#define BC_SPI_CTRL0			0x100
+#define BC_SPI_CTRL0_DFS_FLD		0
+#define BC_SPI_CTRL0_DFS_MASK		GENMASK(3, BC_SPI_CTRL0_DFS_FLD)
+#define BC_SPI_CTRL0_FRF_FLD		4
+#define BC_SPI_CTRL0_FRF_MASK		GENMASK(5, BC_SPI_CTRL0_FRF_FLD)
+#define BC_SPI_CTRL0_FRF_SPI		0
+#define BC_SPI_CTRL0_FRF_SSP		1
+#define BC_SPI_CTRL0_FRF_MW		2
+#define BC_SPI_CTRL0_SCPH		BIT(6)
+#define BC_SPI_CTRL0_SCPOL		BIT(7)
+#define BC_SPI_CTRL0_TMOD_FLD		8
+#define BC_SPI_CTRL0_TMOD_MASK		GENMASK(9, BC_SPI_CTRL0_TMOD_FLD)
+#define BC_SPI_CTRL0_TMOD_TR		0
+#define BC_SPI_CTRL0_TMOD_TO		1
+#define BC_SPI_CTRL0_TMOD_RO		2
+#define BC_SPI_CTRL0_TMOD_READ_EEPROM	3
+#define BC_SPI_CTRL0_SRL		BIT(11)
+#define BC_SPI_CTRL1			0x104
+#define BC_SPI_CTRL1_NDF_FLD		0
+#define BC_SPI_CTRL1_NDF_MASK		GENMASK(15, BC_SPI_CTRL1_NDF_FLD)
+#define BC_SPI_SSIENR			0x108
+#define BC_SPI_SSIENR_EN		BIT(0)
+#define BC_SPI_SER			0x110
+#define BC_SPI_SER_0			BIT(0)
+#define BC_SPI_BAUDR			0x114
+#define BC_SPI_BAUDR_SCKDV_FLD		0
+#define BC_SPI_BAUDR_SCKDV_MASK		GENMASK(15, BC_SPI_BAUDR_SCKDV_FLD)
+#define BC_SPI_TXFTLR			0x118
+#define BC_SPI_RXFTLR			0x11C
+#define BC_SPI_TXFLR			0x120
+#define BC_SPI_TXFLR_TXTFL_FLD		0
+#define BC_SPI_TXFLR_TXTFL_MASK		GENMASK(2, BC_SPI_TXFLR_TXTFL_FLD)
+#define BC_SPI_RXFLR			0x124
+#define BC_SPI_RXFLR_RXTFL_FLD		0
+#define BC_SPI_RXFLR_RXTFL_MASK		GENMASK(2, BC_SPI_RXFLR_RXTFL_FLD)
+#define BC_SPI_SR			0x128
+#define BC_SPI_SR_BUSY			BIT(0)
+#define BC_SPI_SR_TFNF			BIT(1)
+#define BC_SPI_SR_TFE			BIT(2)
+#define BC_SPI_SR_RFNE			BIT(3)
+#define BC_SPI_SR_RFF			BIT(4)
+#define BC_SPI_RISR			0x134
+#define BC_SPI_RISR_TXE			BIT(0)
+#define BC_SPI_RISR_TXO			BIT(1)
+#define BC_SPI_RISR_RXU			BIT(2)
+#define BC_SPI_RISR_RXO			BIT(3)
+#define BC_SPI_RISR_RXF			BIT(4)
+#define BC_SPI_TXOICR			0x138
+#define BC_SPI_RXOICR			0x13C
+#define BC_SPI_RXUICR			0x140
+#define BC_SPI_ICR			0x148
+#define BC_SPI_ICR_STS			BIT(0)
+#define BC_SPI_IDR			0x158
+#define BC_SPI_VERSION			0x15C
+#define BC_SPI_DR			0x160
+#define BC_SPI_RX_DLY			0x1F0
+
+/*
+ * Boot controller specific parameters
+ * @BC_SPI_FIFO_LEN: DW APB SSI controller FIFO length.
+ * @BC_SPI_FIFO_LVL: Safe Tx FIFO level so not to afraid the xfer task being
+ *		     scheduled.
+ * @BC_SPI_NUM_CS: Number of hardware chip selects.
+ * @BC_SPI_MIN_SCKDV: Minimal clock divider supported when native CS is used.
+ * @BC_SPI_RETRY: Number of retries before give up in waiting new data.
+ *		  Timeout may happen if the controller detected Rx-overrun
+ *		  event.
+ * @BC_SPI_BUF_SIZE: Size of the cmd-buffer for Read/Write-EEPROM transfer. It
+ *		     includes the opcode, address, dummy and data bytes.
+ */
+#define BC_SPI_FIFO_LEN			8
+#define BC_SPI_FIFO_LVL			(BC_SPI_FIFO_LEN / 2)
+#define BC_SPI_NUM_CS			1
+#define BC_SPI_MIN_SCKDV		6
+#define BC_SPI_RETRY			1000
+#define BC_SPI_BUF_SIZE		(1 + BITS_PER_LONG_LONG / BITS_PER_BYTE + 512)
+
+/*
+ * struct bt1_spi_cfg - DW APB SPI controller configuration
+ * @dirmap: Flag of SPI flash direct mapping.
+ * @cs: Native chip select number.
+ * @cs_gpiod: Chip-select GPIO descriptor.
+ * @flags: IRQ flags saved in case of the boosted transfers.
+ * @mode: SPI transfer mode.
+ * @tmode: TR/TO/RO/EEPROM modes.
+ * @dfs: Data frame size.
+ * @ndf: Number of data frames for RO/EEPROM transfers.
+ * @freq: Transfer frequency.
+ */
+struct bt1_spi_cfg {
+	bool dirmap;
+	u8 cs;
+	struct gpio_desc *cs_gpiod;
+	unsigned long flags;
+	u32 mode;
+	u32 tmode;
+	u32 dfs;
+	u32 ndf;
+	u32 freq;
+};
+
+/*
+ * struct bt1_spi - Bailal-T1 System Boot SPI controller private data
+ * @dev: Pointer to the device structure.
+ * @pdev: Pointer to the platform device structure.
+ * @ctrl: Pointer to the SPI controller structure.
+ * @regs: Memory mapped controller registers.
+ * @map: Physically mapped SPI flash.
+ * @map_len: Length of the physically mapped ROM.
+ * @buf: Temporary buffer.
+ * @cfg: Current controller configuration.
+ * @ref_freq: Reference frequency.
+ * @ref: Reference clock source.
+ */
+struct bt1_spi {
+	struct device *dev;
+	struct platform_device *pdev;
+	struct spi_controller *ctrl;
+
+	void __iomem *regs;
+	void __iomem *map;
+	resource_size_t map_len;
+
+	u8 buf[BC_SPI_BUF_SIZE];
+	struct bt1_spi_cfg cfg;
+	unsigned long ref_freq;
+	struct clk *ref;
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *dbgfs;
+#endif
+};
+
+#endif /* __SPI_BT1_SYS_H__ */
