MicroZed Chronicles: Device Trees
Understand embedded Linux device trees in the Zynq / Zynq MPSoC and MicroBlaze.
One of the great things about Zynq and Zynq MPSoC devices and the MicroBlaze microprocessor is that we can run (them?) on embedded Linux operating systems. This gives us the ability to easily work with networking, communications, and leverage high-level open source frameworks.
Of course, the great thing about using the SoCs of FPGA-based processors is their highly flexible nature that allows new peripherals to be added as needed. For embedded Linux solutions, this flexibility can be an issue as the kernel needs to know the hardware it is running on, its configuration, and also which peripherals are available.
In the embedded Linux world, this information is provided by the device tree. The device tree is separate from the kernel and describes the Linux system in a tree structure. From the root node, each element of the system is defined as a node including the processor, memory, and peripherals.
The interconnect between nodes represents the different buses used to interconnect the system.
Each node will have properties associated with it including address range, device properties, interrupt numbers, etc. Each node needs a name and an optional unit address. Names should be generic and reflect the function of the node. The unit address value is the address of the node on the specific bus type. If there is no unit address on that bus type, the node name acts to distinguish different nodes of the same type connected to the same bus.
As you can hopefully see, using a device tree avoids the need for us to include hard-coded values within the source code. These values must be changed when the hardware description changes.
When working with device trees, there are three commonly used terms:
- DTS – Device Tree Source is a text description of the device tree.
- DTC – The Device Tree Compiler converts the DTS to a compiled output.
- DTB – Device Tree Blob is the compiled device tree that is deployed.
When working with PetaLinux for our Zynq, Zynq MPSoC, and MicroBlaze solutions, the device trees are generated and compiled as part of the configuration and build process. However, at times we need to make updates to the device tree to include additional drivers for elements within either the processing system or programmable logic. For example, including SPI-DEV or support for VDMA.
Within the PetaLinux project, the generated device tree source can be found under the following directory.
<Petalinux_project>/components/plnx_workspace/device-tree-generation
Looking in this directory, you will notice two things. Along with the DTS, there are also several DTSI files. DSTI files are device tree source-include files which can be called up from the DTS. This allows for a modular approach to the DTS creation.
Upon opening one of these files, you will also observe that these files are automatically generated by PetaLinux and as such, changes to the device tree will be lost.
Within the PetaLinux build system, we will find the following DTS and DTSI files:
- System-top.dts – The top-level device tree file.
- pl.dtsi – This file includes the memory mapped IP in the programmable logic.
- pcw.dtsi – The configuration of the dynamic processing system.
- zynq7000.dsti or zynqmp.dsti – Defines the processors and PS peripheral information.
So where do we make changes to the device tree if we need to make any changes? These changes are made under the meta-user layer at the following path:
<petalinux-project>/project-spec/meta-user/recipes-bsp/device-tree/files
Under this location you will see the system-user.dtsi file which is called up by system-top.dts. This lets us include additional drivers and binding. Of course, if necessary, we can include other dtsi files in the system-user.dtsi.
Additions to the system-user.dtsi may not just include additional bindings for elements within the programmable logic, but will also include configuration of the larger board design. For instance, it is common practice to include a I2C multiplexer to select between several I2C channels. Information about this I2C multiplexer needs to be included in the device tree to enable support within the Linux environment.
Let’s take a look in more detail using the Ultra96-V2 board as an example. In the hardware design, the I2C mux is connected to PS I2C1.
Within the system-user.dtsi, we can define another node on I2C1. We can also declare the I2C mux address 0x75 and what the switch is compatible with. For each of the switch outputs, we can then define each of the mux output channels and assign a label if we desire.
When we rebuild the PetaLinux system, this will be included in the embedded Linux solution. Looking under the /dev directory you will see ten I2C ports.
You can then use the command I2Cdetect -l to determine which of the 10 ports are connected to which node.
If you want to know more about device trees, I recommend checking out devicetree.org.
See My FPGA / SoC Projects: Adam Taylor on Hackster.io
Get the Code: ATaylorCEngFIET (Adam Taylor)