summaryrefslogtreecommitdiff
path: root/src/soc/broadcom
diff options
context:
space:
mode:
authorCorneliu Doban <cdoban@broadcom.com>2015-02-18 17:25:20 -0800
committerPatrick Georgi <pgeorgi@google.com>2015-04-21 08:28:53 +0200
commitb048432578871abe69c7eb54404089c42f35c2f6 (patch)
tree20909190c5e29d441f28110871add1efd2726fed /src/soc/broadcom
parent82a7bc45f77a8223fc3072e8c76de80685767186 (diff)
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>
Diffstat (limited to 'src/soc/broadcom')
-rw-r--r--src/soc/broadcom/cygnus/include/soc/addressmap.h2
-rw-r--r--src/soc/broadcom/cygnus/spi.c294
2 files changed, 289 insertions, 7 deletions
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 <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);
+}