Files
system76-coreboot/src/soc/intel/braswell/spi.c
Furquan Shaikh c28984d9ea spi: Clean up SPI flash driver interface
RW flag was added to spi_slave structure to get around a requirement on
some AMD flash controllers that need to group together all spi volatile
operations (write/erase). This rw flag is not a property or attribute of
the SPI slave or controller. Thus, instead of saving it in spi_slave
structure, clean up the SPI flash driver interface. This allows
chipsets/mainboards (that require volatile operations to be grouped) to
indicate beginning and end of such grouped operations.

New user APIs are added to allow users to perform probe, read, write,
erase, volatile group begin and end operations. Callbacks defined in
spi_flash structure are expected to be used only by the SPI flash
driver. Any chipset that requires grouping of volatile operations can
select the newly added Kconfig option SPI_FLASH_HAS_VOLATILE_GROUP and
define callbacks for chipset_volatile_group_{begin,end}.

spi_claim_bus/spi_release_bus calls have been removed from the SPI flash
chip drivers which end up calling do_spi_flash_cmd since it already has
required calls for claiming and releasing SPI bus before performing a
read/write operation.

BUG=None
BRANCH=None
TEST=Compiles successfully.

Change-Id: Idfc052e82ec15b6c9fa874cee7a61bd06e923fbf
Signed-off-by: Furquan Shaikh <furquan@chromium.org>
Reviewed-on: https://review.coreboot.org/17462
Tested-by: build bot (Jenkins)
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
2016-11-22 17:32:09 +01:00

622 lines
14 KiB
C

/*
* Copyright (c) 2013 Google Inc.
* Copyright (C) 2015 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.
*
* This program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/* This file is derived from the flashrom project. */
#include <arch/io.h>
#include <bootstate.h>
#include <console/console.h>
#include <delay.h>
#include <device/pci_ids.h>
#include <rules.h>
#include <soc/lpc.h>
#include <soc/pci_devs.h>
#include <spi-generic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#if ENV_SMM
#define pci_read_config_byte(dev, reg, targ)\
*(targ) = pci_read_config8(dev, reg)
#define pci_read_config_word(dev, reg, targ)\
*(targ) = pci_read_config16(dev, reg)
#define pci_read_config_dword(dev, reg, targ)\
*(targ) = pci_read_config32(dev, reg)
#define pci_write_config_byte(dev, reg, val)\
pci_write_config8(dev, reg, val)
#define pci_write_config_word(dev, reg, val)\
pci_write_config16(dev, reg, val)
#define pci_write_config_dword(dev, reg, val)\
pci_write_config32(dev, reg, val)
#else /* ENV_SMM */
#include <device/device.h>
#include <device/pci.h>
#define pci_read_config_byte(dev, reg, targ)\
*(targ) = pci_read_config8(dev, reg)
#define pci_read_config_word(dev, reg, targ)\
*(targ) = pci_read_config16(dev, reg)
#define pci_read_config_dword(dev, reg, targ)\
*(targ) = pci_read_config32(dev, reg)
#define pci_write_config_byte(dev, reg, val)\
pci_write_config8(dev, reg, val)
#define pci_write_config_word(dev, reg, val)\
pci_write_config16(dev, reg, val)
#define pci_write_config_dword(dev, reg, val)\
pci_write_config32(dev, reg, val)
#endif /* ENV_SMM */
typedef struct spi_slave ich_spi_slave;
static int ichspi_lock = 0;
typedef struct ich9_spi_regs {
uint32_t bfpr;
uint16_t hsfs;
uint16_t hsfc;
uint32_t faddr;
uint32_t _reserved0;
uint32_t fdata[16];
uint32_t frap;
uint32_t freg[5];
uint32_t _reserved1[3];
uint32_t pr[5];
uint32_t _reserved2[2];
uint8_t ssfs;
uint8_t ssfc[3];
uint16_t preop;
uint16_t optype;
uint8_t opmenu[8];
} __attribute__((packed)) ich9_spi_regs;
typedef struct ich_spi_controller {
int locked;
uint8_t *opmenu;
int menubytes;
uint16_t *preop;
uint16_t *optype;
uint32_t *addr;
uint8_t *data;
unsigned databytes;
uint8_t *status;
uint16_t *control;
} ich_spi_controller;
static ich_spi_controller cntlr;
enum {
SPIS_SCIP = 0x0001,
SPIS_GRANT = 0x0002,
SPIS_CDS = 0x0004,
SPIS_FCERR = 0x0008,
SSFS_AEL = 0x0010,
SPIS_LOCK = 0x8000,
SPIS_RESERVED_MASK = 0x7ff0,
SSFS_RESERVED_MASK = 0x7fe2
};
enum {
SPIC_SCGO = 0x000002,
SPIC_ACS = 0x000004,
SPIC_SPOP = 0x000008,
SPIC_DBC = 0x003f00,
SPIC_DS = 0x004000,
SPIC_SME = 0x008000,
SSFC_SCF_MASK = 0x070000,
SSFC_RESERVED = 0xf80000
};
enum {
HSFS_FDONE = 0x0001,
HSFS_FCERR = 0x0002,
HSFS_AEL = 0x0004,
HSFS_BERASE_MASK = 0x0018,
HSFS_BERASE_SHIFT = 3,
HSFS_SCIP = 0x0020,
HSFS_FDOPSS = 0x2000,
HSFS_FDV = 0x4000,
HSFS_FLOCKDN = 0x8000
};
enum {
HSFC_FGO = 0x0001,
HSFC_FCYCLE_MASK = 0x0006,
HSFC_FCYCLE_SHIFT = 1,
HSFC_FDBC_MASK = 0x3f00,
HSFC_FDBC_SHIFT = 8,
HSFC_FSMIE = 0x8000
};
enum {
SPI_OPCODE_TYPE_READ_NO_ADDRESS = 0,
SPI_OPCODE_TYPE_WRITE_NO_ADDRESS = 1,
SPI_OPCODE_TYPE_READ_WITH_ADDRESS = 2,
SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS = 3
};
#if IS_ENABLED(CONFIG_DEBUG_SPI_FLASH)
static u8 readb_(void *addr)
{
u8 v = read8(addr);
printk(BIOS_DEBUG, "0x%p --> 0x%2.2x\n", addr, v);
return v;
}
static u16 readw_(void *addr)
{
u16 v = read16(addr);
printk(BIOS_DEBUG, "0x%p --> 0x%4.4x\n", addr, v);
return v;
}
static u32 readl_(void *addr)
{
u32 v = read32(addr);
printk(BIOS_DEBUG, "0x%p --> 0x%8.8x\n", addr, v);
return v;
}
static void writeb_(u8 b, void *addr)
{
printk(BIOS_DEBUG, "0x%p <-- 0x%2.2x\n", addr, b);
write8(addr, b);
}
static void writew_(u16 b, void *addr)
{
printk(BIOS_DEBUG, "0x%p <-- 0x%4.4x\n", addr, b);
write16(addr, b);
}
static void writel_(u32 b, void *addr)
{
printk(BIOS_DEBUG, "0x%p <-- 0x%8.8x\n", addr, b);
write32(addr, b);
}
#else /* CONFIG_DEBUG_SPI_FLASH ^^^ enabled vvv NOT enabled */
#define readb_(a) read8(a)
#define readw_(a) read16(a)
#define readl_(a) read32(a)
#define writeb_(val, addr) write8(addr, val)
#define writew_(val, addr) write16(addr, val)
#define writel_(val, addr) write32(addr, val)
#endif /* CONFIG_DEBUG_SPI_FLASH ^^^ NOT enabled */
static void write_reg(const void *value, void *dest, uint32_t size)
{
const uint8_t *bvalue = value;
uint8_t *bdest = dest;
while (size >= 4) {
writel_(*(const uint32_t *)bvalue, bdest);
bdest += 4; bvalue += 4; size -= 4;
}
while (size) {
writeb_(*bvalue, bdest);
bdest++; bvalue++; size--;
}
}
static void read_reg(void *src, void *value, uint32_t size)
{
uint8_t *bsrc = src;
uint8_t *bvalue = value;
while (size >= 4) {
*(uint32_t *)bvalue = readl_(bsrc);
bsrc += 4; bvalue += 4; size -= 4;
}
while (size) {
*bvalue = readb_(bsrc);
bsrc++; bvalue++; size--;
}
}
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs)
{
ich_spi_slave *slave = malloc(sizeof(*slave));
if (!slave) {
printk(BIOS_ERR, "ICH SPI: Bad allocation\n");
return NULL;
}
memset(slave, 0, sizeof(*slave));
slave->bus = bus;
slave->cs = cs;
return slave;
}
static ich9_spi_regs *spi_regs(void)
{
device_t dev;
uint32_t sbase;
#if ENV_SMM
dev = PCI_DEV(0, LPC_DEV, LPC_FUNC);
#else
dev = dev_find_slot(0, PCI_DEVFN(LPC_DEV, LPC_FUNC));
#endif
if (!dev) {
printk(BIOS_ERR, "%s: PCI device not found", __func__);
return NULL;
}
pci_read_config_dword(dev, SBASE, &sbase);
sbase &= ~0x1ff;
return (void *)sbase;
}
void spi_init(void)
{
ich9_spi_regs *ich9_spi;
ich9_spi = spi_regs();
if (!ich9_spi) {
printk(BIOS_ERR, "Not initialising spi as %s returned NULL\n",
__func__);
return;
}
ichspi_lock = readw_(&ich9_spi->hsfs) & HSFS_FLOCKDN;
cntlr.opmenu = ich9_spi->opmenu;
cntlr.menubytes = sizeof(ich9_spi->opmenu);
cntlr.optype = &ich9_spi->optype;
cntlr.addr = &ich9_spi->faddr;
cntlr.data = (uint8_t *)ich9_spi->fdata;
cntlr.databytes = sizeof(ich9_spi->fdata);
cntlr.status = &ich9_spi->ssfs;
cntlr.control = (uint16_t *)ich9_spi->ssfc;
cntlr.preop = &ich9_spi->preop;
}
static void spi_init_cb(void *unused)
{
spi_init();
}
BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_ENTRY, spi_init_cb, NULL);
int spi_claim_bus(struct spi_slave *slave)
{
/* Handled by ICH automatically. */
return 0;
}
void spi_release_bus(struct spi_slave *slave)
{
/* Handled by ICH automatically. */
}
typedef struct spi_transaction {
const uint8_t *out;
uint32_t bytesout;
uint8_t *in;
uint32_t bytesin;
uint8_t type;
uint8_t opcode;
uint32_t offset;
} spi_transaction;
static inline void spi_use_out(spi_transaction *trans, unsigned bytes)
{
trans->out += bytes;
trans->bytesout -= bytes;
}
static inline void spi_use_in(spi_transaction *trans, unsigned bytes)
{
trans->in += bytes;
trans->bytesin -= bytes;
}
static void spi_setup_type(spi_transaction *trans)
{
trans->type = 0xFF;
/* Try to guess spi type from read/write sizes. */
if (trans->bytesin == 0) {
if (trans->bytesout > 4)
/*
* If bytesin = 0 and bytesout > 4, we presume this is
* a write data operation, which is accompanied by an
* address.
*/
trans->type = SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS;
else
trans->type = SPI_OPCODE_TYPE_WRITE_NO_ADDRESS;
return;
}
if (trans->bytesout == 1) { /* and bytesin is > 0 */
trans->type = SPI_OPCODE_TYPE_READ_NO_ADDRESS;
return;
}
if (trans->bytesout == 4) { /* and bytesin is > 0 */
trans->type = SPI_OPCODE_TYPE_READ_WITH_ADDRESS;
}
/* Fast read command is called with 5 bytes instead of 4 */
if (trans->out[0] == SPI_OPCODE_FAST_READ && trans->bytesout == 5) {
trans->type = SPI_OPCODE_TYPE_READ_WITH_ADDRESS;
--trans->bytesout;
}
}
static int spi_setup_opcode(spi_transaction *trans)
{
uint16_t optypes;
uint8_t opmenu[cntlr.menubytes];
trans->opcode = trans->out[0];
spi_use_out(trans, 1);
if (!ichspi_lock) {
/* The lock is off, so just use index 0. */
writeb_(trans->opcode, cntlr.opmenu);
optypes = readw_(cntlr.optype);
optypes = (optypes & 0xfffc) | (trans->type & 0x3);
writew_(optypes, cntlr.optype);
return 0;
} else {
/* The lock is on. See if what we need is on the menu. */
uint8_t optype;
uint16_t opcode_index;
/* Write Enable is handled as atomic prefix */
if (trans->opcode == SPI_OPCODE_WREN)
return 0;
read_reg(cntlr.opmenu, opmenu, sizeof(opmenu));
for (opcode_index = 0; opcode_index < cntlr.menubytes;
opcode_index++) {
if (opmenu[opcode_index] == trans->opcode)
break;
}
if (opcode_index == cntlr.menubytes) {
printk(BIOS_DEBUG, "ICH SPI: Opcode %x not found\n",
trans->opcode);
return -1;
}
optypes = readw_(cntlr.optype);
optype = (optypes >> (opcode_index * 2)) & 0x3;
if (trans->type == SPI_OPCODE_TYPE_WRITE_NO_ADDRESS &&
optype == SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS &&
trans->bytesout >= 3) {
/* We guessed wrong earlier. Fix it up. */
trans->type = optype;
}
if (optype != trans->type) {
printk(BIOS_DEBUG, "ICH SPI: Transaction doesn't fit type %d\n",
optype);
return -1;
}
return opcode_index;
}
}
static int spi_setup_offset(spi_transaction *trans)
{
/* Separate the SPI address and data. */
switch (trans->type) {
case SPI_OPCODE_TYPE_READ_NO_ADDRESS:
case SPI_OPCODE_TYPE_WRITE_NO_ADDRESS:
return 0;
case SPI_OPCODE_TYPE_READ_WITH_ADDRESS:
case SPI_OPCODE_TYPE_WRITE_WITH_ADDRESS:
trans->offset = ((uint32_t)trans->out[0] << 16) |
((uint32_t)trans->out[1] << 8) |
((uint32_t)trans->out[2] << 0);
spi_use_out(trans, 3);
return 1;
default:
printk(BIOS_DEBUG, "Unrecognized SPI transaction type %#x\n",
trans->type);
return -1;
}
}
/*
* Wait for up to 400ms til status register bit(s) turn 1 (in case wait_til_set
* below is True) or 0. In case the wait was for the bit(s) to set - write
* those bits back, which would cause resetting them.
*
* Return the last read status value on success or -1 on failure.
*/
static int ich_status_poll(u16 bitmask, int wait_til_set)
{
int timeout = 40000; /* This will result in 400 ms */
u16 status = 0;
wait_til_set &= 1;
while (timeout--) {
status = readw_(cntlr.status);
if (wait_til_set ^ ((status & bitmask) == 0)) {
if (wait_til_set)
writew_((status & bitmask), cntlr.status);
return status;
}
udelay(10);
}
printk(BIOS_ERR, "ICH SPI: SCIP timeout, read %x, expected %x\n",
status, bitmask);
return -1;
}
unsigned int spi_crop_chunk(unsigned int cmd_len, unsigned int buf_len)
{
return min(cntlr.databytes, buf_len);
}
int spi_xfer(struct spi_slave *slave, const void *dout,
unsigned int bytesout, void *din, unsigned int bytesin)
{
uint16_t control;
int16_t opcode_index;
int with_address;
int status;
spi_transaction trans = {
dout, bytesout,
din, bytesin,
0xff, 0xff, 0
};
/* There has to always at least be an opcode. */
if (!bytesout || !dout) {
printk(BIOS_DEBUG, "ICH SPI: No opcode for transfer\n");
return -1;
}
/* Make sure if we read something we have a place to put it. */
if (bytesin != 0 && !din) {
printk(BIOS_DEBUG, "ICH SPI: Read but no target buffer\n");
return -1;
}
if (ich_status_poll(SPIS_SCIP, 0) == -1)
return -1;
writew_(SPIS_CDS | SPIS_FCERR, cntlr.status);
spi_setup_type(&trans);
opcode_index = spi_setup_opcode(&trans);
if (opcode_index < 0)
return -1;
with_address = spi_setup_offset(&trans);
if (with_address < 0)
return -1;
if (trans.opcode == SPI_OPCODE_WREN) {
/*
* Treat Write Enable as Atomic Pre-Op if possible
* in order to prevent the Management Engine from
* issuing a transaction between WREN and DATA.
*/
if (!ichspi_lock)
writew_(trans.opcode, cntlr.preop);
return 0;
}
/* Preset control fields */
control = SPIC_SCGO | ((opcode_index & 0x07) << 4);
/* Issue atomic preop cycle if needed */
if (readw_(cntlr.preop))
control |= SPIC_ACS;
if (!trans.bytesout && !trans.bytesin) {
/* SPI addresses are 24 bit only */
if (with_address)
writel_(trans.offset & 0x00FFFFFF, cntlr.addr);
/*
* This is a 'no data' command (like Write Enable), its
* bytesout size was 1, decremented to zero while executing
* spi_setup_opcode() above. Tell the chip to send the
* command.
*/
writew_(control, cntlr.control);
/* wait for the result */
status = ich_status_poll(SPIS_CDS | SPIS_FCERR, 1);
if (status == -1)
return -1;
if (status & SPIS_FCERR) {
printk(BIOS_ERR, "ICH SPI: Command transaction error\n");
return -1;
}
return 0;
}
/*
* Check if this is a write command attempting to transfer more bytes
* than the controller can handle. Iterations for writes are not
* supported here because each SPI write command needs to be preceded
* and followed by other SPI commands, and this sequence is controlled
* by the SPI chip driver.
*/
if (trans.bytesout > cntlr.databytes) {
printk(BIOS_DEBUG,
"ICH SPI: Too much to write. Does your SPI chip driver use"
" CONTROLLER_PAGE_LIMIT?\n");
return -1;
}
/*
* Read or write up to databytes bytes at a time until everything has
* been sent.
*/
while (trans.bytesout || trans.bytesin) {
uint32_t data_length;
/* SPI addresses are 24 bit only */
writel_(trans.offset & 0x00FFFFFF, cntlr.addr);
if (trans.bytesout)
data_length = min(trans.bytesout, cntlr.databytes);
else
data_length = min(trans.bytesin, cntlr.databytes);
/* Program data into FDATA0 to N */
if (trans.bytesout) {
write_reg(trans.out, cntlr.data, data_length);
spi_use_out(&trans, data_length);
if (with_address)
trans.offset += data_length;
}
/* Add proper control fields' values */
control &= ~((cntlr.databytes - 1) << 8);
control |= SPIC_DS;
control |= (data_length - 1) << 8;
/* write it */
writew_(control, cntlr.control);
/* Wait for Cycle Done Status or Flash Cycle Error. */
status = ich_status_poll(SPIS_CDS | SPIS_FCERR, 1);
if (status == -1)
return -1;
if (status & SPIS_FCERR) {
printk(BIOS_ERR, "ICH SPI: Data transaction error\n");
return -1;
}
if (trans.bytesin) {
read_reg(cntlr.data, trans.in, data_length);
spi_use_in(&trans, data_length);
if (with_address)
trans.offset += data_length;
}
}
/* Clear atomic preop now that xfer is done */
writew_(0, cntlr.preop);
return 0;
}