spi: aspeed: SPI dirmap read support

From the HW point of view, the performance of
command read mode is greater than user mode slightly.
Thus, dirmap read framework is introduced to achieve
this goal. In dirmap_create, command read mode is
configured. Usually, the decoded address area with flash
size is assigned to each CS. CPU can thus access the
SPI flash as normal memory in dirmap_read function.

Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c
index f858d36..6099b85 100644
--- a/drivers/spi/spi-aspeed-smc.c
+++ b/drivers/spi/spi-aspeed-smc.c
@@ -87,6 +87,7 @@
 };
 
 static const struct aspeed_spi_info ast2400_spi_info;
+static int aspeed_spi_decoded_range_config(struct udevice *bus);
 
 static u32 aspeed_spi_get_io_mode(u32 bus_width)
 {
@@ -381,6 +382,91 @@
 	return 0;
 }
 
+static int aspeed_spi_dirmap_create(struct spi_mem_dirmap_desc *desc)
+{
+	int ret = 0;
+	struct udevice *dev = desc->slave->dev;
+	struct udevice *bus = dev->parent;
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+	const struct aspeed_spi_info *info = priv->info;
+	struct spi_mem_op op_tmpl = desc->info.op_tmpl;
+	u32 i;
+	u32 cs = slave_plat->cs;
+	u32 reg_val;
+	u32 ce_ctrl_reg;
+
+	if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT) {
+		/*
+		 * dirmap_write is not supported currently due to a HW
+		 * limitation for command write mode: The written data
+		 * length should be multiple of 4-byte.
+		 */
+		return -EOPNOTSUPP;
+	}
+
+	ce_ctrl_reg = (u32)&priv->regs->ce_ctrl[cs];
+	if (info == &ast2400_spi_info)
+		ce_ctrl_reg = (u32)&priv->regs->ctrl;
+
+	if (desc->info.length > 0x1000000)
+		priv->info->set_4byte(bus, cs);
+
+	/* AST2400 SPI1 doesn't have decoded address segment register. */
+	if (info != &ast2400_spi_info) {
+		priv->flashes[cs].ahb_decoded_sz = desc->info.length;
+
+		for (i = 0; i < priv->num_cs; i++) {
+			dev_dbg(dev, "cs: %d, sz: 0x%x\n", i,
+				priv->flashes[cs].ahb_decoded_sz);
+		}
+
+		ret = aspeed_spi_decoded_range_config(bus);
+		if (ret)
+			return ret;
+	}
+
+	reg_val = aspeed_spi_get_io_mode(op_tmpl.data.buswidth) |
+		  op_tmpl.cmd.opcode << 16 |
+		  ((op_tmpl.dummy.nbytes) & 0x3) << 6 |
+		  ((op_tmpl.dummy.nbytes) & 0x4) << 14 |
+		  CTRL_IO_MODE_CMD_READ;
+
+	writel(reg_val, ce_ctrl_reg);
+
+	priv->flashes[cs].ce_ctrl_read = reg_val;
+
+	dev_dbg(dev, "read bus width: %d ce_ctrl_val: 0x%08x\n",
+		op_tmpl.data.buswidth, priv->flashes[cs].ce_ctrl_read);
+
+	return ret;
+}
+
+static ssize_t aspeed_spi_dirmap_read(struct spi_mem_dirmap_desc *desc,
+				      u64 offs, size_t len, void *buf)
+{
+	struct udevice *dev = desc->slave->dev;
+	struct aspeed_spi_priv *priv = dev_get_priv(dev->parent);
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+	u32 cs = slave_plat->cs;
+	int ret;
+
+	dev_dbg(dev, "read op:0x%x, addr:0x%llx, len:0x%x\n",
+		desc->info.op_tmpl.cmd.opcode, offs, len);
+
+	if (priv->flashes[cs].ahb_decoded_sz < offs + len ||
+	    (offs % 4) != 0) {
+		ret = aspeed_spi_exec_op_user_mode(desc->slave,
+						   &desc->info.op_tmpl);
+		if (ret != 0)
+			return 0;
+	} else {
+		memcpy_fromio(buf, priv->flashes[cs].ahb_base + offs, len);
+	}
+
+	return len;
+}
+
 static struct aspeed_spi_flash *aspeed_spi_get_flash(struct udevice *dev)
 {
 	struct udevice *bus = dev->parent;
@@ -662,6 +748,8 @@
 static const struct spi_controller_mem_ops aspeed_spi_mem_ops = {
 	.supports_op = aspeed_spi_supports_op,
 	.exec_op = aspeed_spi_exec_op_user_mode,
+	.dirmap_create = aspeed_spi_dirmap_create,
+	.dirmap_read = aspeed_spi_dirmap_read,
 };
 
 static const struct dm_spi_ops aspeed_spi_ops = {