Adding I2C to Linux Yocto

In this example you will add single I2C (IIC) IP block using FPGA resources and connect it to PMOD connector of Zybo board.

Procedure

  1. Add new hardware with SPI in Vivado and generate XSA file.
  1. Add new XSA file in Yocto.
  1. Configure Linux kernel to use I2C.
  1. Add I2C tools to Linux Yocto filesystem.
  1. Build Yocto image for SD card.

Results

Finally, after these steps you will have in Yocto:

  • single I2C in PL connected to AXI and externally to PMOD.

1. In Vivado: add new SPI hardware to obtain XSA file

Add AXI IIC in Vivado Block Design diagram

  • Add AXI IIC IP core:
  • Run Connection Automation.
  • Regenerate HDL wrapper and copy the names of the I2C pins (i.e. iic_rtl_scl_io, iic_rtl_sda_io) to XDC constrains file.
  • Connect interrupt:
  • Enable interrupts in Zynq (in not already enabled): Zynq/Interrupts/Fabric interrupts/PL-PS Interrupt Ports/IRQ_F2P[15:0].
  • Connect AXI IC interrupt  pin to Zynq IRQ_F2P input. If you already have something connected to Zynq's interrupt input (i.e. SPI or UART), then add Concat IP block and use it to join interrupts from various sources. If you already have Concat block, increment its input size, then connect interrupt by dragging a wire connection with the mouse.

Edit constrains

Example of XDC (watch out of pin assignments, adjust to your needs!):

##Pmod Header JC                                                                                                                  
set_property -dict { PACKAGE_PIN V15   IOSTANDARD LVCMOS33     } [get_ports { iic_rtl_scl_io }]; #IO_L10P_T1_34 Sch=jc_p[1] JC-1                  
set_property -dict { PACKAGE_PIN W15   IOSTANDARD LVCMOS33     } [get_ports {  iic_rtl_sda_io}]; #IO_L10N_T1_34 Sch=jc_n[1] JC2            

Generate and export           

  • Generate bitstream.
  • Export hardware with bitstream to XSA file.

2. Configure Linux Yocto - add changed XSA to Yocto

  • Copy newly generated XSA to:
lab_msw_spi/yocto-scarthgap/poky/meta-custom-zybo-z7/recipes-bsp/external-hdf/files/<your_XSA_FILE>
  • Edit the file: lab_msw_spi/yocto-scarthgap/poky/meta-custom-zybo-z7/recipes-bsp/external-hdf/external-hdf_%.bbappend

and update the XSA filename (if changed after adding SPI hardware) :

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
HDF_EXT  = "xsa"
HDF_URI = file://zybo-z7-20-hw-platform_spi_uart.xsa

3. Configure Linux Yocto - configure kernel

Find the name for the recipe

  • Find the name of virtual provider for the kernel in your project:
bitbake -e core-image-minimal-dev | grep ^PREFERRED_PROVIDER_virtual/kernel=
à PREFERRED_PROVIDER_virtual/kernel="linux-xlnx"
  • Use the  name of the provider (here linux-xlnx) to find the name of the kernel recipe used in your project:
bitbake -e linux-xlnx | grep ^FILE=
à FILE="/home2/ldap/Users/wujek_ldap/lab_msw_spi/yocto-scarthgap/poky/meta-xilinx/meta-xilinx-core/recipes-kernel/linux/linux-xlnx_6.1-v2023.2.bb"
  • Use the name of the recipe (here linux-xlnx_6.1-v2023.2.bb) to create BBAPPEND file to include it in your kernel recipe.

Create BBAPPEND file

  • Create file with the name of  the recipe found above (if you already have it, modify it):
nano recipes-kernel/linux/linux-xlnx_6.1-v2023.2.bbappend

with lines (if you are not using SPI or UART, remove parts for spi.cfg and/or uartlite.cfg):

# Look for files (in folder recipes-kernel/linux/)
FILESEXTRAPATHS:prepend := "${THISDIR}:"
SRC_URI:append = " \
    file://spidev.cfg \
    file://uartlite.cfg \
    file://i2c.cfg \
"
do_configure:append() {
    bbnote "Merging custom kernel config fragments (spidev + uartlite)"
    KCONFIG_CONFIG="${B}/.config" \
       bash ${S}/scripts/kconfig/merge_config.sh -m -O ${B} \
         ${B}/.config ${WORKDIR}/spidev.cfg ${WORKDIR}/uartlite.cfg ${WORKDIR}/i2c.cfg   oe_runmake -C ${S} O=${B} olddefconfig  
}

The function do_configure:append() injects i2c.cfg after the XSCT tools from Vivado builds the kernel for Zybo and sets Zybo defaults. The function provides, that the setting CONFIG_I2C_XILINX=y, CONFIG_I2C_CHARDEV=y will be applied at the end and will not be overwritten by defaults.

Create kernel configuration fragment file

  • Create file:
nano recipes-kernel/linux/linux-xlnx/i2c.cfg

with line:

CONFIG_I2C_XILINX=y
CONFIG_I2C_CHARDEV=y

Final check

You should have the following files:

meta-custom-zybo-z7/
├── conf
│   ├── layer.conf
│   └── machine
│       └── zybo-z7.conf
├── COPYING.MIT
├── README
├── recipes-bsp
│   ├── bootfiles
│   │   ├── bootfiles.bb
│   │   └── files
│   │       ├── boot.cmd
│   │       └── uEnv.txt
│   ├── device-tree
│   │   ├── device-tree.bbappend
│   │   └── files
│   │       └── system-user.dtsi
│   └── external-hdf
│       ├── external-hdf_%.bbappend
│       └── files
│           └── zybo-z7-20-hw-platform_spi_uart_i2c.xsa (updated XSA)
├── recipes-core
│   └── base-files
│   |   └── base-files_%.bbappend
├── recipes-kernel (new)
│   ├── linux (new)
│   │   ├── linux-xlnx_6.1-v2023.2.bbappend ß(new or modified)
│   │   └── i2c.cfg ß(added CONFIG_I2C_*)
│   │   └── uartlite.cfg (optional)
|   |   └── spidev.cfg (optional)
└── wks
    └── wic-zynq.wks

Check if setting will work (replace ~/yocto_tmp with your $TMPDIR):

bitbake -c cleansstate linux-xlnx
bitbake -c configure linux-xlnx
grep CONFIG_I2C $(find ~/yocto_tmp/work -type f -name ".config" -path "*/linux-xlnx/*"); 

You should read the following settings:

CONFIG_I2C=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_COMPAT=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_MUX=y
CONFIG_I2C_MUX_PCA954x=y
CONFIG_I2C_HELPER_AUTO=y
CONFIG_I2C_ALGOBIT=y
CONFIG_I2C_CADENCE=y

4. Configure Linux Yocto - configure filesystem

Add I2C tools

In file poky/build/conf/local.conf add the line:

IMAGE_INSTALL:append = " i2c-tools"

to install i2cdetect command needed for testing I2C.

4. Configure Linux Yocto - correct the Device Tree

  • Build your device tree:
 bitbake device-tree

and decode your current device tree:

cp ~/yocto_tmp/deploy/images/zybo-z7/devicetree/system-top.dtb .
dtc -I dtb -O dts system-top.dtb  > system-top.dts 

Our current driver in the sources of our Yocto Linux for AXI I2C has the following table (the source of the driver is in the local build directory: i.e. yocto_tmp\work-shared\zybo-z7\kernel-source\drivers\i2c\busses\i2c-xiic.c):

static const struct of_device_id xiic_of_match[] = {
   { .compatible = "xlnx,xps-iic-2.00.a", .data = &xiic_2_00 },
   { .compatible = "xlnx,axi-iic-2.1", },
   {},
};

As you can see, the "xlnx,xps-iic-2.00.a" has structure .data defined, the second one does not have it. On the other side, our default device tree has the following entry:

compatible = "xlnx,axi-iic-2.1", "xlnx,xps-iic-2.00.a";

The first string from the device tree (xlnx,axi-iic-2.1) is checked first and it matches to the second entry in the driver, so it is treated as success. As the selected "xlnx,axi-iic-2.1" does not have .data in the drivers' table, the driver's initialization code sets the driver into a legacy mode (some read sequences and dynamic mode differences will NOT be configured, sometimes it may work incorrectly). Therefore it is safer to use as the preferred driver for our hardware in device tree the best fully defined driver, i.e.: "xlnx,xps-iic-2.00.a".

So now you will find the node with your i2c device, copy its name and address (here &amba_pl/i2c@41600000) and:

  • add status "okey" to it,
  • correct compatible string to load the right driver

using your existing dtsi file:

poky/meta-custom-zybo-z7/recipes-bsp/device-tree/files/system-user.dtsi

with the following content:

&amba_pl {
    i2c@41600000 {    
        compatible = "xlnx,xps-iic-2.00.a", "xlnx,axi-iic-2.1";
        status = "okay";
    };
};

5. Build Yocto

Build the WIC SC card image:

bitbake core-image-minimal-dev

and burn the WIC image

core-image-minimal-dev-zybo-z7.rootfs-*.wic.gz

using Balena Etcher to SD card.

After building and booting, in Yocto Linux, you should have the following devices in /dev:

ls /dev/i2c-*
/dev/i2c-0

Then you can check with command:

i2cdetect -l

and you should get:

i2c-0 i2c xiic-i2c 41600000.i2c  I2C adapter

 - you should see xiic-i2c driver.

When no physical I2C devices are connected to your Zybo board, the scan:

i2cdetect -y 0

gives some strange numbers:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         08 09 0a 0b 0c 0d 0e 0f
10: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
20: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
30: -- -- -- -- -- -- -- -- 38 39 3a 3b 3c 3d 3e 3f
40: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f
70: 70 71 72 73 74 75 76 77