Currently, i2c_init_recovery() only assigns the set_sda/set_scl
hooks if gpiod_get_direction() returns 0 (output).
This logic fails on certain SoC controllers where open-drain lines
in a high-impedance state are physically reported as inputs. This
leads to a "deadlock" where the I2C core refuses to assign the
recovery hooks because it incorrectly assumes the pins are
input-only, even though they are fully capable of driving the bus
low for recovery.
Update the recovery initialization to use the new
gpiod_is_single_ended() helper. If a GPIO is configured as
open-drain or open-source in the firmware, it is safe to assume
it can be used for bus recovery, even if the current hardware
direction is reported as input.
Signed-off-by: Jie Li <jie.i.li@nokia.com>
---
drivers/i2c/i2c-core-base.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index ae7e9c8b65a6..11bd801418e8 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -446,7 +446,8 @@ static int i2c_init_recovery(struct i2c_adapter *adap)
if (bri->sda_gpiod) {
bri->get_sda = get_sda_gpio_value;
/* FIXME: add proper flag instead of '0' once available */
- if (gpiod_get_direction(bri->sda_gpiod) == 0)
+ if (gpiod_get_direction(bri->sda_gpiod) == 0 ||
+ gpiod_is_single_ended(bri->sda_gpiod))
bri->set_sda = set_sda_gpio_value;
}
} else if (bri->recover_bus == i2c_generic_scl_recovery) {
--
2.43.0
On Sun, Jan 25, 2026 at 8:51 PM Jie Li <lj29312931@gmail.com> wrote: > Currently, i2c_init_recovery() only assigns the set_sda/set_scl > hooks if gpiod_get_direction() returns 0 (output). > > This logic fails on certain SoC controllers where open-drain lines > in a high-impedance state are physically reported as inputs. This > leads to a "deadlock" where the I2C core refuses to assign the > recovery hooks because it incorrectly assumes the pins are > input-only, even though they are fully capable of driving the bus > low for recovery. > > Update the recovery initialization to use the new > gpiod_is_single_ended() helper. If a GPIO is configured as > open-drain or open-source in the firmware, it is safe to assume > it can be used for bus recovery, even if the current hardware > direction is reported as input. > > Signed-off-by: Jie Li <jie.i.li@nokia.com> This looks good! Reviewed-by: Linus Walleij <linusw@kernel.org> Yours, Linus Walleij
Greetings, Apologies for the delay in responding. Thank you very much for your review and the specific guidance regarding the return types. I really appreciate your patience and time spent guiding me through my first contribution to the kernel. This series (v3) updates the helper function to use the 'bool' type as suggested and includes the Reviewed-by tags. This series addresses a limitation in the I2C bus recovery mechanism where certain open-drain GPIOs are incorrectly identified as input-only, preventing the recovery logic from functioning. Following the suggestion from Linus Walleij, this version drops the previously proposed "force-set-sda" DT property. Instead, it introduces a generic helper in the GPIO subsystem to identify single-ended configurations. This allows the I2C core to reliably enable recovery for open-drain lines regardless of the instantaneous hardware direction reporting. Changes in v3: - Patch 1: - Changed return type of gpiod_is_single_ended() from int to bool. - Updated return values from 0/1 to false/true. - Added Reviewed-by: Linus Walleij. - Patch 2: - Added Reviewed-by: Linus Walleij. Changes in v2: - Replaced DT-based "force-set-sda" with a gpiolib helper. - Added gpiod_is_single_ended() to drivers/gpio/gpiolib.c. - Updated i2c-core-base.c to use the new helper. Jie Li (2): gpiolib: add gpiod_is_single_ended() helper i2c: core: support recovery for single-ended GPIOs drivers/gpio/gpiolib.c | 22 ++++++++++++++++++++++ drivers/i2c/i2c-core-base.c | 3 ++- include/linux/gpio/consumer.h | 5 +++++ 3 files changed, 29 insertions(+), 1 deletion(-) -- 2.43.0
The direction of a single-ended (open-drain or open-source) GPIO line
cannot always be reliably determined by reading hardware registers.
In true open-drain implementations, the "high" state is achieved by
entering a high-impedance mode, which many hardware controllers report
as "input" even if the software intends to use it as an output.
This creates issues for consumer drivers (like I2C) that rely on
gpiod_get_direction() to decide if a line can be driven.
Introduce gpiod_is_single_ended() to allow consumers to check the
software configuration (GPIO_FLAG_OPEN_DRAIN/GPIO_FLAG_OPEN_SOURCE) of
a descriptor. This provides a robust way to identify lines that are
capable of being driven, regardless of their instantaneous hardware state.
Signed-off-by: Jie Li <jie.i.li@nokia.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/gpio/gpiolib.c | 22 ++++++++++++++++++++++
include/linux/gpio/consumer.h | 5 +++++
2 files changed, 27 insertions(+)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 1578cf3a8c74..08e6960053f8 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -486,6 +486,28 @@ int gpiod_get_direction(struct gpio_desc *desc)
}
EXPORT_SYMBOL_GPL(gpiod_get_direction);
+/**
+ * gpiod_is_single_ended - check if the GPIO is configured as single-ended
+ * @desc: the GPIO descriptor to check
+ *
+ * Returns true if the GPIO is configured as either Open Drain or Open Source.
+ * In these modes, the direction of the line cannot always be reliably
+ * determined by reading hardware registers, as the "off" state (High-Z)
+ * is physically indistinguishable from an input state.
+ */
+bool gpiod_is_single_ended(struct gpio_desc *desc)
+{
+ if (!desc)
+ return false;
+
+ if (test_bit(GPIOD_FLAG_OPEN_DRAIN, &desc->flags) ||
+ test_bit(GPIOD_FLAG_OPEN_SOURCE, &desc->flags))
+ return true;
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(gpiod_is_single_ended);
+
/*
* Add a new chip to the global chips list, keeping the list of chips sorted
* by range(means [base, base + ngpio - 1]) order.
diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
index cafeb7a40ad1..12ef6e07ee1a 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -109,6 +109,7 @@ void devm_gpiod_unhinge(struct device *dev, struct gpio_desc *desc);
void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs);
int gpiod_get_direction(struct gpio_desc *desc);
+bool gpiod_is_single_ended(struct gpio_desc *desc);
int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);
int gpiod_direction_output_raw(struct gpio_desc *desc, int value);
@@ -335,6 +336,10 @@ static inline int gpiod_get_direction(const struct gpio_desc *desc)
WARN_ON(desc);
return -ENOSYS;
}
+static inline bool gpiod_is_single_ended(struct gpio_desc *desc)
+{
+ return false;
+}
static inline int gpiod_direction_input(struct gpio_desc *desc)
{
/* GPIO can never have been requested */
--
2.43.0
Currently, i2c_init_recovery() only assigns the set_sda/set_scl
hooks if gpiod_get_direction() returns 0 (output).
This logic fails on certain SoC controllers where open-drain lines
in a high-impedance state are physically reported as inputs. This
leads to a "deadlock" where the I2C core refuses to assign the
recovery hooks because it incorrectly assumes the pins are
input-only, even though they are fully capable of driving the bus
low for recovery.
Update the recovery initialization to use the new
gpiod_is_single_ended() helper. If a GPIO is configured as
open-drain or open-source in the firmware, it is safe to assume
it can be used for bus recovery, even if the current hardware
direction is reported as input.
Signed-off-by: Jie Li <jie.i.li@nokia.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/i2c/i2c-core-base.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index ae7e9c8b65a6..11bd801418e8 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -446,7 +446,8 @@ static int i2c_init_recovery(struct i2c_adapter *adap)
if (bri->sda_gpiod) {
bri->get_sda = get_sda_gpio_value;
/* FIXME: add proper flag instead of '0' once available */
- if (gpiod_get_direction(bri->sda_gpiod) == 0)
+ if (gpiod_get_direction(bri->sda_gpiod) == 0 ||
+ gpiod_is_single_ended(bri->sda_gpiod))
bri->set_sda = set_sda_gpio_value;
}
} else if (bri->recover_bus == i2c_generic_scl_recovery) {
--
2.43.0
© 2016 - 2026 Red Hat, Inc.