diff mbox series

[2/3] mtd: spi-nor: Add a driver for the VIA/WonderMedia serial flash controller

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

Commit Message

Alexey Charkov May 10, 2025, 7:42 p.m. UTC
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(+)

Comments

kernel test robot May 11, 2025, 11:13 a.m. UTC | #1
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
diff mbox series

Patch

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