Message ID | 20250510-wmt-sflash-v1-2-02a1ac6adf12@gmail.com |
---|---|
State | New |
Headers | show |
Series | mtd: spi-nor: Add VIA/WonderMedia serial flash controller driver | expand |
Hi Alexey, kernel test robot noticed the following build errors: [auto build test ERROR on ed61cb3d78d585209ec775933078e268544fe9a4] url: https://github.com/intel-lab-lkp/linux/commits/Alexey-Charkov/dt-bindings-spi-Add-VIA-WonderMedia-serial-flash-controller/20250511-034459 base: ed61cb3d78d585209ec775933078e268544fe9a4 patch link: https://lore.kernel.org/r/20250510-wmt-sflash-v1-2-02a1ac6adf12%40gmail.com patch subject: [PATCH 2/3] mtd: spi-nor: Add a driver for the VIA/WonderMedia serial flash controller config: sh-allmodconfig (https://download.01.org/0day-ci/archive/20250511/202505111905.tlinDurh-lkp@intel.com/config) compiler: sh4-linux-gcc (GCC) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250511/202505111905.tlinDurh-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202505111905.tlinDurh-lkp@intel.com/ All errors (new ones prefixed by >>): drivers/mtd/spi-nor/controllers/wmt-sflash.c: In function 'wmt_sflash_pcmd_mode': >> drivers/mtd/spi-nor/controllers/wmt-sflash.c:132:16: error: implicit declaration of function 'FIELD_PREP' [-Wimplicit-function-declaration] 132 | reg |= FIELD_PREP(SF_PROG_CMD_MOD, enable); | ^~~~~~~~~~ vim +/FIELD_PREP +132 drivers/mtd/spi-nor/controllers/wmt-sflash.c 126 127 static void wmt_sflash_pcmd_mode(struct wmt_sflash_host *host, bool enable) 128 { 129 u32 reg = readl(host->regbase + SF_SPI_INTF_CFG); 130 131 reg &= ~SF_PROG_CMD_MOD; > 132 reg |= FIELD_PREP(SF_PROG_CMD_MOD, enable); 133 writel(reg, host->regbase + SF_SPI_INTF_CFG); 134 } 135
Hi Alexey, kernel test robot noticed the following build warnings: [auto build test WARNING on ed61cb3d78d585209ec775933078e268544fe9a4] url: https://github.com/intel-lab-lkp/linux/commits/Alexey-Charkov/dt-bindings-spi-Add-VIA-WonderMedia-serial-flash-controller/20250511-034459 base: ed61cb3d78d585209ec775933078e268544fe9a4 patch link: https://lore.kernel.org/r/20250510-wmt-sflash-v1-2-02a1ac6adf12%40gmail.com patch subject: [PATCH 2/3] mtd: spi-nor: Add a driver for the VIA/WonderMedia serial flash controller config: s390-allyesconfig (https://download.01.org/0day-ci/archive/20250511/202505112028.FOL7oTti-lkp@intel.com/config) compiler: s390-linux-gcc (GCC) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250511/202505112028.FOL7oTti-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202505112028.FOL7oTti-lkp@intel.com/ All warnings (new ones prefixed by >>): In file included from include/linux/device.h:15, from include/linux/mtd/mtd.h:13, from drivers/mtd/spi-nor/controllers/wmt-sflash.c:14: drivers/mtd/spi-nor/controllers/wmt-sflash.c: In function 'wmt_sflash_read_reg': >> drivers/mtd/spi-nor/controllers/wmt-sflash.c:154:17: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 154 | "Cannot read %d bytes from registers\n", len); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:110:30: note: in definition of macro 'dev_printk_index_wrap' 110 | _p_func(dev, fmt, ##__VA_ARGS__); \ | ^~~ include/linux/dev_printk.h:154:56: note: in expansion of macro 'dev_fmt' 154 | dev_printk_index_wrap(_dev_err, KERN_ERR, dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~ drivers/mtd/spi-nor/controllers/wmt-sflash.c:153:17: note: in expansion of macro 'dev_err' 153 | dev_err(host->dev, | ^~~~~~~ drivers/mtd/spi-nor/controllers/wmt-sflash.c:154:31: note: format string is defined here 154 | "Cannot read %d bytes from registers\n", len); | ~^ | | | int | %ld drivers/mtd/spi-nor/controllers/wmt-sflash.c: In function 'wmt_sflash_write_reg': drivers/mtd/spi-nor/controllers/wmt-sflash.c:187:17: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 187 | "Cannot write %d bytes to registers\n", len); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:110:30: note: in definition of macro 'dev_printk_index_wrap' 110 | _p_func(dev, fmt, ##__VA_ARGS__); \ | ^~~ include/linux/dev_printk.h:154:56: note: in expansion of macro 'dev_fmt' 154 | dev_printk_index_wrap(_dev_err, KERN_ERR, dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~ drivers/mtd/spi-nor/controllers/wmt-sflash.c:186:17: note: in expansion of macro 'dev_err' 186 | dev_err(host->dev, | ^~~~~~~ drivers/mtd/spi-nor/controllers/wmt-sflash.c:187:32: note: format string is defined here 187 | "Cannot write %d bytes to registers\n", len); | ~^ | | | int | %ld drivers/mtd/spi-nor/controllers/wmt-sflash.c: In function 'wmt_sflash_register': drivers/mtd/spi-nor/controllers/wmt-sflash.c:365:47: error: passing argument 3 of 'of_property_read_u32' from incompatible pointer type [-Wincompatible-pointer-types] 365 | ret = of_property_read_u32(np, "reg", &priv->cs); | ^~~~~~~~~ | | | size_t * {aka long unsigned int *} In file included from include/linux/mtd/mtd.h:14: include/linux/of.h:1419:45: note: expected 'u32 *' {aka 'unsigned int *'} but argument is of type 'size_t *' {aka 'long unsigned int *'} 1419 | u32 *out_value) | ~~~~~^~~~~~~~~ drivers/mtd/spi-nor/controllers/wmt-sflash.c:373:52: warning: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 373 | "Chip select %d is out of bounds\n", | ~^ | | | int | %ld 374 | priv->cs); | ~~~~~~~~ | | | size_t {aka long unsigned int} drivers/mtd/spi-nor/controllers/wmt-sflash.c:394:46: warning: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 394 | "Failed to map chip %d at address 0x%x size 0x%llx\n", | ~^ | | | int | %ld 395 | priv->cs, priv->mmap_phys, mtd->size); | ~~~~~~~~ | | | size_t {aka long unsigned int} >> drivers/mtd/spi-nor/controllers/wmt-sflash.c:394:62: warning: format '%x' expects argument of type 'unsigned int', but argument 5 has type 'resource_size_t' {aka 'long long unsigned int'} [-Wformat=] 394 | "Failed to map chip %d at address 0x%x size 0x%llx\n", | ~^ | | | unsigned int | %llx 395 | priv->cs, priv->mmap_phys, mtd->size); | ~~~~~~~~~~~~~~~ | | | resource_size_t {aka long long unsigned int} vim +154 drivers/mtd/spi-nor/controllers/wmt-sflash.c > 14 #include <linux/mtd/mtd.h> 15 #include <linux/mtd/spi-nor.h> 16 #include <linux/of.h> 17 #include <linux/platform_device.h> 18 #include <linux/slab.h> 19 20 #define SF_CHIP_SEL_0_CFG 0x000 /* chip select 0 config */ 21 #define SF_CHIP_SEL_1_CFG 0x008 /* chip select 0 config */ 22 #define SF_CHIP_SEL_CFG(x) (8 * (x)) 23 #define SF_CHIP_SEL_ADDR GENMASK(31, 16) /* 64kb aligned address */ 24 #define SF_CHIP_SEL_SIZE GENMASK(11, 8) /* log2(size/32kb) */ 25 26 #define SF_SPI_INTF_CFG 0x040 /* SPI interface config */ 27 #define SF_ADDR_WIDTH_32 BIT(0) /* 0: 24 bit, 1: 32 bit addr */ 28 #define SF_USR_RD_CMD_MOD BIT(4) /* 0: normal, 1: user cmd read */ 29 #define SF_USR_WR_CMD_MOD BIT(5) /* 0: normal, 1: user cmd write */ 30 #define SF_PROG_CMD_MOD BIT(6) /* 0: normal, 1: prog cmd */ 31 #define SF_CS_DELAY GENMASK(18, 16) /* chip select delay */ 32 #define SF_RES_DELAY GENMASK(27, 24) /* reset delay */ 33 #define SF_PDWN_DELAY GENMASK(31, 28) /* power down delay */ 34 35 #define SF_SPI_RD_WR_CTR 0x050 /* read/write control */ 36 #define SF_RD_FAST BIT(0) /* 0: normal read, 1: fast read */ 37 #define SF_RD_ID BIT(4) /* 0: read status, 1: read ID */ 38 39 #define SF_SPI_WR_EN_CTR 0x060 /* write enable control */ 40 #define SF_CS0_WR_EN BIT(0) 41 #define SF_CS1_WR_EN BIT(1) 42 #define SF_CS_WR_EN(x) BIT(x) 43 44 #define SF_SPI_ER_CTR 0x070 /* erase control */ 45 #define SF_CHIP_ERASE BIT(0) /* full chip erase */ 46 #define SF_SEC_ERASE BIT(15) /* sector erase */ 47 48 #define SF_SPI_ER_START_ADDR 0x074 /* erase start address */ 49 #define SF_CHIP_ER_CS0 BIT(0) /* erase chip 0 */ 50 #define SF_CHIP_ER_CS1 BIT(1) /* erase chip 1 */ 51 #define SF_CHIP_ER_CS(x) BIT(x) 52 #define SF_ER_START_ADDR GENMASK(31, 16) 53 54 #define SF_SPI_ERROR_STATUS 0x080 55 #define SF_MASLOCK_ERR BIT(0) /* master lock */ 56 #define SF_PCMD_ACC_ERR BIT(1) /* programmable cmd access */ 57 #define SF_PCMD_OP_ERR BIT(2) /* programmable cmd opcode */ 58 #define SF_PWR_DWN_ACC_ERR BIT(3) /* power down access */ 59 #define SF_MEM_REGION_ERR BIT(4) /* memory region */ 60 #define SF_WR_PROT_ERR BIT(5) /* write protection */ 61 #define SF_SPI_ERROR_CLEARALL (SF_MASLOCK_ERR | \ 62 SF_PCMD_ACC_ERR | \ 63 SF_PCMD_OP_ERR | \ 64 SF_PWR_DWN_ACC_ERR | \ 65 SF_MEM_REGION_ERR | \ 66 SF_WR_PROT_ERR) 67 68 #define SF_SPI_MEM_0_SR_ACC 0x100 /* status read from chip 0 */ 69 #define SF_SPI_MEM_1_SR_ACC 0x110 /* status read from chip 1 */ 70 #define SF_SPI_MEM_SR_ACC(x) (0x100 + 0x10 * (x)) 71 72 #define SF_SPI_PDWN_CTR_0 0x180 /* power down chip 0 */ 73 #define SF_SPI_PDWN_CTR_1 0x190 /* power down chip 1 */ 74 #define SF_SPI_PDWN_CTR_(x) (0x180 + 0x10 * (x)) 75 #define SF_PWR_DOWN BIT(0) 76 77 #define SF_SPI_PROG_CMD_CTR 0x200 /* programmable cmd control */ 78 #define SF_PROG_CMD_EN BIT(0) /* enable programmable cmd */ 79 #define SF_PROG_CMD_CS GENMASK(1, 1) /* chip select for cmd */ 80 #define SF_RX_DATA_SIZE GENMASK(22, 16) /* receive data size */ 81 #define SF_TX_DATA_SIZE GENMASK(30, 24) /* transmit data size */ 82 83 #define SF_SPI_USER_CMD_VAL 0x210 84 #define SF_USR_RD_CMD GENMASK(7, 0) /* user read command */ 85 #define SF_USR_WR_CMD GENMASK(23, 16) /* user write command */ 86 87 #define SF_SPI_PROG_CMD_WBF 0x300 /* 64 bytes pcmd write buffer */ 88 #define SF_SPI_PROG_CMD_RBF 0x380 /* 64 bytes pcmd read buffer */ 89 90 #define SF_WAIT_TIMEOUT 1000000 91 92 struct wmt_sflash_priv { 93 size_t cs; 94 struct wmt_sflash_host *host; 95 void __iomem *mmap_base; 96 resource_size_t mmap_phys; 97 }; 98 99 #define SF_MAX_CHIP_NUM 2 100 struct wmt_sflash_host { 101 struct device *dev; 102 struct clk *clk; 103 104 void __iomem *regbase; 105 struct resource *mmap_res[SF_MAX_CHIP_NUM]; 106 107 struct spi_nor *nor[SF_MAX_CHIP_NUM]; 108 size_t num_chips; 109 }; 110 111 static int wmt_sflash_prep(struct spi_nor *nor) 112 { 113 struct wmt_sflash_priv *priv = nor->priv; 114 struct wmt_sflash_host *host = priv->host; 115 116 return clk_prepare_enable(host->clk); 117 } 118 119 static void wmt_sflash_unprep(struct spi_nor *nor) 120 { 121 struct wmt_sflash_priv *priv = nor->priv; 122 struct wmt_sflash_host *host = priv->host; 123 124 clk_disable_unprepare(host->clk); 125 } 126 127 static void wmt_sflash_pcmd_mode(struct wmt_sflash_host *host, bool enable) 128 { 129 u32 reg = readl(host->regbase + SF_SPI_INTF_CFG); 130 131 reg &= ~SF_PROG_CMD_MOD; 132 reg |= FIELD_PREP(SF_PROG_CMD_MOD, enable); 133 writel(reg, host->regbase + SF_SPI_INTF_CFG); 134 } 135 136 static inline int wmt_sflash_wait_pcmd(struct wmt_sflash_host *host) 137 { 138 u32 reg; 139 140 return readl_poll_timeout(host->regbase + SF_SPI_PROG_CMD_CTR, reg, 141 !(reg & SF_PROG_CMD_EN), 1, SF_WAIT_TIMEOUT); 142 } 143 144 static int wmt_sflash_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, 145 size_t len) 146 { 147 struct wmt_sflash_priv *priv = nor->priv; 148 struct wmt_sflash_host *host = priv->host; 149 int ret; 150 u32 reg; 151 152 if (len > 64) { 153 dev_err(host->dev, > 154 "Cannot read %d bytes from registers\n", len); 155 return -EINVAL; 156 } 157 158 wmt_sflash_pcmd_mode(host, true); 159 writeb(opcode, host->regbase + SF_SPI_PROG_CMD_WBF); 160 161 reg = SF_PROG_CMD_EN | 162 FIELD_PREP(SF_PROG_CMD_CS, priv->cs) | 163 FIELD_PREP(SF_TX_DATA_SIZE, 1) | 164 FIELD_PREP(SF_RX_DATA_SIZE, len); 165 writel(reg, host->regbase + SF_SPI_PROG_CMD_CTR); 166 167 ret = wmt_sflash_wait_pcmd(host); 168 169 if (len) 170 memcpy_fromio(buf, host->regbase + SF_SPI_PROG_CMD_RBF, len); 171 172 wmt_sflash_pcmd_mode(host, false); 173 174 return ret; 175 } 176
Hi Alexey, On Sat, May 10 2025, Alexey Charkov wrote: > The controller is tailored to SPI NOR flash memory and abstracts away all > SPI communications behind a small set of MMIO registers and a physical > memory mapping of the actual chip contents. > > It doesn't expose chip probing functions beyond reading the ID though, so > use lower level chip opcodes via the "programmable command mode" of the > controller and the kernel's SPI NOR framework to avoid hard-coding chip > parameters for each ID the way the vendor kernel does it. > > Currently tested on a WonderMedia WM8950 SoC with a Macronix MX25L4005A > flash chip (APC Rock board), but should work on all VIA/WonderMedia SoCs > > Signed-off-by: Alexey Charkov <alchark@gmail.com> > --- > MAINTAINERS | 1 + > drivers/mtd/spi-nor/controllers/Kconfig | 14 + > drivers/mtd/spi-nor/controllers/Makefile | 1 + > drivers/mtd/spi-nor/controllers/wmt-sflash.c | 525 +++++++++++++++++++++++++++ Drivers in drivers/mtd/spi-nor/controllers/ are deprecated, and we want to eventually get rid of the API. The expected way is for drivers to use SPI MEM (drivers/spi/spi-mem.c). SPI MEM drivers are usually more general and not tailored specifically to SPI NOR flashes, so it might be a bit tricky to write drivers for specialized hardware with it. But I think the drivers/spi/spi-intel.c driver is written for similar kind of hardware so it should be possible. > [...]
Hi Pratyush, On Mon, May 12, 2025 at 1:20 PM Pratyush Yadav <pratyush@kernel.org> wrote: > > Hi Alexey, > > On Sat, May 10 2025, Alexey Charkov wrote: > > > The controller is tailored to SPI NOR flash memory and abstracts away all > > SPI communications behind a small set of MMIO registers and a physical > > memory mapping of the actual chip contents. > > > > It doesn't expose chip probing functions beyond reading the ID though, so > > use lower level chip opcodes via the "programmable command mode" of the > > controller and the kernel's SPI NOR framework to avoid hard-coding chip > > parameters for each ID the way the vendor kernel does it. > > > > Currently tested on a WonderMedia WM8950 SoC with a Macronix MX25L4005A > > flash chip (APC Rock board), but should work on all VIA/WonderMedia SoCs > > > > Signed-off-by: Alexey Charkov <alchark@gmail.com> > > --- > > MAINTAINERS | 1 + > > drivers/mtd/spi-nor/controllers/Kconfig | 14 + > > drivers/mtd/spi-nor/controllers/Makefile | 1 + > > drivers/mtd/spi-nor/controllers/wmt-sflash.c | 525 +++++++++++++++++++++++++++ > > Drivers in drivers/mtd/spi-nor/controllers/ are deprecated, and we want > to eventually get rid of the API. The expected way is for drivers to use > SPI MEM (drivers/spi/spi-mem.c). SPI MEM drivers are usually more > general and not tailored specifically to SPI NOR flashes, so it might be > a bit tricky to write drivers for specialized hardware with it. But I > think the drivers/spi/spi-intel.c driver is written for similar kind of > hardware so it should be possible. Oops. I've had a look at spi-mem, and it seems like it's not a particularly fitting abstraction for this controller.
diff --git a/MAINTAINERS b/MAINTAINERS index f09c457bbfc5ef71a3f8379c111bac52b767cbbc..ff849c03a3b79a0487d3ab7e704ed11ffdb58f41 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3481,6 +3481,7 @@ F: arch/arm/mach-vt8500/ F: drivers/clocksource/timer-vt8500.c F: drivers/i2c/busses/i2c-viai2c-wmt.c F: drivers/mmc/host/wmt-sdmmc.c +F: drivers/mtd/spi-nor/controllers/wmt-sflash.c F: drivers/pwm/pwm-vt8500.c F: drivers/rtc/rtc-vt8500.c F: drivers/soc/vt8500/ diff --git a/drivers/mtd/spi-nor/controllers/Kconfig b/drivers/mtd/spi-nor/controllers/Kconfig index ca45dcd3ffe81f87dbf9ddc2a1535244ea92be20..aa8400b9aeaaec3b8b3f8996d501f5ac77a488ea 100644 --- a/drivers/mtd/spi-nor/controllers/Kconfig +++ b/drivers/mtd/spi-nor/controllers/Kconfig @@ -16,3 +16,17 @@ config SPI_NXP_SPIFI SPIFI is a specialized controller for connecting serial SPI Flash. Enable this option if you have a device with a SPIFI controller and want to access the Flash as a mtd device. + +config SPI_WMT_SFLASH + tristate "VIA/WonderMedia serial flash controller" + depends on ARCH_VT8500 || COMPILE_TEST + depends on OF + depends on HAS_IOMEM + help + Enable support for the VIA/WonderMedia serial flash controller. + + This is the specialized controller driving SPI NOR flash chips + inside VIA/WonderMedia SoCs. Most if not all VIA/WonderMedia + based devices use SPI NOR flash as their boot storage, so select + this if you need to access the primary or secondary bootloader + and their environment partitions. diff --git a/drivers/mtd/spi-nor/controllers/Makefile b/drivers/mtd/spi-nor/controllers/Makefile index 0b8e1d5309138619bbfdf3e27639b6be2935e65e..9c0365354a37986755f3c067ba2f1f5708fd20ad 100644 --- a/drivers/mtd/spi-nor/controllers/Makefile +++ b/drivers/mtd/spi-nor/controllers/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o +obj-$(CONFIG_SPI_WMT_SFLASH) += wmt-sflash.o diff --git a/drivers/mtd/spi-nor/controllers/wmt-sflash.c b/drivers/mtd/spi-nor/controllers/wmt-sflash.c new file mode 100644 index 0000000000000000000000000000000000000000..d63c3402345a9dac7e8a0591bb032481ad3b02f7 --- /dev/null +++ b/drivers/mtd/spi-nor/controllers/wmt-sflash.c @@ -0,0 +1,525 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * VIA/WonderMedia SPI NOR flash controller driver + * + * Copyright (c) 2025 Alexey Charkov <alchark@gmail.com> + */ +#include <linux/clk.h> +#include <linux/bitops.h> +#include <linux/errno.h> +#include <linux/iopoll.h> +#include <linux/log2.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/spi-nor.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define SF_CHIP_SEL_0_CFG 0x000 /* chip select 0 config */ +#define SF_CHIP_SEL_1_CFG 0x008 /* chip select 0 config */ +#define SF_CHIP_SEL_CFG(x) (8 * (x)) +#define SF_CHIP_SEL_ADDR GENMASK(31, 16) /* 64kb aligned address */ +#define SF_CHIP_SEL_SIZE GENMASK(11, 8) /* log2(size/32kb) */ + +#define SF_SPI_INTF_CFG 0x040 /* SPI interface config */ +#define SF_ADDR_WIDTH_32 BIT(0) /* 0: 24 bit, 1: 32 bit addr */ +#define SF_USR_RD_CMD_MOD BIT(4) /* 0: normal, 1: user cmd read */ +#define SF_USR_WR_CMD_MOD BIT(5) /* 0: normal, 1: user cmd write */ +#define SF_PROG_CMD_MOD BIT(6) /* 0: normal, 1: prog cmd */ +#define SF_CS_DELAY GENMASK(18, 16) /* chip select delay */ +#define SF_RES_DELAY GENMASK(27, 24) /* reset delay */ +#define SF_PDWN_DELAY GENMASK(31, 28) /* power down delay */ + +#define SF_SPI_RD_WR_CTR 0x050 /* read/write control */ +#define SF_RD_FAST BIT(0) /* 0: normal read, 1: fast read */ +#define SF_RD_ID BIT(4) /* 0: read status, 1: read ID */ + +#define SF_SPI_WR_EN_CTR 0x060 /* write enable control */ +#define SF_CS0_WR_EN BIT(0) +#define SF_CS1_WR_EN BIT(1) +#define SF_CS_WR_EN(x) BIT(x) + +#define SF_SPI_ER_CTR 0x070 /* erase control */ +#define SF_CHIP_ERASE BIT(0) /* full chip erase */ +#define SF_SEC_ERASE BIT(15) /* sector erase */ + +#define SF_SPI_ER_START_ADDR 0x074 /* erase start address */ +#define SF_CHIP_ER_CS0 BIT(0) /* erase chip 0 */ +#define SF_CHIP_ER_CS1 BIT(1) /* erase chip 1 */ +#define SF_CHIP_ER_CS(x) BIT(x) +#define SF_ER_START_ADDR GENMASK(31, 16) + +#define SF_SPI_ERROR_STATUS 0x080 +#define SF_MASLOCK_ERR BIT(0) /* master lock */ +#define SF_PCMD_ACC_ERR BIT(1) /* programmable cmd access */ +#define SF_PCMD_OP_ERR BIT(2) /* programmable cmd opcode */ +#define SF_PWR_DWN_ACC_ERR BIT(3) /* power down access */ +#define SF_MEM_REGION_ERR BIT(4) /* memory region */ +#define SF_WR_PROT_ERR BIT(5) /* write protection */ +#define SF_SPI_ERROR_CLEARALL (SF_MASLOCK_ERR | \ + SF_PCMD_ACC_ERR | \ + SF_PCMD_OP_ERR | \ + SF_PWR_DWN_ACC_ERR | \ + SF_MEM_REGION_ERR | \ + SF_WR_PROT_ERR) + +#define SF_SPI_MEM_0_SR_ACC 0x100 /* status read from chip 0 */ +#define SF_SPI_MEM_1_SR_ACC 0x110 /* status read from chip 1 */ +#define SF_SPI_MEM_SR_ACC(x) (0x100 + 0x10 * (x)) + +#define SF_SPI_PDWN_CTR_0 0x180 /* power down chip 0 */ +#define SF_SPI_PDWN_CTR_1 0x190 /* power down chip 1 */ +#define SF_SPI_PDWN_CTR_(x) (0x180 + 0x10 * (x)) +#define SF_PWR_DOWN BIT(0) + +#define SF_SPI_PROG_CMD_CTR 0x200 /* programmable cmd control */ +#define SF_PROG_CMD_EN BIT(0) /* enable programmable cmd */ +#define SF_PROG_CMD_CS GENMASK(1, 1) /* chip select for cmd */ +#define SF_RX_DATA_SIZE GENMASK(22, 16) /* receive data size */ +#define SF_TX_DATA_SIZE GENMASK(30, 24) /* transmit data size */ + +#define SF_SPI_USER_CMD_VAL 0x210 +#define SF_USR_RD_CMD GENMASK(7, 0) /* user read command */ +#define SF_USR_WR_CMD GENMASK(23, 16) /* user write command */ + +#define SF_SPI_PROG_CMD_WBF 0x300 /* 64 bytes pcmd write buffer */ +#define SF_SPI_PROG_CMD_RBF 0x380 /* 64 bytes pcmd read buffer */ + +#define SF_WAIT_TIMEOUT 1000000 + +struct wmt_sflash_priv { + size_t cs; + struct wmt_sflash_host *host; + void __iomem *mmap_base; + resource_size_t mmap_phys; +}; + +#define SF_MAX_CHIP_NUM 2 +struct wmt_sflash_host { + struct device *dev; + struct clk *clk; + + void __iomem *regbase; + struct resource *mmap_res[SF_MAX_CHIP_NUM]; + + struct spi_nor *nor[SF_MAX_CHIP_NUM]; + size_t num_chips; +}; + +static int wmt_sflash_prep(struct spi_nor *nor) +{ + struct wmt_sflash_priv *priv = nor->priv; + struct wmt_sflash_host *host = priv->host; + + return clk_prepare_enable(host->clk); +} + +static void wmt_sflash_unprep(struct spi_nor *nor) +{ + struct wmt_sflash_priv *priv = nor->priv; + struct wmt_sflash_host *host = priv->host; + + clk_disable_unprepare(host->clk); +} + +static void wmt_sflash_pcmd_mode(struct wmt_sflash_host *host, bool enable) +{ + u32 reg = readl(host->regbase + SF_SPI_INTF_CFG); + + reg &= ~SF_PROG_CMD_MOD; + reg |= FIELD_PREP(SF_PROG_CMD_MOD, enable); + writel(reg, host->regbase + SF_SPI_INTF_CFG); +} + +static inline int wmt_sflash_wait_pcmd(struct wmt_sflash_host *host) +{ + u32 reg; + + return readl_poll_timeout(host->regbase + SF_SPI_PROG_CMD_CTR, reg, + !(reg & SF_PROG_CMD_EN), 1, SF_WAIT_TIMEOUT); +} + +static int wmt_sflash_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, + size_t len) +{ + struct wmt_sflash_priv *priv = nor->priv; + struct wmt_sflash_host *host = priv->host; + int ret; + u32 reg; + + if (len > 64) { + dev_err(host->dev, + "Cannot read %d bytes from registers\n", len); + return -EINVAL; + } + + wmt_sflash_pcmd_mode(host, true); + writeb(opcode, host->regbase + SF_SPI_PROG_CMD_WBF); + + reg = SF_PROG_CMD_EN | + FIELD_PREP(SF_PROG_CMD_CS, priv->cs) | + FIELD_PREP(SF_TX_DATA_SIZE, 1) | + FIELD_PREP(SF_RX_DATA_SIZE, len); + writel(reg, host->regbase + SF_SPI_PROG_CMD_CTR); + + ret = wmt_sflash_wait_pcmd(host); + + if (len) + memcpy_fromio(buf, host->regbase + SF_SPI_PROG_CMD_RBF, len); + + wmt_sflash_pcmd_mode(host, false); + + return ret; +} + +static int wmt_sflash_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf, + size_t len) +{ + struct wmt_sflash_priv *priv = nor->priv; + struct wmt_sflash_host *host = priv->host; + int ret; + u32 reg; + + if (len > 63) { + dev_err(host->dev, + "Cannot write %d bytes to registers\n", len); + return -EINVAL; + } + + wmt_sflash_pcmd_mode(host, true); + writeb(opcode, host->regbase + SF_SPI_PROG_CMD_WBF); + + if (len) + memcpy_toio(host->regbase + SF_SPI_PROG_CMD_WBF + 1, buf, len); + + reg = SF_PROG_CMD_EN | + FIELD_PREP(SF_PROG_CMD_CS, priv->cs) | + FIELD_PREP(SF_TX_DATA_SIZE, len + 1); + writel(reg, host->regbase + SF_SPI_PROG_CMD_CTR); + + ret = wmt_sflash_wait_pcmd(host); + wmt_sflash_pcmd_mode(host, false); + + return ret; +} + +static int wmt_sflash_wait_spi(struct wmt_sflash_priv *priv) +{ + struct wmt_sflash_host *host = priv->host; + int timeout = SF_WAIT_TIMEOUT; + u32 error; + + while (timeout--) { + if (!(readl(host->regbase + + SF_SPI_MEM_SR_ACC(priv->cs)) & 1)) + return 0; + + error = readl(host->regbase + SF_SPI_ERROR_STATUS); + if (error & SF_MASLOCK_ERR) { + dev_err(host->dev, + "Master lock error\n"); + goto err; + } + if (error & SF_PCMD_ACC_ERR) { + dev_err(host->dev, + "Programmable command access error\n"); + goto err; + } + if (error & SF_PCMD_OP_ERR) { + dev_err(host->dev, + "Programmable command opcode error\n"); + goto err; + } + if (error & SF_PWR_DWN_ACC_ERR) { + dev_err(host->dev, + "Power down access error\n"); + goto err; + } + if (error & SF_MEM_REGION_ERR) { + dev_err(host->dev, + "Memory region error\n"); + goto err; + } + if (error & SF_WR_PROT_ERR) { + dev_err(host->dev, + "Write protection error\n"); + goto err; + } + } + return 0; + +err: + writel(SF_SPI_ERROR_CLEARALL, host->regbase + SF_SPI_ERROR_STATUS); + return -EBUSY; +} + +static ssize_t wmt_sflash_read(struct spi_nor *nor, loff_t from, size_t len, + u_char *read_buf) +{ + struct wmt_sflash_priv *priv = nor->priv; + struct wmt_sflash_host *host = priv->host; + u32 reg = nor->read_opcode == SPINOR_OP_READ_FAST ? SF_RD_FAST : 0; + + writel(reg, host->regbase + SF_SPI_RD_WR_CTR); + + if (wmt_sflash_wait_spi(priv)) + return 0; + + memcpy_fromio(read_buf, priv->mmap_base + from, len); + return len; +} + +static ssize_t wmt_sflash_write(struct spi_nor *nor, loff_t to, size_t len, + const u_char *write_buf) +{ + struct wmt_sflash_priv *priv = nor->priv; + struct wmt_sflash_host *host = priv->host; + size_t burst, offset = 0; + + writel(SF_CS_WR_EN(priv->cs), + host->regbase + SF_SPI_WR_EN_CTR); + + while (offset < len) { + /* select 8 / 4 / 2 / 1 byte write length */ + burst = 1 << min(3, ilog2(len - offset)); + memcpy_toio(priv->mmap_base + to + offset, + write_buf + offset, burst); + + if (wmt_sflash_wait_spi(priv)) + return offset; + + offset += burst; + } + + writel(0, host->regbase + SF_SPI_WR_EN_CTR); + + return offset; +} + +static int wmt_sflash_erase(struct spi_nor *nor, loff_t offs) +{ + struct wmt_sflash_priv *priv = nor->priv; + struct wmt_sflash_host *host = priv->host; + int ret = 0; + u32 reg; + + if (offs & (SZ_64K - 1)) { + dev_err(host->dev, + "Erase offset 0x%llx not on 64k boundary\n", offs); + return -EINVAL; + } + + writel(SF_CS_WR_EN(priv->cs), + host->regbase + SF_SPI_WR_EN_CTR); + + reg = SF_CHIP_ER_CS(priv->cs) | + FIELD_PREP(SF_ER_START_ADDR, (priv->mmap_phys + offs) >> 16); + writel(reg, host->regbase + SF_SPI_ER_START_ADDR); + + writel(SF_SEC_ERASE, host->regbase + SF_SPI_ER_CTR); + + ret = wmt_sflash_wait_spi(priv); + writel(0, host->regbase + SF_SPI_WR_EN_CTR); + + return ret; +} + +static const struct spi_nor_controller_ops wmt_sflash_controller_ops = { + .prepare = wmt_sflash_prep, + .unprepare = wmt_sflash_unprep, + .read_reg = wmt_sflash_read_reg, + .write_reg = wmt_sflash_write_reg, + .read = wmt_sflash_read, + .write = wmt_sflash_write, + .erase = wmt_sflash_erase, +}; + +static int wmt_sflash_register(struct device_node *np, + struct wmt_sflash_host *host) +{ + const struct spi_nor_hwcaps hwcaps = { + .mask = SNOR_HWCAPS_READ | + SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_PP, + }; + struct device *dev = host->dev; + struct wmt_sflash_priv *priv; + struct mtd_info *mtd; + struct spi_nor *nor; + int ret; + u32 reg; + + nor = devm_kzalloc(dev, sizeof(*nor), GFP_KERNEL); + if (!nor) + return -ENOMEM; + + nor->dev = dev; + spi_nor_set_flash_node(nor, np); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = of_property_read_u32(np, "reg", &priv->cs); + if (ret) + return dev_err_probe(dev, ret, + "There's no reg property for %pOF\n", + np); + + if (priv->cs >= SF_MAX_CHIP_NUM) + return dev_err_probe(dev, -ENXIO, + "Chip select %d is out of bounds\n", + priv->cs); + + priv->host = host; + nor->priv = priv; + nor->controller_ops = &wmt_sflash_controller_ops; + + ret = spi_nor_scan(nor, NULL, &hwcaps); + if (ret) + return dev_err_probe(dev, ret, + "Failed to scan SPI NOR chip\n"); + + mtd = &nor->mtd; + mtd->name = np->name; + + priv->mmap_phys = host->mmap_res[priv->cs]->end - mtd->size + 1; + priv->mmap_phys &= -SZ_64K; + + priv->mmap_base = devm_ioremap(dev, priv->mmap_phys, mtd->size); + if (IS_ERR(priv->mmap_base)) + return dev_err_probe(dev, PTR_ERR(priv->mmap_base), + "Failed to map chip %d at address 0x%x size 0x%llx\n", + priv->cs, priv->mmap_phys, mtd->size); + + reg = FIELD_PREP(SF_CHIP_SEL_ADDR, priv->mmap_phys >> 16) | + FIELD_PREP(SF_CHIP_SEL_SIZE, order_base_2(mtd->size) - 15); + writel(reg, host->regbase + SF_CHIP_SEL_CFG(priv->cs)); + + reg = FIELD_PREP(SF_CS_DELAY, 3); + writel(reg, host->regbase + SF_SPI_INTF_CFG); + + /* the controller only handles 64k aligned addresses */ + mtd->erasesize = max(mtd->erasesize, SZ_64K); + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register MTD device\n"); + + host->nor[host->num_chips] = nor; + host->num_chips++; + return 0; +} + +static void wmt_sflash_unregister_all(struct wmt_sflash_host *host) +{ + int i; + + for (i = 0; i < host->num_chips; i++) + mtd_device_unregister(&host->nor[i]->mtd); +} + +static int wmt_sflash_register_all(struct wmt_sflash_host *host) +{ + struct device *dev = host->dev; + struct device_node *np; + int ret; + + for_each_available_child_of_node(dev->of_node, np) { + ret = wmt_sflash_register(np, host); + if (ret) { + of_node_put(np); + goto fail; + } + + if (host->num_chips == SF_MAX_CHIP_NUM) { + dev_warn(dev, "Flash count exceeds the maximum chipselect number\n"); + of_node_put(np); + break; + } + } + return 0; + +fail: + wmt_sflash_unregister_all(host); + return ret; +} + +static int wmt_sflash_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct wmt_sflash_host *host; + char mmap_str[32]; + int ret, i; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return dev_err_probe(dev, -ENOMEM, + "Failed to allocate controller private data\n"); + + platform_set_drvdata(pdev, host); + host->dev = dev; + + host->regbase = devm_platform_ioremap_resource_byname(pdev, "io"); + if (IS_ERR(host->regbase)) + return dev_err_probe(dev, PTR_ERR(host->regbase), + "Failed to remap controller MMIO registers\n"); + + for (i = 0; i < SF_MAX_CHIP_NUM; i++) { + snprintf(mmap_str, sizeof(mmap_str), "chip%d-mmap", i); + + host->mmap_res[i] = platform_get_resource_byname(pdev, + IORESOURCE_MEM, mmap_str); + if (!host->mmap_res[i]) + return dev_err_probe(dev, -ENXIO, + "Memory map region not found for chip %d\n", + i); + } + + host->clk = devm_clk_get(dev, NULL); + if (IS_ERR(host->clk)) + return dev_err_probe(dev, PTR_ERR(host->clk), + "Failed to get clock\n"); + + ret = clk_prepare_enable(host->clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable clock\n"); + + ret = wmt_sflash_register_all(host); + + clk_disable_unprepare(host->clk); + return ret; +} + +static void wmt_sflash_remove(struct platform_device *pdev) +{ + struct wmt_sflash_host *host = platform_get_drvdata(pdev); + + wmt_sflash_unregister_all(host); +} + +static const struct of_device_id wmt_sflash_dt_ids[] = { + { .compatible = "via,vt8500-sflash"}, + { .compatible = "wm,wm8505-sflash"}, + { .compatible = "wm,wm8650-sflash"}, + { .compatible = "wm,wm8750-sflash"}, + { .compatible = "wm,wm8850-sflash"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, wmt_sflash_dt_ids); + +static struct platform_driver wmt_sflash_driver = { + .driver = { + .name = "wmt-sflash", + .of_match_table = wmt_sflash_dt_ids, + }, + .probe = wmt_sflash_probe, + .remove = wmt_sflash_remove, +}; +module_platform_driver(wmt_sflash_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VIA/WonderMedia SPI NOR flash controller driver");
The controller is tailored to SPI NOR flash memory and abstracts away all SPI communications behind a small set of MMIO registers and a physical memory mapping of the actual chip contents. It doesn't expose chip probing functions beyond reading the ID though, so use lower level chip opcodes via the "programmable command mode" of the controller and the kernel's SPI NOR framework to avoid hard-coding chip parameters for each ID the way the vendor kernel does it. Currently tested on a WonderMedia WM8950 SoC with a Macronix MX25L4005A flash chip (APC Rock board), but should work on all VIA/WonderMedia SoCs Signed-off-by: Alexey Charkov <alchark@gmail.com> --- MAINTAINERS | 1 + drivers/mtd/spi-nor/controllers/Kconfig | 14 + drivers/mtd/spi-nor/controllers/Makefile | 1 + drivers/mtd/spi-nor/controllers/wmt-sflash.c | 525 +++++++++++++++++++++++++++ 4 files changed, 541 insertions(+)