summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Tomsich <philipp.tomsich@theobroma-systems.com>2017-03-01 10:42:39 +0100
committerPhilipp Tomsich <philipp.tomsich@theobroma-systems.com>2017-03-09 01:40:21 +0100
commitd78746fdd7a447a21c346081cd0fc67c1fb5ae96 (patch)
treedfbde295bbf092b6012d35dc7328a7af869a12f3
parentc40cccd0c8cce2ffe20811454baeea2d467fe8d1 (diff)
sunxi_spi: add support for dual-IO flashes
The SPI controller in the Allwinner A64 SoC supports dual-IO for the RX phase of transmission. This can be used with the command 'fast read dual output' (cmd, addr and dummy byte are transmitted in single-IO mode; data is received in dual-IO mode) to quickly read out SPI flashes, when the device-tree marks the flash as having 'spi-rx-bus-width = <2>'. Unfortunately, the SPI-NOR flash layer in U-Boot does not manage the single-IO and dual-IO transition (partially due to the fact that spi_xfer(...) does not allow to convery such information), but correctly chooses the FAST_READ_DUAL_OUTPUT (0x3b) opcode. The net result of this is that a dual-IO read is initiated, but the data reception will capture only every other bit... This change puts a temporary fix in place, which identifies a 0x3b opcode being sent in a transaction with a SPI flash and then manages the switching to dual-IO within the driver. This change should be reverted, once more permanent solutions in the higher layers and in the SPI driver model have been agreed on and have been put in place. Tested on an A64 (sun50iw1p1) against a Winbond W25Q80DV flash at up to 100MHz (i.e. 200MBit/s read bursts). X-AffectedPlatforms: A64-uQ7 Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
-rw-r--r--drivers/spi/sunxi_spi.c68
1 files changed, 67 insertions, 1 deletions
diff --git a/drivers/spi/sunxi_spi.c b/drivers/spi/sunxi_spi.c
index f26becf368..871c88d871 100644
--- a/drivers/spi/sunxi_spi.c
+++ b/drivers/spi/sunxi_spi.c
@@ -24,6 +24,9 @@
DECLARE_GLOBAL_DATA_PTR;
+/* The SPI flash opcode for a FAST READ DUAL OUTPUT operation. */
+#define CMD_READ_DUAL_OUTPUT_FAST 0x3b
+
struct sunxi_spi_platdata {
void *base;
unsigned int max_hz;
@@ -47,7 +50,13 @@ struct sunxi_spi_driverdata {
unsigned int fifo_depth;
};
+enum {
+ NONE = 0,
+ OPC_READ_DUAL_CMD,
+};
+
struct sunxi_spi_privdata {
+ int transaction_type;
ulong last_transaction_us; /* Time of last transaction end */
unsigned int hz_requested; /* last requested bitrate */
unsigned int hz_actual; /* currently set bitrate */
@@ -211,6 +220,43 @@ static inline uint8_t *spi_drain_readfifo(struct sunxi_spi_reg *spi,
return din;
}
+static int sunxi_spi_trans_setup(struct sunxi_spi_privdata *priv,
+ const uint8_t *dout,
+ const uint8_t *din,
+ unsigned int n_bytes)
+{
+ if (!dout) {
+ error("%s: SPI flash command requires at least an opcode\n",
+ __func__);
+ return -EPROTO;
+ }
+
+ /* Detect dual-IO read commands */
+ if (dout[0] == CMD_READ_DUAL_OUTPUT_FAST) {
+ /* This is always called as two xfer-requests from the
+ * higher layers:
+ * 1. a write-only request with the 1-byte opcode,
+ * 4-byte address and a dummy byte
+ * 2. a read-only for the requested amount of data
+ */
+
+ /* TODO: The "cmd, addr, dummy" sequence should be
+ * changed to "cmd, addr" w/ the controller
+ * generating the dummy cycles, so the Hi-Z
+ * state for IO0 and IO1 can already be
+ * generated during the dummy cycles.
+ */
+ priv->transaction_type = OPC_READ_DUAL_CMD;
+ }
+
+ return 0;
+}
+
+static void sunxi_spi_trans_end(struct sunxi_spi_privdata *priv)
+{
+ priv->transaction_type = NONE;
+}
+
static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
const void *out, void *in, unsigned long flags)
{
@@ -239,6 +285,18 @@ static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
dev->name, __func__, spi, bitlen, din, flags, fifo_depth);
if (flags & SPI_XFER_BEGIN) {
+ /* For dual-IO support, we need to detect flash read
+ * commands here... this is actually a layering
+ * violation, but can't be fixed non-intrusively now
+ * and other drivers (e.g. Freescale QSPI, Intel ICH)
+ * follow this pattern as well.
+ */
+ if (device_get_uclass_id(dev) == UCLASS_SPI_FLASH) {
+ ret = sunxi_spi_trans_setup(priv, dout, din, n_bytes);
+ if (ret < 0)
+ return ret;
+ }
+
ret = sunxi_spi_cs_activate(dev, slave->cs);
if (ret < 0) {
error("%s: failed to activate chip-select %d\n",
@@ -263,6 +321,12 @@ static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
if (!din)
setbits_le32(&spi->TCR, TCR_DHB);
+ /* Set the dual-mode input bit */
+ if (priv->transaction_type == OPC_READ_DUAL_CMD)
+ setbits_le32(&spi->BCC, BIT(28));
+ else
+ clrbits_le32(&spi->BCC, BIT(28));
+
/* Transfer in blocks of FIFO_DEPTH */
while (n_bytes > 0) {
int cnt = (n_bytes < fifo_depth) ? n_bytes : fifo_depth;
@@ -300,8 +364,10 @@ static int sunxi_spi_xfer(struct udevice *dev, unsigned int bitlen,
}
fail:
- if (flags & SPI_XFER_END)
+ if (flags & SPI_XFER_END) {
sunxi_spi_cs_deactivate(dev, slave->cs);
+ sunxi_spi_trans_end(priv);
+ }
return 0;
};