Why should I read this?
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 a RTC device to the I2C 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 I2C bus on the BBB as well as hooking up a DS1308 RTC chip using the device drivers included in the kernel. Sounds 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.
First things first.
I'm using ArchLinux 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.
What is the device tree?
I'll just do it quick, 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 a imaginable exhaustive task it came to the famous Linus Torvalds rant. Here you go with still some more device tree background.
Yeah nice, but how does it work?
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.
You mentioned overlays, what's that?
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
)
And how am I going to use all this?
I'll give you an example. Since the BBB has no 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:
- a node named 'i2c1' is defined
- it is defined as compatible to the omap4-i2c driver
- the device gets assigned a memory mapped address (0x4802a000) and an appropriate address range (0x1000) according to the processors reference manual (page 181)
- the devices status is disabled
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 muxed together and therefore we have to adjust the pinmux settings to use them for I2C communication. As you can see in this table for the I2C1 bus we'll have to use the header pins 17 and 18 in mux 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 */
OMG what just happend?
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 it's children).
In this case we target the am33xx_pinmux
device node that is defined in the processors 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 it's behavior and all the registers are controlled by the pinctrl-single
driver. To set a specific pin just use it's address offset from the base address (you will find that in the P9 header table above) and it's pin configuration as 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 enabled pullup resistor and active slew control in mux mode 2.
And mux 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.
But we still have to enable I2C1?
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 tell's it to use our previously defined pin configuration. We set a I2C clock frequency of 100kHz 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 I2C address can be obtained from the datasheet.
And how do I get that code into the kernel?
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 I2C device (/dev/i2c-1
) and a new rtc device (/dev/rtc1
) in your filesystem.
To have a look on your i2c buses install the package i2c-tools
and use..
i2cdetect -r 1
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
But that's not all, or is it?
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.
Sorry for long post.
./arch/arm/boot/dts
) and add your custom configuration. That is the easy part. Afterwards you need to recompile the device tree for your board and replace the old device tree binary blob, which usually is attached to the kernel uimage or zimage files. The probably best way is to recompile the kernel for your board. While doing so you could embed your driver in the kernel as well. Regards