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);