We recently had the chance to work on a customer project that involved the RaspberryPi Compute Module 3, with custom peripherals attached: a Microchip WILC1000 WiFi chip connected on SDIO, and a SGTL5000 audio codec connected over I2S/I2C. We take this opportunity to share some insights on how to introduce new hardware support on RaspberryPi platforms, by taking advantage of the Raspberry Pi specific Device Tree overlay mechanism.
Building and loading an upstream RPi Linux
Our customer is using the RaspberryPi OS Lite, which is a Debian-based system, coming with its pre-compiled Linux kernel image. However, for this project, we obviously need to customize the Linux kernel configuration, so we had to build our own kernel.
While Bootlin normally has a preference for using the upstream Linux kernel, for this project, we decided to keep using the Linux kernel fork provided by Raspberry Pi on Github, which is used by Raspberry Pi OS Lite. So we start our work by grabbing the Linux kernel source code from a the rpi-5.4.y
provided by the Raspberry Pi Linux kernel repository:
$ git clone https://github.com/raspberrypi/linux -b rpi-5.4.y
After installing an appropriate ARM 32-bit cross-compilation toolchain, we’re ready to configure and build the kernel:
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
Thanks to the USB mass storage mode of the Raspberry Pi it is quite straightforward to update the kernel as it allows to mount the board eMMC partitions on the host system, and update whichever files we want. The tool that actually sets the RaspberryPi in this mode is called rpiboot.
After executing it, we deploy the zImage into the RaspberryPi /media/${USER}/boot/
partition and update the file /media/${USER}/boot/config.txt
to set the firmware directive kernel=zImage
.
That’s all it takes to replace the kernel image provided by the RaspberryPi OS Lite system by our own kernel, which means we are now ready to integrate our new features.
Device Tree Overlays
A specificity of the Raspberry Pi is that the boot flow starts from the GPU core and not the ARM core like it does on most embedded processors. The GPU load a first bootloader from a ROM that will load a second bootloader (bootcode.bin
) from eMMC/SD card that is in charge of executing a firmware (start.elf
). This GPU firmware finally reads and parses a file stored in the boot partition (config.txt
), which is used to set various boot parameters such as the image to boot on.
This config.txt
file also allows to indicate which Device Tree file should be used as the hardware description, as well as Device Tree Overlays that should be applied on top of the Device Tree files. Device Tree Overlays are a bit like patches for the Device Tree: they allow to extend the base Device Tree with new properties and nodes. They are typically used to describe the hardware attached to the RaspberryPi through expansion boards.
The Raspberry Pi kernel tree contains a number of such Device Tree Overlays in the arch/arm/boot/dts/overlays folder. Each of those overlays, stored in .dts
file gets compiled into a .dtbo
files. Those .dtbo
can be loaded and applied to the main Device Tree by adding the following statement to the config.txt
file:
dtoverlay=overlay-name,overlay-arguments
We will fully take advantage of this mechanism to introduce two new Device Tree Overlays that will be parsed and dynamically merged into the main Device Tree.
WILC1000 WiFi chip Device Tree overlay
The ATWILC1000 we want to support is a Microchip WiFi module. As of the Linux 5.4 kernel we are using for this project, the driver for this WiFi chip was still in the staging area of the Linux kernel, in drivers/staging/wilc1000
, but it more recently graduated out of staging. We obviously started by enabling this driver in the kernel configuration, through its CONFIG_WILC1000
and CONFIG_WILC1000_SDIO
options.
To describe the WILC1000 module in terms of Device Tree, we need to create a arch/arm/boot/dts/overlays/wilc1000-overlays.dts
file, with the following contents:
/dts-v1/; /plugin/; / { compatible = "brcm,bcm2835"; fragment@0 { target = <&mmc>; wifi_ovl: __overlay__ { pinctrl-0 = <&sdio_ovl_pins &wilc_sdio_pins>; pinctrl-names = "default"; non-removable; bus-width = <4>; status = "okay"; #address-cells = <1>; #size-cells = <0>; wilc_sdio: wilc_sdio@0 { compatible = "microchip,wilc1000", "microchip,wilc3000"; irq-gpios = <&gpio 1 0>; status = "okay"; reg = <0>; bus-width = <4>; }; }; }; fragment@1 { target = <&gpio>; __overlay__ { sdio_ovl_pins: sdio_ovl_pins { brcm,pins = <22 23 24 25 26 27>; brcm,function = <7>; /* ALT3 = SD1 */ brcm,pull = <0 2 2 2 2 2>; }; wilc_sdio_pins: wilc_sdio_pins { brcm,pins = <1>; /* CHIP-IRQ */ brcm,function = <0>; brcm,pull = <2>; }; }; }; };
To be compiled, this overlay needs to be referenced in arch/arm/boot/dts/overlays/Makefile.
This overlay contains two fragments: fragment@0
and fragment@1
. Each fragment will extend a Device Tree node of the main Device Tree, which is specified by the target
property of each fragment: fragment@0
will extend the node referenced by the &mmc
phandle, and fragment@1
will extend the node referenced by the &gpio
phandle. The mmc
and gpio
labels are defined in the main Device Tree describing the system-on-chip, arch/arm/boot/dts/bcm283x.dtsi
. Practically speaking, the first fragment enables the MMC controller, its pin-muxing, and describes the WiFi chip as a sub-node of the MMC controller. The second fragment describes the pin-muxing configurations used for the MMC controller.
Above, each fragment target, &mmc
and &gpio
is matching an existing device node in the underlying tree arch/arm/boot/dts/bcm283x.dtsi. Note that our hardware is designed such as the reset pin of the wilc is automatically de-assert when powering the board so we don’t define it here.
Now that we have our overlay we enable it through the firmware config.txt :
dtoverlay=sdio,poll_once=off dtoverlay=wilc1000 gpio=38=op,dh
In addition to our own wilc1000 overlay, we are also loading the sdio overlay, with the poll_once=off
argument to make sure we are polling our wilc device even after the boot is complete when the chip enable gpio is asserted through the firmware directive gpio=38=op,dh
.
After copying the wilc1000.dtbo
on our board we can verify that both overlays are getting merged in the main device-tree using the vcdbg
command:
pi@raspberrypi:~$ sudo vcdbg log msg 002351.555: brfs: File read: /mfs/sd/config.txt 002354.107: brfs: File read: 4322 bytes ... 005603.860: dtdebug: Opened overlay file 'overlays/wilc1000.dtbo' 005605.500: brfs: File read: /mfs/sd/overlays/wilc1000.dtbo 005619.506: Loaded overlay 'sdio' 005624.063: dtdebug: fragment 3 disabled 005624.160: dtdebug: fragment 4 disabled 005624.256: dtdebug: fragment 5 disabled 005633.283: dtdebug: merge_fragment(/soc/mmcnr@7e300000,/fragment@0/__overlay__) 005633.308: dtdebug: +prop(status) 005633.757: dtdebug: merge_fragment() end 005642.521: dtdebug: merge_fragment(/soc/mmc@7e300000,/fragment@1/__overlay__) 005642.549: dtdebug: +prop(pinctrl-0) 005643.003: dtdebug: +prop(pinctrl-names) 005643.443: dtdebug: +prop(non-removable) 005644.022: dtdebug: +prop(bus-width) 005644.477: dtdebug: +prop(status) 005644.935: dtdebug: merge_fragment() end 005646.614: dtdebug: merge_fragment(/soc/gpio@7e200000,/fragment@2/__overlay__) 005650.499: dtdebug: merge_fragment(/soc/gpio@7e200000/sdio_ovl_pins,/fragment@22 /__overlay__/sdio_ovl_pins) ...
Of course there are lots of other useful tools that we used to debug, such as raspi-gpio
and dtoverlay
:
pi@raspberrypi:~$ sudo raspi-gpio get BANK0 (GPIO 0 to 27): GPIO 0: level=1 fsel=0 func=INPUT GPIO 1: level=1 fsel=0 func=INPUT GPIO 2: level=1 fsel=0 func=INPUT ... GPIO 22: level=0 fsel=7 alt=3 func=SD1_CLK GPIO 23: level=1 fsel=7 alt=3 func=SD1_CMD GPIO 24: level=1 fsel=7 alt=3 func=SD1_DAT0 GPIO 25: level=1 fsel=7 alt=3 func=SD1_DAT1 GPIO 26: level=1 fsel=7 alt=3 func=SD1_DAT2 GPIO 27: level=1 fsel=7 alt=3 func=SD1_DAT3
Here we can actually see that the correct pinmuxing configuration is set for the SDIO pins.
Testing WiFi on RaspberryPi OS
During our test we found that the wilc1000 seems to only support one mode at time which means only STA or soft-AP. The first mode is quite straightforward to test using the raspi-config
tool, we just had have to supply our SSID name and password.
To know which modes are supported and if they are supported simultaneously use iw list
:
pi@raspberrypi:~$ iw list Wiphy phy0 max # scan SSIDs: 10 max scan IEs length: 1000 bytes max # sched scan SSIDs: 0 max # match sets: 0 max # scan plans: 1 max scan plan interval: -1 max scan plan iterations: 0 Retry short limit: 7 Retry long limit: 4 Coverage class: 0 (up to 0m) Supported Ciphers: * WEP40 (00-0f-ac:1) * WEP104 (00-0f-ac:5) * TKIP (00-0f-ac:2) * CCMP-128 (00-0f-ac:4) * CMAC (00-0f-ac:6) Available Antennas: TX 0x3 RX 0x3 Supported interface modes: * managed * AP * monitor * P2P-client * P2P-GO ... software interface modes (can always be added): interface combinations are not supported Device supports scan flush. Supported extended features:
To use the wilc1000 in soft-AP mode, one need to install additional packages in the RaspberryPi OS, such as hostapd
and dnsmasq
.
SGTL5000 audio codec Device Tree Overlay
After integrating support for the WILC1000 WiFi chip, we can now look at how we got the SGTL5000 audio codec to work. We wrote an overlay arch/arm/boot/dts/overlays/sgtl5000-overlay.dts
with the following contents, and also edited arch/arm/boot/dts/overlays/Makefile to ensure it is built. Our sgtl5000-overlay.dts
contains:
/dts-v1/; /plugin/; /{ compatible = "brcm,bcm2835"; fragment@0 { target-path = "/"; __overlay__ { sgtl5000_mclk: sgtl5000_mclk { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <12288000>; clock-output-names = "sgtl5000-mclk"; }; }; }; fragment@1 { target = <&soc>; __overlay__ { reg_1v8: reg_1v8@0 { compatible = "regulator-fixed"; regulator-name = "1V8"; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-always-on; }; }; }; fragment@2 { target = <&i2c1>; __overlay__ { status = "okay"; sgtl5000_codec: sgtl5000@0a { #sound-dai-cells = <0>; compatible = "fsl,sgtl5000"; reg = <0x0a>; clocks = <&sgtl5000_mclk>; micbias-resistor-k-ohms = <2>; micbias-voltage-m-volts = <3000>; VDDA-supply = <&vdd_3v3_reg>; VDDIO-supply = <&vdd_3v3_reg>; VDDD-supply = <®_1v8>; status = "okay"; }; }; }; fragment@3 { target = <&i2s>; __overlay__ { status = "okay"; }; }; fragment@4 { target = <&sound>; sound_overlay: __overlay__ { compatible = "simple-audio-card"; simple-audio-card,format = "i2s"; simple-audio-card,name = "sgtl5000"; simple-audio-card,bitclock-master = <&dailink0_master>; simple-audio-card,frame-master = <&dailink0_master>; simple-audio-card,widgets = "Microphone", "Microphone Jack", "Speaker", "External Speaker"; simple-audio-card,routing = "MIC_IN", "Mic Bias", "External Speaker", "LINE_OUT"; status = "okay"; simple-audio-card,cpu { sound-dai = <&i2s>; }; dailink0_master: simple-audio-card,codec { sound-dai = <&sgtl5000_codec>; }; }; }; };
This is a much more complicated overlay, with a total of 5 fragments, which we will describe in detail:
fragment@0
: this fragment adds a new Device Tree node that describes a clock that runs at a fixed frequency. Indeed, the sys_mclk clock of our codec is provided by an external oscillator running at 12.288 Mhz: thissgtl5000_mclk
describes this external oscillator.fragment@1
: this fragment defines an external power supply used as the VDDD-supply of the sgtl5000 codec. Indeed, this codec is simply power by an always-on power supply at 1.8V. Don’t forget to enable both kernel optionsCONFIG_REGULATOR=y
andCONFIG_REGULATOR_FIXED_VOLTAGE=y
otherwise the driver will just silently fail to probe at boot.fragment@2
: this fragment describes the audio codec itself. The audio codec is described as a child of the I2C controller it is connected to: indeed the SGTL5000 uses I2C as its control interface.fragment@3
: this fragment simply enables the I2S interface. TheCONFIG_SND_BCM2835_SOC_I2S=y
kernel option must be enabled to have the corresponding driver.fragment@4
: this fragment describes the actual sound card. It uses the genericsimple-audio-card
driver, describes the two sides of the audio link: the CPU interface in the&i2s
and the codec interface in the&sgtl_codec
node, and describes the audio widgets and routes. See the simple-audio-card Device Tree binding
With this overlay in place, we need to enable it in the config.txt
file, as well as the I2C1 overlay with a correct pin-muxing configuration:
dtparam=i2s=on dtoverlay=sgtl5000 dtoverlay=i2c1,pins_2_3 # AUDIO_SD gpio=40=op,dh
Once the system has booted, a new audio card is visible, and all the classic ALSA user-space utilities can be used: amixer
to control the volume, aplay
and arecord
to play/record audio.
Conclusion
In this blog post, we have shown how Device Tree Overlays can easily be used to extend the description of the hardware, and enable the use of additional hardware devices on a Raspberry Pi system. Do not hesitate to contact us for support on Raspberry Pi platforms!
Thanks for this article! When is the target resolved to a numerical phandle? Is that part of generating the dtbo? Does it happen on the target during boot? I am wondering how tightly dtbo files are coupled to the dtb files. Thanks!
Hello Chris,
Indeed the phandles are resolved when the firmware loader parse and merge overlays with the main device-tree /boot/dt-blob.dtb.
Following this process is possible through command “sudo vcdbg log msg”.
Could you also do a post for the same with more traditional soc’s with for example U-Boot and arguments to those dtbo’s? Also I really need to learn/get more informations how to write dtb’s for devices which not yet have any available. I do work on reverse engineering old TomTom navigation systems.
You can get more details about writing Device Tree in our recent talk https://www.youtube.com/watch?v=Nz6aBffv-Ek, or in our Linux kernel driver development training course at https://bootlin.com/training/kernel/ (training materials are freely available).
Could you please suggest an overlay for MAX14830 as well
While we have used the MAX14830 in another project, we haven’t used it on a RaspberryPi with the need to use an overlay. But if you look at the Device Tree binding for the MAX14380 at https://elixir.bootlin.com/linux/latest/source/Documentation/devicetree/bindings/serial/maxim,max310x.txt and read up this article, you should relatively easily be able to write such an overlay.
If not, do not hesitate to contact us, and we can provide commercial support/consulting to help you with your project.
hello sir, I got error when I want to compile .dts and I don’t know what to do.
I’m really confused.
If you want to get help and support, you need to ask precise and specific questions. What are you doing, which command are you running, what error message are you getting, etc.
Running into an issue with loading the module. Are you compiling the zImage with the WILC baked into the kernel ( CONFIG_WILC1000=y ) or as a module (CONFIG_WILC1000=m)?
I tried making it a module and then copying over the .ko files to /lib/modules/$(uname -r) but that doesn’t work. I get the error
modprobe: FATAL: Module wilc1000-spi not found in directory /lib/modules/5.10.17-v7l+
I see that modules are now organized like the source tree, e.g.,
/lib/modules/5.10.17-v7l+/kernel/drivers/net/wireless/broadcom/brcm80211/brcmfmac/brcmfmac.ko
I did a depmod and tried loading again but got the error:
modprobe: ERROR: could not insert ‘wilc1000_spi’: Exec format error
Cross-compiling for RPi CM4 with ARCH=arm CC=arm-linux-gnueabihf- and base config was bcm2711_defconfig.
Thanks!
Hello,
Which version of the kernel are you using ?
If you are using the in staging driver version then enable support for the WILC module with either sdio (CONFIG_WILC_SDIO) or spi (CONFIG_WILC_SPI) option.
Only the upstream driver version provide options CONFIG_WILC1000/CONFIG_WILC1000_SDIO and CONFIG_WILC1000_SPI.
Regards,
Hi,
I just came to find this site. Very useful for a newbie like me.
Do you always need to build a new kernel configuration when adding new hardware with raspberry pi? Will adding an overlay alone sufficient?
Do you have a step by step guide to adding new hardware like the above? I want to add new codec to pi 3B+.
Thank you
A Device Tree overlay only describes the hardware, i.e tells the kernel which hardware devices exist, and how they are connected to the system. You still need a driver (i.e, code) that knows how to control this hardware. And whether such drivers are compiled into your kernel or not is defined by the kernel configuration.
Bottom line is: yes, when you want to add support for a new peripheral attached to a RaspberryPi, you typically need to (1) describe this device in the Device Tree, potentially using overlays and (2) adjust the kernel configuration to enable the relevant drivers, of course if they are not already enabled in your existing kernel configuration.
Would you consider only more example? Illustrate how to do a dtoverlay for GPIO expansion, say the PCF8574 and/or the PCF8575?
I am not sure to understand your request. Are you asking that we add another example for a I2C-connected GPIO expander?
I guess so.
https://github.com/raspberrypi/firmware/issues/1790
Hi,
I just came to find this site. Very useful for a newbie like me.
I am trying to install “Newracom” HaLow driver from the following URL
https://github.com/newracom/nrc7292_sw_pkg
I followed the following steps:
1) cd ~/
2) git clone https://github.com/newracom/nrc7292_sw_pkg.git
3) cd /boot/overlays
4)sudo dtc -I dts -O dtb -o newracom.dtbo newracom.dts
I am unable to proceed. I think the problem is in step-1 the package is downloaded to home directory whereas in step-3 cd to /boot/overlays
Hence step-4 is failing.
Any suggestion as to how to overcome this problem?
Thanks in advance
Ravi Chandar