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>
322 lines
7.8 KiB
C
322 lines
7.8 KiB
C
/*
|
|
* 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
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
#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);
|
|
}
|