Device Tree Tips

This page is intended to be a collection place for tips and tricks related to device trees.

Table Of Contents

1 Documentation


This is the first place you should start to better understand many details of device trees. Many thanks to Free Electrons for their work on this.

Device Trees For Dummies

There are now many other good sites to help with links at the end of the page.

2 Device Tree Bindings


The Linux kernel Documentation directory contains device tree bindings for many devices such that it is the area to consider. Not all Xilinx devices are documented but many are and there is an effort to document them all.

Kernel Device Tree Bindings

3 Zynq UltraScale+ MPSOC Memory Nodes


Zynq UltraScale+ MPSOC is ARM64 such that memory addresses in the device tree memory node utilize 64 bits. There is also an address gap such that not all of the DDR is contiguous in the address map. These both affect the reg property for the memory node and are worthy of explanation.

The following device tree properties control the size of the address and size cells for any reg properties which follow them. Note that each cell is by default 32 bits such that a 64 bit value requires a value of 2 for the numbers of cells.

#address-cells = <2>;
#size-cells = <2>;


The following memory node illustrated two address ranges for the memory with the 1st starting at address 0 and the 2nd starting at address 0x800000000 with both memory ranges having a size of 2 GB each.

memory@0 {
     device_type = "memory";
     reg = <0x0 0x0 0x0 0x80000000>, <0x8 0x00000000 0x0 0x80000000>;
};

In 2018.x release onward DTG reserves 1MB for PMU on lower DDR memory range which is auto generated and this takes care by PCW.

memory@0 {
     device_type = "memory";
     reg = <0x0 0x0 0x0 0x7ff00000>, <0x8 0x00000000 0x0 0x80000000>;
};

4 Clocks

4.1 Linux Disables Clocks



By default Linux disables clocks which have no reference in the device tree (no nodes using the clock). This can create issues in an AMP design where Linux is running on one CPU and another non-Linux based design is running on the other CPU. When the non-Linux CPU software is trying to use devices that are not in the device tree the clock will be disabled such that the device is not accessible.

The text "clk_ignore_unused" can be added to the kernel command line and it will not disable unused clocks. This was tested for a 3.17 Linux kernel (Xilinx 2014.4 release). This topic is discussed in the following forum thread. This option is also documented in the kernel clock documentation referenced at the end of the page.


Clock Disable Linux Forum Thread

4.2 References to Clocks In Nodes


The SLCR node of the device tree contains a clkc subnode which is used by the Zynq clock driver. The clkc node (as illustated below) contains a list of the clocks in the clock-output-names property.

clkc: clkc@100 {
 #clock-cells = <1>;
 compatible = "xlnx,ps7-clkc";
 fclk-enable = <0xf>;
 clock-output-names = "armpll", "ddrpll", "iopll", "cpu_6or4x",
  "cpu_3or2x", "cpu_2x", "cpu_1x", "ddr2x", "ddr3x",
  "dci", "lqspi", "smc", "pcap", "gem0", "gem1",
  "fclk0", "fclk1", "fclk2", "fclk3", "can0", "can1",
  "sdio0", "sdio1", "uart0", "uart1", "spi0", "spi1",
  "dma", "usb0_aper", "usb1_aper", "gem0_aper",
  "gem1_aper", "sdio0_aper", "sdio1_aper",
  "spi0_aper", "spi1_aper", "can0_aper", "can1_aper",
  "i2c0_aper", "i2c1_aper", "uart0_aper", "uart1_aper",
  "gpio_aper", "lqspi_aper", "smc_aper", "swdt",
  "dbg_trc", "dbg_apb";
 reg = <0x100 0x100>;
};


Other nodes in the device tree reference the clocks in the clkc node to indicate that the node uses the clock. The following property illustrates an example clock property.

clocks = <&clkc 15>;


The &clkc is a reference to the clkc node which contains the clock-output-names. The 15 is a zero based index into the clock-output-names such that it refers to fclk0.

4.2.1 Device Driver Example


The following code illustrates an example of a Linux device driver using the clocks property of a device tree node. The snippet of code was taken from the probe function. When the clocks property is not found the driver exits and does not probe successfully. This failure may be silent or may have console output which may not be explicit that the clock property was the issue.

            xadc->clk = devm_clk_get(&pdev->dev, NULL);
            if (IS_ERR(xadc->clk)) {
                        ret = PTR_ERR(xadc->clk);
                        goto err_free_samplerate_trigger;
            }
            clk_prepare_enable(xadc->clk);

4.3 Clock Status At Runtime


The clocks of the system can be viewed in the filesystem at /sys/kernel/debug/clk with clk_summary showing all the clocks. The debug filesystem must be turned on in the kernel configuration for this feature to be used. The following configuration path allows the debug filesystem to be turned on.

Kernel hacking -> Compile-time checks and compiler options -> Debug Filesystem (CONFIG_DEBUG_FS)

The following example illustrates the clock summary at runtime. When the enable count is zero the clock is disabled. For more details see the link for the kernel clock documentation at the end of the page.

   clock                         enable_cnt  prepare_cnt        rate   accuracy   phase
----------------------------------------------------------------------------------------
ps_clk                                   3            3      33333333        0 0
  iopll_int                              1            1      999999990       0 0
    iopll                                7            7      999999990       0 0

4.4 Creating A Clock


Some devices in the device tree may require clock nodes to describe their clock inputs. For fixed clocks this is easily done as illustrated below.

osc: oscillator {
     compatible = "fixed-clock";
     #clock-cells = <1>;
     clock-frequency  = <1000000>;
     clock-output-names = "osc";
};


The clock can be referenced in another node as illustrated below.

max3109@0 {
    compatible = "maxim,max3109";
    reg = <0>;
    clocks = <&osc 0>;
    clock-names = "osc";
};


5 Disabling A Device Node


There are times when a device in the device tree, a node, is not wanted in the system. The status property of a node can be used to disable it. This property may not be present on all nodes by default, but can be added.

&i2c1 { 
    status = "disabled";
};

6 Fixing A Broken Node


There may be node that has a property which only has to exist, with no specific value (such as xlnx,tx_termination_fix), and the driver just checks for it's existence in the device tree. There is no easy way to get rid of the property in an existing node but you can create a new node with a new name which does not have that property. Any references to the old node will need to change to the new node.

7 Interrupt Inputs Using GPIO


Interrupts can be connected direct to an interrupt controller or they can be connected to a GPIO input that can generate an interrupt. The following device tree illustrates the changes required to support this feature. It adds interrupt controller ability to the existing GPIO node and then indicates in the SPI device node that the GPIO node is the interrupt controller.

The interrupts property on the SPI device node uses the same interrupt type (edge, level, etc...) as when connected to an interrupt controller. The interrupt number in the interrupts property is the GPIO pin number on the GPIO controller. For example, on Zynq with the PS GPIO using an MIO for the interrupt, the interrupt number starts at 0 which corresponds to GPIO pin 0 and MIO0. This GPIO pin number is not the same as the GPIO pin numbers see in /sys/class/gpio as those seem to be a virtualized pin number and can be a bigger number as the base.

&gpio0 {
        #interrupt-cells = <2>;
        interrupt-controller;
};
 
&spi1  {
 
        adxl345 {
                compatible = "adi,adxl34x";
                reg = <1>;
                spi-max-frequency = <1000000>;
                spi-cpha;
                spi-cpol;
                interrupt-parent = <&gpio0>;
                interrupts = < 0 4 >;
        };
};

The interrupt should be seen in /proc/interrupts and it should should show the GPIO node as the interrupt controller.

Note: This feature has less testing and early testing with a 3.19 kernel has shown there are GPIO driver issues related to clocking when using an interrupt that is active high level triggered. There should be a commit to alter Zynq GPIO coming in future to fix the issue.

8 Using UIO


Typically a node is added to the device tree or an existing node is used for the UIO framework/driver. The node compatible property could be any desired string but "generic-uio" is typically used. The key is that the bootargs of the kernel must be altered as described below to match this string.

8.1 Kernel Bootargs


After adding the "compatible = generic-uio" to the device tree node as described above, the boot args of the kernel must be altered to allow the UIO device driver to be compatible with the device tree node.

Add "uio_pdrv_genirq.of_id=generic-uio" to the bootargs of the kernel in the device tree. This string is altering the module parameter of_id for the uio_pdrv_genirq device driver so that when it's built into the kernel (rather than a module) it will be compatible with the device tree node. If the driver is inserted as a module the user could also specify the module parameter at the time of insertion into the kernel.

9 How to add or delete nodes and properties in PetaLinux

9.1 Adding a New Node or Update existing nodes

New nodes in the device tree should be added near the top of <plnx-proj-root>/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi between the first pair of brackets "{}" as illustrated below.

/include/ "system-conf.dtsi"
{
	/* Add new nodes here, One such example */
	gpio-lkm@0xa0000000 {
        	compatible = "xlnx,gpio-lkm";
	        reg = <0x0 0xa0000000 0x0 0x10000>;
        	xlnx,gpio-width = <2>;
		    interrupt-names = "ip2intc_irpt";
        	interrupt-parent = <&gic>;
	        interrupts = <0 89 4>;
	};
};
 
/* Modify existing nodes here */
&i2c1 {

	/* FIXME PL i2c via PCA9306 - u45 */
	/* FIXME MSP430 - u41 - not detected */
	i2c-mux@74 { /* u34 */
		i2c@0 { /* i2c mw 74 0 1 */
			/*
			 * IIC_EEPROM 1kB memory which uses 256B blocks
			 * where every block has different address.
			 *    0 - 256B address 0x54
			 * 256B - 512B address 0x55
			 * 512B - 768B address 0x56
			 * 768B - 1024B address 0x57
			 */
			eeprom: eeprom@54 { /* u23 */
				compatible = "atmel,24c08";
				reg = <0x54>;
			};
		};
	};
};

9.2 Deleting a Node or Properties of existing nodes

Delete nodes or properties in the device tree should be added near the top of <plnx-proj-root>/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi between the first pair of brackets "{}" as illustrated below.


/include/ "system-conf.dtsi"
{
	/* Before deleting the node make sure you have removed the alias and other reference. */
    aliases {
        /delete-property/ i2c0;
    };

};

/* Delete existing nodes which has reference in alias */
&amba_pl {
    /delete-node/ i2c@40800000;
};
 
/* Delete existing properties from a node */
&dwc3{
    /delete-property/ phy-names;
    /delete-property/ phys;
};

9.3 Adding a New Node or Update existing Device-tree Overlay nodes

New nodes in the device tree overlay should be added near the top of <plnx-proj-root>/project-spec/meta-user/recipes-bsp/device-tree/files/pl-custom.dtsi between the first pair of brackets "/{}" as illustrated below.

/ {
	/* Add new nodes here, One such example PL VCU Encoder and Decoder */
	fragment@9 {
		target = <&amba>;
		overlay9: __overlay__ {
			vcu: vcu@a0000000 {
				#address-cells = <2>;
				#clock-cells = <1>;
				#size-cells = <2>;
				clock-names = "pll_ref", "aclk", "vcu_core_enc", "vcu_core_dec", "vcu_mcu_enc", "vcu_mcu_dec";
				clocks = <&misc_clk_3>, <&misc_clk_0>, <&vcu 1>, <&vcu 2>, <&vcu 3>, <&vcu 4>;
				compatible = "xlnx,vcu-1.2", "xlnx,vcu";
				interrupt-names = "vcu_host_interrupt";
				interrupt-parent = <&gic>;
				interrupts = <0 89 4>;
				ranges ;
				reg = <0x0 0xa0040000 0x0 0x1000>, <0x0 0xa0041000 0x0 0x1000>;
				reg-names = "vcu_slcr", "logicore";
				reset-gpios = <&gpio 79 0>;
				encoder: al5e@a0000000 {
					compatible = "al,al5e-1.2", "al,al5e";
					interrupt-parent = <&gic>;
					interrupts = <0 89 4>;
					reg = <0x0 0xa0000000 0x0 0x10000>;
				};
				decoder: al5d@a0020000 {
					compatible = "al,al5d-1.2", "al,al5d";
					interrupt-parent = <&gic>;
					interrupts = <0 89 4>;
					reg = <0x0 0xa0020000 0x0 0x10000>;
				};
			};
		};
	};
};
 
/* Modify existing DT overlay nodes here */
&overlay0 {
	fpga-config-from-dmabuf;
};

9.4 Deleting a Node or Properties of existing Device-tree Overlay nodes

Delete nodes or properties in the device tree should be added near the top of <plnx-proj-root>/project-spec/meta-user/recipes-bsp/device-tree/files/pl-custom.dtsi between the first pair of brackets "/{}" as illustrated below.


/ {
	/* Delete DT fragment node property */
	fragment@1 {
		/delete-property/ target;
	};
	
	/* Delete DT overlay node */
	fragment@2 {
		/delete-node/ __overlay__;
	};
};

10 References


Fabric Clock Control

Kernel Interrupt Bindings

Undocumented Features of Device Tree

DeviceTree Specifications

© Copyright 2019 - 2022 Xilinx Inc. Privacy Policy