diff for duplicates of <20260930065945.88008-3-eleanor.lin@realtek.com> diff --git a/a/1.txt b/N1/1.txt index 7039447..8e342b0 100644 --- a/a/1.txt +++ b/N1/1.txt @@ -1,713 +1,49 @@ -From: Jyan Chou <jyanchou@realtek.com> +Commit aca8cbecdd2e ("spi: spi-mem: Add Realtek SPI NOR flash +controller driver") from Sep 30, 2026 (linux-next), leads to the +following Smatch static checker warning: -Add a spi-mem driver for the SPI NOR Flash Controller (SFC) found on -Realtek DHC SoC. SFC supports Dual I/O. + drivers/spi/spi-rtk-nor.c:347 rtk_spi_supports_op() + warn: '(-95)' is not bool -Implement the exec_op API for regular control commands and the dirmap API -for hardware-accelerated read/write operations. +On Wed, 30 Sep 2026 14:59:45 +0800 Yu-Chun Lin <eleanor.lin@realtek.com> wrote: +> +static bool rtk_spi_supports_op(struct spi_mem *mem, +> + const struct spi_mem_op *op) +> +{ +> + if (op->cmd.buswidth != 1) +> + return false; +> + +> + if (op->cmd.dtr || op->addr.dtr || op->data.dtr) +> + 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; +> + } +> + +> + /* +> + * The data window is access-triggered: each CPU access to host->iobase +> + * generates one complete SPI transaction. Consecutive byte accesses +> + * therefore cannot be merged into a single transaction, so +> + * multi-byte stateless commands are not supported by this controller. +> + */ +> + if (!op->addr.nbytes && op->data.nbytes > 1) +> + return -EOPNOTSUPP; -The controller is described by three register ranges. The first one (ctrl) -holds the control registers. The second one (dma) holds the DMA engine -registers, used to transfer the payload between the flash and main memory. -The third range is the memory aperture (dirmap) that maps the flash -contents. +This is a bool function. s/-EOPNOTSUPP/false/? -Signed-off-by: Jyan Chou <jyanchou@realtek.com> -Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com> -Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com> ---- -Changes in v5: -- Fix the initialization and teardown sequences. -- Add the check for unsupported multi-byte stateless commands in -rtk_spi_supports_op(). -- Move the hardware workaround for 4-byte mode switching to rtk_spi_exec_op(). ---- - MAINTAINERS | 6 + - drivers/spi/Kconfig | 10 + - drivers/spi/Makefile | 1 + - drivers/spi/spi-rtk-nor.c | 624 ++++++++++++++++++++++++++++++++++++++ - 4 files changed, 641 insertions(+) - create mode 100644 drivers/spi/spi-rtk-nor.c +regards, +dan carpenter -diff --git a/MAINTAINERS b/MAINTAINERS -index 34d5c9a0d562..20cd9332f823 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -22754,6 +22754,12 @@ S: Maintained - F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml - F: drivers/spi/spi-realtek-rtl-snand.c - -+REALTEK SPI-NOR -+M: Jyan Chou <jyanchou@realtek.com> -+M: Yu-Chun Lin <eleanor.lin@realtek.com> -+S: Supported -+F: drivers/spi/spi-rtk-nor.c -+ - REALTEK SYSTIMER DRIVER - M: Hao-Wen Ting <haowen.ting@realtek.com> - S: Maintained -diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig -index 8782514bb89b..d45baa15a601 100644 ---- a/drivers/spi/Kconfig -+++ b/drivers/spi/Kconfig -@@ -957,6 +957,16 @@ config SPI_RSPI - help - SPI driver for Renesas RSPI and QSPI blocks. - -+config SPI_RTK_NOR -+ tristate "Realtek SPI-NOR flash controller" -+ depends on ARCH_REALTEK || COMPILE_TEST -+ depends on SPI_MEM -+ help -+ This enables support for the SPI NOR Flash Controller found on -+ Realtek DHC SoCs. The controller supports single and dual I/O -+ transfers and offloads the payload to an integrated DMA engine. -+ Say Y or M here if you are building a kernel for a Realtek DHC SoC -+ - config SPI_RZV2H_RSPI - tristate "Renesas RZ/V2H RSPI controller" - depends on ARCH_RENESAS || COMPILE_TEST -diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile -index 9fa12498ce8c..68f4d3283e0c 100644 ---- a/drivers/spi/Makefile -+++ b/drivers/spi/Makefile -@@ -131,6 +131,7 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o - obj-$(CONFIG_SPI_REALTEK_SNAND) += spi-realtek-rtl-snand.o - obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o - obj-$(CONFIG_SPI_RSPI) += spi-rspi.o -+obj-$(CONFIG_SPI_RTK_NOR) += spi-rtk-nor.o - obj-$(CONFIG_SPI_RZV2H_RSPI) += spi-rzv2h-rspi.o - obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o - obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o -diff --git a/drivers/spi/spi-rtk-nor.c b/drivers/spi/spi-rtk-nor.c -new file mode 100644 -index 000000000000..7f56346506b3 ---- /dev/null -+++ b/drivers/spi/spi-rtk-nor.c -@@ -0,0 +1,624 @@ -+// SPDX-License-Identifier: GPL-2.0 -+// -+// Realtek SPI Nor Flash Controller Driver (SFC) -+// -+// Copyright (c) 2024-2026 Realtek Semiconductor Corp. -+ -+#include <linux/bits.h> -+#include <linux/clk.h> -+#include <linux/delay.h> -+#include <linux/dma-mapping.h> -+#include <linux/errno.h> -+#include <linux/iopoll.h> -+#include <linux/minmax.h> -+#include <linux/module.h> -+#include <linux/platform_device.h> -+#include <linux/pm_runtime.h> -+#include <linux/reset.h> -+#include <linux/spi/spi.h> -+#include <linux/spi/spi-mem.h> -+#include <linux/types.h> -+ -+#define SFC_OPCODE 0x00 -+#define DUAL_MODE_EN BIT(9) -+ -+#define SFC_CTL 0x04 -+#define RW_DATAEN BIT(4) -+#define ADDR_EN BIT(3) -+ -+#define SFC_SCK 0x08 -+#define FDIV_MASK GENMASK(7, 0) -+ -+#define SFC_CE 0x0c -+#define DESLT_TIME 0x1a -+#define DESLT_TIME_SHIFT 16 -+#define PH_CNT 0x13 -+#define PH_CNT_SHIFT 8 -+#define PL_CNT 0x7 -+ -+#define WT_PROM_DONE BIT(8) -+ -+#define SFC_POS_LATCH 0x14 -+#define FALLING_EDGE_EN 0x0 -+ -+#define SFC_WAIT_WR 0x18 -+#define SFC_EN_WR 0x1c -+#define WT_PROM_EN BIT(8) -+ -+#define SFC_ADR_FOUR_BYTE_EN 0x28 -+ -+#define MD_FDMA_DDR_SADDR 0x0c -+#define MD_FDMA_FL_SADDR 0x10 -+ -+#define MD_FDMA_CTRL2 0x14 -+#define MAX_XFER_DMA_LEN (BIT(26) | BIT(27)) -+#define MAX_XFER_256 BIT(26) -+#define DMA_TO_FLASH BIT(25) -+ -+#define MD_FDMA_CTRL1 0x18 -+#define DMA_W_EN_START BIT(3) -+#define DMA_END BIT(2) -+#define DMA_W_EN BIT(1) -+#define DMA_START BIT(0) -+ -+#define MD_FDMA_DDR_SADDR1 0x20 -+ -+#define SFC_DMA_TIMEOUT 20000 -+#define SFC_DMA_MAX_LEN 0x100 -+ -+#define SFC_CTL_DMYCNT_MASK GENMASK(31, 24) -+#define SFC_CTL_DMYCNT_SHIFT 24 -+ -+#define DMA_HIGH_BITS_MASK 0x7 -+ -+#define RTK_SPI_OP_RDSR 0x05 -+#define RTK_SPI_OP_WREN 0x06 -+#define RTK_SPI_OP_EN4B 0xb7 -+#define RTK_SPI_OP_EX4B 0xe9 -+ -+#define SFC_AUTOSUSPEND_TIMEOUT 2000 -+ -+struct rtk_spi_host { -+ struct device *dev; -+ struct clk *clk; -+ struct reset_control *rstc; -+ void __iomem *regbase; -+ void __iomem *iobase; -+ void __iomem *mdbase; -+ void *buffer; -+ bool is_4byte; -+ dma_addr_t dma_buffer; -+ resource_size_t flash_phys_base; -+ size_t dirmap_size; -+}; -+ -+static u32 rtk_spi_calc_dummy_cycles(const struct spi_mem_op *op) -+{ -+ if (!op->dummy.nbytes) -+ return 0; -+ -+ return (op->dummy.nbytes * 8) / op->dummy.buswidth; -+} -+ -+static void rtk_spi_read_mode(struct rtk_spi_host *host, const struct spi_mem_op *op) -+{ -+ u32 opcode = op->cmd.opcode; -+ u32 dummy_cycles, val; -+ -+ if (op->data.buswidth == 2) -+ opcode |= DUAL_MODE_EN; -+ -+ writel(opcode, host->regbase + SFC_OPCODE); -+ -+ val = readl(host->regbase + SFC_CTL); -+ val |= RW_DATAEN | ADDR_EN; -+ -+ dummy_cycles = rtk_spi_calc_dummy_cycles(op); -+ -+ val &= ~SFC_CTL_DMYCNT_MASK; -+ val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; -+ writel(val, host->regbase + SFC_CTL); -+ -+ readl(host->iobase); -+} -+ -+static void rtk_spi_write_mode(struct rtk_spi_host *host, -+ const struct spi_mem_op *op) -+{ -+ u32 opcode = op->cmd.opcode; -+ u32 val; -+ -+ writel(opcode, host->regbase + SFC_OPCODE); -+ -+ val = readl(host->regbase + SFC_CTL); -+ val |= RW_DATAEN | ADDR_EN; -+ writel(val, host->regbase + SFC_CTL); -+} -+ -+static void rtk_spi_enable_auto_write(struct rtk_spi_host *host) -+{ -+ u32 val; -+ -+ val = WT_PROM_DONE | RTK_SPI_OP_RDSR; -+ writel(val, host->regbase + SFC_WAIT_WR); -+ -+ val = WT_PROM_EN | RTK_SPI_OP_WREN; -+ writel(val, host->regbase + SFC_EN_WR); -+} -+ -+static void rtk_spi_disable_auto_write(struct rtk_spi_host *host) -+{ -+ writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); -+ writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); -+} -+ -+static int rtk_spi_byte_transfer(struct rtk_spi_host *host, loff_t offset, -+ size_t len, unsigned char *buf, bool is_read) -+{ -+ if (offset + len > host->dirmap_size) { -+ dev_err(host->dev, "PIO transfer out of bounds\n"); -+ return -EINVAL; -+ } -+ -+ if (is_read) -+ memcpy_fromio(buf, host->iobase + offset, len); -+ else -+ memcpy_toio(host->iobase + offset, buf, len); -+ -+ return 0; -+} -+ -+static void rtk_spi_init(struct rtk_spi_host *host) -+{ -+ u32 val; -+ -+ val = readl(host->regbase + SFC_SCK); -+ val &= ~FDIV_MASK; -+ val |= (3 << 0) & FDIV_MASK; -+ writel(val, host->regbase + SFC_SCK); -+ -+ val = (DESLT_TIME << DESLT_TIME_SHIFT) | (PH_CNT << PH_CNT_SHIFT) | PL_CNT; -+ writel(val, host->regbase + SFC_CE); -+ -+ writel(FALLING_EDGE_EN, host->regbase + SFC_POS_LATCH); -+ writel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR); -+ writel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR); -+ -+ writel(host->is_4byte ? 0x1 : 0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); -+} -+ -+static int rtk_spi_command_read(struct rtk_spi_host *host, const struct spi_mem_op *op) -+{ -+ size_t len = op->data.nbytes; -+ loff_t offset = op->addr.val; -+ u8 opcode = op->cmd.opcode; -+ u32 dummy_cycles, val; -+ -+ writel(opcode, host->regbase + SFC_OPCODE); -+ -+ val = readl(host->regbase + SFC_CTL); -+ val &= ~(SFC_CTL_DMYCNT_MASK | ADDR_EN); -+ val |= RW_DATAEN; -+ -+ if (op->addr.nbytes > 0) -+ val |= ADDR_EN; -+ -+ dummy_cycles = rtk_spi_calc_dummy_cycles(op); -+ val |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK; -+ writel(val, host->regbase + SFC_CTL); -+ -+ return rtk_spi_byte_transfer(host, offset, len, op->data.buf.in, true); -+} -+ -+static int rtk_spi_do_write_and_cmds(struct rtk_spi_host *host, const struct spi_mem_op *op) -+{ -+ u8 opcode = op->cmd.opcode; -+ u32 ctl_val = 0; -+ -+ writel(opcode, host->regbase + SFC_OPCODE); -+ -+ if (op->data.nbytes > 0) -+ ctl_val |= RW_DATAEN; -+ -+ if (op->addr.nbytes > 0) -+ ctl_val |= ADDR_EN; -+ -+ writel(ctl_val, host->regbase + SFC_CTL); -+ -+ if (op->data.nbytes > 0) { -+ const u8 *buf = op->data.buf.out; -+ -+ if (op->addr.val + op->data.nbytes > host->dirmap_size) -+ return -EINVAL; -+ -+ writeb(*buf, host->iobase + op->addr.val); -+ } else { -+ if (op->addr.val >= host->dirmap_size) -+ return -EINVAL; -+ -+ readb(host->iobase + op->addr.val); -+ } -+ -+ return 0; -+} -+ -+static int rtk_spi_dma_transfer(struct rtk_spi_host *host, loff_t offset, -+ size_t len, bool is_read) -+{ -+ u64 dma_buffer, timeout_us = SFC_DMA_TIMEOUT * 100; -+ u32 flash_phys_addr, val; -+ int ret; -+ -+ if (offset + len > host->dirmap_size) { -+ dev_err(host->dev, "DMA transfer out of bounds\n"); -+ return -EINVAL; -+ } -+ -+ writel(DMA_W_EN_START | DMA_W_EN, host->mdbase + MD_FDMA_CTRL1); -+ -+ dma_buffer = host->dma_buffer; -+ -+ /* Setup MD DDR address and flash address */ -+ writel(lower_32_bits(dma_buffer), host->mdbase + MD_FDMA_DDR_SADDR); -+ writel(upper_32_bits(dma_buffer) & DMA_HIGH_BITS_MASK, -+ host->mdbase + MD_FDMA_DDR_SADDR1); -+ -+ /* MD_FDMA_FL_SADDR is a 32-bit hardware register */ -+ flash_phys_addr = lower_32_bits(host->flash_phys_base + offset); -+ writel(flash_phys_addr, host->mdbase + MD_FDMA_FL_SADDR); -+ -+ if (is_read) -+ val = MAX_XFER_DMA_LEN | len; -+ else -+ val = DMA_TO_FLASH | MAX_XFER_256 | len; -+ -+ writel(val, host->mdbase + MD_FDMA_CTRL2); -+ -+ writel(DMA_W_EN | DMA_START, host->mdbase + MD_FDMA_CTRL1); -+ udelay(1); -+ -+ ret = readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, -+ !(val & DMA_START), 100, timeout_us); -+ if (ret) { -+ dev_err(host->dev, "DMA transfer timed out\n"); -+ val = readl(host->mdbase + MD_FDMA_CTRL1); -+ val |= DMA_END | DMA_W_EN_START; -+ writel(val, host->mdbase + MD_FDMA_CTRL1); -+ -+ if (readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val, -+ !(val & DMA_END), 10, 1000)) { -+ dev_err(host->dev, "Failed to stop DMA gracefully\n"); -+ } -+ -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int rtk_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) -+{ -+ struct rtk_spi_host *host = spi_controller_get_devdata(mem->spi->controller); -+ u32 opcode = op->cmd.opcode; -+ -+ /* -+ * Hardware Workaround: -+ * The controller's auto-mode engine requires SFC_ADR_FOUR_BYTE_EN to be -+ * explicitly updated when the flash enters or exits 4-byte mode via control -+ * commands. -+ */ -+ if ((op->addr.nbytes == 4 || opcode == RTK_SPI_OP_EN4B) && !host->is_4byte) { -+ host->is_4byte = true; -+ writel(0x1, host->regbase + SFC_ADR_FOUR_BYTE_EN); -+ } else if ((op->addr.nbytes == 3 || opcode == RTK_SPI_OP_EX4B) && host->is_4byte) { -+ host->is_4byte = false; -+ writel(0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN); -+ } -+ -+ if (op->data.dir == SPI_MEM_DATA_IN) -+ return rtk_spi_command_read(host, op); -+ -+ return rtk_spi_do_write_and_cmds(host, op); -+} -+ -+static bool rtk_spi_supports_op(struct spi_mem *mem, -+ const struct spi_mem_op *op) -+{ -+ if (op->cmd.buswidth != 1) -+ return false; -+ -+ if (op->cmd.dtr || op->addr.dtr || op->data.dtr) -+ 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; -+ } -+ -+ /* -+ * The data window is access-triggered: each CPU access to host->iobase -+ * generates one complete SPI transaction. Consecutive byte accesses -+ * therefore cannot be merged into a single transaction, so -+ * multi-byte stateless commands are not supported by this controller. -+ */ -+ if (!op->addr.nbytes && op->data.nbytes > 1) -+ return -EOPNOTSUPP; -+ -+ if (op->dummy.nbytes != 0) { -+ if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) -+ return false; -+ } -+ -+ if (op->data.nbytes != 0 && op->data.buswidth > 2) -+ return false; -+ -+ return spi_mem_default_supports_op(mem, op); -+} -+ -+static int rtk_spi_dirmap_create(struct spi_mem_dirmap_desc *desc) -+{ -+ const struct spi_mem_op *op = desc->info.op_tmpl; -+ -+ if (op->data.dir == SPI_MEM_DATA_IN && op->addr.nbytes != 3 && op->addr.nbytes != 4) -+ return -EOPNOTSUPP; -+ -+ if (op->data.dir != SPI_MEM_DATA_IN && op->data.dir != SPI_MEM_DATA_OUT) -+ return -EOPNOTSUPP; -+ -+ return 0; -+} -+ -+static ssize_t rtk_spi_dirmap_read(struct spi_mem_dirmap_desc *desc, -+ u64 offs, size_t len, void *buf) -+{ -+ struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); -+ const struct spi_mem_op *op = desc->info.op_tmpl; -+ loff_t addr = desc->info.offset + offs; -+ size_t chunk_len; -+ int ret; -+ -+ /* -+ * Handle unaligned address bytes at the beginning of the read operation. -+ * The hardware requires 4-byte alignment for DMA transfers. -+ */ -+ if (addr & 0x3) { -+ rtk_spi_read_mode(host, op); -+ chunk_len = min_t(size_t, 4 - (addr & 0x3), len); -+ ret = rtk_spi_byte_transfer(host, addr, chunk_len, buf, true); -+ if (ret) -+ return ret; -+ -+ return chunk_len; -+ } -+ -+ rtk_spi_read_mode(host, op); -+ chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); -+ ret = rtk_spi_dma_transfer(host, addr, chunk_len, true); -+ if (ret) { -+ dev_err(host->dev, "DMA read transfer failed: %d\n", ret); -+ return ret; -+ } -+ -+ memcpy(buf, host->buffer, chunk_len); -+ -+ return chunk_len; -+} -+ -+static ssize_t rtk_spi_dirmap_write(struct spi_mem_dirmap_desc *desc, -+ u64 offs, size_t len, const void *buf) -+{ -+ struct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller); -+ const struct spi_mem_op *op = desc->info.op_tmpl; -+ loff_t addr = desc->info.offset + offs; -+ size_t chunk_len; -+ int ret = 0; -+ -+ rtk_spi_enable_auto_write(host); -+ rtk_spi_write_mode(host, op); -+ -+ /* -+ * Handle unaligned address bytes at the beginning of the write operation. -+ * The hardware requires 4-byte alignment for DMA transfers. -+ */ -+ if (addr & 0x3) { -+ chunk_len = min_t(size_t, 4 - (addr & 0x3), len); -+ ret = rtk_spi_byte_transfer(host, addr, chunk_len, (u8 *)buf, false); -+ -+ goto out; -+ } -+ -+ chunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN); -+ -+ memcpy(host->buffer, buf, chunk_len); -+ -+ ret = rtk_spi_dma_transfer(host, addr, chunk_len, false); -+ if (ret) -+ dev_err(host->dev, "DMA write transfer failed: %d\n", ret); -+ -+out: -+ rtk_spi_disable_auto_write(host); -+ -+ return ret < 0 ? ret : chunk_len; -+} -+ -+static const struct spi_controller_mem_ops rtk_spi_mem_ops = { -+ .supports_op = rtk_spi_supports_op, -+ .exec_op = rtk_spi_exec_op, -+ .dirmap_create = rtk_spi_dirmap_create, -+ .dirmap_read = rtk_spi_dirmap_read, -+ .dirmap_write = rtk_spi_dirmap_write, -+}; -+ -+static int rtk_spi_probe(struct platform_device *pdev) -+{ -+ struct device *dev = &pdev->dev; -+ struct spi_controller *ctrl; -+ struct rtk_spi_host *host; -+ struct resource *res; -+ int ret; -+ -+ ctrl = devm_spi_alloc_host(dev, sizeof(*host)); -+ if (!ctrl) -+ return -ENOMEM; -+ -+ platform_set_drvdata(pdev, ctrl); -+ host = spi_controller_get_devdata(ctrl); -+ host->dev = dev; -+ -+ host->clk = devm_clk_get(dev, NULL); -+ if (IS_ERR(host->clk)) -+ return PTR_ERR(host->clk); -+ -+ host->rstc = devm_reset_control_get_optional_exclusive(dev, NULL); -+ if (IS_ERR(host->rstc)) -+ return PTR_ERR(host->rstc); -+ -+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(35)); -+ if (ret) { -+ dev_err(dev, "Failed to set dma mask\n"); -+ return ret; -+ } -+ -+ host->buffer = dmam_alloc_coherent(dev, SFC_DMA_MAX_LEN, -+ &host->dma_buffer, GFP_KERNEL); -+ if (!host->buffer) -+ return -ENOMEM; -+ -+ host->regbase = devm_platform_ioremap_resource_byname(pdev, "ctrl"); -+ if (IS_ERR(host->regbase)) -+ return PTR_ERR(host->regbase); -+ -+ host->mdbase = devm_platform_ioremap_resource_byname(pdev, "dma"); -+ if (IS_ERR(host->mdbase)) -+ return PTR_ERR(host->mdbase); -+ -+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap"); -+ host->iobase = devm_ioremap_resource(dev, res); -+ if (IS_ERR(host->iobase)) -+ return PTR_ERR(host->iobase); -+ -+ host->dirmap_size = resource_size(res); -+ host->flash_phys_base = res->start; -+ host->is_4byte = false; -+ -+ ret = clk_prepare_enable(host->clk); -+ if (ret) -+ return ret; -+ -+ ret = reset_control_assert(host->rstc); -+ usleep_range(10, 20); -+ ret = reset_control_deassert(host->rstc); -+ -+ rtk_spi_init(host); -+ -+ pm_runtime_set_autosuspend_delay(dev, SFC_AUTOSUSPEND_TIMEOUT); -+ pm_runtime_use_autosuspend(dev); -+ pm_runtime_set_active(dev); -+ pm_runtime_enable(dev); -+ pm_runtime_get_noresume(dev); -+ -+ ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL; -+ ctrl->bus_num = -1; -+ ctrl->mem_ops = &rtk_spi_mem_ops; -+ ctrl->num_chipselect = 1; -+ ctrl->auto_runtime_pm = true; -+ -+ ret = spi_register_controller(ctrl); -+ if (ret < 0) { -+ dev_err(dev, "failed to register controller\n"); -+ goto err_pm_disable; -+ } -+ -+ pm_runtime_put_autosuspend(dev); -+ -+ return 0; -+ -+err_pm_disable: -+ pm_runtime_put_noidle(dev); -+ pm_runtime_disable(dev); -+ pm_runtime_set_suspended(dev); -+ pm_runtime_dont_use_autosuspend(dev); -+ reset_control_assert(host->rstc); -+ clk_disable_unprepare(host->clk); -+ -+ return ret; -+} -+ -+static void rtk_spi_remove(struct platform_device *pdev) -+{ -+ struct spi_controller *ctrl = platform_get_drvdata(pdev); -+ struct rtk_spi_host *host = spi_controller_get_devdata(ctrl); -+ struct device *dev = &pdev->dev; -+ -+ spi_unregister_controller(ctrl); -+ -+ if (pm_runtime_resume_and_get(dev) < 0) -+ dev_warn(dev, "Failed to resume device during remove\n"); -+ -+ pm_runtime_put_noidle(dev); -+ pm_runtime_disable(dev); -+ pm_runtime_set_suspended(dev); -+ pm_runtime_dont_use_autosuspend(dev); -+ reset_control_assert(host->rstc); -+ clk_disable_unprepare(host->clk); -+} -+ -+static const struct of_device_id rtk_spi_dt_ids[] = { -+ { .compatible = "realtek,rtd1625-nor" }, -+ { /* sentinel */ } -+}; -+ -+MODULE_DEVICE_TABLE(of, rtk_spi_dt_ids); -+ -+static int rtk_spi_runtime_suspend(struct device *dev) -+{ -+ struct spi_controller *ctlr = dev_get_drvdata(dev); -+ struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); -+ -+ reset_control_assert(host->rstc); -+ -+ clk_disable_unprepare(host->clk); -+ -+ return 0; -+} -+ -+static int rtk_spi_runtime_resume(struct device *dev) -+{ -+ struct spi_controller *ctlr = dev_get_drvdata(dev); -+ struct rtk_spi_host *host = spi_controller_get_devdata(ctlr); -+ int ret; -+ -+ ret = clk_prepare_enable(host->clk); -+ if (ret < 0) { -+ dev_err(dev, "clk_prepare_enable failed: %d\n", ret); -+ return ret; -+ } -+ -+ reset_control_assert(host->rstc); -+ usleep_range(10, 20); -+ reset_control_deassert(host->rstc); -+ -+ rtk_spi_init(host); -+ -+ return 0; -+} -+ -+static DEFINE_RUNTIME_DEV_PM_OPS(rtk_spi_pm_ops, rtk_spi_runtime_suspend, -+ rtk_spi_runtime_resume, NULL); -+ -+static struct platform_driver rtk_spi_driver = { -+ .driver = { -+ .name = "rtk-spi-nor", -+ .of_match_table = rtk_spi_dt_ids, -+ .pm = pm_ptr(&rtk_spi_pm_ops), -+ }, -+ .probe = rtk_spi_probe, -+ .remove = rtk_spi_remove, -+}; -+module_platform_driver(rtk_spi_driver); -+ -+MODULE_DESCRIPTION("Realtek SPI Nor Controller Driver"); -+MODULE_AUTHOR("Jyan Chou <jyanchou@realtek.com>"); -+MODULE_LICENSE("GPL"); --- -2.43.0 +> + +> + if (op->dummy.nbytes != 0) { +> + if (op->dummy.buswidth > 1 || op->dummy.nbytes > 7) +> + return false; +> + } +> + +> + if (op->data.nbytes != 0 && op->data.buswidth > 2) +> + return false; +> + +> + return spi_mem_default_supports_op(mem, op); +> +} diff --git a/a/content_digest b/N1/content_digest index 6d83bbd..cefe053 100644 --- a/a/content_digest +++ b/N1/content_digest @@ -1,734 +1,70 @@ "ref\020260930065945.88008-1-eleanor.lin@realtek.com\0" - "From\0Yu-Chun Lin <eleanor.lin@realtek.com>\0" + "From\0Dan Carpenter <error27@gmail.com>\0" "Subject\0[PATCH v5 2/2] spi: spi-mem: Add Realtek SPI NOR flash controller driver\0" - "Date\0Wed, 30 Sep 2026 14:59:45 +0800\0" - "To\0<broonie@kernel.org>" - <robh@kernel.org> - <krzk+dt@kernel.org> - " <conor+dt@kernel.org>\0" - "Cc\0<p.zabel@pengutronix.de>" - <james.tai@realtek.com> - <cy.huang@realtek.com> - <stanley_chang@realtek.com> - <eleanor.lin@realtek.com> - <jyanchou@realtek.com> - <linux-spi@vger.kernel.org> - <devicetree@vger.kernel.org> - " <linux-kernel@vger.kernel.org>\0" + "Date\0Fri, 2 Oct 2026 14:12:38 +0300\0" + "To\0broonie@kernel.org" + robh@kernel.org + krzk+dt@kernel.org + " conor+dt@kernel.org\0" + "Cc\0p.zabel@pengutronix.de" + james.tai@realtek.com + cy.huang@realtek.com + stanley_chang@realtek.com + eleanor.lin@realtek.com + jyanchou@realtek.com + linux-spi@vger.kernel.org + devicetree@vger.kernel.org + " linux-kernel@vger.kernel.org\0" "\00:1\0" "b\0" - "From: Jyan Chou <jyanchou@realtek.com>\n" + "Commit aca8cbecdd2e (\"spi: spi-mem: Add Realtek SPI NOR flash\n" + "controller driver\") from Sep 30, 2026 (linux-next), leads to the\n" + "following Smatch static checker warning:\n" "\n" - "Add a spi-mem driver for the SPI NOR Flash Controller (SFC) found on\n" - "Realtek DHC SoC. SFC supports Dual I/O.\n" + "\tdrivers/spi/spi-rtk-nor.c:347 rtk_spi_supports_op()\n" + "\twarn: '(-95)' is not bool\n" "\n" - "Implement the exec_op API for regular control commands and the dirmap API\n" - "for hardware-accelerated read/write operations.\n" + "On Wed, 30 Sep 2026 14:59:45 +0800 Yu-Chun Lin <eleanor.lin@realtek.com> wrote:\n" + "> +static bool rtk_spi_supports_op(struct spi_mem *mem,\n" + "> +\t\t\t\tconst struct spi_mem_op *op)\n" + "> +{\n" + "> +\tif (op->cmd.buswidth != 1)\n" + "> +\t\treturn false;\n" + "> +\n" + "> +\tif (op->cmd.dtr || op->addr.dtr || op->data.dtr)\n" + "> +\t\treturn false;\n" + "> +\n" + "> +\tif (op->addr.nbytes != 0) {\n" + "> +\t\tif (op->addr.buswidth > 1)\n" + "> +\t\t\treturn false;\n" + "> +\t\tif (op->addr.nbytes < 3 || op->addr.nbytes > 4)\n" + "> +\t\t\treturn false;\n" + "> +\t}\n" + "> +\n" + "> +\t/*\n" + "> +\t * The data window is access-triggered: each CPU access to host->iobase\n" + "> +\t * generates one complete SPI transaction. Consecutive byte accesses\n" + "> +\t * therefore cannot be merged into a single transaction, so\n" + "> +\t * multi-byte stateless commands are not supported by this controller.\n" + "> +\t */\n" + "> +\tif (!op->addr.nbytes && op->data.nbytes > 1)\n" + "> +\t\treturn -EOPNOTSUPP;\n" "\n" - "The controller is described by three register ranges. The first one (ctrl)\n" - "holds the control registers. The second one (dma) holds the DMA engine\n" - "registers, used to transfer the payload between the flash and main memory.\n" - "The third range is the memory aperture (dirmap) that maps the flash\n" - "contents.\n" + "This is a bool function. s/-EOPNOTSUPP/false/?\n" "\n" - "Signed-off-by: Jyan Chou <jyanchou@realtek.com>\n" - "Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com>\n" - "Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com>\n" - "---\n" - "Changes in v5:\n" - "- Fix the initialization and teardown sequences.\n" - "- Add the check for unsupported multi-byte stateless commands in\n" - "rtk_spi_supports_op().\n" - "- Move the hardware workaround for 4-byte mode switching to rtk_spi_exec_op().\n" - "---\n" - " MAINTAINERS | 6 +\n" - " drivers/spi/Kconfig | 10 +\n" - " drivers/spi/Makefile | 1 +\n" - " drivers/spi/spi-rtk-nor.c | 624 ++++++++++++++++++++++++++++++++++++++\n" - " 4 files changed, 641 insertions(+)\n" - " create mode 100644 drivers/spi/spi-rtk-nor.c\n" + "regards,\n" + "dan carpenter\n" "\n" - "diff --git a/MAINTAINERS b/MAINTAINERS\n" - "index 34d5c9a0d562..20cd9332f823 100644\n" - "--- a/MAINTAINERS\n" - "+++ b/MAINTAINERS\n" - "@@ -22754,6 +22754,12 @@ S:\tMaintained\n" - " F:\tDocumentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml\n" - " F:\tdrivers/spi/spi-realtek-rtl-snand.c\n" - " \n" - "+REALTEK SPI-NOR\n" - "+M:\tJyan Chou <jyanchou@realtek.com>\n" - "+M:\tYu-Chun Lin <eleanor.lin@realtek.com>\n" - "+S:\tSupported\n" - "+F:\tdrivers/spi/spi-rtk-nor.c\n" - "+\n" - " REALTEK SYSTIMER DRIVER\n" - " M:\tHao-Wen Ting <haowen.ting@realtek.com>\n" - " S:\tMaintained\n" - "diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig\n" - "index 8782514bb89b..d45baa15a601 100644\n" - "--- a/drivers/spi/Kconfig\n" - "+++ b/drivers/spi/Kconfig\n" - "@@ -957,6 +957,16 @@ config SPI_RSPI\n" - " \thelp\n" - " \t SPI driver for Renesas RSPI and QSPI blocks.\n" - " \n" - "+config SPI_RTK_NOR\n" - "+\ttristate \"Realtek SPI-NOR flash controller\"\n" - "+\tdepends on ARCH_REALTEK || COMPILE_TEST\n" - "+\tdepends on SPI_MEM\n" - "+\thelp\n" - "+\t This enables support for the SPI NOR Flash Controller found on\n" - "+\t Realtek DHC SoCs. The controller supports single and dual I/O\n" - "+\t transfers and offloads the payload to an integrated DMA engine.\n" - "+\t Say Y or M here if you are building a kernel for a Realtek DHC SoC\n" - "+\n" - " config SPI_RZV2H_RSPI\n" - " \ttristate \"Renesas RZ/V2H RSPI controller\"\n" - " \tdepends on ARCH_RENESAS || COMPILE_TEST\n" - "diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile\n" - "index 9fa12498ce8c..68f4d3283e0c 100644\n" - "--- a/drivers/spi/Makefile\n" - "+++ b/drivers/spi/Makefile\n" - "@@ -131,6 +131,7 @@ obj-$(CONFIG_MACH_REALTEK_RTL)\t\t+= spi-realtek-rtl.o\n" - " obj-$(CONFIG_SPI_REALTEK_SNAND)\t\t+= spi-realtek-rtl-snand.o\n" - " obj-$(CONFIG_SPI_RPCIF)\t\t\t+= spi-rpc-if.o\n" - " obj-$(CONFIG_SPI_RSPI)\t\t\t+= spi-rspi.o\n" - "+obj-$(CONFIG_SPI_RTK_NOR)\t\t+= spi-rtk-nor.o\n" - " obj-$(CONFIG_SPI_RZV2H_RSPI)\t\t+= spi-rzv2h-rspi.o\n" - " obj-$(CONFIG_SPI_RZV2M_CSI)\t\t+= spi-rzv2m-csi.o\n" - " obj-$(CONFIG_SPI_S3C64XX)\t\t+= spi-s3c64xx.o\n" - "diff --git a/drivers/spi/spi-rtk-nor.c b/drivers/spi/spi-rtk-nor.c\n" - "new file mode 100644\n" - "index 000000000000..7f56346506b3\n" - "--- /dev/null\n" - "+++ b/drivers/spi/spi-rtk-nor.c\n" - "@@ -0,0 +1,624 @@\n" - "+// SPDX-License-Identifier: GPL-2.0\n" - "+//\n" - "+// Realtek SPI Nor Flash Controller Driver (SFC)\n" - "+//\n" - "+// Copyright (c) 2024-2026 Realtek Semiconductor Corp.\n" - "+\n" - "+#include <linux/bits.h>\n" - "+#include <linux/clk.h>\n" - "+#include <linux/delay.h>\n" - "+#include <linux/dma-mapping.h>\n" - "+#include <linux/errno.h>\n" - "+#include <linux/iopoll.h>\n" - "+#include <linux/minmax.h>\n" - "+#include <linux/module.h>\n" - "+#include <linux/platform_device.h>\n" - "+#include <linux/pm_runtime.h>\n" - "+#include <linux/reset.h>\n" - "+#include <linux/spi/spi.h>\n" - "+#include <linux/spi/spi-mem.h>\n" - "+#include <linux/types.h>\n" - "+\n" - "+#define SFC_OPCODE\t\t0x00\n" - "+#define DUAL_MODE_EN\t\tBIT(9)\n" - "+\n" - "+#define SFC_CTL\t\t\t0x04\n" - "+#define RW_DATAEN\t\tBIT(4)\n" - "+#define ADDR_EN\t\t\tBIT(3)\n" - "+\n" - "+#define SFC_SCK\t\t\t0x08\n" - "+#define FDIV_MASK\t\tGENMASK(7, 0)\n" - "+\n" - "+#define SFC_CE\t\t\t0x0c\n" - "+#define DESLT_TIME\t\t0x1a\n" - "+#define DESLT_TIME_SHIFT\t16\n" - "+#define PH_CNT\t\t\t0x13\n" - "+#define PH_CNT_SHIFT\t\t8\n" - "+#define PL_CNT\t\t\t0x7\n" - "+\n" - "+#define WT_PROM_DONE\t\tBIT(8)\n" - "+\n" - "+#define SFC_POS_LATCH\t\t0x14\n" - "+#define FALLING_EDGE_EN\t\t0x0\n" - "+\n" - "+#define SFC_WAIT_WR\t\t0x18\n" - "+#define SFC_EN_WR\t\t0x1c\n" - "+#define WT_PROM_EN\t\tBIT(8)\n" - "+\n" - "+#define SFC_ADR_FOUR_BYTE_EN\t0x28\n" - "+\n" - "+#define MD_FDMA_DDR_SADDR\t0x0c\n" - "+#define MD_FDMA_FL_SADDR\t0x10\n" - "+\n" - "+#define MD_FDMA_CTRL2\t\t0x14\n" - "+#define MAX_XFER_DMA_LEN\t(BIT(26) | BIT(27))\n" - "+#define MAX_XFER_256\t\tBIT(26)\n" - "+#define DMA_TO_FLASH\t\tBIT(25)\n" - "+\n" - "+#define MD_FDMA_CTRL1\t\t0x18\n" - "+#define DMA_W_EN_START\t\tBIT(3)\n" - "+#define DMA_END\t\t\tBIT(2)\n" - "+#define DMA_W_EN\t\tBIT(1)\n" - "+#define DMA_START\t\tBIT(0)\n" - "+\n" - "+#define MD_FDMA_DDR_SADDR1\t0x20\n" - "+\n" - "+#define SFC_DMA_TIMEOUT\t\t20000\n" - "+#define SFC_DMA_MAX_LEN\t\t0x100\n" - "+\n" - "+#define SFC_CTL_DMYCNT_MASK\tGENMASK(31, 24)\n" - "+#define SFC_CTL_DMYCNT_SHIFT\t24\n" - "+\n" - "+#define DMA_HIGH_BITS_MASK\t0x7\n" - "+\n" - "+#define RTK_SPI_OP_RDSR\t\t0x05\n" - "+#define RTK_SPI_OP_WREN\t\t0x06\n" - "+#define RTK_SPI_OP_EN4B\t\t0xb7\n" - "+#define RTK_SPI_OP_EX4B\t\t0xe9\n" - "+\n" - "+#define SFC_AUTOSUSPEND_TIMEOUT\t2000\n" - "+\n" - "+struct rtk_spi_host {\n" - "+\tstruct device\t\t*dev;\n" - "+\tstruct clk\t\t*clk;\n" - "+\tstruct reset_control\t*rstc;\n" - "+\tvoid __iomem\t\t*regbase;\n" - "+\tvoid __iomem\t\t*iobase;\n" - "+\tvoid __iomem\t\t*mdbase;\n" - "+\tvoid\t\t\t*buffer;\n" - "+\tbool\t\t\tis_4byte;\n" - "+\tdma_addr_t\t\tdma_buffer;\n" - "+\tresource_size_t\t\tflash_phys_base;\n" - "+\tsize_t\t\t\tdirmap_size;\n" - "+};\n" - "+\n" - "+static u32 rtk_spi_calc_dummy_cycles(const struct spi_mem_op *op)\n" - "+{\n" - "+\tif (!op->dummy.nbytes)\n" - "+\t\treturn 0;\n" - "+\n" - "+\treturn (op->dummy.nbytes * 8) / op->dummy.buswidth;\n" - "+}\n" - "+\n" - "+static void rtk_spi_read_mode(struct rtk_spi_host *host, const struct spi_mem_op *op)\n" - "+{\n" - "+\tu32 opcode = op->cmd.opcode;\n" - "+\tu32 dummy_cycles, val;\n" - "+\n" - "+\tif (op->data.buswidth == 2)\n" - "+\t\topcode |= DUAL_MODE_EN;\n" - "+\n" - "+\twritel(opcode, host->regbase + SFC_OPCODE);\n" - "+\n" - "+\tval = readl(host->regbase + SFC_CTL);\n" - "+\tval |= RW_DATAEN | ADDR_EN;\n" - "+\n" - "+\tdummy_cycles = rtk_spi_calc_dummy_cycles(op);\n" - "+\n" - "+\tval &= ~SFC_CTL_DMYCNT_MASK;\n" - "+\tval |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK;\n" - "+\twritel(val, host->regbase + SFC_CTL);\n" - "+\n" - "+\treadl(host->iobase);\n" - "+}\n" - "+\n" - "+static void rtk_spi_write_mode(struct rtk_spi_host *host,\n" - "+\t\t\t const struct spi_mem_op *op)\n" - "+{\n" - "+\tu32 opcode = op->cmd.opcode;\n" - "+\tu32 val;\n" - "+\n" - "+\twritel(opcode, host->regbase + SFC_OPCODE);\n" - "+\n" - "+\tval = readl(host->regbase + SFC_CTL);\n" - "+\tval |= RW_DATAEN | ADDR_EN;\n" - "+\twritel(val, host->regbase + SFC_CTL);\n" - "+}\n" - "+\n" - "+static void rtk_spi_enable_auto_write(struct rtk_spi_host *host)\n" - "+{\n" - "+\tu32 val;\n" - "+\n" - "+\tval = WT_PROM_DONE | RTK_SPI_OP_RDSR;\n" - "+\twritel(val, host->regbase + SFC_WAIT_WR);\n" - "+\n" - "+\tval = WT_PROM_EN | RTK_SPI_OP_WREN;\n" - "+\twritel(val, host->regbase + SFC_EN_WR);\n" - "+}\n" - "+\n" - "+static void rtk_spi_disable_auto_write(struct rtk_spi_host *host)\n" - "+{\n" - "+\twritel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR);\n" - "+\twritel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR);\n" - "+}\n" - "+\n" - "+static int rtk_spi_byte_transfer(struct rtk_spi_host *host, loff_t offset,\n" - "+\t\t\t\t size_t len, unsigned char *buf, bool is_read)\n" - "+{\n" - "+\tif (offset + len > host->dirmap_size) {\n" - "+\t\tdev_err(host->dev, \"PIO transfer out of bounds\\n\");\n" - "+\t\treturn -EINVAL;\n" - "+\t}\n" - "+\n" - "+\tif (is_read)\n" - "+\t\tmemcpy_fromio(buf, host->iobase + offset, len);\n" - "+\telse\n" - "+\t\tmemcpy_toio(host->iobase + offset, buf, len);\n" - "+\n" - "+\treturn 0;\n" - "+}\n" - "+\n" - "+static void rtk_spi_init(struct rtk_spi_host *host)\n" - "+{\n" - "+\tu32 val;\n" - "+\n" - "+\tval = readl(host->regbase + SFC_SCK);\n" - "+\tval &= ~FDIV_MASK;\n" - "+\tval |= (3 << 0) & FDIV_MASK;\n" - "+\twritel(val, host->regbase + SFC_SCK);\n" - "+\n" - "+\tval = (DESLT_TIME << DESLT_TIME_SHIFT) | (PH_CNT << PH_CNT_SHIFT) | PL_CNT;\n" - "+\twritel(val, host->regbase + SFC_CE);\n" - "+\n" - "+\twritel(FALLING_EDGE_EN, host->regbase + SFC_POS_LATCH);\n" - "+\twritel(RTK_SPI_OP_RDSR, host->regbase + SFC_WAIT_WR);\n" - "+\twritel(RTK_SPI_OP_WREN, host->regbase + SFC_EN_WR);\n" - "+\n" - "+\twritel(host->is_4byte ? 0x1 : 0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN);\n" - "+}\n" - "+\n" - "+static int rtk_spi_command_read(struct rtk_spi_host *host, const struct spi_mem_op *op)\n" - "+{\n" - "+\tsize_t len = op->data.nbytes;\n" - "+\tloff_t offset = op->addr.val;\n" - "+\tu8 opcode = op->cmd.opcode;\n" - "+\tu32 dummy_cycles, val;\n" - "+\n" - "+\twritel(opcode, host->regbase + SFC_OPCODE);\n" - "+\n" - "+\tval = readl(host->regbase + SFC_CTL);\n" - "+\tval &= ~(SFC_CTL_DMYCNT_MASK | ADDR_EN);\n" - "+\tval |= RW_DATAEN;\n" - "+\n" - "+\tif (op->addr.nbytes > 0)\n" - "+\t\tval |= ADDR_EN;\n" - "+\n" - "+\tdummy_cycles = rtk_spi_calc_dummy_cycles(op);\n" - "+\tval |= (dummy_cycles << SFC_CTL_DMYCNT_SHIFT) & SFC_CTL_DMYCNT_MASK;\n" - "+\twritel(val, host->regbase + SFC_CTL);\n" - "+\n" - "+\treturn rtk_spi_byte_transfer(host, offset, len, op->data.buf.in, true);\n" - "+}\n" - "+\n" - "+static int rtk_spi_do_write_and_cmds(struct rtk_spi_host *host, const struct spi_mem_op *op)\n" - "+{\n" - "+\tu8 opcode = op->cmd.opcode;\n" - "+\tu32 ctl_val = 0;\n" - "+\n" - "+\twritel(opcode, host->regbase + SFC_OPCODE);\n" - "+\n" - "+\tif (op->data.nbytes > 0)\n" - "+\t\tctl_val |= RW_DATAEN;\n" - "+\n" - "+\tif (op->addr.nbytes > 0)\n" - "+\t\tctl_val |= ADDR_EN;\n" - "+\n" - "+\twritel(ctl_val, host->regbase + SFC_CTL);\n" - "+\n" - "+\tif (op->data.nbytes > 0) {\n" - "+\t\tconst u8 *buf = op->data.buf.out;\n" - "+\n" - "+\t\tif (op->addr.val + op->data.nbytes > host->dirmap_size)\n" - "+\t\t\treturn -EINVAL;\n" - "+\n" - "+\t\twriteb(*buf, host->iobase + op->addr.val);\n" - "+\t} else {\n" - "+\t\tif (op->addr.val >= host->dirmap_size)\n" - "+\t\t\treturn -EINVAL;\n" - "+\n" - "+\t\treadb(host->iobase + op->addr.val);\n" - "+\t}\n" - "+\n" - "+\treturn 0;\n" - "+}\n" - "+\n" - "+static int rtk_spi_dma_transfer(struct rtk_spi_host *host, loff_t offset,\n" - "+\t\t\t\tsize_t len, bool is_read)\n" - "+{\n" - "+\tu64 dma_buffer, timeout_us = SFC_DMA_TIMEOUT * 100;\n" - "+\tu32 flash_phys_addr, val;\n" - "+\tint ret;\n" - "+\n" - "+\tif (offset + len > host->dirmap_size) {\n" - "+\t\tdev_err(host->dev, \"DMA transfer out of bounds\\n\");\n" - "+\t\treturn -EINVAL;\n" - "+\t}\n" - "+\n" - "+\twritel(DMA_W_EN_START | DMA_W_EN, host->mdbase + MD_FDMA_CTRL1);\n" - "+\n" - "+\tdma_buffer = host->dma_buffer;\n" - "+\n" - "+\t/* Setup MD DDR address and flash address */\n" - "+\twritel(lower_32_bits(dma_buffer), host->mdbase + MD_FDMA_DDR_SADDR);\n" - "+\twritel(upper_32_bits(dma_buffer) & DMA_HIGH_BITS_MASK,\n" - "+\t host->mdbase + MD_FDMA_DDR_SADDR1);\n" - "+\n" - "+\t/* MD_FDMA_FL_SADDR is a 32-bit hardware register */\n" - "+\tflash_phys_addr = lower_32_bits(host->flash_phys_base + offset);\n" - "+\twritel(flash_phys_addr, host->mdbase + MD_FDMA_FL_SADDR);\n" - "+\n" - "+\tif (is_read)\n" - "+\t\tval = MAX_XFER_DMA_LEN | len;\n" - "+\telse\n" - "+\t\tval = DMA_TO_FLASH | MAX_XFER_256 | len;\n" - "+\n" - "+\twritel(val, host->mdbase + MD_FDMA_CTRL2);\n" - "+\n" - "+\twritel(DMA_W_EN | DMA_START, host->mdbase + MD_FDMA_CTRL1);\n" - "+\tudelay(1);\n" - "+\n" - "+\tret = readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val,\n" - "+\t\t\t\t !(val & DMA_START), 100, timeout_us);\n" - "+\tif (ret) {\n" - "+\t\tdev_err(host->dev, \"DMA transfer timed out\\n\");\n" - "+\t\tval = readl(host->mdbase + MD_FDMA_CTRL1);\n" - "+\t\tval |= DMA_END | DMA_W_EN_START;\n" - "+\t\twritel(val, host->mdbase + MD_FDMA_CTRL1);\n" - "+\n" - "+\t\tif (readl_poll_timeout(host->mdbase + MD_FDMA_CTRL1, val,\n" - "+\t\t\t\t !(val & DMA_END), 10, 1000)) {\n" - "+\t\t\tdev_err(host->dev, \"Failed to stop DMA gracefully\\n\");\n" - "+\t\t}\n" - "+\n" - "+\t\treturn ret;\n" - "+\t}\n" - "+\n" - "+\treturn 0;\n" - "+}\n" - "+\n" - "+static int rtk_spi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)\n" - "+{\n" - "+\tstruct rtk_spi_host *host = spi_controller_get_devdata(mem->spi->controller);\n" - "+\tu32 opcode = op->cmd.opcode;\n" - "+\n" - "+\t/*\n" - "+\t * Hardware Workaround:\n" - "+\t * The controller's auto-mode engine requires SFC_ADR_FOUR_BYTE_EN to be\n" - "+\t * explicitly updated when the flash enters or exits 4-byte mode via control\n" - "+\t * commands.\n" - "+\t */\n" - "+\tif ((op->addr.nbytes == 4 || opcode == RTK_SPI_OP_EN4B) && !host->is_4byte) {\n" - "+\t\thost->is_4byte = true;\n" - "+\t\twritel(0x1, host->regbase + SFC_ADR_FOUR_BYTE_EN);\n" - "+\t} else if ((op->addr.nbytes == 3 || opcode == RTK_SPI_OP_EX4B) && host->is_4byte) {\n" - "+\t\thost->is_4byte = false;\n" - "+\t\twritel(0x0, host->regbase + SFC_ADR_FOUR_BYTE_EN);\n" - "+\t}\n" - "+\n" - "+\tif (op->data.dir == SPI_MEM_DATA_IN)\n" - "+\t\treturn rtk_spi_command_read(host, op);\n" - "+\n" - "+\treturn rtk_spi_do_write_and_cmds(host, op);\n" - "+}\n" - "+\n" - "+static bool rtk_spi_supports_op(struct spi_mem *mem,\n" - "+\t\t\t\tconst struct spi_mem_op *op)\n" - "+{\n" - "+\tif (op->cmd.buswidth != 1)\n" - "+\t\treturn false;\n" - "+\n" - "+\tif (op->cmd.dtr || op->addr.dtr || op->data.dtr)\n" - "+\t\treturn false;\n" - "+\n" - "+\tif (op->addr.nbytes != 0) {\n" - "+\t\tif (op->addr.buswidth > 1)\n" - "+\t\t\treturn false;\n" - "+\t\tif (op->addr.nbytes < 3 || op->addr.nbytes > 4)\n" - "+\t\t\treturn false;\n" - "+\t}\n" - "+\n" - "+\t/*\n" - "+\t * The data window is access-triggered: each CPU access to host->iobase\n" - "+\t * generates one complete SPI transaction. Consecutive byte accesses\n" - "+\t * therefore cannot be merged into a single transaction, so\n" - "+\t * multi-byte stateless commands are not supported by this controller.\n" - "+\t */\n" - "+\tif (!op->addr.nbytes && op->data.nbytes > 1)\n" - "+\t\treturn -EOPNOTSUPP;\n" - "+\n" - "+\tif (op->dummy.nbytes != 0) {\n" - "+\t\tif (op->dummy.buswidth > 1 || op->dummy.nbytes > 7)\n" - "+\t\t\treturn false;\n" - "+\t}\n" - "+\n" - "+\tif (op->data.nbytes != 0 && op->data.buswidth > 2)\n" - "+\t\treturn false;\n" - "+\n" - "+\treturn spi_mem_default_supports_op(mem, op);\n" - "+}\n" - "+\n" - "+static int rtk_spi_dirmap_create(struct spi_mem_dirmap_desc *desc)\n" - "+{\n" - "+\tconst struct spi_mem_op *op = desc->info.op_tmpl;\n" - "+\n" - "+\tif (op->data.dir == SPI_MEM_DATA_IN && op->addr.nbytes != 3 && op->addr.nbytes != 4)\n" - "+\t\treturn -EOPNOTSUPP;\n" - "+\n" - "+\tif (op->data.dir != SPI_MEM_DATA_IN && op->data.dir != SPI_MEM_DATA_OUT)\n" - "+\t\treturn -EOPNOTSUPP;\n" - "+\n" - "+\treturn 0;\n" - "+}\n" - "+\n" - "+static ssize_t rtk_spi_dirmap_read(struct spi_mem_dirmap_desc *desc,\n" - "+\t\t\t\t u64 offs, size_t len, void *buf)\n" - "+{\n" - "+\tstruct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller);\n" - "+\tconst struct spi_mem_op *op = desc->info.op_tmpl;\n" - "+\tloff_t addr = desc->info.offset + offs;\n" - "+\tsize_t chunk_len;\n" - "+\tint ret;\n" - "+\n" - "+\t/*\n" - "+\t * Handle unaligned address bytes at the beginning of the read operation.\n" - "+\t * The hardware requires 4-byte alignment for DMA transfers.\n" - "+\t */\n" - "+\tif (addr & 0x3) {\n" - "+\t\trtk_spi_read_mode(host, op);\n" - "+\t\tchunk_len = min_t(size_t, 4 - (addr & 0x3), len);\n" - "+\t\tret = rtk_spi_byte_transfer(host, addr, chunk_len, buf, true);\n" - "+\t\tif (ret)\n" - "+\t\t\treturn ret;\n" - "+\n" - "+\t\treturn chunk_len;\n" - "+\t}\n" - "+\n" - "+\trtk_spi_read_mode(host, op);\n" - "+\tchunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN);\n" - "+\tret = rtk_spi_dma_transfer(host, addr, chunk_len, true);\n" - "+\tif (ret) {\n" - "+\t\tdev_err(host->dev, \"DMA read transfer failed: %d\\n\", ret);\n" - "+\t\treturn ret;\n" - "+\t}\n" - "+\n" - "+\tmemcpy(buf, host->buffer, chunk_len);\n" - "+\n" - "+\treturn chunk_len;\n" - "+}\n" - "+\n" - "+static ssize_t rtk_spi_dirmap_write(struct spi_mem_dirmap_desc *desc,\n" - "+\t\t\t\t u64 offs, size_t len, const void *buf)\n" - "+{\n" - "+\tstruct rtk_spi_host *host = spi_controller_get_devdata(desc->mem->spi->controller);\n" - "+\tconst struct spi_mem_op *op = desc->info.op_tmpl;\n" - "+\tloff_t addr = desc->info.offset + offs;\n" - "+\tsize_t chunk_len;\n" - "+\tint ret = 0;\n" - "+\n" - "+\trtk_spi_enable_auto_write(host);\n" - "+\trtk_spi_write_mode(host, op);\n" - "+\n" - "+\t/*\n" - "+\t * Handle unaligned address bytes at the beginning of the write operation.\n" - "+\t * The hardware requires 4-byte alignment for DMA transfers.\n" - "+\t */\n" - "+\tif (addr & 0x3) {\n" - "+\t\tchunk_len = min_t(size_t, 4 - (addr & 0x3), len);\n" - "+\t\tret = rtk_spi_byte_transfer(host, addr, chunk_len, (u8 *)buf, false);\n" - "+\n" - "+\t\tgoto out;\n" - "+\t}\n" - "+\n" - "+\tchunk_len = min_t(size_t, len, SFC_DMA_MAX_LEN);\n" - "+\n" - "+\tmemcpy(host->buffer, buf, chunk_len);\n" - "+\n" - "+\tret = rtk_spi_dma_transfer(host, addr, chunk_len, false);\n" - "+\tif (ret)\n" - "+\t\tdev_err(host->dev, \"DMA write transfer failed: %d\\n\", ret);\n" - "+\n" - "+out:\n" - "+\trtk_spi_disable_auto_write(host);\n" - "+\n" - "+\treturn ret < 0 ? ret : chunk_len;\n" - "+}\n" - "+\n" - "+static const struct spi_controller_mem_ops rtk_spi_mem_ops = {\n" - "+\t.supports_op = rtk_spi_supports_op,\n" - "+\t.exec_op = rtk_spi_exec_op,\n" - "+\t.dirmap_create = rtk_spi_dirmap_create,\n" - "+\t.dirmap_read = rtk_spi_dirmap_read,\n" - "+\t.dirmap_write = rtk_spi_dirmap_write,\n" - "+};\n" - "+\n" - "+static int rtk_spi_probe(struct platform_device *pdev)\n" - "+{\n" - "+\tstruct device *dev = &pdev->dev;\n" - "+\tstruct spi_controller *ctrl;\n" - "+\tstruct rtk_spi_host *host;\n" - "+\tstruct resource *res;\n" - "+\tint ret;\n" - "+\n" - "+\tctrl = devm_spi_alloc_host(dev, sizeof(*host));\n" - "+\tif (!ctrl)\n" - "+\t\treturn -ENOMEM;\n" - "+\n" - "+\tplatform_set_drvdata(pdev, ctrl);\n" - "+\thost = spi_controller_get_devdata(ctrl);\n" - "+\thost->dev = dev;\n" - "+\n" - "+\thost->clk = devm_clk_get(dev, NULL);\n" - "+\tif (IS_ERR(host->clk))\n" - "+\t\treturn PTR_ERR(host->clk);\n" - "+\n" - "+\thost->rstc = devm_reset_control_get_optional_exclusive(dev, NULL);\n" - "+\tif (IS_ERR(host->rstc))\n" - "+\t\treturn PTR_ERR(host->rstc);\n" - "+\n" - "+\tret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(35));\n" - "+\tif (ret) {\n" - "+\t\tdev_err(dev, \"Failed to set dma mask\\n\");\n" - "+\t\treturn ret;\n" - "+\t}\n" - "+\n" - "+\thost->buffer = dmam_alloc_coherent(dev, SFC_DMA_MAX_LEN,\n" - "+\t\t\t\t\t &host->dma_buffer, GFP_KERNEL);\n" - "+\tif (!host->buffer)\n" - "+\t\treturn -ENOMEM;\n" - "+\n" - "+\thost->regbase = devm_platform_ioremap_resource_byname(pdev, \"ctrl\");\n" - "+\tif (IS_ERR(host->regbase))\n" - "+\t\treturn PTR_ERR(host->regbase);\n" - "+\n" - "+\thost->mdbase = devm_platform_ioremap_resource_byname(pdev, \"dma\");\n" - "+\tif (IS_ERR(host->mdbase))\n" - "+\t\treturn PTR_ERR(host->mdbase);\n" - "+\n" - "+\tres = platform_get_resource_byname(pdev, IORESOURCE_MEM, \"dirmap\");\n" - "+\thost->iobase = devm_ioremap_resource(dev, res);\n" - "+\tif (IS_ERR(host->iobase))\n" - "+\t\treturn PTR_ERR(host->iobase);\n" - "+\n" - "+\thost->dirmap_size = resource_size(res);\n" - "+\thost->flash_phys_base = res->start;\n" - "+\thost->is_4byte = false;\n" - "+\n" - "+\tret = clk_prepare_enable(host->clk);\n" - "+\tif (ret)\n" - "+\t\treturn ret;\n" - "+\n" - "+\tret = reset_control_assert(host->rstc);\n" - "+\tusleep_range(10, 20);\n" - "+\tret = reset_control_deassert(host->rstc);\n" - "+\n" - "+\trtk_spi_init(host);\n" - "+\n" - "+\tpm_runtime_set_autosuspend_delay(dev, SFC_AUTOSUSPEND_TIMEOUT);\n" - "+\tpm_runtime_use_autosuspend(dev);\n" - "+\tpm_runtime_set_active(dev);\n" - "+\tpm_runtime_enable(dev);\n" - "+\tpm_runtime_get_noresume(dev);\n" - "+\n" - "+\tctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL;\n" - "+\tctrl->bus_num = -1;\n" - "+\tctrl->mem_ops = &rtk_spi_mem_ops;\n" - "+\tctrl->num_chipselect = 1;\n" - "+\tctrl->auto_runtime_pm = true;\n" - "+\n" - "+\tret = spi_register_controller(ctrl);\n" - "+\tif (ret < 0) {\n" - "+\t\tdev_err(dev, \"failed to register controller\\n\");\n" - "+\t\tgoto err_pm_disable;\n" - "+\t}\n" - "+\n" - "+\tpm_runtime_put_autosuspend(dev);\n" - "+\n" - "+\treturn 0;\n" - "+\n" - "+err_pm_disable:\n" - "+\tpm_runtime_put_noidle(dev);\n" - "+\tpm_runtime_disable(dev);\n" - "+\tpm_runtime_set_suspended(dev);\n" - "+\tpm_runtime_dont_use_autosuspend(dev);\n" - "+\treset_control_assert(host->rstc);\n" - "+\tclk_disable_unprepare(host->clk);\n" - "+\n" - "+\treturn ret;\n" - "+}\n" - "+\n" - "+static void rtk_spi_remove(struct platform_device *pdev)\n" - "+{\n" - "+\tstruct spi_controller *ctrl = platform_get_drvdata(pdev);\n" - "+\tstruct rtk_spi_host *host = spi_controller_get_devdata(ctrl);\n" - "+\tstruct device *dev = &pdev->dev;\n" - "+\n" - "+\tspi_unregister_controller(ctrl);\n" - "+\n" - "+\tif (pm_runtime_resume_and_get(dev) < 0)\n" - "+\t\tdev_warn(dev, \"Failed to resume device during remove\\n\");\n" - "+\n" - "+\tpm_runtime_put_noidle(dev);\n" - "+\tpm_runtime_disable(dev);\n" - "+\tpm_runtime_set_suspended(dev);\n" - "+\tpm_runtime_dont_use_autosuspend(dev);\n" - "+\treset_control_assert(host->rstc);\n" - "+\tclk_disable_unprepare(host->clk);\n" - "+}\n" - "+\n" - "+static const struct of_device_id rtk_spi_dt_ids[] = {\n" - "+\t{ .compatible = \"realtek,rtd1625-nor\" },\n" - "+\t{ /* sentinel */ }\n" - "+};\n" - "+\n" - "+MODULE_DEVICE_TABLE(of, rtk_spi_dt_ids);\n" - "+\n" - "+static int rtk_spi_runtime_suspend(struct device *dev)\n" - "+{\n" - "+\tstruct spi_controller *ctlr = dev_get_drvdata(dev);\n" - "+\tstruct rtk_spi_host *host = spi_controller_get_devdata(ctlr);\n" - "+\n" - "+\treset_control_assert(host->rstc);\n" - "+\n" - "+\tclk_disable_unprepare(host->clk);\n" - "+\n" - "+\treturn 0;\n" - "+}\n" - "+\n" - "+static int rtk_spi_runtime_resume(struct device *dev)\n" - "+{\n" - "+\tstruct spi_controller *ctlr = dev_get_drvdata(dev);\n" - "+\tstruct rtk_spi_host *host = spi_controller_get_devdata(ctlr);\n" - "+\tint ret;\n" - "+\n" - "+\tret = clk_prepare_enable(host->clk);\n" - "+\tif (ret < 0) {\n" - "+\t\tdev_err(dev, \"clk_prepare_enable failed: %d\\n\", ret);\n" - "+\t\treturn ret;\n" - "+\t}\n" - "+\n" - "+\treset_control_assert(host->rstc);\n" - "+\tusleep_range(10, 20);\n" - "+\treset_control_deassert(host->rstc);\n" - "+\n" - "+\trtk_spi_init(host);\n" - "+\n" - "+\treturn 0;\n" - "+}\n" - "+\n" - "+static DEFINE_RUNTIME_DEV_PM_OPS(rtk_spi_pm_ops, rtk_spi_runtime_suspend,\n" - "+\t\t\t\t rtk_spi_runtime_resume, NULL);\n" - "+\n" - "+static struct platform_driver rtk_spi_driver = {\n" - "+\t.driver = {\n" - "+\t\t.name = \"rtk-spi-nor\",\n" - "+\t\t.of_match_table = rtk_spi_dt_ids,\n" - "+\t\t.pm = pm_ptr(&rtk_spi_pm_ops),\n" - "+\t},\n" - "+\t.probe\t= rtk_spi_probe,\n" - "+\t.remove\t= rtk_spi_remove,\n" - "+};\n" - "+module_platform_driver(rtk_spi_driver);\n" - "+\n" - "+MODULE_DESCRIPTION(\"Realtek SPI Nor Controller Driver\");\n" - "+MODULE_AUTHOR(\"Jyan Chou <jyanchou@realtek.com>\");\n" - "+MODULE_LICENSE(\"GPL\");\n" - "-- \n" - 2.43.0 + "> +\n" + "> +\tif (op->dummy.nbytes != 0) {\n" + "> +\t\tif (op->dummy.buswidth > 1 || op->dummy.nbytes > 7)\n" + "> +\t\t\treturn false;\n" + "> +\t}\n" + "> +\n" + "> +\tif (op->data.nbytes != 0 && op->data.buswidth > 2)\n" + "> +\t\treturn false;\n" + "> +\n" + "> +\treturn spi_mem_default_supports_op(mem, op);\n" + > +} -ff2f688906166280c44ffb31af534b0431488d52c69776aa57c2c0d905c7d3fa +ce653c4d8f5c74fa358d13edfd78357a5f648ffa33ca7b956c9436f9f6ab584b
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox