If you have a BeagleBone Black (BBB) and you want to wire up your own devices to it (not capes), you might already have heard about the device tree. In my case, I wanted to connect an RTC device to the I²C bus on the BBB. There is lots of information scattered around the web and this article is meant to be a summary of what I found as also a guide to get it done.
So I'll give a full example of activating the I²C bus on the BBB as well as hooking up a DS1308 RTC chip using the device drivers included in the kernel. Does it sound interesting?
Then read on and please leave comments if anything is not clear. If you are in a bit of a hurry you can also just grab the device tree overlay code on GitHub and fly away.
I'm using Arch Linux ARM on my BBB mainly because Arch Linux is awesome and I am possibly too stupid to use debianoid distributions.
Here's a screenfetch of the system...
As you might notice, the kernel version is already above that 3.x stuff. What you can not see in the screenfetch is that the kernel supports device tree overlays using the Capemgr utility.
I'll just do it quick, and you can find deeper knowledge here, here, here and here.
The device tree is a structure describing the underlying hardware on your platform. It's heavily used in embedded devices since SoCs and stuff don't have buses, like PCI, where devices can be discovered. They have to be defined statically and are attached to a "platform bus" to give a handle to the device drivers shipped with the kernel.
Before the device tree was introduced to Linux, all that work had to be done with specific C header files and custom implementations which then all had to be merged into the mainline kernel. Thus that being an imaginable exhaustive task, it came to the famous Linus Torvalds rant. Here you go with still some more device tree background.
To describe the device tree we're using .dts
(device tree source) files, which are human-readable and get compiled by the device tree compiler (dtc
) into device tree blobs (.dtb
), a binary format. When the system boots the bootloader (e.g., U-Boot) hands over that blob to the kernel. The kernel parses it and creates all the devices as given by the device tree.
If you're not believing me, use the device tree compiler to peak into the device tree your BBB is using right now.
If you haven't installed it yet, get the appropriate package...
pacman -Sy dtc-overlay
dtc -f -I fs /proc/device-tree | less
That pipe to the pager less
is recommended due to a lot of output generated by that command. The result should look something like this...
All the parts of your device tree could also be investigated within the kernel source but since there is also an include mechanism the information is split among several files in
<kernel-source>/arch/arm/boot/dts/..
Some relevant files are:
am335x-bone-common.dtsi
am335x-boneblack.dts
am33xx.dtsi
Note: The
.dtsi
files are equivalent to.h
files in C or C++ because they get included (therefore the 'i' at the end) by.dts
files
They all describe devices related to the processor, common devices on the BeagleBone platform or devices only suitable on the BeagleBone Black.
Good question. I see you're still with me. As I said before, the device tree blob is parsed when the kernel boots. So when your system is up and running, the whole magic is already over. On a platform, like the BBB, with a whole bunch of expansion boards (Capes), this would require you to recompile the device tree every time you go for another cape to use.
Therefore, you have the overlay mechanism that allows you to add or modify devices in your device tree at runtime! Amazing.
Note: to be able to compile device tree overlays, make sure to install the appropriate package, like above (dtc-overlay
)
I'll give you an example. Since the BBB doesn't have a real-time clock (RTC) which would be useful to generate timestamps for measurements, etc., we are going to fix that.
We'll use a ds1307 real-time clock chip (in fact, I have a ds1308 RTC, but the driver is compatible) and communicate with it over the I2C1 bus on the BBB. By default, that bus is disabled on the BBB, as you can see from the device tree sources..
The important information in that snippet is:
Now we'll create an overlay to configure the GPIO pins for the i2c1 bus, activate that bus and afterwards we'll add the rtc-device i2c1 bus, so that the appropriate driver is automatically loaded and the rtc-device gets created in /dev
.
The GPIO pins on the P8 and P9 headers on the BBB have multiple functionalities which are multiplexed together, and therefore we have to adjust the pinmux settings to use them for I²C communication. As you can see in this table for the I2C1 bus, we'll have to use the header pins 17 and 18 in multiplex mode 2. To get more information on the GPIO handling on the BBB, look here.
/dts-v1/;
/plugin/;
/{ /* This is our device tree overlay root node */
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "BBB-I2C1"; // You can choose any name here but it should be memorable
version = "00A0";
fragment@0 {
target = <&am33xx_pinmux>; // This is a link to an already defined node in the device tree, so that node is overlayed with our modification
__overlay__ {
i2c1_pins: pinmux_i2c1_pins {
pinctrl-single,pins = <
0x158 0x72 /* spi0_d1.i2c1_sda */
0x15C 0x72 /* spi0_cs0.i2c1_sdl */
>;
};
};
};
}; /* root node end */
At a first glance, the overlay syntax looks quite weird, but it's basically made of so-called fragments that target an already-existing device node and modify that node (and its children).
In this case, we target the am33xx_pinmux
device node that is defined in the processor's device tree (am33xx.dtsi
). Within that node, we add a new child node called pinmux_i2c1_pins, which was not existent before (take a look at am335x-bone-common.dtsi
to verify) and the label i2c1_pins.
The next part is a bit more complex, and if you are interested, read this. Every GPIO pin is configured by a single register with several bits to control its behavior and all the registers are controlled by the pinctrl-single
driver. To set a specific pin, just use its address offset from the base address (you will find that in the P9 header table above) and its pin configuration as the second parameter..
I borrowed this overview from Derek Molloy to explain the pin mode. Since 0x72
is equivalent to 01110010b
, we have both pins configured as inputs with an enabled pull-up resistor and active slew control in multiplex mode 2.
And multiplex mode 2 for these pins means pin 17 on header P9 is the clock line SCL and pin 18 on header P9 is the data line SDA.
That's absolutely correct, so let's extend our overlay as follows...
/dts-v1/;
/plugin/;
/{ /* This is our device tree overlay root node */
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "BBB-I2C1"; // You can choose any name here, but it should be memorable
version = "00A0";
fragment@0 {
target = <&am33xx_pinmux>; // This is a link to an already defined node in the device tree, so that node is overlayed with our modification
__overlay__ {
i2c1_pins: pinmux_i2c1_pins {
pinctrl-single,pins = <
0x158 0x72 /* spi0_d1.i2c1_sda */
0x15C 0x72 /* spi0_cs0.i2c1_sdl */
>;
};
};
};
fragment@1 {
target = <&i2c1>;
__overlay__ {
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
rtc: rtc@68 { /* The real-time clock defined as child of the i2c1 bus */
compatible = "dallas,ds1307";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x68>;
};
};
};
}; /* Root node end */
In the code above, we added a new fragment that targets the i2c1 device node and tells it to use our previously defined pin configuration. We set a I²C clock frequency of 100 kHz and activate the device.
Furthermore, the RTC clock was added as a child to the i2c1 node. The important information for the kernel is the compatible statement, naming the driver to use (ds1307
) and the devices address on the I2C bus (0x68
). The RTC's I²C address can be obtained from the datasheet.
At first, the device tree source has to be compiled. Use the 'dtc' compiler with the following call..
dtc -O dtb -o <filename>-00A0.dtbo -b 0 -@ <filename>.dts
Caution! The filename must be a concatenation of the name you desire plus the version tag as seen above (-00A0). Otherwise, you'll have a hard time.
The resulting .dtbo
file should be copied into /lib/firmware
, and I really have no idea where that "-00A0" naming convention comes from, but there are other files in the firmware directory using it as well.
From now on, you can load your overlay dynamically by using Capemgr. To do so, move into /sys/devices/platform/bone_capemgr/
and then execute..
echo <filename> > slots
Capemgr will then look for your .dtbo
file in the firmware directory and load it if possible. By looking into the slots file, you can see if the procedure was successful. It should look something like this...
Examine the device tree used by the BeagleBone.
dtc -f -I fs /proc/device-tree | less
You'll find all the entries from the overlay...
Furthermore, there should be a new I²C device (/dev/i2c-1
) and a new RTC device (/dev/rtc1
) in your filesystem.
To have a look on your I²C buses, install the package i2c-tools
and use..
i2cdetect -r 1
The output should be something like this..
As you can see, address 0x68 is occupied by a device.
To query your RTC, use..
hwclock -r -f /dev/rtc1
No, there is one more option you have, loading device tree overlays at boot. Awesome!
To do so, open /boot/uEnv.txt
and add bone_capemgr.enable_partno=<filename>
to the optargs
statement. That's what it looks on my BBB
optargs=coherent_pool=1M bone_capemgr.enable_partno=bbb-i2c1
Confusingly, the filename is used in optargs, not the part-number
tag defined in the device tree overlay.
You can get my example code aside a useful Makefile on GitHub if you like.
This is very helpful and valuable information. I wrote an I²C kernel driver which I can load dynamically to talk to a custom chip at address 0x77. I have been successful in communicating with the chip in the past by instantiating the device manually as follows:
echo act2_chip 0x77 > /sys/bus/i2c/devices/i2c-1/new_device
After the device is instantiated, I can see it using i2cdetect tools and my loadable kernel driver can communicate with the chip.
Now I am trying to instantiate the device using the device tree method. So following your lead, I changed some parameters in your dtsi file like below:
fragment@1 {
target = <&i2c1>;
__overlay__ {
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
status = "okay";
act2_chip: act2_chip@77 { /* The real time clock defined as child of the i2c1 bus */
compatible = "xx,act2_chip";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x77>;
};
I connected the chip at pins 17 and 18 for SCL and SDA. Here is the dmesg output I get after echo <filename> > slots
:
But while inserting the driver into the kernel, I see the probe function being called. this means the driver is able to see the device as far as I think.
And when I try to write to the kernel driver, I get the following message:
omap_i2c 4802a000.i2c: controller timed out
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With