NanoPi Neo USB OTG with Armbian mainline 4.x kernel

The long awaited quest of the USB On-the-Go with mainline Linux kernel 4.x.

Introduction

As already documented here before, it is possible to configure FriendlyARM's NanoPi Neo to behave as an USB Ethernet/RNDIS gadget device. Unfortunately this functionality relied on legacy/hacked sunxi kernel. This kernel (3.x) is very old and not updated anymore in the sunxi legacy branch.

Is it possible to achieve the functionality with a mainline kernel?

1 - Naive attempt

Download the mainline kernel image for SD-card [1] and write it:

sudo dd of=/dev/mmcblk0 if=Armbian_5.27.170603_Nanopineo_Ubuntu_xenial_dev_4.11.3.img bs=4M status=progress

The kernel is 4.11.3-sun8i:

root@nanopineo:~# uname -a
Linux nanopineo 4.11.3-sun8i #21 SMP Fri Jun 2 03:02:22 CEST 2017 armv7l armv7l armv7l GNU/Linux

Note: this image is built every night.

g_serial (Serial gadget) is loaded by default in /etc/modules. Remove it and load g_ether (Ethernet gadget):

rmmod g_serial
modprobe g_ether

Check dmesg for kernel logs:

udc-core: couldn't find an available UDC - added [g_ether] to list of pending drivers

This error comes from the function usb_gadget_probe_driver in the file drivers/usb/gadget/udc/core.c [2]. g_ether cannot find an available UDC. According to [3], an UDC is a "USB Device Controller" (image source [4]).

2 - musb activation

Quote from [5]:

Allwinner H3 have a its USB PHY0 routed to two USB controllers: one is
a MUSB controller, which can work in peripheral mode, but works badly in
host mode (several hardware will fail on the MUSB controller, even connect
one MUSB controller in peripheral mode to another one in host mode cannot
work); the other is a pair of EHCI/OHCI controller, which can work only
in host mode, but have better compatibillity. The route is controlled in
a register [...]

The Allwinner H3 chip includes the silicon IP "Mentor Graphics Inventra HDRC" [6]. This USB controller is a "Dual-Role Controller", which means that it can work as host-only or OTG. The driver is called musb in the Linux kernel. musb's sunxi glue is patched by Icenowy Zheng for H3 support in this commit [7] [8]. Note: "https://github.com/megous/linux" is the kernel source used for Armbian kernel build, see [9].

musb is build in the kernel with the settings described at [10]. The configuration of the Armbian kernel for NanoPi NEO is at [11]. All the options are set correctly except for CONFIG_MUSB_PIO_ONLY, which is "is not set" instead of "y".

Using dmesg | grep "musb", it can be noticed that musb is not enabled by default in the Armbian image for NanoPi NEO. Its activation depends the kernel overlay settings [12] [13].

The overlay settings can be changed in a running image using:

# Convert binary to text
dtc -I dtb /boot/dtb/sun8i-h3-nanopi-neo.dtb -O dts -o /boot/dtb/sun8i-h3-nanopi-neo.dts

[...edit .dts file here...]

# Convert text to binary
dtc -I dts /boot/dtb/sun8i-h3-nanopi-neo.dts -O dtb -o /boot/dtb/sun8i-h3-nanopi-neo.dtb

At the section usb@01c19000, set status to "okay" to enable musb, and add the dr_mode option with "otg":

usb@01c19000 {
        compatible = "allwinner,sun8i-h3-musb";
        reg = <0x1c19000 0x400>;
        clocks = <0x2 0x20>;
        resets = <0x2 0x11>;
        interrupts = <0x0 0x47 0x4>;
        interrupt-names = "mc";
        phys = <0x12 0x0>;
        phy-names = "usb";
        extcon = <0x12 0x0>;
        status = "okay";            # changed
        linux,phandle = <0x39>;
        phandle = <0x39>;
        dr_mode = "otg";            # added
};

After rebooting the target, musb is now loaded. Check with dmesg:

[    4.195197] usb_phy_generic usb_phy_generic.0.auto: usb_phy_generic.0.auto supply vcc not found, using dummy regulator
[    4.195811] musb-hdrc musb-hdrc.1.auto: MUSB HDRC host driver
[    4.195831] musb-hdrc musb-hdrc.1.auto: new USB bus registered, assigned bus number 3
[    4.196291] usb usb3: New USB device found, idVendor=1d6b, idProduct=0002
[    4.196307] usb usb3: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    4.196318] usb usb3: Product: MUSB HDRC host driver
[    4.196329] usb usb3: Manufacturer: Linux 4.11.3-sun8i musb-hcd
[    4.196339] usb usb3: SerialNumber: musb-hdrc.1.auto

Load g_ether and dmesg shows that a MAC address was assigned to the new usb0 interface:

root@nanopineo:~# dmesg | grep usb0
[    8.324626] usb0: HOST MAC f2:47:f7:66:a3:76
[    8.324744] usb0: MAC 4a:41:e7:b3:ce:4c

root@nanopineo:~# ifconfig usb0
usb0      Link encap:Ethernet  HWaddr 4a:41:e7:b3:ce:4c
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

3 - All nice and working then?

Not yet. When trying to list USB devices from a host connected to the NanoPi NEO, the lsusb hangs:

open("/sys/bus/usb/devices/usb2/busnum", O_RDONLY) = 6
fstat(6, {st_mode=S_IFREG|0444, st_size=4096, ...}) = 0
read(6, "2\n", 4096)                    = 2
close(6)                                = 0
open("/sys/bus/usb/devices/usb2/devnum", O_RDONLY) = 6
fstat(6, {st_mode=S_IFREG|0444, st_size=4096, ...}) = 0
read(6, "1\n", 4096)                    = 2
close(6)                                = 0
open("/sys/bus/usb/devices/usb2/speed", O_RDONLY) = 6
fstat(6, {st_mode=S_IFREG|0444, st_size=4096, ...}) = 0
read(6, "480\n", 4096)                  = 4
close(6)                                = 0
open("/sys/bus/usb/devices/usb2/descriptors", O_RDONLY) = 6
read(6,

Waiting forever to open /sys/bus/usb/devices/usb2/descriptors (where the NanoPi NEO is acting as Ethernet/RNDIS gadget).

On the NanoPi NEO, the new USB driver seems to be correctly activated:

root@nanopineo:~# lsusb -t
/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=musb-hdrc/1p, 480M
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M

4 - Are the pins correctly configured?

Does GPIOG12/USB0-OTGI really arrives to musb? (image source [14])

USB0-OTGI is pin PD1 and labeld PG12 in the chip.

In his/her OTG patch, Icenowy Zheng updates the DTS files for the Orange Pi Zero and One boards. The following change looks interesting [15]:

&usb_otg {
        dr_mode = "otg";
        status = "okay";
};

&usbphy {
        /* USB Type-A port's VBUS is always on */
        usb0_id_det-gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; /* PG12 */
        usb0_vbus-supply = <&reg_usb0_vbus>;
        status = "okay";
};

The DTS and DTB files are device-tree [16] source and binary files. The source is converted into a binary with the dtc tool [17].

By "decompiling" the corresponding DTB file on the target, it is visibly not the same configuration:

dtc -I dtb /boot/dtb/sun8i-h3-orangepi-one.dtb -O dts -o /boot/dtb/sun8i-h3-orangepi-one.dts

Please note that there is no need to add both usb0_id_det-gpios and usb0_vbus-supply. usb0_vbus-supply is not needed because the NanoPi-Neo is different from the OrangePi-One [18]. The NanoPi-Neo cannot supply power to a USB device because it has no external power supply. The power supply comes from the USB itself.

usb@01c19000 {
        compatible = "allwinner,sun8i-h3-musb";
        reg = <0x1c19000 0x400>;
        clocks = <0x2 0x20>;
        resets = <0x2 0x11>;
        interrupts = <0x0 0x47 0x4>;
        interrupt-names = "mc";
        phys = <0xf 0x0>;
        phy-names = "usb";
        extcon = <0xf 0x0>;
        status = "okay";
        dr_mode = "otg";
        linux,phandle = <0x39>;
        phandle = <0x39>;
};

phy@01c19400 {
        compatible = "allwinner,sun8i-h3-usb-phy";
        reg = <0x1c19400 0x2c 0x1c1a800 0x4 0x1c1b800 0x4 0x1c1c800 0x4 0x1c1d800 0x4>;
        reg-names = "phy_ctrl", "pmu0", "pmu1", "pmu2", "pmu3";
        clocks = <0x2 0x58 0x2 0x59 0x2 0x5a 0x2 0x5b>;
        clock-names = "usb0_phy", "usb1_phy", "usb2_phy", "usb3_phy";
        resets = <0x2 0x0 0x2 0x1 0x2 0x2 0x2 0x3>;
        reset-names = "usb0_reset", "usb1_reset", "usb2_reset", "usb3_reset";
        status = "okay";
        #phy-cells = <0x1>;
        usb0_id_det-gpios = <0xe 0x6 0xc 0x0>;
        #usb0_vbus-supply = <0x10>;
        linux,phandle = <0xf>;
        phandle = <0xf>;
};

Note: documentation about those options are available at [19] (usb-phy) and at [20] (musb). About extcon, see [21].

Warning: ff usb-phy fails, and there will not be any USB support at all anymore:

sun4i-usb-phy: probe of 1c19400.phy failed with error -22

Original device-tree source

The original DTS files are found alone with the kernel sources:

wget https://github.com/megous/linux/archive/orange-pi-4.11.zip
cd linux-orange-pi-4.11/arch/arm/boot/dts
dtc -I dts sun8i-h3-nanopi-neo.dts -O dtb -o /boot/dtb/sun8i-h3-nanopi-neo.dtb
Error: sun8i-h3-nanopi-neo.dts:43.1-2 syntax error
FATAL ERROR: Unable to parse input tree

But they cannot be converted into DTB right away because they use an include hierarchy ([22]):

DTS files do not strictly follow the Device-Tree syntax as they include c/c++ pre-processors.
The file(s) must first be "scrubbed", then run through the dtc compiler.

Example:

cpp -nostdinc -I include -undef -x assembler-with-cpp sun8i-h3-nanopi-neo.dts > /tmp/tmp.dts
dtc -I dts /tmp/tmp.dts -O dtb -o /boot/dtb/sun8i-h3-nanopi-neo.dtb

References

[1]Armbian project, "NanoPi NEO", https://www.armbian.com/nanopi-neo/
[2]Sunxi, "USB OTG Controller Register Guide", http://linux-sunxi.org/USB_OTG_Controller_Register_Guide
[3]Linux kernel 4.11.3, "drivers/usb/gadget/udc/core.c", https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/tree/drivers/usb/gadget/udc/core.c?h=v4.11.3
[4]Matt Porter, "Kernel USB Gadget Configfs Interface", http://events.linuxfoundation.org/sites/events/files/slides/USB%20Gadget%20Configfs%20API_0.pdf
[5]Sunxi mailing-list, "[PATCH v4 0/8] Add dual-role OTG support for Allwinner H3", https://groups.google.com/forum/m/#!topic/linux-sunxi/CgyTjm5BD-I
[6]Mentor, "USB 2.0 IP, Multi-Point Hi-Speed OTG Controller", http://s3.mentor.com/public_documents/datasheet/products/ip/usb/usb20otg/MUSBMHDRC__Datasheet.pdf
[7]https://github.com/megous/linux/commit/c1fce66ecd271dee5379f419a69b8ff5dae49ba1
[8]"Allwinner sun4i MUSB Glue Layer", https://github.com/megous/linux/blob/orange-pi-4.11/drivers/usb/musb/sunxi.c
[9]https://github.com/armbian/build/commit/744192b750015a5e045afe136b0432f019c26d68
[10]Sunxi, "USB Gadget/Ethernet", http://linux-sunxi.org/USB_Gadget/Ethernet
[11]https://github.com/megous/linux/blob/orange-pi-4.11/arch/arm/boot/dts/sun8i-h3-nanopi-neo.dts
[12]https://github.com/megous/linux/blob/orange-pi-4.11/arch/arm/boot/dts/sunxi-h3-h5.dtsi
[13]Armbian, "sun8i-dev kernel config", https://github.com/armbian/build/blob/master/config/kernel/linux-sun8i-dev.config
[14]NanoPi NEO schematics, http://wiki.friendlyarm.com/wiki/images/a/aa/NanoPi-NEO-1606-Schematic.pdf
[15]https://github.com/megous/linux/commit/b97b8104c1f61071c6b7c4d6fe7a2eb06517e956
[16]Massimo Bonazzi, "Device Tree Made Easy", https://community.nxp.com/servlet/JiveServlet/downloadBody/104818-102-5-24855/EUF-DES-T1465.pdf
[17]Sunxi, "Compiling the Device-Tree", https://linux-sunxi.org/Device_Tree#Compiling_the_Device_Tree
[18]OrangePi One schematics, http://linux-sunxi.org/images/7/7e/ORANGE_PI-ONE-V1_1.pdf
[19]Linux kernel, https://www.kernel.org/doc/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt
[20]Linux kernel, https://www.kernel.org/doc/Documentation/devicetree/bindings/usb/allwinner%2Csun4i-a10-musb.txt
[21]Linux kernel, https://patchwork.kernel.org/patch/9640485/
[22]https://community.nxp.com/thread/394569