diff --git a/target/linux/realtek/files-6.12/Documentation/devicetree/bindings/i2c/i2c-gpio-shared.yaml b/target/linux/realtek/files-6.12/Documentation/devicetree/bindings/i2c/i2c-gpio-shared.yaml new file mode 100644 index 00000000000..f496f957b79 --- /dev/null +++ b/target/linux/realtek/files-6.12/Documentation/devicetree/bindings/i2c/i2c-gpio-shared.yaml @@ -0,0 +1,51 @@ +# 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 + +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 since the signal is by definition + open drain. + maxItems: 1 + +examples: + - | + #include + + 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 \ No newline at end of file diff --git a/target/linux/realtek/files-6.12/drivers/i2c/busses/i2c-gpio-shared.c b/target/linux/realtek/files-6.12/drivers/i2c/busses/i2c-gpio-shared.c new file mode 100644 index 00000000000..e7f817abd77 --- /dev/null +++ b/target/linux/realtek/files-6.12/drivers/i2c/busses/i2c-gpio-shared.c @@ -0,0 +1,179 @@ +// 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 + */ + +#include +#include +#include +#include +#include + +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; +}; + +#define GPIO_SHARED_MAX_BUS 4 + +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 fwnode_handle *child; + 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(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)) { + fwnode_handle_put(child); + 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 "); +MODULE_DESCRIPTION("bitbanging multi I2C driver for shared SCL"); diff --git a/target/linux/realtek/patches-6.12/805-add-i2c-gpio-shared-driver.patch b/target/linux/realtek/patches-6.12/805-add-i2c-gpio-shared-driver.patch new file mode 100644 index 00000000000..e50ee312ddc --- /dev/null +++ b/target/linux/realtek/patches-6.12/805-add-i2c-gpio-shared-driver.patch @@ -0,0 +1,41 @@ +From 9d2327c5f1ac63cb14af088a95eba110ab0c473e Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +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 +--- + 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 +@@ -683,6 +683,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 +@@ -67,6 +67,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