--- /dev/null
+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>;
++ };
++ };
--- /dev/null
+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
+@@ -20151,6 +20151,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");
--- /dev/null
+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
--- /dev/null
+From c8e44fae5b05528d4d672a935c8ace21eed8b8e7 Mon Sep 17 00:00:00 2001
+From: Alex Guo <alexguo1023@gmail.com>
+Date: Sat, 9 Aug 2025 08:40:54 +0200
+Subject: [PATCH 1/5] 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()")
+
+Cc: <stable@vger.kernel.org>
+Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
+Signed-off-by: Alex Guo <alexguo1023@gmail.com>
+Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
+Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Link: https://lore.kernel.org/linux-i2c/20250809-i2c-rtl9300-multi-byte-v4-1-d71dd5eb6121@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;
--- /dev/null
+From 0190fed3eed28be78717a0b45d7038c855e83b18 Mon Sep 17 00:00:00 2001
+From: Harshal Gohel <hg@simonwunderlich.de>
+Date: Sat, 9 Aug 2025 08:40:55 +0200
+Subject: [PATCH 2/5] 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.
+
+Cc: <stable@vger.kernel.org>
+Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
+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>
+Link: https://lore.kernel.org/linux-i2c/20250809-i2c-rtl9300-multi-byte-v4-2-d71dd5eb6121@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,
--- /dev/null
+From 1757cce14242192be41819db4bc7b6b707d20b87 Mon Sep 17 00:00:00 2001
+From: Sven Eckelmann <sven@narfation.org>
+Date: Sat, 9 Aug 2025 08:40:56 +0200
+Subject: [PATCH 3/5] 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
+
+Cc: <stable@vger.kernel.org>
+Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
+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>
+Link: https://lore.kernel.org/linux-i2c/20250809-i2c-rtl9300-multi-byte-v4-3-d71dd5eb6121@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;
+
--- /dev/null
+From ebc6d39a075f89ab3a7bb3cf011c5fbd555ab82b Mon Sep 17 00:00:00 2001
+From: Sven Eckelmann <sven@narfation.org>
+Date: Sat, 9 Aug 2025 08:40:57 +0200
+Subject: [PATCH 4/5] 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.
+
+Cc: <stable@vger.kernel.org>
+Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller")
+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>
+Link: https://lore.kernel.org/linux-i2c/20250809-i2c-rtl9300-multi-byte-v4-4-d71dd5eb6121@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:
--- /dev/null
+From 1bd7526db1cf9d5c6a700c37b8bf915f8ff72c44 Mon Sep 17 00:00:00 2001
+From: Harshal Gohel <hg@simonwunderlich.de>
+Date: Sat, 9 Aug 2025 08:40:58 +0200
+Subject: [PATCH 5/5] 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>
+Link: https://lore.kernel.org/linux-i2c/20250809-i2c-rtl9300-multi-byte-v4-5-d71dd5eb6121@narfation.org/
+
+--- a/drivers/i2c/busses/i2c-rtl9300.c
++++ b/drivers/i2c/busses/i2c-rtl9300.c
+@@ -183,22 +183,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;
+ }
+ }
+
+@@ -296,6 +306,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;
+@@ -314,6 +343,7 @@ static u32 rtl9300_i2c_func(struct i2c_a
+ {
+ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
++ I2C_FUNC_SMBUS_READ_I2C_BLOCK | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
+ I2C_FUNC_SMBUS_BLOCK_DATA;
+ }
+
--- /dev/null
+From 055c2659011812c1abbd879ce80b2342e4d93576 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Mon, 30 Jun 2025 15:37:06 +0000
+Subject: [PATCH v5 01/11] 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.
+
+Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
+Tested-by: Chris Packham <chris.packham@alliedtelesis.co.nz> # On RTL9302c
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-2-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 192 ++++++++++++++++++++-----------
+ 1 file changed, 124 insertions(+), 68 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-rtl9300.c
++++ b/drivers/i2c/busses/i2c-rtl9300.c
+@@ -23,94 +23,114 @@ 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;
+-
+- val = chan->bus_freq << RTL9300_I2C_MST_CTRL2_SCL_FREQ_OFS;
+- mask = RTL9300_I2C_MST_CTRL2_SCL_FREQ_MASK;
++ int ret;
+
+- 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)
+@@ -121,8 +141,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;
+
+@@ -149,52 +168,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;
+@@ -362,9 +378,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)
+@@ -383,9 +401,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;
+@@ -402,7 +433,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;
+@@ -434,11 +464,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);
--- /dev/null
+From 571dd5203fcd1b9f78bc3d913492c0e13ad1bb00 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Sat, 9 Aug 2025 19:58:43 +0000
+Subject: [PATCH v5 02/11] 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) >= drv_data->max_nchan)
+
+drv_data->max_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) > drv_data->max_nchan)
+
+Issue occured on a TP-Link TL-ST1008F v2.0 device (8 SFP+ ports) and fix
+is tested there.
+
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-3-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/i2c/busses/i2c-rtl9300.c
++++ b/drivers/i2c/busses/i2c-rtl9300.c
+@@ -402,7 +402,7 @@ static int rtl9300_i2c_probe(struct plat
+ platform_set_drvdata(pdev, i2c);
+
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+- if (device_get_child_node_count(dev) >= drv_data->max_nchan)
++ 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;
--- /dev/null
+From 7bb38428b718a281e09b35bc26478600b53300fc Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Mon, 4 Aug 2025 10:26:35 +0000
+Subject: [PATCH v5 03/11] 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>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-4-jelonek.jonas@gmail.com/
+---
+ .../devicetree/bindings/i2c/realtek,rtl9301-i2c.yaml | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- 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:
--- /dev/null
+From bccfb0e2a337529d07134e52ca3e2ec1c156bd7d Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Mon, 4 Aug 2025 11:27:57 +0000
+Subject: [PATCH v5 04/11] 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>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-5-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 24 ++++++++++++------------
+ 1 file changed, 12 insertions(+), 12 deletions(-)
+
+--- 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;
+
+@@ -240,11 +240,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) {
+@@ -381,7 +381,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);
+@@ -421,7 +421,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;
+
+@@ -438,11 +438,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;
+@@ -452,14 +452,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;
+ }
--- /dev/null
+From 47c2f21b934bda1d4031db8d54e08d52b180d9df Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Tue, 5 Aug 2025 09:06:44 +0000
+Subject: [PATCH v5 05/11] i2c: rtl9300: check if xfer length is valid
+
+Add an explicit check for the xfer length to 'rtl9300_i2c_config_xfer'
+to make sure a length < 1 or > 16 isn't accepted. While there shouldn't
+be a length > 16 because this is specified in the i2c_adapter_quirks, a
+length of 0 may be passed. This is problematic, because the code adopts
+this value with
+
+(len - 1) & 0xf
+
+because the corresponding register documentation states that the value
+in the register is always used + 1 ([1]). Obvious reason for this is to
+fit allow xfer length of 16 to be specified in this 4-bit wide register
+field.
+
+Another consequence of this is also, that an xfer length of 0 cannot be
+set. Thus, an explicit check should avoid this. Before, this actually
+led to writing 0xf into the register, probably causing unintended
+behaviour.
+
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-6-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/i2c/busses/i2c-rtl9300.c
++++ b/drivers/i2c/busses/i2c-rtl9300.c
+@@ -118,6 +118,9 @@ static int rtl9300_i2c_config_xfer(struc
+ {
+ int ret;
+
++ if (len < 1 || len > 16)
++ return -EINVAL;
++
+ ret = regmap_field_write(i2c->fields[F_SCL_FREQ], chan->bus_freq);
+ if (ret)
+ return ret;
--- /dev/null
+From cee289c080977752b485dc41a5fbc53c5af93aa9 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Tue, 5 Aug 2025 09:14:51 +0000
+Subject: [PATCH v5 06/11] i2c: rtl9300: remove 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.
+
+Although there are currently no reports of actual issues this caused.
+However, as an example, i2cdetect by default uses Quick Write operation
+to probe the bus and this may already write anything to some register
+of a device, causing unintended behaviour. This could be the cause of a
+recent brick of one of my DAC cables where there was a checksum mismatch
+of the EEPROM after having run 'i2cdetect -l' before.
+
+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 may
+occur in case of a 'Quick' Write which writes unknown data to an unknown
+register.
+
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-7-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 16 +++-------------
+ 1 file changed, 3 insertions(+), 13 deletions(-)
+
+--- a/drivers/i2c/busses/i2c-rtl9300.c
++++ b/drivers/i2c/busses/i2c-rtl9300.c
+@@ -251,15 +251,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);
+@@ -360,10 +351,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_READ_I2C_BLOCK | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
+- I2C_FUNC_SMBUS_BLOCK_DATA;
++ return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
++ I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_READ_I2C_BLOCK |
++ I2C_FUNC_SMBUS_WRITE_I2C_BLOCK | I2C_FUNC_SMBUS_BLOCK_DATA;
+ }
+
+ static const struct i2c_algorithm rtl9300_i2c_algo = {
--- /dev/null
+From c64be5e5ee6326ca7ec38dbcc8892254f1da3cf8 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Fri, 1 Aug 2025 21:18:56 +0000
+Subject: [PATCH v5 07/11] 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>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-8-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 17 +++++++++--------
+ 1 file changed, 9 insertions(+), 8 deletions(-)
+
+--- 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;
--- /dev/null
+From bd70c2680786f3b1def31285ad5114f17e381b3a Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Fri, 1 Aug 2025 21:31:09 +0000
+Subject: [PATCH v5 08/11] 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>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-9-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+--- 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;
+ }
+
--- /dev/null
+From 9665c6a9812d9e2fa2b86be1fa1fc2b381e6f34e Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Mon, 4 Aug 2025 11:31:11 +0000
+Subject: [PATCH v5 09/11] 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>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-10-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 234 +++++++++++++++----------------
+ 1 file changed, 114 insertions(+), 120 deletions(-)
+
+--- 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);
--- /dev/null
+From f891af341e32c8a2f2d2d25dd3c424ff47147a31 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Mon, 4 Aug 2025 10:39:39 +0000
+Subject: [PATCH v5 10/11] 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
+Realtek-internal ID of the defined I2C controller/master. This is
+required, in particular for RTL9310, to describe the correct I2C
+master. Require this property for RTL9310.
+
+Add compatibles for known SoC variants RTL9311, RTL9312 and RTL9313.
+
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-11-jelonek.jonas@gmail.com/
+---
+ .../bindings/i2c/realtek,rtl9301-i2c.yaml | 39 ++++++++++++++++++-
+ 1 file changed, 37 insertions(+), 2 deletions(-)
+
+--- 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
--- /dev/null
+From ceddd84c5f73b17a1d8b9a760d9d5b5b7f0f0519 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Mon, 30 Jun 2025 17:02:32 +0000
+Subject: [PATCH v5 11/11] 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>
+Link: https://lore.kernel.org/linux-i2c/20250809220713.1038947-12-jelonek.jonas@gmail.com/
+---
+ drivers/i2c/busses/i2c-rtl9300.c | 44 ++++++++++++++++++++++++++++++--
+ 1 file changed, 42 insertions(+), 2 deletions(-)
+
+--- 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;
+ };
+@@ -96,6 +98,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;
+@@ -112,6 +120,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;
+@@ -125,7 +138,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;
+
+@@ -383,6 +396,10 @@ static int rtl9300_i2c_probe(struct plat
+ if (ret)
+ return ret;
+
++ ret = device_property_read_u8(dev, "realtek,scl", &i2c->scl_num);
++ if (ret || i2c->scl_num != 1)
++ i2c->scl_num = 0;
++
+ platform_set_drvdata(pdev, i2c);
+
+ drv_data = (struct rtl9300_i2c_drv_data *)device_get_match_data(i2c->dev);
+@@ -478,12 +495,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);