From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from rtits2.realtek.com.tw (rtits2.realtek.com [211.75.126.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 75260451986; Wed, 30 Sep 2026 07:00:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.75.126.72 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790751618; cv=none; b=PnJCFIeYuZDBEtHDsJfFIr7eJsAiAdhzsCo7MNIP2NUAuqZdRux/GYfoeL8cvNtBKPX4CY5tJwcwvboi1UUW9U6v1NymqOSj9WPBysIQTBhnfhjK5k693I2exGgWLjPWxblyHB5wSK27uAnvcfQkq8WwfEz6yZsFrHp4ASYlcq4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790751618; c=relaxed/simple; bh=B9qICTOKUHPpDmKaCwYxpk4hCL0T7sF+xmujo+fDsfg=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=XjVzzK/5GCkiW5JlggWy42YMx8eJ9RUwjmeQm7Gcts8I2Pz0PF5JDzZrOXPciSfmDyn6AlzFwcfamNggKlApmKuwmiONLI4IecG1zuxVhxkeawZqH5NJ72lYN5qWjNkGXwlhWu3Rd5WKsNtFXFQr8zq0ur8eS8ha6jkzAjQIBoE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=realtek.com; spf=pass smtp.mailfrom=realtek.com; dkim=pass (2048-bit key) header.d=realtek.com header.i=@realtek.com header.b=o250Vui/; arc=none smtp.client-ip=211.75.126.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=realtek.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=realtek.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=realtek.com header.i=@realtek.com header.b="o250Vui/" X-SpamFilter-By: ArmorX SpamTrap 5.80 with qID 68U6xk1U31887173, This message is accepted by code: ctloc85258 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=realtek.com; s=dkim; t=1790751586; bh=RRKWQc2NvH5ep3ipU261rN4f38Pk4ghJFsd2wT4BNlY=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding:Content-Type; b=o250Vui/t3sdEQkh0l2ojJijQVAFnvjp6GZpfhZH1oxZtzFy9TY+6wVJhFQDeaAoF kTxN3Mey2zRpQG3Cj67tmPgmQkNrvmKbrJhPTVx3hJC2IJk0+rEE99fNJDfeFjWYtx YcSxy6X97qCOiFZBxAQVQUeX2lCrQESMIoty+qEHwmxssw/OuQKpUHcfnr/c9KFw1M hQPwJrjLUNblNjZEcOfJvwu6xYvGjChcSIpj8Wssk7EWeVlQSRxBTgB6fhxgqTlm1O VXzpsrFry8R5NXD6MS1aF5GmSxFRrsJQvLV+TYJEK1kPXoy5IM+2DYpRjr8tpCBhN/ 3pf34b9t9qjIw== Received: from mail.realtek.com (rtkexhmbs02.realtek.com.tw[172.21.6.41]) by rtits2.realtek.com.tw (8.15.2/3.29/5.94) with ESMTPS id 68U6xk1U31887173 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Wed, 30 Sep 2026 14:59:46 +0800 Received: from RTKEXHMBS06.realtek.com.tw (10.21.1.56) by RTKEXHMBS02.realtek.com.tw (172.21.6.41) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.49; Wed, 30 Sep 2026 14:59:46 +0800 Received: from cn1dhc-k02 (172.21.252.101) by RTKEXHMBS06.realtek.com.tw (10.21.1.56) with Microsoft SMTP Server id 15.2.2562.49 via Frontend Transport; Wed, 30 Sep 2026 14:59:46 +0800 From: Yu-Chun Lin To: , , , CC: , , , , , , , , Subject: [PATCH v5 2/2] spi: spi-mem: Add Realtek SPI NOR flash controller driver Date: Wed, 30 Sep 2026 14:59:45 +0800 Message-ID: <20260930065945.88008-3-eleanor.lin@realtek.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260930065945.88008-1-eleanor.lin@realtek.com> References: <20260930065945.88008-1-eleanor.lin@realtek.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Content-Type: text/plain From: Jyan Chou Add a spi-mem driver for the SPI NOR Flash Controller (SFC) found on Realtek DHC SoC. SFC supports Dual I/O. Implement the exec_op API for regular control commands and the dirmap API for hardware-accelerated read/write operations. 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. Signed-off-by: Jyan Chou Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin --- 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 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 +M: Yu-Chun Lin +S: Supported +F: drivers/spi/spi-rtk-nor.c + REALTEK SYSTIMER DRIVER M: Hao-Wen Ting 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); -- 2.43.0 From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm2-f13.google.com (mail-wm2-f13.google.com [74.125.225.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D14A92D12EE for ; Fri, 2 Oct 2026 11:12:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.141 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790939567; cv=none; b=XTFsf8l0E6PnHC60XLMOodsQzakCpyLDVISY+KC4ZZNWbGK/M05dbm7ySWjh6Q2qhVwMpv5H6bKlI4Zfaa/69UPU8IgwTS4IiC0v6PRgxmDrMmO9022w9GBiANN9FtW4NRXPg0py0OTOEe9pjz2sMkm28VlK/8zDX9NIzxIIY0s= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790939567; c=relaxed/simple; bh=+tK14WpoRtFvM1GF6Iy9jNqGoc2KBjWKkKZg8oF+y8g=; h=Date:From:To:Cc:Subject:Message-ID:MIME-Version:Content-Type: Content-Disposition:In-Reply-To; b=alYUAgqhWU0sqaGvvCoBGZh5IRcLdoQv0Xj5SBQCyldPBlOOfVZRM/pQWEjoqtHat+EOv4Gx1oZ8HOgpYNgAc7Z/OhPZlUSwx8C7TgLRTnarbAItXHFrNuI5pd6/taWkxX8E6FGAHr2TL99/bTsCb8aJj7pUYwUEO7dIKCe4OhM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=npmmAOAl; arc=none smtp.client-ip=74.125.225.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="npmmAOAl" Received: by mail-wm2-f13.google.com with SMTP id 5b1f17b1804b1-4a09e2d0f7dso3102585e9.1 for ; Fri, 02 Oct 2026 04:12:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790939564; x=1791544364; darn=vger.kernel.org; h=in-reply-to:content-disposition:content-type:mime-version :message-id:subject:cc:to:from:date:from:to:cc:subject:date :message-id:reply-to:content-type; bh=SQOQV1ATfhMaKONDJVd6HhB6Jyj0fejjrTFbl7JHLkE=; b=npmmAOAlPXNHvuNDuJvAsRRy2sdP4FJlponQ/fl7PAT3q8IpRZjswWQIUPUFKLBAU7 FA4IkIiXZ/V+8We9aKwTvC5KnksMDPrI2F1p3NHKGeo5ceNS9wggZj1KxU3zntfUmV5X LXAGNjwhHplwZhKxp5O0YcID/2TDRa1ymIutRKUYQ3supSudEMiiXyTZAwKq3FCxs/aw Xzvqpg0Q7M6GN0O+ekBKU2cdrpKpSmts3tMhMJp2fk8uZhXifs8EhpmTibEyso2UV/j8 VHocejlXlroCcnQ8X7lE2FE59n2R/Y+lCaHvhQykEvQ6Zknz9YHvKMSVu+RpLrN6AL5k 6Nyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790939564; x=1791544364; h=in-reply-to:content-disposition:content-type:mime-version :message-id:subject:cc:to:from:date:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=SQOQV1ATfhMaKONDJVd6HhB6Jyj0fejjrTFbl7JHLkE=; b=ngDyBikWrroqxkxm9RNfxaK4YSh/g2Tty7MWZnX2070Nnla7uX2tJshJEG44PKP8N4 gij4L9CKjArjYvDlGFYvEyCmRE3ZSDrXctX0wdQ7tHIFogeLlPoDD9T67fUMCp1sgfBt yA9Nv5P89HKG7eA0TPLeK7QuGfI/51J1MVCPgrV9CP//BwdnSMnl5wHIj70PyPAaBiZe HHPWjhC9gY4PwQwNS0Fe585PEUWEAG4lcCfl+3B8h1p38QAImRIQVwLEKl4lBafk+V8E ApGGUPw9OnMkqCJ1P2vv9HuCPCB0xzGcJYLpoDKbP+LNd05V9/76Vg9RfrY5SKiS1rrd 5OlQ== X-Forwarded-Encrypted: i=1; AKwUvBwAC1J9avwEbqvA08cRc7nxUB5+w0pl1BW1nMFdgj//6h5ggDq9TCKBg+Ti7t4mPJzeIuwXHm9dZSsPWYc=@vger.kernel.org X-Gm-Message-State: AFuF++mhsk/KGR7fO1nJrojULQtuy/m0S8bo12oGQtrZlpySCMlFea6d qNSbggJ9HnzWHXjssR91mKLt7UVhkQxIsrsxRHgXkxRvCmPrflvckZrn X-Gm-Gg: AYBFou0nL1XnqTBxHD/EvQXz6BTsbjbTQusEy7YrcUK0s/5AkMhhYiqji0GyoBVTifk zRkPPAxjZHD/+6SXNo431lK7dJLu1kiEUPxzIUWE7kfQDAxP5JIWgxdZS5DHf565cuFHFayAqVj sqbZPLDySh4+A0XoY6qXogSg3qngu68PzdY88y/pyzCqoIyP7xx4iUwCCliFRb7DIU8lh2EaA3f C0YmMbNX7gLU/pntWEr1ZVnxYSyOQNMSrCjuR6rge6aSNAs+U22V8pdC+OOURKd6EgTl16MHeYq sNOeGBAcIW9GKMU1VFNZhKvMYTkTciRGDY10otsPUr9LUm9PXeDbxb05PMi8N2jLUUPp435ZwJa x0lozGYc/pbdyjPssB5ULs1MA/IzqxS6c19O9HyqFKFqiMRJ3IDmbLEZDKPVdvMcofuqrWnBkSB MlB/xiukgx7rZuzy7W0AvN2UsdfqiGnFoVZkR2Kz+djAlQ53XY/tEnO2OI+AQ+Yx5QE6A= X-Received: by 2002:a05:600c:3b19:b0:4a0:25d5:e853 with SMTP id 5b1f17b1804b1-4a02759a425mr42668275e9.35.1790939563758; Fri, 02 Oct 2026 04:12:43 -0700 (PDT) Received: from localhost ([2c0f:3d00:6be:8900:ce5e:9212:ea4b:f30]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4a03ff2280csm42446365e9.4.2026.10.02.04.12.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 02 Oct 2026 04:12:42 -0700 (PDT) Date: Fri, 2 Oct 2026 14:12:38 +0300 From: Dan Carpenter To: broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: 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 Subject: [PATCH v5 2/2] spi: spi-mem: Add Realtek SPI NOR flash controller driver Message-ID: <20260930065945.88008-3-eleanor.lin@realtek.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20260930065945.88008-1-eleanor.lin@realtek.com> Message-ID: <20261002111238.zmU8TY9cdPo1jRPt_Xg1el9kj_ozynuVbJQ2-farWEs@z> 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: drivers/spi/spi-rtk-nor.c:347 rtk_spi_supports_op() warn: '(-95)' is not bool On Wed, 30 Sep 2026 14:59:45 +0800 Yu-Chun Lin 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; This is a bool function. s/-EOPNOTSUPP/false/? regards, dan carpenter > + > + 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); > +}