All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] i2c: add GPIO buses sharing one SCL line
@ 2026-10-18 19:43 Ross Cawston
  2026-10-18 19:43 ` [PATCH 1/2] dt-bindings: " Ross Cawston
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Ross Cawston @ 2026-10-18 19:43 UTC (permalink / raw)
  To: andi.shyti
  Cc: robh, krzk+dt, conor+dt, kees, gustavoars, linux-i2c, devicetree,
	linux-hardening, linux-kernel, Ross Cawston

Some Realtek RTL930x switches bit-bang several I2C buses on SoC GPIOs
with a single SCL line and one SDA line per bus. The MokerLink
POE-10G07110GSM, for example, has its fan controller, its PoE
controllers and its SFP+ cage on SDA GPIOs 9, 10 and 11, all clocked
by GPIO 8. i2c-gpio cannot describe this: every bus would request the
same SCL GPIO.

This series adds a binding and a small driver that requests SCL once
and registers one i2c-algo-bit adapter per SDA line, serializing the
transfers so that only one bus clocks SCL at a time.

The SoC's own I2C master (i2c-rtl9300) is wired to the same pins, but
it moves at most 16 bytes per transfer, and the PoE controllers on this
board only accept their 25 KiB firmware as one continuous write: sent
in 16-byte writes, they NAK the second one. Tested on the MokerLink
POE-10G07110GSM.

Ross Cawston (2):
  dt-bindings: i2c: add GPIO buses sharing one SCL line
  i2c: add driver for GPIO buses sharing one SCL line

 .../bindings/i2c/i2c-gpio-shared-scl.yaml     |  94 +++++++
 MAINTAINERS                                   |   7 +
 drivers/i2c/busses/Kconfig                    |  12 +
 drivers/i2c/busses/Makefile                   |   1 +
 drivers/i2c/busses/i2c-gpio-shared-scl.c      | 229 ++++++++++++++++++
 5 files changed, 343 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml
 create mode 100644 drivers/i2c/busses/i2c-gpio-shared-scl.c

-- 
2.55.0.windows.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 1/2] dt-bindings: i2c: add GPIO buses sharing one SCL line
  2026-10-18 19:43 [PATCH 0/2] i2c: add GPIO buses sharing one SCL line Ross Cawston
@ 2026-10-18 19:43 ` Ross Cawston
  2026-10-18 19:56   ` sashiko-bot
  2026-10-18 19:43 ` [PATCH 2/2] i2c: add driver for " Ross Cawston
  2026-10-18 21:20 ` [PATCH 0/2] i2c: add " Ross Cawston
  2 siblings, 1 reply; 7+ messages in thread
From: Ross Cawston @ 2026-10-18 19:43 UTC (permalink / raw)
  To: andi.shyti
  Cc: robh, krzk+dt, conor+dt, kees, gustavoars, linux-i2c, devicetree,
	linux-hardening, linux-kernel, Ross Cawston

Add a binding for several bit-banged I2C buses that share one SCL GPIO
and have one SDA GPIO each, as on some Realtek RTL930x switches. Each
child node is a bus with its own SDA line; the parent holds the shared
SCL line and the i2c-gpio style timing properties.

Signed-off-by: Ross Cawston <ross@r-sc.ca>
---
 .../bindings/i2c/i2c-gpio-shared-scl.yaml     | 94 +++++++++++++++++++
 1 file changed, 94 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml

diff --git a/Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml b/Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml
new file mode 100644
index 00000000000..e725120973a
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/i2c-gpio-shared-scl.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Bit-banged I2C buses sharing one SCL line
+
+maintainers:
+  - Ross Cawston <ross@r-sc.ca>
+
+description: |
+  Several I2C buses bit-banged on GPIOs, with one SCL line common to all of
+  them and one SDA line per bus. Each child node is a separate I2C bus.
+  Transfers are serialized, so only one bus clocks SCL at a time.
+
+properties:
+  compatible:
+    const: i2c-gpio-shared-scl
+
+  scl-gpios:
+    description: The shared SCL line, usually open drain.
+    maxItems: 1
+
+  i2c-gpio,delay-us:
+    description: Half the clock period in microseconds, as for i2c-gpio.
+    default: 5
+
+  i2c-gpio,timeout-ms:
+    description: Clock stretching timeout in milliseconds, as for i2c-gpio.
+    default: 100
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+patternProperties:
+  "^i2c@[0-9a-f]+$":
+    $ref: /schemas/i2c/i2c-controller.yaml#
+    unevaluatedProperties: false
+
+    properties:
+      reg:
+        description: Index of the bus, for naming only.
+        maxItems: 1
+
+      sda-gpios:
+        description: The SDA line of this bus, usually open drain.
+        maxItems: 1
+
+    required:
+      - reg
+      - sda-gpios
+
+required:
+  - compatible
+  - scl-gpios
+  - "#address-cells"
+  - "#size-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c-gpio-shared {
+        compatible = "i2c-gpio-shared-scl";
+        scl-gpios = <&gpio0 8 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+        i2c-gpio,delay-us = <5>;
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        i2c@0 {
+            reg = <0>;
+            sda-gpios = <&gpio0 9 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            fan-controller@2e {
+                compatible = "adi,adt7476";
+                reg = <0x2e>;
+            };
+        };
+
+        i2c@1 {
+            reg = <1>;
+            sda-gpios = <&gpio0 10 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+        };
+    };
-- 
2.55.0.windows.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 2/2] i2c: add driver for GPIO buses sharing one SCL line
  2026-10-18 19:43 [PATCH 0/2] i2c: add GPIO buses sharing one SCL line Ross Cawston
  2026-10-18 19:43 ` [PATCH 1/2] dt-bindings: " Ross Cawston
@ 2026-10-18 19:43 ` Ross Cawston
  2026-10-18 20:04   ` sashiko-bot
  2026-10-19  6:59   ` Uwe Kleine-König
  2026-10-18 21:20 ` [PATCH 0/2] i2c: add " Ross Cawston
  2 siblings, 2 replies; 7+ messages in thread
From: Ross Cawston @ 2026-10-18 19:43 UTC (permalink / raw)
  To: andi.shyti
  Cc: robh, krzk+dt, conor+dt, kees, gustavoars, linux-i2c, devicetree,
	linux-hardening, linux-kernel, Ross Cawston

Some boards bit-bang several I2C buses on GPIOs with one common SCL
line and one SDA line per bus. The MokerLink POE-10G07110GSM, an
RTL9303 PoE switch, clocks its fan controller, its two PoE controllers
and its SFP+ cage this way. i2c-gpio cannot be used, as each instance
would request the shared SCL GPIO.

Request SCL once and register one i2c-algo-bit adapter per SDA child
node. A mutex taken in pre_xfer and released in post_xfer serializes
the transfers across the buses, so only one bus clocks SCL at a time
and the idle SDA lines see clock pulses only without a START.

Signed-off-by: Ross Cawston <ross@r-sc.ca>
---
 MAINTAINERS                              |   7 +
 drivers/i2c/busses/Kconfig               |  12 ++
 drivers/i2c/busses/Makefile              |   1 +
 drivers/i2c/busses/i2c-gpio-shared-scl.c | 229 +++++++++++++++++++++++
 4 files changed, 249 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-gpio-shared-scl.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 782389bd5cd..9ac40ad4399 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12250,6 +12250,13 @@ S:	Maintained
 F:	Documentation/i2c/busses/i2c-nvidia-gpu.rst
 F:	drivers/i2c/busses/i2c-nvidia-gpu.c
 
+I2C GPIO BUSES SHARING SCL DRIVER
+M:	Ross Cawston <ross@r-sc.ca>
+L:	linux-i2c@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml
+F:	drivers/i2c/busses/i2c-gpio-shared-scl.c
+
 I2C MUXES
 M:	Peter Rosin <peda@lysator.liu.se>
 L:	linux-i2c@vger.kernel.org
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d7b89508311..caeed4385d1 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -691,6 +691,18 @@ config I2C_GPIO
 	  This is a very simple bitbanging I2C driver utilizing the
 	  arch-neutral GPIO API to control the SCL and SDA lines.
 
+config I2C_GPIO_SHARED_SCL
+	tristate "GPIO-based bitbanging I2C buses sharing one SCL line"
+	depends on GPIOLIB || COMPILE_TEST
+	select I2C_ALGOBIT
+	help
+	  Several bitbanged I2C buses with one common SCL GPIO and one SDA
+	  GPIO per bus, as found on some Realtek RTL930x switches. Each
+	  SDA line becomes its own I2C adapter; transfers are serialized.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called i2c-gpio-shared-scl.
+
 config I2C_GPIO_FAULT_INJECTOR
 	bool "GPIO-based fault injector"
 	depends on I2C_GPIO
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3755c54b3d8..c3b98e8abc4 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_I2C_EG20T)		+= i2c-eg20t.o
 obj-$(CONFIG_I2C_EMEV2)		+= i2c-emev2.o
 obj-$(CONFIG_I2C_EXYNOS5)	+= i2c-exynos5.o
 obj-$(CONFIG_I2C_GPIO)		+= i2c-gpio.o
+obj-$(CONFIG_I2C_GPIO_SHARED_SCL)	+= i2c-gpio-shared-scl.o
 obj-$(CONFIG_I2C_HIGHLANDER)	+= i2c-highlander.o
 obj-$(CONFIG_I2C_HISI)		+= i2c-hisi.o
 obj-$(CONFIG_I2C_HIX5HD2)	+= i2c-hix5hd2.o
diff --git a/drivers/i2c/busses/i2c-gpio-shared-scl.c b/drivers/i2c/busses/i2c-gpio-shared-scl.c
new file mode 100644
index 00000000000..6f7ee898392
--- /dev/null
+++ b/drivers/i2c/busses/i2c-gpio-shared-scl.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Bit-banged I2C buses that share one SCL line
+ *
+ * Some boards wire several I2C buses to GPIOs with one common SCL line and
+ * a separate SDA line per bus. The vendor firmware bit-bangs them in turn.
+ * i2c-gpio cannot describe this, because every bus would request the same
+ * SCL GPIO. This driver requests SCL once and registers one adapter per SDA
+ * line. Transfers on the adapters are serialized, so only one bus drives
+ * SCL at a time; devices on the other buses see SCL toggle while their SDA
+ * stays high, which is never a START condition, and ignore it.
+ *
+ * Copyright (C) 2026 Ross Cawston <ross@r-sc.ca>
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/i2c.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+struct i2c_gpio_shared_scl;
+
+struct i2c_gpio_shared_scl_bus {
+	struct i2c_gpio_shared_scl *parent;
+	struct gpio_desc *sda;
+	struct i2c_algo_bit_data bit_data;
+	struct i2c_adapter adap;
+	bool registered;
+};
+
+struct i2c_gpio_shared_scl {
+	struct device *dev;
+	struct gpio_desc *scl;
+	struct mutex lock;	/* one transfer on the shared SCL at a time */
+	unsigned int num_buses;
+	struct i2c_gpio_shared_scl_bus buses[] __counted_by(num_buses);
+};
+
+static void i2c_gpio_shared_scl_setsda(void *data, int state)
+{
+	struct i2c_gpio_shared_scl_bus *bus = data;
+
+	gpiod_set_value_cansleep(bus->sda, state);
+}
+
+static int i2c_gpio_shared_scl_getsda(void *data)
+{
+	struct i2c_gpio_shared_scl_bus *bus = data;
+
+	return gpiod_get_value_cansleep(bus->sda);
+}
+
+static void i2c_gpio_shared_scl_setscl(void *data, int state)
+{
+	struct i2c_gpio_shared_scl_bus *bus = data;
+
+	gpiod_set_value_cansleep(bus->parent->scl, state);
+}
+
+static int i2c_gpio_shared_scl_getscl(void *data)
+{
+	struct i2c_gpio_shared_scl_bus *bus = data;
+
+	return gpiod_get_value_cansleep(bus->parent->scl);
+}
+
+static int i2c_gpio_shared_scl_pre_xfer(struct i2c_adapter *adap)
+{
+	struct i2c_gpio_shared_scl_bus *bus = i2c_get_adapdata(adap);
+
+	mutex_lock(&bus->parent->lock);
+
+	return 0;
+}
+
+static void i2c_gpio_shared_scl_post_xfer(struct i2c_adapter *adap)
+{
+	struct i2c_gpio_shared_scl_bus *bus = i2c_get_adapdata(adap);
+
+	mutex_unlock(&bus->parent->lock);
+}
+
+static void i2c_gpio_shared_scl_del_buses(struct i2c_gpio_shared_scl *priv)
+{
+	unsigned int i;
+
+	for (i = 0; i < priv->num_buses; i++)
+		if (priv->buses[i].registered)
+			i2c_del_adapter(&priv->buses[i].adap);
+}
+
+static int i2c_gpio_shared_scl_add_bus(struct i2c_gpio_shared_scl *priv,
+				       struct i2c_gpio_shared_scl_bus *bus,
+				       struct fwnode_handle *child,
+				       u32 udelay, u32 timeout_ms)
+{
+	struct device *dev = priv->dev;
+	struct i2c_algo_bit_data *bit_data = &bus->bit_data;
+	struct i2c_adapter *adap = &bus->adap;
+	u32 reg;
+	int ret;
+
+	ret = fwnode_property_read_u32(child, "reg", &reg);
+	if (ret)
+		return dev_err_probe(dev, ret, "%pfwP: missing reg\n", child);
+
+	bus->parent = priv;
+	bus->sda = devm_fwnode_gpiod_get(dev, child, "sda",
+					 GPIOD_OUT_HIGH_OPEN_DRAIN,
+					 fwnode_get_name(child));
+	if (IS_ERR(bus->sda))
+		return dev_err_probe(dev, PTR_ERR(bus->sda),
+				     "%pfwP: cannot get SDA GPIO\n", child);
+
+	bit_data->data = bus;
+	bit_data->setsda = i2c_gpio_shared_scl_setsda;
+	bit_data->getsda = i2c_gpio_shared_scl_getsda;
+	bit_data->setscl = i2c_gpio_shared_scl_setscl;
+	bit_data->getscl = i2c_gpio_shared_scl_getscl;
+	bit_data->pre_xfer = i2c_gpio_shared_scl_pre_xfer;
+	bit_data->post_xfer = i2c_gpio_shared_scl_post_xfer;
+	bit_data->udelay = udelay;
+	bit_data->timeout = msecs_to_jiffies(timeout_ms);
+
+	adap->owner = THIS_MODULE;
+	adap->algo_data = bit_data;
+	adap->dev.parent = dev;
+	device_set_node(&adap->dev, child);
+	snprintf(adap->name, sizeof(adap->name), "%s SDA%u", dev_name(dev), reg);
+	i2c_set_adapdata(adap, bus);
+
+	ret = i2c_bit_add_bus(adap);
+	if (ret)
+		return dev_err_probe(dev, ret, "%pfwP: cannot add bus\n", child);
+
+	bus->registered = true;
+
+	return 0;
+}
+
+static int i2c_gpio_shared_scl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct i2c_gpio_shared_scl *priv;
+	u32 udelay = 5, timeout_ms = 100;
+	unsigned int num_buses, i = 0;
+	int ret;
+
+	num_buses = device_get_child_node_count(dev);
+	if (!num_buses)
+		return dev_err_probe(dev, -EINVAL, "no buses\n");
+
+	priv = devm_kzalloc(dev, struct_size(priv, buses, num_buses), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->dev = dev;
+	priv->num_buses = num_buses;
+	ret = devm_mutex_init(dev, &priv->lock);
+	if (ret)
+		return ret;
+
+	/* Same properties as i2c-gpio */
+	device_property_read_u32(dev, "i2c-gpio,delay-us", &udelay);
+	device_property_read_u32(dev, "i2c-gpio,timeout-ms", &timeout_ms);
+
+	priv->scl = devm_gpiod_get(dev, "scl", GPIOD_OUT_HIGH_OPEN_DRAIN);
+	if (IS_ERR(priv->scl))
+		return dev_err_probe(dev, PTR_ERR(priv->scl), "cannot get SCL GPIO\n");
+
+	platform_set_drvdata(pdev, priv);
+
+	device_for_each_child_node_scoped(dev, child) {
+		ret = i2c_gpio_shared_scl_add_bus(priv, &priv->buses[i++], child,
+						  udelay, timeout_ms);
+		if (ret) {
+			i2c_gpio_shared_scl_del_buses(priv);
+			return ret;
+		}
+	}
+
+	dev_info(dev, "%u buses on shared SCL, %u us half period\n", num_buses, udelay);
+
+	return 0;
+}
+
+static void i2c_gpio_shared_scl_remove(struct platform_device *pdev)
+{
+	i2c_gpio_shared_scl_del_buses(platform_get_drvdata(pdev));
+}
+
+static const struct of_device_id i2c_gpio_shared_scl_of_match[] = {
+	{ .compatible = "i2c-gpio-shared-scl" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, i2c_gpio_shared_scl_of_match);
+
+static struct platform_driver i2c_gpio_shared_scl_driver = {
+	.driver = {
+		.name = "i2c-gpio-shared-scl",
+		.of_match_table = i2c_gpio_shared_scl_of_match,
+	},
+	.probe = i2c_gpio_shared_scl_probe,
+	.remove = i2c_gpio_shared_scl_remove,
+};
+
+/* Registered early, like i2c-gpio, so the buses exist before their clients */
+static int __init i2c_gpio_shared_scl_init(void)
+{
+	return platform_driver_register(&i2c_gpio_shared_scl_driver);
+}
+subsys_initcall(i2c_gpio_shared_scl_init);
+
+static void __exit i2c_gpio_shared_scl_exit(void)
+{
+	platform_driver_unregister(&i2c_gpio_shared_scl_driver);
+}
+module_exit(i2c_gpio_shared_scl_exit);
+
+MODULE_DESCRIPTION("Bit-banged I2C buses sharing one SCL line");
+MODULE_LICENSE("GPL");
-- 
2.55.0.windows.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/2] dt-bindings: i2c: add GPIO buses sharing one SCL line
  2026-10-18 19:43 ` [PATCH 1/2] dt-bindings: " Ross Cawston
@ 2026-10-18 19:56   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-18 19:56 UTC (permalink / raw)
  To: Ross Cawston; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] dt-bindings: i2c: redundant i2c-gpio-shared-scl binding duplicates i2c-gpio
--

commit ee80c5111c8336e97eace1faedb4188f4c454089
Author: Ross Cawston <ross@r-sc.ca>

dt-bindings: i2c: add GPIO buses sharing one SCL line

This commit adds a Device Tree binding for bit-banged I2C buses that share
a single SCL GPIO line across child bus nodes, each with its own SDA GPIO
line.

> diff --git a/Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml b/Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml
> new file mode 100644
> index 0000000000000..e725120973ad2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-gpio-shared-scl.yaml
> @@ -0,0 +1,94 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/i2c/i2c-gpio-shared-scl.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Bit-banged I2C buses sharing one SCL line
> +
> +maintainers:
> +  - Ross Cawston <ross@r-sc.ca>
> +
> +description: |
> +  Several I2C buses bit-banged on GPIOs, with one SCL line common to all of
> +  them and one SDA line per bus. Each child node is a separate I2C bus.
> +  Transfers are serialized, so only one bus clocks SCL at a time.
> +
> +properties:
> +  compatible:
> +    const: i2c-gpio-shared-scl
> +
> +  scl-gpios:
> +    description: The shared SCL line, usually open drain.
> +    maxItems: 1
> +
> +  i2c-gpio,delay-us:
> +    description: Half the clock period in microseconds, as for i2c-gpio.
> +    default: 5
> +
> +  i2c-gpio,timeout-ms:
> +    description: Clock stretching timeout in milliseconds, as for i2c-gpio.
> +    default: 100
> +
> +  "#address-cells":
> +    const: 1
> +
> +  "#size-cells":
> +    const: 0
> +
> +patternProperties:
> +  "^i2c@[0-9a-f]+$":
> +    $ref: /schemas/i2c/i2c-controller.yaml#
> +    unevaluatedProperties: false
> +
> +    properties:
> +      reg:
> +        description: Index of the bus, for naming only.
> +        maxItems: 1
> +
> +      sda-gpios:
> +        description: The SDA line of this bus, usually open drain.
> +        maxItems: 1
> +
> +    required:
> +      - reg
> +      - sda-gpios

[Severity: Medium]
Is a separate i2c-gpio-shared-scl binding needed here? The existing
i2c-gpio binding in Documentation/devicetree/bindings/i2c/i2c-gpio.yaml and
the driver in drivers/i2c/busses/i2c-gpio.c already support multiple buses
sharing a single SCL GPIO line (added in commit 645c3d7d07fa ("i2c:
i2c-gpio: Enhance driver for buses with shared SCL") for Realtek switches).

In drivers/i2c/busses/i2c-gpio.c, i2c_gpio_create_scl() and
i2c_gpio_lookup_scl() match the scl-gpios phandle and specifier cells across
standard i2c-gpio nodes:

drivers/i2c/busses/i2c-gpio.c:i2c_gpio_create_scl() {
	if (fwnode) {
		ret = fwnode_property_get_reference_args(fwnode, "scl-gpios",
							 "#gpio-cells", 0, 0, &args);
		if (ret)
			/* try the ancient way */
			ret = fwnode_property_get_reference_args(fwnode, "gpios",
								 "#gpio-cells", 0, 1, &args);

		if (!ret) {
			if (args.nargs >= 1)
				sharable = true;
			else
				fwnode_handle_put(args.fwnode);
		}
	}
}

That existing mechanism refcounts the shared struct gpio_desc and serializes
transfers across all adapters sharing the SCL line via i2c_gpio_lock_ops and
scl->lock, while preserving standard per-bus i2c-gpio properties (such as
per-bus i2c-gpio,delay-us, i2c-gpio,timeout-ms, i2c-gpio,sda-output-only,
and i2c-gpio,scl-output-only).

Also, defining a virtual parent container node and child reg properties
described as "Index of the bus, for naming only" runs counter to
Documentation/devicetree/bindings/writing-bindings.rst regarding driver-only
container nodes and arbitrary software instance indices. Could standard
i2c-gpio nodes referencing the same scl-gpios phandle be used instead?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261018194315.945-1-ross@r-sc.ca?part=1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/2] i2c: add driver for GPIO buses sharing one SCL line
  2026-10-18 19:43 ` [PATCH 2/2] i2c: add driver for " Ross Cawston
@ 2026-10-18 20:04   ` sashiko-bot
  2026-10-19  6:59   ` Uwe Kleine-König
  1 sibling, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-18 20:04 UTC (permalink / raw)
  To: Ross Cawston; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] i2c: i2c-gpio-shared-scl: sleeping mutex in pre_xfer() breaks atomic xfer
- [Medium] i2c: i2c-gpio-shared-scl: buses registered before all SDA GPIOs driven high
- [Medium] i2c: i2c-gpio-shared-scl: duplicates shared-SCL support in i2c-gpio
--

commit 79f8386f6a7c7a3c6b945869078cb1a7bbcc4a64
Author: Ross Cawston <ross@r-sc.ca>

i2c: add driver for GPIO buses sharing one SCL line

This commit adds a driver for GPIO-bitbanged I2C buses that share a
single SCL GPIO line across multiple SDA child nodes, serializing
transfers with a mutex in pre_xfer and post_xfer.

>     i2c: add driver for GPIO buses sharing one SCL line
>     
>     Some boards bit-bang several I2C buses on GPIOs with one common SCL
>     line and one SDA line per bus. The MokerLink POE-10G07110GSM, an
>     RTL9303 PoE switch, clocks its fan controller, its two PoE controllers
>     and its SFP+ cage this way. i2c-gpio cannot be used, as each instance
>     would request the shared SCL GPIO.
>     
>     Request SCL once and register one i2c-algo-bit adapter per SDA child
>     node. A mutex taken in pre_xfer and released in post_xfer serializes
>     the transfers across the buses, so only one bus clocks SCL at a time
>     and the idle SDA lines see clock pulses only without a START.
>     
>     Signed-off-by: Ross Cawston <ross@r-sc.ca>

> diff --git a/drivers/i2c/busses/i2c-gpio-shared-scl.c b/drivers/i2c/busses/i2c-gpio-shared-scl.c
> new file mode 100644
> index 0000000000000..6f7ee898392a5
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-gpio-shared-scl.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Bit-banged I2C buses that share one SCL line
> + *
> + * Some boards wire several I2C buses to GPIOs with one common SCL line and
> + * a separate SDA line per bus. The vendor firmware bit-bangs them in turn.
> + * i2c-gpio cannot describe this, because every bus would request the same
> + * SCL GPIO. This driver requests SCL once and registers one adapter per SDA
> + * line. Transfers on the adapters are serialized, so only one bus drives
> + * SCL at a time; devices on the other buses see SCL toggle while their SDA
> + * stays high, which is never a START condition, and ignore it.

[Severity: Medium]
The commit message and file header state that i2c-gpio cannot be used
because each instance would request the shared SCL GPIO. Is a separate
driver needed here given that drivers/i2c/busses/i2c-gpio.c already
supports shared SCL lines since commit 645c3d7d07fa ("i2c: i2c-gpio:
Enhance driver for buses with shared SCL")?

In drivers/i2c/busses/i2c-gpio.c, i2c_gpio_lookup_scl() matches shared
scl-gpios phandles and GPIO cells across multiple i2c-gpio DT nodes via
i2c_gpio_scl_matches(), requests the SCL descriptor once, reference-counts
it across bus instances in i2c_gpio_scl_list, sets bit_data->skip_bit_test,
and serializes transfers across all buses sharing that SCL line via
i2c_gpio_lock_ops:

drivers/i2c/busses/i2c-gpio.c:i2c_gpio_lookup_scl() {
	scoped_guard(mutex, &i2c_gpio_scl_list_lock) {
		list_for_each_entry(scl, &i2c_gpio_scl_list, list) {
			if (!i2c_gpio_scl_matches(scl, new_scl))
				continue;

			i2c_gpio_free_scl(new_scl);
			if (!scl->gpio)
				return ERR_PTR(-EPROBE_DEFER);

			refcount_inc(&scl->ref);
			if (scl->args.fwnode)
				dev_dbg(dev, "reusing shared SCL (%pfwP)\n", scl->args.fwnode);

			return scl;
		}
		list_add(&new_scl->list, &i2c_gpio_scl_list);
	}
}

Can the MokerLink RTL9303 switch use the existing i2c-gpio driver and
binding instead?

[ ... ]

> +static int i2c_gpio_shared_scl_pre_xfer(struct i2c_adapter *adap)
> +{
> +	struct i2c_gpio_shared_scl_bus *bus = i2c_get_adapdata(adap);
> +
> +	mutex_lock(&bus->parent->lock);
> +
> +	return 0;
> +}
> +
> +static void i2c_gpio_shared_scl_post_xfer(struct i2c_adapter *adap)
> +{
> +	struct i2c_gpio_shared_scl_bus *bus = i2c_get_adapdata(adap);
> +
> +	mutex_unlock(&bus->parent->lock);
> +}

[Severity: High]
Will taking bus->parent->lock in i2c_gpio_shared_scl_pre_xfer() sleep in
atomic context during atomic I2C transfers, and allow sibling buses to
interleave during multi-transfer locked sequences?

i2c_gpio_shared_scl_add_bus() registers each adapter with
i2c_bit_add_bus(), which sets adap->algo to i2c_bit_algo (whose
xfer_atomic callback is bit_xfer_atomic()) while leaving adap->lock_ops
unset.

When i2c_in_atomic_xfer_mode() is true (such as during system shutdown,
reboot, poweroff, or panic), __i2c_lock_bus_helper() calls
i2c_trylock_bus(), which only trylocks the per-adapter adap->bus_lock and
cannot see if a sibling bus holds bus->parent->lock. __i2c_transfer()
then invokes:

bit_xfer_atomic()
  -> bit_xfer()
    -> adap->pre_xfer()
      -> i2c_gpio_shared_scl_pre_xfer()
        -> mutex_lock(&bus->parent->lock)

Even though bit_data->can_do_atomic is false, bit_xfer_atomic() only logs
a warning and still proceeds into bit_xfer():

drivers/i2c/algos/i2c-algo-bit.c:bit_xfer_atomic() {
	struct i2c_algo_bit_data *adap = i2c_adap->algo_data;

	if (!adap->can_do_atomic)
		dev_warn(&i2c_adap->dev, "not flagged for atomic transfers\n");

	return bit_xfer(i2c_adap, msgs, num);
}

This unconditionally calls mutex_lock() (followed by
gpiod_set_value_cansleep() and gpiod_get_value_cansleep()) in atomic
context.

In addition, callers that lock an adapter across multiple __i2c_transfer()
calls via i2c_lock_bus() (for example, i2c_transfer_rollball() in
drivers/net/mdio/mdio-i2c.c for SFP+ cages, or i2c-mux operations) only
hold the per-adapter adap->bus_lock while bus->parent->lock is released in
i2c_gpio_shared_scl_post_xfer() after each individual transfer, allowing a
sibling bus to clock the shared SCL line mid-sequence. Normal transfers
also nest struct mutex (bus->parent->lock) inside rt_mutex
(adap->bus_lock).

Should this instead implement struct i2c_lock_operations (lock_bus,
trylock_bus, unlock_bus) on adap->lock_ops backed by an rt_mutex (and set
bit_data->can_do_atomic based on gpiod_cansleep()), similar to how
drivers/i2c/busses/i2c-gpio.c handles shared SCL locking?

[ ... ]

> +static int i2c_gpio_shared_scl_add_bus(struct i2c_gpio_shared_scl *priv,
> +				       struct i2c_gpio_shared_scl_bus *bus,
> +				       struct fwnode_handle *child,
> +				       u32 udelay, u32 timeout_ms)
> +{
> +	struct device *dev = priv->dev;
> +	struct i2c_algo_bit_data *bit_data = &bus->bit_data;
> +	struct i2c_adapter *adap = &bus->adap;
> +	u32 reg;
> +	int ret;
> +
> +	ret = fwnode_property_read_u32(child, "reg", &reg);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "%pfwP: missing reg\n", child);
> +
> +	bus->parent = priv;
> +	bus->sda = devm_fwnode_gpiod_get(dev, child, "sda",
> +					 GPIOD_OUT_HIGH_OPEN_DRAIN,
> +					 fwnode_get_name(child));
> +	if (IS_ERR(bus->sda))
> +		return dev_err_probe(dev, PTR_ERR(bus->sda),
> +				     "%pfwP: cannot get SDA GPIO\n", child);
> +
> +	bit_data->data = bus;
> +	bit_data->setsda = i2c_gpio_shared_scl_setsda;
> +	bit_data->getsda = i2c_gpio_shared_scl_getsda;
> +	bit_data->setscl = i2c_gpio_shared_scl_setscl;
> +	bit_data->getscl = i2c_gpio_shared_scl_getscl;
> +	bit_data->pre_xfer = i2c_gpio_shared_scl_pre_xfer;
> +	bit_data->post_xfer = i2c_gpio_shared_scl_post_xfer;
> +	bit_data->udelay = udelay;
> +	bit_data->timeout = msecs_to_jiffies(timeout_ms);
> +
> +	adap->owner = THIS_MODULE;
> +	adap->algo_data = bit_data;
> +	adap->dev.parent = dev;
> +	device_set_node(&adap->dev, child);
> +	snprintf(adap->name, sizeof(adap->name), "%s SDA%u", dev_name(dev), reg);
> +	i2c_set_adapdata(adap, bus);
> +
> +	ret = i2c_bit_add_bus(adap);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "%pfwP: cannot add bus\n", child);
> +
> +	bus->registered = true;
> +
> +	return 0;
> +}
> +
> +static int i2c_gpio_shared_scl_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	device_for_each_child_node_scoped(dev, child) {
> +		ret = i2c_gpio_shared_scl_add_bus(priv, &priv->buses[i++], child,
> +						  udelay, timeout_ms);
> +		if (ret) {
> +			i2c_gpio_shared_scl_del_buses(priv);
> +			return ret;
> +		}
> +	}

[Severity: Medium]
Can registering each adapter in the same loop that requests the per-bus
SDA GPIOs cause spurious START or STOP conditions on sibling buses that
have not yet been initialized?

For devices on idle buses to ignore activity on the shared priv->scl line,
their SDA lines must already be driven high (GPIOD_OUT_HIGH_OPEN_DRAIN).
However, i2c_gpio_shared_scl_probe() calls i2c_gpio_shared_scl_add_bus()
in a single pass, which calls i2c_bit_add_bus() for priv->buses[0] before
priv->buses[1..num_buses - 1] have requested their SDA GPIOs.

During i2c_bit_add_bus(&priv->buses[0].adap), the shared SCL line is
clocked immediately if i2c_algo_bit.bit_test is set or when child client
devices on bus 0 probe via of_i2c_register_devices() and perform transfers
while sibling SDA lines are still in their pre-probe state. Furthermore,
once bus 0 is registered, transfers on bus 0 can toggle priv->scl under
priv->lock concurrently while subsequent iterations of the loop call
devm_fwnode_gpiod_get(..., GPIOD_OUT_HIGH_OPEN_DRAIN, ...) for later
buses without holding priv->lock; transitioning a sibling SDA line while
priv->scl happens to be high creates a START or STOP condition on that
sibling bus.

Would it be safer to request and configure all child SDA GPIOs (and
validate DT properties) in a first pass before registering any of the
adapters with i2c_bit_add_bus()?

[ ... ]

>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261018194315.945-1-ross@r-sc.ca?part=2

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 0/2] i2c: add GPIO buses sharing one SCL line
  2026-10-18 19:43 [PATCH 0/2] i2c: add GPIO buses sharing one SCL line Ross Cawston
  2026-10-18 19:43 ` [PATCH 1/2] dt-bindings: " Ross Cawston
  2026-10-18 19:43 ` [PATCH 2/2] i2c: add driver for " Ross Cawston
@ 2026-10-18 21:20 ` Ross Cawston
  2 siblings, 0 replies; 7+ messages in thread
From: Ross Cawston @ 2026-10-18 21:20 UTC (permalink / raw)
  To: andi.shyti
  Cc: robh, krzk+dt, conor+dt, kees, gustavoars, linux-i2c, devicetree,
	linux-hardening, linux-kernel, Ross Cawston

Hi,

Please drop this series. As the automated review points out, i2c-gpio
has handled buses that share one SCL line since commit 645c3d7d07fa
("i2c: i2c-gpio: Enhance driver for buses with shared SCL") in v7.3,
which I had missed. The MokerLink POE-10G07110GSM will use i2c-gpio
instead.

Sorry for the noise.

Ross

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/2] i2c: add driver for GPIO buses sharing one SCL line
  2026-10-18 19:43 ` [PATCH 2/2] i2c: add driver for " Ross Cawston
  2026-10-18 20:04   ` sashiko-bot
@ 2026-10-19  6:59   ` Uwe Kleine-König
  1 sibling, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2026-10-19  6:59 UTC (permalink / raw)
  To: Ross Cawston
  Cc: andi.shyti, robh, krzk+dt, conor+dt, kees, gustavoars, linux-i2c,
	devicetree, linux-hardening, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 337 bytes --]

On Sun, Oct 18, 2026 at 12:43:15PM -0700, Ross Cawston wrote:
> +#include <linux/mod_devicetable.h>
> [...]
> +#include <linux/platform_device.h>

<linux/platform_device.h> already provides of_device_id, so there is no
need to also #include <linux/mod_devicetable.h> which also pulls in a
lot of unneeded dependencies.

Best regards
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-19  6:59 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-18 19:43 [PATCH 0/2] i2c: add GPIO buses sharing one SCL line Ross Cawston
2026-10-18 19:43 ` [PATCH 1/2] dt-bindings: " Ross Cawston
2026-10-18 19:56   ` sashiko-bot
2026-10-18 19:43 ` [PATCH 2/2] i2c: add driver for " Ross Cawston
2026-10-18 20:04   ` sashiko-bot
2026-10-19  6:59   ` Uwe Kleine-König
2026-10-18 21:20 ` [PATCH 0/2] i2c: add " Ross Cawston

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.