APS2 Mainlining Linux笔记

本文最后更新于 2025年10月7日 下午

本人不对因参考这篇笔记产生的任何硬件、软件问题负责,任何问题
应由使用者个人承担。使用本文章中的方法或资源即代表你同意本条款。

前言

感谢ayaneo公司提供的APS2样机和支持。APS2是Pocket S的二代产品,采用高通G3 Gen3平台,根据现有资料推测为SM8650换皮版本,因此可以共享SM8650主线Linux平台驱动。
可以参考Linaro的主线驱动适配情况列表Qualcomm mainline status了解其他平台的驱动情况。
24.07.21截取的图片

适配笔记

记录一下开发过程,反正闲着也是闲着,写点内容说不定有人看呢。

UEFI 适配

前些年已经在本地为Aloha UEFI适配了SM8650平台可从ABL启动的二阶段UEFI,相同平台的话,不同设备只需要手动给驱动应用一些patch即可使用,因此只花了不到一天的时间就为APS2适配好了UEFI。
但是由于Aloha UEFI目前不考虑合并SM8650平台,所以相关代码不会合并到主仓库中,目前来看UEFI基本功能正常,USB(UFP)、UFS均可用。
UEFI默认加载界面FFULoader
由于UEFI开发不是重点,且ABL也可以直接启动Linux,所以这里就不详细展开说移植过程了。这两个我都用过,我觉得不存在孰好孰坏一说,有的平台UEFI方便,有的平台ABL方便。

安卓/Linux多启动

关于安卓和Linux的多启动问题,由于DualbootKernelPatcher(简称DBKP)基本上只能检测接SOC的GPIO状态从而区分不同系统启动,例如OP7系列和XiaomiPad5, 一个是检测TriKey的GPIO电平状态,一个是检测保护套盖子的霍尔传感器的GPIO状态。
之前APS1的Turbo按键也是挂在GPIO上的,所以可以通过读取状态来区分启动安卓还是Linux,但是APS1之后的设备这个Turbo按键(还有除了开关音量键的所有按键)都是接到手柄控制器上面的,走USB->PCIe->SOC被系统读取,那我肯定不会就为了读取一个GPIO状态在裸机环境把又是USB又是PCIe的好几套协议栈和驱动搭起来,所以基于DBKP的多启动在APS2/DMG/ACE上不可用。那多启动就真的不行了?

  • 一个思路是完善DBKP,添加GUI和音量键选择功能,驱动SPMI相对简单。
  • 一个思路是完全从UEFI启动,安卓内核和Linux内核直接在Grub中选择启动,更简单和容易实现。
  • 一个思路是,即然设备是Unfused,那直接改个ABL在里面加上一个启动到Linux选项不就完事了。
    众所周知,高通的ABL是开放源代码的,直接编译签名一下就可以运行了,我这里打算结合高通最近开源的abl2esp,替换原来的ABL,启动ESP分区的GRUB之后,通过切换启动项选择启动Linux or LinuxLoader,efi。

abl2esp是一个基于RUST实现的引导程序加载器(应该是干了之前BDS干的活),从所有可能的ESP分区找bootaa64.efi加载,利用这个程序可以加载grub或者linux,或者其他efi,甚至补上驱动直接启动Windows?

目前已经完成对abl2esp的测试,参考博文ABL2ESP测试

ABL2ESP 配置

在刷入ABL2ESP之前,需要首先配置ESP分区,不然不出意料的话会直接变砖。
ESP分区可以选择ufs上的任意fat/fat32/exfat分区,或者放SD卡上的分区也行。

放在UFS的分区上可能会因为系统升级导致引导丢失,如果不慎重启之后机器就砖了,因为abl2esp无法找到引导。所以在安卓里面升级之后、重启之前请务必检查存放引导的分区。推荐将grub和LinuxLoader放在logfs分区(只有8MB),可以塞得下,然后把linux的Image/initrd/dtb等文件放在格式化成ext4的rawdump分区,因为这俩分区都是不会干扰系统升级的,也不会被系统升级覆盖,算比较安全。或者推荐直接把ESP分区放在SD卡。

patch过按键的GRUB2放在里面,并且配置/boot/grub/grub.cfg,给出一个基础grub.cfg的例子,假设linux内核和grub放在esp分区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
set menu_color_normal=white/light-gray
set menu_color_highlight=black/light-gray
if background_color 44,0,30,0; then
clear
fi

insmod gzio
insmod fat
insmod part_gpt

set timeout=5

menuentry "LinuxLoader" {
set gfxpayload=keep
chainloader /EFI/BOOT/LinuxLoader.efi
}

menuentry "Boot Linux" {
set gfxpayload=keep
devicetree /for_boot/sm8650-ayaneo-aps2-pro.dtb
linux /for_boot/Image panic=30 loglevel=7 pd_ignore_unused clk_ignore_unused efi=novamap earlycon=efifb console=ttyS0
}

LinuxLoader.efi是从原机的abl.elf提取的,用来启动安卓;第二个是Linux的启动项,存放在esp分区的/for_boot/目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~/esp # tree -L 3
.
├── EFI
│   └── BOOT
│   ├── BOOTAA64.EFI
│   └── LinuxLoader.efi
├── boot
│   └── grub
│   ├── arm64-efi
│   └── grub.cfg
└── for_boot
├── Image
└── sm8650-ayaneo-aps2-pro.dtb

7 directories, 5 files

Linux 启动

上一节,UEFI已经正常启动了,只需要编写编译设备树,在机器上配置ESP分区、rootfs就可以启动系统了。

设备树编写

编写已有平台的设备树通常需要参考其他参考平台的设备。例如SM8650的手机通常需要参考SM8650 qrd/mtp的设备树进行编写,本案例中采用QRD的设备树进行参考。复制arch/arm64/boot/dts/qcom/sm8650-qrd.dtsarch/arm64/boot/dts/qcom/sm8650-ayaneo-ps2.dts,然后根据具体硬件对其进行删改。

检查PMIC配置

PMIC是电源管理IC,高通平台上通常以PM8xxx, PM8xxxA/B类似的规则命名,打开device info hw,即可查看本机有哪些在线的pmic/ldo,如下图所示:
PMIC 配置

通过结合安卓设备树和主线设备树,humuldob,对应主线设备树中的pm8550pm_v8对应ldoipm_v6x对应pm8550vs,id有c、d、e、g,检查主线设备树中的对应配置,发现多出了mn(pm8010),这些都没用到,可以直接去掉。
保留如下pmic进行测试,同时删去apps_rsc中的regulator配置。

1
2
3
4
5
6
7
8
9
#include "sm8650.dtsi"
// #include "pm8010.dtsi"
#include "pm8550.dtsi"
#include "pm8550b.dtsi"
#define PMK8550VE_SID 8
#include "pm8550ve.dtsi"
#include "pm8550vs.dtsi"
#include "pmk8550.dtsi"
// #include "pmr735d_a.dtsi"

大多数情况下,参考设备设备树里面的ldo默认电压配置和大多数设备的电压配置是相同的,不过处于安全考虑,还是需要核查一遍。
如下代码块中的regulator-min/max-microvolt需要和安卓中的进行对比,数值差不多就行。

1
2
3
4
5
6
7
8
...
vreg_bob1: bob1 {
regulator-name = "vreg_bob1";
regulator-min-microvolt = <3296000>;
regulator-max-microvolt = <3960000>;
regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
};
...

如果发现主线设备树的apps_rsc节点中缺少某个ldo(例如ldod2),大概率是因为压根没用到那一路的LDO,可以通过搜索安卓设备树里面这路ldo的phandle数值查找是否存在引用该ldo的设备。不过现阶段不急着自己补上,可以等到后面写设备节点的时候再补。

检查GPIO保留

一些特殊设备可能会在devcfg中保留gpio用作特殊用途,这些gpio在主线设备树如果添加保留属性,可能会导致系统崩溃。
安卓设备树中,该参数的属性名称是qcom,gpios-reserved

1
2
3
4
5
6
7
8
9
10
11
12
pinctrl@f000000 {
compatible="qcompineapple-pinctrl";
reg = <0xf000000 0x1000000>;
interrupts = <0x00 0xd0 0x04>;
gpio-controller;
#gpio-cells = <0x02>;
interrupt-controller;
#interrupt-cells = <0x02>;
wakeup-parent = <0x2e>;
qcom,gpios-reserved = <0x24 0x26 0x27 0x28 0x29 0x2a 0x2b 0x4a>;
phandle = <0xc5>;
}

主线中的该参数属性名称为:gpio-reserved-ranges。代码块如下,其中32代表从32开始,8代表保留32~39八个gpio。

1
2
3
&tlmm {
/* Reserved I/Os for NFC */
gpio-reserved-ranges = <32 8>, <74 1>;

对比安卓和主线设备树,可以发现gpio保留有缺失,修正后的代码如下:

1
2
3
4
&tlmm {
/* Reserved I/Os for NFC */
// gpio-reserved-ranges = <32 8>, <74 1>;
gpio-reserved-ranges = <36 1>, <38 6>, <74 1>;

配置固件路径

默认Linux的固件路径通常/usr/lib/firmware/,设备树中的路径是基于此路径的相对路径。
我这里使用mdt,Linux主线在6.几,具体是几我也忘了,就合并了加载mdt格式的固件的支持了,而且pil-squasher貌似还不支持合并新版本的mdt固件。最好在路径中加上vendor名称和设备代号/设备名称,用来区分不同设备的固件(这些常常不一样)。

下面是一个ipa固件的例子,按照该配置为其他dsp/协处理器也配置固件路径。

1
2
- firmware-name = "qcom/sm8650/ipa_fws.mbn";
+ firmware-name = "qcom/sm8650/ayaneo/ps2/ipa_fws.mdt";

配置完成之后,将安卓中/vendor/firmware//vendor/firmware_mnt/image/vendor/bt_firmware/中用到的固件复制到Linux Rootfs分区里面,设备树中配置的位置。

添加i2c等外设设备

管他用得到用不到,先把三个gpi_dma打开:

1
2
3
4
5
6
7
8
9
&gpi_dma1 {
status = "okay";
};
&gpi_dma2 {
status = "okay";
};
&gpi_dma3 {
status = "okay";
};

搜索安卓设备树中的i2c@xxxx,查找i2c master设备下的子节点,如下案例中的nq@64redriver@1c,此时可以去主线内核源码中搜索是否存在对应驱动,例如搜索rtc6226,没有任何结果,搜索rtc6,也没相关结果,基本上代表主线并不支持该芯片。redriver在qrd的配置中默认已经存在,不用去修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
i2c@a98000 {
compatible = "qcom,i2c-geni";
reg = <0xa98000 0x4000>;
#address-cells = <0x01>;
#size-cells = <0x00>;
interrupts = <0x00 0x16b 0x04>;
clock-names = "se-clk";
clocks = <0x2c 0x70>;
interconnect-names = "qup-core\0qup-config\0qup-memory";
interconnects = <0x105 0x27 0x105 0x241 0x53 0x03 0x54 0x221 0x59 0x07 0x41 0x200>;
pinctrl-names = "default\0sleep";
pinctrl-0 = <0x142 0x143>;
pinctrl-1 = <0x144>;
dmas = <0x109 0x00 0x06 0x03 0x40 0x00 0x109 0x01 0x06 0x03 0x40 0x00>;
dma-names = "tx\0rx";
qcom,shared;
status = "ok";
phandle = <0x42c>;

nq@64 {
phandle = <0x536>;
rtc6226,vio-supply-voltage = <0x1b7740 0x1b7740>;
vio-supply = <0x127>;
rtc6226,vdd-load = <0x3a98>;
rtc6226,vdd-supply-voltage = <0x2ab980 0x2ab980>;
vdd-supply = <0x394>;
fmint-gpio = <0xc5 0x55 0x00>;
reg = <0x64>;
compatible = "rtc6226";/*?*/
};

redriver@1c {
compatible = "onnn,redriver";
reg = <0x1c>;
vdd-supply = <0x127>;
eq = <0x4060604 0x5070705>;
flat-gain = <0x3030303 0x30300>;
output-comp = <0x3030303 0x3030303>;
loss-match = <0x1030301 0x2020202>;
phandle = <0x580>;
};
};

触摸驱动通常也挂在i2c下面,aps2采用了汇顶科技的gt9xx驱动,在i2cdump中可以验证:

1
2
3
4
5
6
7
8
9
10
11
.../class/i2c-dev # i2cdetect 2
Probe chips 0x03-0x77 on bus 2? (Y/n):y
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- UU -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

对应安卓设备树中的:

1
2
3
4
5
6
7
gt9xx@5d {
goodix,cfg-group0 = [...]
goodix,pen-suppress-finger = <0x00>;
goodix,power-off-sleep = <0x01>;
goodix,auto-update = <0x00>;
...
}

推测该芯片大概率是gt911,在主线的Documentation目录中搜索gt911,通过Documentation/devicetree/bindings/input/touchscreen/goodix.yaml文件参考编写设备树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Example */
i2c {
...
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
touchscreen@5d {
compatible = "goodix,gt928";
reg = <0x5d>;
interrupt-parent = <&gpio>;
interrupts = <0 0>;
irq-gpios = <&gpio1 0 0>;
reset-gpios = <&gpio1 1 0>;
};
};

通过查找安卓设备树中的属性值,对应到主线驱动的属性,编写主线设备树中的设备节点,下面是编写完成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
&i2cX {
status = "okay";
touchscreen@5d {
reg = <0x5d>;
status = "okay";
compatible = "goodix,gt911";

interrupt-parent = <&tlmm>;
interrupts = <162 IRQ_TYPE_EDGE_FALLING>;

reset-gpios = <&tlmm 161 GPIO_ACTIVE_HIGH>;
VDDIO-supply = <&vreg_l14b_3p2>;
AVDD28-supply = <&vreg_l14b_3p2>;

touchscreen-size-x = <1440>;
touchscreen-size-y = <2560>;

pinctrl-names = "default";
pinctrl-0 = <&ts_reset>, <&ts_irq>;
};
}

&tlmm {
ts_irq: ts-irq-state {
pins = "gpio161";
function = "gpio";
drive-strength = <8>;
bias-pull-up;
output-disable;
};

ts_reset: ts-reset-state {
pins = "gpio162";
function = "gpio";
drive-strength = <8>;
bias-pull-up;
};
}

按照上面的逻辑编写其余子设备节点即可。

检查已存在的gpio值

参考设备的谁倍数中常常已经定义了部分芯片使用gpio,但是OEM生产的设备上,这些值常常会被改变,因此需要检查已经存在gpio配置是否正确,包括gpio序号,中断配置类型,gpio电平配置,gpio模式配置等。

检查reserved-memory

OEM可能会定义不一样的保留内存区域,该区域一般定义在soc级dtsi文件中,可以在dts文件中对include进来的节点进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/delete-node/ &rmtfs_mem;
/delete-node/ &hwfence_shbuf;

...

&reserved_memory {
lost_reg_mem: lost-reg-mem {
reg = <0 0x9b09c000 0 0x4000>;
no-map;
};

hwfence_shbuf: hwfence-shbuf@d4e23000 {
reg = <0 0xd4e23000 0 0x2dd000>;
no-map;
};
}

面板配置

通过以下命令查找设备面板型号

1
2
3
cat /proc/cmdline | grep --color dsi_display
...
msm_drm.dsi_display0=qcom,mdss_dsi_wt0630_60hz_video:

使用msm8916-mainline项目组的linux-mdss-dsi-panel-driver-generator/sys/firmware/fdt生成面板驱动框架:

1
./lmdpdg.py dtb --dumb-dcs

如何遇到报错ValueError: 'bl_ctrl_external' is not a valid BacklightControl,可以尝试将mdss_dsi_wt0630_60hz_video节点下面的属性qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_external";改成qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_dcs";,重新反编译成dtb,再运行lmdpdg,即可正常导出。
把生成的驱动放在drivers/gpu/drm/panel/panel-wt0630-2k.c,编辑该目录下面的Makefile和Kconfig,添加构建选项。

由于APS2面板依然采用的是Dual DSI面板, panel driver generator导出的驱动并不能直接使用,需要手动添加dsi1之后才能使用。

之后调试的发现,wt0630模式和属性都和wt0600一致,直接沿用上代驱动即可。

声音

麻了,花了两周时间debug声音问题,最后找到是audioreach.c中active channel配置问题,我写死了个正确的值就可以放音乐了。但是目前还没有正确的修复方法,写了封邮件给代码作者,问他有什么意见。

重命名声卡model,这个很重要。

1
2
compatible = "qcom,sm8650-sndcard", "qcom,sm8450-sndcard";
model = "SM8650-APS2";

高通新平台上使用Audioreach架构,具体audio bring up思路如下:

  1. 启动ADSP/LPASS/LPAIDSP, 高通的Hexagon DSP负责音频数据处理和DMA输出配置,所以需要先起来.
  2. Audioreach topology, 是alsatplg,需要自己根据平台的具体硬件进行编译,可以参考其他设备的,名称必须为<model>-tplg.bin,必须被放在/usr/lib/firmware/qcom/sm8650/
  3. Alsa UCM, Use case manager, 需要根据驱动和具体硬件编写,可以参考其他设备的配置,名称需要和model一致,且必须创建一个conf下的软连接。
  • 扬声器:
    添加路由:

    1
    2
    audio-routing = "SpkrLeft IN", "WSA_SPK1 OUT",
    "SpkrRight IN", "WSA_SPK2 OUT",

    APS2使用WSA2/SWR3双路Speaker,由于audioreach驱动中按照num_channels生成active channels mask,导致DSP一致尝试给swr0/wsa1发送数据,但是实际上发不出去,就一直卡请求dsp返回音频处理结果那里,通过强制设置为0xc/0b1100即可修复问题,但是会给其他设备引入问题,所以之后考虑其他修复方法:

    1
    2
    -intf_cfg->cfg.active_channels_mask = (1 << cfg->num_channels) - 1;
    +intf_cfg->cfg.active_channels_mask = 0b1100;
  • 麦克风
    APS2使用WCD939x Codec,麦克风挂在WCD TX上面的DMIC1链路,是通过安卓termux下面使用alsactl monitor监控,打开录音机和关闭录音机获取到的状态可以判断出来。如果你对着一个孔说话但是Linux下面没声音,可能是那个孔不是麦克风,建议在安卓测试一下洞是不是麦克风。

  • UCM2配置
    根据实际硬件编写就可,可以参考其他设备的。

结语

[2025/10/7]
前前后后弄了也有个把月了,基本功能除了mic也算正常了,之后可能考虑手搓一下指纹驱动,这篇笔记就先完结了,之后会在博客发完整系统。

相关链接


APS2 Mainlining Linux笔记
https://kancy.life/2025/07/21/APS2Linux_note/
作者
Kancy Joe
发布于
2025年7月21日
许可协议