Jump to content

MSM8227 Mainlining

From postmarketOS Wiki

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 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

Note 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

Note 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 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 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.

Note 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 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 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 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 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.