This adds support for XIP mode. It is not actually any faster than QPI
(yet), but it serves as a good starting point for using XIP mode for other
purposes (such as actual eXecuting In Place).

Signed-off-by: Sean Anderson <sean...@gmail.com>
---

 drivers/spi/designware_spi.c | 131 +++++++++++++++++++++++++++++++++++
 1 file changed, 131 insertions(+)

diff --git a/drivers/spi/designware_spi.c b/drivers/spi/designware_spi.c
index 6f74a471e3..cb7a28c3bf 100644
--- a/drivers/spi/designware_spi.c
+++ b/drivers/spi/designware_spi.c
@@ -1056,10 +1056,141 @@ static int dw_spi_adjust_op_size(struct spi_slave 
*slave, struct spi_mem_op *op)
        return 0;
 }
 
+#if CONFIG_IS_ENABLED(SPI_DIRMAP)
+static int dw_spi_dirmap_create(struct spi_mem_dirmap_desc *desc)
+{
+       struct dw_spi_priv *priv = dev_get_priv(desc->slave->dev->parent);
+
+       /*
+        * Currently only DWC XIP is supported. DW APB SSI XIP exists, but
+        * cannot send an instruction before the address, so it is left for when
+        * U-Boot supports 0-X-X instructions. In addition, we only support
+        * concurrent XIP (since I have no non-condcurrent XIP hardware to test
+        * with)
+        */
+       if (!(priv->caps & (DW_SPI_CAP_XIP)) ||
+           !(priv->caps & (DW_SPI_CAP_DWC_SSI)) ||
+           !(priv->caps & (DW_SPI_CAP_XIP_CONCURRENT)))
+               return -ENOTSUPP;
+
+       if (!spi_mem_supports_op(desc->slave, &desc->info.op_tmpl))
+               return -ENOTSUPP;
+
+       /*
+        * Make sure the requested region doesn't go out of the physically
+        * mapped flash memory bounds and the operation is read-only.
+        */
+       if (desc->info.offset + desc->info.length > priv->regs_size ||
+           desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN)
+               return -ENOTSUPP;
+
+       /* XIP only supports enhanced SPI modes */
+       if (desc->info.op_tmpl.data.buswidth == 1)
+               return -ENOTSUPP;
+
+       return 0;
+}
+
+static u32 dw_spi_update_xip_cr(const struct spi_mem_op *op, uint frf)
+{
+       uint trans_type, wait_cycles;
+
+       /* This assumes support_op has filtered invalid types */
+       if (op->addr.buswidth == 1)
+               trans_type = TRANS_TYPE_1_1_X;
+       else if (op->cmd.buswidth == 1)
+               trans_type = TRANS_TYPE_1_X_X;
+       else
+               trans_type = TRANS_TYPE_X_X_X;
+
+       if (op->dummy.buswidth)
+               wait_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth;
+       else
+               wait_cycles = 0;
+
+       return FIELD_PREP(XIP_CTRL_FRF, frf)
+              | FIELD_PREP(XIP_CTRL_TRANS_TYPE_MASK, trans_type)
+              | FIELD_PREP(XIP_CTRL_ADDR_L_MASK, op->addr.nbytes * 2)
+              | FIELD_PREP(XIP_CTRL_INST_L_MASK, INST_L_8)
+              | FIELD_PREP(XIP_CTRL_WAIT_CYCLES_MASK, wait_cycles)
+              //| XIP_CTRL_DFS_HC
+              | XIP_CTRL_INST_EN
+              | XIP_CTRL_CONT_XFER_EN
+              | XIP_CTRL_PREFETCH_EN;
+}
+
+static ssize_t dw_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs,
+                                 size_t len, void *buf)
+{
+       int ret;
+       size_t count = len;
+       struct spi_slave *slave = desc->slave;
+       struct udevice *bus = slave->dev->parent;
+       struct dw_spi_priv *priv = dev_get_priv(bus);
+       struct spi_mem_op *op = &desc->info.op_tmpl;
+       u8 *from, *to;
+
+       switch (op->data.buswidth) {
+       case 2:
+               priv->spi_frf = CTRLR0_SPI_FRF_DUAL;
+               break;
+       case 4:
+               priv->spi_frf = CTRLR0_SPI_FRF_QUAD;
+               break;
+       case 8:
+               priv->spi_frf = CTRLR0_SPI_FRF_OCTAL;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       ret = dw_spi_mux_ctrl(bus);
+       if (ret)
+               return ret;
+
+       dw_write(priv, DW_SPI_SSIENR, 0);
+       //dw_write(priv, DW_SPI_CTRLR0, dw_spi_update_cr0(priv));
+       dw_write(priv, DW_SPI_XIP_CTRL, dw_spi_update_xip_cr(op, 
priv->spi_frf));
+       dw_write(priv, DW_SPI_XIP_INCR_INST, op->cmd.opcode);
+       /*
+        * FIXME: U-Boot doesn't currently support wrap instructions, but we
+        * can't control what the AHB master does. Just write 0 to get something
+        * obviously bogus.
+        */
+       dw_write(priv, DW_SPI_XIP_WRAP_INST, 0);
+       dw_write(priv, DW_SPI_XIP_SER, 1 << spi_chip_select(slave->dev));
+       dw_write(priv, DW_SPI_SSIENR, 1);
+
+       dw_spi_mux_deselect(bus);
+
+       external_cs_manage(slave->dev, true);
+
+       ret = dw_spi_mux_xip(bus);
+       if (ret)
+               return ret;
+
+       //memcpy(buf, priv->regs + offs, len);
+       from = priv->regs + offs;
+       to = buf;
+       while (count--)
+               *to++ = *from++;
+
+       dw_spi_mux_deselect(bus);
+
+       external_cs_manage(slave->dev, false);
+
+       return len;
+}
+#endif /* CONFIG_SPI_DIRMAP */
+
 static const struct spi_controller_mem_ops dw_spi_mem_ops = {
        .exec_op = dw_spi_exec_op,
        .supports_op = dw_spi_supports_op,
        .adjust_op_size = dw_spi_adjust_op_size,
+#if CONFIG_IS_ENABLED(SPI_DIRMAP)
+       .dirmap_create = dw_spi_dirmap_create,
+       .dirmap_read = dw_spi_dirmap_read,
+#endif
 };
 
 static int dw_spi_set_speed(struct udevice *bus, uint speed)
-- 
2.29.2

Reply via email to