I2c address conflict with RTC

I am working to convert an existing product from WinCE to Torizon.
We’ve run into an interesting problem with i2c.
I think we have a workaround, but it is not very elegant, and I hope you might have a better idea.

The product uses the Colibri iMX6DL and uses many i2c devices.
The PCBA for this product includes an RTC in the same way as the Toradex dev board.
The product only uses one i2c channel to comply with the standard Colibri family pin-out.
We can’t change the hardware design at this time as this is a released product.

One of the other i2c devices has the same address as the RTC, which is 0x68.
This product contains multiple instances of the device with address 0x68.
The hardware solution which was implemented for WinCE was to use multiple i2c multiplexors.
On power-up the multiplexors default to connecting the RTC to the Colibri.
WinCE then finds the RTC at boot time and reads the time from it.
Then our firmware takes control of the i2c bus and sets the multiplexors to access each device.
This works well for WinCE.

This scheme does not work well on Linux because the device tree instantiates an RTC device on address 0x68. The kernel then reserves address 0x68 for its RTC driver.
If we try to access one of the non-RTC devices which have the same i2c address then the kernel denies our request.
We are able to access all the other i2c devices which use different i2c addresses.

If we edit the device tree to disable the RTC then the kernel does not reserve access to address 0x68. We can then access all of the i2c devices in our system.
But the downside of this is that all of the kernel RTC features are disabled, which is unfortunate.

A potential solution to this problem is for us to control the RTC directly from our firmware and take over responsibility for time management from the kernel. We’d rather not do that as the kernel normally does a good job of taking care of this.
Before we invest time in doing that, does anyone have a suggestion for a more elegant solution?
A way to share i2c address 0x68 between the kernel and user space?

Hi, I’m the Toradex AI Assistant.

I might make mistakes or occasionally get you wrong. Our support team will step in if needed.


I understand your challenge with multiple I²C devices sharing address 0x68 on your Colibri iMX6DL with I²C multiplexers. This is a common issue when migrating from WinCE to Linux due to how the Linux kernel manages device drivers.

Understanding the Problem

The issue you’re experiencing occurs because Linux device drivers claim exclusive access to their configured I²C addresses(1). When the RTC driver is instantiated through the device tree, it reserves address 0x68, preventing user-space applications from accessing other devices at the same address through the multiplexers.

Recommended Solutions

1. Device Tree Overlay Approach

You can create a device tree overlay that dynamically enables/disables the RTC driver. This allows you to:

  • Boot with RTC enabled for initial time synchronization
  • Disable RTC driver when you need to access other devices at 0x68
  • Re-enable RTC driver when needed

The overlay structure would look like this(2):

/dts-v1/;
/plugin/;

/ {
    compatible = "toradex,colibri_imx6dl";
};

&rtc_i2c {
    status = "disabled";
};

You can apply and remove overlays dynamically using TorizonCore Builder(2).

2. I²C Multiplexer Integration

Since your hardware already has I²C multiplexers, you can define them in the device tree. Linux supports I²C multiplexers natively, allowing the kernel to manage channel switching automatically(1).

The device tree would include:

  • Multiplexer definitions
  • RTC on the default channel
  • Other devices on their respective channels

3. Kernel Space Driver Modification

For a more elegant solution, you could modify or create a custom driver that:

  • Manages the multiplexer switching
  • Provides controlled access to address 0x68
  • Maintains RTC functionality while allowing access to other devices

Container Access Considerations

When accessing I²C devices from containers, remember to expose the hardware properly(1):

# docker run -it --rm -v /dev:/dev --device-cgroup-rule='c 89:* rmw' <container-image>

RTC Management

The Colibri iMX6DL typically has both an on-module RTC and the carrier board RTC(3). You can verify which RTCs are available by checking the kernel boot log:

dmesg | grep rtc

This will show you the registered RTC devices, typically showing both the carrier board RTC (rtc0) and the SoC internal RTC (rtc1)(3).

Recommendation

I recommend implementing the I²C multiplexer support in the device tree as the most elegant solution. This approach:

  • Maintains kernel RTC functionality
  • Allows proper device isolation
  • Follows Linux best practices
  • Eliminates the need for custom user-space multiplexer management

Would you like guidance on implementing any of these approaches? Also, could you specify which version of Torizon OS you’re running to ensure the most accurate implementation details?

Best regards, Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

Thanks, that’s very helpful.
We are using Torizon 7.4.0 build 28.

Thank you for providing the Torizon version information! With Torizon 7.4.0 build 28, here are the specific implementation details for your I²C multiplexer solution:

Device Tree Overlay Implementation

For Torizon 7.4.0, you can create a device tree overlay to properly define your I²C multiplexers(1). The overlay structure would be:

/dts-v1/;
/plugin/;

/ {
    compatible = "toradex,colibri-imx6dl";
};

&i2c1 {
    i2c-mux@70 {
        compatible = "nxp,pca9548";
        reg = <0x70>;
        #address-cells = <1>;
        #size-cells = <0>;
        
        i2c@0 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <0>;
            
            rtc@68 {
                compatible = "dallas,ds1307";
                reg = <0x68>;
            };
        };
        
        i2c@1 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <1>;
            
            your_device@68 {
                compatible = "your,device";
                reg = <0x68>;
            };
        };
    };
};

(1)

Container Access for Torizon 7.4.0

When accessing I²C devices from containers on Torizon 7.4.0, use this command to expose the hardware properly(2):

# docker run -it --rm -v /dev:/dev --device-cgroup-rule='c 89:* rmw' <container_namespace>

(2)

I²C Bus Identification

For the Colibri iMX6DL, you can identify available I²C buses using(2):

# ls -l /dev | grep colibri-i2c

(2)

This will show the symbolic links that Torizon OS creates to facilitate access to I²C buses from your application, ensuring pin compatibility between different modules(2).

Testing Your Setup

You can test your I²C devices using the I²C Tools within containers(2):

# i2cdetect -y <bus_number>

(2)

This will help you verify that each device appears on its respective multiplexer channel.

TorizonCore Builder Integration

For Torizon 7.4.0, use TorizonCore Builder to apply your device tree overlay. Your tcbuild.yaml configuration should include the appropriate include directories for the kernel headers(1).

This approach will allow the kernel to properly manage the multiplexer switching while maintaining RTC functionality and providing access to all your devices at address 0x68 through their respective channels.

Best regards, Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

@ToradexAI can you also give me more details on option 1 to enable and disable the device tree overlay dynamically. Is it possible for the firmware running on the iMX6 to enable and disable the device tree overlay dynamically at runtime?

I know how to use torizon-core builder to do this from my development machine, but torizon-core builder won’t be available to the firmware on the iMX6 at run time.

@ToradexAI this page describes a way to enable and disable device tree overlays at runtime, but the folder names don’t match well with Torizon:
Advanced Device Tree Overlays: Enabling Runtime Hardware Reconfiguration on Linux - OpenLib.IO

The page says that for this tool to work the kernel needs to have CONFIG_OF_OVERLAY enabled. Perhaps the Torizon kernel does not enable that option?

Great question! You’re right that TorizonCore Builder won’t be available at runtime on the device. For runtime device tree overlay management on Torizon, there are specific approaches available:

Runtime Device Tree Overlay Management on Torizon

Approach 1: Direct overlays.txt Modification

On Torizon OS, you can modify device tree overlays at runtime by editing the overlays.txt file directly on the device(1). This is the most straightforward approach for your firmware:

First, get the current OSTree deployment hash(1):

cat /proc/cmdline

(1)

Then modify the overlays file(1):

# cd /sysroot/boot/ostree/torizon-<hash>/dtb/
# sudo sh -c "echo 'fdt_overlays=your-overlay.dtbo' > overlays.txt"
# sync

(1)

This approach allows your firmware to:

  • Enable overlays by adding them to the fdt_overlays= line
  • Disable overlays by removing them from the line
  • Requires a reboot to take effect

Approach 2: TorizonCore Builder dto deploy Command

For evaluation and prototyping, you can use the dto deploy command directly on the device(1):

$ torizoncore-builder dto deploy --remote-host 192.168.1.117 --remote-username torizon --remote-password torizon --force --reboot device-trees/overlays/your-overlay.dts

(1)

However, this requires TorizonCore Builder to be installed on the device, which isn’t typical for production systems.

Kernel Configuration for Runtime Overlays

You’re correct to ask about CONFIG_OF_OVERLAY. The Torizon kernel does support device tree overlays, but the runtime loading mechanism works differently than the generic Linux approach you referenced.

Recommended Solution for Your Use Case

For your specific I²C multiplexer scenario, I recommend creating two overlays:

  1. rtc-enabled.dtbo - Enables RTC driver
  2. rtc-disabled.dtbo - Disables RTC driver and allows user-space access to 0x68

Your firmware can switch between these by:

# Enable RTC mode
echo 'fdt_overlays=rtc-enabled.dtbo' > /sysroot/boot/ostree/torizon-<hash>/dtb/overlays.txt
sync
reboot

# Enable user-space I²C access mode  
echo 'fdt_overlays=rtc-disabled.dtbo' > /sysroot/boot/ostree/torizon-<hash>/dtb/overlays.txt
sync
reboot

Alternative: Pre-built Overlay Management

Since your hardware already uses multiplexers, you could create a single overlay that defines the multiplexer topology in the device tree(2). This would allow the kernel to manage channel switching automatically without needing runtime overlay changes.

The overlay would include definitions for each multiplexer channel with the appropriate devices(2):

  • Channel 0: RTC at 0x68
  • Channel 1: Device A at 0x68
  • Channel 2: Device B at 0x68
  • etc.

This approach eliminates the need for runtime overlay switching while maintaining proper device isolation.

Would you like me to provide more details on implementing either the overlay switching mechanism or the multiplexer topology approach?

Best regards, Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

@ToradexAI I would need to be able to enable and disable device tree overlays at runtime without rebooting. Is there a way to do that?

I understand your need for runtime overlay switching without reboots. Unfortunately, based on the available Toradex documentation, the standard device tree overlay mechanisms require a reboot to take effect(1)(2).

Current Limitations

The Torizon OS device tree overlay system works by modifying the overlays.txt file and requires a reboot for changes to take effect(2). The documentation shows that after modifying the overlays, you must run:

# sync
# sudo reboot

(2)

Alternative Approaches for Your I²C Challenge

Given your requirement for runtime switching without reboots, here are some alternative solutions:

1. I²C Multiplexer Management in User Space

Instead of using device tree overlays for runtime switching, you could:

  • Define all your I²C multiplexers and devices in a single device tree overlay at boot time
  • Use user-space tools to control the multiplexer switching directly
  • Temporarily unbind/rebind the RTC driver when needed

2. Driver Unbinding/Rebinding Approach

You can dynamically unbind and rebind the RTC driver without rebooting:

# Unbind RTC driver to free address 0x68
echo "1-0068" > /sys/bus/i2c/drivers/rtc-ds1307/unbind

# Access your other devices at 0x68 through multiplexers
# ... your application logic here ...

# Rebind RTC driver when done
echo "1-0068" > /sys/bus/i2c/drivers/rtc-ds1307/bind

3. Custom Kernel Module

For a more elegant solution, you could develop a custom kernel module that:

  • Manages exclusive access to address 0x68
  • Coordinates between RTC driver and your application
  • Handles multiplexer switching automatically

Recommendation

For your specific use case with multiple devices at address 0x68 behind multiplexers, I recommend implementing the I²C multiplexer topology directly in the device tree(3). This would allow the kernel to manage all devices properly without requiring runtime overlay changes.

The device tree overlay would define each multiplexer channel with its respective device, allowing the kernel’s I²C subsystem to handle the channel switching automatically when accessing different devices.

Would you like me to provide more details on implementing the multiplexer topology approach or the driver unbinding mechanism?

Best regards, Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

As AI mentioned it’s not possible DT overlay get applied during boot and you can not disable/enable it after. However you can unbind particular driver

  • find the driver RTC driver in ls /sys/bus/i2c/drivers (for example rtc-ds1307)
  • Identify the device name bound to the driver using ls /sys/bus/i2c/drivers/rtc-ds1307/
  • Most likely you will see something like 0-0068

The X-00YY format means:

X = I²C bus number
YY = device address

  • Unbind echo 0-0068 | sudo tee /sys/bus/i2c/drivers/rtc-ds1307/unbind

Hi Alex,

Thanks for joining the discussion. I’ve been testing the idea of binding and unbinding the driver since the AI suggested it and it almost works. I can unbind and bind using these commands:

ls /dev -l | grep rtc
echo 1-0068 | sudo tee /sys/bus/i2c/drivers/rtc-ds1307/unbind
ls /dev -l | grep rtc
echo 1-0068 | sudo tee /sys/bus/i2c/drivers/rtc-ds1307/bind
ls /dev -l | grep rtc

If I run those commands one after the other then I can see the rtc disappear and reappear from the /dev folder as shown below - so far so good.

torizon@colibri-imx6-11082456:\~$ ls /dev -l | grep rtc
lrwxrwxrwx 1 root root           7 Feb  4 16:13 colibri-uartc → ttymxc2
lrwxrwxrwx 1 root root           4 Feb  4 16:13 rtc → rtc0
crw------- 1 root root    253,   0 Feb  4 16:13 rtc0
crw------- 1 root root    253,   1 Feb  4 16:13 rtc1
torizon@colibri-imx6-11082456:\~$ echo 1-0068 | sudo tee /sys/bus/i2c/drivers/rtc-ds1307/unbind
1-0068
torizon@colibri-imx6-11082456:\~$ ls /dev -l | grep rtc
lrwxrwxrwx 1 root root           7 Feb  4 16:13 colibri-uartc → ttymxc2
crw------- 1 root root    253,   1 Feb  4 16:13 rtc1
torizon@colibri-imx6-11082456:\~$ echo 1-0068 | sudo tee /sys/bus/i2c/drivers/rtc-ds1307/bind
1-0068
torizon@colibri-imx6-11082456:\~$ ls /dev -l | grep rtc
lrwxrwxrwx 1 root root           7 Feb  4 16:13 colibri-uartc → ttymxc2
lrwxrwxrwx 1 root root           4 Feb  4 16:15 rtc → rtc0
crw------- 1 root root    253,   0 Feb  4 16:15 rtc0
crw------- 1 root root    253,   1 Feb  4 16:13 rtc1

After I unbind the driver my firmware can then access all the devices on the i2c bus.

But if I try to bind the driver after shutting down my firmware then I get this error message:

torizon@colibri-imx6-11082456:~$ echo 1-0068 | sudo tee /sys/bus/i2c/drivers/rtc-ds1307/bind
1-0068
tee: /sys/bus/i2c/drivers/rtc-ds1307/bind: No such device or address

For this idea to work the firmware would need to close the i2c port, then bind the driver, then tell the kernel to interact with the RTC, then unbind the driver, then reopen the i2c port. But I can’t seem to rebind the driver after I have been using the i2c port. I can rebind it if I didn’t mess with the i2c port inbetween.

@alex.tx I just figured it out! I was unable to rebind the driver because my i2c mux was left in a random state when I shutdown my firmware, so the kernel couldn’t see the RTC on the bus. If I set the i2c multiplexer first, then the bind command works!

I agree with the AI that defining the i2c multiplexers in the device tree is the correct and most elegant way to do this when starting a new project, but unbinding and binding the driver when needed could be a much smaller change. We only need access to the RTC occasionally, so the overhead would be acceptable.