cygnus: add QSPI driver

The driver uses the MSPI controller to read/write to/from SPI flash

BUG=chrome-os-partner:35811
BRANCH=boradcom-firmware
TEST=bootblock loads and executes verstage

Change-Id: I34c7882170e4f89bee1b6001563c09b16dfea8ca
Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Original-Commit-Id: 8c3b156019df429e9d12728224ed4eec8436f415
Original-Signed-off-by: Corneliu Doban <cdoban@broadcom.com>
Original-Reviewed-on: https://chrome-internal-review.googlesource.com/199776
Original-Reviewed-by: Scott Branden <sbranden@broadcom.com>
Original-Tested-by: Corneliu Doban <cdoban@broadcom.com>
Original-Commit-Queue: Corneliu Doban <cdoban@broadcom.com>
Original-Change-Id: Ice798ec76011ee47e13174b4c5534b0d0bc8b4ad
Original-Reviewed-on: https://chromium-review.googlesource.com/256414
Original-Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Original-Tested-by: Daisuke Nojiri <dnojiri@chromium.org>
Original-Commit-Queue: Daisuke Nojiri <dnojiri@chromium.org>
Reviewed-on: http://review.coreboot.org/9849
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
This commit is contained in:
Corneliu Doban 2015-02-18 17:25:20 -08:00 committed by Patrick Georgi
parent 82a7bc45f7
commit b048432578
3 changed files with 292 additions and 8 deletions

View File

@ -30,6 +30,8 @@ config BOARD_SPECIFIC_OPTIONS # dummy
select SOC_BROADCOM_CYGNUS
select SPI_FLASH
select SPI_FLASH_SPANSION
select SPI_FLASH_STMICRO # required for the reference board BCM958305K
select SPI_ATOMIC_SEQUENCING
config MAINBOARD_DIR
string
@ -49,7 +51,7 @@ config VBOOT_RAMSTAGE_INDEX
config BOOT_MEDIA_SPI_BUS
int
default 2
default 0
config DRAM_SIZE_MB
int

View File

@ -23,4 +23,6 @@
#define IPROC_PERIPH_BASE 0x19020000
#define IPROC_PERIPH_GLB_TIM_REG_BASE (IPROC_PERIPH_BASE + 0x200)
#define IPROC_QSPI_BASE 0x18047000
#endif /* __SOC_BROADCOM_CYGNUS_ADDRESSMAP_H__ */

View File

@ -1,7 +1,5 @@
/*
* This file is part of the coreboot project.
*
* Copyright 2015 Google Inc.
* Copyright (C) 2015 Broadcom Corporation
*
* 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
@ -17,25 +15,307 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stddef.h>
#include <arch/io.h>
#include <timer.h>
#include <delay.h>
#include <stdlib.h>
#include <spi-generic.h>
#include <spi_flash.h>
#include <soc/addressmap.h>
#define IPROC_QSPI_CLK 100000000
/* SPI mode flags */
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* original MicroWire */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define QSPI_MAX_HZ 50000000
#define QSPI_MODE SPI_MODE_3
#define QSPI_WAIT_TIMEOUT 200U /* msec */
/* Controller attributes */
#define SPBR_MIN 8U
#define SPBR_MAX 255U
#define NUM_TXRAM 32
#define NUM_RXRAM 32
#define NUM_CDRAM 16
/*
* Register fields
*/
#define MSPI_SPCR0_MSB_BITS_8 0x00000020
/* BSPI registers */
#define BSPI_MAST_N_BOOT_CTRL_REG 0x008
#define BSPI_BUSY_STATUS_REG 0x00c
/* MSPI registers */
#define MSPI_SPCR0_LSB_REG 0x200
#define MSPI_SPCR0_MSB_REG 0x204
#define MSPI_SPCR1_LSB_REG 0x208
#define MSPI_SPCR1_MSB_REG 0x20c
#define MSPI_NEWQP_REG 0x210
#define MSPI_ENDQP_REG 0x214
#define MSPI_SPCR2_REG 0x218
#define MSPI_STATUS_REG 0x220
#define MSPI_CPTQP_REG 0x224
#define MSPI_TXRAM_REG 0x240
#define MSPI_RXRAM_REG 0x2c0
#define MSPI_CDRAM_REG 0x340
#define MSPI_WRITE_LOCK_REG 0x380
#define MSPI_DISABLE_FLUSH_GEN_REG 0x384
/*
* Register access macros
*/
#define REG_RD(x) read32(x)
#define REG_WR(x, y) write32((x), (y))
#define REG_CLR(x, y) REG_WR((x), REG_RD(x) & ~(y))
#define REG_SET(x, y) REG_WR((x), REG_RD(x) | (y))
/* QSPI private data */
struct qspi_priv {
/* Slave entry */
struct spi_slave slave;
/* Specified SPI parameters */
unsigned int max_hz;
unsigned int spi_mode;
int mspi_enabled;
int mspi_16bit;
int bus_claimed;
/* Registers */
void *reg;
};
static struct qspi_priv qspi_slave;
/* Macro to get the private data */
#define to_qspi_slave(s) container_of(s, struct qspi_priv, slave)
struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs)
{
return NULL;
struct qspi_priv *priv = &qspi_slave;
unsigned int spbr;
priv->slave.bus = bus;
priv->slave.cs = cs;
priv->max_hz = QSPI_MAX_HZ;
priv->spi_mode = QSPI_MODE;
priv->reg = (void *)(IPROC_QSPI_BASE);
priv->mspi_enabled = 0;
priv->bus_claimed = 0;
/* MSPI: Basic hardware initialization */
REG_WR(priv->reg + MSPI_SPCR1_LSB_REG, 0);
REG_WR(priv->reg + MSPI_SPCR1_MSB_REG, 0);
REG_WR(priv->reg + MSPI_NEWQP_REG, 0);
REG_WR(priv->reg + MSPI_ENDQP_REG, 0);
REG_WR(priv->reg + MSPI_SPCR2_REG, 0);
/* MSPI: SCK configuration */
spbr = (IPROC_QSPI_CLK - 1) / (2 * priv->max_hz) + 1;
REG_WR(priv->reg + MSPI_SPCR0_LSB_REG,
MAX(MIN(spbr, SPBR_MAX), SPBR_MIN));
/* MSPI: Mode configuration (8 bits by default) */
priv->mspi_16bit = 0;
REG_WR(priv->reg + MSPI_SPCR0_MSB_REG,
0x80 | /* Master */
(8 << 2) | /* 8 bits per word */
(priv->spi_mode & 3)); /* mode: CPOL / CPHA */
return &priv->slave;
}
static int mspi_enable(struct qspi_priv *priv)
{
struct stopwatch sw;
/* Switch to MSPI if not yet */
if ((REG_RD(priv->reg + BSPI_MAST_N_BOOT_CTRL_REG) & 1) == 0) {
stopwatch_init_msecs_expire(&sw, QSPI_WAIT_TIMEOUT);
while (!stopwatch_expired(&sw)) {
if ((REG_RD(priv->reg + BSPI_BUSY_STATUS_REG) & 1)
== 0) {
REG_WR(priv->reg + BSPI_MAST_N_BOOT_CTRL_REG,
1);
udelay(1);
break;
}
udelay(1);
}
if (REG_RD(priv->reg + BSPI_MAST_N_BOOT_CTRL_REG) != 1)
return -1;
}
priv->mspi_enabled = 1;
return 0;
}
int spi_claim_bus(struct spi_slave *slave)
{
struct qspi_priv *priv = to_qspi_slave(slave);
if (priv->bus_claimed)
return -1;
if (!priv->mspi_enabled)
if (mspi_enable(priv))
return -1;
/* MSPI: Enable write lock */
REG_WR(priv->reg + MSPI_WRITE_LOCK_REG, 1);
priv->bus_claimed = 1;
return 0;
}
void spi_release_bus(struct spi_slave *slave)
{
struct qspi_priv *priv = to_qspi_slave(slave);
/* MSPI: Disable write lock */
REG_WR(priv->reg + MSPI_WRITE_LOCK_REG, 0);
priv->bus_claimed = 0;
}
int spi_xfer(struct spi_slave *slave, const void *dout,
unsigned out_bytes, void *din, unsigned in_bytes)
#define RXRAM_16B(p, i) (REG_RD((p)->reg + MSPI_RXRAM_REG + ((i) << 2)) & 0xff)
#define RXRAM_8B(p, i) (REG_RD((p)->reg + MSPI_RXRAM_REG + \
((((i) << 1) + 1) << 2)) & 0xff)
int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bytesout,
void *din, unsigned int bytesin)
{
struct qspi_priv *priv = to_qspi_slave(slave);
const u8 *tx = (const u8 *)dout;
u8 *rx = (u8 *)din;
unsigned int bytes = bytesout + bytesin;
unsigned int rx_idx = 0;
unsigned int tx_idx = 0;
unsigned int in = 0;
unsigned int chunk;
unsigned int queues;
unsigned int i;
struct stopwatch sw;
if (!priv->bus_claimed)
return -1;
if (bytes & 1) {
/* Use 8-bit queue for odd-bytes transfer */
if (priv->mspi_16bit) {
REG_SET(priv->reg + MSPI_SPCR0_MSB_REG,
MSPI_SPCR0_MSB_BITS_8);
priv->mspi_16bit = 0;
}
} else {
/* Use 16-bit queue for even-bytes transfer */
if (!priv->mspi_16bit) {
REG_CLR(priv->reg + MSPI_SPCR0_MSB_REG,
MSPI_SPCR0_MSB_BITS_8);
priv->mspi_16bit = 1;
}
}
while (bytes) {
/* Separate code for 16bit and 8bit transfers for performance */
if (priv->mspi_16bit) {
/* Determine how many bytes to process this time */
chunk = min(bytes, NUM_CDRAM * 2);
queues = (chunk - 1) / 2 + 1;
bytes -= chunk;
/* Fill CDRAMs */
for (i = 0; i < queues; i++)
REG_WR(priv->reg + MSPI_CDRAM_REG + (i << 2),
0xc2);
/* Fill TXRAMs */
for (i = 0; i < chunk; i++) {
REG_WR(priv->reg + MSPI_TXRAM_REG + (i << 2),
(tx && (tx_idx < bytesout)) ?
tx[tx_idx] : 0xff);
tx_idx++;
}
} else {
/* Determine how many bytes to process this time */
chunk = min(bytes, NUM_CDRAM);
queues = chunk;
bytes -= chunk;
/* Fill CDRAMs and TXRAMS */
for (i = 0; i < chunk; i++) {
REG_WR(priv->reg + MSPI_CDRAM_REG + (i << 2),
0x82);
REG_WR(priv->reg + MSPI_TXRAM_REG + (i << 3),
(tx && (tx_idx < bytesout)) ?
tx[tx_idx] : 0xff);
tx_idx++;
}
}
/* Setup queue pointers */
REG_WR(priv->reg + MSPI_NEWQP_REG, 0);
REG_WR(priv->reg + MSPI_ENDQP_REG, queues - 1);
/* Deassert CS */
if (bytes == 0)
REG_CLR(priv->reg + MSPI_CDRAM_REG +
((queues - 1) << 2), 0x0);
/* Kick off */
REG_WR(priv->reg + MSPI_STATUS_REG, 0);
REG_WR(priv->reg + MSPI_SPCR2_REG, 0xc0); /* cont | spe */
/* Wait for completion */
stopwatch_init_msecs_expire(&sw, QSPI_WAIT_TIMEOUT);
while (!stopwatch_expired(&sw)) {
if (REG_RD(priv->reg + MSPI_STATUS_REG) & 1)
break;
}
if ((REG_RD(priv->reg + MSPI_STATUS_REG) & 1) == 0) {
/* Make sure no operation is in progress */
REG_WR(priv->reg + MSPI_SPCR2_REG, 0);
udelay(1);
return -1;
}
/* Read data */
if (rx) {
if (priv->mspi_16bit) {
for (i = 0; i < chunk; i++) {
if (rx_idx >= bytesout) {
rx[in] = RXRAM_16B(priv, i);
in++;
}
rx_idx++;
}
} else {
for (i = 0; i < chunk; i++) {
if (rx_idx >= bytesout) {
rx[in] = RXRAM_8B(priv, i);
in++;
}
rx_idx++;
}
}
}
}
return 0;
}
unsigned int spi_crop_chunk(unsigned int cmd_len, unsigned int buf_len)
{
return min(65535, buf_len);
}