/* * 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. */ #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) { 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); }