MSM8227 Mainlining
The Qualcomm MSM8227 is a mid-range SoC from 2012. It's similar to the MSM8930 and MSM8627, so depending on the context, MSM8227 can also mean MSM8930.
Status
The following features provided by the MSM8227 SoC are supported and should work on most MSM8227-based devices after the device tree has been set up:
- UART
- USB
- Internal/External Storage (eMMC/SD card) (SD card may or may not work depending on device)
- Power, volume up/down buttons
Certain components (e.g. touchscreen, sensors, ...) are device-specific. With a bit of luck, some will be already supported by mainline, others won't.
Overview
linux-msm8227
The close-to-mainline Linux kernel fork (currently more like bringup playground) which consists of APQ8064, flto's and Konrad's work.
msm8916-mainline/lk2nd
The reference bootloader provided by Qualcomm for MSM8960 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 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 MSM8960 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 a small offset into the boot partition. |
Getting Started
Requirements
- MSM8227-based device
- 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. MSM8227 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 MSM8227.
If you have any questions, ask @wctrl:matrix.org in matrix.
Preparations
Some code in downstream only applies to MSM8930 or MSM8227. The MSM8227 probably won't be as a separate SoC, it will be MSM8627 instead. To be sure, run dmesg | grep Machine
.
Your device most likely either the CDP or MTP or FLUID board. Use the note above to determine exact board. When adding device tree nodes, don't forget to look in the _devices
array, prefixed with board type
Warning: Downstream may refer to MSM8960 devices in board files for MSM8930, be careful when taking values from other SoC (although MSM8960 is quite similar to MSM8930/MSM8227, so some values are the same) |
lk2nd
Note: lk2nd is not strictly required to boot mainline. However, certain features (e.g. WiFi, BT, secondary CPU cores, ...) 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.
Try building it. Make sure you are on the experimental-tmp branch.
Build Kernel
Go back to your linux clone. Try to build it with envkernel.sh:
$ cd path/to/kernel $ source path/to/pmbootstrap/helpers/envkernel.sh # Initialize kernel configuration # Replace <cores> with the number of cores you'd like to use for compilation $ make qcom_msm8227_defconfig -j<cores> # Compile kernel $ make -j<cores>
Otherwise, you can build with LLVM (e.g. using toolchain with PGO, which should be faster than gcc):
$ make ARCH=arm LLVM=1 qcom_msm8227_defconfig -j<cores> $ make ARCH=arm LLVM=1 -j<cores>
After the device tree has been set up, you may want fast incremental builds, install ccache
and append CC="ccache clang"
Initrd
The initramfs generated by pmbootstrap is probably not suitable for debugging. You can of course just use busybox, but to include more utilities like irqtop
it would be better to use a builder like buildroot. You can use my config (use first revision!, glibc doesn't play well with downstream) or make your own, it should work with both mainline and downstream kernels.
Note: You want a very small initramfs, because usually boot.img cannot exceed 20MB on old devices (remember that the kernel takes up space too). You may want to use LZMA for compression, which can compress about 3-5 times, but it takes a long time to decompress and requires more RAM. |
Booting kernel
Chances are you'd like to test the changes faster, so you need to build the boot.img yourself rather updating package. Since the kernel needs the device tree to work properly, you need to append it to the kernel, and package it into android boot image with your device parameters (you can get them from the device package). So your loop will boil down to:
$ make ... $ cat arch/arm/boot/zImage arch/arm/boot/dts/qcom/YOUR-DT-HERE.dtb > kernel.bin # This may be different for your device $ mkbootimg -o boot.img --pagesize 4096 --base 0x80200000 --kernel_offset 0x00008000 --ramdisk_offset 0x02000000 --second_offset 0x00f00000 --tags_offset 0x00000100 --kernel kernel.bin --ramdisk /path/to/initrd --cmdline "console=ttyMSM0,115200,n8 earlycon" # On the device after you test stuff $ reboot
When you want to boot the downstream, take cmdline from device package or use panic=3 console=ttyHSL0,115200,n8 androidboot.hardware=qcom user_debug=23 msm_rtb.filter=0x3F ehci-hcd.park=3 androidboot.bootdevice=msm_sdcc.1
.
Warning: The downstream kernel uses ttyHSL0 for console, however this is wrong on mainline, don't repeat my mistakes and use ttyMSM0 |
Initial device tree
Fortunately, the following skeleton is enough for UART:
// SPDX-License-Identifier: GPL-2.0-only
#include "qcom-msm8930.dtsi"
#include "pm8038.dtsi"
/ {
model = "Model"; // FIXME
compatible = "vendor,codename", "qcom,msm8930", "qcom,msm8227"; // FIXME
aliases {
serial0 = &gsbi5_serial;
};
chosen {
stdout-path = "serial0:115200n8";
};
};
&gsbi5 {
qcom,mode = <GSBI_PROT_I2C_UART>;
status = "okay";
};
&gsbi5_serial {
status = "okay";
};
Regulators
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 |
Before you add something else, you need the regulators. In the downstream kernel, look for arch/arm/mach-msm/board-8930-regulator-pm8038.c
, here are the values of the regulators themselves and what they supply.
Note: MSM8930 ships with PM8038 or PM8917 PMIC, but only the PM8038 is supported now |
Find something similar to RPM_SMPS(S1, 0, 1, 1, 500000, 1150000, NULL, 100000, 4p80, AUTO, LPM)
and copy values like in this example:
&rpm {
regulators {
compatible = "qcom,rpm-pm8038-regulators";
/* SMPS */
pm8038_s1: s1 {
regulator-always-on;
regulator-min-microvolt = <500000>;
regulator-max-microvolt = <1150000>;
qcom,switch-mode-frequency = <4800000>;
};
// s2, s3, ..., l1, l2, ..., lvs1, lvs2, ...
}
};
Note: You don't need to add s5 and s6 , they're defined in SoC tree. You may also notice that some regulators use other regulators for power supply. Use qcom-msm8227-nokia-fame.dts as an example. |
USB
Usually you won't find a regulator with an exact voltage of 3.3V, instead you should use 3.075V. In most cases, all you need to do is set the supply regulators and select USB OTG mode:
&usb_hs1_phy {
v3p3-supply = <&pm8038_l3>;
v1p8-supply = <&pm8038_l4>;
};
&usb1 {
dr_mode = "otg";
status = "okay";
};
Note: You can check the USB connection by looking at your PC's dmesg |
eMMC
Adding regulators may be enough:
&sdcc1 {
vmmc-supply = <&pm8038_l5>; // FIXME: change for your device
vqmmc-supply = <&pm8038_l11>; // FIXME: change for your device
status = "okay";
};
However, pinctrl is recommended. Open board-8930-storage.c
and find the pad data structures. They're quite simple, so the device tree equivalent should look like this:
&msmgpio {
sdcc1_pins_active: sdcc1-pins-active {
clk {
pins = "sdc1_clk";
drive-strengh = <16>;
bias-disable;
};
cmd {
pins = "sdc1_cmd";
drive-strengh = <10>;
bias-pull-up;
};
data {
pins = "sdc1_data";
drive-strengh = <10>;
bias-pull-up;
};
};
sdcc1_pins_sleep: sdcc1-pins-sleep {
clk {
pins = "sdc1_clk";
drive-strengh = <2>;
bias-disable;
};
cmd {
pins = "sdc1_cmd";
drive-strengh = <2>;
bias-pull-up;
};
data {
pins = "sdc1_data";
drive-strengh = <2>;
bias-pull-up;
};
};
};
&sdcc1 {
vmmc-supply = <&pm8038_l5>; // FIXME
vqmmc-supply = <&pm8038_l11>; // FIXME
pinctrl-0 = <&sdcc1_pins_active>;
pinctrl-1 = <&sdcc1_pins_sleep>
pinctrl-names = "default", "sleep";
status = "okay";
};
You also may need set the values for eMMC and I/O regulators. Copy values from that file:
&sdcc1 {
vmmc-supply = <&pm8038_l5>; // FIXME
vqmmc-supply = <&pm8038_l11>; // FIXME
qcom,vdd-voltage-level = <2950000 2950000>;
qcom,vdd-current_level = <9000 200000>;
qcom,vdd-always-on;
qcom,vdd-lpm_sup;
qcom,vdd-io-voltage-level = <1800000 1800000>;
qcom,vdd-io-current_level = <0 200000>; // no lpm for vdd-io
qcom,vdd-io-always-on;
pinctrl-0 = <&sdcc1_pins_active>;
pinctrl-1 = <&sdcc1_pins_sleep>
pinctrl-names = "default", "sleep";
status = "okay";
};
GPIO keys
Look for keys_8930_pm8038
in board-8930.c
. Add following skeleton to your root node and change values:
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&gpio_keys_pin_a>; // defined in pm8038.dtsi
key-camera-focus {
label = "camera_focus";
gpios = <&pm8038_gpio 11 GPIO_ACTIVE_LOW>; // FIXME
linux,input-type = <1>;
linux,code = <KEY_CAMERA_FOCUS>;
};
key-camera-snapshot {
label = "camera_snapshot";
gpios = <&pm8038_gpio 10 GPIO_ACTIVE_LOW>; // FIXME
linux,input-type = <1>;
linux,code = <KEY_CAMERA>;
};
key-volume-down {
label = "volume_down";
gpios = <&pm8038_gpio 8 GPIO_ACTIVE_LOW>; // FIXME
linux,input-type = <1>;
linux,code = <KEY_VOLUMEDOWN>;
};
key-volume-up {
label = "volume_up";
gpios = <&pm8038_gpio 3 GPIO_ACTIVE_LOW>; // FIXME
linux,input-type = <1>;
linux,code = <KEY_VOLUMEUP>;
};
};
The power button should work without any configuration.
Troubleshooting
Device doesn't boot after small change in device tree or complains about bad initrd
This is probably due to incorrect memory ranges. Currently, we don't rely on lk2nd that much, so you can try booting from original bootloader (if possible), kernel should apply atags information and fix it. Otherwise parse atags to determine memory ranges and add memory node to the root node:
memory@80000000 {
device_type = "memory";
reg = <0x80200000 0x8c00000 0x90000000 0x10000000>; // FIXME
};
The above example is equivalent to:
Mem: 0x80200000 - 0x88e00000 (size 146800640) Mem: 0x90000000 - 0xa0000000 (size 268435456)
Remember to apply lk2nd hack or the bootloader will override the values.