[coreboot-gerrit] Patch set updated for coreboot: soc/intel/apollolake: Implement SPI controller driver

Andrey Petrov (andrey.petrov@intel.com) gerrit at coreboot.org
Sat Apr 9 18:53:04 CEST 2016


Andrey Petrov (andrey.petrov at intel.com) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/14246

-gerrit

commit dc06450c2faa0720e963070e754a75fd9fda1d77
Author: Alexandru Gagniuc <alexandrux.gagniuc at intel.com>
Date:   Wed Feb 24 15:08:23 2016 -0800

    soc/intel/apollolake: Implement SPI controller driver
    
    Implement flash read, write, and erase functionality using the
    hardware sequencing capabilities of the SOC. Due to changes in
    hardware requirements, the flash chip must be probed differently
    than on previous platforms (details explained in comments).
    
    Note that this is a minimal implementation, and does not provide all
    the bells and whistles.
    
    Change-Id: I6dcc3bc36dfce61927d126d231a16d485acb1bdc
    Signed-off-by: Alexandru Gagniuc <alexandrux.gagniuc at intel.com>
    Signed-off-by: Andrey Petrov <andrey.petrov at intel.com>
---
 src/soc/intel/apollolake/Kconfig                |   1 +
 src/soc/intel/apollolake/Makefile.inc           |   1 +
 src/soc/intel/apollolake/include/soc/pci_devs.h |   1 +
 src/soc/intel/apollolake/include/soc/spi.h      |  66 +++++
 src/soc/intel/apollolake/spi.c                  | 370 ++++++++++++++++++++++++
 5 files changed, 439 insertions(+)

diff --git a/src/soc/intel/apollolake/Kconfig b/src/soc/intel/apollolake/Kconfig
index efebe51..30ee7e5 100644
--- a/src/soc/intel/apollolake/Kconfig
+++ b/src/soc/intel/apollolake/Kconfig
@@ -32,6 +32,7 @@ config CPU_SPECIFIC_OPTIONS
 	select REG_SCRIPT
 	select RELOCATABLE_RAMSTAGE	# Build fails if this is not selected
 	select SOC_INTEL_COMMON
+	select SPI_FLASH
 	select UDELAY_TSC
 	select TSC_CONSTANT_RATE
 	select UDELAY_TSC
diff --git a/src/soc/intel/apollolake/Makefile.inc b/src/soc/intel/apollolake/Makefile.inc
index 70ab515..eb3058d 100644
--- a/src/soc/intel/apollolake/Makefile.inc
+++ b/src/soc/intel/apollolake/Makefile.inc
@@ -38,6 +38,7 @@ ramstage-y += memmap.c
 ramstage-y += mmap_boot.c
 ramstage-y += uart.c
 ramstage-y += northbridge.c
+ramstage-y += spi.c
 
 postcar-y += exit_car.S
 postcar-y += memmap.c
diff --git a/src/soc/intel/apollolake/include/soc/pci_devs.h b/src/soc/intel/apollolake/include/soc/pci_devs.h
index 2a65a22..f7e574c 100644
--- a/src/soc/intel/apollolake/include/soc/pci_devs.h
+++ b/src/soc/intel/apollolake/include/soc/pci_devs.h
@@ -36,5 +36,6 @@
 
 #define P2SB_DEV		PCI_DEV(0, 0xd, 0)
 #define PMC_DEV			PCI_DEV(0, 0xd, 1)
+#define SPI_DEV			PCI_DEV(0, 0xd, 2)
 
 #endif
diff --git a/src/soc/intel/apollolake/include/soc/spi.h b/src/soc/intel/apollolake/include/soc/spi.h
new file mode 100644
index 0000000..1019a51
--- /dev/null
+++ b/src/soc/intel/apollolake/include/soc/spi.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2016 Intel Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _SOC_APOLLOLAKE_SPI_H_
+#define _SOC_APOLLOLAKE_SPI_H_
+
+/* PCI configuration registers */
+#define SPIBAR_BIOS_CONTROL		0xdc
+
+/* Maximum bytes of data that can fit in FDATAn registers */
+#define SPIBAR_FDATA_FIFO_SIZE		0x40
+
+/* Bit definitions for BIOS_CONTROL */
+#define SPIBAR_BIOS_CONTROL_WPD		(1 << 0)
+#define  SPIBAR_BIOS_CONTROL_EISS	(1 << 5)
+
+/* Register offsets from the MMIO region base (PCI_BASE_ADDRESS_0) */
+#define SPIBAR_HSFSTS_CTL		0x04
+#define SPIBAR_FADDR			0x08
+#define SPIBAR_FDATA(n)			(0x10 + ((n) & 0xf) * 4)
+#define SPIBAR_PTINX			0xcc
+#define SPIBAR_PTDATA			0xd0
+
+/* Bit definitions for HSFSTS_CTL register */
+#define  SPIBAR_HSFSTS_FBDC_MASK	(0x3f << 24)
+#define  SPIBAR_HSFSTS_FBDC(n)		(((n) << 24) & SPIBAR_HSFSTS_FBDC_MASK)
+#define  SPIBAR_HSFSTS_WET		(1 << 21)
+#define  SPIBAR_HSFSTS_FCYCLE_MASK	(0xf << 17)
+#define  SPIBAR_HSFSTS_FCYCLE(cyc)	(((cyc) << 17) & SPIBAR_HSFSTS_FCYCLE_MASK)
+#define  SPIBAR_HSFSTS_FGO		(1 << 16)
+#define  SPIBAR_HSFSTS_FLOCKDN		(1 << 15)
+#define  SPIBAR_HSFSTS_FDV		(1 << 14)
+#define  SPIBAR_HSFSTS_FDOPSS		(1 << 13)
+#define  SPIBAR_HSFSTS_SAF_CE		(1 << 8)
+#define  SPIBAR_HSFSTS_SAF_ACTIVE	(1 << 7)
+#define  SPIBAR_HSFSTS_SAF_LE		(1 << 6)
+#define  SPIBAR_HSFSTS_SCIP		(1 << 5)
+#define  SPIBAR_HSFSTS_SAF_DLE		(1 << 4)
+#define  SPIBAR_HSFSTS_SAF_ERROR	(1 << 3)
+#define  SPIBAR_HSFSTS_AEL		(1 << 2)
+#define  SPIBAR_HSFSTS_FCERR		(1 << 1)
+#define  SPIBAR_HSFSTS_FDONE		(1 << 0)
+#define  SPIBAR_HSFSTS_W1C_BITS	(0xff)
+/* Supported flash cycle types */
+#define  SPIBAR_HSFSTS_CYCLE_READ	SPIBAR_HSFSTS_FCYCLE(0)
+#define  SPIBAR_HSFSTS_CYCLE_WRITE	SPIBAR_HSFSTS_FCYCLE(2)
+#define  SPIBAR_HSFSTS_CYCLE_4K_ERASE	SPIBAR_HSFSTS_FCYCLE(3)
+#define  SPIBAR_HSFSTS_CYCLE_64K_ERASE	SPIBAR_HSFSTS_FCYCLE(4)
+
+/* Bit definitions for PTINX register */
+#define  SPIBAR_PTINX_COMP_0		(0 << 14)
+#define  SPIBAR_PTINX_COMP_1		(1 << 14)
+#define  SPIBAR_PTINX_HORD_SFDP		(0 << 12)
+#define  SPIBAR_PTINX_HORD_PARAM	(1 << 12)
+#define  SPIBAR_PTINX_HORD_JEDEC	(2 << 12)
+#define  SPIBAR_PTINX_IDX_MASK		0xffc
+
+#endif
diff --git a/src/soc/intel/apollolake/spi.c b/src/soc/intel/apollolake/spi.c
new file mode 100644
index 0000000..e0c1547
--- /dev/null
+++ b/src/soc/intel/apollolake/spi.c
@@ -0,0 +1,370 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2016 Intel Corp.
+ * (Written by Alexandru Gagniuc <alexandrux.gagniuc at intel.com> for Intel Corp.)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#define __SIMPLE_DEVICE__
+
+#include <arch/io.h>
+#include <device/device.h>
+#include <device/pci.h>
+#include <soc/pci_devs.h>
+#include <soc/spi.h>
+#include <spi_flash.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Helper to create a SPI context on API entry. */
+#define BOILERPLATE_CREATE_CTX(ctx)		\
+	struct spi_ctx	real_ctx;		\
+	struct spi_ctx *ctx = &real_ctx;	\
+	_spi_get_ctx(ctx)
+
+/*
+ * Anything that's not success is <0. Provided solely for readability, as these
+ * constants are not used outside this file.
+ */
+enum errors {
+	SUCCESS			= 0,
+	E_NOT_IMPLEMENTED	= -1,
+	E_TIMEOUT		= -2,
+	E_HW_ERROR		= -3,
+	E_ARGUMENT		= -4,
+};
+
+/* Reduce data-passing burden by grouping transaction data in a context. */
+struct spi_ctx {
+	uintptr_t mmio_base;
+	device_t pci_dev;
+	uint32_t hsfsts_on_last_error;
+};
+
+static void _spi_get_ctx(struct spi_ctx *ctx)
+{
+	uint32_t bar;
+
+	/* FIXME: use device definition */
+	ctx->pci_dev = SPI_DEV;
+
+	bar = pci_read_config32(ctx->pci_dev, PCI_BASE_ADDRESS_0);
+	ctx->mmio_base = bar & ~PCI_BASE_ADDRESS_MEM_ATTR_MASK;
+	ctx->hsfsts_on_last_error = 0;
+}
+
+/* Read register from the SPI controller. 'reg' is the register offset. */
+static uint32_t _spi_reg_read(struct spi_ctx *ctx, uint16_t reg)
+{
+	uintptr_t addr =  ALIGN_DOWN(ctx->mmio_base + reg, 4);
+	return read32((void *)addr);
+}
+
+/* Write to register in the SPI controller. 'reg' is the register offset. */
+static void _spi_reg_write(struct spi_ctx *ctx, uint16_t reg, uint32_t val)
+{
+	uintptr_t addr =  ALIGN_DOWN(ctx->mmio_base + reg, 4);
+	write32((void *)addr, val);
+
+}
+
+/*
+ * 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 SFPD, since the SPI controller requires an SFDP 1.5 or
+ * newer compliant SPI chip.
+ * NOTE: Due to the register layout of the hardware, all accesses will be
+ * aligned to a 4 byte boundary.
+ */
+static uint32_t read_spi_sfdp_param(struct spi_ctx *ctx, uint16_t sfdp_reg)
+{
+	uint32_t ptinx_index = sfdp_reg & SPIBAR_PTINX_IDX_MASK;
+	_spi_reg_write(ctx, SPIBAR_PTINX, ptinx_index | SPIBAR_PTINX_HORD_JEDEC);
+	return _spi_reg_read(ctx, SPIBAR_PTDATA);
+}
+
+/* Fill FDATAn FIFO in preparation for a write transaction. */
+static void fill_xfer_fifo(struct spi_ctx *ctx, const void *data, size_t len)
+{
+	len = min(len, SPIBAR_FDATA_FIFO_SIZE);
+
+	/* YES! memcpy() works. FDATAn does not require 32-bit accesses. */
+	memcpy((void*)(ctx->mmio_base + SPIBAR_FDATA(0)), data, len);
+}
+
+/* Drain FDATAn FIFO after a read transaction populates data. */
+static void drain_xfer_fifo(struct spi_ctx *ctx, void *dest, size_t len)
+{
+	len = min(len, SPIBAR_FDATA_FIFO_SIZE);
+
+	/* YES! memcpy() works. FDATAn does not require 32-bit accesses. */
+	memcpy(dest, (void*)(ctx->mmio_base + SPIBAR_FDATA(0)), len);
+}
+
+/* Fire up a transfer using the hardware sequencer. */
+static void start_hwseq_xfer(struct spi_ctx *ctx, uint32_t hsfsts_cycle,
+				 uint32_t flash_addr, size_t len)
+{
+	/* Make sure all W1C status bits get cleared. */
+	uint32_t hsfsts = SPIBAR_HSFSTS_W1C_BITS;
+	/* Set up transaction parameters. */
+	hsfsts |= hsfsts_cycle & SPIBAR_HSFSTS_FCYCLE_MASK;
+	hsfsts |= SPIBAR_HSFSTS_FBDC(len - 1);
+
+	_spi_reg_write(ctx, SPIBAR_FADDR, flash_addr);
+	_spi_reg_write(ctx, SPIBAR_HSFSTS_CTL, hsfsts | SPIBAR_HSFSTS_FGO);
+}
+
+static void print_xfer_error(struct spi_ctx *ctx, const char *failure_reason,
+			     uint32_t flash_addr)
+{
+	printk(BIOS_ERR, "SPI Transaction %s at flash offset %x.\n"
+			 "\tHSFSTS = 0x%08x\n",
+	       failure_reason, flash_addr, ctx->hsfsts_on_last_error);
+}
+
+static int wait_for_hwseq_xfer(struct spi_ctx *ctx)
+{
+	uint32_t hsfsts;
+	do {
+		hsfsts = _spi_reg_read(ctx, SPIBAR_HSFSTS_CTL);
+
+		if (hsfsts & SPIBAR_HSFSTS_FCERR) {
+			ctx->hsfsts_on_last_error = hsfsts;
+			return E_HW_ERROR;
+		}
+	/* TODO: set up timer and abort on timeout */
+	} while (!(hsfsts & SPIBAR_HSFSTS_FDONE));
+
+	return SUCCESS;
+}
+
+/* Execute SPI transfer. This is a blocking call. */
+static int exec_sync_hwseq_xfer(struct spi_ctx *ctx, uint32_t hsfsts_cycle,
+				 uint32_t flash_addr, size_t len)
+{
+	int ret;
+	start_hwseq_xfer(ctx, hsfsts_cycle, flash_addr, len);
+	ret = wait_for_hwseq_xfer(ctx);
+	if (ret != SUCCESS) {
+		const char *reason = (ret == E_TIMEOUT) ? "timeout" : "error";
+		print_xfer_error(ctx, reason, flash_addr);
+	}
+	return ret;
+}
+
+unsigned int spi_crop_chunk(unsigned int cmd_len, unsigned int buf_len)
+{
+	return MIN(buf_len, SPIBAR_FDATA_FIFO_SIZE);
+}
+
+int spi_xfer(struct spi_slave *slave, const void *dout,
+		unsigned int bytesout, void *din, unsigned int bytesin)
+{
+	printk(BIOS_DEBUG, "NOT IMPLEMENTED: %s() !!!\n", __func__);
+	return E_NOT_IMPLEMENTED;
+}
+
+/*
+ * Write-protection status for BIOS region (BIOS_CONTROL register):
+ * EISS/WPD bits	00	01	10	11
+ * 			--	--	--	--
+ * normal mode		RO	RW	RO	RO
+ * SMM mode		RO	RW	RO	RW
+ */
+void spi_init(void)
+{
+	uint32_t bios_ctl;
+
+	BOILERPLATE_CREATE_CTX(ctx);
+
+	bios_ctl = pci_read_config32(ctx->pci_dev, SPIBAR_BIOS_CONTROL);
+	bios_ctl |= SPIBAR_BIOS_CONTROL_WPD;
+	bios_ctl &= ~SPIBAR_BIOS_CONTROL_EISS;
+	pci_write_config32(ctx->pci_dev, SPIBAR_BIOS_CONTROL, bios_ctl);
+}
+
+int spi_claim_bus(struct spi_slave *slave)
+{
+	/* There's nothing we need to to here. */
+	return 0;
+}
+
+void spi_release_bus(struct spi_slave *slave)
+{
+	/* No magic needed here. */
+}
+
+static int nuclear_spi_erase(struct spi_flash *flash, uint32_t offset, size_t len)
+{
+	int ret;
+	size_t erase_size;
+	uint32_t erase_cycle;
+
+	BOILERPLATE_CREATE_CTX(ctx);
+
+	if (!IS_ALIGNED(offset, 4 * KiB) || !IS_ALIGNED(len, 4 * KiB)) {
+		printk(BIOS_ERR, "BUG! SPI erase region not sector aligned.\n");
+		return E_ARGUMENT;
+	}
+
+	while (len) {
+		if (IS_ALIGNED(offset, 64 * KiB) && (len >= 64 * KiB)) {
+			erase_size = 64 * KiB;
+			erase_cycle = SPIBAR_HSFSTS_CYCLE_64K_ERASE;
+		} else {
+			erase_size = 4 * KiB;
+			erase_cycle = SPIBAR_HSFSTS_CYCLE_4K_ERASE;
+		}
+		printk(BIOS_SPEW, "Erasing flash addr %x + %zu KiB\n",
+		       offset, erase_size / KiB);
+
+		ret = exec_sync_hwseq_xfer(ctx, erase_cycle, offset, 0);
+		if (ret != SUCCESS)
+			return ret;
+
+		offset += erase_size;
+		len -= erase_size;
+	}
+
+	return SUCCESS;
+}
+
+static int nuclear_spi_read(struct spi_flash *flash, uint32_t addr, size_t len, void *buf)
+{
+	int ret;
+	size_t xfer_len;
+	uint8_t *data = buf;
+
+	BOILERPLATE_CREATE_CTX(ctx);
+
+	while (len) {
+		xfer_len = min(len, SPIBAR_FDATA_FIFO_SIZE);
+
+		ret = exec_sync_hwseq_xfer(ctx, SPIBAR_HSFSTS_CYCLE_READ,
+						addr, xfer_len);
+		if (ret != SUCCESS)
+			return ret;
+
+		drain_xfer_fifo(ctx, data, xfer_len);
+
+		addr += xfer_len;
+		data += xfer_len;
+		len -= xfer_len;
+	}
+
+	return SUCCESS;
+}
+
+static int nuclear_spi_write(struct spi_flash *flash,
+			   uint32_t addr, size_t len, const void *buf)
+{
+	int ret;
+	size_t xfer_len;
+	const uint8_t *data = buf;
+
+	BOILERPLATE_CREATE_CTX(ctx);
+
+	while (len) {
+		xfer_len = min(len, SPIBAR_FDATA_FIFO_SIZE);
+		fill_xfer_fifo(ctx, data, xfer_len);
+
+		ret = exec_sync_hwseq_xfer(ctx, SPIBAR_HSFSTS_CYCLE_WRITE,
+						addr, xfer_len);
+		if (ret != SUCCESS)
+			return ret;
+
+		addr += xfer_len;
+		data += xfer_len;
+		len -= xfer_len;
+	}
+
+	return SUCCESS;
+}
+
+static int nuclear_spi_status(struct spi_flash *flash, uint8_t *reg)
+{
+	printk(BIOS_DEBUG, "NOT IMPLEMENTED: %s() !!!\n", __func__);
+	return E_NOT_IMPLEMENTED;
+}
+
+/*
+ * We can't use FDOC and FDOD to read FLCOMP, as previous platforms did.
+ * For details see:
+ * Ch 31, SPI: p. 194
+ * The size of the flash component is always taken from density field in the
+ * SFDP table. FLCOMP.C0DEN is no longer used by the Flash Controller.
+ */
+static struct spi_flash *nuclear_flash_probe(struct spi_slave *spi)
+{
+	BOILERPLATE_CREATE_CTX(ctx);
+	struct spi_flash *flash;
+	uint32_t flash_bits;
+
+	flash = malloc(sizeof(*flash));
+	if (!flash) {
+		printk(BIOS_ERR, "%s(): Could not allocate memory\n", __func__);
+		return NULL;
+	}
+
+	/*
+	 * bytes = (bits + 1) / 8;
+	 * But we need to do the addition in a way which doesn't overflow for
+	 * 4 Gbit devices (flash_bits == 0xffffffff).
+	 */
+	/* FIXME: Don't hardcode 0x04 ? */
+	flash_bits = read_spi_sfdp_param(ctx, 0x04);
+	flash->size = (flash_bits >> 3) + 1;
+
+	flash->spi = spi;
+	flash->name = "Apollolake hardware sequencer";
+
+	/* Can erase both 4 KiB and 64 KiB chunks. Declare the smaller size. */
+	flash->sector_size = 4 * KiB;
+	/*
+	 * FIXME: Get erase+cmd, and status_cmd from SFDP.
+	 *
+	 * flash->erase_cmd = ???
+	 * flash->status_cmd = ???
+	 */
+
+	flash->write = nuclear_spi_write;
+	flash->erase = nuclear_spi_erase;
+	flash->read = nuclear_spi_read;
+	flash->status = nuclear_spi_status;
+
+	return flash;
+}
+
+struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs)
+{
+	BOILERPLATE_CREATE_CTX(ctx);
+
+	/* This is special hardware. We expect bus 0 and CS line 0 here. */
+	if ((bus != 0) || (cs != 0))
+		return NULL;
+
+	struct spi_slave *slave = malloc(sizeof(*slave));
+
+	if (!slave) {
+		printk(BIOS_ERR, "%s(): Could not allocate memory\n", __func__);
+		return NULL;
+	}
+
+	memset(slave, 0, sizeof(*slave));
+
+	slave->bus = bus;
+	slave->cs = cs;
+	slave->programmer_specific_probe = nuclear_flash_probe;
+	slave->force_programmer_specific = 1;
+
+	return slave;
+}



More information about the coreboot-gerrit mailing list