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@intel.com>
Signed-off-by: Andrey Petrov <andrey.petrov@intel.com>
Reviewed-on: https://review.coreboot.org/14246
Tested-by: build bot (Jenkins)
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
This commit is contained in:
Alexandru Gagniuc 2016-02-24 15:08:23 -08:00 committed by Martin Roth
parent b8671eafde
commit 0581a6759d
5 changed files with 439 additions and 0 deletions

View File

@ -32,6 +32,7 @@ config CPU_SPECIFIC_OPTIONS
select REG_SCRIPT select REG_SCRIPT
select RELOCATABLE_RAMSTAGE # Build fails if this is not selected select RELOCATABLE_RAMSTAGE # Build fails if this is not selected
select SOC_INTEL_COMMON select SOC_INTEL_COMMON
select SPI_FLASH
select UDELAY_TSC select UDELAY_TSC
select TSC_CONSTANT_RATE select TSC_CONSTANT_RATE
select UDELAY_TSC select UDELAY_TSC

View File

@ -38,6 +38,7 @@ ramstage-y += memmap.c
ramstage-y += mmap_boot.c ramstage-y += mmap_boot.c
ramstage-y += uart.c ramstage-y += uart.c
ramstage-y += northbridge.c ramstage-y += northbridge.c
ramstage-y += spi.c
postcar-y += exit_car.S postcar-y += exit_car.S
postcar-y += memmap.c postcar-y += memmap.c

View File

@ -36,5 +36,6 @@
#define P2SB_DEV PCI_DEV(0, 0xd, 0) #define P2SB_DEV PCI_DEV(0, 0xd, 0)
#define PMC_DEV PCI_DEV(0, 0xd, 1) #define PMC_DEV PCI_DEV(0, 0xd, 1)
#define SPI_DEV PCI_DEV(0, 0xd, 2)
#endif #endif

View File

@ -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

View File

@ -0,0 +1,370 @@
/*
* This file is part of the coreboot project.
*
* Copyright (C) 2016 Intel Corp.
* (Written by Alexandru Gagniuc <alexandrux.gagniuc@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;
}