MSM8996 Mainlining

From postmarketOS
Note This page is WIP.

MSM8996/APQ8096 (Snapdragon 820/820E), as well as MSM8996SG/APQ8096SG (Snapdragon 821, also known as MSM8996 Pro) are Qualcomm SoCs with good support in the mainline Linux kernel. Getting it to boot on them generally does not require a lot of effort.


  • CPU: SMP (bring up secondary CPU cores), CPU frequency scaling, CPU idle
  • Storage: eMMC, SD cards, UFS, ...
  • Video: Hardware-accelerated video codec
  • Modem: Calls, SMS, Mobile data
SoC Arch Year CPU UART Storage USB Display GPU Pinctrl I²C Audio Video Thermal Modem Camera
Qualcomm MSM8996 aarch64 2016 P Y P Y Y Y Y Y Y Y Y P P

First Steps


SoC Common Kernel

This is a close to mainline fork which is used as a staging area for new devices and code. It has additional patches including fixes for some as well as new drivers that aren't ready for upstream, or haven't been merged there yet. Start off by cloning this kernel tree:

git clone

Compiling the kernel

While it is possible to use envkernel to aid in compiling and packaging the kernel, it often causes several issues and can be cumbersome to deal with. It is generally easier to manage editing and compiling the kernel, as well as building boot images entirely outside of the scope of pmbootstrap.

Linux uses the GNU Make build system. Usually compiling it would just require running `make`, but since the build machine likely would have a different architecture, some variables will have to be changed to cross-compile for arm64 instead:

  • CROSS_COMPILE: This holds a prefix that will be appended to the build tools (gcc, ld, as, ...). This could be aarch64-linux-gnu- or aarch64-linux-musl- depending on the host distribution and the toolchains packaged for it. aarch64-linux-musl- will be used going forward since it is used in Alpine Linux, the base distribution of postmarketOS.
  • CC: C compiler to use for compiling the kernel. Should be aarch64-linux-musl-gcc
  • HOSTCC: C compiler to use for compiling any needed build tools that will run on the host while compiling the kernel. Should be gcc.

In order to keep the source tree clean, it would be better to place output files in a separate directory. To do this, the O variable can be used. Finally, the -j parameter can be set to use multiple threads for compiling, making the compilation process faster.

Putting everything together results in this command, which will be used for compiling as well as any other make targets:

make -j$(nproc) CROSS_COMPILE=aarch64-linux-musl- CC=aarch64-linux-musl-gcc HOSTCC=gcc O=.output

Compiling Linux requires configuring it first, something that can be done using a configuration file. Each architecture has some configuration files in the kernel source tree under arch/*/configs. For the arm64 architecture, a single default configuration (`defconfig`) that tries to cover most devices of this architecture is available. This file however has many components enabled that are unneeded on MSM8996 devices, resulting in increased compilation time. It is better to use the configuration file included in the linux-postmarketos-qcom-msm8996 package, which has been optimized for MSM8996 devices.

To apply the configuration, copy the file to arch/arm64/configs, rename it to msm8996_defconfig, then run:

make -j$(nproc) CROSS_COMPILE=aarch64-linux-musl- CC=aarch64-linux-musl-gcc HOSTCC=gcc O=.output msm8996_defconfig

Once done, the previous command can be used to compile the kernel. Resulting files will be found in .output

Icon TODO: Add packaging and boot image creation instructions


Downstream DTS

This will probably be the most useful reference material. You should be able to find it in the downstream kernel somewhere in arch/arm/boot/dts/qcom, or arch/arm64/boot/dts/qcom which should be symlinked to the previous path. A DTB can also be extracted from the device in runtime from /sys/firmware/fdt, then decompiled with DTC:

dtc -I dtb -O dts fdt > fdt.dts

This however will be stripped of any comments or labels, so it makes finding things a bit harder, but you can be certain that this is the correct DTS for the device variant you have.


Having board schematics will help confirm connections between components, and especially regulators which can sometimes not be clear in downstream DTS. It can also help find UART pads or ports.

Downstream Kernel

This will help when writing drivers. The downstream drivers can be used as reference.

DTS of Other Mainlined Devices

There's a good chance that your device shares some hardware with other mainlined devices. Usually that's the case for WiFi/Bluetooth and Audio. It can help to take a look in device trees of other mainlined devices to see how things are done, or even copy similar parts.

Initial Device Tree

Every device requires a device tree that describes its hardware. Linux reads the device tree to determine what hardware is available, how it's connected, and what drivers are needed to operate it. Device trees in the kernel source tree can be found in arch/*/boot/dts. For MSM8996 devices, device trees are placed in arch/arm64/boot/dts/qcom.

Creating the DTS

Create a new file there named msm8996-<vendor>-<codename>.dts using this template:

// SPDX-License-Identifier: BSD-3-Clause

#include "msm8996.dtsi"
#include "pm8994.dtsi"
#include "pmi8994.dtsi"

/ {
	model = "Device Name";
	compatible = "vendor,codename", "qcom,msm8996";

Then add it to the Makefile in the same directory, respecting alphabetical order:

dtb-$(CONFIG_ARCH_QCOM)	+= msm8996-vendor-codename.dtb

Adding qcom,*-id

The bootloader on devices with Qualcomm SoCs uses a set of device tree properties to pick a suitable DTB for the device variant it's running on:

  • qcom,msm-id
  • qcom,pmic-id
  • qcom,board-id

This allows vendors to pack multiple DTBs for several variants of their device into a single image and use it for all of them, instead of having to create an image for each variant. Setting those properties is necessary to make the bootloader pick our device tree. You should be able to find those in the downstream DTS. Once you find them, put them in the root node:

/ {
	model = "Device Name";
	compatible = "vendor,codename", "qcom,msm8996";
	qcom,msm-id = <0x131 0x10000>;
	qcom,pmic-id = <0x20009 0x10013 0 0>;
	qcom,board-id = <34 0>;

This device tree should be enough to boot, but so far there is no way of getting any sort of feedback.

Getting Kernel Output

Getting any kind of output from the kernel is necessary to confirm that it boots, and to know if anything is not working as expected. There are a few methods to get kernel logs:

Simple Framebuffer

Framebuffer console with simplefb on Xiaomi Mi Note 2

Perhaps the easiest one to get working, provided you have suitable hardware and a nice bootloader.

It's a fbdev driver that makes use of the framebuffer configured by the bootloader for boot splash. We can use it to draw a console on the screen with little effort. All it takes is to know the memory region where the bootloader configures the framebuffer, and fortunately that should be the same across all MSM8996 devices. Unfortunately however, it usually only works on video-mode panels and doesn't work on command-mode panels due to many bootloaders not enabling auto-refresh in MDP. To find out what type of panel you have, check the downstream cmdline (on Android or TWRP for example). This example is taken from a Xiaomi Mi Note 2:

# cat /proc/cmdline
sched_enable_hmp=1 sched_enable_power_aware=1 app_setting.use_32bit_app_setting_pro=1 kpti=1 androidboot.hardware=qcom ehci-hcd.park=3 lpm_levels.sleep_disabled=1 cma=32M@0-0xffffffff loop.max_part=7 buildvariant=userdebug androidboot.bootdevice=624000.ufshc androidboot.verifiedbootstate=orange androidboot.veritymode=eio androidboot.keymaster=1 androidboot.serialno=91000201 androidboot.secureboot=1 androidboot.hwversion=4.5.0 androidboot.baseband=msm mdss_mdp.panel=1:dsi:0:qcom,mdss_dsi_lgd_sw43101_fhd_video:1:none:cfg:single_dsi fpsimd.fpsimd_settings=0 app_setting.use_app_setting=0

This argument is what we're looking for:


In this case the panel name is qcom,mdss_dsi_lgd_sw43101_fhd_video. What's important here is video at the end of the name. This tells us that this panel is video-mode. Command-mode panels would have cmd in the panel name.

Note It might still be worth it to try enabling a simple framebuffer on a device with a command-mode panel, so one should proceed here even if the suffix found is cmd.

Once you confirm that you have a suitable panel, you can proceed with enabling simplefb. You just have to add these nodes to the root node of your device tree, replacing height and width with the dimensions of your panel:

/ {
	chosen {
		#address-cells = <2>;
		#size-cells = <2>;

		framebuffer0: framebuffer@83401000 {
			compatible = "simple-framebuffer";
			reg = <0x00 0x83401000 0x00 (height * width * 3)>;
			width = <width>;
			height = <height>;
			stride = <(height * 3)>;
			format = "r8g8b8";

	reserved-memory {
		cont_splash_mem: memory@83401000 {
			reg = <0x0 0x83401000 0x0 (height * width * 3)>;

Once done, build and flash/boot the kernel. If all went well, you should get some penguins on your screen!


Icon TODO: Add info about enabling UART output


  • Some discussion about mainline status on this platform occurs in the chat room on Matrix