@@ -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" },
{ }
};
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 <benoit.monin@bootlin.com> --- drivers/spi/atcspi200_spi.c | 176 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 8 deletions(-)