I2C Bus Recovery

Posted by Marcus Folkesson on Saturday, October 4, 2025

I2C Bus Recovery

Brief

I was working on a project where we had a problem with an I2C bus that was sporadically hanging during communication with a certain device. I2C works with open-drain lines, which means that devices can only pull the lines low, and a pull-up resistor is used to pull the line high. If a device misbehaves and holds one of the lines low, the bus will be stuck, and no further communication can take place.

/media/i2c-tug.png

I've seen this a couple of times before, so I would say that it is not that common to be honest, but still, it happens. I2C is basically a very simple protocol if you stick to the basics, which most chips do. Just to be clear; what I mean with basics is:

  • No clockstretching
  • No 10-bit addressing
  • No repeated start conditions
  • No multi-master setups
  • No combined transactions
  • No high-speed mode (3.4MHz)
  • No PEC (Error checksum)
  • ...

In other words, just the basic 7-bit addressing and standard/fast mode (100/400kHz).

In Linux, I2C bus recovery is a mechanism to get a hung/stuck I2C bus working again.

The I2C bus recovery mechanism

The I2C bus recovery mechanism is implemented in the I2C core and can be used by any I2C adapter/driver that supports it. It is implemented by the struct i2c_bus_recovery_info structure, which is defined in include/linux/i2c.h [1].

 1struct i2c_bus_recovery_info {
 2	int (*recover_bus)(struct i2c_adapter *adap);
 3
 4	int (*get_scl)(struct i2c_adapter *adap);
 5	void (*set_scl)(struct i2c_adapter *adap, int val);
 6	int (*get_sda)(struct i2c_adapter *adap);
 7	void (*set_sda)(struct i2c_adapter *adap, int val);
 8	int (*get_bus_free)(struct i2c_adapter *adap);
 9
10	void (*prepare_recovery)(struct i2c_adapter *adap);
11	void (*unprepare_recovery)(struct i2c_adapter *adap);
12
13	/* gpio recovery */
14	struct gpio_desc *scl_gpiod;
15	struct gpio_desc *sda_gpiod;
16	struct pinctrl *pinctrl;
17	struct pinctrl_state *pins_default;
18	struct pinctrl_state *pins_gpio;
19};

A custom recovery function may be used by implementing the recover_bus function pointer. If not, the standard recovery function [2] will be used, which is implemented in the I2C core.

How it works

There could be several reasons to why a device misbehaves and holds the bus low, e.g.:

  • A slave somehow crashes in mid-transfer
  • Bad signal integrity on the bus causing hard to detect edges
  • ...

Most of the time a few extra clock cycles is enough to get the slave back in sync. The standard recovery function will do the following:

  • Check if the bus is really stuck (SCL or SDA low)
  • If the bus is stuck, it will try to toggle the SCL line up to 9 times (to cover a full byte transfer)
  • After each clock cycle, it will check if the SDA line is released (high)
  • If the SDA line is released, it will generate a STOP condition by pulling SDA low while SCL is high, and then release SDA
  • If the bus is still stuck after 9 clock cycles, it will return an error

This procedure is described in the I2C specification [3] in the Bus clear section:

/media/i2c-bus-clear.png

How this is implemented on the DaVinci platform

As I'm currently using the DaVinci platform from Texas Instruments, lets look at how this is implemented there.

This I2C controller is a bit special as it contains register (ICPFUNC) to control if the pins should be used as I2C (open-drain) or GPIO (push-pull). In other words, there is no need to use pinctrl to mux the pins, which is the most common way to do this.

/media/pfunc.png

The bus driver defines the i2c_bus_recovery_info structure as follows:

1static struct i2c_bus_recovery_info davinci_i2c_scl_recovery_info = {
2	.recover_bus = i2c_generic_scl_recovery,
3	.set_scl = davinci_i2c_set_scl,
4	.get_scl = davinci_i2c_get_scl,
5	.get_sda = davinci_i2c_get_sda,
6	.prepare_recovery = davinci_i2c_scl_prepare_recovery,
7	.unprepare_recovery = davinci_i2c_scl_unprepare_recovery,
8};

Here we can see that it is using the generic recovery function, but it provides custom functions to set and get the SCL and SDA lines.

The prepare_recovery function will set the pins to GPIO mode by setting the appropriate bits in the I2C controller registers:

 1static void davinci_i2c_scl_prepare_recovery(struct i2c_adapter *adap)
 2{
 3	struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
 4
 5	davinci_i2c_prepare_recovery(adap);
 6
 7	/* SCL output, SDA input */
 8	davinci_i2c_write_reg(dev, DAVINCI_I2C_DIR_REG, DAVINCI_I2C_DIR_PDIR0);
 9
10	/* change to GPIO mode */
11	davinci_i2c_write_reg(dev, DAVINCI_I2C_FUNC_REG,
12			      DAVINCI_I2C_FUNC_PFUNC0);
13}

get_scl and get_sda will read the current state of the lines:

 1static int davinci_i2c_get_scl(struct i2c_adapter *adap)
 2{
 3	struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
 4	int val;
 5
 6	/* read the state of SCL */
 7	val = davinci_i2c_read_reg(dev, DAVINCI_I2C_DIN_REG);
 8	return val & DAVINCI_I2C_DIN_PDIN0;
 9}
10
11static int davinci_i2c_get_sda(struct i2c_adapter *adap)
12{
13	struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
14	int val;
15
16	/* read the state of SDA */
17	val = davinci_i2c_read_reg(dev, DAVINCI_I2C_DIN_REG);
18	return val & DAVINCI_I2C_DIN_PDIN1;
19}

while set_scl will set or clear the SCL line:

 1static void davinci_i2c_set_scl(struct i2c_adapter *adap, int val)
 2{
 3	struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
 4
 5	if (val)
 6		davinci_i2c_write_reg(dev, DAVINCI_I2C_DSET_REG,
 7				      DAVINCI_I2C_DSET_PDSET0);
 8	else
 9		davinci_i2c_write_reg(dev, DAVINCI_I2C_DCLR_REG,
10				      DAVINCI_I2C_DCLR_PDCLR0);
11}

The unprepare_recovery function will set the pins back to I2C mode:

1static void davinci_i2c_unprepare_recovery(struct i2c_adapter *adap)
2{
3	struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
4
5	i2c_davinci_init(dev);
6}

As we can see, there is not much code needed to implement this. Most of the logic resides in the I2C core.

Conclusion

It is often possible to recover a stuck I2C bus by using the I2C bus recovery mechanism in the Linux kernel. However, it is not guaranteed to work in all cases and this is not a substitute for proper hardware design and signal integrity on the bus.