Adding a Custom IP Node
This page contains an overview of how to add a custom IP node to a Zephyr project along with an example.
Table of Contents
Introduction
As you develop with Zephyr, you may need to add custom IP nodes that are not officially supported in the AMD Zephyr or mainline Zephyr repositories. This page will go over the requirements for adding an unsupported node as well as walk through a simple example step-by-step. If you are not already familiar with device trees and device tree bindings, please consult Zephyr’s device tree guide and their device tree bindings guide.
By default, Zephyr sets the chosen memory node as the largest memory node in the hardware design. Users can edit this manually after device tree generation.
Adding a Custom IP Node
Prerequisites
To add a new node to an existing device tree, there are two requirements: a device tree binding and the corresponding node. The device tree binding will determine the properties of the node while the node itself can be added by either hand editing the board’s <BOARD>.dts
file or by creating a <BOARD>.overlay
file. The recommended flow is to create an overlay since if you update your local Zephyr repo, the changes you’ve made to <BOARD>.dts
may be erased.
Another reason to use <BOARD>.overlay
is that when west lopper-command
is called to generate a device tree, it removes any officially unsupported IP node from the device tree.
Choosing an IP Node
This tutorial will cover how to add a GPIO node to the MicroBlaze V processor in the 2024.2 AMD Zephyr release. This node was chosen because it is a common node that will serve as a good example. Moving forward, we will assume that we configured the GPIO as shown in the image below:
This configuration was chosen because it allows us to show many different properties enabled in the device tree.
Adding a Device tree Binding
Zephyr will look for bindings
in the dts/bindings
subdirectories of the following places:
The Zephyr repository
Your application source directory
Your board directory
Any directories manually included in the DTS_ROOT CMake variable
Any module that defines a
dts_root
in its build settings
As mentioned above, the device tree binding defines what properties a node contains. In Zephyr, these are .yaml
files and can be found in zephyr/dts/bindings/<device>
(for example zephyr/dts/bindings/gpio
is a directory containing various GPIO bindings). Looking at zephyr/dts/bindings/gpio
, we can see that there are four different xlnx
GPIO bindings already available with descriptions:
xlnx,ps-gpio-bank.yaml
– Xilinx Zynq 7000/ZynqMP MIO/EMIO GPIO controller bank nodexlnx,ps-gpio.yaml
– Xilinx Zynq 7000/ZynqMP MIO/EMIO GPIO controller nodexlnx,xps-gpio-1.00.a.yaml
– Xilinx AXI GPIO IP nodexlnx,xps-gpio-1.00.a-gpio2.yaml
– Xilinx AXI GPIO IP GPIO2 node
From the descriptions, it looks like what we’re looking for (a generic AXI GPIO node) is covered by xlnx,xps-gpio-1.00.a.yaml
(link). Because we enabled dual channel in our configuration, we will also need xlnx,xps-gpio-1.00.a-gpio2.yaml
(link). From looking at the files, we can see that there is one required property, reg
, and a variety of properties we can enable/disable to match how we configured the GPIO in Vivado.
Furthermore, looking into the included device tree bindings, we find gpio-controller.yaml
(link), base.yaml
(link), and pm.yaml
(link). These files are responsible for adding two more required properties, gpio-controller
and #gpio-cells
, as well as two more properties we are interested in for controlling interrupts, interrupt-parent
and interrupts
.
Adding a Device tree Overlay
Zephyr will look for .overlay
files in the following places in your application source directory:
If the file
socs/<SOC>_<BOARD_QUALIFIERS>.overlay
exists, it will be used.If the file
boards/<BOARD>.overlay
exists, it will be used in addition to the above.If the current board has multiple revisions and
boards/<BOARD>_<revision>.overlay
exists, it will be used in addition to the above.If one or more files have been found in the previous steps, the build system stops looking and just uses those files.
Otherwise, if
<BOARD>.overlay
exists, it will be used, and the build system will stop looking for more files.Otherwise, if
app.overlay
exists, it will be used.
Before we can create the .overlay
file, we should take a look at the existing device tree for the MicroBlaze V. We are going to want to add our GPIO under axi: soc
, so we should start our overlay file from there. In this case, we will not be overwriting any of the default values, so we will jump straight to the GPIO nodes:
/ {
axi: soc {
axi_gpio_0_1: gpio@FACEFEED {
compatible = "xlnx,xps-gpio-1.00.a";
reg = <0xFACEFEED 0x10000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-parent = <&axi_intc>;
interrupts = <0x2 0x0>;
xlnx,all-inputs = <1>;
xlnx,all-outputs = <0>;
xlnx,dout-default = <0x0>;
xlnx,gpio-width = <0x20>;
xlnx,tri-default = <0xffffffff>;
xlnx,is-dual = <1>;
xlnx,all-inputs-2 = <0>;
xlnx,all-outputs-2 = <1>;
xlnx,dout-default-2 = <0x0>;
xlnx,gpio2-width = <0x20>;
xlnx,tri-default-2 = <0xffffffff>;
axi_gpio_0_2: gpio2 {
compatible = "xlnx,xps-gpio-1.00.a-gpio2";
gpio-controller;
#gpio-cells = <2>;
};
};
};
};
Change 0xFACEFEED
to the correct address and save the following file as mbv32.overlay
in your application’s source directory.
For example, we could use zephyr/tests/misc/test_build
as a base to make sure we formatted the overlay correctly before developing more.
Building the Application
Execute the following commands:
cd <path_to>/zephyr
cp <path_to>/mbv32.overlay ./tests/misc/test_build/
west build -p -b mbv32 ./tests/misc/test_build/
cd build/zephyr
This folder will contain the final combined devicetree called zephyr.dts
which will now include your added GPIO nodes:
/dts-v1/;
/ {
#address-cells = < 0x1 >;
#size-cells = < 0x1 >;
model = "AMD MicroBlaze V 32bit";
compatible = "qemu,mbv", "amd,mbv";
cpus: cpus {
#address-cells = < 0x1 >;
#size-cells = < 0x0 >;
timebase-frequency = < 0x5f5e100 >;
cpu_0: cpu@0 {
compatible = "amd,mbv32", "riscv";
device_type = "cpu";
reg = < 0x0 >;
riscv,isa = "rv32imafdc";
i-cache-size = < 0x8000 >;
d-cache-size = < 0x8000 >;
clock-frequency = < 0x5f5e100 >;
cpu0_intc: interrupt-controller {
compatible = "riscv,cpu-intc";
interrupt-controller;
#interrupt-cells = < 0x1 >;
phandle = < 0x1 >;
};
};
};
aliases {
serial0 = &uart0;
};
chosen {
bootargs = "earlycon=sbi console=ttyUL0,115200";
stdout-path = "serial0:115200n8";
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,sram = &ram0;
};
ram0: memory@80000000 {
device_type = "memory";
reg = < 0x80000000 0x80000000 >;
};
lmb0: memory@0 {
compatible = "mmio-sram";
reg = < 0x0 0x20000 >;
};
clk100: clock {
compatible = "fixed-clock";
#clock-cells = < 0x0 >;
clock-frequency = < 0x5f5e100 >;
phandle = < 0x3 >;
};
axi: soc {
#address-cells = < 0x1 >;
#size-cells = < 0x1 >;
compatible = "simple-bus";
ranges;
bootph-all;
axi_intc: interrupt-controller@41200000 {
compatible = "xlnx,xps-intc-1.00.a";
reg = < 0x41200000 0x1000 >;
interrupt-controller;
interrupt-parent = < &cpu0_intc >;
#interrupt-cells = < 0x2 >;
interrupts-extended = < &cpu0_intc 0xb >;
xlnx,kind-of-intr = < 0x1a >;
xlnx,num-intr-inputs = < 0xb >;
phandle = < 0x2 >;
};
xlnx_timer0: timer@41c00000 {
compatible = "amd,xps-timer-1.00.a";
reg = < 0x41c00000 0x1000 >;
interrupt-parent = < &axi_intc >;
interrupts = < 0x0 0x2 >;
xlnx,one-timer-only = < 0x0 >;
clocks = < &clk100 >;
};
uart0: serial@40600000 {
compatible = "xlnx,xps-uartlite-1.00.a";
reg = < 0x40600000 0x1000 >;
interrupt-parent = < &axi_intc >;
interrupts = < 0x1 0x2 >;
clocks = < &clk100 >;
current-speed = < 0x1c200 >;
};
axi_gpio_0_1: gpio@FACEFEED {
compatible = "xlnx,xps-gpio-1.00.a";
reg = < 0xfacefeed 0x10000 >;
gpio-controller;
#gpio-cells = < 0x2 >;
interrupt-parent = < &axi_intc >;
interrupts = < 0x2 0x0 >;
xlnx,all-inputs = < 0x1 >;
xlnx,all-outputs = < 0x0 >;
xlnx,dout-default = < 0x0 >;
xlnx,gpio-width = < 0x20 >;
xlnx,tri-default = < 0xffffffff >;
xlnx,is-dual = < 0x1 >;
xlnx,all-inputs-2 = < 0x0 >;
xlnx,all-outputs-2 = < 0x1 >;
xlnx,dout-default-2 = < 0x0 >;
xlnx,gpio2-width = < 0x20 >;
xlnx,tri-default-2 = < 0xffffffff >;
axi_gpio_0_2: gpio2 {
compatible = "xlnx,xps-gpio-1.00.a-gpio2";
gpio-controller;
#gpio-cells = < 0x2 >;
};
};
};
};
Related content
© Copyright 2019 - 2022 Xilinx Inc. Privacy Policy