Conquering the Linux device tree
New hardware can easily be added to a Linux device if you are using the correct driver and the correct information in the device tree. The driver acts as a translator between the operating system (OS) and the hardware while the device tree describes what hardware exists within the system and which buses and pins they are using, describing where, and how, the OS should try to talk to the hardware.
We needed to add a second ethernet port to our hardware, so I found a suitable IC (Wiznet W5500) with software support already existing in the Linux kernel. To be able to add this device to our cape, it needs to be put into the hardware design of the Printed Circuit Board (PCB), but before that happens, it should be tested.
You usually come across the device tree files in two forms, source files and overlays. The source file is the core file that is read by the bootloader upon starting your device, which later on loads the overlay that contains changes or additions to the source. Bigger changes in the device tree by overlays are usually added to the device tree source. A device tree overlay can be used to quickly append new device information to a existing device tree.
The Wiznet W5500 communicates to other devices via SPI (Serial Peripheral Interface), and thus needs the master device to support SPI. The BeagleBone Green (BBG) has two separate SPI interfaces available. We are going to use the second SPI port, as the first one is busy. The BBG has other serial interfaces that can be used to talk to external peripherals, such as I2C and UART, but SPI is chosen because it supports higher speeds than I2C.
Finding out what GPIOs that support SPI is an easy task if you look into the documentation for the P8 and P9 headers. The BBG is pin compatible with the BeagleBoneBlack (BBB), so the documentation covers both models.
As there are more peripherals than there are (General Purpose Input Output) GPIOs on the BBG/BBB, each GPIO can be programmed to do several different tasks via the built in pin MUX inside the AM335X chip. The pins chosen in the examples below are specifically chosen as they dont interfere with the peripherals existing on our cape today.
Connecting the hardware
For development I used MikroElektronika's prototyping solution in the form of a cape compatible with their click boards. Assembly is quick and requires little to no knowledge to do.
The device tree overlay
Lets take a look at the Device Tree Overlay (DTO). The snippet below shows a complete example of a valid DTO. It appends device properties to the device tree source thats already loaded into the system (We will get into that part later). It begins with /dts-v1/;
, which is a header declaring that this is a device tree file. If the file is a overlay, it should be followed by a /plugin/;
, which allows for undefined label references to be made and saved.
The fragment contains a target statement which appends into a already existing node inside the existing DTS when loaded, in this case the ocp (OCP means On-Chip Peripheral) node.
/dts-v1/;
/ {
node_1 {
compatible = "manufacturer, device_cpu";
a-property = "this is a property";
a-label : a_child_node {
another-property = "this property belongs to the child node";
};
fragment@1 {
// This is a comment, but only one line.
/*
* This
* is
* also
* a
* comment
*/
target = <&ocp>;
__overlay__ {
status = "okay";
};
};
};
};
The device tree consists of nodes and fragments. A node can have child nodes.
Each new device inside the DT consists of a node. The node should contain the properties describing the parameters necessary for the OS to couple the driver to the hardware. Pinout, GPIO configuration, serial interface connections and parameters such as speed, interrupt configuration etc. are part of these necessary parameters.
Lets look into some snippets from the device tree overlay for the Wiznet W5500.
fragment@1 {
target = <&ocp>;
__overlay__ {
P9_12_pinmux {
status = "disabled";
};
};
};
Fragment@1 targets the <&ocp>
node where it appends our new information.
Here we can see P9_12_pinmux
being configured as disabled, thus making the GPIOs out of reach for other code to change it. This is because the W5500 chip need this pin reserved for the purpose of chip reset. So while this DTO is loaded, no other code can change the pinmux affecting this pin.
Fragment@0 contains pin assignments for both the w5500 unique pins (INT and RESET) which can be seen in the node w5500_pins
, and the more standard SPI pins, which are configured in the node bbg_spi1_pins
.
fragment@0 {
target = <&am33xx_pinmux>;
__overlay__ {
w5500_pins: pinmux_w5500_pins {
pinctrl-single,pins = <
BONE_P9_15 ( PIN_INPUT | MUX_MODE7) /* INT */
BONE_P9_12 ( PIN_OUTPUT_PULLUP | MUX_MODE7 ) /* RESET */
>;
};
bbg_spi1_pins: pinmux_bbg_spi1_pins {
pinctrl-single,pins = <
BONE_P9_31 ( PIN_INPUT | MUX_MODE3 ) /* SCLK */
BONE_P9_29 ( PIN_INPUT | MUX_MODE3 ) /* D0 */
BONE_P9_30 ( PIN_INPUT | MUX_MODE3 ) /* D1 */
BONE_P9_28 ( PIN_INPUT | MUX_MODE3 ) /* CS0 */
>;
};
};
};
Mentioned in the code is also some properties describing the interrupt functionality (if needed). As seen in fragment@2 below, the ´interrupt-parent´ property´ is being set to gpio1. This is appending this node into the gpio1 interrupt-controller. The later ´interrupts´ property is set to ´<16 IRQ_TYPE_EDGE_FALLING>´, where 16 is the gpio number on the AM3358 CPU, and IRQ_TYPE_EDGE_FALLING describes what type of event that should trigger the interrupt.
fragment@2 {
target = <&spi1>;
__overlay__ {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&bbg_spi1_pins>;
#address-cells = <1>; /* number of cells required to define a chip select address on the SPI bus */
#size-cells = <0>; /* Should be zero */
channel@0 {
status = "disabled"; /* Enabled by default in bb-kernel so users can use spidev from userspace */
};
channel@1 {
status = "disabled";
};
w5500: ethernet@0 {
compatible = "wiznet,w5500";
pinctrl-names = "default";
pinctrl-0 = <&w5500_pins>;
reg = <0x0>; /* chip select address of device */
interrupt-parent = <&gpio1>; /* Should match the chosen gpio interrupt group */
interrupts = <16 IRQ_TYPE_EDGE_FALLING>; /* same as x in gpioY[x], see table http://exploringbeaglebone.com/wp-content/uploads/resources/BBBP9Header.pdf */
spi-max-frequency = <24000000>;
};
};
};
The device tree source
When more permanent changes to the system is made, like putting new perpherials onto the cape, the changes made in the previously made overlay can instead be merged into the device tree source (DTS).
The merged device tree source will contain the information for the entire system.
/dts-v1/;
node-name {
node-property = "This_is_a_node_property";
}
Above we can see a valid DTS. Our overlay will be merged into this while the system is live, and can be removed while the system is live, thus disabling the added device in the overlay.
Kernel drivers
Now that the OS knows which pins it can expect communication with our new device, we need to make sure that the OS also knows what the communication means, or how to translate the device commands into its own commands. This is done by using the correct driver. By choosing the correct Linux kernel we will provide our system with the correct driver that integrates into our OS. The W5500 driver first appeared in upstream Linux 4.4, but the SPI drivers for the IC was not implemented by upstream kernels until 4.6, so that is the lowest kernel version we can use.
Setting up the interface with ifconfig
Linux needs to be told that the new interface should be used, so we need to run sudo ifconfig eth1 up
. This tells Linux to enable the interface and start communicating via it.
Shuffle data around!
By now we should have a fully functional ethernet port to communicate via. The bandwidth is of course more limited than the on-board PHY on the BBG, but it can provide is with a dedicated port for traffic filtering and Modbus TCP/IP communication.
By Eric Felix