mirror of
https://git.openwrt.org/openwrt/openwrt.git/
synced 2025-10-06 05:52:54 +02:00
realtek: backport upstream RTL9300 I2C driver
Backport/add all patches for the upstream RTL9300 I2C driver. The upstream driver was added in 6.13 and was heavily based on our downstream driver except for how the multiplexing behaviour is handled. This driver was working fine with basic SFP operation on RTL930x devices but there was no support for RTL931x though. Major advantage over our downstream driver is: The multiplexing behaviour is handled completely by the driver. Thus, there's no need for a separate rtl9300-mux driver as we had it downstream. Moreover, this simplifies the DTS of affected devices a lot since we can now move the controller definition - which is in the DTS of each device so far - to the base DTSI. Currently pending patches are also included because the progress on getting this upstream seems really slow right now, albeit upstream maintainers may require several changes to the current state. These include: - patches fixing several issues in the driver - patches doing a refactoring of the driver and adding support for RTL931x See the commit messages included in each patch to have details on the changes. Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com> Link: https://github.com/openwrt/openwrt/pull/19736 Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
committed by
Robert Marko
parent
e005cdea10
commit
44655c97bb
@@ -0,0 +1,84 @@
|
||||
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>;
|
||||
+ };
|
||||
+ };
|
@@ -0,0 +1,493 @@
|
||||
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");
|
@@ -0,0 +1,27 @@
|
||||
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
|
@@ -0,0 +1,37 @@
|
||||
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;
|
@@ -0,0 +1,74 @@
|
||||
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,
|
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
|
@@ -0,0 +1,52 @@
|
||||
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:
|
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
}
|
||||
|
@@ -0,0 +1,352 @@
|
||||
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);
|
@@ -0,0 +1,36 @@
|
||||
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;
|
@@ -0,0 +1,50 @@
|
||||
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:
|
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
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;
|
@@ -0,0 +1,75 @@
|
||||
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 = {
|
@@ -0,0 +1,66 @@
|
||||
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;
|
@@ -0,0 +1,47 @@
|
||||
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;
|
||||
}
|
||||
|
@@ -0,0 +1,381 @@
|
||||
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);
|
@@ -0,0 +1,93 @@
|
||||
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
|
@@ -0,0 +1,123 @@
|
||||
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);
|
Reference in New Issue
Block a user