realtek: i2c: replace shared gpio driver with upstream

Upstream will support I2C GPIO based buses with shared SCL shortly [1].
Drop downstream driver, provide backport and adapt DTS.

[1] https://lore.kernel.org/linux-i2c/20260714162915.3018703-1-markus.stockhausen@gmx.de/

Link: https://github.com/openwrt/openwrt/pull/24269
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
This commit is contained in:
Markus Stockhausen
2026-07-20 18:50:01 +02:00
parent f31b65a870
commit b73b209693
16 changed files with 509 additions and 358 deletions
@@ -66,21 +66,22 @@
};
};
i2c-gpio-shared {
compatible = "i2c-gpio-shared";
i2c0: i2c-gpio-0 {
compatible = "i2c-gpio";
sda-gpios = <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio1 31 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c0: i2c@0 {
sda-gpios = <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c1: i2c@1 {
sda-gpios = <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c1: i2c-gpio-1 {
compatible = "i2c-gpio";
sda-gpios = <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio1 31 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
};
sfp0: sfp-p9 {
@@ -41,21 +41,22 @@
};
};
i2c-gpio-shared {
compatible = "i2c-gpio-shared";
i2c0: i2c-gpio-0 {
compatible = "i2c-gpio";
sda-gpios = <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio1 31 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c0: i2c@0 {
sda-gpios = <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c1: i2c@1 {
sda-gpios = <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c1: i2c-gpio-1 {
compatible = "i2c-gpio";
sda-gpios = <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio1 31 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
};
sfp0: sfp-p9 {
@@ -51,26 +51,10 @@
linux,input-type = <EV_KEY>;
};
};
};
/delete-node/ i2c-gpio-0;
/delete-node/ i2c-gpio-1;
i2c-gpio-shared {
compatible = "i2c-gpio-shared";
scl-gpios = <&gpio1 3 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
#address-cells = <1>;
#size-cells = <0>;
i2c0: i2c@0 {
sda-gpios = <&gpio1 4 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c1: i2c@1 {
sda-gpios = <&gpio1 10 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
};
&i2c1 {
scl-gpios = <&gpio1 3 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
};
&i2c2 {
@@ -6,40 +6,40 @@
compatible = "hpe,1920-48g", "realtek,rtl8393-soc";
model = "HPE 1920-48G (JG927A)";
i2c-gpio-shared0 {
compatible = "i2c-gpio-shared";
i2c0: i2c-gpio-0 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 18 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 17 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
i2c0: i2c@0 {
sda-gpios = <&gpio0 18 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c2: i2c@2 {
sda-gpios = <&gpio0 21 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
};
i2c-gpio-shared1 {
compatible = "i2c-gpio-shared";
i2c1: i2c-gpio-1 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 2 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c1: i2c@1 {
sda-gpios = <&gpio0 2 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c2: i2c-gpio-2 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 21 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 17 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c3: i2c@3 {
sda-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c3: i2c-gpio-3 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
#address-cells = <1>;
#size-cells = <0>;
};
sfp0: sfp-p49 {
@@ -33,31 +33,41 @@
#cooling-cells = <2>;
};
i2c-gpio-shared {
compatible = "i2c-gpio-shared";
i2c0: i2c-gpio-0 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 17 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c0: i2c@0 {
sda-gpios = <&gpio0 17 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
};
i2c1: i2c-gpio-1 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 18 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c1: i2c@1 {
sda-gpios = <&gpio0 18 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
};
i2c2: i2c@2 {
sda-gpios = <&gpio0 19 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
};
i2c2: i2c-gpio-2 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 19 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c3: i2c@3 {
sda-gpios = <&gpio0 20 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
};
i2c3: i2c-gpio-3 {
compatible = "i2c-gpio";
sda-gpios = <&gpio0 20 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
#address-cells = <1>;
#size-cells = <0>;
};
i2c4: i2c-gpio-4 {
@@ -1,51 +0,0 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: http://devicetree.org/schemas/i2c/i2c-gpio-shared.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: mulitple GPIO bitbanged I2C busses with shared SCL
maintainers:
- Markus Stockhausen <markus.stockhausen@gmx.de>
allOf:
- $ref: /schemas/i2c/i2c-controller.yaml#
properties:
compatible:
items:
- const: i2c-gpio-shared
scl-gpios:
description:
gpio used for the shared scl signal, this should be flagged as
active high using open drain with (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)
from <dt-bindings/gpio/gpio.h> since the signal is by definition
open drain.
maxItems: 1
examples:
- |
#include <dt-bindings/gpio/gpio.h>
i2c-gpio-shared {
compatible = "i2c-gpio-shared";
scl-gpios = <&gpio1 31 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
#address-cells = <1>;
#size-cells = <0>;
i2c0: i2c@0 {
sda-gpios = <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
i2c1: i2c@1 {
sda-gpios = <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>;
};
};
required:
- compatible
- scl-gpios
@@ -1,177 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Bitbanging driver for multiple I2C busses with shared SCL pin using the GPIO API
* Copyright (c) 2025 Markus Stockhausen <markus.stockhausen at gmx.de>
*/
#include <linux/i2c-algo-bit.h>
#include <linux/gpio/consumer.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#define GPIO_SHARED_MAX_BUS 4
struct gpio_shared_ctx;
struct gpio_shared_bus {
int num;
struct gpio_desc *sda;
struct i2c_adapter adap;
struct i2c_algo_bit_data bit_data;
struct gpio_shared_ctx *ctx;
};
struct gpio_shared_ctx {
struct device *dev;
struct gpio_desc *scl;
struct mutex lock;
struct gpio_shared_bus bus[GPIO_SHARED_MAX_BUS];
};
static void gpio_shared_setsda(void *data, int state)
{
struct gpio_shared_bus *bus = data;
gpiod_set_value_cansleep(bus->sda, state);
}
static void gpio_shared_setscl(void *data, int state)
{
struct gpio_shared_bus *bus = data;
struct gpio_shared_ctx *ctx = bus->ctx;
gpiod_set_value_cansleep(ctx->scl, state);
}
static int gpio_shared_getsda(void *data)
{
struct gpio_shared_bus *bus = data;
return gpiod_get_value_cansleep(bus->sda);
}
static int gpio_shared_getscl(void *data)
{
struct gpio_shared_bus *bus = data;
struct gpio_shared_ctx *ctx = bus->ctx;
return gpiod_get_value_cansleep(ctx->scl);
}
static int gpio_shared_pre_xfer(struct i2c_adapter *adap)
{
struct gpio_shared_bus *bus = container_of(adap, typeof(*bus), adap);
struct gpio_shared_ctx *ctx = bus->ctx;
mutex_lock(&ctx->lock);
dev_dbg(ctx->dev, "lock before transfer to bus %d\n", bus->num);
return 0;
}
static void gpio_shared_post_xfer(struct i2c_adapter *adap)
{
struct gpio_shared_bus *bus = container_of(adap, typeof(*bus), adap);
struct gpio_shared_ctx *ctx = bus->ctx;
dev_dbg(ctx->dev, "unlock after transfer to bus %d\n", bus->num);
mutex_unlock(&ctx->lock);
}
static int gpio_shared_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpio_shared_ctx *ctx;
int msecs, ret, bus_num = -1;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->dev = dev;
mutex_init(&ctx->lock);
ctx->scl = devm_gpiod_get(dev, "scl", GPIOD_OUT_HIGH_OPEN_DRAIN);
if (IS_ERR(ctx->scl))
return dev_err_probe(dev, PTR_ERR(ctx->scl), "shared SCL node not found\n");
if (device_get_child_node_count(dev) > GPIO_SHARED_MAX_BUS)
return dev_err_probe(dev, -EINVAL, "Too many channels\n");
device_for_each_child_node_scoped(dev, child) {
struct gpio_shared_bus *bus = &ctx->bus[++bus_num];
struct i2c_adapter *adap = &bus->adap;
struct i2c_algo_bit_data *bit_data = &bus->bit_data;
bus->sda = devm_fwnode_gpiod_get(dev, child, "sda", GPIOD_OUT_HIGH_OPEN_DRAIN,
fwnode_get_name(child));
if (IS_ERR(bus->sda)) {
dev_err(dev, "SDA node for bus %d not found\n", bus_num);
continue;
}
bus->num = bus_num;
bus->ctx = ctx;
bit_data->data = bus;
bit_data->setsda = gpio_shared_setsda;
bit_data->setscl = gpio_shared_setscl;
bit_data->pre_xfer = gpio_shared_pre_xfer;
bit_data->post_xfer = gpio_shared_post_xfer;
if (fwnode_property_read_u32(child, "i2c-gpio,delay-us", &bit_data->udelay))
bit_data->udelay = 5;
if (!fwnode_property_read_bool(child, "i2c-gpio,sda-output-only"))
bit_data->getsda = gpio_shared_getsda;
if (!device_property_read_bool(dev, "i2c-gpio,scl-output-only"))
bit_data->getscl = gpio_shared_getscl;
if (!device_property_read_u32(dev, "i2c-gpio,timeout-ms", &msecs))
bit_data->timeout = msecs_to_jiffies(msecs);
else
bit_data->timeout = HZ / 10; /* 100ms */
if (gpiod_cansleep(bus->sda) || gpiod_cansleep(ctx->scl))
dev_warn(dev, "Slow GPIO pins might wreak havoc into I2C/SMBus bus timing");
else
bit_data->can_do_atomic = true;
adap->owner = THIS_MODULE;
strscpy(adap->name, KBUILD_MODNAME, sizeof(adap->name));
adap->dev.parent = dev;
device_set_node(&adap->dev, child);
adap->algo_data = &bus->bit_data;
adap->class = I2C_CLASS_HWMON;
ret = i2c_bit_add_bus(adap);
if (ret)
return ret;
dev_info(dev, "shared I2C bus %u using lines %u (SDA) and %u (SCL) delay=%d\n",
bus_num, desc_to_gpio(bus->sda), desc_to_gpio(ctx->scl),
bit_data->udelay);
}
return 0;
}
static const struct of_device_id gpio_shared_of_match[] = {
{ .compatible = "i2c-gpio-shared" },
{}
};
MODULE_DEVICE_TABLE(of, gpio_shared_of_match);
static struct platform_driver gpio_shared_driver = {
.probe = gpio_shared_probe,
.driver = {
.name = "i2c-gpio-shared",
.of_match_table = gpio_shared_of_match,
},
};
module_platform_driver(gpio_shared_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen at gmx.de>");
MODULE_DESCRIPTION("bitbanging multi I2C driver for shared SCL");
@@ -0,0 +1,46 @@
From c25b80536efdffab6a73675ae153175348b4521a Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Mon, 13 Jul 2026 19:54:03 +0200
Subject: [PATCH 1/2] i2c: algo: bit: Allow to skip bit test
An I2C bus can make use of shared resources. E.g. two GPIO based buses
that share a single SCL line. To synchronize access to the bus the driver
might use locking with the help of i2c_lock_operations(). While this
works for normal transfers it is not available during initialization.
Especially if i2c-algo-bit module is loaded with parameter bit_test
it will issue some basic sanity checks that will access the bus without
locking. This might interfere badly with concurrent transfers. Even
if these are well synchronized via locks.
Allow the consumer of an algorithm to override if the bit_test is allowed
or not. For this add a new boolean attribute named skip_bit_test to
i2c_algo_bit_data. If set the test is not run.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/i2c/algos/i2c-algo-bit.c | 2 +-
include/linux/i2c-algo-bit.h | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
--- a/drivers/i2c/algos/i2c-algo-bit.c
+++ b/drivers/i2c/algos/i2c-algo-bit.c
@@ -638,7 +638,7 @@ static int __i2c_bit_add_bus(struct i2c_
struct i2c_algo_bit_data *bit_adap = adap->algo_data;
int ret;
- if (bit_test) {
+ if (bit_test && !bit_adap->skip_bit_test) {
ret = test_bus(adap);
if (bit_test >= 2 && ret < 0)
return -ENODEV;
--- a/include/linux/i2c-algo-bit.h
+++ b/include/linux/i2c-algo-bit.h
@@ -34,6 +34,7 @@ struct i2c_algo_bit_data {
maximum 50 us for SMBus */
int timeout; /* in jiffies */
bool can_do_atomic; /* callbacks don't sleep, we can be atomic */
+ bool skip_bit_test; /* override bit_test module parameter */
};
int i2c_bit_add_bus(struct i2c_adapter *);
@@ -0,0 +1,384 @@
From 7899b3e8af418d5ad6edd3cffb77df4800af52da Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Mon, 18 May 2026 18:07:21 +0200
Subject: [PATCH] i2c: i2c-gpio: Enhance driver for buses with shared SCL
Some lower end hardware (especially Realtek based switches) are
designed with multiple I2C buses that share a single clock line.
E.g. the D-Link DGS-1250-28X realizes 4 I2C SFP busses with 5 GPIOs.
Enhance the i2c-gpio driver so it can handle such hardware designs.
- Detect shared SCL GPIOs that are used by multiple I2C buses in the
devicetree by using a "unique identifier". This is basically the
phandle and all additional cells.
- The first probing instance allocates and requests the shared SCL
GPIO with an associated rt_mutex. Subsequent instances detect the
existing entry via the identifier and increment a reference count
to reuse the descriptor.
- All data transfers are serialized via custom lock_ops that handle
both the standard adapter bus lock and the shared SCL mutex. This
ensures mutual exclusion across adapters sharing the clock line.
- This shared SCL detection works only for dts based systems where
the GPIO node has at least one cell (usually the pin). GPIOs in
legacy systems without devicetree will be handled individudally
as before.
This patch was successfully tested on Linksys LGS310C that has two
SFP slots with two GPIO based I2C buses that share a single SCL.
Test environment: OpenWrt snapshot ported to kernel 6.19.14
including CONFIG_GPIO_SHARED=y and CONFIG_GPIO_SHARED_PROXY=y.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Tested-by: Sander Vanheule <sander@svanheule.net>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
---
drivers/i2c/busses/i2c-gpio.c | 223 +++++++++++++++++++++++++++++++---
1 file changed, 209 insertions(+), 14 deletions(-)
--- a/drivers/i2c/busses/i2c-gpio.c
+++ b/drivers/i2c/busses/i2c-gpio.c
@@ -18,9 +18,21 @@
#include <linux/property.h>
#include <linux/slab.h>
+static LIST_HEAD(i2c_gpio_scl_list);
+static DEFINE_MUTEX(i2c_gpio_scl_list_lock);
+static struct lock_class_key i2c_gpio_scl_lock_key;
+
+struct i2c_gpio_scl_data {
+ struct fwnode_reference_args args;
+ struct gpio_desc *gpio;
+ struct rt_mutex lock;
+ struct list_head list;
+ refcount_t ref;
+};
+
struct i2c_gpio_private_data {
struct gpio_desc *sda;
- struct gpio_desc *scl;
+ struct i2c_gpio_scl_data *scl;
struct i2c_adapter adap;
struct i2c_algo_bit_data bit_data;
struct i2c_gpio_platform_data pdata;
@@ -31,6 +43,11 @@ struct i2c_gpio_private_data {
#endif
};
+static inline struct i2c_gpio_private_data *adap_to_priv(struct i2c_adapter *adap)
+{
+ return container_of(adap, struct i2c_gpio_private_data, adap);
+}
+
/*
* Toggle SDA by changing the output value of the pin. This is only
* valid for pins configured as open drain (i.e. setting the value
@@ -53,7 +70,7 @@ static void i2c_gpio_setscl_val(void *da
{
struct i2c_gpio_private_data *priv = data;
- gpiod_set_value_cansleep(priv->scl, state);
+ gpiod_set_value_cansleep(priv->scl->gpio, state);
}
static int i2c_gpio_getsda(void *data)
@@ -67,9 +84,41 @@ static int i2c_gpio_getscl(void *data)
{
struct i2c_gpio_private_data *priv = data;
- return gpiod_get_value_cansleep(priv->scl);
+ return gpiod_get_value_cansleep(priv->scl->gpio);
+}
+
+static void i2c_gpio_lock_bus(struct i2c_adapter *adap, unsigned int flags)
+{
+ /* Take care about adapter lock. See i2c_adapter_lock_bus() and others. */
+ rt_mutex_lock_nested(&adap->bus_lock, i2c_adapter_depth(adap));
+ rt_mutex_lock(&adap_to_priv(adap)->scl->lock);
+}
+
+static int i2c_gpio_trylock_bus(struct i2c_adapter *adap, unsigned int flags)
+{
+ if (!rt_mutex_trylock(&adap->bus_lock))
+ return 0;
+
+ if (!rt_mutex_trylock(&adap_to_priv(adap)->scl->lock)) {
+ rt_mutex_unlock(&adap->bus_lock);
+ return 0;
+ }
+
+ return 1;
+}
+
+static void i2c_gpio_unlock_bus(struct i2c_adapter *adap, unsigned int flags)
+{
+ rt_mutex_unlock(&adap_to_priv(adap)->scl->lock);
+ rt_mutex_unlock(&adap->bus_lock);
}
+static const struct i2c_lock_operations i2c_gpio_lock_ops = {
+ .lock_bus = i2c_gpio_lock_bus,
+ .trylock_bus = i2c_gpio_trylock_bus,
+ .unlock_bus = i2c_gpio_unlock_bus,
+};
+
#ifdef CONFIG_I2C_GPIO_FAULT_INJECTOR
#define setsda(bd, val) ((bd)->setsda((bd)->data, val))
@@ -165,14 +214,14 @@ DEFINE_DEBUGFS_ATTRIBUTE(fops_incomplete
static int i2c_gpio_fi_act_on_scl_irq(struct i2c_gpio_private_data *priv,
irqreturn_t handler(int, void*))
{
- int ret, irq = gpiod_to_irq(priv->scl);
+ int ret, irq = gpiod_to_irq(priv->scl->gpio);
if (irq < 0)
return irq;
i2c_lock_bus(&priv->adap, I2C_LOCK_ROOT_ADAPTER);
- ret = gpiod_direction_input(priv->scl);
+ ret = gpiod_direction_input(priv->scl->gpio);
if (ret)
goto unlock;
@@ -187,7 +236,7 @@ static int i2c_gpio_fi_act_on_scl_irq(st
free_irq(irq, priv);
output:
- ret = gpiod_direction_output(priv->scl, 1) ?: ret;
+ ret = gpiod_direction_output(priv->scl->gpio, 1) ?: ret;
unlock:
i2c_unlock_bus(&priv->adap, I2C_LOCK_ROOT_ADAPTER);
@@ -308,13 +357,17 @@ static struct gpio_desc *i2c_gpio_get_de
struct gpio_desc *retdesc;
int ret;
- retdesc = devm_gpiod_get(dev, con_id, gflags);
+ /*
+ * Don't use resource-managed functions. SCL may be shared across adapters and has
+ * its own lifetime management. SDA uses the same path for consistency.
+ */
+ retdesc = gpiod_get(dev, con_id, gflags);
if (!IS_ERR(retdesc)) {
dev_dbg(dev, "got GPIO from name %s\n", con_id);
return retdesc;
}
- retdesc = devm_gpiod_get_index(dev, NULL, index, gflags);
+ retdesc = gpiod_get_index(dev, NULL, index, gflags);
if (!IS_ERR(retdesc)) {
dev_dbg(dev, "got GPIO from index %u\n", index);
return retdesc;
@@ -336,6 +389,134 @@ static struct gpio_desc *i2c_gpio_get_de
return retdesc;
}
+static struct i2c_gpio_scl_data *i2c_gpio_create_scl(struct device *dev)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct fwnode_reference_args args;
+ struct i2c_gpio_scl_data *scl;
+ bool sharable = false;
+ int ret;
+
+ /*
+ * SCL gpios can be shared if they are defined in the devicetree and have at least one
+ * cell in addition to the phandle. This is usually the pin. To support complex designs
+ * with arbitrary of_xlate() mappings handle the node cells opaque.
+ */
+ 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);
+ }
+ }
+
+ scl = kzalloc(sizeof(*scl), GFP_KERNEL);
+ if (!scl) {
+ if (sharable)
+ fwnode_handle_put(args.fwnode);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ if (sharable) {
+ scl->args.fwnode = args.fwnode;
+ scl->args.nargs = args.nargs;
+ memcpy(scl->args.args, args.args, sizeof(args.args[0]) * args.nargs);
+ }
+
+ rt_mutex_init(&scl->lock);
+ lockdep_set_class(&scl->lock, &i2c_gpio_scl_lock_key);
+ refcount_set(&scl->ref, 1);
+
+ return scl;
+}
+
+static void i2c_gpio_free_scl(struct i2c_gpio_scl_data *scl)
+{
+ if (scl->args.fwnode)
+ fwnode_handle_put(scl->args.fwnode);
+ kfree(scl);
+}
+
+static bool i2c_gpio_scl_matches(struct i2c_gpio_scl_data *a, struct i2c_gpio_scl_data *b)
+{
+ if (!a->args.fwnode || !b->args.fwnode ||
+ a->args.fwnode != b->args.fwnode || a->args.nargs != b->args.nargs)
+ return false;
+
+ return memcmp(a->args.args, b->args.args, sizeof(a->args.args[0]) * a->args.nargs) == 0;
+}
+
+/*
+ * Look up an existing or create a new shared SCL structure. Optimistic setup sequence always
+ * creates and tries to add a new entry to the list. This uses minimum locking and afterwards
+ * requests the GPIO without a lock held. Concurrent probes for the same SCL pin see the entry
+ * and do not race into a second gpiod_get(). Until everything is setup they terminate with
+ * -EPROBE_DEFER.
+ */
+static struct i2c_gpio_scl_data *i2c_gpio_lookup_scl(struct device *dev, enum gpiod_flags gflags)
+{
+ struct i2c_gpio_scl_data *scl, *new_scl;
+ struct gpio_desc *gpio;
+
+ new_scl = i2c_gpio_create_scl(dev);
+ if (IS_ERR(new_scl))
+ return new_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);
+ }
+
+ gpio = i2c_gpio_get_desc(dev, "scl", 1, gflags);
+ if (IS_ERR(gpio)) {
+ scoped_guard(mutex, &i2c_gpio_scl_list_lock)
+ list_del(&new_scl->list);
+ i2c_gpio_free_scl(new_scl);
+
+ return ERR_CAST(gpio);
+ }
+
+ scoped_guard(mutex, &i2c_gpio_scl_list_lock)
+ new_scl->gpio = gpio;
+
+ if (new_scl->args.fwnode)
+ dev_dbg(dev, "registered shared SCL (%pfwP)\n", new_scl->args.fwnode);
+
+ return new_scl;
+}
+
+static void i2c_gpio_cleanup_scl(struct i2c_gpio_scl_data *scl)
+{
+ if (!refcount_dec_and_mutex_lock(&scl->ref, &i2c_gpio_scl_list_lock))
+ return;
+
+ list_del(&scl->list);
+ mutex_unlock(&i2c_gpio_scl_list_lock);
+ gpiod_put(scl->gpio);
+ i2c_gpio_free_scl(scl);
+}
+
static int i2c_gpio_probe(struct platform_device *pdev)
{
struct i2c_gpio_private_data *priv;
@@ -386,15 +567,18 @@ static int i2c_gpio_probe(struct platfor
gflags = GPIOD_OUT_HIGH;
else
gflags = GPIOD_OUT_HIGH_OPEN_DRAIN;
- priv->scl = i2c_gpio_get_desc(dev, "scl", 1, gflags);
- if (IS_ERR(priv->scl))
- return PTR_ERR(priv->scl);
+ priv->scl = i2c_gpio_lookup_scl(dev, gflags);
+ if (IS_ERR(priv->scl)) {
+ ret = PTR_ERR(priv->scl);
+ goto err_cleanup_sda;
+ }
- if (gpiod_cansleep(priv->sda) || gpiod_cansleep(priv->scl))
+ if (gpiod_cansleep(priv->sda) || gpiod_cansleep(priv->scl->gpio))
dev_warn(dev, "Slow GPIO pins might wreak havoc into I2C/SMBus bus timing");
else
bit_data->can_do_atomic = true;
+ bit_data->skip_bit_test = !!priv->scl->args.fwnode;
bit_data->setsda = i2c_gpio_setsda_val;
bit_data->setscl = i2c_gpio_setscl_val;
@@ -423,6 +607,8 @@ static int i2c_gpio_probe(struct platfor
else
snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
+ /* Always use shared SCL aware locking */
+ adap->lock_ops = &i2c_gpio_lock_ops;
adap->algo_data = bit_data;
adap->class = I2C_CLASS_HWMON;
adap->dev.parent = dev;
@@ -431,7 +617,7 @@ static int i2c_gpio_probe(struct platfor
adap->nr = pdev->id;
ret = i2c_bit_add_numbered_bus(adap);
if (ret)
- return ret;
+ goto err_cleanup_scl;
platform_set_drvdata(pdev, priv);
@@ -441,13 +627,20 @@ static int i2c_gpio_probe(struct platfor
* from the descriptor, then provide that instead.
*/
dev_info(dev, "using lines %u (SDA) and %u (SCL%s)\n",
- desc_to_gpio(priv->sda), desc_to_gpio(priv->scl),
+ desc_to_gpio(priv->sda), desc_to_gpio(priv->scl->gpio),
pdata->scl_is_output_only
? ", no clock stretching" : "");
i2c_gpio_fault_injector_init(pdev);
return 0;
+
+err_cleanup_scl:
+ i2c_gpio_cleanup_scl(priv->scl);
+err_cleanup_sda:
+ gpiod_put(priv->sda);
+
+ return ret;
}
static void i2c_gpio_remove(struct platform_device *pdev)
@@ -459,6 +652,8 @@ static void i2c_gpio_remove(struct platf
adap = &priv->adap;
i2c_del_adapter(adap);
+ i2c_gpio_cleanup_scl(priv->scl);
+ gpiod_put(priv->sda);
}
static const struct of_device_id i2c_gpio_dt_ids[] = {
@@ -1,41 +0,0 @@
From 9d2327c5f1ac63cb14af088a95eba110ab0c473e Mon Sep 17 00:00:00 2001
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Wed, 7 May 2025 09:47:24 -0400
Subject: [PATCH] realtek: add i2c-gpio-shared driver
Adds the Kconfig and Makefile settings to make the new driver available.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/i2c/busses/Kconfig | 9 +++++++++
drivers/i2c/busses/Makefile | 1 +
2 files changed, 10 insertions(+)
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -696,6 +696,15 @@ 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
+ tristate "multiple GPIO-based bitbanging I2C with shared SCL"
+ depends on GPIOLIB || COMPILE_TEST
+ select I2C_ALGOBIT
+ help
+ This is an alternative of the I2C GPIO driver for devices with only
+ few GPIO pins where multiple busses with dedicated SDA lines share
+ a single SCL line.
+
config I2C_GPIO_FAULT_INJECTOR
bool "GPIO-based fault injector"
depends on I2C_GPIO
--- 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) += i2c-gpio-shared.o
obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o
obj-$(CONFIG_I2C_HISI) += i2c-hisi.o
obj-$(CONFIG_I2C_HIX5HD2) += i2c-hix5hd2.o
-1
View File
@@ -109,7 +109,6 @@ CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_GPIO=y
CONFIG_I2C_GPIO_SHARED=y
CONFIG_I2C_MUX=y
# CONFIG_I2C_RTL9300 is not set
CONFIG_INITRAMFS_SOURCE=""
-1
View File
@@ -112,7 +112,6 @@ CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_GPIO=y
CONFIG_I2C_GPIO_SHARED=y
# CONFIG_I2C_RTL9300 is not set
CONFIG_INITRAMFS_SOURCE=""
CONFIG_IRQCHIP=y
-1
View File
@@ -99,7 +99,6 @@ CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_GPIO=y
CONFIG_I2C_GPIO_SHARED=y
CONFIG_I2C_MUX=y
CONFIG_I2C_RTL9300=y
CONFIG_I2C_SMBUS=y
@@ -101,7 +101,6 @@ CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_GPIO=y
# CONFIG_I2C_GPIO_SHARED is not set
CONFIG_I2C_MUX=y
CONFIG_I2C_RTL9300=y
CONFIG_I2C_SMBUS=y
-1
View File
@@ -101,7 +101,6 @@ CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_GPIO=y
# CONFIG_I2C_GPIO_SHARED is not set
CONFIG_I2C_MUX=y
CONFIG_I2C_RTL9300=y
CONFIG_I2C_SMBUS=y
@@ -103,7 +103,6 @@ CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_GPIO=y
# CONFIG_I2C_GPIO_SHARED is not set
CONFIG_I2C_MUX=y
CONFIG_I2C_RTL9300=y
CONFIG_I2C_SMBUS=y