Olimex Olinuxino Lime 2 CAN connection under Debian 🔗

Posted by Médéric Ribreux 🗓 In blog/Debian/

#electronic #debian #sysadmin

Introduction

I wanted to use a CAN bus tool to make diagnostics on my car. Nowadays, thanks to the OBD2 standard, you can have an access to a CAN port via the OBD2 port. I bought a (fake) chinese version of an ELM327, which is a generic and multi-protocol OBD2 connector that you can pilot from a serial connection (via USB). Unfortunately, this fake version was unable to send and receive any real CAN request on the bus other than the standard one.

I suspected that this connector wasn't able to achieve real CAN requests because the producer just handled the basic OBD2 queries or base address and I wanted to verify it. I investigated to find a certified CAN hardware that I could use under one of my computer. After reading stuff into the Car Hacker's handbook, I found that you can have a CAN connection directly referenced under GNU/Linux, not relying on other software than the Linux kernel itself!

By searching after a CAN controller device usable under GNU/Linux, I found that a computer board I previously purchased (to build my personal internet website) was using a SOC with an embedded CAN controller: the Olimex Olinuxino Lime 2. All I had to buy was a CAN transceiver (which I did) and find a way to enable the CAN connection under the system. As all my computers use GNU/Linux Debian, I had to investigate why there were no CAN interface.

You can find how I achieved this by reading this article.

About Olimex

Olimex is a bulgarian Open hardware electronic manufacturer that I recommend for the following reasons:

Whenever I have a need for electronic stuff, I always start by exploring Olimex products and I recommend you do the same. Those guys are clearly on the light side of the force.

You need a CAN transceiver

Allwinner A20 has an embedded CAN controller which is directly integrated into the SOC. A CAN controller is like an ethernet controller but for CAN protocol. But you need a CAN transceiver to deal with voltages and current levels: you can't directly put a CAN connection directly into the controller (which is embeded in the SOC). A CAN transceiver is an interface between a CAN protocol controller and the physical bus.

But Olimex provides this with a "shield": the A20 CAN shield.

I also provide a photo of the shield connected to the board (because it is not so obvious):

Connecting Olinuxino to A20 CAN shield

You need to use the GPIO_3 connector because this is were the CAN_RX and CAN_TX pins are located (PH20 and PH21). You can check the reference do understand why.

What should be done?

I've digged the dirty stuff for you and here is what I have learned so far to activate the CAN controller on the board.

I have checked things into the Linux kernel source code packaged under Debian (for stable bullseye distribution) and found that:

Olimex got a set of instructions on its wiki, that I have sumarized in two points:

can0_pins_a: can0@0 {
       allwinner,pins = "PH20","PH21";
       allwinner,function = "can";
       allwinner,drive = <0>;
       allwinner,pull = <0>;
   };
...
can0: can@01c2bc00 {
       compatible = "allwinner,sun4i-a10-can";
       reg = <0x01c2bc00 0x400>;
       interrupts = <0 26 4>;
       clocks = <&apb1_gates 4>;
       status = "disabled";
   };
&can0 {
       pinctrl-names = "default";
       pinctrl-0 = <&can0_pins_a>;
       status = "okay";
   };

DTB? What's that?

Ok, time to deal with DTB on Linux Kernel. I am no specialist, but after listening to this video and reading documentation, here what I have understood so far:

Build a new DTB

Introduction

So, all we need to do is to modify the Device Tree Source for the Olinuxino Lime2 board under the kernel source code to activate the CAN controller. You can do this without too much thinking with modifying the Device Tree source and rebuilding the kernel. But on an arm machine like the Olinuxino, it would take about 2 or 3 hours and a lot of space.

Instead of this bulk method, you can choose a lighter way by only producing a modified Device Tree Blob (DTB) and using it on boot. That's what I describe from now.

Prerequisites

First we need to install the Linux kernel source code which has been used by Debian to compile the kernel which is installed in the actual machine. I do this because I will be sure that there will be no suprisesat all: this is the same kernel (with the exact revision and configuration) than the one which is running on the machine. Then, we need to install some Debian packages in order to be able to generate the blob:

apt install linux-source libncurses-dev rsync build-essential libssl-dev

Once installed, you can find a compressed source tree into /usr/src/linux-source-5.10.tar.xz that you can decompress:

$ mkdir ~/kernel; cd ~/kernel
$ tar -xaf /usr/src/linux-source-5.10.tar.xz

You can read all the DTS files directly into ~/kernel/linux-source-5.10/arch/arm/boot/dts. Once the source code is extracted, all we need to do is to modify the DTS used for our board. But hell, what is the stuff I need to modify?

Understanding what we are doing

You can easily find the name of the file used as a DTB by U-boot by looking into the boot directory at the link /boot/dtb. On my system it points to dtbs/5.10.0-12-armmp-lpae/./sun7i-a20-olinuxino-lime2-emmc.dtb and you can deduce from this that the main source for the DTS used on this machine is a file named like sun7i-a20-olinuxino-lime2-emmc. Bingo, there is ~/kernel/linux-source-5.10/arch/arm/boot/dts/sun7i-a20-olinuxino-lime2-emmc.dts.

I am not displaying the content of the source file because all this file does is reference another dts file (with C #include pre-processing instruction): sun7i-a20-olinuxino-lime2.dts and declare an additional internal emmc interface to this board sun7i-a20-olinuxino-lime2.dts

So we have to inspect this file and compare its content against the instructions provided by Olimex.

If you search in sun7i-a20-olinuxino-lime2.dts, you will not find any mention of the following text:

&can0 {
       pinctrl-names = "default";
       pinctrl-0 = <&can_ph_pins>;
       status = "okay";
};

All you have to to is to add those lines at the end of the file. This DTS part means that we are going to:

Ok, so far, adding this to the DTS will enable the interface. But what about &can0 and &can_ph_pin properties? In the DTS language, they are named address cells. So if we want to fully understand, what we are doing, we need to find what is declared at those adresses.

In sun7i-a20-olinuxino-lime2.dts, sun7i-a20.dtsi DTS include file is included by default. sun7i-a20-olinuxino-lime2.dts is declaring specific stuff for the Olimex olinuxino lime2. But there are other boards based on A20 (named sun7i-a20 architecture). If you grep can0 in sun7i-a20.dtsi, you will find the declaration of &can0 as:

can0: can@1c2bc00 {
        compatible = "allwinner,sun7i-a20-can",
                     "allwinner,sun4i-a10-can";
        reg = <0x01c2bc00 0x400>;
        interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&ccu CLK_APB1_CAN>;
        status = "disabled";
};

Here you can read that can0 is:

If you grep can_ph_pins in sun7i-a20.dtsi, you will find the declaration of can_ph_pins as:

can_ph_pins: can-ph-pins {
        pins = "PH20", "PH21";
        function = "can";
};

It simply declares two pins for the can function: PH20 and PH21. And if you remember about A20 CAN shield before, PH20 and PH21 are referenced in the Olinuxino GPIO reference as CAN_RX and CAN_TX pins are located (PH20 and PH21). The DTS declaration matches the hardware stuff, everything seems to be all right.

To sum it up, a lot of stuff is already well declared in the Kernel DTS:

So, all we have to do is to add the following text at the end of sun7i-a20-olinuxino-lime2.dts:

&can0 {
       pinctrl-names = "default";
       pinctrl-0 = <&can_ph_pins>;
       status = "okay";
};

Compile and install the DTB

Once the file is modified, we can produce the blob. It will take two instructions:

cpp -nostdinc -I include -undef -x assembler-with-cpp arch/arm/boot/dts/sun7i-a20-olinuxino-lime2-emmc.dts > test.dts
dtc -I dts -O dtb -o ./test.dtb test.dts

Once done, ./test.dtb is our Device Tree Blob and it is nearly ready to be installed and deployed. We are going to use flash-kernel because this is the official tool to deploy kernels and DTBs to the bootloader (U-Boot). All dtb files (for all supported systems) are stored into /usr/lib/linux-image-5.xxxxx/ directory (/usr/lib/linux-image-5.10.0-12-armmp-lpae in our case with version 5.10 under Debian). Those files are used by flash-kernel and copied into /boot directory.

All we have to do is to make a backup of the original DTB for our board, overwrite it with the new version and update bootloader configuration with flash-kernel:

# cp /usr/lib/linux-image-5.10.0-12-armmp-lpae/sun7i-a20-olinuxino-lime2-emmc.dtb /home/user/kernel/sun7i-a20-olinuxino-lime2-emmc.dtb
# cp /home/user/kernel/linux-source-5.10/test.dtb /usr/lib/linux-image-5.10.0-12-armmp-lpae/sun7i-a20-olinuxino-lime2-emmc.dtb
# flash-kernel

And reboot!

After that, you should have a can0 connection referenced by ip a:

3: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN group default qlen 10
    link/can 

Congratulations, job's done!

Conclusions

I don't know why the Debian Kernel dts doesn't add the CAN controller by default. I suspect this is because whenever there is no CAN transceiver, it is dangerous to enable it directly in case a wrong connection is made on the wrong GPIO.

Now you have some basic information about DTB, DTS and file organisation. I could have elaborated a bit more about DTB but other people (and the Linux Kernel documentation) are doing better than I can ever do. My goal was simply to enable a CAN controller on an A20 SOC board. Next step, connect the Olinuxino to the car, but this another story!