Potato board hacking

Forewords

Thanks to the nice guys from Baylibre, I got a Potato board from Libretech. As often with this kind of boards, there’s no battery powered RTC on it. This is kind of annoying so I tend to have a bunch of DS3231 module, which have a battery. They’re quite easy to find and can be used on every board with a raspberry-pi-like 40-pin header. Moreover, there’s a temperature sensor on it, even if it’s no really precise (+/- 3°C).

This post will be more or less an elaborate backup of my notes, which means that some basic knowledge of kernel, kernel build, devicetree and u-boot is needed. I will even skip some details, like how to patch, configure or build a kernel or u-boot. The different commands are done on a Debian sid and on the board serial port (connector 2J1) for commands run on the board. Due to cut&pasting, there may also be some spacing issue on the command output and file content.

First approach: Modifying the kernel device tree

According to the schematics found on Libretech documentation, the I2C bus available on the 1/3/5/7 pins of the 7J1 connector is the I2C connected on the AO bus (AO stands for Always On). So, all is needed is:

  1. Add an alias to the I2C bus, so that it becomes I2C bus 1
  2. Configure the pinctrl for the pins 3 and 5,
  3. Enable the I2C AO module,
  4. Add the DS3231 node.

This roughly translate into adding the following code to the kernel device-tree:

...
 model = "Libre Technology CC";

aliases {
 serial0 = &uart_AO;
 i2c1 = &i2c_AO;
};
 ....

&i2c_AO {
 status = "okay";
 pinctrl-0 = <&i2c_ao_pins>;
 pinctrl-names = "default";
 rtc@68 {
   compatible = "maxim,ds3231";
   reg = <0x68>;
 };
};

So, if the new dts is compiled, and the kernel booted, we can check everything is fine:

# ls /proc/device-tree/soc/aobus\@c8100000/i2c\@500/
 #address-cells #size-cells clocks compatible interrupts linux,phandle name phandle pinctrl-0 pinctrl-names reg rtc@68 status
# ls /sys/bus/i2c/devices/i2c-1/
 1-0068 delete_device device name new_device of_node power subsystem uevent
# readlink /sys/class/rtc/rtc0/device
 ../../../1-0068

Modifying on u-boot side

The first approach is simple but has some small problems like (the list is not complete and subjective) :

  • Having to patch the dts and then build the dtb every new kernel update (as long as there are changes in the device tree)
  • Deal with patch rejects

Given that our board is new enough to have a u-boot with device tree support, the new solution coming at hands is to modify the device tree from it. This means that all the changes done in the device tree must be written in terms of u-boot commands. Each line will be a new command. Looking at the changes done in the device tree, it seems easy. Unfortunately, there are some small challenges: &i2c_AO and <&i2c_ao_pins> are some kind of references.
The first one is a label, so all is needed is to replace it with the corresponding node path : /soc/aobus@c8100000/i2c@500.
The second is called a phandle (see the elinux wiki for instance) and when compiled, the device tree binaries are only keeping the ones used and thus, unfortunately for us, the <&i2c_ao_pins> phandle is removed. The quick and dirty solution I’m using is to add manually the new phandle. I’ll choose 0x100 as number because there are very few chances of collisions.

On the u-boot command line, this translate into (alias not handled here, not the main aim of the change):

libretech_cc# ext4load mmc 0:1 0x02f00000 /meson-gxl-s905x-libretech-cc.dtb
libretech_cc# fdt addr 0x02f00000 ; fdt resize
libretech_cc# fdt set /soc/aobus@c8100000/pinctrl@14/i2c_ao linux,phandle <0x000000100>
libretech_cc# fdt set /soc/aobus@c8100000/i2c@500 pinctrl-0 <0x00000100>
libretech_cc# fdt set /soc/aobus@c8100000/i2c@500 pinctrl-names "default"
libretech_cc# fdt set /soc/aobus@c8100000/i2c@500 status "ok"
libretech_cc# fdt mknode /soc/aobus@c8100000/i2c@500 rtc@68
libretech_cc# fdt set /soc/aobus@c8100000/i2c@500/rtc@68 compatible "maxim,ds3231"
libretech_cc# fdt set /soc/aobus@c8100000/i2c@500/rtc@68 reg <0x68>
libretech_cc# fdt set /soc/aobus@c8100000/i2c@500/rtc@68 status "ok"

Note: about the “fdt resize” command (excerpt from u-boot’s help):

fdt resize - Resize fdt to size + padding to 4k addr

This will allow us to gain some memory/room for our changes.

It is now possible to check that our changes have been applied:

libretech_cc# fdt print /soc/aobus@c8100000/pinctrl@14/i2c_ao
 i2c_ao {
   linux,phandle = <0x00000100>;
   mux {
     groups = "i2c_sck_ao", "i2c_sda_ao";
     function = "i2c_ao";
   };
 };
libretech_cc# fdt print /soc/aobus@c8100000/i2c@500
 i2c@500 {
   pinctrl-names = "default";
   pinctrl-0 = <0x00000100>;
   compatible = "amlogic,meson-gx-i2c", "amlogic,meson-gxbb-i2c";
   reg = <0x00000000 0x00000500 0x00000000 0x00000020>;
   interrupts = <0x00000000 0x000000c3 0x00000001>;
   #address-cells = <0x00000001>;
   #size-cells = <0x00000000>;
   status = "ok";
   clocks = <0x0000000c 0x0000005d>;
   rtc@68 {
     status = "ok";
     reg = <0x00000068>;
     compatible = "maxim,ds3231";
   };
 };

Device-tree overlays

As we’ve seen using u-boot to modify the device tree works, but may be quite painful. With all theses boards with rpi-like extension connectors, a different solution has to be found. This proposed solution is to create some kind of patch format dedicated to device trees. It’s called device-tree overlays. To use the patch analogy:

  • patch hunks are called fragments
  • patch context (lines number, …) are device tree node names or path
  • changes done are the properties we want to change

The full format is describe here, for instance: linux kernel documentation

Our device tree change can be written into device-tree overlay with :

$ nano meson-gxl-s905x-libretech-cc-ds3231.dts
 /*
  * Copyright (c) 2017 Hupstream
  * Author: Arnaud Patard <apatard@hupstream.com>
  *
  * copy/paste off files from:
  * Copyright (c) 2017 BayLibre, SAS.
  * Author: Neil Armstrong <narmstrong@baylibre.com>
  *
  * SPDX-License-Identifier: (GPL-2.0+ OR MIT)
  */

/*
 * Overlay aimed to enable I2C_AO on Header 7J1 :
 * Pins 3 (SDA), 5 (SCL)
 */

/dts-v1/;
/plugin/;

/ {
  compatible = "libretech,cc", "amlogic,s905x", "amlogic,meson-gxl";
  fragment@0 {
    target-path = "/aliases";

    __overlay__ {
      i2c1 = "/soc/aobus@c8100000/i2c@500";
    };
  };

  fragment@1 {
    target-path = "/soc/aobus@c8100000/i2c@500";

    __overlay__ {
      status = "okay";
      pinctrl-0 = <&i2c_ao_pins>;
      pinctrl-names = "default";
      rtc@68 {
        compatible = "maxim,ds3231";
        reg = <0x68>;
      };
    };
  };
};

As it can be seen, it’s a little bit easier to write than u-boot commands. Now, to be useful, this device tree overlay must be compiled into an object file, with dtbo extension. Of couse, we’ll be using the dtc tool. Unfortunately, the overlay support comes with version 1.4.3 upstream, which means that on Debian, one needs at least package version 1.4.4-1.

$ dtc -@ -I dts -O dtb -o meson-gxl-s905x-libretech-cc-ds3231.dtbo meson-gxl-s905x-libretech-cc-ds3231.dts

The next step is, evidently, to apply the change. The device-tree compiler comes with a tool called fdtoverlay, which we’ll use to apply on our device-tree binary file. See, no need to patch the dts file.

$ fdtoverlay -i meson-gxl-s905x-libretech-cc.dtb -o meson-gxl-s905x-libretech-cc-mod.dtb meson-gxl-s905x-libretech-cc-ds3231.dtbo

Failed to apply meson-gxl-s905x-libretech-cc-ds3231.dtbo (-1)

Oops. It can be a problem with our overlay but given it’s nearly a cut&paste of working device-tree change, it’s something else. Let’s read the kernel document entirely and see at the very end:

Using the non-phandle based target method allows one to use a base DT which does
not contain a __symbols__ node, i.e. it was not compiled with the -@ option.
The __symbols__ node is only required for the target=<phandle> method, since it
contains the information required to map from a phandle to a tree location.

So the dtb file must be generated with the “-@” option so that our phandle can be resolved. When building it from the kernel source, it’s possible to ask the kernel build system to use the flag:

 $ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- DTC_FLAGS_meson-gxl-s905x-libretech-cc='-@' dtbs
 $ fdtoverlay -i arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dtb -o meson-gxl-s905x-libretech-cc-mod.dtb meson-gxl-s905x-libretech-cc-ds3231.dtbo

Let’s check it really did its job:

$ dtdiff arch/arm64/boot/dts/amlogic/meson-gxl-s905x-libretech-cc.dtb meson-gxl-s905x-libretech-cc-mod.dtb
 --- /dev/fd/63 2017-11-16 12:06:17.036535453 +0100
 +++ /dev/fd/62 2017-11-16 12:06:17.040535538 +0100
 @@ -145,6 +145,7 @@

aliases {
 ethernet0 = "/soc/ethernet@c9410000";
+ i2c1 = "/soc/aobus@c8100000/i2c@500";
 serial0 = "/soc/aobus@c8100000/serial@4c0";
 };

@@ -447,8 +448,15 @@
 interrupts = <0x0 0xc3 0x1>;
 linux,phandle = <0x47>;
 phandle = <0x47>;
+ pinctrl-0 = <0x4e>;
+ pinctrl-names = "default";
 reg = <0x0 0x500 0x0 0x20>;
- status = "disabled";
+ status = "okay";
+
+ rtc@68 {
+   compatible = "maxim,ds3231";
+   reg = <0x68>;
+ };
 };

ir@580 {

There has been some discussions to ease this step and set the flag for all the dtbs on the LKML but it’s not in -next and I’ve not checked recently how far this discussion went. One of the cons of adding the flag by default is the increase of the size of the dtb.

The way to automate this is left to the reader.

Applying the overlay from the kernel, at runtime

When looking at the online documentation and scripts about overlays, they’re applying the overlay at runtime throught the configfs pseudo filesystem but what’s not mentionned is that it’s not possible with vanilla kernel. It requires a patch for this. Given it’s not mailine, there are many version of the patch. One possible version is available here.  Once applied, the CONFIG_OF_CONFIGFS option must be enabled.
With this kernel, all we now need is to tell the kernel to apply our overlay:

# cd /sys/kernel/config/device-tree/overlays/
# mkdir rtc && cd rtc
# cp /path/to/meson-gxl-s905x-libretech-cc-ds3231.dtbo /lib/firmware/
# echo meson-gxl-s905x-libretech-cc-ds3231.dtbo > path
# dmesg|tail -n2
 [ 579.954336] (NULL device *): firmware: direct-loading firmware meson-gxl-s905x-libretech-cc-ds3231.dtbo
 [ 579.971667] rtc-ds1307 1-0068: registered as rtc0

(nice “(NULL device *)” message, btw)

To unapply the overlay, just do:

# rmdir /sys/kernel/config/device-tree/overlays/rtc/

Let’s note that it works fine as we’re adding a new device, but depending on the change, some extra work may be needed. For instance, here, I’ll change the default trigger of the blue led:

$ nano meson-gxl-s905x-libretech-cc-led.dts
 /*
  * Copyright (c) 2017 Hupstream
  * Author: Arnaud Patard <apatard@hupstream.com>
  *
  * SPDX-License-Identifier: (GPL-2.0+ OR MIT)
  */

/dts-v1/;
/plugin/;

/ {
  compatible = "libretech,cc", "amlogic,s905x", "amlogic,meson-gxl";

  fragment@0 {
    target-path = "/leds/blue";

    __overlay__ {
      linux,default-trigger = "default-on";
    };
 };
};
$ dtc -@ -I dts -O dtb -o meson-gxl-s905x-libretech-cc-led.dtbo meson-gxl-s905x-libretech-cc-led.dts

My original change was to switch to the nice activity led trigger, but it’s only available on recent kernels

On the potato serial console:

# modprobe ledtrig-heartbeat
# modprobe ledtrig-default-on
# cd /sys/kernel/config/device-tree/overlays/
# mkdir leds && cd leds
# echo meson-gxl-s905x-libretech-cc-led.dtbo > path
 [ 169.785569] (NULL device *): firmware: direct-loading firmware meson-gxl-s905x-libretech-cc-led.dtbo

Oh, the trigger has not been changed ! The explanation is that the property is read at module load time only. To make it work, just do:

# rmmod leds-gpio && modprobe leds-gpio

Again, some automation still needs to be written here. A beginning may be the tool from Baylibre.
The problem remaining is that we may need to apply the change before booting the kernel (and of course that the kernel needs to be patched), for instance, when one wants to apply the mainline kernel commit “[PATCH] ARM64: dts: meson-gxl: Add alternate ARM Trusted Firmware reserved memory zone” (4ee8e51b9edfe7845a094690a365c844e5a35b4b).

Applying the overlay from u-boot

To try to address all problems mentionned, the last approach would be to merge all previous methods, aka apply overlays from u-boot. Mainline u-boot has support for it but unfortunately, the u-boot code I found here did not. (note: since I work on this, mainline u-boot gained support for this board) After a short (but dirty) backport session, I’ve added overlay support. While I was at it, I’ve written some hackish changes to build with the version of Debian’s aarch64 cross compile I had on my system (Debian 6.3.0-18).
The patchset is available here.

Be warned that if you use it, while it works for me, I decline any responsability for troubles you’ll get when using this u-boot. USE IT AT YOUR OWN RISKS

Once on u-boot prompt, applying the overlay is really easy:

libretech_cc# ext4load mmc 0:1 0x02f00000 /meson-gxl-s905x-libretech-cc.dtb
libretech_cc# ext4load mmc 0:1 0x02fc0000 /meson-gxl-s905x-libretech-cc-ds3231.dtbo
libretech_cc# fdt addr 0x02f00000 ; fdt resize
libretech_cc# fdt apply 0x02fc0000

Again, check that it applied correctly:

libretech_cc# fdt print /soc/aobus@c8100000/i2c@500
 i2c@500 {
   pinctrl-names = "default";
   pinctrl-0 = <0x0000004c>;
   compatible = "amlogic,meson-gx-i2c", "amlogic,meson-gxbb-i2c";
   reg = <0x00000000 0x00000500 0x00000000 0x00000020>;
   interrupts = <0x00000000 0x000000c3 0x00000001>;
   #address-cells = <0x00000001>;
   #size-cells = <0x00000000>;
   status = "okay";
   clocks = <0x0000000c 0x0000005d>;
   linux,phandle = <0x00000045>;
   phandle = <0x00000045>;
   rtc@68 {
     reg = <0x00000068>;
     compatible = "maxim,ds3231";
   };
 };

This can of course be automated by adding the commands to a uboot script or by modifying the bootcmd but that’s all what’s needed. No shell script to write/use (and possibly no init script or udev rule to write). When available, it’s the solution I use, otherwise I’m modifying the device tree by hand from u-boot.

Extra note

I got some troubles with ethernet. It was stalling from time to time. A fix for this has been found, but it’s not mainline so I’m mentionning it, in case it can be usefull for someone : https://patchwork.kernel.org/patch/10092657/