realtek: 6.18: remove obsolete patches

Remove patches which are accepted upstream and were backported to
pre-6.18. Those are obsolete now.

Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/21181
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Jonas Jelonek 2026-01-21 23:17:04 +00:00 committed by Robert Marko
parent a7e63b4dac
commit c3d998f147
26 changed files with 0 additions and 3643 deletions

View File

@ -1,481 +0,0 @@
From 42d20a6a61b8fccbb57d80df1ccde7dd82d5bbd6 Mon Sep 17 00:00:00 2001
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
Date: Wed, 16 Oct 2024 11:54:34 +1300
Subject: [PATCH] spi: spi-mem: Add Realtek SPI-NAND controller
Add a driver for the SPI-NAND controller on the RTL9300 family of
devices.
The controller supports
* Serial/Dual/Quad data with
* PIO and DMA data read/write operation
* Configurable flash access timing
There is a separate ECC controller on the RTL9300 which isn't currently
supported (instead we rely on the on-die ECC supported by most SPI-NAND
chips).
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Link: https://patch.msgid.link/20241015225434.3970360-4-chris.packham@alliedtelesis.co.nz
Signed-off-by: Mark Brown <broonie@kernel.org>
---
MAINTAINERS | 6 +
drivers/spi/Kconfig | 11 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-realtek-rtl-snand.c | 405 ++++++++++++++++++++++++++++
4 files changed, 423 insertions(+)
create mode 100644 drivers/spi/spi-realtek-rtl-snand.c
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19507,6 +19507,12 @@ S: Maintained
F: Documentation/devicetree/bindings/net/dsa/realtek.yaml
F: drivers/net/dsa/realtek/*
+REALTEK SPI-NAND
+M: Chris Packham <chris.packham@alliedtelesis.co.nz>
+S: Maintained
+F: Documentation/devicetree/bindings/spi/realtek,rtl9301-snand.yaml
+F: drivers/spi/spi-realtek-rtl-snand.c
+
REALTEK WIRELESS DRIVER (rtlwifi family)
M: Ping-Ke Shih <pkshih@realtek.com>
L: linux-wireless@vger.kernel.org
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -843,6 +843,17 @@ config SPI_PXA2XX
config SPI_PXA2XX_PCI
def_tristate SPI_PXA2XX && PCI && COMMON_CLK
+config SPI_REALTEK_SNAND
+ tristate "Realtek SPI-NAND Flash Controller"
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
+ select REGMAP
+ help
+ This enables support for the SPI-NAND Flash controller on
+ Realtek SoCs.
+
+ This driver does not support generic SPI. The implementation
+ only supports the spi-mem interface.
+
config SPI_ROCKCHIP
tristate "Rockchip SPI controller driver"
depends on ARCH_ROCKCHIP || COMPILE_TEST
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -120,6 +120,7 @@ obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockc
obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.o
obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o
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_RZV2M_CSI) += spi-rzv2m-csi.o
--- /dev/null
+++ b/drivers/spi/spi-realtek-rtl-snand.c
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+#define SNAFCFR 0x00
+#define SNAFCFR_DMA_IE BIT(20)
+#define SNAFCCR 0x04
+#define SNAFWCMR 0x08
+#define SNAFRCMR 0x0c
+#define SNAFRDR 0x10
+#define SNAFWDR 0x14
+#define SNAFDTR 0x18
+#define SNAFDRSAR 0x1c
+#define SNAFDIR 0x20
+#define SNAFDIR_DMA_IP BIT(0)
+#define SNAFDLR 0x24
+#define SNAFSR 0x40
+#define SNAFSR_NFCOS BIT(3)
+#define SNAFSR_NFDRS BIT(2)
+#define SNAFSR_NFDWS BIT(1)
+
+#define CMR_LEN(len) ((len) - 1)
+#define CMR_WID(width) (((width) >> 1) << 28)
+
+struct rtl_snand {
+ struct device *dev;
+ struct regmap *regmap;
+ struct completion comp;
+};
+
+static irqreturn_t rtl_snand_irq(int irq, void *data)
+{
+ struct rtl_snand *snand = data;
+ u32 val = 0;
+
+ regmap_read(snand->regmap, SNAFSR, &val);
+ if (val & (SNAFSR_NFCOS | SNAFSR_NFDRS | SNAFSR_NFDWS))
+ return IRQ_NONE;
+
+ regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
+ complete(&snand->comp);
+
+ return IRQ_HANDLED;
+}
+
+static bool rtl_snand_supports_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ if (!spi_mem_default_supports_op(mem, op))
+ return false;
+ if (op->cmd.nbytes != 1 || op->cmd.buswidth != 1)
+ return false;
+ return true;
+}
+
+static void rtl_snand_set_cs(struct rtl_snand *snand, int cs, bool active)
+{
+ u32 val;
+
+ if (active)
+ val = ~(1 << (4 * cs));
+ else
+ val = ~0;
+
+ regmap_write(snand->regmap, SNAFCCR, val);
+}
+
+static int rtl_snand_wait_ready(struct rtl_snand *snand)
+{
+ u32 val;
+
+ return regmap_read_poll_timeout(snand->regmap, SNAFSR, val, !(val & SNAFSR_NFCOS),
+ 0, 2 * USEC_PER_MSEC);
+}
+
+static int rtl_snand_xfer_head(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
+{
+ int ret;
+ u32 val, len = 0;
+
+ rtl_snand_set_cs(snand, cs, true);
+
+ val = op->cmd.opcode << 24;
+ len = 1;
+ if (op->addr.nbytes && op->addr.buswidth == 1) {
+ val |= op->addr.val << ((3 - op->addr.nbytes) * 8);
+ len += op->addr.nbytes;
+ }
+
+ ret = rtl_snand_wait_ready(snand);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(len));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
+ if (ret)
+ return ret;
+
+ ret = rtl_snand_wait_ready(snand);
+ if (ret)
+ return ret;
+
+ if (op->addr.buswidth > 1) {
+ val = op->addr.val << ((3 - op->addr.nbytes) * 8);
+ len = op->addr.nbytes;
+
+ ret = regmap_write(snand->regmap, SNAFWCMR,
+ CMR_WID(op->addr.buswidth) | CMR_LEN(len));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
+ if (ret)
+ return ret;
+
+ ret = rtl_snand_wait_ready(snand);
+ if (ret)
+ return ret;
+ }
+
+ if (op->dummy.nbytes) {
+ val = 0;
+
+ ret = regmap_write(snand->regmap, SNAFWCMR,
+ CMR_WID(op->dummy.buswidth) | CMR_LEN(op->dummy.nbytes));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
+ if (ret)
+ return ret;
+
+ ret = rtl_snand_wait_ready(snand);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rtl_snand_xfer_tail(struct rtl_snand *snand, int cs)
+{
+ rtl_snand_set_cs(snand, cs, false);
+}
+
+static int rtl_snand_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
+{
+ unsigned int pos, nbytes;
+ int ret;
+ u32 val, len = 0;
+
+ ret = rtl_snand_xfer_head(snand, cs, op);
+ if (ret)
+ goto out_deselect;
+
+ if (op->data.dir == SPI_MEM_DATA_IN) {
+ pos = 0;
+ len = op->data.nbytes;
+
+ while (pos < len) {
+ nbytes = len - pos;
+ if (nbytes > 4)
+ nbytes = 4;
+
+ ret = rtl_snand_wait_ready(snand);
+ if (ret)
+ goto out_deselect;
+
+ ret = regmap_write(snand->regmap, SNAFRCMR,
+ CMR_WID(op->data.buswidth) | CMR_LEN(nbytes));
+ if (ret)
+ goto out_deselect;
+
+ ret = rtl_snand_wait_ready(snand);
+ if (ret)
+ goto out_deselect;
+
+ ret = regmap_read(snand->regmap, SNAFRDR, &val);
+ if (ret)
+ goto out_deselect;
+
+ memcpy(op->data.buf.in + pos, &val, nbytes);
+
+ pos += nbytes;
+ }
+ } else if (op->data.dir == SPI_MEM_DATA_OUT) {
+ pos = 0;
+ len = op->data.nbytes;
+
+ while (pos < len) {
+ nbytes = len - pos;
+ if (nbytes > 4)
+ nbytes = 4;
+
+ memcpy(&val, op->data.buf.out + pos, nbytes);
+
+ pos += nbytes;
+
+ ret = regmap_write(snand->regmap, SNAFWCMR, CMR_LEN(nbytes));
+ if (ret)
+ goto out_deselect;
+
+ ret = regmap_write(snand->regmap, SNAFWDR, val);
+ if (ret)
+ goto out_deselect;
+
+ ret = rtl_snand_wait_ready(snand);
+ if (ret)
+ goto out_deselect;
+ }
+ }
+
+out_deselect:
+ rtl_snand_xfer_tail(snand, cs);
+
+ if (ret)
+ dev_err(snand->dev, "transfer failed %d\n", ret);
+
+ return ret;
+}
+
+static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
+{
+ int ret;
+ dma_addr_t buf_dma;
+ enum dma_data_direction dir;
+ u32 trig;
+
+ ret = rtl_snand_xfer_head(snand, cs, op);
+ if (ret)
+ goto out_deselect;
+
+ if (op->data.dir == SPI_MEM_DATA_IN) {
+ dir = DMA_FROM_DEVICE;
+ trig = 0;
+ } else if (op->data.dir == SPI_MEM_DATA_OUT) {
+ dir = DMA_TO_DEVICE;
+ trig = 1;
+ } else {
+ ret = -EOPNOTSUPP;
+ goto out_deselect;
+ }
+
+ buf_dma = dma_map_single(snand->dev, op->data.buf.in, op->data.nbytes, dir);
+ ret = dma_mapping_error(snand->dev, buf_dma);
+ if (ret)
+ goto out_deselect;
+
+ ret = regmap_write(snand->regmap, SNAFDIR, SNAFDIR_DMA_IP);
+ if (ret)
+ goto out_unmap;
+
+ ret = regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, SNAFCFR_DMA_IE);
+ if (ret)
+ goto out_unmap;
+
+ reinit_completion(&snand->comp);
+
+ ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma);
+ if (ret)
+ goto out_disable_int;
+
+ ret = regmap_write(snand->regmap, SNAFDLR,
+ CMR_WID(op->data.buswidth) | (op->data.nbytes & 0xffff));
+ if (ret)
+ goto out_disable_int;
+
+ ret = regmap_write(snand->regmap, SNAFDTR, trig);
+ if (ret)
+ goto out_disable_int;
+
+ if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
+ ret = -ETIMEDOUT;
+
+ if (ret)
+ goto out_disable_int;
+
+out_disable_int:
+ regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0);
+out_unmap:
+ dma_unmap_single(snand->dev, buf_dma, op->data.nbytes, dir);
+out_deselect:
+ rtl_snand_xfer_tail(snand, cs);
+
+ if (ret)
+ dev_err(snand->dev, "transfer failed %d\n", ret);
+
+ return ret;
+}
+
+static bool rtl_snand_dma_op(const struct spi_mem_op *op)
+{
+ switch (op->data.dir) {
+ case SPI_MEM_DATA_IN:
+ case SPI_MEM_DATA_OUT:
+ return op->data.nbytes > 32;
+ default:
+ return false;
+ }
+}
+
+static int rtl_snand_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+ struct rtl_snand *snand = spi_controller_get_devdata(mem->spi->controller);
+ int cs = spi_get_chipselect(mem->spi, 0);
+
+ dev_dbg(snand->dev, "cs %d op cmd %02x %d:%d, dummy %d:%d, addr %08llx@%d:%d, data %d:%d\n",
+ cs, op->cmd.opcode,
+ op->cmd.buswidth, op->cmd.nbytes, op->dummy.buswidth,
+ op->dummy.nbytes, op->addr.val, op->addr.buswidth,
+ op->addr.nbytes, op->data.buswidth, op->data.nbytes);
+
+ if (rtl_snand_dma_op(op))
+ return rtl_snand_dma_xfer(snand, cs, op);
+ else
+ return rtl_snand_xfer(snand, cs, op);
+}
+
+static const struct spi_controller_mem_ops rtl_snand_mem_ops = {
+ .supports_op = rtl_snand_supports_op,
+ .exec_op = rtl_snand_exec_op,
+};
+
+static const struct of_device_id rtl_snand_match[] = {
+ { .compatible = "realtek,rtl9301-snand" },
+ { .compatible = "realtek,rtl9302b-snand" },
+ { .compatible = "realtek,rtl9302c-snand" },
+ { .compatible = "realtek,rtl9303-snand" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rtl_snand_match);
+
+static int rtl_snand_probe(struct platform_device *pdev)
+{
+ struct rtl_snand *snand;
+ struct device *dev = &pdev->dev;
+ struct spi_controller *ctrl;
+ void __iomem *base;
+ const struct regmap_config rc = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .cache_type = REGCACHE_NONE,
+ };
+ int irq, ret;
+
+ ctrl = devm_spi_alloc_host(dev, sizeof(*snand));
+ if (!ctrl)
+ return -ENOMEM;
+
+ snand = spi_controller_get_devdata(ctrl);
+ snand->dev = dev;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ snand->regmap = devm_regmap_init_mmio(dev, base, &rc);
+ if (IS_ERR(snand->regmap))
+ return PTR_ERR(snand->regmap);
+
+ init_completion(&snand->comp);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = dma_set_mask(snand->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set DMA mask\n");
+
+ ret = devm_request_irq(dev, irq, rtl_snand_irq, 0, "rtl-snand", snand);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request irq\n");
+
+ ctrl->num_chipselect = 2;
+ ctrl->mem_ops = &rtl_snand_mem_ops;
+ ctrl->bits_per_word_mask = SPI_BPW_MASK(8);
+ ctrl->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
+ device_set_node(&ctrl->dev, dev_fwnode(dev));
+
+ return devm_spi_register_controller(dev, ctrl);
+}
+
+static struct platform_driver rtl_snand_driver = {
+ .driver = {
+ .name = "realtek-rtl-snand",
+ .of_match_table = rtl_snand_match,
+ },
+ .probe = rtl_snand_probe,
+};
+module_platform_driver(rtl_snand_driver);
+
+MODULE_DESCRIPTION("Realtek SPI-NAND Flash Controller Driver");
+MODULE_LICENSE("GPL");

View File

@ -1,96 +0,0 @@
From 25d284715845a465a1a3693a09cf8b6ab8bd9caf Mon Sep 17 00:00:00 2001
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
Date: Thu, 31 Oct 2024 08:49:20 +1300
Subject: [PATCH] spi: spi-mem: rtl-snand: Correctly handle DMA transfers
The RTL9300 has some limitations on the maximum DMA transfers possible.
For reads this is 2080 bytes (520*4) for writes this is 520 bytes. Deal
with this by splitting transfers into appropriately sized parts.
Fixes: 42d20a6a61b8 ("spi: spi-mem: Add Realtek SPI-NAND controller")
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Link: https://patch.msgid.link/20241030194920.3202282-1-chris.packham@alliedtelesis.co.nz
Signed-off-by: Mark Brown <broonie@kernel.org>
---
drivers/spi/spi-realtek-rtl-snand.c | 46 +++++++++++++++++++----------
1 file changed, 30 insertions(+), 16 deletions(-)
--- a/drivers/spi/spi-realtek-rtl-snand.c
+++ b/drivers/spi/spi-realtek-rtl-snand.c
@@ -231,19 +231,22 @@ out_deselect:
static int rtl_snand_dma_xfer(struct rtl_snand *snand, int cs, const struct spi_mem_op *op)
{
+ unsigned int pos, nbytes;
int ret;
dma_addr_t buf_dma;
enum dma_data_direction dir;
- u32 trig;
+ u32 trig, len, maxlen;
ret = rtl_snand_xfer_head(snand, cs, op);
if (ret)
goto out_deselect;
if (op->data.dir == SPI_MEM_DATA_IN) {
+ maxlen = 2080;
dir = DMA_FROM_DEVICE;
trig = 0;
} else if (op->data.dir == SPI_MEM_DATA_OUT) {
+ maxlen = 520;
dir = DMA_TO_DEVICE;
trig = 1;
} else {
@@ -264,26 +267,37 @@ static int rtl_snand_dma_xfer(struct rtl
if (ret)
goto out_unmap;
- reinit_completion(&snand->comp);
+ pos = 0;
+ len = op->data.nbytes;
- ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma);
- if (ret)
- goto out_disable_int;
+ while (pos < len) {
+ nbytes = len - pos;
+ if (nbytes > maxlen)
+ nbytes = maxlen;
- ret = regmap_write(snand->regmap, SNAFDLR,
- CMR_WID(op->data.buswidth) | (op->data.nbytes & 0xffff));
- if (ret)
- goto out_disable_int;
+ reinit_completion(&snand->comp);
- ret = regmap_write(snand->regmap, SNAFDTR, trig);
- if (ret)
- goto out_disable_int;
+ ret = regmap_write(snand->regmap, SNAFDRSAR, buf_dma + pos);
+ if (ret)
+ goto out_disable_int;
- if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
- ret = -ETIMEDOUT;
+ pos += nbytes;
- if (ret)
- goto out_disable_int;
+ ret = regmap_write(snand->regmap, SNAFDLR,
+ CMR_WID(op->data.buswidth) | nbytes);
+ if (ret)
+ goto out_disable_int;
+
+ ret = regmap_write(snand->regmap, SNAFDTR, trig);
+ if (ret)
+ goto out_disable_int;
+
+ if (!wait_for_completion_timeout(&snand->comp, usecs_to_jiffies(20000)))
+ ret = -ETIMEDOUT;
+
+ if (ret)
+ goto out_disable_int;
+ }
out_disable_int:
regmap_update_bits(snand->regmap, SNAFCFR, SNAFCFR_DMA_IE, 0);

View File

@ -1,84 +0,0 @@
From c5eda0333076e031197816454998a918f1de0831 Mon Sep 17 00:00:00 2001
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
Date: Fri, 1 Nov 2024 09:03:46 +1300
Subject: [PATCH] dt-bindings: i2c: Add Realtek RTL I2C Controller
Add dt-schema for the I2C controller on the RTL9300 Ethernet switch
with integrated SoC.
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/realtek,rtl9301-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL I2C Controller
+
+maintainers:
+ - Chris Packham <chris.packham@alliedtelesis.co.nz>
+
+description:
+ The RTL9300 SoC has two I2C controllers. Each of these has an SCL line (which
+ if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
+ assigned to either I2C controller.
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - enum:
+ - realtek,rtl9302b-i2c
+ - realtek,rtl9302c-i2c
+ - realtek,rtl9303-i2c
+ - const: realtek,rtl9301-i2c
+ - const: realtek,rtl9301-i2c
+
+ reg:
+ description: Register offset and size this I2C controller.
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ '^i2c@[0-7]$':
+ $ref: /schemas/i2c/i2c-controller.yaml
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ description: The SDA pin associated with the I2C bus.
+ maxItems: 1
+
+ required:
+ - reg
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c@36c {
+ compatible = "realtek,rtl9301-i2c";
+ reg = <0x36c 0x14>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@2 {
+ reg = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };

View File

@ -1,493 +0,0 @@
From c366be720235301fdadf67e6f1ea6ff32669c074 Mon Sep 17 00:00:00 2001
From: Chris Packham <chris.packham@alliedtelesis.co.nz>
Date: Wed, 6 Nov 2024 13:18:35 +1300
Subject: [PATCH] i2c: Add driver for the RTL9300 I2C controller
Add support for the I2C controller on the RTL9300 SoC. There are two I2C
controllers in the RTL9300 that are part of the Ethernet switch register
block. Each of these controllers owns a SCL pin (GPIO8 for the fiorst
I2C controller, GPIO17 for the second). There are 8 possible SDA pins
(GPIO9-16) that can be assigned to either I2C controller. This
relationship is represented in the device tree with a child node for
each SDA line in use.
This is based on the openwrt implementation[1] but has been
significantly modified
[1] - https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-5.15/drivers/i2c/busses/i2c-rtl9300.c
Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20164,6 +20164,13 @@ S: Maintained
T: git https://github.com/pkshih/rtw.git
F: drivers/net/wireless/realtek/rtl8xxxu/
+RTL9300 I2C DRIVER (rtl9300-i2c)
+M: Chris Packham <chris.packham@alliedtelesis.co.nz>
+L: linux-i2c@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
+F: drivers/i2c/busses/i2c-rtl9300.c
+
RTRS TRANSPORT DRIVERS
M: Md. Haris Iqbal <haris.iqbal@ionos.com>
M: Jack Wang <jinpu.wang@ionos.com>
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1062,6 +1062,16 @@ config I2C_RK3X
This driver can also be built as a module. If so, the module will
be called i2c-rk3x.
+config I2C_RTL9300
+ tristate "Realtek RTL9300 I2C controller"
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
+ help
+ Say Y here to include support for the I2C controller in Realtek
+ RTL9300 SoCs.
+
+ This driver can also be built as a module. If so, the module will
+ be called i2c-rtl9300.
+
config I2C_RZV2M
tristate "Renesas RZ/V2M adapter"
depends on ARCH_RENESAS || COMPILE_TEST
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -103,6 +103,7 @@ obj-$(CONFIG_I2C_QCOM_GENI) += i2c-qcom-
obj-$(CONFIG_I2C_QUP) += i2c-qup.o
obj-$(CONFIG_I2C_RIIC) += i2c-riic.o
obj-$(CONFIG_I2C_RK3X) += i2c-rk3x.o
+obj-$(CONFIG_I2C_RTL9300) += i2c-rtl9300.o
obj-$(CONFIG_I2C_RZV2M) += i2c-rzv2m.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
--- /dev/null
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+enum rtl9300_bus_freq {
+ RTL9300_I2C_STD_FREQ,
+ RTL9300_I2C_FAST_FREQ,
+};
+
+struct rtl9300_i2c;
+
+struct rtl9300_i2c_chan {
+ struct i2c_adapter adap;
+ struct rtl9300_i2c *i2c;
+ enum rtl9300_bus_freq bus_freq;
+ u8 sda_pin;
+};
+
+#define RTL9300_I2C_MUX_NCHAN 8
+
+struct rtl9300_i2c {
+ struct regmap *regmap;
+ struct device *dev;
+ struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
+ u32 reg_base;
+ u8 sda_pin;
+ struct mutex lock;
+};
+
+#define RTL9300_I2C_MST_CTRL1 0x0
+#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS 8
+#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK GENMASK(31, 8)
+#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS 4
+#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK GENMASK(6, 4)
+#define RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL BIT(3)
+#define RTL9300_I2C_MST_CTRL1_RWOP BIT(2)
+#define RTL9300_I2C_MST_CTRL1_I2C_FAIL BIT(1)
+#define RTL9300_I2C_MST_CTRL1_I2C_TRIG BIT(0)
+#define RTL9300_I2C_MST_CTRL2 0x4
+#define RTL9300_I2C_MST_CTRL2_RD_MODE BIT(15)
+#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS 8
+#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK GENMASK(14, 8)
+#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS 4
+#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK GENMASK(7, 4)
+#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS 2
+#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK GENMASK(3, 2)
+#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS 0
+#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK GENMASK(1, 0)
+#define RTL9300_I2C_MST_DATA_WORD0 0x8
+#define RTL9300_I2C_MST_DATA_WORD1 0xc
+#define RTL9300_I2C_MST_DATA_WORD2 0x10
+#define RTL9300_I2C_MST_DATA_WORD3 0x14
+
+#define RTL9300_I2C_MST_GLB_CTRL 0x384
+
+static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
+{
+ u32 val, mask;
+ int ret;
+
+ val = len << RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS;
+ mask = RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK;
+
+ ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
+ if (ret)
+ return ret;
+
+ val = reg << RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS;
+ mask = RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK;
+
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
+}
+
+static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
+{
+ int ret;
+ u32 val, mask;
+
+ ret = regmap_update_bits(i2c->regmap, RTL9300_I2C_MST_GLB_CTRL, BIT(sda_pin), BIT(sda_pin));
+ if (ret)
+ return ret;
+
+ val = (sda_pin << RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS) |
+ RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
+ mask = RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK | RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
+
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
+}
+
+static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
+ u16 addr, u16 len)
+{
+ u32 val, mask;
+
+ val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
+ mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
+
+ val |= addr << RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS;
+ mask |= RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK;
+
+ val |= ((len - 1) & 0xf) << RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS;
+ mask |= RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK;
+
+ mask |= RTL9300_I2C_MST_CTRL2_RD_MODE;
+
+ return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
+}
+
+static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
+{
+ u32 vals[4] = {};
+ int i, ret;
+
+ if (len > 16)
+ return -EIO;
+
+ ret = regmap_bulk_read(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
+ vals, ARRAY_SIZE(vals));
+ if (ret)
+ return ret;
+
+ for (i = 0; i < len; i++) {
+ buf[i] = vals[i/4] & 0xff;
+ vals[i/4] >>= 8;
+ }
+
+ return 0;
+}
+
+static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
+{
+ u32 vals[4] = {};
+ int i;
+
+ if (len > 16)
+ return -EIO;
+
+ for (i = 0; i < len; i++) {
+ if (i % 4 == 0)
+ vals[i/4] = 0;
+ vals[i/4] <<= 8;
+ vals[i/4] |= buf[i];
+ }
+
+ return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
+ vals, ARRAY_SIZE(vals));
+}
+
+static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
+{
+ return regmap_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, data);
+}
+
+static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
+ int size, union i2c_smbus_data *data, int len)
+{
+ u32 val, mask;
+ int ret;
+
+ val = read_write == I2C_SMBUS_WRITE ? RTL9300_I2C_MST_CTRL1_RWOP : 0;
+ mask = RTL9300_I2C_MST_CTRL1_RWOP;
+
+ val |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
+ mask |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
+
+ ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
+ val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 2000);
+ if (ret)
+ return ret;
+
+ if (val & RTL9300_I2C_MST_CTRL1_I2C_FAIL)
+ return -EIO;
+
+ if (read_write == I2C_SMBUS_READ) {
+ if (size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA) {
+ ret = regmap_read(i2c->regmap,
+ i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
+ if (ret)
+ return ret;
+ data->byte = val & 0xff;
+ } else if (size == I2C_SMBUS_WORD_DATA) {
+ ret = regmap_read(i2c->regmap,
+ i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
+ if (ret)
+ return ret;
+ data->word = val & 0xffff;
+ } else {
+ ret = rtl9300_i2c_read(i2c, &data->block[0], len);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int rtl9300_i2c_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags,
+ char read_write, u8 command, int size,
+ union i2c_smbus_data *data)
+{
+ struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
+ struct rtl9300_i2c *i2c = chan->i2c;
+ int len = 0, ret;
+
+ mutex_lock(&i2c->lock);
+ if (chan->sda_pin != i2c->sda_pin) {
+ ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
+ if (ret)
+ goto out_unlock;
+ i2c->sda_pin = chan->sda_pin;
+ }
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
+ if (ret)
+ goto out_unlock;
+ ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
+ if (ret)
+ goto out_unlock;
+ break;
+
+ case I2C_SMBUS_BYTE:
+ if (read_write == I2C_SMBUS_WRITE) {
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
+ if (ret)
+ goto out_unlock;
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
+ if (ret)
+ goto out_unlock;
+ } else {
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
+ if (ret)
+ goto out_unlock;
+ ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
+ if (ret)
+ goto out_unlock;
+ }
+ break;
+
+ case I2C_SMBUS_BYTE_DATA:
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
+ if (ret)
+ goto out_unlock;
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
+ if (ret)
+ goto out_unlock;
+ if (read_write == I2C_SMBUS_WRITE) {
+ ret = rtl9300_i2c_writel(i2c, data->byte);
+ if (ret)
+ goto out_unlock;
+ }
+ break;
+
+ case I2C_SMBUS_WORD_DATA:
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
+ if (ret)
+ goto out_unlock;
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 2);
+ if (ret)
+ goto out_unlock;
+ if (read_write == I2C_SMBUS_WRITE) {
+ ret = rtl9300_i2c_writel(i2c, data->word);
+ if (ret)
+ goto out_unlock;
+ }
+ break;
+
+ case I2C_SMBUS_BLOCK_DATA:
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
+ if (ret)
+ goto out_unlock;
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
+ if (ret)
+ goto out_unlock;
+ if (read_write == I2C_SMBUS_WRITE) {
+ ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
+ if (ret)
+ goto out_unlock;
+ }
+ len = data->block[0];
+ break;
+
+ default:
+ dev_err(&adap->dev, "Unsupported transaction %d\n", size);
+ ret = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+
+ ret = rtl9300_i2c_execute_xfer(i2c, read_write, size, data, len);
+
+out_unlock:
+ mutex_unlock(&i2c->lock);
+
+ return ret;
+}
+
+static u32 rtl9300_i2c_func(struct i2c_adapter *a)
+{
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm rtl9300_i2c_algo = {
+ .smbus_xfer = rtl9300_i2c_smbus_xfer,
+ .functionality = rtl9300_i2c_func,
+};
+
+static struct i2c_adapter_quirks rtl9300_i2c_quirks = {
+ .flags = I2C_AQ_NO_CLK_STRETCH,
+ .max_read_len = 16,
+ .max_write_len = 16,
+};
+
+static int rtl9300_i2c_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtl9300_i2c *i2c;
+ u32 clock_freq, sda_pin;
+ int ret, i = 0;
+ struct fwnode_handle *child;
+
+ i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return -ENOMEM;
+
+ i2c->regmap = syscon_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(i2c->regmap))
+ return PTR_ERR(i2c->regmap);
+ i2c->dev = dev;
+
+ mutex_init(&i2c->lock);
+
+ ret = device_property_read_u32(dev, "reg", &i2c->reg_base);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, i2c);
+
+ if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
+ return dev_err_probe(dev, -EINVAL, "Too many channels\n");
+
+ device_for_each_child_node(dev, child) {
+ struct rtl9300_i2c_chan *chan = &i2c->chans[i];
+ struct i2c_adapter *adap = &chan->adap;
+
+ ret = fwnode_property_read_u32(child, "reg", &sda_pin);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_read_u32(child, "clock-frequency", &clock_freq);
+ if (ret)
+ clock_freq = I2C_MAX_STANDARD_MODE_FREQ;
+
+ switch (clock_freq) {
+ case I2C_MAX_STANDARD_MODE_FREQ:
+ chan->bus_freq = RTL9300_I2C_STD_FREQ;
+ break;
+
+ case I2C_MAX_FAST_MODE_FREQ:
+ chan->bus_freq = RTL9300_I2C_FAST_FREQ;
+ break;
+ default:
+ dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
+ sda_pin, clock_freq);
+ break;
+ }
+
+ chan->sda_pin = sda_pin;
+ chan->i2c = i2c;
+ adap = &i2c->chans[i].adap;
+ adap->owner = THIS_MODULE;
+ adap->algo = &rtl9300_i2c_algo;
+ adap->quirks = &rtl9300_i2c_quirks;
+ adap->retries = 3;
+ adap->dev.parent = dev;
+ i2c_set_adapdata(adap, chan);
+ adap->dev.of_node = to_of_node(child);
+ snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_pin);
+ i++;
+
+ ret = devm_i2c_add_adapter(dev, adap);
+ if (ret)
+ return ret;
+ }
+ i2c->sda_pin = 0xff;
+
+ return 0;
+}
+
+static const struct of_device_id i2c_rtl9300_dt_ids[] = {
+ { .compatible = "realtek,rtl9301-i2c" },
+ { .compatible = "realtek,rtl9302b-i2c" },
+ { .compatible = "realtek,rtl9302c-i2c" },
+ { .compatible = "realtek,rtl9303-i2c" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
+
+static struct platform_driver rtl9300_i2c_driver = {
+ .probe = rtl9300_i2c_probe,
+ .driver = {
+ .name = "i2c-rtl9300",
+ .of_match_table = i2c_rtl9300_dt_ids,
+ },
+};
+
+module_platform_driver(rtl9300_i2c_driver);
+
+MODULE_DESCRIPTION("RTL9300 I2C controller driver");
+MODULE_LICENSE("GPL");

View File

@ -1,27 +0,0 @@
From 5f05fc6e2218db7ecc52c60eb34b707fe69262c2 Mon Sep 17 00:00:00 2001
From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Date: Wed, 2 Jul 2025 08:15:31 +0200
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301: Fix missing 'reg'
constraint
Lists should have fixed amount if items, so add missing constraint to
the 'reg' property (only one address space entry).
Fixes: c5eda0333076 ("dt-bindings: i2c: Add Realtek RTL I2C Controller")
Cc: <stable@vger.kernel.org> # v6.13+
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250702061530.6940-2-krzysztof.kozlowski@linaro.org
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
@@ -26,7 +26,8 @@ properties:
- const: realtek,rtl9301-i2c
reg:
- description: Register offset and size this I2C controller.
+ items:
+ - description: Register offset and size this I2C controller.
"#address-cells":
const: 1

View File

@ -1,38 +0,0 @@
From 57f312b955938fc4663f430cb57a71f2414f601b Mon Sep 17 00:00:00 2001
From: Alex Guo <alexguo1023@gmail.com>
Date: Sun, 10 Aug 2025 20:05:13 +0200
Subject: [PATCH] i2c: rtl9300: Fix out-of-bounds bug in rtl9300_i2c_smbus_xfer
The data->block[0] variable comes from user. Without proper check,
the variable may be very large to cause an out-of-bounds bug.
Fix this bug by checking the value of data->block[0] first.
1. commit 39244cc75482 ("i2c: ismt: Fix an out-of-bounds bug in
ismt_access()")
2. commit 92fbb6d1296f ("i2c: xgene-slimpro: Fix out-of-bounds bug in
xgene_slimpro_i2c_xfer()")
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
Signed-off-by: Alex Guo <alexguo1023@gmail.com>
Cc: <stable@vger.kernel.org> # v6.13+
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-1-cd9dca0db722@narfation.org
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -281,6 +281,10 @@ static int rtl9300_i2c_smbus_xfer(struct
ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
if (ret)
goto out_unlock;
+ if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
if (ret)
goto out_unlock;

View File

@ -1,75 +0,0 @@
From d67b740b9edfa46310355e2b68050f79ebf05a4c Mon Sep 17 00:00:00 2001
From: Harshal Gohel <hg@simonwunderlich.de>
Date: Sun, 10 Aug 2025 20:05:14 +0200
Subject: [PATCH] i2c: rtl9300: Fix multi-byte I2C write
The RTL93xx I2C controller has 4 32 bit registers to store the bytes for
the upcoming I2C transmission. The first byte is stored in the
least-significant byte of the first register. And the last byte in the most
significant byte of the last register. A map of the transferred bytes to
their order in the registers is:
reg 0: 0x04_03_02_01
reg 1: 0x08_07_06_05
reg 2: 0x0c_0b_0a_09
reg 3: 0x10_0f_0e_0d
The i2c_read() function basically demonstrates how the hardware would pick
up bytes from this register set. But the i2c_write() function was just
pushing bytes one after another to the least significant byte of a register
AFTER shifting the last one to the next more significant byte position.
If you would then have tried to send a buffer with numbers 1-11 using
i2c_write(), you would have ended up with following register content:
reg 0: 0x01_02_03_04
reg 1: 0x05_06_07_08
reg 2: 0x00_09_0a_0b
reg 3: 0x00_00_00_00
On the wire, you would then have seen:
Sr Addr Wr [A] 04 A 03 A 02 A 01 A 08 A 07 A 06 A 05 A 0b A 0a A 09 A P
But the correct data transmission was expected to be
Sr Addr Wr [A] 01 A 02 A 03 A 04 A 05 A 06 A 07 A 08 A 09 A 0a A 0b A P
Because of this multi-byte ordering problem, only single byte i2c_write()
operations were executed correctly (on the wire).
By shifting the byte directly to the correct end position in the register,
it is possible to avoid this incorrect byte ordering and fix multi-byte
transmissions.
The second initialization (to 0) of vals was also be dropped because this
array is initialized to 0 on the stack by using `= {};`. This makes the
fix a lot more readable.
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
Signed-off-by: Harshal Gohel <hg@simonwunderlich.de>
Cc: <stable@vger.kernel.org> # v6.13+
Co-developed-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-2-cd9dca0db722@narfation.org
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -143,10 +143,10 @@ static int rtl9300_i2c_write(struct rtl9
return -EIO;
for (i = 0; i < len; i++) {
- if (i % 4 == 0)
- vals[i/4] = 0;
- vals[i/4] <<= 8;
- vals[i/4] |= buf[i];
+ unsigned int shift = (i % 4) * 8;
+ unsigned int reg = i / 4;
+
+ vals[reg] |= buf[i] << shift;
}
return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,

View File

@ -1,37 +0,0 @@
From ceee7776c010c5f09d30985c9e5223b363a6172a Mon Sep 17 00:00:00 2001
From: Sven Eckelmann <sven@narfation.org>
Date: Sun, 10 Aug 2025 20:05:15 +0200
Subject: [PATCH] i2c: rtl9300: Increase timeout for transfer polling
The timeout for transfers was only set to 2ms. Because of this relatively
low limit, 12-byte read operations to the frontend MCU of a RTL8239 POE PSE
chip cluster was consistently resulting in a timeout.
The original OpenWrt downstream driver [1] was not using any timeout limit
at all. This is also possible by setting the timeout_us parameter of
regmap_read_poll_timeout() to 0. But since the driver currently implements
the ETIMEDOUT error, it is more sensible to increase the timeout in such a
way that communication with the (quite common) Realtek I2C-connected POE
management solution is possible.
[1] https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-6.12/drivers/i2c/busses/i2c-rtl9300.c;h=c4d973195ef39dc56d6207e665d279745525fcac#l202
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Cc: <stable@vger.kernel.org> # v6.13+
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-3-cd9dca0db722@narfation.org
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -175,7 +175,7 @@ static int rtl9300_i2c_execute_xfer(stru
return ret;
ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
- val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 2000);
+ val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 100000);
if (ret)
return ret;

View File

@ -1,53 +0,0 @@
From 82b350dd8185ce790e61555c436f90b6501af23c Mon Sep 17 00:00:00 2001
From: Sven Eckelmann <sven@narfation.org>
Date: Sun, 10 Aug 2025 20:05:16 +0200
Subject: [PATCH] i2c: rtl9300: Add missing count byte for SMBus Block Ops
The expected on-wire format of an SMBus Block Write is
S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P
Everything starting from the Count byte is provided by the I2C subsystem in
the array data->block. But the driver was skipping the Count byte
(data->block[0]) when sending it to the RTL93xx I2C controller.
Only the actual data could be seen on the wire:
S Addr Wr [A] Comm [A] Data [A] Data [A] ... [A] Data [A] P
This wire format is not SMBus Block Write compatible but matches the format
of an I2C Block Write. Simply adding the count byte to the buffer for the
I2C controller is enough to fix the transmission.
This also affects read because the I2C controller must receive the count
byte + $count * data bytes.
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Cc: <stable@vger.kernel.org> # v6.13+
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250810-i2c-rtl9300-multi-byte-v5-4-cd9dca0db722@narfation.org
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -285,15 +285,15 @@ static int rtl9300_i2c_smbus_xfer(struct
ret = -EINVAL;
goto out_unlock;
}
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0] + 1);
if (ret)
goto out_unlock;
if (read_write == I2C_SMBUS_WRITE) {
- ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
+ ret = rtl9300_i2c_write(i2c, &data->block[0], data->block[0] + 1);
if (ret)
goto out_unlock;
}
- len = data->block[0];
+ len = data->block[0] + 1;
break;
default:

View File

@ -1,40 +0,0 @@
From cd6c956fbc13156bcbcca084b46a8380caebc2a8 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sun, 31 Aug 2025 10:04:46 +0000
Subject: [PATCH] i2c: rtl9300: fix channel number bound check
Fix the current check for number of channels (child nodes in the device
tree). Before, this was:
if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
RTL9300_I2C_MUX_NCHAN gives the maximum number of channels so checking
with '>=' isn't correct because it doesn't allow the last channel
number. Thus, fix it to:
if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
Issue occured on a TP-Link TL-ST1008F v2.0 device (8 SFP+ ports) and fix
is tested there.
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
Cc: stable@vger.kernel.org # v6.13+
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250831100457.3114-2-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -353,7 +353,7 @@ static int rtl9300_i2c_probe(struct plat
platform_set_drvdata(pdev, i2c);
- if (device_get_child_node_count(dev) >= RTL9300_I2C_MUX_NCHAN)
+ if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
device_for_each_child_node(dev, child) {

View File

@ -1,61 +0,0 @@
From 06418cb5a1a542a003fdb4ad8e76ea542d57cfba Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sun, 31 Aug 2025 10:04:47 +0000
Subject: [PATCH] i2c: rtl9300: ensure data length is within supported range
Add an explicit check for the xfer length to 'rtl9300_i2c_config_xfer'
to ensure the data length isn't within the supported range. In
particular a data length of 0 is not supported by the hardware and
causes unintended or destructive behaviour.
This limitation becomes obvious when looking at the register
documentation [1]. 4 bits are reserved for DATA_WIDTH and the value
of these 4 bits is used as N + 1, allowing a data length range of
1 <= len <= 16.
Affected by this is the SMBus Quick Operation which works with a data
length of 0. Passing 0 as the length causes an underflow of the value
due to:
(len - 1) & 0xf
and effectively specifying a transfer length of 16 via the registers.
This causes a 16-byte write operation instead of a Quick Write. For
example, on SFP modules without write-protected EEPROM this soft-bricks
them by overwriting some initial bytes.
For completeness, also add a quirk for the zero length.
[1] https://svanheule.net/realtek/longan/register/i2c_mst1_ctrl2
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
Cc: stable@vger.kernel.org # v6.13+
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250831100457.3114-3-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -99,6 +99,9 @@ static int rtl9300_i2c_config_xfer(struc
{
u32 val, mask;
+ if (len < 1 || len > 16)
+ return -EINVAL;
+
val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
@@ -323,7 +326,7 @@ static const struct i2c_algorithm rtl930
};
static struct i2c_adapter_quirks rtl9300_i2c_quirks = {
- .flags = I2C_AQ_NO_CLK_STRETCH,
+ .flags = I2C_AQ_NO_CLK_STRETCH | I2C_AQ_NO_ZERO_LEN,
.max_read_len = 16,
.max_write_len = 16,
};

View File

@ -1,77 +0,0 @@
From ede965fd555ac2536cf651893a998dbfd8e57b86 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sun, 31 Aug 2025 10:04:48 +0000
Subject: [PATCH] i2c: rtl9300: remove broken SMBus Quick operation support
Remove the SMBus Quick operation from this driver because it is not
natively supported by the hardware and is wrongly implemented in the
driver.
The I2C controllers in Realtek RTL9300 and RTL9310 are SMBus-compliant
but there doesn't seem to be native support for the SMBus Quick
operation. It is not explicitly mentioned in the documentation but
looking at the registers which configure an SMBus transaction, one can
see that the data length cannot be set to 0. This suggests that the
hardware doesn't allow any SMBus message without data bytes (except for
those it does on it's own, see SMBus Block Read).
The current implementation of SMBus Quick operation passes a length of
0 (which is actually invalid). Before the fix of a bug in a previous
commit, this led to a read operation of 16 bytes from any register (the
one of a former transaction or any other value.
This caused issues like soft-bricked SFP modules after a simple probe
with i2cdetect which uses Quick by default. Running this with SFP
modules whose EEPROM isn't write-protected, some of the initial bytes
are overwritten because a 16-byte write operation is executed instead of
a Quick Write. (This temporarily soft-bricked one of my DAC cables.)
Because SMBus Quick operation is obviously not supported on these
controllers (because a length of 0 cannot be set, even when no register
address is set), remove that instead of claiming there is support. There
also shouldn't be any kind of emulated 'Quick' which just does another
kind of operation in the background. Otherwise, specific issues occur
in case of a 'Quick' Write which actually writes unknown data to an
unknown register.
Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
Cc: stable@vger.kernel.org # v6.13+
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250831100457.3114-4-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -225,15 +225,6 @@ static int rtl9300_i2c_smbus_xfer(struct
}
switch (size) {
- case I2C_SMBUS_QUICK:
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
- if (ret)
- goto out_unlock;
- ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
- if (ret)
- goto out_unlock;
- break;
-
case I2C_SMBUS_BYTE:
if (read_write == I2C_SMBUS_WRITE) {
ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
@@ -315,9 +306,9 @@ out_unlock:
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
{
- return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
- I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
- I2C_FUNC_SMBUS_BLOCK_DATA;
+ return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK;
}
static const struct i2c_algorithm rtl9300_i2c_algo = {

View File

@ -1,26 +0,0 @@
From 095530512152e6811278de9c30f170f0ac9705eb Mon Sep 17 00:00:00 2001
From: Sven Eckelmann <sven@narfation.org>
Date: Sat, 27 Sep 2025 11:52:16 +0200
Subject: [PATCH] i2c: rtl9300: Drop unsupported I2C_FUNC_SMBUS_I2C_BLOCK
While applying the patch for commit ede965fd555a ("i2c: rtl9300: remove
broken SMBus Quick operation support"), a conflict was incorrectly solved
by adding the I2C_FUNC_SMBUS_I2C_BLOCK feature flag. But the code to handle
I2C_SMBUS_I2C_BLOCK_DATA requests will be added by a separate commit.
Fixes: ede965fd555a ("i2c: rtl9300: remove broken SMBus Quick operation support")
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -307,8 +307,7 @@ out_unlock:
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
{
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
- I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
- I2C_FUNC_SMBUS_I2C_BLOCK;
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA;
}
static const struct i2c_algorithm rtl9300_i2c_algo = {

View File

@ -1,119 +0,0 @@
From 415216ae3196e67bdb9515519f219d553bd38d3a Mon Sep 17 00:00:00 2001
From: Harshal Gohel <hg@simonwunderlich.de>
Date: Sat, 27 Sep 2025 11:52:17 +0200
Subject: [PATCH] i2c: rtl9300: Implement I2C block read and write
It was noticed that the original implementation of SMBus Block Write in the
driver was actually an I2C Block Write. Both differ only in the Count byte
before the actual data:
S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P
The I2C Block Write is just skipping this Count byte and starts directly
with the data:
S Addr Wr [A] Comm [A] Data [A] Data [A] ... [A] Data [A] P
The I2C controller of RTL93xx doesn't handle this Count byte special and it
is simply another one of (16 possible) data bytes. Adding support for the
I2C Block Write therefore only requires skipping the count byte (0) in
data->block.
It is similar for reads. The SMBUS Block read is having a Count byte before
the data:
S Addr Wr [A] Comm [A]
Sr Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P
And the I2C Block Read is directly starting with the actual data:
S Addr Wr [A] Comm [A]
Sr Addr Rd [A] [Data] A [Data] A ... A [Data] NA P
The I2C controller is also not handling this byte in a special way. It
simply provides every byte after the Rd marker + Ack as part of the 16 byte
receive buffer (registers). The content of this buffer just has to be
copied to the right position in the receive data->block.
Signed-off-by: Harshal Gohel <hg@simonwunderlich.de>
Co-developed-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Reviewed-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Jonas Jelonek <jelonek.jonas@gmail.com>
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -186,22 +186,32 @@ static int rtl9300_i2c_execute_xfer(stru
return -EIO;
if (read_write == I2C_SMBUS_READ) {
- if (size == I2C_SMBUS_BYTE || size == I2C_SMBUS_BYTE_DATA) {
+ switch (size) {
+ case I2C_SMBUS_BYTE:
+ case I2C_SMBUS_BYTE_DATA:
ret = regmap_read(i2c->regmap,
i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
if (ret)
return ret;
data->byte = val & 0xff;
- } else if (size == I2C_SMBUS_WORD_DATA) {
+ break;
+ case I2C_SMBUS_WORD_DATA:
ret = regmap_read(i2c->regmap,
i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
if (ret)
return ret;
data->word = val & 0xffff;
- } else {
+ break;
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ ret = rtl9300_i2c_read(i2c, &data->block[1], len);
+ if (ret)
+ return ret;
+ break;
+ default:
ret = rtl9300_i2c_read(i2c, &data->block[0], len);
if (ret)
return ret;
+ break;
}
}
@@ -290,6 +300,25 @@ static int rtl9300_i2c_smbus_xfer(struct
len = data->block[0] + 1;
break;
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
+ if (ret)
+ goto out_unlock;
+ if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+ ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
+ if (ret)
+ goto out_unlock;
+ if (read_write == I2C_SMBUS_WRITE) {
+ ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
+ if (ret)
+ goto out_unlock;
+ }
+ len = data->block[0];
+ break;
+
default:
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
ret = -EOPNOTSUPP;
@@ -307,7 +336,8 @@ out_unlock:
static u32 rtl9300_i2c_func(struct i2c_adapter *a)
{
return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
- I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA;
+ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK;
}
static const struct i2c_algorithm rtl9300_i2c_algo = {

View File

@ -1,354 +0,0 @@
From 5a6ecb27435ef7a67d7bec4543f0c6303f34e8a6 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:23 +0000
Subject: [PATCH] i2c: rtl9300: use regmap fields and API for registers
Adapt the RTL9300 I2C controller driver to use more of the regmap
API, especially make use of reg_field and regmap_field instead of macros
to represent registers. Most register operations are performed through
regmap_field_* API then.
Handle SCL selection using separate chip-specific functions since this
is already known to differ between the Realtek SoC families in such a
way that this cannot be properly handled using just a different
reg_field.
This makes it easier to add support for newer generations or to handle
differences between specific revisions within a series. Just by
defining a separate driver data structure with the corresponding
register field definitions and linking it to a new compatible.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-2-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -23,97 +23,117 @@ struct rtl9300_i2c_chan {
u8 sda_pin;
};
+enum rtl9300_i2c_reg_scope {
+ REG_SCOPE_GLOBAL,
+ REG_SCOPE_MASTER,
+};
+
+struct rtl9300_i2c_reg_field {
+ struct reg_field field;
+ enum rtl9300_i2c_reg_scope scope;
+};
+
+enum rtl9300_i2c_reg_fields {
+ F_DATA_WIDTH = 0,
+ F_DEV_ADDR,
+ F_I2C_FAIL,
+ F_I2C_TRIG,
+ F_MEM_ADDR,
+ F_MEM_ADDR_WIDTH,
+ F_RD_MODE,
+ F_RWOP,
+ F_SCL_FREQ,
+ F_SCL_SEL,
+ F_SDA_OUT_SEL,
+ F_SDA_SEL,
+
+ /* keep last */
+ F_NUM_FIELDS
+};
+
+struct rtl9300_i2c_drv_data {
+ struct rtl9300_i2c_reg_field field_desc[F_NUM_FIELDS];
+ int (*select_scl)(struct rtl9300_i2c *i2c, u8 scl);
+ u32 data_reg;
+ u8 max_nchan;
+};
+
#define RTL9300_I2C_MUX_NCHAN 8
struct rtl9300_i2c {
struct regmap *regmap;
struct device *dev;
struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
+ struct regmap_field *fields[F_NUM_FIELDS];
u32 reg_base;
+ u32 data_reg;
u8 sda_pin;
struct mutex lock;
};
#define RTL9300_I2C_MST_CTRL1 0x0
-#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS 8
-#define RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK GENMASK(31, 8)
-#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS 4
-#define RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK GENMASK(6, 4)
-#define RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL BIT(3)
-#define RTL9300_I2C_MST_CTRL1_RWOP BIT(2)
-#define RTL9300_I2C_MST_CTRL1_I2C_FAIL BIT(1)
-#define RTL9300_I2C_MST_CTRL1_I2C_TRIG BIT(0)
#define RTL9300_I2C_MST_CTRL2 0x4
-#define RTL9300_I2C_MST_CTRL2_RD_MODE BIT(15)
-#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS 8
-#define RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK GENMASK(14, 8)
-#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS 4
-#define RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK GENMASK(7, 4)
-#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS 2
-#define RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK GENMASK(3, 2)
-#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS 0
-#define RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK GENMASK(1, 0)
#define RTL9300_I2C_MST_DATA_WORD0 0x8
#define RTL9300_I2C_MST_DATA_WORD1 0xc
#define RTL9300_I2C_MST_DATA_WORD2 0x10
#define RTL9300_I2C_MST_DATA_WORD3 0x14
-
#define RTL9300_I2C_MST_GLB_CTRL 0x384
static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
{
- u32 val, mask;
int ret;
- val = len << RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_OFS;
- mask = RTL9300_I2C_MST_CTRL2_MEM_ADDR_WIDTH_MASK;
-
- ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
+ ret = regmap_field_write(i2c->fields[F_MEM_ADDR_WIDTH], len);
if (ret)
return ret;
- val = reg << RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS;
- mask = RTL9300_I2C_MST_CTRL1_MEM_ADDR_MASK;
+ return regmap_field_write(i2c->fields[F_MEM_ADDR], reg);
+}
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
+static int rtl9300_i2c_select_scl(struct rtl9300_i2c *i2c, u8 scl)
+{
+ return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
}
static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
{
+ struct rtl9300_i2c_drv_data *drv_data;
int ret;
- u32 val, mask;
- ret = regmap_update_bits(i2c->regmap, RTL9300_I2C_MST_GLB_CTRL, BIT(sda_pin), BIT(sda_pin));
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_pin), BIT(sda_pin));
if (ret)
return ret;
- val = (sda_pin << RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_OFS) |
- RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
- mask = RTL9300_I2C_MST_CTRL1_SDA_OUT_SEL_MASK | RTL9300_I2C_MST_CTRL1_GPIO_SCL_SEL;
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_pin);
+ if (ret)
+ return ret;
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
+ return drv_data->select_scl(i2c, 0);
}
static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
u16 addr, u16 len)
{
- u32 val, mask;
+ int ret;
if (len < 1 || len > 16)
return -EINVAL;
- val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
- mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
-
- val |= addr << RTL9300_I2C_MST_CTRL2_DEV_ADDR_OFS;
- mask |= RTL9300_I2C_MST_CTRL2_DEV_ADDR_MASK;
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
+ if (ret)
+ return ret;
- val |= ((len - 1) & 0xf) << RTL9300_I2C_MST_CTRL2_DATA_WIDTH_OFS;
- mask |= RTL9300_I2C_MST_CTRL2_DATA_WIDTH_MASK;
+ ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
+ if (ret)
+ return ret;
- mask |= RTL9300_I2C_MST_CTRL2_RD_MODE;
+ ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
+ if (ret)
+ return ret;
- return regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL2, mask, val);
+ return regmap_field_write(i2c->fields[F_RD_MODE], 0);
}
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
@@ -124,8 +144,7 @@ static int rtl9300_i2c_read(struct rtl93
if (len > 16)
return -EIO;
- ret = regmap_bulk_read(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
- vals, ARRAY_SIZE(vals));
+ ret = regmap_bulk_read(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
if (ret)
return ret;
@@ -152,52 +171,49 @@ static int rtl9300_i2c_write(struct rtl9
vals[reg] |= buf[i] << shift;
}
- return regmap_bulk_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0,
- vals, ARRAY_SIZE(vals));
+ return regmap_bulk_write(i2c->regmap, i2c->data_reg, vals, ARRAY_SIZE(vals));
}
static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
{
- return regmap_write(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, data);
+ return regmap_write(i2c->regmap, i2c->data_reg, data);
}
static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
int size, union i2c_smbus_data *data, int len)
{
- u32 val, mask;
+ u32 val;
int ret;
- val = read_write == I2C_SMBUS_WRITE ? RTL9300_I2C_MST_CTRL1_RWOP : 0;
- mask = RTL9300_I2C_MST_CTRL1_RWOP;
-
- val |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
- mask |= RTL9300_I2C_MST_CTRL1_I2C_TRIG;
+ ret = regmap_field_write(i2c->fields[F_RWOP], read_write == I2C_SMBUS_WRITE);
+ if (ret)
+ return ret;
- ret = regmap_update_bits(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1, mask, val);
+ ret = regmap_field_write(i2c->fields[F_I2C_TRIG], 1);
if (ret)
return ret;
- ret = regmap_read_poll_timeout(i2c->regmap, i2c->reg_base + RTL9300_I2C_MST_CTRL1,
- val, !(val & RTL9300_I2C_MST_CTRL1_I2C_TRIG), 100, 100000);
+ ret = regmap_field_read_poll_timeout(i2c->fields[F_I2C_TRIG], val, !val, 100, 100000);
if (ret)
return ret;
- if (val & RTL9300_I2C_MST_CTRL1_I2C_FAIL)
+ ret = regmap_field_read(i2c->fields[F_I2C_FAIL], &val);
+ if (ret)
+ return ret;
+ if (val)
return -EIO;
if (read_write == I2C_SMBUS_READ) {
switch (size) {
case I2C_SMBUS_BYTE:
case I2C_SMBUS_BYTE_DATA:
- ret = regmap_read(i2c->regmap,
- i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
+ ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
if (ret)
return ret;
data->byte = val & 0xff;
break;
case I2C_SMBUS_WORD_DATA:
- ret = regmap_read(i2c->regmap,
- i2c->reg_base + RTL9300_I2C_MST_DATA_WORD0, &val);
+ ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
if (ret)
return ret;
data->word = val & 0xffff;
@@ -355,9 +371,11 @@ static int rtl9300_i2c_probe(struct plat
{
struct device *dev = &pdev->dev;
struct rtl9300_i2c *i2c;
+ struct fwnode_handle *child;
+ struct rtl9300_i2c_drv_data *drv_data;
+ struct reg_field fields[F_NUM_FIELDS];
u32 clock_freq, sda_pin;
int ret, i = 0;
- struct fwnode_handle *child;
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c)
@@ -376,9 +394,22 @@ static int rtl9300_i2c_probe(struct plat
platform_set_drvdata(pdev, i2c);
- if (device_get_child_node_count(dev) > RTL9300_I2C_MUX_NCHAN)
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+ if (device_get_child_node_count(dev) > drv_data->max_nchan)
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
+ i2c->data_reg = i2c->reg_base + drv_data->data_reg;
+ for (i = 0; i < F_NUM_FIELDS; i++) {
+ fields[i] = drv_data->field_desc[i].field;
+ if (drv_data->field_desc[i].scope == REG_SCOPE_MASTER)
+ fields[i].reg += i2c->reg_base;
+ }
+ ret = devm_regmap_field_bulk_alloc(dev, i2c->regmap, i2c->fields,
+ fields, F_NUM_FIELDS);
+ if (ret)
+ return ret;
+
+ i = 0;
device_for_each_child_node(dev, child) {
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
struct i2c_adapter *adap = &chan->adap;
@@ -395,7 +426,6 @@ static int rtl9300_i2c_probe(struct plat
case I2C_MAX_STANDARD_MODE_FREQ:
chan->bus_freq = RTL9300_I2C_STD_FREQ;
break;
-
case I2C_MAX_FAST_MODE_FREQ:
chan->bus_freq = RTL9300_I2C_FAST_FREQ;
break;
@@ -427,11 +457,37 @@ static int rtl9300_i2c_probe(struct plat
return 0;
}
+#define GLB_REG_FIELD(reg, msb, lsb) \
+ { .field = REG_FIELD(reg, msb, lsb), .scope = REG_SCOPE_GLOBAL }
+#define MST_REG_FIELD(reg, msb, lsb) \
+ { .field = REG_FIELD(reg, msb, lsb), .scope = REG_SCOPE_MASTER }
+
+static const struct rtl9300_i2c_drv_data rtl9300_i2c_drv_data = {
+ .field_desc = {
+ [F_MEM_ADDR] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 8, 31),
+ [F_SDA_OUT_SEL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 4, 6),
+ [F_SCL_SEL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 3, 3),
+ [F_RWOP] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 2, 2),
+ [F_I2C_FAIL] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 1, 1),
+ [F_I2C_TRIG] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL1, 0, 0),
+ [F_RD_MODE] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 15, 15),
+ [F_DEV_ADDR] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 8, 14),
+ [F_DATA_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 4, 7),
+ [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 2, 3),
+ [F_SCL_FREQ] = MST_REG_FIELD(RTL9300_I2C_MST_CTRL2, 0, 1),
+ [F_SDA_SEL] = GLB_REG_FIELD(RTL9300_I2C_MST_GLB_CTRL, 0, 7),
+ },
+ .select_scl = rtl9300_i2c_select_scl,
+ .data_reg = RTL9300_I2C_MST_DATA_WORD0,
+ .max_nchan = RTL9300_I2C_MUX_NCHAN,
+};
+
+
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
- { .compatible = "realtek,rtl9301-i2c" },
- { .compatible = "realtek,rtl9302b-i2c" },
- { .compatible = "realtek,rtl9302c-i2c" },
- { .compatible = "realtek,rtl9303-i2c" },
+ { .compatible = "realtek,rtl9301-i2c", .data = (void *) &rtl9300_i2c_drv_data },
+ { .compatible = "realtek,rtl9302b-i2c", .data = (void *) &rtl9300_i2c_drv_data },
+ { .compatible = "realtek,rtl9302c-i2c", .data = (void *) &rtl9300_i2c_drv_data },
+ { .compatible = "realtek,rtl9303-i2c", .data = (void *) &rtl9300_i2c_drv_data },
{}
};
MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);

View File

@ -1,51 +0,0 @@
From 80f3e37d5e734bbfe891592bb669ceb5e8b314dc Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:24 +0000
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301-i2c: fix wording and typos
Fix wording of binding description to use plural because there is not
only a single RTL9300 SoC. RTL9300 describes a whole family of Realtek
SoCs.
Add missing word 'of' in description of reg property.
Change 'SDA pin' to 'SDA line number' because the property must contain
the SDA (channel) number ranging from 0-7 instead of a real pin number.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-3-jelonek.jonas@gmail.com
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
@@ -10,7 +10,7 @@ maintainers:
- Chris Packham <chris.packham@alliedtelesis.co.nz>
description:
- The RTL9300 SoC has two I2C controllers. Each of these has an SCL line (which
+ RTL9300 SoCs have two I2C controllers. Each of these has an SCL line (which
if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
assigned to either I2C controller.
@@ -27,7 +27,7 @@ properties:
reg:
items:
- - description: Register offset and size this I2C controller.
+ - description: Register offset and size of this I2C controller.
"#address-cells":
const: 1
@@ -42,7 +42,7 @@ patternProperties:
properties:
reg:
- description: The SDA pin associated with the I2C bus.
+ description: The SDA line number associated with the I2C bus.
maxItems: 1
required:

View File

@ -1,118 +0,0 @@
From 8ff3819d7edcd56e4c533b9391a156cd607048fa Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:25 +0000
Subject: [PATCH] i2c: rtl9300: rename internal sda_pin to sda_num
Rename the internally used 'sda_pin' to 'sda_num' to make it clear that
this is NOT the actual pin number of the GPIO pin but rather the logical
SDA channel number. Although the alternate function SDA_Y is sometimes
given with the GPIO number, this is not always the case. Thus, avoid any
confusion or misconfiguration by giving the variable the correct name.
This follows the description change in the devicetree bindings.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-4-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -20,7 +20,7 @@ struct rtl9300_i2c_chan {
struct i2c_adapter adap;
struct rtl9300_i2c *i2c;
enum rtl9300_bus_freq bus_freq;
- u8 sda_pin;
+ u8 sda_num;
};
enum rtl9300_i2c_reg_scope {
@@ -67,7 +67,7 @@ struct rtl9300_i2c {
struct regmap_field *fields[F_NUM_FIELDS];
u32 reg_base;
u32 data_reg;
- u8 sda_pin;
+ u8 sda_num;
struct mutex lock;
};
@@ -102,11 +102,11 @@ static int rtl9300_i2c_config_io(struct
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_pin), BIT(sda_pin));
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_num), BIT(sda_num));
if (ret)
return ret;
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_pin);
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_num);
if (ret)
return ret;
@@ -243,11 +243,11 @@ static int rtl9300_i2c_smbus_xfer(struct
int len = 0, ret;
mutex_lock(&i2c->lock);
- if (chan->sda_pin != i2c->sda_pin) {
+ if (chan->sda_num != i2c->sda_num) {
ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
if (ret)
goto out_unlock;
- i2c->sda_pin = chan->sda_pin;
+ i2c->sda_num = chan->sda_num;
}
switch (size) {
@@ -374,7 +374,7 @@ static int rtl9300_i2c_probe(struct plat
struct fwnode_handle *child;
struct rtl9300_i2c_drv_data *drv_data;
struct reg_field fields[F_NUM_FIELDS];
- u32 clock_freq, sda_pin;
+ u32 clock_freq, sda_num;
int ret, i = 0;
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
@@ -414,7 +414,7 @@ static int rtl9300_i2c_probe(struct plat
struct rtl9300_i2c_chan *chan = &i2c->chans[i];
struct i2c_adapter *adap = &chan->adap;
- ret = fwnode_property_read_u32(child, "reg", &sda_pin);
+ ret = fwnode_property_read_u32(child, "reg", &sda_num);
if (ret)
return ret;
@@ -431,11 +431,11 @@ static int rtl9300_i2c_probe(struct plat
break;
default:
dev_warn(i2c->dev, "SDA%d clock-frequency %d not supported using default\n",
- sda_pin, clock_freq);
+ sda_num, clock_freq);
break;
}
- chan->sda_pin = sda_pin;
+ chan->sda_num = sda_num;
chan->i2c = i2c;
adap = &i2c->chans[i].adap;
adap->owner = THIS_MODULE;
@@ -445,14 +445,14 @@ static int rtl9300_i2c_probe(struct plat
adap->dev.parent = dev;
i2c_set_adapdata(adap, chan);
adap->dev.of_node = to_of_node(child);
- snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_pin);
+ snprintf(adap->name, sizeof(adap->name), "%s SDA%d\n", dev_name(dev), sda_num);
i++;
ret = devm_i2c_add_adapter(dev, adap);
if (ret)
return ret;
}
- i2c->sda_pin = 0xff;
+ i2c->sda_num = 0xff;
return 0;
}

View File

@ -1,67 +0,0 @@
From 6b0549abc8582a81425f89a436def8e28d8d7dce Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:26 +0000
Subject: [PATCH] i2c: rtl9300: move setting SCL frequency to config_io
Move the register operation to set the SCL frequency to the
rtl9300_i2c_config_io function instead of the rtl9300_i2c_config_xfer
function. This rather belongs there next to selecting the current SDA
output line.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-5-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -95,18 +95,23 @@ static int rtl9300_i2c_select_scl(struct
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
}
-static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, u8 sda_pin)
+static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
{
struct rtl9300_i2c_drv_data *drv_data;
int ret;
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(sda_num), BIT(sda_num));
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
+ BIT(chan->sda_num));
if (ret)
return ret;
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], sda_num);
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
if (ret)
return ret;
@@ -121,10 +126,6 @@ static int rtl9300_i2c_config_xfer(struc
if (len < 1 || len > 16)
return -EINVAL;
- ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
- if (ret)
- return ret;
-
ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
if (ret)
return ret;
@@ -244,7 +245,7 @@ static int rtl9300_i2c_smbus_xfer(struct
mutex_lock(&i2c->lock);
if (chan->sda_num != i2c->sda_num) {
- ret = rtl9300_i2c_config_io(i2c, chan->sda_pin);
+ ret = rtl9300_i2c_config_io(i2c, chan);
if (ret)
goto out_unlock;
i2c->sda_num = chan->sda_num;

View File

@ -1,49 +0,0 @@
From 0cb24186d0ebd7dd12c070fed9d782bf7c6dfb1e Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:27 +0000
Subject: [PATCH] i2c: rtl9300: do not set read mode on every transfer
Move the operation to set the read mode from config_xfer to probe.
The I2C controller of RTL9300 and RTL9310 support a legacy message mode
for READs with 'Read Address Data' instead of the standard format 'Write
Address ; Read Data'. There is no way to pass that via smbus_xfer, thus
there is no point in supported this in the driver and moreover no point
in setting this on every transaction. Setting this once in the probe
call is sufficient.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-6-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -130,11 +130,7 @@ static int rtl9300_i2c_config_xfer(struc
if (ret)
return ret;
- ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
- if (ret)
- return ret;
-
- return regmap_field_write(i2c->fields[F_RD_MODE], 0);
+ return regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
}
static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
@@ -455,6 +451,11 @@ static int rtl9300_i2c_probe(struct plat
}
i2c->sda_num = 0xff;
+ /* only use standard read format */
+ ret = regmap_field_write(i2c->fields[F_RD_MODE], 0);
+ if (ret)
+ return ret;
+
return 0;
}

View File

@ -1,382 +0,0 @@
From 447dd46f95014eb4ea94f6164963bf23ce05b927 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:28 +0000
Subject: [PATCH] i2c: rtl9300: separate xfer configuration and execution
So far, the rtl9300_i2c_smbus_xfer code is quite a mess with function
calls distributed over the whole function setting different values in
different cases. Calls to rtl9300_i2c_config_xfer and
rtl9300_i2c_reg_addr_set are used in every case-block with varying
values whose meaning is not instantly obvious. In some cases, there are
additional calls within these case-blocks doing more things.
This is in general a bad design and especially really bad for
readability and maintainability because it distributes changes or
issues to multiple locations due to the same function being called with
different hardcoded values in different places.
To have a good structure, setting different parameters based on the
desired operation should not be interleaved with applying these
parameters to the hardware registers. Or in different words, the
parameter site should be mixed with the call site.
Thus, separate configuration and execution of an SMBus xfer within
rtl9300_i2c_smbus_xfer to improve readability and maintainability. Add a
new 'struct rtl9300_i2c_xfer' to carry the required parameters for an
xfer which are configured based on the input parameters within a single
switch-case block, without having any function calls within this block.
The function calls to actually apply these values to the hardware
registers then appear below in a single place and just operate on the
passed instance of 'struct rtl9300_i2c_xfer'. These are
'rtl9300_i2c_prepare_xfer' which combines applying all parameters of the
xfer to the corresponding register, and 'rtl9300_i2c_do_xfer' which
actually executes the xfer and does post-processing if needed.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-7-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -8,6 +8,7 @@
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/unaligned.h>
enum rtl9300_bus_freq {
RTL9300_I2C_STD_FREQ,
@@ -71,6 +72,22 @@ struct rtl9300_i2c {
struct mutex lock;
};
+enum rtl9300_i2c_xfer_type {
+ RTL9300_I2C_XFER_BYTE,
+ RTL9300_I2C_XFER_WORD,
+ RTL9300_I2C_XFER_BLOCK,
+};
+
+struct rtl9300_i2c_xfer {
+ enum rtl9300_i2c_xfer_type type;
+ u16 dev_addr;
+ u8 reg_addr;
+ u8 reg_addr_len;
+ u8 *data;
+ u8 data_len;
+ bool write;
+};
+
#define RTL9300_I2C_MST_CTRL1 0x0
#define RTL9300_I2C_MST_CTRL2 0x4
#define RTL9300_I2C_MST_DATA_WORD0 0x8
@@ -95,45 +112,37 @@ static int rtl9300_i2c_select_scl(struct
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
}
-static int rtl9300_i2c_config_io(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
+static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
{
struct rtl9300_i2c_drv_data *drv_data;
int ret;
- drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+ if (i2c->sda_num == chan->sda_num)
+ return 0;
- ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
- BIT(chan->sda_num));
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
if (ret)
return ret;
- ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+ ret = drv_data->select_scl(i2c, 0);
if (ret)
return ret;
- ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
+ ret = regmap_field_update_bits(i2c->fields[F_SDA_SEL], BIT(chan->sda_num),
+ BIT(chan->sda_num));
if (ret)
return ret;
- return drv_data->select_scl(i2c, 0);
-}
-
-static int rtl9300_i2c_config_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan,
- u16 addr, u16 len)
-{
- int ret;
-
- if (len < 1 || len > 16)
- return -EINVAL;
-
- ret = regmap_field_write(i2c->fields[F_DEV_ADDR], addr);
+ ret = regmap_field_write(i2c->fields[F_SDA_OUT_SEL], chan->sda_num);
if (ret)
return ret;
- return regmap_field_write(i2c->fields[F_DATA_WIDTH], (len - 1) & 0xf);
+ i2c->sda_num = chan->sda_num;
+ return 0;
}
-static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, int len)
+static int rtl9300_i2c_read(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
{
u32 vals[4] = {};
int i, ret;
@@ -153,7 +162,7 @@ static int rtl9300_i2c_read(struct rtl93
return 0;
}
-static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
+static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, u8 len)
{
u32 vals[4] = {};
int i;
@@ -176,16 +185,51 @@ static int rtl9300_i2c_writel(struct rtl
return regmap_write(i2c->regmap, i2c->data_reg, data);
}
-static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
- int size, union i2c_smbus_data *data, int len)
+static int rtl9300_i2c_prepare_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
{
- u32 val;
int ret;
- ret = regmap_field_write(i2c->fields[F_RWOP], read_write == I2C_SMBUS_WRITE);
+ if (xfer->data_len < 1 || xfer->data_len > 16)
+ return -EINVAL;
+
+ ret = regmap_field_write(i2c->fields[F_DEV_ADDR], xfer->dev_addr);
if (ret)
return ret;
+ ret = rtl9300_i2c_reg_addr_set(i2c, xfer->reg_addr, xfer->reg_addr_len);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(i2c->fields[F_RWOP], xfer->write);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(i2c->fields[F_DATA_WIDTH], (xfer->data_len - 1) & 0xf);
+ if (ret)
+ return ret;
+
+ if (xfer->write) {
+ switch (xfer->type) {
+ case RTL9300_I2C_XFER_BYTE:
+ ret = rtl9300_i2c_writel(i2c, *xfer->data);
+ break;
+ case RTL9300_I2C_XFER_WORD:
+ ret = rtl9300_i2c_writel(i2c, get_unaligned((const u16 *)xfer->data));
+ break;
+ default:
+ ret = rtl9300_i2c_write(i2c, xfer->data, xfer->data_len);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int rtl9300_i2c_do_xfer(struct rtl9300_i2c *i2c, struct rtl9300_i2c_xfer *xfer)
+{
+ u32 val;
+ int ret;
+
ret = regmap_field_write(i2c->fields[F_I2C_TRIG], 1);
if (ret)
return ret;
@@ -200,28 +244,24 @@ static int rtl9300_i2c_execute_xfer(stru
if (val)
return -EIO;
- if (read_write == I2C_SMBUS_READ) {
- switch (size) {
- case I2C_SMBUS_BYTE:
- case I2C_SMBUS_BYTE_DATA:
+ if (!xfer->write) {
+ switch (xfer->type) {
+ case RTL9300_I2C_XFER_BYTE:
ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
if (ret)
return ret;
- data->byte = val & 0xff;
+
+ *xfer->data = val & 0xff;
break;
- case I2C_SMBUS_WORD_DATA:
+ case RTL9300_I2C_XFER_WORD:
ret = regmap_read(i2c->regmap, i2c->data_reg, &val);
if (ret)
return ret;
- data->word = val & 0xffff;
- break;
- case I2C_SMBUS_I2C_BLOCK_DATA:
- ret = rtl9300_i2c_read(i2c, &data->block[1], len);
- if (ret)
- return ret;
+
+ put_unaligned(val & 0xffff, (u16*)xfer->data);
break;
default:
- ret = rtl9300_i2c_read(i2c, &data->block[0], len);
+ ret = rtl9300_i2c_read(i2c, xfer->data, xfer->data_len);
if (ret)
return ret;
break;
@@ -237,108 +277,62 @@ static int rtl9300_i2c_smbus_xfer(struct
{
struct rtl9300_i2c_chan *chan = i2c_get_adapdata(adap);
struct rtl9300_i2c *i2c = chan->i2c;
- int len = 0, ret;
+ struct rtl9300_i2c_xfer xfer = {0};
+ int ret;
+
+ if (addr > 0x7f)
+ return -EINVAL;
mutex_lock(&i2c->lock);
- if (chan->sda_num != i2c->sda_num) {
- ret = rtl9300_i2c_config_io(i2c, chan);
- if (ret)
- goto out_unlock;
- i2c->sda_num = chan->sda_num;
- }
+
+ ret = rtl9300_i2c_config_chan(i2c, chan);
+ if (ret)
+ goto out_unlock;
+
+ xfer.dev_addr = addr & 0x7f;
+ xfer.write = (read_write == I2C_SMBUS_WRITE);
+ xfer.reg_addr = command;
+ xfer.reg_addr_len = 1;
switch (size) {
case I2C_SMBUS_BYTE:
- if (read_write == I2C_SMBUS_WRITE) {
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 0);
- if (ret)
- goto out_unlock;
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
- if (ret)
- goto out_unlock;
- } else {
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
- if (ret)
- goto out_unlock;
- ret = rtl9300_i2c_reg_addr_set(i2c, 0, 0);
- if (ret)
- goto out_unlock;
- }
+ xfer.data = (read_write == I2C_SMBUS_READ) ? &data->byte : &command;
+ xfer.data_len = 1;
+ xfer.reg_addr = 0;
+ xfer.reg_addr_len = 0;
+ xfer.type = RTL9300_I2C_XFER_BYTE;
break;
-
case I2C_SMBUS_BYTE_DATA:
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
- if (ret)
- goto out_unlock;
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 1);
- if (ret)
- goto out_unlock;
- if (read_write == I2C_SMBUS_WRITE) {
- ret = rtl9300_i2c_writel(i2c, data->byte);
- if (ret)
- goto out_unlock;
- }
+ xfer.data = &data->byte;
+ xfer.data_len = 1;
+ xfer.type = RTL9300_I2C_XFER_BYTE;
break;
-
case I2C_SMBUS_WORD_DATA:
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
- if (ret)
- goto out_unlock;
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, 2);
- if (ret)
- goto out_unlock;
- if (read_write == I2C_SMBUS_WRITE) {
- ret = rtl9300_i2c_writel(i2c, data->word);
- if (ret)
- goto out_unlock;
- }
+ xfer.data = (u8 *)&data->word;
+ xfer.data_len = 2;
+ xfer.type = RTL9300_I2C_XFER_WORD;
break;
-
case I2C_SMBUS_BLOCK_DATA:
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
- if (ret)
- goto out_unlock;
- if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
- ret = -EINVAL;
- goto out_unlock;
- }
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0] + 1);
- if (ret)
- goto out_unlock;
- if (read_write == I2C_SMBUS_WRITE) {
- ret = rtl9300_i2c_write(i2c, &data->block[0], data->block[0] + 1);
- if (ret)
- goto out_unlock;
- }
- len = data->block[0] + 1;
+ xfer.data = &data->block[0];
+ xfer.data_len = data->block[0] + 1;
+ xfer.type = RTL9300_I2C_XFER_BLOCK;
break;
-
case I2C_SMBUS_I2C_BLOCK_DATA:
- ret = rtl9300_i2c_reg_addr_set(i2c, command, 1);
- if (ret)
- goto out_unlock;
- if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
- ret = -EINVAL;
- goto out_unlock;
- }
- ret = rtl9300_i2c_config_xfer(i2c, chan, addr, data->block[0]);
- if (ret)
- goto out_unlock;
- if (read_write == I2C_SMBUS_WRITE) {
- ret = rtl9300_i2c_write(i2c, &data->block[1], data->block[0]);
- if (ret)
- goto out_unlock;
- }
- len = data->block[0];
+ xfer.data = &data->block[1];
+ xfer.data_len = data->block[0];
+ xfer.type = RTL9300_I2C_XFER_BLOCK;
break;
-
default:
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
ret = -EOPNOTSUPP;
goto out_unlock;
}
- ret = rtl9300_i2c_execute_xfer(i2c, read_write, size, data, len);
+ ret = rtl9300_i2c_prepare_xfer(i2c, &xfer);
+ if (ret)
+ goto out_unlock;
+
+ ret = rtl9300_i2c_do_xfer(i2c, &xfer);
out_unlock:
mutex_unlock(&i2c->lock);

View File

@ -1,68 +0,0 @@
From bcd5f0da57e6c47a884dcad94ad6b0e32cce8705 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:29 +0000
Subject: [PATCH] i2c: rtl9300: use scoped guard instead of explicit
lock/unlock
Use the scoped guard infrastructure which unlocks a mutex automatically
when the guard goes out of scope, instead of explicit lock and unlock.
This simplifies the code and control flow in rtl9300_i2c_smbus_xfer and
removes the need of using goto in error cases to unlock before
returning.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302C based board
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-8-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -72,6 +72,8 @@ struct rtl9300_i2c {
struct mutex lock;
};
+DEFINE_GUARD(rtl9300_i2c, struct rtl9300_i2c *, mutex_lock(&_T->lock), mutex_unlock(&_T->lock))
+
enum rtl9300_i2c_xfer_type {
RTL9300_I2C_XFER_BYTE,
RTL9300_I2C_XFER_WORD,
@@ -283,11 +285,11 @@ static int rtl9300_i2c_smbus_xfer(struct
if (addr > 0x7f)
return -EINVAL;
- mutex_lock(&i2c->lock);
+ guard(rtl9300_i2c)(i2c);
ret = rtl9300_i2c_config_chan(i2c, chan);
if (ret)
- goto out_unlock;
+ return ret;
xfer.dev_addr = addr & 0x7f;
xfer.write = (read_write == I2C_SMBUS_WRITE);
@@ -324,20 +326,14 @@ static int rtl9300_i2c_smbus_xfer(struct
break;
default:
dev_err(&adap->dev, "Unsupported transaction %d\n", size);
- ret = -EOPNOTSUPP;
- goto out_unlock;
+ return -EOPNOTSUPP;
}
ret = rtl9300_i2c_prepare_xfer(i2c, &xfer);
if (ret)
- goto out_unlock;
-
- ret = rtl9300_i2c_do_xfer(i2c, &xfer);
-
-out_unlock:
- mutex_unlock(&i2c->lock);
+ return ret;
- return ret;
+ return rtl9300_i2c_do_xfer(i2c, &xfer);
}
static u32 rtl9300_i2c_func(struct i2c_adapter *a)

View File

@ -1,94 +0,0 @@
From 17689aafb793599a862617a127429dd3d6f675c9 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:30 +0000
Subject: [PATCH] dt-bindings: i2c: realtek,rtl9301-i2c: extend for RTL9310
support
Adjust the regex for child-node address to account for the fact that
RTL9310 supports 12 instead of only 8 SDA lines. Also, narrow this per
variant.
Add a vendor-specific property to explicitly specify the SCL line number
of the defined I2C controller/master. This is required, in particular
for RTL9310, to operate on the correct SCL for each controller. Require
this property to be specified for RTL9310.
Add compatibles for known SoC variants RTL9311, RTL9312 and RTL9313.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-9-jelonek.jonas@gmail.com
--- a/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
+++ b/Documentation/devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml
@@ -13,6 +13,8 @@ description:
RTL9300 SoCs have two I2C controllers. Each of these has an SCL line (which
if not-used for SCL can be a GPIO). There are 8 common SDA lines that can be
assigned to either I2C controller.
+ RTL9310 SoCs have equal capabilities but support 12 common SDA lines which
+ can be assigned to either I2C controller.
properties:
compatible:
@@ -23,7 +25,15 @@ properties:
- realtek,rtl9302c-i2c
- realtek,rtl9303-i2c
- const: realtek,rtl9301-i2c
- - const: realtek,rtl9301-i2c
+ - items:
+ - enum:
+ - realtek,rtl9311-i2c
+ - realtek,rtl9312-i2c
+ - realtek,rtl9313-i2c
+ - const: realtek,rtl9310-i2c
+ - enum:
+ - realtek,rtl9301-i2c
+ - realtek,rtl9310-i2c
reg:
items:
@@ -35,8 +45,14 @@ properties:
"#size-cells":
const: 0
+ realtek,scl:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ The SCL line number of this I2C controller.
+ enum: [ 0, 1 ]
+
patternProperties:
- '^i2c@[0-7]$':
+ '^i2c@[0-9ab]$':
$ref: /schemas/i2c/i2c-controller.yaml
unevaluatedProperties: false
@@ -48,6 +64,25 @@ patternProperties:
required:
- reg
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: realtek,rtl9310-i2c
+ then:
+ required:
+ - realtek,scl
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: realtek,rtl9301-i2c
+ then:
+ patternProperties:
+ '^i2c@[89ab]$': false
+
required:
- compatible
- reg

View File

@ -1,134 +0,0 @@
From 8d43287120ce6437e7a77e735d99137f3fdb3ae9 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
Date: Sat, 27 Sep 2025 10:19:31 +0000
Subject: [PATCH] i2c: rtl9300: add support for RTL9310 I2C controller
Add support for the internal I2C controllers of RTL9310 series based
SoCs to the driver for RTL9300. Add register definitions, chip-specific
functions and compatible strings for known RTL9310-based SoCs RTL9311,
RTL9312 and RTL9313.
Make use of a new device tree property 'realtek,scl' which needs to be
specified in case both or only the second master is used. This is
required due how the register layout changed in contrast to RTL9300,
which has SCL selection in a global register instead of a
master-specific one.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
Tested-by: Sven Eckelmann <sven@narfation.org>
Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Tested-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20250927101931.71575-10-jelonek.jonas@gmail.com
--- a/drivers/i2c/busses/i2c-rtl9300.c
+++ b/drivers/i2c/busses/i2c-rtl9300.c
@@ -60,14 +60,16 @@ struct rtl9300_i2c_drv_data {
};
#define RTL9300_I2C_MUX_NCHAN 8
+#define RTL9310_I2C_MUX_NCHAN 12
struct rtl9300_i2c {
struct regmap *regmap;
struct device *dev;
- struct rtl9300_i2c_chan chans[RTL9300_I2C_MUX_NCHAN];
+ struct rtl9300_i2c_chan chans[RTL9310_I2C_MUX_NCHAN];
struct regmap_field *fields[F_NUM_FIELDS];
u32 reg_base;
u32 data_reg;
+ u8 scl_num;
u8 sda_num;
struct mutex lock;
};
@@ -98,6 +100,12 @@ struct rtl9300_i2c_xfer {
#define RTL9300_I2C_MST_DATA_WORD3 0x14
#define RTL9300_I2C_MST_GLB_CTRL 0x384
+#define RTL9310_I2C_MST_IF_CTRL 0x1004
+#define RTL9310_I2C_MST_IF_SEL 0x1008
+#define RTL9310_I2C_MST_CTRL 0x0
+#define RTL9310_I2C_MST_MEMADDR_CTRL 0x4
+#define RTL9310_I2C_MST_DATA_CTRL 0x8
+
static int rtl9300_i2c_reg_addr_set(struct rtl9300_i2c *i2c, u32 reg, u16 len)
{
int ret;
@@ -114,6 +122,11 @@ static int rtl9300_i2c_select_scl(struct
return regmap_field_write(i2c->fields[F_SCL_SEL], 1);
}
+static int rtl9310_i2c_select_scl(struct rtl9300_i2c *i2c, u8 scl)
+{
+ return regmap_field_update_bits(i2c->fields[F_SCL_SEL], BIT(scl), BIT(scl));
+}
+
static int rtl9300_i2c_config_chan(struct rtl9300_i2c *i2c, struct rtl9300_i2c_chan *chan)
{
struct rtl9300_i2c_drv_data *drv_data;
@@ -127,7 +140,7 @@ static int rtl9300_i2c_config_chan(struc
return ret;
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
- ret = drv_data->select_scl(i2c, 0);
+ ret = drv_data->select_scl(i2c, i2c->scl_num);
if (ret)
return ret;
@@ -361,7 +374,7 @@ static int rtl9300_i2c_probe(struct plat
struct fwnode_handle *child;
struct rtl9300_i2c_drv_data *drv_data;
struct reg_field fields[F_NUM_FIELDS];
- u32 clock_freq, sda_num;
+ u32 clock_freq, scl_num, sda_num;
int ret, i = 0;
i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL);
@@ -379,6 +392,11 @@ static int rtl9300_i2c_probe(struct plat
if (ret)
return ret;
+ ret = device_property_read_u32(dev, "realtek,scl", &scl_num);
+ if (ret || scl_num != 1)
+ scl_num = 0;
+ i2c->scl_num = (u8)scl_num;
+
platform_set_drvdata(pdev, i2c);
drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
@@ -474,12 +492,35 @@ static const struct rtl9300_i2c_drv_data
.max_nchan = RTL9300_I2C_MUX_NCHAN,
};
+static const struct rtl9300_i2c_drv_data rtl9310_i2c_drv_data = {
+ .field_desc = {
+ [F_SCL_SEL] = GLB_REG_FIELD(RTL9310_I2C_MST_IF_SEL, 12, 13),
+ [F_SDA_SEL] = GLB_REG_FIELD(RTL9310_I2C_MST_IF_SEL, 0, 11),
+ [F_SCL_FREQ] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 30, 31),
+ [F_DEV_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 11, 17),
+ [F_SDA_OUT_SEL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 18, 21),
+ [F_MEM_ADDR_WIDTH] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 9, 10),
+ [F_DATA_WIDTH] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 5, 8),
+ [F_RD_MODE] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 4, 4),
+ [F_RWOP] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 2, 2),
+ [F_I2C_FAIL] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 1, 1),
+ [F_I2C_TRIG] = MST_REG_FIELD(RTL9310_I2C_MST_CTRL, 0, 0),
+ [F_MEM_ADDR] = MST_REG_FIELD(RTL9310_I2C_MST_MEMADDR_CTRL, 0, 23),
+ },
+ .select_scl = rtl9310_i2c_select_scl,
+ .data_reg = RTL9310_I2C_MST_DATA_CTRL,
+ .max_nchan = RTL9310_I2C_MUX_NCHAN,
+};
static const struct of_device_id i2c_rtl9300_dt_ids[] = {
{ .compatible = "realtek,rtl9301-i2c", .data = (void *) &rtl9300_i2c_drv_data },
{ .compatible = "realtek,rtl9302b-i2c", .data = (void *) &rtl9300_i2c_drv_data },
{ .compatible = "realtek,rtl9302c-i2c", .data = (void *) &rtl9300_i2c_drv_data },
{ .compatible = "realtek,rtl9303-i2c", .data = (void *) &rtl9300_i2c_drv_data },
+ { .compatible = "realtek,rtl9310-i2c", .data = (void *) &rtl9310_i2c_drv_data },
+ { .compatible = "realtek,rtl9311-i2c", .data = (void *) &rtl9310_i2c_drv_data },
+ { .compatible = "realtek,rtl9312-i2c", .data = (void *) &rtl9310_i2c_drv_data },
+ { .compatible = "realtek,rtl9313-i2c", .data = (void *) &rtl9310_i2c_drv_data },
{}
};
MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);

View File

@ -1,51 +0,0 @@
From 85e27f218121bdaa8e8afd68674262aa154d2cb4 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Mon, 4 Aug 2025 04:03:26 -0400
Subject: clocksource/drivers/timer-rtl-otto: Drop set_counter function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The current counter value is a read only register. It will be
reset when writing a new target timer value with rttm_set_period().
rttm_set_counter() is essentially a noop. Drop it.
While this makes rttm_start_timer() and rttm_enable_timer() the
same functions keep both to make the established abstraction layers
for register and control functions active.
Downstream has already tested and confirmed a patch. See
https://github.com/openwrt/openwrt/pull/19468
https://forum.openwrt.org/t/support-for-rtl838x-based-managed-switches/57875/3788
Tested-by: Stephen Howell <howels@allthatwemight.be>
Tested-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://lore.kernel.org/r/20250804080328.2609287-3-markus.stockhausen@gmx.de
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
drivers/clocksource/timer-rtl-otto.c | 6 ------
1 file changed, 6 deletions(-)
--- a/drivers/clocksource/timer-rtl-otto.c
+++ b/drivers/clocksource/timer-rtl-otto.c
@@ -56,11 +56,6 @@ struct rttm_cs {
};
/* Simple internal register functions */
-static inline void rttm_set_counter(void __iomem *base, unsigned int counter)
-{
- iowrite32(counter, base + RTTM_CNT);
-}
-
static inline unsigned int rttm_get_counter(void __iomem *base)
{
return ioread32(base + RTTM_CNT);
@@ -137,7 +132,6 @@ static void rttm_stop_timer(void __iomem
static void rttm_start_timer(struct timer_of *to, u32 mode)
{
- rttm_set_counter(to->of_base.base, 0);
rttm_enable_timer(to->of_base.base, mode, to->of_clk.rate / RTTM_TICKS_PER_SEC);
}

View File

@ -1,521 +0,0 @@
From 3148d0e5b1c5733d69ec51b70c8280e46488750a Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Fri, 19 Sep 2025 03:52:01 -0400
Subject: mtd: nand: realtek-ecc: Add Realtek external ECC engine support
The Realtek RTl93xx switch SoC series has a built in ECC controller
that can provide BCH6 or BCH12 over 512 data and 6 tag bytes. It
generates 10 (BCH6) or 20 (BCH12) bytes of parity.
This engine will most likely work in conjunction with the Realtek
spi-mem based NAND controller but can work on its own. Therefore
the initial implementation will be of type external.
Remark! The engine can support any data blocks that are multiples
of 512 bytes. For now limit it to data+oob layouts that have been
analyzed from existing devices. This way it keeps compatibility
and pre-existing vendor data can be read.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/nand/Kconfig | 8 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/ecc-realtek.c | 464 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 473 insertions(+)
create mode 100644 drivers/mtd/nand/ecc-realtek.c
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -65,6 +65,14 @@ config MTD_NAND_ECC_MEDIATEK
help
This enables support for the hardware ECC engine from Mediatek.
+config MTD_NAND_ECC_REALTEK
+ tristate "Realtek RTL93xx hardware ECC engine"
+ depends on HAS_IOMEM
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
+ select MTD_NAND_ECC
+ help
+ This enables support for the hardware ECC engine from Realtek.
+
endmenu
endmenu
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -2,6 +2,7 @@
nandcore-objs := core.o bbt.o
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
+obj-$(CONFIG_MTD_NAND_ECC_REALTEK) += ecc-realtek.o
obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
obj-$(CONFIG_MTD_NAND_MTK_BMT) += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o
obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
--- /dev/null
+++ b/drivers/mtd/nand/ecc-realtek.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for Realtek hardware ECC engine in RTL93xx SoCs
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/nand.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/*
+ * The Realtek ECC engine has two operation modes.
+ *
+ * - BCH6 : Generate 10 ECC bytes from 512 data bytes plus 6 free bytes
+ * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
+ *
+ * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there
+ * are only two known devices in the wild that have NAND flash and make use of this ECC engine
+ * (Linksys LGS328C & LGS352C). To keep compatibility with vendor firmware, new modes can only
+ * be added when new data layouts have been analyzed. For now allow BCH6 on flash with 2048 byte
+ * blocks and 64 bytes oob.
+ *
+ * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the
+ * Realtek naming conventions for the different structures in the OOB area.
+ *
+ * - BBI : Bad block indicator. The first two bytes of OOB. Protected by ECC!
+ * - tag : 6 User/free bytes. First tag "contains" 2 bytes BBI. Protected by ECC!
+ * - syndrome : ECC/parity bytes
+ *
+ * Altogether this gives currently the following block layout.
+ *
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ * | 512 | 512 | 512 | 512 | 2 | 4 | 6 | 6 | 6 | 10 | 10 | 10 | 10 |
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ * | data | data | data | data | BBI | free | free | free | free | ECC | ECC | ECC | ECC |
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ */
+
+#define RTL_ECC_ALLOWED_PAGE_SIZE 2048
+#define RTL_ECC_ALLOWED_OOB_SIZE 64
+#define RTL_ECC_ALLOWED_STRENGTH 6
+
+#define RTL_ECC_BLOCK_SIZE 512
+#define RTL_ECC_FREE_SIZE 6
+#define RTL_ECC_PARITY_SIZE_BCH6 10
+#define RTL_ECC_PARITY_SIZE_BCH12 20
+
+/*
+ * The engine is fed with two DMA regions. One for data (always 512 bytes) and one for free bytes
+ * and parity (either 16 bytes for BCH6 or 26 bytes for BCH12). Start and length of each must be
+ * aligned to a multiple of 4.
+ */
+
+#define RTL_ECC_DMA_FREE_PARITY_SIZE ALIGN(RTL_ECC_FREE_SIZE + RTL_ECC_PARITY_SIZE_BCH12, 4)
+#define RTL_ECC_DMA_SIZE (RTL_ECC_BLOCK_SIZE + RTL_ECC_DMA_FREE_PARITY_SIZE)
+
+#define RTL_ECC_CFG 0x00
+#define RTL_ECC_BCH6 0
+#define RTL_ECC_BCH12 BIT(28)
+#define RTL_ECC_DMA_PRECISE BIT(12)
+#define RTL_ECC_BURST_128 GENMASK(1, 0)
+#define RTL_ECC_DMA_TRIGGER 0x08
+#define RTL_ECC_OP_DECODE 0
+#define RTL_ECC_OP_ENCODE BIT(0)
+#define RTL_ECC_DMA_START 0x0c
+#define RTL_ECC_DMA_TAG 0x10
+#define RTL_ECC_STATUS 0x14
+#define RTL_ECC_CORR_COUNT GENMASK(19, 12)
+#define RTL_ECC_RESULT BIT(8)
+#define RTL_ECC_ALL_ONE BIT(4)
+#define RTL_ECC_OP_STATUS BIT(0)
+
+struct rtl_ecc_engine {
+ struct device *dev;
+ struct nand_ecc_engine engine;
+ struct mutex lock;
+ char *buf;
+ dma_addr_t buf_dma;
+ struct regmap *regmap;
+};
+
+struct rtl_ecc_ctx {
+ struct rtl_ecc_engine * rtlc;
+ struct nand_ecc_req_tweak_ctx req_ctx;
+ int steps;
+ int bch_mode;
+ int strength;
+ int parity_size;
+};
+
+static const struct regmap_config rtl_ecc_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static inline void *nand_to_ctx(struct nand_device *nand)
+{
+ return nand->ecc.ctx.priv;
+}
+
+static inline struct rtl_ecc_engine *nand_to_rtlc(struct nand_device *nand)
+{
+ struct nand_ecc_engine *eng = nand->ecc.engine;
+
+ return container_of(eng, struct rtl_ecc_engine, engine);
+}
+
+static int rtl_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_device *nand = mtd_to_nanddev(mtd);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+
+ if (section < 0 || section >= ctx->steps)
+ return -ERANGE;
+
+ oobregion->offset = ctx->steps * RTL_ECC_FREE_SIZE + section * ctx->parity_size;
+ oobregion->length = ctx->parity_size;
+
+ return 0;
+}
+
+static int rtl_ecc_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_device *nand = mtd_to_nanddev(mtd);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ int bbm;
+
+ if (section < 0 || section >= ctx->steps)
+ return -ERANGE;
+
+ /* reserve 2 BBM bytes in first block */
+ bbm = section ? 0 : 2;
+ oobregion->offset = section * RTL_ECC_FREE_SIZE + bbm;
+ oobregion->length = RTL_ECC_FREE_SIZE - bbm;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops rtl_ecc_ooblayout_ops = {
+ .ecc = rtl_ecc_ooblayout_ecc,
+ .free = rtl_ecc_ooblayout_free,
+};
+
+static void rtl_ecc_kick_engine(struct rtl_ecc_ctx *ctx, int operation)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+
+ regmap_write(rtlc->regmap, RTL_ECC_CFG,
+ ctx->bch_mode | RTL_ECC_BURST_128 | RTL_ECC_DMA_PRECISE);
+
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_START, rtlc->buf_dma);
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TAG, rtlc->buf_dma + RTL_ECC_BLOCK_SIZE);
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TRIGGER, operation);
+}
+
+static int rtl_ecc_wait_for_engine(struct rtl_ecc_ctx *ctx)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+ int ret, status, bitflips;
+ bool all_one;
+
+ /*
+ * The ECC engine needs 6-8 us to encode/decode a BCH6 syndrome for 512 bytes of data
+ * and 6 free bytes. In case the NAND area has been erased and all data and oob is
+ * set to 0xff, decoding takes 30us (reason unknown). Although the engine can trigger
+ * interrupts when finished, use active polling for now. 12 us maximum wait time has
+ * proven to be a good tradeoff between performance and overhead.
+ */
+
+ ret = regmap_read_poll_timeout(rtlc->regmap, RTL_ECC_STATUS, status,
+ !(status & RTL_ECC_OP_STATUS), 12, 1000000);
+ if (ret)
+ return ret;
+
+ ret = FIELD_GET(RTL_ECC_RESULT, status);
+ all_one = FIELD_GET(RTL_ECC_ALL_ONE, status);
+ bitflips = FIELD_GET(RTL_ECC_CORR_COUNT, status);
+
+ /* For erased blocks (all bits one) error status can be ignored */
+ if (all_one)
+ ret = 0;
+
+ return ret ? -EBADMSG : bitflips;
+}
+
+static int rtl_ecc_run_engine(struct rtl_ecc_ctx *ctx, char *data, char *free,
+ char *parity, int operation)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+ char *buf_parity = rtlc->buf + RTL_ECC_BLOCK_SIZE + RTL_ECC_FREE_SIZE;
+ char *buf_free = rtlc->buf + RTL_ECC_BLOCK_SIZE;
+ char *buf_data = rtlc->buf;
+ int ret;
+
+ mutex_lock(&rtlc->lock);
+
+ memcpy(buf_data, data, RTL_ECC_BLOCK_SIZE);
+ memcpy(buf_free, free, RTL_ECC_FREE_SIZE);
+ memcpy(buf_parity, parity, ctx->parity_size);
+
+ dma_sync_single_for_device(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_TO_DEVICE);
+ rtl_ecc_kick_engine(ctx, operation);
+ ret = rtl_ecc_wait_for_engine(ctx);
+ dma_sync_single_for_cpu(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_FROM_DEVICE);
+
+ if (ret >= 0) {
+ memcpy(data, buf_data, RTL_ECC_BLOCK_SIZE);
+ memcpy(free, buf_free, RTL_ECC_FREE_SIZE);
+ memcpy(parity, buf_parity, ctx->parity_size);
+ }
+
+ mutex_unlock(&rtlc->lock);
+
+ return ret;
+}
+
+static int rtl_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req)
+{
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ char *data, *free, *parity;
+ int ret = 0;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ nand_ecc_tweak_req(&ctx->req_ctx, req);
+
+ if (req->type == NAND_PAGE_READ)
+ return 0;
+
+ free = req->oobbuf.in;
+ data = req->databuf.in;
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
+
+ for (int i = 0; i < ctx->steps; i++) {
+ ret |= rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_ENCODE);
+
+ free += RTL_ECC_FREE_SIZE;
+ data += RTL_ECC_BLOCK_SIZE;
+ parity += ctx->parity_size;
+ }
+
+ if (unlikely(ret))
+ dev_dbg(rtlc->dev, "ECC calculation failed\n");
+
+ return ret ? -EBADMSG : 0;
+}
+
+static int rtl_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req)
+{
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ char *data, *free, *parity;
+ bool failure = false;
+ int bitflips = 0;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ if (req->type == NAND_PAGE_WRITE) {
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+ return 0;
+ }
+
+ free = req->oobbuf.in;
+ data = req->databuf.in;
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
+
+ for (int i = 0 ; i < ctx->steps; i++) {
+ int ret = rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_DECODE);
+
+ if (unlikely(ret < 0))
+ /* ECC totally fails for bitflips in erased blocks */
+ ret = nand_check_erased_ecc_chunk(data, RTL_ECC_BLOCK_SIZE,
+ parity, ctx->parity_size,
+ free, RTL_ECC_FREE_SIZE,
+ ctx->strength);
+ if (unlikely(ret < 0)) {
+ failure = true;
+ mtd->ecc_stats.failed++;
+ } else {
+ mtd->ecc_stats.corrected += ret;
+ bitflips = max_t(unsigned int, bitflips, ret);
+ }
+
+ free += RTL_ECC_FREE_SIZE;
+ data += RTL_ECC_BLOCK_SIZE;
+ parity += ctx->parity_size;
+ }
+
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+
+ if (unlikely(failure))
+ dev_dbg(rtlc->dev, "ECC correction failed\n");
+ else if (unlikely(bitflips > 2))
+ dev_dbg(rtlc->dev, "%d bitflips detected\n", bitflips);
+
+ return failure ? -EBADMSG : bitflips;
+}
+
+static int rtl_ecc_check_support(struct nand_device *nand)
+{
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ struct device *dev = nand->ecc.engine->dev;
+
+ if (mtd->oobsize != RTL_ECC_ALLOWED_OOB_SIZE ||
+ mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
+ dev_err(dev, "only flash geometry data=%d, oob=%d supported\n",
+ RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_OOB_SIZE);
+ return -EINVAL;
+ }
+
+ if (nand->ecc.user_conf.algo != NAND_ECC_ALGO_BCH ||
+ nand->ecc.user_conf.strength != RTL_ECC_ALLOWED_STRENGTH ||
+ nand->ecc.user_conf.placement != NAND_ECC_PLACEMENT_OOB ||
+ nand->ecc.user_conf.step_size != RTL_ECC_BLOCK_SIZE) {
+ dev_err(dev, "only algo=bch, strength=%d, placement=oob, step=%d supported\n",
+ RTL_ECC_ALLOWED_STRENGTH, RTL_ECC_BLOCK_SIZE);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl_ecc_init_ctx(struct nand_device *nand)
+{
+ struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ int strength = nand->ecc.user_conf.strength;
+ struct device *dev = nand->ecc.engine->dev;
+ struct rtl_ecc_ctx *ctx;
+ int ret;
+
+ ret = rtl_ecc_check_support(nand);
+ if (ret)
+ return ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ nand->ecc.ctx.priv = ctx;
+ mtd_set_ooblayout(mtd, &rtl_ecc_ooblayout_ops);
+
+ conf->algo = NAND_ECC_ALGO_BCH;
+ conf->strength = strength;
+ conf->step_size = RTL_ECC_BLOCK_SIZE;
+ conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
+
+ ctx->rtlc = rtlc;
+ ctx->steps = mtd->writesize / RTL_ECC_BLOCK_SIZE;
+ ctx->strength = strength;
+ ctx->bch_mode = strength == 6 ? RTL_ECC_BCH6 : RTL_ECC_BCH12;
+ ctx->parity_size = strength == 6 ? RTL_ECC_PARITY_SIZE_BCH6 : RTL_ECC_PARITY_SIZE_BCH12;
+
+ ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "using bch%d with geometry data=%dx%d, free=%dx6, parity=%dx%d",
+ conf->strength, ctx->steps, conf->step_size,
+ ctx->steps, ctx->steps, ctx->parity_size);
+
+ return 0;
+}
+
+static void rtl_ecc_cleanup_ctx(struct nand_device *nand)
+{
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+
+ if (ctx)
+ nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
+}
+
+static struct nand_ecc_engine_ops rtl_ecc_engine_ops = {
+ .init_ctx = rtl_ecc_init_ctx,
+ .cleanup_ctx = rtl_ecc_cleanup_ctx,
+ .prepare_io_req = rtl_ecc_prepare_io_req,
+ .finish_io_req = rtl_ecc_finish_io_req,
+};
+
+static int rtl_ecc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtl_ecc_engine *rtlc;
+ void __iomem *base;
+ int ret;
+
+ rtlc = devm_kzalloc(dev, sizeof(*rtlc), GFP_KERNEL);
+ if (!rtlc)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ ret = devm_mutex_init(dev, &rtlc->lock);
+ if (ret)
+ return ret;
+
+ rtlc->regmap = devm_regmap_init_mmio(dev, base, &rtl_ecc_regmap_config);
+ if (IS_ERR(rtlc->regmap))
+ return PTR_ERR(rtlc->regmap);
+
+ /*
+ * Focus on simplicity and use a preallocated DMA buffer for data exchange with the
+ * engine. For now make it a noncoherent memory model as invalidating/flushing caches
+ * is faster than reading/writing uncached memory on the known architectures.
+ */
+
+ rtlc->buf = dma_alloc_noncoherent(dev, RTL_ECC_DMA_SIZE, &rtlc->buf_dma,
+ DMA_BIDIRECTIONAL, GFP_KERNEL);
+ if (IS_ERR(rtlc->buf))
+ return PTR_ERR(rtlc->buf);
+
+ rtlc->dev = dev;
+ rtlc->engine.dev = dev;
+ rtlc->engine.ops = &rtl_ecc_engine_ops;
+ rtlc->engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
+
+ nand_ecc_register_on_host_hw_engine(&rtlc->engine);
+
+ platform_set_drvdata(pdev, rtlc);
+
+ return 0;
+}
+
+static void rtl_ecc_remove(struct platform_device *pdev)
+{
+ struct rtl_ecc_engine *rtlc = platform_get_drvdata(pdev);
+
+ nand_ecc_unregister_on_host_hw_engine(&rtlc->engine);
+ dma_free_noncoherent(rtlc->dev, RTL_ECC_DMA_SIZE, rtlc->buf, rtlc->buf_dma,
+ DMA_BIDIRECTIONAL);
+}
+
+static const struct of_device_id rtl_ecc_of_ids[] = {
+ {
+ .compatible = "realtek,rtl9301-ecc",
+ },
+ { /* sentinel */ },
+};
+
+static struct platform_driver rtl_ecc_driver = {
+ .driver = {
+ .name = "rtl-nand-ecc-engine",
+ .of_match_table = rtl_ecc_of_ids,
+ },
+ .probe = rtl_ecc_probe,
+ .remove = rtl_ecc_remove,
+};
+module_platform_driver(rtl_ecc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
+MODULE_DESCRIPTION("Realtek NAND hardware ECC controller");

View File

@ -1,47 +0,0 @@
From 2250db8628a0d8293ad2e0671138b848a185fba1 Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Sat, 21 Jun 2025 01:49:51 -0400
Subject: irqchip/mips-gic: Allow forced affinity
Devices of the Realtek MIPS Otto platform use the official rtl-otto-timer
as clock event generator and CPU clocksource. It is registered for each CPU
startup via cpuhp_setup_state() and forces the affinity of the clockevent
interrupts to the appropriate CPU via irq_force_affinity().
On the "smaller" devices with a vendor specific interrupt controller
(supported by irq-realtek-rtl) the registration works fine. The "larger"
RTL931x series is based on a MIPS interAptiv dual core with a MIPS GIC
controller. Interrupt routing setup is cancelled because gic_set_affinity()
does not accept the current (not yet online) CPU as a target.
Relax the checks by evaluating the force parameter that is provided for
exactly this purpose like in other drivers. With this the affinity can be
set as follows:
- force = false: allow to set affinity to any online cpu
- force = true: allow to set affinity to any cpu
Co-developed-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
Signed-off-by: Sebastian Gottschall <s.gottschall@dd-wrt.com>
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20250621054952.380374-1-markus.stockhausen@gmx.de
---
drivers/irqchip/irq-mips-gic.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -263,7 +263,11 @@ static int gic_set_affinity(struct irq_d
unsigned long flags;
unsigned int cpu;
- cpu = cpumask_first_and(cpumask, cpu_online_mask);
+ if (force)
+ cpu = cpumask_first(cpumask);
+ else
+ cpu = cpumask_first_and(cpumask, cpu_online_mask);
+
if (cpu >= NR_CPUS)
return -EINVAL;