Merge branch '2022-09-13-add-aspeed-spi-controller' into next

To quote the author:
This patch series aims to porting ASPEED FMC/SPI memory controller
driver with spi-mem interface. spi-mem dirmap framework is also
synchronized from Linux. These patches have been verified on
AST2600, AST2500 and AST2400 EVBs.
diff --git a/MAINTAINERS b/MAINTAINERS
index 36a2b69..1ebcd36 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -724,6 +724,13 @@
 F:	drivers/pci/pcie_phytium.c
 F:	arch/arm/dts/phytium-durian.dts
 
+ASPEED FMC SPI DRIVER
+M:	Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+M:	Cédric Le Goater <clg@kaod.org>
+R:	Aspeed BMC SW team <BMC-SW@aspeedtech.com>
+S:	Maintained
+F:	drivers/spi/spi-aspeed-smc.c
+
 BINMAN
 M:	Simon Glass <sjg@chromium.org>
 M:	Alper Nebi Yasak <alpernebiyasak@gmail.com>
diff --git a/arch/arm/dts/ast2500-evb.dts b/arch/arm/dts/ast2500-evb.dts
index cc57776..1fbacf9 100644
--- a/arch/arm/dts/ast2500-evb.dts
+++ b/arch/arm/dts/ast2500-evb.dts
@@ -78,6 +78,39 @@
 	pinctrl-0 = <&pinctrl_sd2_default>;
 };
 
+&fmc {
+	status = "okay";
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_fwspics1_default>;
+
+	flash@0 {
+		status = "okay";
+		spi-max-frequency = <50000000>;
+		spi-tx-bus-width = <2>;
+		spi-rx-bus-width = <2>;
+	};
+
+	flash@1 {
+		status = "okay";
+		spi-max-frequency = <50000000>;
+		spi-tx-bus-width = <2>;
+		spi-rx-bus-width = <2>;
+	};
+};
+
+&spi1 {
+	status = "okay";
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_spi1cs1_default>;
+
+	flash@0 {
+		status = "okay";
+		spi-max-frequency = <50000000>;
+		spi-tx-bus-width = <2>;
+		spi-rx-bus-width = <2>;
+	};
+};
+
 &i2c3 {
         status = "okay";
 
diff --git a/arch/arm/dts/ast2500.dtsi b/arch/arm/dts/ast2500.dtsi
index cea08e6..320d2e5 100644
--- a/arch/arm/dts/ast2500.dtsi
+++ b/arch/arm/dts/ast2500.dtsi
@@ -57,23 +57,26 @@
 		ranges;
 
 		fmc: flash-controller@1e620000 {
-			reg = < 0x1e620000 0xc4
-				0x20000000 0x10000000 >;
+			reg = <0x1e620000 0xc4>, <0x20000000 0x10000000>;
 			#address-cells = <1>;
 			#size-cells = <0>;
 			compatible = "aspeed,ast2500-fmc";
+			clocks = <&scu ASPEED_CLK_AHB>;
+			num-cs = <3>;
 			status = "disabled";
-			interrupts = <19>;
+
 			flash@0 {
 				reg = < 0 >;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@1 {
 				reg = < 1 >;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@2 {
 				reg = < 2 >;
 				compatible = "jedec,spi-nor";
@@ -82,17 +85,20 @@
 		};
 
 		spi1: flash-controller@1e630000 {
-			reg = < 0x1e630000 0xc4
-				0x30000000 0x08000000 >;
+			reg = <0x1e630000 0xc4>, <0x30000000 0x08000000>;
 			#address-cells = <1>;
 			#size-cells = <0>;
 			compatible = "aspeed,ast2500-spi";
+			clocks = <&scu ASPEED_CLK_AHB>;
+			num-cs = <2>;
 			status = "disabled";
+
 			flash@0 {
 				reg = < 0 >;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@1 {
 				reg = < 1 >;
 				compatible = "jedec,spi-nor";
@@ -101,17 +107,20 @@
 		};
 
 		spi2: flash-controller@1e631000 {
-			reg = < 0x1e631000 0xc4
-				0x38000000 0x08000000 >;
+			reg = <0x1e631000 0xc4>, <0x38000000 0x08000000>;
 			#address-cells = <1>;
 			#size-cells = <0>;
 			compatible = "aspeed,ast2500-spi";
+			clocks = <&scu ASPEED_CLK_AHB>;
+			num-cs = <2>;
 			status = "disabled";
+
 			flash@0 {
 				reg = < 0 >;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@1 {
 				reg = < 1 >;
 				compatible = "jedec,spi-nor";
diff --git a/arch/arm/dts/ast2600-evb.dts b/arch/arm/dts/ast2600-evb.dts
index a9bba96..a097f32 100644
--- a/arch/arm/dts/ast2600-evb.dts
+++ b/arch/arm/dts/ast2600-evb.dts
@@ -72,12 +72,10 @@
 
 &fmc {
 	status = "okay";
-
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_fmcquad_default>;
 
 	flash@0 {
-		compatible = "spi-flash", "sst,w25q256";
 		status = "okay";
 		spi-max-frequency = <50000000>;
 		spi-tx-bus-width = <4>;
@@ -85,7 +83,6 @@
 	};
 
 	flash@1 {
-		compatible = "spi-flash", "sst,w25q256";
 		status = "okay";
 		spi-max-frequency = <50000000>;
 		spi-tx-bus-width = <4>;
@@ -93,7 +90,6 @@
 	};
 
 	flash@2 {
-		compatible = "spi-flash", "sst,w25q256";
 		status = "okay";
 		spi-max-frequency = <50000000>;
 		spi-tx-bus-width = <4>;
@@ -103,14 +99,12 @@
 
 &spi1 {
 	status = "okay";
-
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_spi1_default &pinctrl_spi1abr_default
 			&pinctrl_spi1cs1_default &pinctrl_spi1wp_default
 			&pinctrl_spi1wp_default &pinctrl_spi1quad_default>;
 
 	flash@0 {
-		compatible = "spi-flash", "sst,w25q256";
 		status = "okay";
 		spi-max-frequency = <50000000>;
 		spi-tx-bus-width = <4>;
@@ -120,13 +114,11 @@
 
 &spi2 {
 	status = "okay";
-
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_spi2_default &pinctrl_spi2cs1_default
 			&pinctrl_spi2cs2_default &pinctrl_spi2quad_default>;
 
 	flash@0 {
-		compatible = "spi-flash", "sst,w25q256";
 		status = "okay";
 		spi-max-frequency = <50000000>;
 		spi-tx-bus-width = <4>;
diff --git a/arch/arm/dts/ast2600.dtsi b/arch/arm/dts/ast2600.dtsi
index ac8cd4d..8d91eed 100644
--- a/arch/arm/dts/ast2600.dtsi
+++ b/arch/arm/dts/ast2600.dtsi
@@ -129,74 +129,78 @@
 		};
 
 		fmc: flash-controller@1e620000 {
-			reg = < 0x1e620000 0xc4
-				0x20000000 0x10000000 >;
+			reg = <0x1e620000 0xc4>, <0x20000000 0x10000000>;
 			#address-cells = <1>;
 			#size-cells = <0>;
 			compatible = "aspeed,ast2600-fmc";
 			status = "disabled";
-			interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>;
 			clocks = <&scu ASPEED_CLK_AHB>;
 			num-cs = <3>;
+
 			flash@0 {
-				reg = < 0 >;
+				reg = <0>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@1 {
-				reg = < 1 >;
+				reg = <1>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@2 {
-				reg = < 2 >;
+				reg = <2>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
 		};
 
 		spi1: flash-controller@1e630000 {
-			reg = < 0x1e630000 0xc4
-				0x30000000 0x08000000 >;
+			reg = <0x1e630000 0xc4>, <0x30000000 0x10000000>;
 			#address-cells = <1>;
 			#size-cells = <0>;
 			compatible = "aspeed,ast2600-spi";
 			clocks = <&scu ASPEED_CLK_AHB>;
 			num-cs = <2>;
 			status = "disabled";
+
 			flash@0 {
-				reg = < 0 >;
+				reg = <0>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@1 {
-				reg = < 1 >;
+				reg = <1>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
 		};
 
 		spi2: flash-controller@1e631000 {
-			reg = < 0x1e631000 0xc4
-				0x50000000 0x08000000 >;
+			reg = <0x1e631000 0xc4>, <0x50000000 0x10000000>;
 			#address-cells = <1>;
 			#size-cells = <0>;
 			compatible = "aspeed,ast2600-spi";
 			clocks = <&scu ASPEED_CLK_AHB>;
 			num-cs = <3>;
 			status = "disabled";
+
 			flash@0 {
-				reg = < 0 >;
+				reg = <0>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@1 {
-				reg = < 1 >;
+				reg = <1>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
+
 			flash@2 {
-				reg = < 2 >;
+				reg = <2>;
 				compatible = "jedec,spi-nor";
 				status = "disabled";
 			};
diff --git a/configs/evb-ast2500_defconfig b/configs/evb-ast2500_defconfig
index 0bef043..3061cbe 100644
--- a/configs/evb-ast2500_defconfig
+++ b/configs/evb-ast2500_defconfig
@@ -39,6 +39,16 @@
 CONFIG_I2C_EEPROM=y
 CONFIG_MMC_SDHCI=y
 CONFIG_MMC_SDHCI_ASPEED=y
+CONFIG_DM_SPI_FLASH=y
+CONFIG_SPI_FLASH_SFDP_SUPPORT=y
+CONFIG_SPI_FLASH_GIGADEVICE=y
+CONFIG_SPI_FLASH_ISSI=y
+CONFIG_SPI_FLASH_MACRONIX=y
+CONFIG_SPI_FLASH_SPANSION=y
+CONFIG_SPI_FLASH_STMICRO=y
+CONFIG_SPI_FLASH_SST=y
+CONFIG_SPI_FLASH_WINBOND=y
+# CONFIG_SPI_FLASH_USE_4K_SECTORS is not set
 CONFIG_PHY_REALTEK=y
 CONFIG_FTGMAC100=y
 CONFIG_PHY=y
@@ -47,6 +57,10 @@
 CONFIG_DM_RESET=y
 CONFIG_DM_SERIAL=y
 CONFIG_SYS_NS16550=y
+CONFIG_SPI=y
+CONFIG_DM_SPI=y
+CONFIG_SPI_DIRMAP=y
+CONFIG_SPI_ASPEED_SMC=y
 CONFIG_SYSRESET=y
 CONFIG_TIMER=y
 CONFIG_WDT=y
diff --git a/configs/evb-ast2600_defconfig b/configs/evb-ast2600_defconfig
index 1284d51..1981f89 100644
--- a/configs/evb-ast2600_defconfig
+++ b/configs/evb-ast2600_defconfig
@@ -87,6 +87,16 @@
 CONFIG_I2C_EEPROM=y
 CONFIG_MMC_SDHCI=y
 CONFIG_MMC_SDHCI_ASPEED=y
+CONFIG_DM_SPI_FLASH=y
+CONFIG_SPI_FLASH_SFDP_SUPPORT=y
+CONFIG_SPI_FLASH_GIGADEVICE=y
+CONFIG_SPI_FLASH_ISSI=y
+CONFIG_SPI_FLASH_MACRONIX=y
+CONFIG_SPI_FLASH_SPANSION=y
+CONFIG_SPI_FLASH_STMICRO=y
+CONFIG_SPI_FLASH_SST=y
+CONFIG_SPI_FLASH_WINBOND=y
+# CONFIG_SPI_FLASH_USE_4K_SECTORS is not set
 CONFIG_PHY_REALTEK=y
 CONFIG_DM_MDIO=y
 CONFIG_FTGMAC100=y
@@ -98,6 +108,10 @@
 CONFIG_DM_RESET=y
 CONFIG_DM_SERIAL=y
 CONFIG_SYS_NS16550=y
+CONFIG_SPI=y
+CONFIG_DM_SPI=y
+CONFIG_SPI_DIRMAP=y
+CONFIG_SPI_ASPEED_SMC=y
 CONFIG_SYSRESET=y
 CONFIG_SPL_SYSRESET=y
 CONFIG_WDT=y
diff --git a/drivers/clk/aspeed/clk_ast2500.c b/drivers/clk/aspeed/clk_ast2500.c
index 623c691..dc446ce 100644
--- a/drivers/clk/aspeed/clk_ast2500.c
+++ b/drivers/clk/aspeed/clk_ast2500.c
@@ -30,6 +30,12 @@
 
 #define D2PLL_DEFAULT_RATE	(250 * 1000 * 1000)
 
+/*
+ * AXI/AHB clock selection, taken from Aspeed SDK
+ */
+#define SCU_HWSTRAP_AXIAHB_DIV_SHIFT    9
+#define SCU_HWSTRAP_AXIAHB_DIV_MASK     (0x7 << SCU_HWSTRAP_AXIAHB_DIV_SHIFT)
+
 DECLARE_GLOBAL_DATA_PTR;
 
 /*
@@ -86,6 +92,20 @@
 			? 25 * 1000 * 1000 : 24 * 1000 * 1000;
 }
 
+static u32 ast2500_get_hclk(ulong clkin, struct ast2500_scu *scu)
+{
+	u32 hpll_reg = readl(&scu->h_pll_param);
+	ulong axi_div = 2;
+	u32 rate;
+	ulong ahb_div = 1 + ((readl(&scu->hwstrap)
+			      & SCU_HWSTRAP_AXIAHB_DIV_MASK)
+			     >> SCU_HWSTRAP_AXIAHB_DIV_SHIFT);
+
+	rate = ast2500_get_hpll_rate(clkin, hpll_reg);
+
+	return (rate / axi_div / ahb_div);
+}
+
 /**
  * Get current rate or uart clock
  *
@@ -147,6 +167,9 @@
 			rate = rate / apb_div;
 		}
 		break;
+	case ASPEED_CLK_AHB:
+		rate = ast2500_get_hclk(clkin, priv->scu);
+		break;
 	case ASPEED_CLK_SDIO:
 		{
 			ulong apb_div = 4 + 4 * ((readl(&priv->scu->clk_sel1)
diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c
index f461082..e192f97 100644
--- a/drivers/mtd/spi/sf_probe.c
+++ b/drivers/mtd/spi/sf_probe.c
@@ -10,13 +10,69 @@
 #include <common.h>
 #include <dm.h>
 #include <errno.h>
+#include <linux/mtd/spi-nor.h>
 #include <log.h>
 #include <malloc.h>
 #include <spi.h>
 #include <spi_flash.h>
+#include <spi-mem.h>
 
 #include "sf_internal.h"
 
+static int spi_nor_create_read_dirmap(struct spi_nor *nor)
+{
+	struct spi_mem_dirmap_info info = {
+		.op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
+				      SPI_MEM_OP_ADDR(nor->addr_width, 0, 0),
+				      SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
+				      SPI_MEM_OP_DATA_IN(0, NULL, 0)),
+		.offset = 0,
+		.length = nor->mtd.size,
+	};
+	struct spi_mem_op *op = &info.op_tmpl;
+
+	/* get transfer protocols. */
+	spi_nor_setup_op(nor, op, nor->read_proto);
+	op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);
+
+	/* convert the dummy cycles to the number of bytes */
+	op->dummy.nbytes = (nor->read_dummy * op->dummy.buswidth) / 8;
+	if (spi_nor_protocol_is_dtr(nor->read_proto))
+		op->dummy.nbytes *= 2;
+
+	nor->dirmap.rdesc = spi_mem_dirmap_create(nor->spi, &info);
+	if (IS_ERR(nor->dirmap.rdesc))
+		return PTR_ERR(nor->dirmap.rdesc);
+
+	return 0;
+}
+
+static int spi_nor_create_write_dirmap(struct spi_nor *nor)
+{
+	struct spi_mem_dirmap_info info = {
+		.op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0),
+				      SPI_MEM_OP_ADDR(nor->addr_width, 0, 0),
+				      SPI_MEM_OP_NO_DUMMY,
+				      SPI_MEM_OP_DATA_OUT(0, NULL, 0)),
+		.offset = 0,
+		.length = nor->mtd.size,
+	};
+	struct spi_mem_op *op = &info.op_tmpl;
+
+	/* get transfer protocols. */
+	spi_nor_setup_op(nor, op, nor->write_proto);
+	op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto);
+
+	if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
+		op->addr.nbytes = 0;
+
+	nor->dirmap.wdesc = spi_mem_dirmap_create(nor->spi, &info);
+	if (IS_ERR(nor->dirmap.wdesc))
+		return PTR_ERR(nor->dirmap.wdesc);
+
+	return 0;
+}
+
 /**
  * spi_flash_probe_slave() - Probe for a SPI flash device on a bus
  *
@@ -45,6 +101,16 @@
 	if (ret)
 		goto err_read_id;
 
+	if (CONFIG_IS_ENABLED(SPI_DIRMAP)) {
+		ret = spi_nor_create_read_dirmap(flash);
+		if (ret)
+			return ret;
+
+		ret = spi_nor_create_write_dirmap(flash);
+		if (ret)
+			return ret;
+	}
+
 	if (CONFIG_IS_ENABLED(SPI_FLASH_MTD))
 		ret = spi_flash_mtd_register(flash);
 
@@ -83,6 +149,11 @@
 
 void spi_flash_free(struct spi_flash *flash)
 {
+	if (CONFIG_IS_ENABLED(SPI_DIRMAP)) {
+		spi_mem_dirmap_destroy(flash->dirmap.wdesc);
+		spi_mem_dirmap_destroy(flash->dirmap.rdesc);
+	}
+
 	if (CONFIG_IS_ENABLED(SPI_FLASH_MTD))
 		spi_flash_mtd_unregister(flash);
 
@@ -153,6 +224,11 @@
 	struct spi_flash *flash = dev_get_uclass_priv(dev);
 	int ret;
 
+	if (CONFIG_IS_ENABLED(SPI_DIRMAP)) {
+		spi_mem_dirmap_destroy(flash->dirmap.wdesc);
+		spi_mem_dirmap_destroy(flash->dirmap.rdesc);
+	}
+
 	ret = spi_nor_remove(flash);
 	if (ret)
 		return ret;
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c
index e3c86e0..f236e87 100644
--- a/drivers/mtd/spi/spi-nor-core.c
+++ b/drivers/mtd/spi/spi-nor-core.c
@@ -246,9 +246,9 @@
  *			need to be initialized.
  * @proto:		the protocol from which the properties need to be set.
  */
-static void spi_nor_setup_op(const struct spi_nor *nor,
-			     struct spi_mem_op *op,
-			     const enum spi_nor_protocol proto)
+void spi_nor_setup_op(const struct spi_nor *nor,
+		      struct spi_mem_op *op,
+		      const enum spi_nor_protocol proto)
 {
 	u8 ext;
 
@@ -369,13 +369,29 @@
 
 	while (remaining) {
 		op.data.nbytes = remaining < UINT_MAX ? remaining : UINT_MAX;
-		ret = spi_mem_adjust_op_size(nor->spi, &op);
-		if (ret)
-			return ret;
 
-		ret = spi_mem_exec_op(nor->spi, &op);
-		if (ret)
-			return ret;
+		if (CONFIG_IS_ENABLED(SPI_DIRMAP) && nor->dirmap.rdesc) {
+			/*
+			 * Record current operation information which may be used
+			 * when the address or data length exceeds address mapping.
+			 */
+			memcpy(&nor->dirmap.rdesc->info.op_tmpl, &op,
+			       sizeof(struct spi_mem_op));
+			ret = spi_mem_dirmap_read(nor->dirmap.rdesc,
+						  op.addr.val, op.data.nbytes,
+						  op.data.buf.in);
+			if (ret < 0)
+				return ret;
+			op.data.nbytes = ret;
+		} else {
+			ret = spi_mem_adjust_op_size(nor->spi, &op);
+			if (ret)
+				return ret;
+
+			ret = spi_mem_exec_op(nor->spi, &op);
+			if (ret)
+				return ret;
+		}
 
 		op.addr.val += op.data.nbytes;
 		remaining -= op.data.nbytes;
@@ -400,14 +416,21 @@
 
 	spi_nor_setup_op(nor, &op, nor->write_proto);
 
-	ret = spi_mem_adjust_op_size(nor->spi, &op);
-	if (ret)
-		return ret;
-	op.data.nbytes = len < op.data.nbytes ? len : op.data.nbytes;
+	if (CONFIG_IS_ENABLED(SPI_DIRMAP) && nor->dirmap.wdesc) {
+		memcpy(&nor->dirmap.wdesc->info.op_tmpl, &op,
+		       sizeof(struct spi_mem_op));
+		op.data.nbytes = spi_mem_dirmap_write(nor->dirmap.wdesc, op.addr.val,
+						      op.data.nbytes, op.data.buf.out);
+	} else {
+		ret = spi_mem_adjust_op_size(nor->spi, &op);
+		if (ret)
+			return ret;
+		op.data.nbytes = len < op.data.nbytes ? len : op.data.nbytes;
 
-	ret = spi_mem_exec_op(nor->spi, &op);
-	if (ret)
-		return ret;
+		ret = spi_mem_exec_op(nor->spi, &op);
+		if (ret)
+			return ret;
+	}
 
 	return op.data.nbytes;
 }
diff --git a/drivers/mtd/spi/spi-nor-ids.c b/drivers/mtd/spi/spi-nor-ids.c
index 4fe8b0d..65eb35a 100644
--- a/drivers/mtd/spi/spi-nor-ids.c
+++ b/drivers/mtd/spi/spi-nor-ids.c
@@ -425,6 +425,11 @@
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
 	},
 	{
+		INFO("w25q512jvq", 0xef4020, 0, 64 * 1024, 1024,
+		     SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
+		     SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
+	},
+	{
 		INFO("w25q01jv", 0xef4021, 0, 64 * 1024, 2048,
 			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
 			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
diff --git a/drivers/pinctrl/aspeed/pinctrl_ast2500.c b/drivers/pinctrl/aspeed/pinctrl_ast2500.c
index 3c2e10b..93920a6 100644
--- a/drivers/pinctrl/aspeed/pinctrl_ast2500.c
+++ b/drivers/pinctrl/aspeed/pinctrl_ast2500.c
@@ -61,6 +61,8 @@
 	{ "MDIO2", 5, (1 << 2) },
 	{ "SD1", 5, (1 << 0) },
 	{ "SD2", 5, (1 << 1) },
+	{ "FWSPICS1", 3, (1 << 24) },
+	{ "SPI1CS1", 1, (1 << 15) },
 };
 
 static int ast2500_pinctrl_get_groups_count(struct udevice *dev)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 75b7945..ac91d82 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -40,6 +40,16 @@
 	  This extension is meant to simplify interaction with SPI memories
 	  by providing an high-level interface to send memory-like commands.
 
+config SPI_DIRMAP
+	bool "SPI direct mapping"
+	depends on SPI_MEM
+	help
+	  Enable the SPI direct mapping API. Most modern SPI controllers can
+	  directly map a SPI memory (or a portion of the SPI memory) in the CPU
+	  address space. Most of the time this brings significant performance
+	  improvements as it automates the whole process of sending SPI memory
+	  operations every time a new region is accessed.
+
 if DM_SPI
 
 config ALTERA_SPI
@@ -401,6 +411,14 @@
 		};
 	  };
 
+config SPI_ASPEED_SMC
+	bool "ASPEED SPI flash controller driver"
+	depends on DM_SPI && SPI_MEM
+	default n
+	help
+	  Enable ASPEED SPI flash controller driver for AST2500
+	  and AST2600 SoCs.
+
 config SPI_SIFIVE
 	bool "SiFive SPI driver"
 	help
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 4de77c2..7ba953d 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -10,6 +10,7 @@
 obj-$(CONFIG_CADENCE_OSPI_VERSAL) += cadence_ospi_versal.o
 obj-$(CONFIG_SANDBOX) += spi-emul-uclass.o
 obj-$(CONFIG_SOFT_SPI) += soft_spi.o
+obj-$(CONFIG_SPI_ASPEED_SMC) += spi-aspeed-smc.o
 obj-$(CONFIG_SPI_MEM) += spi-mem.o
 obj-$(CONFIG_TI_QSPI) += ti_qspi.o
 obj-$(CONFIG_FSL_QSPI) += fsl_qspi.o
diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c
new file mode 100644
index 0000000..a3c9633
--- /dev/null
+++ b/drivers/spi/spi-aspeed-smc.c
@@ -0,0 +1,1218 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ASPEED FMC/SPI Controller driver
+ *
+ * Copyright (c) 2022 ASPEED Corporation.
+ * Copyright (c) 2022 IBM Corporation.
+ *
+ * Author:
+ *     Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+ *     Cedric Le Goater <clg@kaod.org>
+ */
+
+#include <asm/io.h>
+#include <clk.h>
+#include <common.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/sizes.h>
+#include <malloc.h>
+#include <spi.h>
+#include <spi-mem.h>
+
+#define ASPEED_SPI_MAX_CS       5
+
+#define CTRL_IO_SINGLE_DATA     0
+#define CTRL_IO_QUAD_DATA       BIT(30)
+#define CTRL_IO_DUAL_DATA       BIT(29)
+
+#define CTRL_IO_MODE_USER       GENMASK(1, 0)
+#define CTRL_IO_MODE_CMD_READ   BIT(0)
+#define CTRL_IO_MODE_CMD_WRITE  BIT(1)
+#define CTRL_STOP_ACTIVE        BIT(2)
+
+struct aspeed_spi_regs {
+	u32 conf;                       /* 0x00 CE Type Setting */
+	u32 ctrl;                       /* 0x04 CE Control */
+	u32 intr_ctrl;                  /* 0x08 Interrupt Control and Status */
+	u32 cmd_ctrl;                   /* 0x0c Command Control */
+	u32 ce_ctrl[ASPEED_SPI_MAX_CS]; /* 0x10 .. 0x20 CEx Control */
+	u32 _reserved0[3];              /* .. */
+	u32 segment_addr[ASPEED_SPI_MAX_CS]; /* 0x30 .. 0x40 Segment Address */
+	u32 _reserved1[3];		/* .. */
+	u32 soft_rst_cmd_ctrl;          /* 0x50 Auto Soft-Reset Command Control */
+	u32 _reserved2[11];             /* .. */
+	u32 dma_ctrl;                   /* 0x80 DMA Control/Status */
+	u32 dma_flash_addr;             /* 0x84 DMA Flash Side Address */
+	u32 dma_dram_addr;              /* 0x88 DMA DRAM Side Address */
+	u32 dma_len;                    /* 0x8c DMA Length Register */
+	u32 dma_checksum;               /* 0x90 Checksum Calculation Result */
+	u32 timings[ASPEED_SPI_MAX_CS]; /* 0x94 Read Timing Compensation */
+};
+
+struct aspeed_spi_plat {
+	u8 max_cs;
+	void __iomem *ahb_base; /* AHB address base for all flash devices. */
+	fdt_size_t ahb_sz; /* Overall AHB window size for all flash device. */
+	u32 hclk_rate; /* AHB clock rate */
+};
+
+struct aspeed_spi_flash {
+	void __iomem *ahb_base;
+	u32 ahb_decoded_sz;
+	u32 ce_ctrl_user;
+	u32 ce_ctrl_read;
+	u32 max_freq;
+};
+
+struct aspeed_spi_priv {
+	u32 num_cs;
+	struct aspeed_spi_regs *regs;
+	struct aspeed_spi_info *info;
+	struct aspeed_spi_flash flashes[ASPEED_SPI_MAX_CS];
+	bool fixed_decoded_range;
+};
+
+struct aspeed_spi_info {
+	u32 io_mode_mask;
+	u32 max_bus_width;
+	u32 min_decoded_sz;
+	u32 clk_ctrl_mask;
+	void (*set_4byte)(struct udevice *bus, u32 cs);
+	u32 (*segment_start)(struct udevice *bus, u32 reg);
+	u32 (*segment_end)(struct udevice *bus, u32 reg);
+	u32 (*segment_reg)(u32 start, u32 end);
+	int (*adjust_decoded_sz)(struct udevice *bus);
+	u32 (*get_clk_setting)(struct udevice *dev, uint hz);
+};
+
+struct aspeed_spi_decoded_range {
+	u32 cs;
+	u32 ahb_base;
+	u32 sz;
+};
+
+static const struct aspeed_spi_info ast2400_spi_info;
+static const struct aspeed_spi_info ast2500_fmc_info;
+static const struct aspeed_spi_info ast2500_spi_info;
+static int aspeed_spi_decoded_range_config(struct udevice *bus);
+static int aspeed_spi_trim_decoded_size(struct udevice *bus);
+
+static u32 aspeed_spi_get_io_mode(u32 bus_width)
+{
+	switch (bus_width) {
+	case 1:
+		return CTRL_IO_SINGLE_DATA;
+	case 2:
+		return CTRL_IO_DUAL_DATA;
+	case 4:
+		return CTRL_IO_QUAD_DATA;
+	default:
+		/* keep in default value */
+		return CTRL_IO_SINGLE_DATA;
+	}
+}
+
+static u32 ast2400_spi_segment_start(struct udevice *bus, u32 reg)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	u32 start_offset = ((reg >> 16) & 0xff) << 23;
+
+	if (start_offset == 0)
+		return (u32)plat->ahb_base;
+
+	return (u32)plat->ahb_base + start_offset;
+}
+
+static u32 ast2400_spi_segment_end(struct udevice *bus, u32 reg)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	u32 end_offset = ((reg >> 24) & 0xff) << 23;
+
+	/* Meaningless end_offset, set to physical ahb base. */
+	if (end_offset == 0)
+		return (u32)plat->ahb_base;
+
+	return (u32)plat->ahb_base + end_offset;
+}
+
+static u32 ast2400_spi_segment_reg(u32 start, u32 end)
+{
+	if (start == end)
+		return 0;
+
+	return ((((start) >> 23) & 0xff) << 16) | ((((end) >> 23) & 0xff) << 24);
+}
+
+static void ast2400_fmc_chip_set_4byte(struct udevice *bus, u32 cs)
+{
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 reg_val;
+
+	reg_val = readl(&priv->regs->ctrl);
+	reg_val |= 0x1 << cs;
+	writel(reg_val, &priv->regs->ctrl);
+}
+
+static void ast2400_spi_chip_set_4byte(struct udevice *bus, u32 cs)
+{
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	struct aspeed_spi_flash *flash = &priv->flashes[cs];
+
+	flash->ce_ctrl_read |= BIT(13);
+	writel(flash->ce_ctrl_read, &priv->regs->ctrl);
+}
+
+/* Transfer maximum clock frequency to register setting */
+static u32 ast2400_get_clk_setting(struct udevice *dev, uint max_hz)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(dev->parent);
+	struct aspeed_spi_priv *priv = dev_get_priv(dev->parent);
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+	u32 hclk_clk = plat->hclk_rate;
+	u32 hclk_div = 0x0000; /* default value */
+	u32 i;
+	bool found = false;
+	/* HCLK/1 ..	HCLK/16 */
+	u32 hclk_masks[] = {15, 7, 14, 6, 13, 5, 12, 4,
+			    11, 3, 10, 2, 9,  1, 8,  0};
+
+	/* FMC/SPIR10[11:8] */
+	for (i = 0; i < ARRAY_SIZE(hclk_masks); i++) {
+		if (hclk_clk / (i + 1) <= max_hz) {
+			found = true;
+			break;
+		}
+	}
+
+	if (found) {
+		hclk_div = hclk_masks[i] << 8;
+		priv->flashes[slave_plat->cs].max_freq = hclk_clk / (i + 1);
+	}
+
+	dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", found ? "yes" : "no",
+		hclk_clk, max_hz);
+
+	if (found) {
+		dev_dbg(dev, "h_div: %d (mask %x), speed: %d\n",
+			i + 1, hclk_masks[i], priv->flashes[slave_plat->cs].max_freq);
+	}
+
+	return hclk_div;
+}
+
+static u32 ast2500_spi_segment_start(struct udevice *bus, u32 reg)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	u32 start_offset = ((reg >> 16) & 0xff) << 23;
+
+	if (start_offset == 0)
+		return (u32)plat->ahb_base;
+
+	return (u32)plat->ahb_base + start_offset;
+}
+
+static u32 ast2500_spi_segment_end(struct udevice *bus, u32 reg)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	u32 end_offset = ((reg >> 24) & 0xff) << 23;
+
+	/* Meaningless end_offset, set to physical ahb base. */
+	if (end_offset == 0)
+		return (u32)plat->ahb_base;
+
+	return (u32)plat->ahb_base + end_offset;
+}
+
+static u32 ast2500_spi_segment_reg(u32 start, u32 end)
+{
+	if (start == end)
+		return 0;
+
+	return ((((start) >> 23) & 0xff) << 16) | ((((end) >> 23) & 0xff) << 24);
+}
+
+static void ast2500_spi_chip_set_4byte(struct udevice *bus, u32 cs)
+{
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 reg_val;
+
+	reg_val = readl(&priv->regs->ctrl);
+	reg_val |= 0x1 << cs;
+	writel(reg_val, &priv->regs->ctrl);
+}
+
+/*
+ * For AST2500, the minimum address decoded size for each CS
+ * is 8MB instead of zero. This address decoded size is
+ * mandatory for each CS no matter whether it will be used.
+ * This is a HW limitation.
+ */
+static int ast2500_adjust_decoded_size(struct udevice *bus)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	struct aspeed_spi_flash *flashes = &priv->flashes[0];
+	int ret;
+	int i;
+	int cs;
+	u32 pre_sz;
+	u32 lack_sz;
+
+	/* Assign min_decoded_sz to unused CS. */
+	for (cs = priv->num_cs; cs < plat->max_cs; cs++)
+		flashes[cs].ahb_decoded_sz = priv->info->min_decoded_sz;
+
+	/*
+	 * If commnad mode or normal mode is used, the start address of a
+	 * decoded range should be multiple of its related flash size.
+	 * Namely, the total decoded size from flash 0 to flash N should
+	 * be multiple of the size of flash (N + 1).
+	 */
+	for (cs = priv->num_cs - 1; cs >= 0; cs--) {
+		pre_sz = 0;
+		for (i = 0; i < cs; i++)
+			pre_sz += flashes[i].ahb_decoded_sz;
+
+		if (flashes[cs].ahb_decoded_sz != 0 &&
+		    (pre_sz % flashes[cs].ahb_decoded_sz) != 0) {
+			lack_sz = flashes[cs].ahb_decoded_sz -
+				  (pre_sz % flashes[cs].ahb_decoded_sz);
+			flashes[0].ahb_decoded_sz += lack_sz;
+		}
+	}
+
+	ret = aspeed_spi_trim_decoded_size(bus);
+	if (ret != 0)
+		return ret;
+
+	return 0;
+}
+
+static u32 ast2500_get_clk_setting(struct udevice *dev, uint max_hz)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(dev->parent);
+	struct aspeed_spi_priv *priv = dev_get_priv(dev->parent);
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+	u32 hclk_clk = plat->hclk_rate;
+	u32 hclk_div = 0x0000; /* default value */
+	u32 i;
+	bool found = false;
+	/* HCLK/1 ..	HCLK/16 */
+	u32 hclk_masks[] = {15, 7, 14, 6, 13, 5, 12, 4,
+			    11, 3, 10, 2, 9,  1, 8,  0};
+
+	/* FMC/SPIR10[11:8] */
+	for (i = 0; i < ARRAY_SIZE(hclk_masks); i++) {
+		if (hclk_clk / (i + 1) <= max_hz) {
+			found = true;
+			priv->flashes[slave_plat->cs].max_freq =
+							hclk_clk / (i + 1);
+			break;
+		}
+	}
+
+	if (found) {
+		hclk_div = hclk_masks[i] << 8;
+		goto end;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(hclk_masks); i++) {
+		if (hclk_clk / ((i + 1) * 4) <= max_hz) {
+			found = true;
+			priv->flashes[slave_plat->cs].max_freq =
+						hclk_clk / ((i + 1) * 4);
+			break;
+		}
+	}
+
+	if (found)
+		hclk_div = BIT(13) | (hclk_masks[i] << 8);
+
+end:
+	dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", found ? "yes" : "no",
+		hclk_clk, max_hz);
+
+	if (found) {
+		dev_dbg(dev, "h_div: %d (mask %x), speed: %d\n",
+			i + 1, hclk_masks[i], priv->flashes[slave_plat->cs].max_freq);
+	}
+
+	return hclk_div;
+}
+
+static u32 ast2600_spi_segment_start(struct udevice *bus, u32 reg)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	u32 start_offset = (reg << 16) & 0x0ff00000;
+
+	if (start_offset == 0)
+		return (u32)plat->ahb_base;
+
+	return (u32)plat->ahb_base + start_offset;
+}
+
+static u32 ast2600_spi_segment_end(struct udevice *bus, u32 reg)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	u32 end_offset = reg & 0x0ff00000;
+
+	/* Meaningless end_offset, set to physical ahb base. */
+	if (end_offset == 0)
+		return (u32)plat->ahb_base;
+
+	return (u32)plat->ahb_base + end_offset + 0x100000;
+}
+
+static u32 ast2600_spi_segment_reg(u32 start, u32 end)
+{
+	if (start == end)
+		return 0;
+
+	return ((start & 0x0ff00000) >> 16) | ((end - 0x100000) & 0x0ff00000);
+}
+
+static void ast2600_spi_chip_set_4byte(struct udevice *bus, u32 cs)
+{
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 reg_val;
+
+	reg_val = readl(&priv->regs->ctrl);
+	reg_val |= 0x11 << cs;
+	writel(reg_val, &priv->regs->ctrl);
+}
+
+static int ast2600_adjust_decoded_size(struct udevice *bus)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	struct aspeed_spi_flash *flashes = &priv->flashes[0];
+	int ret;
+	int i;
+	int cs;
+	u32 pre_sz;
+	u32 lack_sz;
+
+	/* Close unused CS. */
+	for (cs = priv->num_cs; cs < plat->max_cs; cs++)
+		flashes[cs].ahb_decoded_sz = 0;
+
+	/*
+	 * If commnad mode or normal mode is used, the start address of a
+	 * decoded range should be multiple of its related flash size.
+	 * Namely, the total decoded size from flash 0 to flash N should
+	 * be multiple of the size of flash (N + 1).
+	 */
+	for (cs = priv->num_cs - 1; cs >= 0; cs--) {
+		pre_sz = 0;
+		for (i = 0; i < cs; i++)
+			pre_sz += flashes[i].ahb_decoded_sz;
+
+		if (flashes[cs].ahb_decoded_sz != 0 &&
+		    (pre_sz % flashes[cs].ahb_decoded_sz) != 0) {
+			lack_sz = flashes[cs].ahb_decoded_sz -
+				  (pre_sz % flashes[cs].ahb_decoded_sz);
+			flashes[0].ahb_decoded_sz += lack_sz;
+		}
+	}
+
+	ret = aspeed_spi_trim_decoded_size(bus);
+	if (ret != 0)
+		return ret;
+
+	return 0;
+}
+
+static u32 ast2600_get_clk_setting(struct udevice *dev, uint max_hz)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(dev->parent);
+	struct aspeed_spi_priv *priv = dev_get_priv(dev->parent);
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+	u32 hclk_clk = plat->hclk_rate;
+	u32 hclk_div = 0x0400; /* default value */
+	u32 i, j;
+	bool found = false;
+	/* HCLK/1 ..	HCLK/16 */
+	u32 hclk_masks[] = {15, 7, 14, 6, 13, 5, 12, 4,
+			    11, 3, 10, 2, 9,  1, 8,  0};
+
+	/* FMC/SPIR10[27:24] */
+	for (j = 0; j < 0xf; j++) {
+		/* FMC/SPIR10[11:8] */
+		for (i = 0; i < ARRAY_SIZE(hclk_masks); i++) {
+			if (i == 0 && j == 0)
+				continue;
+
+			if (hclk_clk / (i + 1 + (j * 16)) <= max_hz) {
+				found = true;
+				break;
+			}
+		}
+
+		if (found) {
+			hclk_div = ((j << 24) | hclk_masks[i] << 8);
+			priv->flashes[slave_plat->cs].max_freq =
+						hclk_clk / (i + 1 + j * 16);
+			break;
+		}
+	}
+
+	dev_dbg(dev, "found: %s, hclk: %d, max_clk: %d\n", found ? "yes" : "no",
+		hclk_clk, max_hz);
+
+	if (found) {
+		dev_dbg(dev, "base_clk: %d, h_div: %d (mask %x), speed: %d\n",
+			j, i + 1, hclk_masks[i], priv->flashes[slave_plat->cs].max_freq);
+	}
+
+	return hclk_div;
+}
+
+/*
+ * As the flash size grows up, we need to trim some decoded
+ * size if needed for the sake of conforming the maximum
+ * decoded size. We trim the decoded size from the largest
+ * CS in order to avoid affecting the default boot up sequence
+ * from CS0 where command mode or normal mode is used.
+ * Notice, if a CS decoded size is trimmed, command mode may
+ * not work perfectly on that CS.
+ */
+static int aspeed_spi_trim_decoded_size(struct udevice *bus)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	struct aspeed_spi_flash *flashes = &priv->flashes[0];
+	u32 total_sz;
+	int cs = plat->max_cs - 1;
+	u32 i;
+
+	do {
+		total_sz = 0;
+		for (i = 0; i < plat->max_cs; i++)
+			total_sz += flashes[i].ahb_decoded_sz;
+
+		if (flashes[cs].ahb_decoded_sz <= priv->info->min_decoded_sz)
+			cs--;
+
+		if (cs < 0)
+			return -ENOMEM;
+
+		if (total_sz > plat->ahb_sz) {
+			flashes[cs].ahb_decoded_sz -=
+					priv->info->min_decoded_sz;
+			total_sz -= priv->info->min_decoded_sz;
+		}
+	} while (total_sz > plat->ahb_sz);
+
+	return 0;
+}
+
+static int aspeed_spi_read_from_ahb(void __iomem *ahb_base, void *buf,
+				    size_t len)
+{
+	size_t offset = 0;
+
+	if (IS_ALIGNED((uintptr_t)ahb_base, sizeof(uintptr_t)) &&
+	    IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) {
+		readsl(ahb_base, buf, len >> 2);
+		offset = len & ~0x3;
+		len -= offset;
+	}
+
+	readsb(ahb_base, (u8 *)buf + offset, len);
+
+	return 0;
+}
+
+static int aspeed_spi_write_to_ahb(void __iomem *ahb_base, const void *buf,
+				   size_t len)
+{
+	size_t offset = 0;
+
+	if (IS_ALIGNED((uintptr_t)ahb_base, sizeof(uintptr_t)) &&
+	    IS_ALIGNED((uintptr_t)buf, sizeof(uintptr_t))) {
+		writesl(ahb_base, buf, len >> 2);
+		offset = len & ~0x3;
+		len -= offset;
+	}
+
+	writesb(ahb_base, (u8 *)buf + offset, len);
+
+	return 0;
+}
+
+/*
+ * Currently, only support 1-1-1, 1-1-2 or 1-1-4
+ * SPI NOR flash operation format.
+ */
+static bool aspeed_spi_supports_op(struct spi_slave *slave,
+				   const struct spi_mem_op *op)
+{
+	struct udevice *bus = slave->dev->parent;
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+
+	if (op->cmd.buswidth > 1)
+		return false;
+
+	if (op->addr.nbytes != 0) {
+		if (op->addr.buswidth > 1)
+			return false;
+		if (op->addr.nbytes < 3 || op->addr.nbytes > 4)
+			return false;
+	}
+
+	if (op->dummy.nbytes != 0) {
+		if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7)
+			return false;
+	}
+
+	if (op->data.nbytes != 0 &&
+	    op->data.buswidth > priv->info->max_bus_width)
+		return false;
+
+	if (!spi_mem_default_supports_op(slave, op))
+		return false;
+
+	return true;
+}
+
+static int aspeed_spi_exec_op_user_mode(struct spi_slave *slave,
+					const struct spi_mem_op *op)
+{
+	struct udevice *dev = 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(slave->dev);
+	u32 cs = slave_plat->cs;
+	u32 ce_ctrl_reg = (u32)&priv->regs->ce_ctrl[cs];
+	u32 ce_ctrl_val;
+	struct aspeed_spi_flash *flash = &priv->flashes[cs];
+	u8 dummy_data[16] = {0};
+	u8 addr[4] = {0};
+	int i;
+
+	dev_dbg(dev, "cmd:%x(%d),addr:%llx(%d),dummy:%d(%d),data_len:0x%x(%d)\n",
+		op->cmd.opcode, op->cmd.buswidth, op->addr.val,
+		op->addr.buswidth, op->dummy.nbytes, op->dummy.buswidth,
+		op->data.nbytes, op->data.buswidth);
+
+	if (priv->info == &ast2400_spi_info)
+		ce_ctrl_reg = (u32)&priv->regs->ctrl;
+
+	/*
+	 * Set controller to 4-byte address mode
+	 * if flash is in 4-byte address mode.
+	 */
+	if (op->cmd.opcode == SPINOR_OP_EN4B)
+		priv->info->set_4byte(bus, cs);
+
+	/* Start user mode */
+	ce_ctrl_val = flash->ce_ctrl_user;
+	writel(ce_ctrl_val, ce_ctrl_reg);
+	ce_ctrl_val &= (~CTRL_STOP_ACTIVE);
+	writel(ce_ctrl_val, ce_ctrl_reg);
+
+	/* Send command */
+	aspeed_spi_write_to_ahb(flash->ahb_base, &op->cmd.opcode, 1);
+
+	/* Send address */
+	for (i = op->addr.nbytes; i > 0; i--) {
+		addr[op->addr.nbytes - i] =
+			((u32)op->addr.val >> ((i - 1) * 8)) & 0xff;
+	}
+
+	/* Change io_mode */
+	ce_ctrl_val &= ~priv->info->io_mode_mask;
+	ce_ctrl_val |= aspeed_spi_get_io_mode(op->addr.buswidth);
+	writel(ce_ctrl_val, ce_ctrl_reg);
+	aspeed_spi_write_to_ahb(flash->ahb_base, addr, op->addr.nbytes);
+
+	/* Send dummy cycles */
+	aspeed_spi_write_to_ahb(flash->ahb_base, dummy_data, op->dummy.nbytes);
+
+	/* Change io_mode */
+	ce_ctrl_val &= ~priv->info->io_mode_mask;
+	ce_ctrl_val |= aspeed_spi_get_io_mode(op->data.buswidth);
+	writel(ce_ctrl_val, ce_ctrl_reg);
+
+	/* Send data */
+	if (op->data.dir == SPI_MEM_DATA_OUT) {
+		aspeed_spi_write_to_ahb(flash->ahb_base, op->data.buf.out,
+					op->data.nbytes);
+	} else {
+		aspeed_spi_read_from_ahb(flash->ahb_base, op->data.buf.in,
+					 op->data.nbytes);
+	}
+
+	ce_ctrl_val |= CTRL_STOP_ACTIVE;
+	writel(ce_ctrl_val, ce_ctrl_reg);
+
+	/* Restore controller setting. */
+	writel(flash->ce_ctrl_read, ce_ctrl_reg);
+
+	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 cmd_io_conf;
+	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;
+	}
+
+	cmd_io_conf = 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;
+
+	priv->flashes[cs].ce_ctrl_read &= priv->info->clk_ctrl_mask;
+	priv->flashes[cs].ce_ctrl_read |= cmd_io_conf;
+
+	writel(priv->flashes[cs].ce_ctrl_read, ce_ctrl_reg);
+
+	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;
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 cs = slave_plat->cs;
+
+	if (cs >= plat->max_cs) {
+		dev_err(dev, "invalid CS %u\n", cs);
+		return NULL;
+	}
+
+	return &priv->flashes[cs];
+}
+
+static void aspeed_spi_decoded_base_calculate(struct udevice *bus)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 cs;
+
+	if (priv->fixed_decoded_range)
+		return;
+
+	priv->flashes[0].ahb_base = plat->ahb_base;
+
+	for (cs = 1; cs < plat->max_cs; cs++) {
+		priv->flashes[cs].ahb_base =
+				priv->flashes[cs - 1].ahb_base +
+				priv->flashes[cs - 1].ahb_decoded_sz;
+	}
+}
+
+static void aspeed_spi_decoded_range_set(struct udevice *bus)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 decoded_reg_val;
+	u32 start_addr, end_addr;
+	u32 cs;
+
+	for (cs = 0; cs < plat->max_cs; cs++) {
+		start_addr = (u32)priv->flashes[cs].ahb_base;
+		end_addr = (u32)priv->flashes[cs].ahb_base +
+			   priv->flashes[cs].ahb_decoded_sz;
+
+		decoded_reg_val = priv->info->segment_reg(start_addr, end_addr);
+
+		writel(decoded_reg_val, &priv->regs->segment_addr[cs]);
+
+		dev_dbg(bus, "cs: %d, decoded_reg: 0x%x, start: 0x%x, end: 0x%x\n",
+			cs, decoded_reg_val, start_addr, end_addr);
+	}
+}
+
+static int aspeed_spi_decoded_range_config(struct udevice *bus)
+{
+	int ret = 0;
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+
+	if (priv->info->adjust_decoded_sz &&
+	    !priv->fixed_decoded_range) {
+		ret = priv->info->adjust_decoded_sz(bus);
+		if (ret != 0)
+			return ret;
+	}
+
+	aspeed_spi_decoded_base_calculate(bus);
+	aspeed_spi_decoded_range_set(bus);
+
+	return ret;
+}
+
+static int aspeed_spi_decoded_ranges_sanity(struct udevice *bus)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 cs;
+	u32 total_sz = 0;
+
+	/* Check overall size. */
+	for (cs = 0; cs < plat->max_cs; cs++)
+		total_sz += priv->flashes[cs].ahb_decoded_sz;
+
+	if (total_sz > plat->ahb_sz) {
+		dev_err(bus, "invalid total size 0x%08x\n", total_sz);
+		return -EINVAL;
+	}
+
+	/* Check each decoded range size for AST2500. */
+	if (priv->info == &ast2500_fmc_info ||
+	    priv->info == &ast2500_spi_info) {
+		for (cs = 0; cs < plat->max_cs; cs++) {
+			if (priv->flashes[cs].ahb_decoded_sz <
+			    priv->info->min_decoded_sz) {
+				dev_err(bus, "insufficient decoded range.\n");
+				return -EINVAL;
+			}
+		}
+	}
+
+	/*
+	 * Check overlay. Here, we assume the deccded ranges and
+	 * address base	are monotonic increasing with CE#.
+	 */
+	for (cs = plat->max_cs - 1; cs > 0; cs--) {
+		if ((u32)priv->flashes[cs].ahb_base != 0 &&
+		    (u32)priv->flashes[cs].ahb_base <
+		    (u32)priv->flashes[cs - 1].ahb_base +
+		    priv->flashes[cs - 1].ahb_decoded_sz) {
+			dev_err(bus, "decoded range overlay 0x%08x 0x%08x\n",
+				(u32)priv->flashes[cs].ahb_base,
+				(u32)priv->flashes[cs - 1].ahb_base);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int aspeed_spi_read_fixed_decoded_ranges(struct udevice *bus)
+{
+	int ret = 0;
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	const char *range_prop = "decoded-ranges";
+	struct aspeed_spi_decoded_range ranges[ASPEED_SPI_MAX_CS];
+	const struct property *prop;
+	u32 prop_sz;
+	u32 count;
+	u32 i;
+
+	priv->fixed_decoded_range = false;
+
+	prop = dev_read_prop(bus, range_prop, &prop_sz);
+	if (!prop)
+		return 0;
+
+	count = prop_sz / sizeof(struct aspeed_spi_decoded_range);
+	if (count > plat->max_cs || count < priv->num_cs) {
+		dev_err(bus, "invalid '%s' property %d %d\n",
+			range_prop, count, priv->num_cs);
+		return -EINVAL;
+	}
+
+	ret = dev_read_u32_array(bus, range_prop, (u32 *)ranges, count * 3);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < count; i++) {
+		priv->flashes[ranges[i].cs].ahb_base =
+				(void __iomem *)ranges[i].ahb_base;
+		priv->flashes[ranges[i].cs].ahb_decoded_sz =
+				ranges[i].sz;
+	}
+
+	for (i = 0; i < plat->max_cs; i++) {
+		dev_dbg(bus, "ahb_base: 0x%p, size: 0x%08x\n",
+			priv->flashes[i].ahb_base,
+			priv->flashes[i].ahb_decoded_sz);
+	}
+
+	ret = aspeed_spi_decoded_ranges_sanity(bus);
+	if (ret != 0)
+		return ret;
+
+	priv->fixed_decoded_range = true;
+
+	return 0;
+}
+
+/*
+ * Initialize SPI controller for each chip select.
+ * Here, only the minimum decode range is configured
+ * in order to get device (SPI NOR flash) information
+ * at the early stage.
+ */
+static int aspeed_spi_ctrl_init(struct udevice *bus)
+{
+	int ret;
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	u32 cs;
+	u32 reg_val;
+	u32 decoded_sz;
+
+	/* Enable write capability for all CS. */
+	reg_val = readl(&priv->regs->conf);
+	if (priv->info == &ast2400_spi_info) {
+		writel(reg_val | BIT(0), &priv->regs->conf);
+	} else {
+		writel(reg_val | (GENMASK(plat->max_cs - 1, 0) << 16),
+		       &priv->regs->conf);
+	}
+
+	memset(priv->flashes, 0x0,
+	       sizeof(struct aspeed_spi_flash) * ASPEED_SPI_MAX_CS);
+
+	/* Initial user mode. */
+	for (cs = 0; cs < priv->num_cs; cs++) {
+		priv->flashes[cs].ce_ctrl_user &= priv->info->clk_ctrl_mask;
+		priv->flashes[cs].ce_ctrl_user |=
+				(CTRL_STOP_ACTIVE | CTRL_IO_MODE_USER);
+	}
+
+	/*
+	 * SPI1 on AST2400 only supports CS0.
+	 * It is unnecessary to configure segment address register.
+	 */
+	if (priv->info == &ast2400_spi_info) {
+		priv->flashes[cs].ahb_base = plat->ahb_base;
+		priv->flashes[cs].ahb_decoded_sz = 0x10000000;
+		return 0;
+	}
+
+
+	ret = aspeed_spi_read_fixed_decoded_ranges(bus);
+	if (ret != 0)
+		return ret;
+
+	if (!priv->fixed_decoded_range) {
+		/* Assign basic AHB decoded size for each CS. */
+		for (cs = 0; cs < plat->max_cs; cs++) {
+			reg_val = readl(&priv->regs->segment_addr[cs]);
+			decoded_sz = priv->info->segment_end(bus, reg_val) -
+				     priv->info->segment_start(bus, reg_val);
+
+			if (decoded_sz < priv->info->min_decoded_sz)
+				decoded_sz = priv->info->min_decoded_sz;
+
+			priv->flashes[cs].ahb_decoded_sz = decoded_sz;
+		}
+	}
+
+	ret = aspeed_spi_decoded_range_config(bus);
+
+	return ret;
+}
+
+static const struct aspeed_spi_info ast2400_fmc_info = {
+	.io_mode_mask = 0x70000000,
+	.max_bus_width = 2,
+	.min_decoded_sz = 0x800000,
+	.clk_ctrl_mask = 0x00002f00,
+	.set_4byte = ast2400_fmc_chip_set_4byte,
+	.segment_start = ast2400_spi_segment_start,
+	.segment_end = ast2400_spi_segment_end,
+	.segment_reg = ast2400_spi_segment_reg,
+	.get_clk_setting = ast2400_get_clk_setting,
+};
+
+static const struct aspeed_spi_info ast2400_spi_info = {
+	.io_mode_mask = 0x70000000,
+	.max_bus_width = 2,
+	.min_decoded_sz = 0x800000,
+	.clk_ctrl_mask = 0x00000f00,
+	.set_4byte = ast2400_spi_chip_set_4byte,
+	.segment_start = ast2400_spi_segment_start,
+	.segment_end = ast2400_spi_segment_end,
+	.segment_reg = ast2400_spi_segment_reg,
+	.get_clk_setting = ast2400_get_clk_setting,
+};
+
+static const struct aspeed_spi_info ast2500_fmc_info = {
+	.io_mode_mask = 0x70000000,
+	.max_bus_width = 2,
+	.min_decoded_sz = 0x800000,
+	.clk_ctrl_mask = 0x00002f00,
+	.set_4byte = ast2500_spi_chip_set_4byte,
+	.segment_start = ast2500_spi_segment_start,
+	.segment_end = ast2500_spi_segment_end,
+	.segment_reg = ast2500_spi_segment_reg,
+	.adjust_decoded_sz = ast2500_adjust_decoded_size,
+	.get_clk_setting = ast2500_get_clk_setting,
+};
+
+/*
+ * There are some different between FMC and SPI controllers.
+ * For example, DMA operation, but this isn't implemented currently.
+ */
+static const struct aspeed_spi_info ast2500_spi_info = {
+	.io_mode_mask = 0x70000000,
+	.max_bus_width = 2,
+	.min_decoded_sz = 0x800000,
+	.clk_ctrl_mask = 0x00002f00,
+	.set_4byte = ast2500_spi_chip_set_4byte,
+	.segment_start = ast2500_spi_segment_start,
+	.segment_end = ast2500_spi_segment_end,
+	.segment_reg = ast2500_spi_segment_reg,
+	.adjust_decoded_sz = ast2500_adjust_decoded_size,
+	.get_clk_setting = ast2500_get_clk_setting,
+};
+
+static const struct aspeed_spi_info ast2600_fmc_info = {
+	.io_mode_mask = 0xf0000000,
+	.max_bus_width = 4,
+	.min_decoded_sz = 0x200000,
+	.clk_ctrl_mask = 0x0f000f00,
+	.set_4byte = ast2600_spi_chip_set_4byte,
+	.segment_start = ast2600_spi_segment_start,
+	.segment_end = ast2600_spi_segment_end,
+	.segment_reg = ast2600_spi_segment_reg,
+	.adjust_decoded_sz = ast2600_adjust_decoded_size,
+	.get_clk_setting = ast2600_get_clk_setting,
+};
+
+static const struct aspeed_spi_info ast2600_spi_info = {
+	.io_mode_mask = 0xf0000000,
+	.max_bus_width = 4,
+	.min_decoded_sz = 0x200000,
+	.clk_ctrl_mask = 0x0f000f00,
+	.set_4byte = ast2600_spi_chip_set_4byte,
+	.segment_start = ast2600_spi_segment_start,
+	.segment_end = ast2600_spi_segment_end,
+	.segment_reg = ast2600_spi_segment_reg,
+	.adjust_decoded_sz = ast2600_adjust_decoded_size,
+	.get_clk_setting = ast2600_get_clk_setting,
+};
+
+static int aspeed_spi_claim_bus(struct udevice *dev)
+{
+	struct udevice *bus = dev->parent;
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+	struct aspeed_spi_priv *priv = dev_get_priv(dev->parent);
+	struct aspeed_spi_flash *flash = &priv->flashes[slave_plat->cs];
+	u32 clk_setting;
+
+	dev_dbg(bus, "%s: claim bus CS%u\n", bus->name, slave_plat->cs);
+
+	if (flash->max_freq == 0) {
+		clk_setting = priv->info->get_clk_setting(dev, slave_plat->max_hz);
+		flash->ce_ctrl_user &= ~(priv->info->clk_ctrl_mask);
+		flash->ce_ctrl_user |= clk_setting;
+		flash->ce_ctrl_read &= ~(priv->info->clk_ctrl_mask);
+		flash->ce_ctrl_read |= clk_setting;
+	}
+
+	return 0;
+}
+
+static int aspeed_spi_release_bus(struct udevice *dev)
+{
+	struct udevice *bus = dev->parent;
+	struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
+
+	dev_dbg(bus, "%s: release bus CS%u\n", bus->name, slave_plat->cs);
+
+	if (!aspeed_spi_get_flash(dev))
+		return -ENODEV;
+
+	return 0;
+}
+
+static int aspeed_spi_set_mode(struct udevice *bus, uint mode)
+{
+	dev_dbg(bus, "%s: setting mode to %x\n", bus->name, mode);
+
+	return 0;
+}
+
+static int aspeed_spi_set_speed(struct udevice *bus, uint hz)
+{
+	dev_dbg(bus, "%s: setting speed to %u\n", bus->name, hz);
+	/*
+	 * ASPEED SPI controller supports multiple CS with different
+	 * clock frequency. We cannot distinguish which CS here.
+	 * Thus, the related implementation is postponed to claim_bus.
+	 */
+
+	return 0;
+}
+
+static int apseed_spi_of_to_plat(struct udevice *bus)
+{
+	struct aspeed_spi_plat *plat = dev_get_plat(bus);
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	int ret;
+	struct clk hclk;
+
+	priv->regs = (void __iomem *)devfdt_get_addr_index(bus, 0);
+	if ((u32)priv->regs == FDT_ADDR_T_NONE) {
+		dev_err(bus, "wrong ctrl base\n");
+		return -ENODEV;
+	}
+
+	plat->ahb_base =
+		(void __iomem *)devfdt_get_addr_size_index(bus, 1, &plat->ahb_sz);
+	if ((u32)plat->ahb_base == FDT_ADDR_T_NONE) {
+		dev_err(bus, "wrong AHB base\n");
+		return -ENODEV;
+	}
+
+	plat->max_cs = dev_read_u32_default(bus, "num-cs", ASPEED_SPI_MAX_CS);
+	if (plat->max_cs > ASPEED_SPI_MAX_CS)
+		return -EINVAL;
+
+	ret = clk_get_by_index(bus, 0, &hclk);
+	if (ret < 0) {
+		dev_err(bus, "%s could not get clock: %d\n", bus->name, ret);
+		return ret;
+	}
+
+	plat->hclk_rate = clk_get_rate(&hclk);
+	clk_free(&hclk);
+
+	dev_dbg(bus, "ctrl_base = 0x%x, ahb_base = 0x%p, size = 0x%lx\n",
+		(u32)priv->regs, plat->ahb_base, plat->ahb_sz);
+	dev_dbg(bus, "hclk = %dMHz, max_cs = %d\n",
+		plat->hclk_rate / 1000000, plat->max_cs);
+
+	return 0;
+}
+
+static int aspeed_spi_probe(struct udevice *bus)
+{
+	int ret;
+	struct aspeed_spi_priv *priv = dev_get_priv(bus);
+	struct udevice *dev;
+
+	priv->info = (struct aspeed_spi_info *)dev_get_driver_data(bus);
+
+	priv->num_cs = 0;
+	for (device_find_first_child(bus, &dev); dev;
+	     device_find_next_child(&dev)) {
+		priv->num_cs++;
+	}
+
+	if (priv->num_cs > ASPEED_SPI_MAX_CS)
+		return -EINVAL;
+
+	ret = aspeed_spi_ctrl_init(bus);
+
+	return ret;
+}
+
+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 = {
+	.claim_bus = aspeed_spi_claim_bus,
+	.release_bus = aspeed_spi_release_bus,
+	.set_speed = aspeed_spi_set_speed,
+	.set_mode = aspeed_spi_set_mode,
+	.mem_ops = &aspeed_spi_mem_ops,
+};
+
+static const struct udevice_id aspeed_spi_ids[] = {
+	{ .compatible = "aspeed,ast2400-fmc", .data = (ulong)&ast2400_fmc_info, },
+	{ .compatible = "aspeed,ast2400-spi", .data = (ulong)&ast2400_spi_info, },
+	{ .compatible = "aspeed,ast2500-fmc", .data = (ulong)&ast2500_fmc_info, },
+	{ .compatible = "aspeed,ast2500-spi", .data = (ulong)&ast2500_spi_info, },
+	{ .compatible = "aspeed,ast2600-fmc", .data = (ulong)&ast2600_fmc_info, },
+	{ .compatible = "aspeed,ast2600-spi", .data = (ulong)&ast2600_spi_info, },
+	{ }
+};
+
+U_BOOT_DRIVER(aspeed_spi) = {
+	.name = "aspeed_spi_smc",
+	.id = UCLASS_SPI,
+	.of_match = aspeed_spi_ids,
+	.ops = &aspeed_spi_ops,
+	.of_to_plat = apseed_spi_of_to_plat,
+	.plat_auto = sizeof(struct aspeed_spi_plat),
+	.priv_auto = sizeof(struct aspeed_spi_priv),
+	.probe = aspeed_spi_probe,
+};
diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index 9c1ede1..8e8995f 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -21,6 +21,8 @@
 #include <spi.h>
 #include <spi-mem.h>
 #include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <linux/bug.h>
 #endif
 
 #ifndef __UBOOT__
@@ -491,6 +493,272 @@
 }
 EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
 
+static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
+				      u64 offs, size_t len, void *buf)
+{
+	struct spi_mem_op op = desc->info.op_tmpl;
+	int ret;
+
+	op.addr.val = desc->info.offset + offs;
+	op.data.buf.in = buf;
+	op.data.nbytes = len;
+	ret = spi_mem_adjust_op_size(desc->slave, &op);
+	if (ret)
+		return ret;
+
+	ret = spi_mem_exec_op(desc->slave, &op);
+	if (ret)
+		return ret;
+
+	return op.data.nbytes;
+}
+
+static ssize_t spi_mem_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
+				       u64 offs, size_t len, const void *buf)
+{
+	struct spi_mem_op op = desc->info.op_tmpl;
+	int ret;
+
+	op.addr.val = desc->info.offset + offs;
+	op.data.buf.out = buf;
+	op.data.nbytes = len;
+	ret = spi_mem_adjust_op_size(desc->slave, &op);
+	if (ret)
+		return ret;
+
+	ret = spi_mem_exec_op(desc->slave, &op);
+	if (ret)
+		return ret;
+
+	return op.data.nbytes;
+}
+
+/**
+ * spi_mem_dirmap_create() - Create a direct mapping descriptor
+ * @mem: SPI mem device this direct mapping should be created for
+ * @info: direct mapping information
+ *
+ * This function is creating a direct mapping descriptor which can then be used
+ * to access the memory using spi_mem_dirmap_read() or spi_mem_dirmap_write().
+ * If the SPI controller driver does not support direct mapping, this function
+ * falls back to an implementation using spi_mem_exec_op(), so that the caller
+ * doesn't have to bother implementing a fallback on his own.
+ *
+ * Return: a valid pointer in case of success, and ERR_PTR() otherwise.
+ */
+struct spi_mem_dirmap_desc *
+spi_mem_dirmap_create(struct spi_slave *slave,
+		      const struct spi_mem_dirmap_info *info)
+{
+	struct udevice *bus = slave->dev->parent;
+	struct dm_spi_ops *ops = spi_get_ops(bus);
+	struct spi_mem_dirmap_desc *desc;
+	int ret = -EOPNOTSUPP;
+
+	/* Make sure the number of address cycles is between 1 and 8 bytes. */
+	if (!info->op_tmpl.addr.nbytes || info->op_tmpl.addr.nbytes > 8)
+		return ERR_PTR(-EINVAL);
+
+	/* data.dir should either be SPI_MEM_DATA_IN or SPI_MEM_DATA_OUT. */
+	if (info->op_tmpl.data.dir == SPI_MEM_NO_DATA)
+		return ERR_PTR(-EINVAL);
+
+	desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		return ERR_PTR(-ENOMEM);
+
+	desc->slave = slave;
+	desc->info = *info;
+	if (ops->mem_ops && ops->mem_ops->dirmap_create)
+		ret = ops->mem_ops->dirmap_create(desc);
+
+	if (ret) {
+		desc->nodirmap = true;
+		if (!spi_mem_supports_op(desc->slave, &desc->info.op_tmpl))
+			ret = -EOPNOTSUPP;
+		else
+			ret = 0;
+	}
+
+	if (ret) {
+		kfree(desc);
+		return ERR_PTR(ret);
+	}
+
+	return desc;
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_create);
+
+/**
+ * spi_mem_dirmap_destroy() - Destroy a direct mapping descriptor
+ * @desc: the direct mapping descriptor to destroy
+ *
+ * This function destroys a direct mapping descriptor previously created by
+ * spi_mem_dirmap_create().
+ */
+void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc)
+{
+	struct udevice *bus = desc->slave->dev->parent;
+	struct dm_spi_ops *ops = spi_get_ops(bus);
+
+	if (!desc->nodirmap && ops->mem_ops && ops->mem_ops->dirmap_destroy)
+		ops->mem_ops->dirmap_destroy(desc);
+
+	kfree(desc);
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_destroy);
+
+#ifndef __UBOOT__
+static void devm_spi_mem_dirmap_release(struct udevice *dev, void *res)
+{
+	struct spi_mem_dirmap_desc *desc = *(struct spi_mem_dirmap_desc **)res;
+
+	spi_mem_dirmap_destroy(desc);
+}
+
+/**
+ * devm_spi_mem_dirmap_create() - Create a direct mapping descriptor and attach
+ *				  it to a device
+ * @dev: device the dirmap desc will be attached to
+ * @mem: SPI mem device this direct mapping should be created for
+ * @info: direct mapping information
+ *
+ * devm_ variant of the spi_mem_dirmap_create() function. See
+ * spi_mem_dirmap_create() for more details.
+ *
+ * Return: a valid pointer in case of success, and ERR_PTR() otherwise.
+ */
+struct spi_mem_dirmap_desc *
+devm_spi_mem_dirmap_create(struct udevice *dev, struct spi_slave *slave,
+			   const struct spi_mem_dirmap_info *info)
+{
+	struct spi_mem_dirmap_desc **ptr, *desc;
+
+	ptr = devres_alloc(devm_spi_mem_dirmap_release, sizeof(*ptr),
+			   GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	desc = spi_mem_dirmap_create(slave, info);
+	if (IS_ERR(desc)) {
+		devres_free(ptr);
+	} else {
+		*ptr = desc;
+		devres_add(dev, ptr);
+	}
+
+	return desc;
+}
+EXPORT_SYMBOL_GPL(devm_spi_mem_dirmap_create);
+
+static int devm_spi_mem_dirmap_match(struct udevice *dev, void *res, void *data)
+{
+	struct spi_mem_dirmap_desc **ptr = res;
+
+	if (WARN_ON(!ptr || !*ptr))
+		return 0;
+
+	return *ptr == data;
+}
+
+/**
+ * devm_spi_mem_dirmap_destroy() - Destroy a direct mapping descriptor attached
+ *				   to a device
+ * @dev: device the dirmap desc is attached to
+ * @desc: the direct mapping descriptor to destroy
+ *
+ * devm_ variant of the spi_mem_dirmap_destroy() function. See
+ * spi_mem_dirmap_destroy() for more details.
+ */
+void devm_spi_mem_dirmap_destroy(struct udevice *dev,
+				 struct spi_mem_dirmap_desc *desc)
+{
+	devres_release(dev, devm_spi_mem_dirmap_release,
+		       devm_spi_mem_dirmap_match, desc);
+}
+EXPORT_SYMBOL_GPL(devm_spi_mem_dirmap_destroy);
+#endif /* __UBOOT__ */
+
+/**
+ * spi_mem_dirmap_read() - Read data through a direct mapping
+ * @desc: direct mapping descriptor
+ * @offs: offset to start reading from. Note that this is not an absolute
+ *	  offset, but the offset within the direct mapping which already has
+ *	  its own offset
+ * @len: length in bytes
+ * @buf: destination buffer. This buffer must be DMA-able
+ *
+ * This function reads data from a memory device using a direct mapping
+ * previously instantiated with spi_mem_dirmap_create().
+ *
+ * Return: the amount of data read from the memory device or a negative error
+ * code. Note that the returned size might be smaller than @len, and the caller
+ * is responsible for calling spi_mem_dirmap_read() again when that happens.
+ */
+ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
+			    u64 offs, size_t len, void *buf)
+{
+	struct udevice *bus = desc->slave->dev->parent;
+	struct dm_spi_ops *ops = spi_get_ops(bus);
+	ssize_t ret;
+
+	if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN)
+		return -EINVAL;
+
+	if (!len)
+		return 0;
+
+	if (desc->nodirmap)
+		ret = spi_mem_no_dirmap_read(desc, offs, len, buf);
+	else if (ops->mem_ops && ops->mem_ops->dirmap_read)
+		ret = ops->mem_ops->dirmap_read(desc, offs, len, buf);
+	else
+		ret = -EOPNOTSUPP;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_read);
+
+/**
+ * spi_mem_dirmap_write() - Write data through a direct mapping
+ * @desc: direct mapping descriptor
+ * @offs: offset to start writing from. Note that this is not an absolute
+ *	  offset, but the offset within the direct mapping which already has
+ *	  its own offset
+ * @len: length in bytes
+ * @buf: source buffer. This buffer must be DMA-able
+ *
+ * This function writes data to a memory device using a direct mapping
+ * previously instantiated with spi_mem_dirmap_create().
+ *
+ * Return: the amount of data written to the memory device or a negative error
+ * code. Note that the returned size might be smaller than @len, and the caller
+ * is responsible for calling spi_mem_dirmap_write() again when that happens.
+ */
+ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
+			     u64 offs, size_t len, const void *buf)
+{
+	struct udevice *bus = desc->slave->dev->parent;
+	struct dm_spi_ops *ops = spi_get_ops(bus);
+	ssize_t ret;
+
+	if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_OUT)
+		return -EINVAL;
+
+	if (!len)
+		return 0;
+
+	if (desc->nodirmap)
+		ret = spi_mem_no_dirmap_write(desc, offs, len, buf);
+	else if (ops->mem_ops && ops->mem_ops->dirmap_write)
+		ret = ops->mem_ops->dirmap_write(desc, offs, len, buf);
+	else
+		ret = -EOPNOTSUPP;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(spi_mem_dirmap_write);
+
 #ifndef __UBOOT__
 static inline struct spi_mem_driver *to_spi_mem_drv(struct device_driver *drv)
 {
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 2595bad..638d807 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -11,6 +11,7 @@
 #include <linux/bitops.h>
 #include <linux/mtd/cfi.h>
 #include <linux/mtd/mtd.h>
+#include <spi-mem.h>
 
 /*
  * Manufacturer IDs
@@ -522,6 +523,7 @@
  * @quad_enable:	[FLASH-SPECIFIC] enables SPI NOR quad mode
  * @octal_dtr_enable:	[FLASH-SPECIFIC] enables SPI NOR octal DTR mode.
  * @ready:		[FLASH-SPECIFIC] check if the flash is ready
+ * @dirmap:		pointers to struct spi_mem_dirmap_desc for reads/writes.
  * @priv:		the private data
  */
 struct spi_nor {
@@ -572,6 +574,11 @@
 	int (*octal_dtr_enable)(struct spi_nor *nor);
 	int (*ready)(struct spi_nor *nor);
 
+	struct {
+		struct spi_mem_dirmap_desc *rdesc;
+		struct spi_mem_dirmap_desc *wdesc;
+	} dirmap;
+
 	void *priv;
 	char mtd_name[MTD_NAME_SIZE(MTD_DEV_TYPE_NOR)];
 /* Compatibility for spi_flash, remove once sf layer is merged with mtd */
@@ -596,6 +603,17 @@
 #endif /* __UBOOT__ */
 
 /**
+ * spi_nor_setup_op() - Set up common properties of a spi-mem op.
+ * @nor:		pointer to a 'struct spi_nor'
+ * @op:			pointer to the 'struct spi_mem_op' whose properties
+ *			need to be initialized.
+ * @proto:		the protocol from which the properties need to be set.
+ */
+void spi_nor_setup_op(const struct spi_nor *nor,
+		      struct spi_mem_op *op,
+		      const enum spi_nor_protocol proto);
+
+/**
  * spi_nor_scan() - scan the SPI NOR
  * @nor:	the spi_nor structure
  *
diff --git a/include/spi-mem.h b/include/spi-mem.h
index 32ffdc2..b07cf2e 100644
--- a/include/spi-mem.h
+++ b/include/spi-mem.h
@@ -134,6 +134,48 @@
 		.dummy = __dummy,				\
 		.data = __data,					\
 	}
+/**
+ * struct spi_mem_dirmap_info - Direct mapping information
+ * @op_tmpl: operation template that should be used by the direct mapping when
+ *	     the memory device is accessed
+ * @offset: absolute offset this direct mapping is pointing to
+ * @length: length in byte of this direct mapping
+ *
+ * This information is used by the controller specific implementation to know
+ * the portion of memory that is directly mapped and the spi_mem_op that should
+ * be used to access the device.
+ * A direct mapping is only valid for one direction (read or write) and this
+ * direction is directly encoded in the ->op_tmpl.data.dir field.
+ */
+struct spi_mem_dirmap_info {
+	struct spi_mem_op op_tmpl;
+	u64 offset;
+	u64 length;
+};
+
+/**
+ * struct spi_mem_dirmap_desc - Direct mapping descriptor
+ * @mem: the SPI memory device this direct mapping is attached to
+ * @info: information passed at direct mapping creation time
+ * @nodirmap: set to 1 if the SPI controller does not implement
+ *            ->mem_ops->dirmap_create() or when this function returned an
+ *            error. If @nodirmap is true, all spi_mem_dirmap_{read,write}()
+ *            calls will use spi_mem_exec_op() to access the memory. This is a
+ *            degraded mode that allows spi_mem drivers to use the same code
+ *            no matter whether the controller supports direct mapping or not
+ * @priv: field pointing to controller specific data
+ *
+ * Common part of a direct mapping descriptor. This object is created by
+ * spi_mem_dirmap_create() and controller implementation of ->create_dirmap()
+ * can create/attach direct mapping resources to the descriptor in the ->priv
+ * field.
+ */
+struct spi_mem_dirmap_desc {
+	struct spi_slave *slave;
+	struct spi_mem_dirmap_info info;
+	unsigned int nodirmap;
+	void *priv;
+};
 
 #ifndef __UBOOT__
 /**
@@ -183,10 +225,32 @@
  *		    limitations)
  * @supports_op: check if an operation is supported by the controller
  * @exec_op: execute a SPI memory operation
+ * @dirmap_create: create a direct mapping descriptor that can later be used to
+ *		   access the memory device. This method is optional
+ * @dirmap_destroy: destroy a memory descriptor previous created by
+ *		    ->dirmap_create()
+ * @dirmap_read: read data from the memory device using the direct mapping
+ *		 created by ->dirmap_create(). The function can return less
+ *		 data than requested (for example when the request is crossing
+ *		 the currently mapped area), and the caller of
+ *		 spi_mem_dirmap_read() is responsible for calling it again in
+ *		 this case.
+ * @dirmap_write: write data to the memory device using the direct mapping
+ *		  created by ->dirmap_create(). The function can return less
+ *		  data than requested (for example when the request is crossing
+ *		  the currently mapped area), and the caller of
+ *		  spi_mem_dirmap_write() is responsible for calling it again in
+ *		  this case.
  *
  * This interface should be implemented by SPI controllers providing an
  * high-level interface to execute SPI memory operation, which is usually the
  * case for QSPI controllers.
+ *
+ * Note on ->dirmap_{read,write}(): drivers should avoid accessing the direct
+ * mapping from the CPU because doing that can stall the CPU waiting for the
+ * SPI mem transaction to finish, and this will make real-time maintainers
+ * unhappy and might make your system less reactive. Instead, drivers should
+ * use DMA to access this direct mapping.
  */
 struct spi_controller_mem_ops {
 	int (*adjust_op_size)(struct spi_slave *slave, struct spi_mem_op *op);
@@ -194,6 +258,12 @@
 			    const struct spi_mem_op *op);
 	int (*exec_op)(struct spi_slave *slave,
 		       const struct spi_mem_op *op);
+	int (*dirmap_create)(struct spi_mem_dirmap_desc *desc);
+	void (*dirmap_destroy)(struct spi_mem_dirmap_desc *desc);
+	ssize_t (*dirmap_read)(struct spi_mem_dirmap_desc *desc,
+			       u64 offs, size_t len, void *buf);
+	ssize_t (*dirmap_write)(struct spi_mem_dirmap_desc *desc,
+				u64 offs, size_t len, const void *buf);
 };
 
 #ifndef __UBOOT__
@@ -260,6 +330,15 @@
 bool spi_mem_default_supports_op(struct spi_slave *mem,
 				 const struct spi_mem_op *op);
 
+struct spi_mem_dirmap_desc *
+spi_mem_dirmap_create(struct spi_slave *mem,
+		      const struct spi_mem_dirmap_info *info);
+void spi_mem_dirmap_destroy(struct spi_mem_dirmap_desc *desc);
+ssize_t spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
+			    u64 offs, size_t len, void *buf);
+ssize_t spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
+			     u64 offs, size_t len, const void *buf);
+
 #ifndef __UBOOT__
 int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv,
 				       struct module *owner);