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:
@@ -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 {
|
||||
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 {
|
||||
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";
|
||||
&i2c1 {
|
||||
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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&i2c2 {
|
||||
|
||||
@@ -6,40 +6,40 @@
|
||||
compatible = "hpe,1920-48g", "realtek,rtl8393-soc";
|
||||
model = "HPE 1920-48G (JG927A)";
|
||||
|
||||
i2c-gpio-shared0 {
|
||||
compatible = "i2c-gpio-shared";
|
||||
scl-gpios = <&gpio0 17 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
i2c0: i2c@0 {
|
||||
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>;
|
||||
};
|
||||
|
||||
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";
|
||||
scl-gpios = <&gpio0 1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
i2c1: i2c@1 {
|
||||
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>;
|
||||
};
|
||||
|
||||
i2c3: i2c@3 {
|
||||
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-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@1 {
|
||||
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>;
|
||||
};
|
||||
|
||||
i2c2: i2c@2 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
-51
@@ -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");
|
||||
+46
@@ -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 *);
|
||||
+384
@@ -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
|
||||
@@ -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=""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user