diff --git a/target/linux/realtek/patches-6.12/003-v6.13-01-dt-bindings-i2c-Add-Realtek-RTL-I2C-Controller.patch b/target/linux/realtek/patches-6.12/003-v6.13-01-dt-bindings-i2c-Add-Realtek-RTL-I2C-Controller.patch new file mode 100644 index 0000000000..8ec0d04127 --- /dev/null +++ b/target/linux/realtek/patches-6.12/003-v6.13-01-dt-bindings-i2c-Add-Realtek-RTL-I2C-Controller.patch @@ -0,0 +1,84 @@ +From c5eda0333076e031197816454998a918f1de0831 Mon Sep 17 00:00:00 2001 +From: Chris Packham +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 +Reviewed-by: Krzysztof Kozlowski +Signed-off-by: Andi Shyti + +--- /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 ++ ++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>; ++ }; ++ }; diff --git a/target/linux/realtek/patches-6.12/003-v6.13-02-i2c-Add-driver-for-the-RTL9300-I2C-controller.patch b/target/linux/realtek/patches-6.12/003-v6.13-02-i2c-Add-driver-for-the-RTL9300-I2C-controller.patch new file mode 100644 index 0000000000..4d025d6049 --- /dev/null +++ b/target/linux/realtek/patches-6.12/003-v6.13-02-i2c-Add-driver-for-the-RTL9300-I2C-controller.patch @@ -0,0 +1,493 @@ +From c366be720235301fdadf67e6f1ea6ff32669c074 Mon Sep 17 00:00:00 2001 +From: Chris Packham +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 +Reviewed-by: Andi Shyti +Signed-off-by: Andi Shyti + +--- 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 ++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 + M: Jack Wang +--- 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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"); diff --git a/target/linux/realtek/patches-6.12/004-v6.16-dt-bindings-i2c-realtek-rtl9301-Fix-missing-reg-cons.patch b/target/linux/realtek/patches-6.12/004-v6.16-dt-bindings-i2c-realtek-rtl9301-Fix-missing-reg-cons.patch new file mode 100644 index 0000000000..c460eb1a40 --- /dev/null +++ b/target/linux/realtek/patches-6.12/004-v6.16-dt-bindings-i2c-realtek-rtl9301-Fix-missing-reg-cons.patch @@ -0,0 +1,27 @@ +From 5f05fc6e2218db7ecc52c60eb34b707fe69262c2 Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +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: # v6.13+ +Signed-off-by: Krzysztof Kozlowski +Signed-off-by: Andi Shyti +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 diff --git a/target/linux/realtek/patches-6.12/100-01-i2c-rtl9300-Fix-out-of-bounds-bug-in-rtl9300_i2c_smb.patch b/target/linux/realtek/patches-6.12/100-01-i2c-rtl9300-Fix-out-of-bounds-bug-in-rtl9300_i2c_smb.patch new file mode 100644 index 0000000000..3caecdf9ab --- /dev/null +++ b/target/linux/realtek/patches-6.12/100-01-i2c-rtl9300-Fix-out-of-bounds-bug-in-rtl9300_i2c_smb.patch @@ -0,0 +1,37 @@ +From c8e44fae5b05528d4d672a935c8ace21eed8b8e7 Mon Sep 17 00:00:00 2001 +From: Alex Guo +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: +Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller") +Signed-off-by: Alex Guo +Reviewed-by: Chris Packham +Tested-by: Chris Packham +Signed-off-by: Sven Eckelmann +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; diff --git a/target/linux/realtek/patches-6.12/100-02-i2c-rtl9300-Fix-multi-byte-I2C-write.patch b/target/linux/realtek/patches-6.12/100-02-i2c-rtl9300-Fix-multi-byte-I2C-write.patch new file mode 100644 index 0000000000..4a8cdacdee --- /dev/null +++ b/target/linux/realtek/patches-6.12/100-02-i2c-rtl9300-Fix-multi-byte-I2C-write.patch @@ -0,0 +1,74 @@ +From 0190fed3eed28be78717a0b45d7038c855e83b18 Mon Sep 17 00:00:00 2001 +From: Harshal Gohel +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: +Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller") +Signed-off-by: Harshal Gohel +Co-developed-by: Sven Eckelmann +Signed-off-by: Sven Eckelmann +Reviewed-by: Chris Packham +Tested-by: Chris Packham +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, diff --git a/target/linux/realtek/patches-6.12/100-03-i2c-rtl9300-Increase-timeout-for-transfer-polling.patch b/target/linux/realtek/patches-6.12/100-03-i2c-rtl9300-Increase-timeout-for-transfer-polling.patch new file mode 100644 index 0000000000..7f0c440fd9 --- /dev/null +++ b/target/linux/realtek/patches-6.12/100-03-i2c-rtl9300-Increase-timeout-for-transfer-polling.patch @@ -0,0 +1,36 @@ +From 1757cce14242192be41819db4bc7b6b707d20b87 Mon Sep 17 00:00:00 2001 +From: Sven Eckelmann +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: +Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller") +Signed-off-by: Sven Eckelmann +Reviewed-by: Chris Packham +Tested-by: Chris Packham +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; + diff --git a/target/linux/realtek/patches-6.12/100-04-i2c-rtl9300-Add-missing-count-byte-for-SMBus-Block-O.patch b/target/linux/realtek/patches-6.12/100-04-i2c-rtl9300-Add-missing-count-byte-for-SMBus-Block-O.patch new file mode 100644 index 0000000000..31869ce854 --- /dev/null +++ b/target/linux/realtek/patches-6.12/100-04-i2c-rtl9300-Add-missing-count-byte-for-SMBus-Block-O.patch @@ -0,0 +1,52 @@ +From ebc6d39a075f89ab3a7bb3cf011c5fbd555ab82b Mon Sep 17 00:00:00 2001 +From: Sven Eckelmann +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: +Fixes: c366be720235 ("i2c: Add driver for the RTL9300 I2C controller") +Signed-off-by: Sven Eckelmann +Reviewed-by: Chris Packham +Tested-by: Chris Packham +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: diff --git a/target/linux/realtek/patches-6.12/100-05-i2c-rtl9300-Implement-I2C-block-read-and-write.patch b/target/linux/realtek/patches-6.12/100-05-i2c-rtl9300-Implement-I2C-block-read-and-write.patch new file mode 100644 index 0000000000..c2e6b333ea --- /dev/null +++ b/target/linux/realtek/patches-6.12/100-05-i2c-rtl9300-Implement-I2C-block-read-and-write.patch @@ -0,0 +1,116 @@ +From 1bd7526db1cf9d5c6a700c37b8bf915f8ff72c44 Mon Sep 17 00:00:00 2001 +From: Harshal Gohel +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 +Co-developed-by: Sven Eckelmann +Signed-off-by: Sven Eckelmann +Reviewed-by: Chris Packham +Tested-by: Chris Packham +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; + } + diff --git a/target/linux/realtek/patches-6.12/101-01-i2c-rtl9300-use-regmap-fields-and-API-for-registe.patch b/target/linux/realtek/patches-6.12/101-01-i2c-rtl9300-use-regmap-fields-and-API-for-registe.patch new file mode 100644 index 0000000000..6292793483 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-01-i2c-rtl9300-use-regmap-fields-and-API-for-registe.patch @@ -0,0 +1,352 @@ +From 055c2659011812c1abbd879ce80b2342e4d93576 Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +Tested-by: Chris Packham # On RTL9302c +Signed-off-by: Jonas Jelonek +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); diff --git a/target/linux/realtek/patches-6.12/101-02-i2c-rtl9300-fix-channel-number-bound-check.patch b/target/linux/realtek/patches-6.12/101-02-i2c-rtl9300-fix-channel-number-bound-check.patch new file mode 100644 index 0000000000..a00bf32541 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-02-i2c-rtl9300-fix-channel-number-bound-check.patch @@ -0,0 +1,36 @@ +From 571dd5203fcd1b9f78bc3d913492c0e13ad1bb00 Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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; diff --git a/target/linux/realtek/patches-6.12/101-03-dt-bindings-i2c-realtek-rtl9301-i2c-fix-wording-a.patch b/target/linux/realtek/patches-6.12/101-03-dt-bindings-i2c-realtek-rtl9301-i2c-fix-wording-a.patch new file mode 100644 index 0000000000..d4b58bc0e1 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-03-dt-bindings-i2c-realtek-rtl9301-i2c-fix-wording-a.patch @@ -0,0 +1,50 @@ +From 7bb38428b718a281e09b35bc26478600b53300fc Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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 + + 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: diff --git a/target/linux/realtek/patches-6.12/101-04-i2c-rtl9300-rename-internal-sda_pin-to-sda_num.patch b/target/linux/realtek/patches-6.12/101-04-i2c-rtl9300-rename-internal-sda_pin-to-sda_num.patch new file mode 100644 index 0000000000..efc3ecb16b --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-04-i2c-rtl9300-rename-internal-sda_pin-to-sda_num.patch @@ -0,0 +1,116 @@ +From bccfb0e2a337529d07134e52ca3e2ec1c156bd7d Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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; + } diff --git a/target/linux/realtek/patches-6.12/101-05-i2c-rtl9300-check-if-xfer-length-is-valid.patch b/target/linux/realtek/patches-6.12/101-05-i2c-rtl9300-check-if-xfer-length-is-valid.patch new file mode 100644 index 0000000000..75ac386b24 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-05-i2c-rtl9300-check-if-xfer-length-is-valid.patch @@ -0,0 +1,41 @@ +From 47c2f21b934bda1d4031db8d54e08d52b180d9df Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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; diff --git a/target/linux/realtek/patches-6.12/101-06-i2c-rtl9300-remove-SMBus-Quick-operation-support.patch b/target/linux/realtek/patches-6.12/101-06-i2c-rtl9300-remove-SMBus-Quick-operation-support.patch new file mode 100644 index 0000000000..718dcfa51a --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-06-i2c-rtl9300-remove-SMBus-Quick-operation-support.patch @@ -0,0 +1,75 @@ +From cee289c080977752b485dc41a5fbc53c5af93aa9 Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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 = { diff --git a/target/linux/realtek/patches-6.12/101-07-i2c-rtl9300-move-setting-SCL-frequency-to-config_.patch b/target/linux/realtek/patches-6.12/101-07-i2c-rtl9300-move-setting-SCL-frequency-to-config_.patch new file mode 100644 index 0000000000..feb9949c09 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-07-i2c-rtl9300-move-setting-SCL-frequency-to-config_.patch @@ -0,0 +1,66 @@ +From c64be5e5ee6326ca7ec38dbcc8892254f1da3cf8 Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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; diff --git a/target/linux/realtek/patches-6.12/101-08-i2c-rtl9300-do-not-set-read-mode-on-every-transfe.patch b/target/linux/realtek/patches-6.12/101-08-i2c-rtl9300-do-not-set-read-mode-on-every-transfe.patch new file mode 100644 index 0000000000..b06719608d --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-08-i2c-rtl9300-do-not-set-read-mode-on-every-transfe.patch @@ -0,0 +1,47 @@ +From bd70c2680786f3b1def31285ad5114f17e381b3a Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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; + } + diff --git a/target/linux/realtek/patches-6.12/101-09-i2c-rtl9300-separate-xfer-configuration-and-execu.patch b/target/linux/realtek/patches-6.12/101-09-i2c-rtl9300-separate-xfer-configuration-and-execu.patch new file mode 100644 index 0000000000..6eee13f0b5 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-09-i2c-rtl9300-separate-xfer-configuration-and-execu.patch @@ -0,0 +1,381 @@ +From 9665c6a9812d9e2fa2b86be1fa1fc2b381e6f34e Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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 + #include + #include ++#include + + 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); diff --git a/target/linux/realtek/patches-6.12/101-10-dt-bindings-i2c-realtek-rtl9301-i2c-extend-for-RT.patch b/target/linux/realtek/patches-6.12/101-10-dt-bindings-i2c-realtek-rtl9301-i2c-extend-for-RT.patch new file mode 100644 index 0000000000..03dea2c088 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-10-dt-bindings-i2c-realtek-rtl9301-i2c-extend-for-RT.patch @@ -0,0 +1,93 @@ +From f891af341e32c8a2f2d2d25dd3c424ff47147a31 Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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 diff --git a/target/linux/realtek/patches-6.12/101-11-i2c-rtl9300-add-support-for-RTL9310-I2C-controlle.patch b/target/linux/realtek/patches-6.12/101-11-i2c-rtl9300-add-support-for-RTL9310-I2C-controlle.patch new file mode 100644 index 0000000000..bf170fad85 --- /dev/null +++ b/target/linux/realtek/patches-6.12/101-11-i2c-rtl9300-add-support-for-RTL9310-I2C-controlle.patch @@ -0,0 +1,123 @@ +From ceddd84c5f73b17a1d8b9a760d9d5b5b7f0f0519 Mon Sep 17 00:00:00 2001 +From: Jonas Jelonek +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 +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);