Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I add I²C devices on the BeagleBone Black using device tree overlays?

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 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.

First things first

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...

Arch Linux screenfetch on BBB

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, 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.

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...

Enter image description here

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 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..

i2c1 node in am33xx.dtsi

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 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 */

OMG, what just happened?

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..

BBB GPIO settings

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.

But do 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 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.

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...

Enter image description here

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...

Enter image description here

Enter image description here

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..

Enter image description here

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.

like image 796
IlikePepsi Avatar asked Sep 05 '25 03:09

IlikePepsi


1 Answers

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:

Image

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

like image 59
Bwani Avatar answered Sep 07 '25 22:09

Bwani