[U-Boot] [PATCH v2 12/38] x86: spi: Add a driver for the Intel Fast SPI interface

Simon Glass sjg at chromium.org
Wed Sep 25 14:11:21 UTC 2019


This provides access to SPI flash both through a read-only memory map and
with operations to erase and write flash. It supports 4KB or 64KB erase
sizes.

This driver is used by Apollolake.

Signed-off-by: Simon Glass <sjg at chromium.org>
---

Changes in v2:
- Add the missing header file
- Add support for of-platdata for TPL

 drivers/mtd/spi/Kconfig          |   9 +
 drivers/mtd/spi/Makefile         |   1 +
 drivers/mtd/spi/intel_fast_spi.c | 360 +++++++++++++++++++++++++++++++
 3 files changed, 370 insertions(+)
 create mode 100644 drivers/mtd/spi/intel_fast_spi.c

diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig
index d3b007a731d..4a184bbc826 100644
--- a/drivers/mtd/spi/Kconfig
+++ b/drivers/mtd/spi/Kconfig
@@ -78,6 +78,15 @@ config SF_DEFAULT_SPEED
 	  speed and mode from platdata values computed from
 	  available node.
 
+config SPI_FLASH_INTEL_FAST
+	bool "Intel Fast SPI interface"
+	depends on DM_SPI_FLASH
+	help
+	  Add support for Intel Fast SPI interface, as used on Apollolake.
+	  This current only supports reading from SPI, enough to load SPL or
+	  U-Boot proper from SPI flash. Future work may enable write and erase
+	  operations.
+
 if SPI_FLASH
 
 config SPI_FLASH_SFDP_SUPPORT
diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile
index 20db1015d9e..11a8f55132a 100644
--- a/drivers/mtd/spi/Makefile
+++ b/drivers/mtd/spi/Makefile
@@ -19,5 +19,6 @@ endif
 
 obj-$(CONFIG_SPI_FLASH) += spi-nor.o
 obj-$(CONFIG_SPI_FLASH_DATAFLASH) += sf_dataflash.o
+obj-$(CONFIG_SPI_FLASH_INTEL_FAST) += intel_fast_spi.o
 obj-$(CONFIG_SPI_FLASH_MTD) += sf_mtd.o
 obj-$(CONFIG_SPI_FLASH_SANDBOX) += sandbox.o
diff --git a/drivers/mtd/spi/intel_fast_spi.c b/drivers/mtd/spi/intel_fast_spi.c
new file mode 100644
index 00000000000..7074669c938
--- /dev/null
+++ b/drivers/mtd/spi/intel_fast_spi.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Intel 'Fast SPI' support
+ *
+ * Copyright 2019 Google LLC
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <dt-structs.h>
+#include <pci.h>
+#include <spi_flash.h>
+#include <spl.h>
+#include <asm/io.h>
+#include <asm/pci.h>
+#include <asm/arch/fast_spi.h>
+#include <asm/arch/iomap.h>
+
+struct fast_spi_platdata {
+#if CONFIG_IS_ENABLED(OF_PLATDATA)
+	struct dtd_intel_fast_spi dtplat;
+#endif
+	ulong mmio_base;
+	pci_dev_t bdf;
+};
+
+struct fast_spi_priv {
+	struct fast_spi_regs *regs;
+	uint page_size;
+	uint flash_size;
+	uint map_offset;
+	ulong map_base;
+	size_t map_size;
+};
+
+enum {
+	/* Erase size options */
+	ERASE_SIZE_SM	= 4 << 10,
+	ERASE_SIZE_LG	= 64 << 10,
+};
+
+/*
+ * The hardware datasheet is not clear on what HORD values actually do. It
+ * seems that HORD_SFDP provides access to the first 8 bytes of the SFDP, which
+ * is the signature and revision fields. HORD_JEDEC provides access to the
+ * actual flash parameters, and is most likely what you want to use when
+ * probing the flash from software.
+ * It's okay to rely on SFDP, since the SPI flash controller requires an SFDP
+ * 1.5 or newer compliant FAST_SPI flash chip.
+ * NOTE: Due to the register layout of the hardware, all accesses will be
+ * aligned to a 4 byte boundary.
+ */
+static u32 read_sfdp_param(struct fast_spi_priv *priv, uint sfdp_reg)
+{
+	u32 ptinx_index = sfdp_reg & SPIBAR_PTINX_IDX_MASK;
+
+	writel(ptinx_index | SPIBAR_PTINX_HORD_JEDEC, &priv->regs->ptinx);
+
+	return readl(&priv->regs->ptdata);
+}
+
+/* Fill FDATAn FIFO in preparation for a write transaction */
+static void fill_xfer_fifo(struct fast_spi_priv *priv, const void *data,
+			   size_t len)
+{
+	memcpy(priv->regs->fdata, data, len);
+}
+
+/* Drain FDATAn FIFO after a read transaction populates data */
+static void drain_xfer_fifo(struct fast_spi_priv *priv, void *dest, size_t len)
+{
+	memcpy(dest, priv->regs->fdata, len);
+}
+
+/* Fire up a transfer using the hardware sequencer */
+static void start_hwseq_xfer(struct fast_spi_priv *priv, u32 hsfsts_cycle,
+			     u32 offset, size_t len)
+{
+	/* Make sure all W1C status bits get cleared */
+	u32 hsfsts = SPIBAR_HSFSTS_W1C_BITS;
+
+	/* Set up transaction parameters */
+	hsfsts |= hsfsts_cycle & SPIBAR_HSFSTS_FCYCLE_MASK;
+	hsfsts |= SPIBAR_HSFSTS_FDBC(len - 1);
+
+	writel(offset, &priv->regs->faddr);
+	writel(hsfsts | SPIBAR_HSFSTS_FGO, &priv->regs->hsfsts_ctl);
+}
+
+static int wait_for_hwseq_xfer(struct fast_spi_priv *priv, u32 offset)
+{
+	ulong start;
+	u32 hsfsts;
+
+	start = get_timer(0);
+	do {
+		hsfsts = readl(&priv->regs->hsfsts_ctl);
+
+		if (hsfsts & SPIBAR_HSFSTS_FCERR) {
+			debug("SPI transaction error at offset %x HSFSTS = %08x\n",
+			      offset, hsfsts);
+			return -EIO;
+		}
+
+		if (hsfsts & SPIBAR_HSFSTS_FDONE)
+			return 0;
+	} while ((int)get_timer(start) < SPIBAR_HWSEQ_XFER_TIMEOUT_MS);
+
+	debug("SPI transaction timeout at offset %x HSFSTS = %08x, timer %d\n",
+	      offset, hsfsts, (uint)get_timer(start));
+	return -ETIMEDOUT;
+}
+
+/* Execute FAST_SPI flash transfer. This is a blocking call */
+static int exec_sync_hwseq_xfer(struct fast_spi_priv *priv,
+				u32 hsfsts_cycle, u32 offset,
+				size_t len)
+{
+	start_hwseq_xfer(priv, hsfsts_cycle, offset, len);
+
+	return wait_for_hwseq_xfer(priv, offset);
+}
+
+/*
+ * Ensure read/write xfer len is not greater than SPIBAR_FDATA_FIFO_SIZE and
+ * that the operation does not cross page boundary.
+ */
+static size_t get_xfer_len(const struct fast_spi_priv *priv, u32 offset,
+			   size_t len)
+{
+	size_t xfer_len = min(len, (size_t)SPIBAR_FDATA_FIFO_SIZE);
+	size_t bytes_left = ALIGN(offset, priv->page_size) - offset;
+
+	if (bytes_left)
+		xfer_len = min(xfer_len, bytes_left);
+
+	return xfer_len;
+}
+
+static int fast_spi_flash_erase(struct udevice *dev, u32 offset, size_t len)
+{
+	struct fast_spi_priv *priv = dev_get_priv(dev);
+	int ret;
+	size_t erase_size;
+	u32 erase_cycle;
+
+	if (!IS_ALIGNED(offset, ERASE_SIZE_SM) ||
+	    !IS_ALIGNED(len, ERASE_SIZE_SM)) {
+		debug("SPI erase region not sector-aligned\n");
+		return -EINVAL;
+	}
+
+	while (len) {
+		if (IS_ALIGNED(offset, ERASE_SIZE_LG) && len >= ERASE_SIZE_LG) {
+			erase_size = ERASE_SIZE_LG;
+			erase_cycle = SPIBAR_HSFSTS_CYCLE_64K_ERASE;
+		} else {
+			erase_size = ERASE_SIZE_SM;
+			erase_cycle = SPIBAR_HSFSTS_CYCLE_4K_ERASE;
+		}
+		debug("Erasing flash addr %x + %x\n", offset, (uint)erase_size);
+
+		ret = exec_sync_hwseq_xfer(priv, erase_cycle, offset, 0);
+		if (ret)
+			return ret;
+
+		offset += erase_size;
+		len -= erase_size;
+	}
+
+	return 0;
+}
+
+static int fast_spi_read(struct udevice *dev, u32 offset, size_t len, void *buf)
+{
+	struct fast_spi_priv *priv = dev_get_priv(dev);
+
+	debug("%s: read at offset %x\n", __func__, offset);
+	while (len) {
+		size_t xfer_len = get_xfer_len(priv, offset, len);
+		int ret;
+
+		ret = exec_sync_hwseq_xfer(priv, SPIBAR_HSFSTS_CYCLE_READ,
+					   offset, xfer_len);
+		if (ret)
+			return ret;
+
+		drain_xfer_fifo(priv, buf, xfer_len);
+
+		offset += xfer_len;
+		buf += xfer_len;
+		len -= xfer_len;
+	}
+
+	return 0;
+}
+
+static int fast_spi_flash_write(struct udevice *dev, u32 addr, size_t len,
+				const void *buf)
+{
+	struct fast_spi_priv *priv = dev_get_priv(dev);
+	const u8 *data = buf;
+	size_t xfer_len;
+	int ret;
+
+	while (len) {
+		xfer_len = get_xfer_len(priv, addr, len);
+		fill_xfer_fifo(priv, data, xfer_len);
+
+		ret = exec_sync_hwseq_xfer(priv, SPIBAR_HSFSTS_CYCLE_WRITE,
+					   addr, xfer_len);
+		if (ret)
+			return ret;
+
+		addr += xfer_len;
+		data += xfer_len;
+		len -= xfer_len;
+	}
+
+	return 0;
+}
+
+static int fast_spi_get_mmap(struct udevice *dev, ulong *map_basep,
+			     size_t *map_sizep, u32 *offsetp)
+{
+	struct fast_spi_priv *priv = dev_get_priv(dev);
+
+	if (priv) {
+		*map_basep = priv->map_base;
+		*map_sizep = priv->map_size;
+		*offsetp = priv->map_offset;
+	} else {
+		return fast_spi_get_bios_mmap(map_basep, map_sizep, offsetp);
+	}
+
+	return 0;
+}
+
+static void fast_spi_early_init(struct udevice *dev)
+{
+	struct fast_spi_platdata *plat = dev_get_platdata(dev);
+	pci_dev_t pdev = plat->bdf;
+
+	/* Clear BIT 1-2 SPI Command Register */
+	//remove
+	pci_x86_clrset_config(pdev, PCI_COMMAND, PCI_COMMAND_MASTER |
+			      PCI_COMMAND_MEMORY, 0, PCI_SIZE_8);
+
+	/* Program Temporary BAR for SPI */
+	pci_x86_write_config(pdev, PCI_BASE_ADDRESS_0,
+			     plat->mmio_base | PCI_BASE_ADDRESS_SPACE_MEMORY,
+			     PCI_SIZE_32);
+
+	/* Enable Bus Master and MMIO Space */
+	pci_x86_clrset_config(pdev, PCI_COMMAND, 0, PCI_COMMAND_MASTER |
+			      PCI_COMMAND_MEMORY, PCI_SIZE_8);
+
+	/*
+	 * Disable the BIOS write protect so write commands are allowed.
+	 * Enable Prefetching and caching.
+	 */
+	pci_x86_clrset_config(pdev, SPIBAR_BIOS_CONTROL,
+			      SPIBAR_BIOS_CONTROL_EISS |
+			      SPIBAR_BIOS_CONTROL_CACHE_DISABLE,
+			      SPIBAR_BIOS_CONTROL_WPD |
+			      SPIBAR_BIOS_CONTROL_PREFETCH_ENABLE, PCI_SIZE_8);
+}
+
+static int fast_spi_ofdata_to_platdata(struct udevice *dev)
+{
+	struct fast_spi_platdata *plat = dev_get_platdata(dev);
+
+#if !CONFIG_IS_ENABLED(OF_PLATDATA)
+	int ret;
+
+	if (spl_phase() == PHASE_TPL) {
+		u32 base[2];
+
+		/* TPL sets up the initial BAR */
+		ret = dev_read_u32_array(dev, "early-regs", base,
+					 ARRAY_SIZE(base));
+		if (ret)
+			return log_msg_ret("Missing/short early-regs", ret);
+		plat->mmio_base = base[0];
+		plat->bdf = pci_x86_get_devfn(dev);
+		if (plat->bdf < 0)
+			return log_msg_ret("Cannot get p2sb PCI address",
+					   plat->bdf);
+	} else {
+		plat->mmio_base = dev_read_addr_pci(dev);
+		/* Don't set BDF since it should not be used */
+		if (plat->mmio_base == FDT_ADDR_T_NONE)
+			return -EINVAL;
+	}
+#else
+	plat->mmio_base = plat->dtplat.early_regs[0];
+	plat->bdf = pci_x86_ofplat_get_devfn(plat->dtplat.reg[0]);
+#endif
+
+	return 0;
+}
+
+static int fast_spi_probe(struct udevice *dev)
+{
+	struct fast_spi_platdata *plat = dev_get_platdata(dev);
+	struct spi_flash *flash = dev_get_uclass_priv(dev);
+	struct fast_spi_priv *priv = dev_get_priv(dev);
+	u32 flash_bits;
+	ulong base;
+
+	if (spl_phase() == PHASE_TPL)
+		fast_spi_early_init(dev);
+
+	priv->regs = (struct fast_spi_regs *)plat->mmio_base;
+
+	/*
+	 * bytes = (bits + 1) / 8;
+	 * But we need to do the addition in a way which doesn't overflow for
+	 * 4 Gb devices (flash_bits == 0xffffffff).
+	 */
+	flash_bits = read_sfdp_param(priv, 0x04);
+	flash->size = (flash_bits >> 3) + 1;
+
+	/* Can erase both 4 KiB and 64 KiB chunks. Declare the smaller size */
+	flash->sector_size = 4 << 10;
+	flash->page_size = 256;
+
+	base = fast_spi_get_bios_region(priv->regs, &priv->map_size);
+	priv->map_base = (u32)-priv->map_size - base;
+	priv->map_offset = base;
+
+	debug("FAST SPI at %lx, size %x with mapping %x, size %x\n",
+	      plat->mmio_base, flash->size, (uint)priv->map_base,
+	      priv->map_size);
+
+	return 0;
+}
+
+static const struct dm_spi_flash_ops fast_spi_ops = {
+	.read = fast_spi_read,
+	.write = fast_spi_flash_write,
+	.erase = fast_spi_flash_erase,
+	.get_mmap = fast_spi_get_mmap,
+};
+
+static const struct udevice_id fast_spi_ids[] = {
+	{ .compatible = "intel,fast-spi" },
+	{ }
+};
+
+U_BOOT_DRIVER(intel_fast_spi) = {
+	.name		= "intel_fast_spi",
+	.id		= UCLASS_SPI_FLASH,
+	.of_match	= fast_spi_ids,
+	.ofdata_to_platdata = fast_spi_ofdata_to_platdata,
+	.probe		= fast_spi_probe,
+	.platdata_auto_alloc_size = sizeof(struct fast_spi_platdata),
+	.priv_auto_alloc_size = sizeof(struct fast_spi_priv),
+	.ops		= &fast_spi_ops,
+};
-- 
2.23.0.444.g18eeb5a265-goog



More information about the U-Boot mailing list