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

Reply via email to