MSM8916 Mainlining

From postmarketOS
Jump to navigation Jump to search

MSM8916/APQ8016 (or Snapdragon 410) is Qualcomm SoC released in 2014. Support in mainline was originally added for the Dragonboard 410c, but most of it also works well on other MSM8916-based devices.


The following features provided by the MSM8916 SoC are supported in mainline and should work on most MSM8916-based devices after the device tree has been set up:

  • UART
  • USB
  • Internal/External Storage (eMMC/SD card)
  • WiFi/Bluetooth
  • GPU, Display (Note: There are many different display panels and each needs a custom panel driver...)
  • Audio
  • Buttons
  • Modem (SMS, voice calls with audio, mobile data)

Certain components (e.g. touchscreen, sensors, ...) are device-specific. With a bit of luck, some will be already supported by mainline, others won't.


The first MSM8916 devices using mainline were added in pmaports!582. The following components are involved when running mainline on MSM8916 devices.


The close-to-mainline Linux kernel fork with patches that have not been accepted/submitted upstream yet. Patches in the master branch are generally in good shape unless marked otherwise. Submit a PR to add new patches there.


The reference bootloader provided by Qualcomm for MSM8916 is open-source and based on Little Kernel (LK). Most devices will use this bootloader in a more or less modified form. On Samsung devices for example, the standard Fastboot interface was replaced by their proprietary download mode.

lk2nd is a fork of the reference bootloader with the goal to provide a unified boot interface on all MSM8916 devices. It provides a standard Fastboot interface (even on Samsung devices!). It also used for a number of mainline quirks (e.g. to set a WiFi/BT MAC address in the device tree; without lk2nd, WiFi/BT does not work out of the box). Eventually, more quirks may be added in the future. It is therefore recommended for all mainline MSM8916 devices. Porting it is generally much easier than porting mainline.

Note: lk2nd does not replace your stock bootloader. It is packed into a standard Android boot image and loaded by the stock bootloader from the boot partition. The real boot partition used by lk2nd is then placed with 1 MB offset into the boot partition.


The shared device package in postmarketOS. It contains shared dependencies and configuration files.

Getting Started


  • MSM8916-based device
  • UART if possible (with a bit of luck you may get USB working without UART)
  • (Downstream) Linux kernel source for your device (explaining ways to mainline without kernel source is out of scope for this article)
  • Basic knowledge about Linux, Git, C, Device Trees, ...
  • Willingness to learn about "mainlining" and to figure out things on your own

Before you start

Warning: Mainlining (or any unintended "modding") may brick your device permanently if you make a mistake. There is a good chance that nothing happens if you are a bit careful, but do not continue with a device that you need every day. You have been warned!

Mainlining is not easy. MSM8916 is a platform where a lot components can be easily enabled by only setting up a device tree, which can be largely copied from other devices with minor changes. However, at some point you will reach the point where you would like to enable a particular component that is device-specific (e.g. the touchscreen, sensors, ...). In this case you will be largely on your own, and need to figure out how to enable it yourself. (Do you just need to add something to the device tree or even write a new kernel driver?) That requires some familiarity with the way the Linux kernel is working for MSM8916.

The best way to make yourself familiar with the process is to attempt to figure out some simple things on your own. Therefore, this guide will only describe everything until (eventually) USB network is working. For everything else, this article provides only some information that might be helpful to figure it out yourself.

If you have any questions, ask in the mainline channel on Matrix or IRC. Make sure to mention "msm8916" if your question is specific to this article.


First, let's take a short look at the downstream kernel. The device trees on downstream are usually in arch/arm/boot/dts/qcom/msm8916-*.dts(i) (in Samsung kernels: arch/arm/boot/dts/samsung/msm8916/msm8916-*.dts(i)). You need to find the .dts file that describes the device tree for your device. One way to check this is to look at /proc/device-tree/model on downstream, and search for the same string in that directory.

Now, let's check how it looks on mainline. Clone msm8916-mainline/linux and take a look at arch/arm64/boot/dts/qcom/. This is the directory where you will add your device tree later.


Note: lk2nd is not strictly required to boot mainline. However, certain features (e.g. WiFi and BT) only work with some additional mainline quirks included in lk2nd. In the future, there may be more features that depend on extra code that needs to be run in the bootloader. Therefore, lk2nd is recommended for all devices, even if your stock bootloader already provides a standard Fastboot interface.

msm8916-mainline/lk2nd does not require any device-specific code. Normally, it should just run out of the box on your device. However, most stock bootloaders require a valid device tree to be present in the Android boot image. To avoid problems, a dummy device tree with the qcom,board-id from the downstream kernel needs to be added to lk2nd.

Before you make any changes, try building it. Then, add a new file for your device to dts/ that looks like:

// SPDX-License-Identifier: GPL-2.0-only


/include/ "msm8916.dtsi"

/ {
	// This is used by the bootloader to find the correct DTB
	qcom,msm-id = <206 0>; // qcom,msm-id = <247 0>; for APQ8016
	qcom,board-id = /* FIXME: Check your downstream device tree */;

	model = "Device Name"; // FIXME
	compatible = "<vendor>,<codename>", "qcom,msm8916", "lk2nd,device"; // FIXME

Note the FIXME lines in the example above. This is where you still need to make changes for your device. For example, replace the model with a display name for your device, and the placeholders in the compatible string.

The qcom,board-id property allows the bootloader to select the correct device tree. MSM8916 devices usually use QCDT (multiple device tree blobs (dtb) are packaged in one Android boot image). You should be able to find the qcom,board-id in the downstream .dts file for your device.

Some devices (in particular Samsung) do not implement the QCDT specification correctly - they all have the same qcom,board-id. In that case, the device cannot be detected only based on the selected DTB. For Samsung devices, msm8916-samsung-r0x.dts covers multiple devices - the actual device is then detected using androidboot.bootloader version passed by the Samsung bootloader.

Make sure to add the new file to dts/, and re-compile lk2nd.

With a bit of luck, flashing the boot image on your device should make it boot into a Fastboot screen, and the device should show up via USB. If either of that is not the case, we need to investigate further. Sometimes, there is some kind of Micro-USB Interface Controller that sits between USB and the SoC, which may require manual setup to work correctly. Setting that up would require device-specific code. If you need help, ask in the mainline channel on Matrix or IRC.

Device Package

Before you can build the mainline kernel for your device, you need to setup the postmarketOS device packages. Avoid creating the device/kernel package directly through pmbootstrap. There is no need to analyze a boot image. (The boot image offsets are not actually used on MSM8916; plus, all MSM8916 devices should use Fastboot through lk2nd...) You can start with the following examples:


# Reference: <>
pkgdesc="Device Name"
options="!check !archcheck"
depends="postmarketos-base mkbootimg linux-postmarketos-qcom-msm8916 soc-qcom-msm8916"

build() {
	devicepkg_build $startdir $pkgname

package() {
	devicepkg_package $startdir $pkgname


# Reference: <>
# Please use double quotes only. You can source this file in shell scripts.

deviceinfo_name="Device Name"
deviceinfo_year="<release year>"

# Device related

# MSM DRM cannot take over the framebuffer from the bootloader at the moment

# Bootloader related
deviceinfo_kernel_cmdline="earlycon console=ttyMSM0,115200 PMOS_NO_OUTPUT_REDIRECT"

Follow the postmarketOS porting guide until the device package builds successfully.

Build Kernel

Go back to your msm8916-mainline/linux clone. Try to build it with

$ cd path/to/msm8916-mainline/linux
$ source path/to/pmbootstrap/helpers/

# Initialize kernel configuration
$ make msm8916_defconfig pmos.config

# Compile kernel; replace <cores> with the number of cores you'd like to use for compilation
$ make -j<cores>

# Create postmarketOS package with your built kernel
$ pmbootstrap build --envkernel linux-postmarketos-qcom-msm8916

This is how you can build and test local changes for the kernel from now on. Now we will continue setting up an initial device tree for your device.

Initial Device Tree

The available hardware components of a device are described in the device tree (see e.g. Mainlining#Materials_to_look_into for introductions). Components provided by the SoC (MSM8916) are described in the common include msm8916.dtsi. Components provided by the PMIC (PM8916) are described in pm8916.dtsi. Finally, the combination of both is described in msm8916-pm8916.dtsi.

You need to create a new device tree with the device-specific configuration, plus additional components used in your device (e.g. touchscreen).

The device tree for your device should be placed in a new file at arch/arm64/boot/dts/qcom/msm8916-<vendor>-<codename>.dts. A very simple device tree that only enables UART output could look like this:

// SPDX-License-Identifier: GPL-2.0-only


#include "msm8916-pm8916.dtsi"

/ {
 	model = "Device Name"; // FIXME
 	compatible = "<vendor>,<codename>", "qcom,msm8916"; // FIXME

	aliases {
		serial0 = &blsp1_uart2;

	chosen {
		stdout-path = "serial0";

&blsp1_uart2 {
	status = "okay";

We see two different kind of device tree nodes here. There is the root device tree node (/), which we use to set up some configuration in the chosen node. Then there is the &blsp1_uart2 node. This is a reference to a device tree node that was defined somewhere under the root node by one of the includes. In this case, msm8916.dtsi sets up lots of pre-defined devices (e.g. for UART, eMMC, SD card, ...). We don't care exactly where or how they are defined. We just want to enable them and (if necessary) add device-specific configuration to them.

We can reference existing device tree nodes using labels. The original device tree node for blsp1_uart2 in msm8916.dtsi looks like this:

/ {
	/* ... */
	soc {
		/* ... */
		blsp1_uart2: serial@78b0000 {
			compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
			/* ... */
			status = "disabled";
		/* ... */
	/* ... */

The part before the colon (blsp1_uart2:) is the label we can use to reference the node. We can use that to add additional properties or to change one of the existing ones. In particular, we can see that msm8916.dtsi declares blsp1_uart2 (one of the UART ports) as disabled by default. We want to enable the UART port so we override status = "disabled" with status = "okay".

Finally, the combination of the aliases and chosen node selects blsp1_uart2 as default console output.

Note: MSM8916 has 122 GPIO pins that can be configured with different functions. GPIO 4 and 5 can be configured to provide UART (blsp1_uart2). Typically, this is the UART port that is used for debug console output. However, there are also some devices that have UART on GPIO 0 and GPIO 1 instead. In that case, you would use blsp1_uart1.

Before you build, don't forget to add an entry to arch/arm64/boot/dts/qcom/Makefile. This device tree should be already enough to boot with working serial console if you are able to access UART. If you don't have UART you will need to skip testing this step and hope that USB works out of the box in the next step.


As next steps we would like to enable internal/external storage (eMMC/SD card) and make USB gadget mode work. This will allow you to attach your device to a PC and get a debugging console.

However, a requirement of both of these hardware components is that you have some voltage regulators set up properly. Hardware components have different power supply requirements, so usually the PMIC(s) used together with a SoC provide several different regulators that can be configured to produce a voltage level within a specified range.

Let's take a quick look at msm8916-pm8916.dtsi. As a reminder: This include contains common definitions that apply to most devices that pair MSM8916 (the SoC) with PM8916 (the PMIC). Most interesting for us are the references to sdhc_1/2 (the SD Host Controller) and usb_hs_phy (a part of the USB controller):

&sdhc_1 {
	vmmc-supply = <&pm8916_l8>;
	vqmmc-supply = <&pm8916_l5>;

&sdhc_2 {
	vmmc-supply = <&pm8916_l11>;
	vqmmc-supply = <&pm8916_l12>;

&usb_hs_phy {
	v1p8-supply = <&pm8916_l7>;
	v3p3-supply = <&pm8916_l13>;

pm8916_l5, pm8916_l11 etc are some of the regulators that are provided by PM8916. As you can see they are configured as supplies for both eMMC/SD card and USB.

All these regulators can be configured to a large voltage range, so it is very important that we set up voltage constraints carefully! The voltage constraints specify the allowed voltage range that can be set for a particular regulator. Once we have set them up properly, we can be reasonably sure that accidental mistakes cannot easily destroy one of the hardware components in a device.

WARNING: Hardware components usually only work correctly within a limited voltage range. Exceeding the specified voltage range for a component may cause it to malfunction, or even break physically. Please go through this section carefully and ask for feedback in the chat.

Let's get back to the device tree for your device. We extend the initial UART example from the previous section with new device tree nodes to set up the voltage constraints. A typical set of voltage constraints for PM8916 looks like this:

// FIXME: Verify all regulators with downstream
&smd_rpm_regulators {
	vdd_l1_l2_l3-supply = <&pm8916_s3>;
	vdd_l4_l5_l6-supply = <&pm8916_s4>;
	vdd_l7-supply = <&pm8916_s4>;

	s3 {
		regulator-min-microvolt = <1200000>;
		regulator-max-microvolt = <1300000>;

	s4 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <2100000>;

	l1 {
		regulator-min-microvolt = <1225000>;
		regulator-max-microvolt = <1225000>;

	l2 {
		regulator-min-microvolt = <1200000>;
		regulator-max-microvolt = <1200000>;

	l4 {
		regulator-min-microvolt = <2050000>;
		regulator-max-microvolt = <2050000>;

	l5 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <1800000>;

	l6 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <1800000>;

	l7 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <1800000>;

	l8 {
		regulator-min-microvolt = <2850000>;
		regulator-max-microvolt = <2900000>;

	l9 {
		regulator-min-microvolt = <3300000>;
		regulator-max-microvolt = <3300000>;

	l10 {
		regulator-min-microvolt = <2700000>;
		regulator-max-microvolt = <2800000>;

	l11 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <2950000>;
		regulator-system-load = <200000>;

	l12 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <2950000>;

	l13 {
		regulator-min-microvolt = <3075000>;
		regulator-max-microvolt = <3075000>;

	l14 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <3300000>;

	l15 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <3300000>;

	l16 {
		regulator-min-microvolt = <1800000>;
		regulator-max-microvolt = <3300000>;

	l17 {
		regulator-min-microvolt = <2850000>;
		regulator-max-microvolt = <2850000>;

	l18 {
		regulator-min-microvolt = <2700000>;
		regulator-max-microvolt = <2700000>;

You should paste this at the end of your device tree and then go through all the voltages and compare them what you see in your downstream reference kernel. Here are some tips and tricks:

  • Downstream regulators look a bit different, but they are usually defined in msm8916-regulators.dtsi. Note that some device trees may override the regulator voltages in a device-specific .dts(i).
  • s1 (smpa1 and l3 (ldoa3) are missing in the voltage constraints because they are managed separately as power domains. Simply put, these two regulators are used by many components in the SoC. Instead of picking an exact voltage, we vote for a particular performance state. On downstream this is called voltage corner. There is a special Resource Power Manager (RPM) co-processor that collects all the votes and then decides for a particular voltage to use.
    • There is no need to set up voltage constraints for these because there is no way to set an dangerous voltage through the corner interface.
  • s2 (spm-regulator) is special (used to control the voltage for the CPU cores) and is not touched by mainline at the moment. Just ignore it.
  • Also ignore the -ao and -so regulators (usually just on l7)
  • regulator-system-load on l11 in the snippet above is for stability of the SD card[1]. This is declared in a different place on downstream, but most likely you can just keep it as-is.
  • vdd_l1_l2_l3-supply etc does not exist on downstream, but you can also keep it as-is. It is extremely unlikely that this is different for a particular device. Most important is validating the voltages.
Note: Usually most of the voltage ranges should stay as-is. If you are unsure, better be safe and ask for feedback in the chat!


Theoretically, enabling USB would be as simple as adding:

&usb {
	status = "okay";

However, due to an implementation detail USB only works if the USB controller is notified when you actually insert an USB cable. On mainline, these notifications come from the extcon (external connector) subsystem. Unfortunately, how we can detect that an USB cable was inserted differs quite significantly between devices. Sometimes (mainly on Samsung devices), there is some kind of Micro-USB Interface Controller that sits between USB and the SoC, which can detect various different cable types (connected to PC, connected to charger adapter, USB-OTG, ...). In most other cases, this notification comes from the charger driver, which is usually quite hard to get working (see #Battery/Charging).

How to handle some of the common variants is described here. Feel free to ask in the chat for further help.

ID pin

This is the easiest solution if you have some kind of USB ID GPIO defined downstream. Usually it's called qcom,usbid-gpio under the &usb_otg node but the name varies a bit sometimes. The USB ID pin is used to differentiate between:

  • Gadget mode (where you connect the device to a PC)
  • Host mode (where you use a special USB-OTG adapter to connect other USB devices, e.g. an USB keyboard)

It can only detect these two cases, so it does not allow us to detect that an USB cable is actually inserted. However, as a start we can pretend that an USB cable is inserted whenever we are not in host mode (i.e. whenever there is no USB-OTG adapter inserted). This means that the USB controller will stay active all the time. This is not much of a problem for getting started, right? :)

For this example, there was qcom,usbid-gpio = <&msm_gpio 110 0>; defined downstream, so the USB ID pin is connected to GPIO 110. Now we declare a new device tree node, which sets up an extcon device using the USB ID GPIO. We put this under the root node (/) because it is an entirely new node (previously we were mainly re-configuring existing device nodes):

#include <dt-bindings/gpio/gpio.h>

/ {
	/* ... */

	usb_id: usb-id {
		compatible = "linux,extcon-usb-gpio";
		id-gpios = <&msmgpio 110 GPIO_ACTIVE_HIGH>;
		pinctrl-names = "default";
		pinctrl-0 = <&usb_id_default>;

Something we have not seen before in this guide is pinctrl (or Pin Control): As mentioned earlier, MSM8916 has 122 GPIO pins that can be configured with different functions. The idea is that there are only so many physical pins you can afford to have on an SoC, but you want to expose a lot of features. Thankfully, most of the time you won't need to use all of the features in the same device, which is why SoC manufatcturers get away with multiplexing multiple functions behind one physical pin. The most common use of a pin is as a GPIO (basically a digital input/output line that can be either set high or low, respectively can be sensed to determine the level it was set to by something outside the SoC). But it's also possible to configure other functions (e.g. UART or I²C) for some of the pins. Additionally, pins can be configured with pull-up/pull-down resistors or with a particular drive strength. In the Linux kernel, the Pin Control Subsystem (pinctrl) handles that configuration.

In most cases you don't need to figure out yourself if you need pull-down/pull-up or a particular drive strength. We can check on downstream how the pins were configured and translate that to a similar configuration on mainline. While downstream may contain errors (like enabling an internal pull-up on a line with external one), it's proven to work reliably. When in doubt, a datasheet is of immense help. In our example here we find the following line downstream: pinctrl-0 = <&usbid_default>;. We can then search for usbid_default: in the related device tree files (if it exists multiple times, check which includes are actually used for your device). In this case, we find it in msm8916-pinctrl.dtsi and it looks like:

usb-id-pin {
	qcom,pins = <&gp 110>;
	qcom,num-grp-pins = <1>;
	qcom,pin-func = <0>;
	label = "usb-id-pin";
	usbid_default: default {
		drive-strength = <8>;

The pin is configured as GPIO (qcom,pin-func = <0>) with pull-up and a drive-strength of 8mA.

We can easily define a similar configuration on mainline: To do that, we define a new pin configuration as part of the &msmgpio node, and then reference it with pinctrl-names/pinctrl-0 as seen above. The above example translates to the following:

&msmgpio {
	usb_id_default: usb-id-default {
		pins = "gpio110";
		function = "gpio";

		drive-strength = <8>;

Finally, we assign our new extcon device to both &usb and &usb_hs_phy, and then USB should be working. :)

&usb {
	status = "okay";
	extcon = <&usb_id>, <&usb_id>;

&usb_hs_phy {
	extcon = <&usb_id>;
Note: In the &usb node, the first extcon should point to the one that detects USB gadget mode, while the second one should detect USB host mode. In our case, our extcon device forces USB gadget mode as long as you don't connect an USB-OTG adapter. Later these would potentially be separate extcon devices. For &usb_hs_phy only the one for USB gadget mode should be specified.


Especially Samsung devices do not have the USB ID pin connected to a GPIO like explained in the previous section. Instead, there is an additional chip that sits between the USB connector and the USB controller: a Micro-USB Interface Controller (or: MUIC). The MUIC handles all USB cable related detection and therefore pretty much represents an extcon device as we have set up manually using the USB ID GPIO in the last section.

The disadvantage is that we now need a special driver for the MUIC. Many different chips exist so often a driver is not available immediately, since you usually don't want to write an entire new driver before you know if mainline is booting on your device at all. Anyway, if there is a driver for it already, the approach to make it work would be the following:

  1. Identify the MUIC chip. Usually you can find it by searching for muic in the downstream device tree. In this example we will use SM5502, which has the extcon-sm5502.c driver in mainline.
  2. Identify the I²C bus it is connected to. (it will probably be an I²C bus, but in theory it might be e.g SPI.) Samsung tends to use i2c-gpio, which emulates the I²C protocol on GPIO pins using bit banging. Note that sometimes they use i2c-gpio even when there is a hardware I²C bus available on the same pins. Not sure why they do that. They also sometimes add i2c-gpio-specific dts properties to hw I²C nodes. You can compare the SDA/SCL GPIO with the pins of the hardware I²C controllers and if it matches it is probably better to use the hardware I²C controller instead of i2c-gpio.
  3. Declare a new device tree node for the MUIC device (and eventually the i2c-gpio bus) and reference it as extcon for &usb/&usb_hs_phy like in the usb_id example above.

If the MUIC chip is not supported by a mainline driver (yet), check out the next section (#Otherwise) for now.

Let's get to the example. The downstream device tree contains:

i2c_9:i2c@9 { /* SM5502 GPIO-I2C */
	compatible = "i2c-gpio";
	i2c-gpio-scl = <&msm_gpio 3 0x00>;
	i2c-gpio-sda = <&msm_gpio 2 0x00>;
	/* ... */

	sm5502@25 {
		compatible = "sm5502,i2c";
		reg = <0x25>;
		interrupt-parent = <&msm_gpio>;
		interrupts = <12 0>;
		/* ... */

As mentioned earlier, there is actually a hardware I²C bus available on these pins (blsp_i2c1) so we have two options. We can use the hardware I²C bus (preferred if possible):

&blsp_i2c1 {
	status = "okay";

	muic: extcon@25 {
		compatible = "siliconmitus,sm5502-muic";

		reg = <0x25>;
		interrupt-parent = <&msmgpio>;
		interrupts = <12 IRQ_TYPE_EDGE_FALLING>;

		pinctrl-names = "default";
		pinctrl-0 = <&muic_int_default>;

... or we can use i2c-gpio like Samsung:

#include <dt-bindings/gpio/gpio.h>

/ {
	/* ... */

	i2c-muic {
		compatible = "i2c-gpio";
		sda-gpios = <&msmgpio 2 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
		scl-gpios = <&msmgpio 3 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;

		pinctrl-names = "default";
		pinctrl-0 = <&muic_i2c_default>;

		#address-cells = <1>;
		#size-cells = <0>;

		/* muic: extcon@25 as above... */

How to define the pinctrl for these nodes is left as an exercise for the reader (see #ID pin for explanations). Ask if you need any help!


If you have neither USB ID GPIO nor MUIC, none of the options above will work for you. For now, please use the following hack for initial bringup:

/ {
	/* ... */

	usb_vbus: extcon-usb-dummy {
		compatible = "linux,extcon-usb-dummy";

&usb {
	status = "okay";
	extcon = <&usb_vbus>;
	dr_mode = "peripheral";

&usb_hs_phy {
	extcon = <&usb_vbus>;

This is just meant as a simple way to get started with USB working. Please ask in the chat later how to clean this up properly. Once a better solution exists this section will be updated.


If you do not have (easy) access to UART, you now have the chance to boot your kernel for the first time!

  1. Make sure to build the kernel (see #Build Kernel).
  2. Create a postmarketOS installation: pmbootstrap install
  3. Enable the debug-shell hook. This will provide a telnet console via USB without having to flash the root file system to the internal storage.
  4. Boot the kernel using pmbootstrap flasher boot. (Alternatively you could flash it using pmbootstrap flasher flash_kernel and reboot, but that's not nearly as convenient during development...)
  5. With a bit of luck, the device should show up on telnet via USB after booting. Take a look at dmesg to see the kernel log of mainline booting on your device!

Note that you need to remove the debug-shell hook explicitly using pmbootstrap initfs hook_del debug-shell before attempting to boot with the rootfs in the next section.


If you have USB working, it's time to get the eMMC (internal storage) working next. This will allow you to flash the postmarketOS rootfs to get access to SSH instead of the minimal telnet session you had so far.

In most cases, enabling the eMMC should be as simple as adding:

&sdhc_1 {
	status = "okay";

	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&sdc1_clk_on &sdc1_cmd_on &sdc1_data_on>;
	pinctrl-1 = <&sdc1_clk_off &sdc1_cmd_off &sdc1_data_off>;

In this case, you don't need to write the pinctrl nodes yourself. Almost all of the devices use the same configuration here so these are shared for all devices in msm8916-pins.dtsi.

That's it! Now you can build the kernel again, flash the rootfs using pmbootstrap flasher flash_rootfs and boot as above. Make sure to remove the debug-shell hook first using pmbootstrap initfs hook_del debug-shell. With a bit of luck, you might boot straight into postmarketOS with working SSH! :)

SD card

Your device probably has a (micro) SD card slot. If you would like to keep your Android installation, you can flash the postmarketOS rootfs there alternatively. Or you can use it as extra/removable storage for some special files. It should be almost as simple as getting the eMMC working, so grab a micro SD card for testing and try to get it working:

#include <dt-bindings/gpio/gpio.h>

&sdhc_2 {
	status = "okay";

	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&sdc2_clk_on &sdc2_cmd_on &sdc2_data_on>;  // add &sdc2_cd_on if you use cd-gpios below
	pinctrl-1 = <&sdc2_clk_off &sdc2_cmd_off &sdc2_data_off>; // add &sdc2_cd_off if you use cd-gpios below

	// FIXME: Pick one! See below and compare with downstream...
	//cd-gpios = <&msmgpio 38 GPIO_ACTIVE_LOW>;

Please take a close look at the comments here. The implementation of SD card detection varies depending on the device. The following two types are common:

  • Hotplug detection using cd-gpios (card detect): This allows detecting if the SD card is removed at runtime, so this is often used if the SD card is removable without turning off the device.
  • Scanning for a SD card once during bootup: The assumption is that the device is non-removable at runtime, e.g. because it is not accessible without removing the battery. In that case, they were able to omit the card-detect GPIO.

Check your downstream device tree for cd-gpios (usually on the &sdhc_2 node). If it exists, uncomment it here and also add &sdc2_cd_on/off to the pinctrl lines. Otherwise, uncomment non-removable.

That's it! Try to boot it and see if the SD card shows up in lsblk or dmesg. If you want, you can also try to mount it and read/write some files.


As a last part of this guide, let's try to get the hardware buttons working. Your device probably has at least a (mechanical) Power, Volume Up and Volume Down button.

The Power and Volume Down buttons are usually connected to the PMIC (PM8916). The Power button is - obviously - used to power on the device. Volume Down is used in combination with the Power button to force a reboot (technically, this behavior is somewhat configurable, but thankfully it is usually set up like this), essentially simulating removing the battery and putting it back in. On devices without removable battery holding these two buttons allows rebooting ("resetting") the device.

On mainline, the pm8941-pwrkey driver implements support for these two buttons. The power button is set up by default in the common pm8916.dtsi include. As mentioned, the Volume Down button is typically connected to the "reset input" (resin). Theoretically, a device could have any button (or even some test points on the mainboard) connected there so we need to enable it explicitly and assign KEY_VOLUMEDOWN:

&pm8916_resin {
	status = "okay";
	linux,code = <KEY_VOLUMEDOWN>;

Volume Up and other additional keys (e.g. Home on Samsung devices) are usually connected to a GPIO pin on the SoC. Therefore, setting them up is trivial using gpio-keys. Try to find the GPIOs in the downstream device tree and set up something like:

#include <dt-bindings/gpio/gpio.h>

/ {
	/* ... */
	gpio-keys {
		compatible = "gpio-keys";

		pinctrl-names = "default";
		pinctrl-0 = <&gpio_keys_default>;

		label = "GPIO Buttons";

		volume-up {
			label = "Volume Up";
			gpios = <&msmgpio 107 GPIO_ACTIVE_LOW>;
			linux,code = <KEY_VOLUMEUP>;

Make sure to set up pinctrl properly: typically these buttons need to be configured with bias-pull-up.

To test the buttons, apk add evtest. Then you can select the input devices in evtest and see if an input event show up when you press them.

Finishing up

If the features enabled during the guide are working, then it is a good time to do some cleanup and propose your changes "upstream" on msm8916-mainline/linux. This repository is intended as a temporary place for patches that are already in good quality, but either pending or not quite finished to submit them on the Linux mainline mailing lists.

Note: The msm8916-mainline/linux repository is not affiliated to the mainline Linux kernel (as maintained by Linus Torvalds). To get your changes into mainline you will need to submit them on the Linux mainline mailing lists later.

Device Tree Structure

So far you have assembled your device tree from various examples shown in this guide. For consistency with the other MSM8916 device trees, please make sure to put your device tree in the following order:

// SPDX-License-Identifier: GPL-2.0-only


#include ...

/ {
 	model = "Device Name"; // FIXME
 	compatible = "<vendor>,<codename>", "qcom,msm8916"; // FIXME

	aliases {
		serial0 = &blsp1_uart2;

	chosen {
		stdout-path = "serial0";

	/* Alphabetically ordered list of new device tree nodes */

/* Alphabetically ordered list of device tree node references, e.g. */
&blsp1_uart2 {
	status = "okay";

&usb {
	status = "okay";

/* Regulators and pinctrl always come last */
&smd_rpm_regulators {
	/* ... */

&msmgpio {
	/* ... */

Patch Requirements

To maintain the quality, all patches are required to:

  • Compile without warnings
  • Have no critical (ERROR:) checkpatch failures (also see Style-check your changes in the mainline "Submitting patches" guide)
    • Sometimes it makes sense to ignore notes and warnings from checkpatch if the code is more readable without fixing them.
  • Have a properly formatted one-line summary in the commit message (there is no need to write a detailed commit message, yet)
    • Look at similar commits in the commit history for examples.

The first two will be automatically checked for you when you open a PR - the CI build will fail otherwise - so you don't have to check it locally. Eventually, you should expect a few additional review comments to improve your code. This will help you to speed up submission of the patches upstream later! ;)

When this is done, then you can continue working on whatever you would like to work on! Below are a few tips and tricks for common features. However, it is hard to write a definitive guide as they differ between devices, so you will need to do some investigation on your own.


The general approach for mainlining is:

  • Look at all resources you can find, e.g.:
    • Commits in msm8916-mainline/linux
    • Existing device trees in mainline and/or commits with relevant changes (sometimes there is helpful information in the commit message)
    • The kernel documentation, especially the device tree binding documentation (all available device tree properties should be documented there)

and compare it with what you can find on downstream (e.g. device tree, kernel code, ...). Do not take the downstream device tree as a base or even copy it as-is. That is the wrong approach. Downstream code tends to be full of mistakes and unnecessarily verbose. It's easier to start from scratch and rather take something in mainline as a base.

Additional tips and tricks:

  • When making changes to kernel code compiled as modules, you need to update the kernel modules on the rootfs (they are not part of the boot image that is loaded via Fastboot). There are different options to do this:


Unfortunately, some components in the SoC rely on proprietary firmware to work properly:

  • WiFi/Bluetooth (wcnss)
  • Modem (modem)
  • Hardware-accelerated video codecs (venus)

Perhaps more unfortunately, typically the firmware is signed with a device-specific key, which means you are forced to continue using the potentially outdated/buggy firmware provided by the stock firmware of your device manufacturer. This means that the firmware must be packaged separately for each device. The following files are needed:

  • wcnss.mdt plus all related segments (wcnss.b00, ...) (usually installed on a separate firmware (NON-HLOS) partition)
  • WCNSS_qcom_wlan_nv.bin (usually installed on the system partition, e.g. /system/etc/firmware/wlan/prima/WCNSS_qcom_wlan_nv.bin)
  • mba.mbn, modem.mdt, modem.b00, ... (usually installed on a separate firmware (NON-HLOS or modem) partition)
  • venus.mdt, venus.00, ... (usually installed on a separate firmware (NON-HLOS) partition, or in /system/etc/firmware/)

First, create a new firmware APKBUILD. Look at firmware/firmware-wileyfox-crackling as an example.

Then, add a new nonfree-firmware subpackage to your device package:


nonfree_firmware() {
	pkgdesc="GPU/WiFi/BT/Video firmware"
	depends="linux-firmware-qcom firmware-vendor-device-venus firmware-vendor-device-wcnss"
	mkdir "$subpkgdir"
Note: On some devices, the manufacturer did not "properly" set up secure boot. We call these devices test-signed, because typically their firmware was signed with keys Qualcomm has provided for testing (test keys). Usually, these devices accept any kind of signed firmware, so you can also use newer firmware from other devices (e.g. from the Dragonboard 410c).


WiFi/Bluetooth are usually provided by the Qualcomm Wireless Connectivity Subsystem (WCNSS), which is partially built into the SoC but paired with an external RF module ("iris").[2] Therefore, enabling it is usually as simple as enabling the &pronto node in the device tree:

&pronto {
	status = "okay";

By default mainline expects WCN3620 as external RF module. However, MSM8916 can be also paired with other RF modules - another common chip is WCN3660B which has dual-band (5 GHz) support additionally. The chip identifier can be obtained from a downstream kernel log (dmesg): There should be a message like wcnss: IRIS Reg: 04000004.[3] Take the first 4 hex chars (i.e. >> 16) and compare it with the chip IDs listed in downstream.

If you do not have WCN3620, you need to override the compatible property of the iris node:

&pronto {
	status = "okay";

	iris {
		compatible = "qcom,wcn3660b";

A few more notes:

  • WiFi and Bluetooth share the same antenna, and they tend to conflict with each other when they are used at the same time ("WiFi/BT coexistence"). In case of problems, try disabling one of them.
  • Dual-band support on WCN3660B seems to be broken at the moment: only 2.4 GHz networks show up in the WiFi network list.


Note: Due to weird dependencies issues in the DRM MSM driver, you need to make WiFi/Bluetooth work to have the DRM MSM driver finish loading successfully. (WCNSS takes a while to load, so it will trigger attempting to probe DRM MSM again after it fails to load on its own due to dependencies issues between the DRM driver and the panel driver...)

The display tends to be one of the most difficult things to get working. This is because manufacturers have many different options how to implement the display in the hardware. Generally, all devices will have a display panel connected to the SoC via MIPI Display Serial Interface (DSI). The panel can vary even for one device model; it is common to support multiple panels to be more flexible when assembling the device. As if this wasn't enough, panels are usually paired with additional controllers (e.g. for the backlight) or regulators (to provide special power supplies). Those may, or may not need to be controlled from the kernel.

The easy part is usually the panel itself. To make it work we likely only need to send some special DSI commands to initialize it. DSI Panel Driver Porting on the Freedreno wiki has an introduction how to port panels. For MSM8916 mainlining, we have created linux-mdss-dsi-panel-driver-generator which attempts to automatically parse the downstream device tree and generates panel drivers for all the supported panels.

How can you figure out which panel is being used by the device you have? The panel is usually listed in a mdss_mdp.panel= kernel parameter provided by the (primary) bootloader. You can check it from the lk2nd log, or using fastboot oem cmdline && fastboot get_staged /dev/stdout. The parameter lists the name of the panel node in the device tree.

Note: The generated panel driver should usually work without larger changes (follow the README). As usual, it is recommended to ask in the chat for further advice before running it.

You also need to add the display panel to your device tree. The panel driver generator also generates a device tree fragment in the .dtsi file that you can use as a base. Typically you just need to fill in the reset-gpios, which can be found downstream. You should also add pinctrl to the &dsi0 node. See commits for other devices for examples.

Note: If you have qcom,regulator-ldo-mode defined downstream you should add (do not add it if you do not have this property downstream!):

&dsi_phy0 {

In some cases, you may need to turn on a GPIO to enable the power supply of the panel. On mainline, this is implemented using a fixed regulator, which represents the hw component that creates the required voltage. You can then use the --regulator option in the panel driver generator to add code to automatically turn on that GPIO.

Note: On downstream, you can often run into drivers which pretend that the GPIO is instead routed to some magic "enable" pin of the panel controller. Ideally, you can dispute this by looking at schematics, respectively by looking at datasheet of said controller and attempting to locate said pin.

Some devices have an extra backlight IC that needs to be controlled separately in the kernel. The easy case is where it is simply enabled using a GPIO: in this case one can simply treat it like a fixed regulator. (It may, in fact, actually be one.) But some backlight ICs have an I²C interface and then need an extra driver in the kernel.


There are different approaches used to dim the brightness of the screen. The approach used in downstream is usually defined by the qcom,mdss-dsi-bl-pmic-control-type property in the panel node of the downstream device tree:

  • Pulse-width modulation (PWM) (bl_ctrl_pwm): In this case there is an external PWM generator that generates a PWM signal for the display. PM8916 (the PMIC of MSM8916) contains one PWM generator that can be configured for the display. arm64: dts: msm8916-longcheer-l8150: Add PWM backlight support is an example of how to set it up.
  • MIPI DCS commands (bl_ctrl_dcs): In this case the panel controller handles the brightness automatically. The generated panel driver should implement brightness without further changes.
  • Custom: Some devices (e.g. Samsung AMOLED panels) have bl_ctrl_dcs or something else listed, but actually use an entirely custom way to control display brightness. This needs to be implemented manually in the panel driver (or using an extra backlight driver).


The touchscreen is usually connected to an I²C bus, e.g. blsp_i2c5. Often it is either supplied by some PM8916 regulators or a set of fixed regulators. (This is often visible when the touchscreen has some "enable" GPIOs listed downstream; those usually represent a chip that can be enabled through a GPIO and then supplies a fixed voltage to the touchscreen...) This is where similarities end. There can be all sorts of touchscreens connected to the I²C bus and manufacturers like to use many different ones. Look through the supported touchscreen in mainline to see if a driver exists already, or can be modified eventually to support another version of a similar chip. Otherwise, you may need to write a simple driver yourself.

Note: if you are really unlucky (this is probably quite unlikely for a device with MSM8916), you may have a so-called incell touchscreen, that is, a touchscreen that is integrated into the panel. That is not necessarily an issue, but sadly these often have some integration with the display (e.g being configured using dsi commands), that is probably quite ugly and doesn't as of yet have a solution in mainline. If you suspect this to be the case, don't hesitate to ask for help in the chat!


Like the touchscreen, sensors are usually connected to an I²C bus, e.g. blsp_i2c2 and usually supplied by L17. This is where similarities end. There can be all sorts of sensors connected to the I²C bus and manufacturers like to use many different ones. Look through the supported sensors in mainline to see if a driver exists already, or can be modified eventually to support another version of a similar chip. Otherwise, you may need to write a simple driver yourself.

Some devices have a Hall effect sensor that is used to detect if the screen is covered by some kind of flip cover. It is usually a simple GPIO like the buttons, and can be set up easily using gpio-keys.


  • The sensor should show up in sysfs (/sys/bus/iio/devices/...). Usually there are some in_*_raw files you can read for initial testing.
  • Accelerometer: The accelerometer is used for automatic screen rotation, so let's make sure that works correctly.
    • Install iio-sensor-proxy (note: need to restart after installing it), start it (rc-service iio-sensor-proxy start) and use monitor-sensor.
    • Check if the orientations are reported correctly. See File:PhoneRotation.png for a visualization of the different orientations.
    • If the orientations are not correct you need a mount-matrix in your device tree. See e.g. [this explanation].
  • Interrupts: If your sensor has interrupts listed downstream you should also add them on mainline and check if they work correctly.
    • Install linux-tools-iio, then use sudo iio_generic_buffer -a -c -1 -N <num> where <num> is the number of the IIO device in sysfs (e.g. /sys/bus/iio/devices/iio:deviceN.
    • It should continuously print weird values.
    • Check /proc/interrupts if some interrupts were triggered for the sensor.


Audio functionality is split up in 3 main blocks (4 when using the modem):

  • LPASS (Low Power Audio Subsystem): The "sound card". Reads/writes digital audio data from/to memory and transmits it via I²S.
  • Digital Codec (msm8916-wcd-digital): Accepts digital audio via I²S, post-processes it (e.g. to change volume). Transmits it via PDM to analog codec. (A proprietary Qualcomm protocol but perhaps there are similiarities to Pulse-density modulation...)
  • Analog Codec (msm8916-wcd-analog): Integrated into PMIC (PM8916). Contains DACs (playback) and ADCs (capture) and amplifiers for Speaker, Earpiece and Headphones.
  • Audio DSP (qdsp6): Can optionally sit between CPU and LPASS to offload audio decoding (e.g. MP3, AC, ...). Also handles voice call audio.

Here is diagram how the blocks relate to each other:

|                   MSM8916               GPIO|              Some devices use       
|                                             |              a custom speaker       
|              +--------+                     |Quaternary        amplifier!         
| +-------+    | Modem  |    +-------+        | MI²S +-------------+                
| |       |<-->| (ADSP) |<-->|       |<--> 112|<---->| Digital     |                
| | Linux |    +--------+    | LPASS |<--> 117|<---->| Audio Codec |--> Speaker     
| | (CPU) |      Memory      |       |<--> 118|<---->| + Amplifier |                
| |       |<---------------->|       |<--> 119|<---->+-------------+                
| +-------+                  +-------+        |                                     
|                      Primary |   ^ Tertiary |    +---------------+                
|                        MI²S  |   |   MI²S   |    |    PM8916     |                
|                              v   |          |PDM |               |                
|                            +-------+<---> 63|<-->|CLK  +--------+|                
|                            |       |<---> 64|<-->|SYNC |        ||<-- Microphone 1
|                            |Digital|<---> 65|<---|TX   | Analog ||<-- Microphone 2
|                            | Codec |<---> 66|--->|RX0  | Codec  ||--> Earpiece    
|                            |       |<---> 67|--->|RX1  |        ||--> Headphones  
|                            +-------+<---> 68|--->|RX2  +--------+|--> Speaker     
+---------------------------------------------+    +---------------+                

Note: MI²S (multichannel I²S) is basically I²S with multiple data lines. This allows using more audio channels, although this is typically not used on MSM8916.

TODO For now, check related commits for other devices and ask on Matrix if you have any questions :)

Custom Speaker Amplifier

The analog codec in PM8916 contains a mono Class-D (audio) amplifier. However, there are several devices that do not make use of it. Perhaps because the manufacturer thought it's not powerful enough, or because they wanted to include stereo speakers. In that case they have likely included a custom speaker amplifier. There are two types of this:

  • Digital codec + speaker amplifier: This can be seen in the diagram above. It's connected via the Quaternary MI²S interface and contains it's own digital-to-analog converter (DAC).
    • Sometimes they are just enabled with a GPIO, sometimes (especially TFA* amplifiers from NXP/Goodix) they are controlled via I²S and may even include their own audio DSP.
    • A typical property of these is that it's difficult to control the audio volume in hardware. Instead, the digital audio data needs to be adjusted in software (typically handled by PulseAudio.)
    • You need to add the amplifier to the device tree and then set up a DAI link for Quaternary MI²S.
      • Example with enable GPIO: arm64: dts: qcom: apq8016-samsung-matissevewifi: add sound
      • Example with I²C: arm64: dts: msm8916-samsung-a2015: Add sound card (Note: You typically need a driver specific to your amplifier in this case...)
  • Analog speaker amplifier: This one sits behind the analog codec in PM8916 and just further amplifies the (analog) output signal. The speaker output is not suitable for this so most devices have the speaker amplifier connected additionally to one of the headphones output, i.e. HPH_L (left channel) or HPH_R (right channel). Typically they are activated using a GPIO. Sometimes there is an extra switch for the headphones as well, to make sure they don't activate when we just want to activate the speaker.
    • You need to add the amplifier to the device tree (and eventually fixed regulators required for it). Then add it as aux-devs to the sound card and set up audio-routing.
      • Example with amplifier connected to HPH_R and extra headphones switch: arm64: dts: qcom: msm8916-wingtech-wt88047: Add sound
      • Example with special amplifier connected to HPH_R without headphones switch: arm64: dts: qcom: msm8916-bq-paella: Add sound


Please try to test as many of the features below as possible. If you don't have headphones or a headset you can obviously not test that.

  • Speaker/Earpiece/Headphones audio playback
  • Mic1/Mic2/Headset audio recording
    • Note: Some devices only have one integrated microphone.
    • Enable microphone via ALSA UCM / PulseAudio, then use (for example): arecord -f dat -Dplughw:0,1 test.wav, then aplay test.wav and check if you hear something.
  • Audio jack detection
    • Plug in/out headphones and check for SW_HEADPHONE_INSERT in evtest. It should be 1 when inserted, 0 otherwise. (Make sure it's not inverted!)
    • Plug in/out headset and check for SW_MICROPHONE_INSERT additionally.
      • If the headset has buttons, try pressing them and see if they show up in evtest.


TODO For now, check related commits for other devices and ask on Matrix if you have any questions :)


TODO Ping Mis012 on Matrix!


Although PM8916 provides a linear charger for batteries only few devices actually make use of it. This might be because it is comparatively inefficient compared to a switching mode battery charger. Therefore most devices actually have custom chips that manage the battery and charging. Usually it is separated in:

  • Fuel gauge: Measures the remaining charge of the battery.
  • Charger: Handles charging the battery.

Similar to the sensors, battery/charger are usually connected to an I²C bus. Look through the supported drivers in mainline (drivers/power/supply) to see if a driver exists already, or can be modified eventually to support another version of a similar chip. Compare with the downstream driver just to be sure.

Warning: Incorrect usage of battery/charger drivers might cause the battery to malfunction. In worst case, incorrect charging of batteries might even cause the battery to explode. Be careful.


The PMIC used together with MSM8916.
Bus Access Manager, some kind of DMA (Direct Memory Access) engine. Basically it can copy data from memory to devices (e.g. UART, USB, ...) more quickly.
BAM Data Mux, a network protocol built using BAM that is used as a network interface to the modem.
"BAM-enabled low-speed peripheral". Controllers for UART, I²C and SPI.
Low-power audio subsystem
Mobile Display Subsystem - the hardware that manages the display.
Mobile Display Processor - Part of MDSS that manages display panels.
MIPI Display Serial Interface (DSI) - used for communication with display panel.
Camera Subsystem
System Power Management Interface - used for communication between MSM8916 and PM8916.

Remote Processors

Wireless Connectivity Subsystem (WCNSS). Provides WiFi and Bluetooth.
Provides hardware-accelerated video encoding/decoding.
Provides mobile connectivity and Audio DSP (ADSP). Note: On MSM8916, modem and ADSP are one remote processor, but the ADSP is usually separate on Qualcomm SoCs.
Microarchitecture for DSPs, used for ADSP/Modem/Sensor Hub/...


  • arm64: dts: apq8016-sbc: Increase load on l11 for SDCARD
  • [PATCH v2 0/9] Qualcomm WCNSS remoteproc (patch on Linux mainline mailing list)
  • drivers/net/wireless/wcnss/wcnss_vreg.c, line 366 (downstream kernel code)