Accessing I2C Devices in Linux Print


This application note explains how to use the I2C Master interface in Linux (uClinux) BSP (Board Support Package) for the NXP i.MX RT devices.

The Linux kernel provides a device driver for the I2C controller of the i.MX RT BSPs, enabled in the kernel with the CONFIG_I2C_IMX_LPI2C build-time option. Another kernel configuration option that you will require is CONFIG_I2C_CHARDEV. This option enables the kernel API that allows accessing I2C devices from user-space application code. These kernel configuration options are enabled by default in the rootfs project.

The i.MX RT1024 and i.MX RT1050 devices provide four I2C bus controllers, while the i.MX RT1170 device provides six I2C bus controllers. The parameters of each bus controller are specified in the lpi2c* child nodes properties in the corresponding linux/arch/arm/boot/dts/<bsp>.dtsi file:

  • For the i.MX RT1024 in linux/arch/arm/boot/dts/imxrt1020.dtsi:

    lpi2c1: lpci2c@403f0000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403f0000 0x4000>; interrupts = <28>; clocks = <&clks IMXRT1020_CLK_LPI2C1>, <&clks IMXRT1020_CLK_PER_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c2: lpci2c@403f4000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403f4000 0x4000>; interrupts = <29>; clocks = <&clks IMXRT1020_CLK_LPI2C2>, <&clks IMXRT1020_CLK_PER_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c3: lpci2c@403f8000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403f8000 0x4000>; interrupts = <30>; clocks = <&clks IMXRT1020_CLK_LPI2C3>, <&clks IMXRT1020_CLK_PER_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c4: lpci2c@403fc000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403fc000 0x4000>; interrupts = <31>; clocks = <&clks IMXRT1020_CLK_LPI2C4>, <&clks IMXRT1020_CLK_PER_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; };

  • For the i.MX RT1050 in linux/arch/arm/boot/dts/imxrt1050.dtsi:

    lpi2c1: lpci2c@403f0000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403f0000 0x4000>; interrupts = <28>; clocks = <&clks IMXRT1050_CLK_LPI2C1>, <&clks IMXRT1050_CLK_IPG_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c2: lpci2c@403f4000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403f4000 0x4000>; interrupts = <29>; clocks = <&clks IMXRT1050_CLK_LPI2C2>, <&clks IMXRT1050_CLK_IPG_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c3: lpci2c@403f8000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403f8000 0x4000>; interrupts = <30>; clocks = <&clks IMXRT1050_CLK_LPI2C3>, <&clks IMXRT1050_CLK_IPG_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c4: lpci2c@403fc000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x403fc000 0x4000>; interrupts = <31>; clocks = <&clks IMXRT1050_CLK_LPI2C4>, <&clks IMXRT1050_CLK_IPG_PODF>; clock-names = "per", "ipg"; #address-cells = <1>; #size-cells = <0>; status = "disabled"; };

  • For the i.MX RT1170 in linux/arch/arm/boot/dts/imxrt1170.dtsi:

    lpi2c1: lpci2c@40104000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x40104000 0x4000>; interrupts = <32>; clocks = <&clks IMXRT1170_CLK_LPI2C1>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c2: lpci2c@40108000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x40108000 0x4000>; interrupts = <33>; clocks = <&clks IMXRT1170_CLK_LPI2C2>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c3: lpci2c@4010c000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x4010c000 0x4000>; interrupts = <34>; clocks = <&clks IMXRT1170_CLK_LPI2C3>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c4: lpci2c@40110000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x40110000 0x4000>; interrupts = <35>; clocks = <&clks IMXRT1170_CLK_LPI2C4>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c5: lpci2c@40c34000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x40c34000 0x4000>; interrupts = <36>; clocks = <&clks IMXRT1170_CLK_LPI2C5>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; }; lpi2c6: lpci2c@40c38000 { compatible = "fsl,imx7ulp-lpi2c"; reg = <0x40c38000 0x4000>; interrupts = <37>; clocks = <&clks IMXRT1170_CLK_LPI2C6>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; };

Assignment of the I2C signals to specific pins is described in the pinctrl_lpi2c* child nodes of the iomuxc node in projects/rootfs/rootfs.dts.< bsp>:

  • For the i.MX RT1024:

    ... pinctrl_lpi2c1: i2c1grp { fsl,pins = < MXRT1020_IOMUXC_GPIO_AD_B1_14_LPI2C1_SCL MXRT10XX_PAD_CFG_I2C MXRT1020_IOMUXC_GPIO_AD_B1_15_LPI2C1_SDA MXRT10XX_PAD_CFG_I2C >; }; ...

  • For the i.MX XRT1050:

    ... pinctrl_lpi2c1: lpi2cgrp { fsl,pins = < MXRT1050_IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL MXRT10XX_PAD_CFG_I2C MXRT1050_IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA MXRT10XX_PAD_CFG_I2C >; }; ...

  • for the i.MX RT1170:

    ... pinctrl_lpi2c5: lpi2cgrp5 { fsl,pins = < IOMUXC_GPIO_LPSR_05_LPI2C5_SCL (IMX_PAD_SION | \ MXRT1170_PAD_DSE | \ MXRT1170_PAD_PUE | \ MXRT1170_PAD_PUS | \ MXRT1170_PAD_SRE) IOMUXC_GPIO_LPSR_04_LPI2C5_SDA (IMX_PAD_SION | \ MXRT1170_PAD_DSE | \ MXRT1170_PAD_PUE | \ MXRT1170_PAD_PUS | \ MXRT1170_PAD_SRE) >; }; ...

The rootfs.dts.<bsp> file defines the I2C interface reference with additional properties, which enable the appropriate I2C bus controller and connect it to the specified pins:

  • For the i.MX RT1024:

    &lpi2c1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lpi2c1>; status = "okay"; ... };

  • For the i.MX RT1050:

    &lpi2c1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lpi2c1>; status = "okay"; ... };

  • For the i.MX RT1170:

    &lpi2c5 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lpi2c5>; status = "okay"; ... };

The Emcraft uClinux distribution includes the Linux run-time tools that can be used to access I2C devices from user space. The following lines in the rootfs.initramfs file add these tools to the target file system:

file /usr/sbin/i2cdump ${INSTALL_ROOT}/A2F/root/usr/sbin/i2cdump 755 0 0
file /usr/sbin/i2cdetect ${INSTALL_ROOT}/A2F/root/usr/sbin/i2cdetect 755 0 0
file /usr/sbin/i2cset ${INSTALL_ROOT}/A2F/root/usr/sbin/i2cset 755 0 0
file /usr/sbin/i2cget ${INSTALL_ROOT}/A2F/root/usr/sbin/i2cget 755 0 0

The functionality described above is enabled by default in the rootfs project. So, just run the rootfs.uImage binary on your target and observe the kernel messages about the I2C bus being enabled:

... i2c i2c-0: LPI2C adapter registered ...

Run the Linux I2C tools to examine I2C devices on your target. For instance, the following command scans the I2C bus interface and reports any devices it detects on the bus:

  • Scan I2C1 bus on the i.MX RT1024 EVK board:

    / # i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- / #

  • Scan I2C1 bus on the i.MX RT1050 EVK board:

    / # i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- 1f 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- 38 -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: 50 -- -- -- -- -- -- -- 58 -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- / #

  • Scan I2C5 bus on the i.MX RT1170 EVK board:

    / # i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- 1f 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- / #

  • Scan I2C5 bus on thei.MX RT1170 EVKB board:

    / # i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- 4a -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- / #

Refer to the documentation available from the Internet for detailed manuals on the Linux I2C tools, for instance: https://linuxhint.com/i2c-linux-utilities. Sometimes your I2C device is already supported in the Linux kernel, and you want to utilize it in some standard way. To provide such access to the I2C device you need:

  • Enable the appropriate I2C device driver in your Linux kernel configuration;
  • Add information about your I2C device into the appropriate i2c node reference in the
    rootfs.dts.<bsp> file.

As an example, let's connect an AT24 EEPROM with address 0x58 to the corresponding I2C bus as follows:

  • For the i.MX RT1024 EVK board:
    • SDA of EEPROM is connected to J18.10;
    • SCL of EEPROM is connected to J18.12;
    • VCC of EEPROM is connected to J19.16;
    • GND of EEPROM is connected to J18.1.
  • For the i.MX RT1050 EVK board:
    • SDA of EEPROM is connected to J23.5;
    • SCL of EEPROM is connected to J23.6;
    • VCC of EEPROM is connected to J21.2;
    • GND of EEPROM is connected to J21.8.
  • For the i.MX RT1170 EVK or EVKB board:
    • SDA of EEPROM is connected to J10.18;
    • SCL of EEPROM is connected to J10.20;
    • VCC of EEPROM is connected to J10.16;
    • GND of EEPROM is connected to J10.14.

After connection provide user with a simple read/write interface to it:

  • Enable the EEPROM driver in the Linux kernel configuration (Device Drivers -> Misc devices -> EEPROM support -> I2C EEPROMs / RAMs / ROMs from most vendors):

    $ make kmenuconfig

  • Add information about the AT24 EEPROM device into rootfs.dts.<bsp>, for example:

    &lpi2c1 { ... eeprom0: eeprom0@58 { compatible = "at,24c256"; reg = <0x58>; status = "okay"; }; };

  • Build rootfs.uImage:

    $ make

  • Run the resultant image on the target, and validate access to the EEPROM device via the standard interfaces defined by the kernel drivers:

    / # echo "Some text written to EEPROM." > /sys/bus/i2c/devices/0-0058/eeprom / # head -n 1 /sys/bus/i2c/devices/0-0058/eeprom Some text written to EEPROM. / #