Jump to content

Qualcomm S4/Krait 200 mainlining

From postmarketOS Wiki
(Redirected from MSM8960 Mainlining)

This page covers the Qualcomm MSM8960/APQ8064 (Snapdragon S4 series) — a high-end smartphone SoC and the Qualcomm MSM8227 with its' siblings — a mid-range SoCs from 2012.

Status

The following features, which are provided by the SoC, are supported in the mainline and should work on most devices once 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

APQ8064/MSM8960

Currently, most of the work is done directly in the mainline Linux. We have an external tree that holds patches submitted to LKML, allowing others to share progress in one place. Please do not submit patches to this repository unless they have been submitted to LKML, notify LogicalErzor of the LKML ID to include it in this repository.

MSM8227/MSM8930

WIP kernel which consists of APQ8064, flto's and Konrad's work.

Getting Started

Requirements

  • S4 family-based device
  • UART (for minimal kernel, once the USB works it's not required)
  • (Downstream) Linux kernel source for your device provided by vendor
  • Basic knowledge about Linux, Git, C, Device Trees, ...
  • Willingness to learn about "mainlining" and to figure out things on your own. Please see Mainlining Guide

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. S4 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 S4 family.

If you have any questions, ask @wctrl:matrix.org about MSM8227/MSM8930.

Please also join the APQ8064/MSM8960 specific channel on Matrix.

Preparations

Board type

To prevent issues in the future, you need to know the type of board, run dmesg | grep Machine on downstream kernel. 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

Warning WARNING: lk2nd has issue on MSM8227/MSM8930 with boot.img larger than 20MB
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.

lk2nd does not require any device-specific code. Normally, it should just run out of the box on your device.

Grab the latest release (20.0 or higher is required) and flash. Buttons won't work, but just simply add that (look at commit history to see how others have done it).

Reserved memory

Since some things (such as TrustZone) allocate some memory to themselves and can cause unexplained problems during the bool process, it is highly recommended to add reserved memory.

To figure out what your reserved memories are, first install lk2nd onto your device and then run fastboot oem parsed-tags && fastboot get_staged atags.bin. You should now have a file called atags.bin

Next, make a file called atags.c in path/to/linux/arch/arm/include/uapi/asm/ and put the following code inside

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "setup.h"

void parse_tag(const struct tag *t) {
        switch (t->hdr.tag) {
        case ATAG_MEM:
                printf("Mem: 0x%x - 0x%x (size %d)\n", t->u.mem.start, t->u.mem.start + t->u.mem.size, t->u.mem.size);
                break;
        case ATAG_INITRD2:
                printf("Initrd: 0x%x - 0x%x (size: %d)\n", t->u.initrd.start, t->u.initrd.start + t->u.initrd.size, t->u.initrd.size);
                break;
        case ATAG_SERIAL:
                printf("Serial: %08x%08x\n", t->u.serialnr.high, t->u.serialnr.low);
                break;
        case ATAG_REVISION:
                printf("Revision: 0x%x\n", t->u.revision.rev);
                break;
        case ATAG_CMDLINE:
                printf("Cmdline: %s\n", t->u.cmdline.cmdline);
                break;
        case ATAG_CORE:
        case ATAG_NONE:
                break; // Ignore
        default:
                printf("Unknown: 0x%x\n", t->hdr.tag);
        }
}

int main(int argc, char* argv[]) {
    FILE *f = fopen(argv[1], "rb");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);

    struct tag *t = malloc(fsize);
    fread(t, fsize, 1, f);
    fclose(f);

    if (t->hdr.tag != ATAG_CORE) {
            printf("Fail: 0x%x\n", t->hdr.tag);
            return 1;
    }

    for (; t->hdr.size; t = tag_next(t)) {
            parse_tag(t);
    }
    return 0;
}

Compile that c file with cc atags.c -o atags and run ./atags atags.bin. It should give you something like the following:

Revision: 0x8
Serial: 0000b1c10000f58e
Mem: 0x80200000 - 0x88d00000 (size 145752064)
Mem: 0x90000000 - 0xc0000000 (size 805306368)
Cmdline: lk2nd ...

Initial devicetree

Unfortunately, as the S4 platform is outdated, many of the downstream kernels use board files instead of device trees. That means you will likely have to manually traverse these board files to make a proper device tree.

Let's add qcom-msm*soc*-*vendor*-*codename*.dts to arch/arm/boot/dts/qcom with UART node:

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

// For MSM8227/8930:
#include "qcom-msm8930.dtsi"
#include "pm8038.dtsi"

// For MSM8960:
#include "qcom-msm8960.dtsi"
#include "pm8921.dtsi"

// For APQ8064:
#include "qcom-apq8064.dtsi"
#include "pm8821.dtsi"
#include "pm8921.dtsi"

/ {
	model = "Model"; // FIXME

    // add "qcom,apq8064" at the end for the APQ8064,
    // "qcom,msm8960" for MSM8960,
    // "qcom,msm8930" for MSM8930
    // and "qcom,msm8930", "qcom,msm8227" if it's MSM8227
	compatible = "vendor,codename"; // FIXME

	aliases {
		serial0 = &gsbi5_serial;
	};

	chosen {
		stdout-path = "serial0:115200n8";
	};
};

&gsbi5 {
	qcom,mode = <GSBI_PROT_I2C_UART>;
	status = "okay";
};

&gsbi5_serial {
	status = "okay";
};
Note Don't forget to remove unnecessary include directives

Now add qcom-msm*soc*-*vendor*-*codename*.dtb to arch/arm/boot/dts/qcom/Makefile in alphabetical order.

Warning WARNING: If your board based on EVT, the GSBI8 should be used for UART instead

The devicetree should look like this:

// SPDX-License-Identifier: GPL-2.0-only
#include "qcom-msm8930.dtsi"
#include "pm8038.dtsi"

/ {
	model = "Sony Xperia M";
	compatible = "sony,nicki", "qcom,msm8930", "qcom,msm8227";

	aliases {
		serial0 = &gsbi5_serial;
	};

	chosen {
		stdout-path = "serial0:115200n8";
	};
};

&gsbi5 {
	qcom,mode = <GSBI_PROT_I2C_UART>;
	status = "okay";
};

&gsbi5_serial {
	status = "okay";
};

Build Kernel

Go back to your kernel tree. Try to build it with envkernel.sh:

$ cd path/to/kernel
$ source path/to/pmbootstrap/helpers/envkernel.sh

# Initialize kernel configuration:
# qcom_msm8227_defconfig for MSM8227/MSM8930,
# qcom_apq8064_defconfig for APQ8064/MSM8960
# Replace <cores> with the number of cores you'd like to use for compilation
$ make <defconfig> -j<cores>

# Compile kernel
$ make -j<cores>

For APQ8064/MSM8960 you can build device package instead (only if downstream could boot pmos, bad for quick local development): pmbootstrap build --envkernel linux-next

Otherwise, you can build with LLVM (e.g. using toolchain with PGO, which should be faster than gcc):

$ make ARCH=arm LLVM=1 <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 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 or make your own, it should work with both mainline and downstream kernels.

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, example). 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 console=ttyHSL0,115200.

Note The downstream kernel uses ttyHSL0 for console, however this is wrong on mainline, don't repeat my mistakes and use ttyMSM0

Devicetree setup

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.

Warning WARNING: 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 is 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";
};

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.

Mainline trees and configs

May help with certain devices