From b048432578871abe69c7eb54404089c42f35c2f6 Mon Sep 17 00:00:00 2001 From: Corneliu Doban Date: Wed, 18 Feb 2015 17:25:20 -0800 Subject: 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 Original-Commit-Id: 8c3b156019df429e9d12728224ed4eec8436f415 Original-Signed-off-by: Corneliu Doban Original-Reviewed-on: https://chrome-internal-review.googlesource.com/199776 Original-Reviewed-by: Scott Branden Original-Tested-by: Corneliu Doban Original-Commit-Queue: Corneliu Doban Original-Change-Id: Ice798ec76011ee47e13174b4c5534b0d0bc8b4ad Original-Reviewed-on: https://chromium-review.googlesource.com/256414 Original-Reviewed-by: Aaron Durbin Original-Tested-by: Daisuke Nojiri Original-Commit-Queue: Daisuke Nojiri Reviewed-on: http://review.coreboot.org/9849 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer --- src/soc/broadcom/cygnus/include/soc/addressmap.h | 2 + src/soc/broadcom/cygnus/spi.c | 294 ++++++++++++++++++++++- 2 files changed, 289 insertions(+), 7 deletions(-) (limited to 'src/soc/broadcom/cygnus') diff --git a/src/soc/broadcom/cygnus/include/soc/addressmap.h b/src/soc/broadcom/cygnus/include/soc/addressmap.h index ef6a709d60..a9af3d805f 100644 --- a/src/soc/broadcom/cygnus/include/soc/addressmap.h +++ b/src/soc/broadcom/cygnus/include/soc/addressmap.h @@ -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__ */ diff --git a/src/soc/broadcom/cygnus/spi.c b/src/soc/broadcom/cygnus/spi.c index 16e09ae79f..866cff5c0c 100644 --- a/src/soc/broadcom/cygnus/spi.c +++ b/src/soc/broadcom/cygnus/spi.c @@ -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 +#include +#include +#include +#include #include +#include +#include + +#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); +} -- cgit v1.2.3