The Metanoia variant of the ATCSPI200 core adds hardware command and address phases along with the dual and quad I/O data formats. Expose them through the spi-mem operations, which is required to use SPI NOR flash devices at full speed.
The Metanoia variant is detected at probe time with device_is_compatible() and gets the mem_ops assigned, following the same pattern as the Octeon driver. The Andes instances keep the generic spi-mem fallback built on top of the .xfer operation, unchanged. Signed-off-by: Benoît Monin <[email protected]> --- drivers/spi/atcspi200_spi.c | 176 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 8 deletions(-) diff --git a/drivers/spi/atcspi200_spi.c b/drivers/spi/atcspi200_spi.c index 72b612c6560..a229b196de1 100644 --- a/drivers/spi/atcspi200_spi.c +++ b/drivers/spi/atcspi200_spi.c @@ -10,6 +10,7 @@ #include <log.h> #include <malloc.h> #include <spi.h> +#include <spi-mem.h> #include <asm/global_data.h> #include <asm/io.h> #include <dm.h> @@ -30,6 +31,7 @@ struct atcspi200_spi_regs { u32 reserve1[3]; u32 format; /* 0x10 */ #define DATA_LENGTH(x) ((x-1)<<8) +#define ADDR_LENGTH(x) ((x-1)<<16) u32 pio; u32 reserve2[2]; u32 tctrl; /* 0x20 */ @@ -49,6 +51,10 @@ struct atcspi200_spi_regs { #define WCNT_MASK (0x1FF<<WCNT_OFFSET) #define RCNT_OFFSET 0 #define RCNT_MASK (0x1FF<<RCNT_OFFSET) +#define CMD_EN (1<<30) +#define ADDR_EN (1<<29) +#define DUAL_QUAD(x) ((x)<<22) +#define DUMMY_CNT(x) (((x)-1)<<9) u32 cmd; u32 addr; u32 data; @@ -364,6 +370,164 @@ static int atcspi200_spi_get_clk(struct udevice *bus) return 0; } +/* + * The dummy phase is generated in units of 8 bits: one unit takes + * (8 / data buswidth) SCLK cycles. The unit count is programmed in the + * 2-bit DummyCnt field of the TransCtrl register. + */ +static unsigned int atcspi200_mem_dummy_units(const struct spi_mem_op *op) +{ + unsigned int cycles; + + cycles = op->dummy.nbytes * 8 / + max_t(unsigned int, op->dummy.buswidth, 1U); + + return cycles * op->data.buswidth / 8; +} + +static int atcspi200_mem_adjust_op_size(struct spi_slave *slave, + struct spi_mem_op *op) +{ + op->data.nbytes = min(op->data.nbytes, (unsigned int)MAX_TRANSFER_LEN); + + return 0; +} + +static bool atcspi200_mem_supports_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + unsigned int cycles, units; + + if (!spi_mem_default_supports_op(slave, op)) + return false; + + /* the address phase is always transferred on a single line */ + if (op->addr.nbytes > 4 || (op->addr.nbytes && op->addr.buswidth != 1)) + return false; + + /* a dummy phase without data cannot be expressed by the controller */ + if (op->dummy.nbytes && !op->data.nbytes) + return false; + + if (!op->data.nbytes) + return true; + + if (op->data.buswidth != 1 && op->data.buswidth != 2 && + op->data.buswidth != 4) + return false; + + /* the write and read counters are 9-bit fields */ + if (op->data.nbytes > MAX_TRANSFER_LEN) + return false; + + /* reject dummy cycles which cannot be represented by DummyCnt */ + cycles = op->dummy.nbytes * 8 / + max_t(unsigned int, op->dummy.buswidth, 1U); + if (cycles * op->data.buswidth % 8) + return false; + + units = cycles * op->data.buswidth / 8; + + return units <= 4; +} + +static int atcspi200_mem_exec_op(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + struct udevice *bus = slave->dev->parent; + struct nds_spi_slave *ns = dev_get_priv(bus); + unsigned int format, tc = 0, units; + unsigned int len = op->data.nbytes; + const u8 *dout = op->data.buf.out; + u8 *din = op->data.buf.in; + int timeout = SPI_TIMEOUT; + + /* reprogram the address length for this operation */ + format = in_le32(&ns->regs->format) & ~(0x3 << 16); + if (op->addr.nbytes) + format |= ADDR_LENGTH(op->addr.nbytes); + out_le32(&ns->regs->format, format); + + if (op->cmd.nbytes) + tc |= CMD_EN; + if (op->addr.nbytes) { + tc |= ADDR_EN; + out_le32(&ns->regs->addr, op->addr.val); + } + + if (op->data.nbytes) { + if (op->data.buswidth == 2) + tc |= DUAL_QUAD(1); + else if (op->data.buswidth == 4) + tc |= DUAL_QUAD(2); + + if (op->data.dir == SPI_MEM_DATA_IN) { + units = atcspi200_mem_dummy_units(op); + tc |= units ? TRAMODE_DR : TRAMODE_RO; + if (units) + tc |= DUMMY_CNT(units); + tc |= (op->data.nbytes - 1) & 0x1FF; + } else { + tc |= TRAMODE_WO; + tc |= ((op->data.nbytes - 1) & 0x1FF) << WCNT_OFFSET; + } + } else { + tc |= TRAMODE_NONE; + } + + out_le32(&ns->regs->tctrl, tc); + out_le32(&ns->regs->cmd, op->cmd.opcode); + + while (len) { + unsigned int event = in_le32(&ns->regs->status); + + if (op->data.dir == SPI_MEM_DATA_OUT) { + if (!(event & TXFFL)) { + out_le32(&ns->regs->data, *dout++); + len--; + } + } else { + if (event & RXFVE_MASK) { + *din++ = in_le32(&ns->regs->data); + len--; + } + } + + if (!timeout--) + return -ETIMEDOUT; + } + + while (in_le32(&ns->regs->status) & SPIBSY) { + if (!timeout--) + return -ETIMEDOUT; + } + + /* restore the timing register like the legacy path does */ + out_le32(&ns->regs->timing, ns->mtiming); + + return 0; +} + +/* + * The Metanoia variant of the ATCSPI200 core adds the command and address + * phases along with the dual and quad I/O data formats, exposed through the + * spi-mem operations assigned at probe time below. The other instances keep + * using the generic spi-mem fallback on top of .xfer. + */ +static struct dm_spi_ops atcspi200_spi_ops = { + .claim_bus = atcspi200_spi_claim_bus, + .release_bus = atcspi200_spi_release_bus, + .xfer = atcspi200_spi_xfer, + .set_speed = atcspi200_spi_set_speed, + .set_mode = atcspi200_spi_set_mode, +}; + +static const struct spi_controller_mem_ops atcspi200_mem_ops = { + .adjust_op_size = atcspi200_mem_adjust_op_size, + .supports_op = atcspi200_mem_supports_op, + .exec_op = atcspi200_mem_exec_op, +}; + static int atcspi200_spi_probe(struct udevice *bus) { struct nds_spi_slave *ns = dev_get_priv(bus); @@ -373,6 +537,9 @@ static int atcspi200_spi_probe(struct udevice *bus) ns->mtiming = ns->regs->timing; atcspi200_spi_get_clk(bus); + if (device_is_compatible(bus, "metanoia,mt2824-spi")) + atcspi200_spi_ops.mem_ops = &atcspi200_mem_ops; + return 0; } @@ -394,16 +561,9 @@ static int atcspi200_ofdata_to_platadata(struct udevice *bus) return 0; } -static const struct dm_spi_ops atcspi200_spi_ops = { - .claim_bus = atcspi200_spi_claim_bus, - .release_bus = atcspi200_spi_release_bus, - .xfer = atcspi200_spi_xfer, - .set_speed = atcspi200_spi_set_speed, - .set_mode = atcspi200_spi_set_mode, -}; - static const struct udevice_id atcspi200_spi_ids[] = { { .compatible = "andestech,atcspi200" }, + { .compatible = "metanoia,mt2824-spi" }, { } }; -- 2.55.0
