| /* |
| * 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; |
| } |