LinearTeam
LinearTeam

【全网首发】如何为 iFLYTEKCB iClass C8Pro 移植 JingPad BSP 中的 kernel 4.14

警告:关于文章完整性

本篇文章只会讲出能保证你插电能正常启动的修改,其他问题请自己探索,谢谢!本篇文章不具备任何售后!

前言

《易经·乾卦》有言:

“同声相应,同气相求。”

选择相近的硬件平台(同架构、同 SoC 系列或同厂商)可以最大程度复用设备树、驱动代码和板级支持包去移植,才算得上“同声相应、同气相求。”

我们至今不知道展讯的 BSP 已经被踢飞几次,只要你有钱,就可以拿到他们旗舰芯片的 BSP 板级支持包,很可惜,我没有钱。但是除了那台新机,我们 B 端的机器都是万年不变的 UD710,不管它的外观从近似 iStudy X2Pro 到近似 iStudy C10,还是从 UD710-AB 到 UD710-AD,它始终都逃不开那颗芯。而且 UD710 已经是一个很老的家伙了,用泄露的 JingPad BSP 去移植 kernel,对我们来说简直是绰绰有余了。

在移植 kernel 之前,先往主板上焊个 TTL,然后把 dtb 读出来,16 进制改一下 loglevel,刷进去,我不知道屏幕前的各位有没有焊锡的能力,但我知道这样会省很多事,“工欲善其事,必先利其器。”说的就是这个道理吧。当然,要是人人都有这个能力,这篇文章就不会出现了,哈哈。

本 kernel 使用全套 LLVM 22.1.8 编译。

确认 iClass C8Pro 的 Codename

这个很简单,解包一下 vendor 就看得到,但是就拿 iClass C8Pro 举例,它解包后查看 build.prop 里写的 codenameud710_21c10_native,可是我们的 BSP 里面没有完整 target,这下怎么办?很简单,在 BSP 里面找个可用的替代品。但是在我们这,它在这个 BSP 里面对应的可用的 codename 都是一代代人传下来的,没有人告诉我为什么他的替代品是 ud710_2h10_native,可能是因为他们都是 UD710-AB 而且 iClass C6 这个同机型的 codenameud710_2h10_native

但是我要告诉你,iClass C8Pro 是一辆登记型号为 ud710_21c10_native 的定制车;我们借用 ud710_2h10_native 的维修手册和工位来编译维护,但它的发动机供油控制这一页,很可能抄自 ud710_5h10 的图纸,哈哈,我被这个坑惨了。为什么我敢这么说?ud710_5h10 写错的地方 iClass C8Pro 的 DT 错的都一模一样,笑死我了。在 BSP 层,iClass C8Pro 对应的可用 codenameud710_2h10_native;在 kernel 配置层,iClass C8Pro 的关键 kernel 配置血缘指向的是 ud710_5h10

有幸亲眼见证老一辈人确定的方法。在确认主板为特殊定制的 codenametx20_c8air(iClass C8Air)的这个机子的在 BSP 中对应的可用的 codename 的时候,我们使用的方法是把 BSP 中的所有 codename 的 FDL1 都编译出来,哪个能握上就用哪个,我们试出来的是 ud710_2c11_native(其实还有个 ud710_9h10u_native 也可以握上),很粗暴,原理也很简单,有可能是某个 RET 低压内存寄存器对上了,反正就是穷举嘛,自定义服务器想咋玩咋玩,不过还是建议不要用通过 FDL1 寻找这个方法哦。

但是 ud710_5h10 的尴尬在于它不是完整的构建参考目标。ud710_5h10 的相关配置主要散落在 kernel 源码中,例如 board config、dts 和 diffconfig。所以说,对于 iClass C8Pro 我们应该在 BSP 层继续借用 ud710_2h10_native 的完整构建链,kernel 则使用 ud710_5h10 和原厂 DT 进行移植。

提取原厂 defconfig 和 DT

defconfig 告诉你这颗芯片要打开哪些内核选项、编进哪些驱动模块。除了展讯的 SoC 不是标准 arm64 公版,里面一堆私有驱动之外,厂商也自己也有一些私有的 Kconfig 项。你就算是拿 ud710_2h10 的 defconfig 编译出来了大概率也是没法正常开机的,会卡住或者 panic。

DT(设备树)更是命脉所在,每一个 GPIO 的复用功能、每一路 I2C/SPI 的从设备地址、每一块屏幕的时序参数、每一颗 PMIC 的寄存器映射,全写在里面。虽然芯片同源,但主板上的外设布局天差地别,它们全要靠 DT 来交代。没有原厂 DT 当参考,就得拿着示波器和万用表去“猜”硬件连线了,有点好笑哦。

首先肯定是要先提取 kernel、dtb 和 dtbo 啦,这个我相信大家都会。那怎么提取我们想要的东西呢?哈哈,踏破铁鞋无觅处,得来全不费工夫!

提取 defconfig 的脚本就在 kernel 的 scripts 文件夹,它叫 extract-ikconfig,大家都会用吧。

至于提取 DT,就要请出 dtc 了,这个是老熟人了,我觉得大家都会用。可是这只够对付 dtb,面对展讯酷爱的 dtbo,我们就要使用 BSP 的 tools 文件夹中的 mkdtimg 了,先用它把 dtbo 转成 dtb,然后就可以使用 dtc 转出 dts 啦!

修改 defconfig

厂商也自己有一些私有的 Kconfig 项,而且肯定也对 kernel 进行了一些修改,支撑他们的设备正常开机。他们做了哪些修改我们是不知道的,我们只能努力地去摸索,至少让它能正常跑完加载流程。好在,我给出了现成的修改:

- CONFIG_MODVERSIONS=y
+ CONFIG_MODVERSIONS=n
+ CONFIG_MITOCHODRIA_MODULE_SKIP_VERMAGIC_VERSION=y
- CONFIG_SCSI_UFSHCD=y
+ CONFIG_SCSI_UFSHCD=n
- CONFIG_CHARGER_MANAGER=n
+ CONFIG_CHARGER_MANAGER=y
+ CONFIG_SPRD_MCD=y
+ CONFIG_SPRD_USER=y
- CONFIG_SPRD_MCD=n
- CONFIG_IFLYTEK_CHARGER_MANAGER=y

关掉 SCSI_UFSHCD 是因为 iClass C8Pro 是 EMMC,没有 UFS,关掉了可以缩短启动时间,不关不影响启动

CONFIG_SPRD_MCD 和 CONFIG_SPRD_USER 这个是为了兼容才打开的其中原厂 DT 已经有 sprd-mcd 节点和 4 个modem GPIO,启用 CONFIG_SPRD_MCD 是为了启用 AP/CP modem-control driverCONFIG_SPRD_USER 是一个 lookat debug 工具,生产不建议打开这两个 Kconfig 项不启用开不了机,不知道原厂整了什么非标实现来支撑它们不启用这俩还能正常开机。考虑到大家没有制造出一个平板的需求,照着这么改就行了。

科大讯飞的 charger-manager 是一个名为 CONFIG_IFLYTEK_CHARGER_MANAGER 私有 Kconfig 实现,我们没有,但是不开 charger-manager 安卓不知道你把电源线插进去了,故启用 CHARGER_MANAGER 项,CONFIG_IFLYTEK_CHARGER_MANAGER 删不删都可以,构建系统会自动略过。

CONFIG_MODVERSIONS 和 CONFIGMITOCHODRIA_MODULE_SKIP_VERMAGIC_VERSION 这是干什么的后面会讲。

杂交 DT

这个东西的重要性上面也讲了,我们不能使用 BSP 直接编译出来的,也不能继续使用原厂的,为了兼容我们的 BSP 我们只能使用杂交的方式。实践是检验真理的唯一标准,我们需要按照对应 ud710_5h10 的 DT 中的设备节点定义,对照实际厂商设备的 DT 来编写我们的 DT。这边不多讲了,会的就认真写不会的直接抛 Ai。

CPUFreq、CPU hotplug 和调度器

这个东西不修一下导致的后果在我这里还挺严重的:

  1. 有几率卡开机动画第二阶段。原厂也有这个问题,只不过发生的概率小。
  2. 会让 APP 卡在用户空间,实际上就是打开死慢,正常人是等不到它启动了。不知道原厂怎么解决的。

注意这里必须使用和我一样使用全套 LLVM 22.1.8 编译才可以这么改

drivers/cpufreq/sprd-cpufreq-common.h:增加 DVFSready 操作。允许 CPUFreq 前端检查完整硬件控制链。

@@ -119,6 +119,7 @@
 #define CPUFREQHW_NAME_LEN			30
 
 struct sprd_cpudvfs_ops {
+	bool (*ready)(void *drvdata);
 	bool (*probed)(void *drvdata, int cluster);
 	bool (*enable)(void *drvdata, int cluster, bool en);
 	int (*opp_add)(void *drvdata, unsigned int cluster,

drivers/cpufreq/sprd-cpufreqhw.c:NVMEM -EPROBE_DEFER 原样返回,等待 DVFS core 和 DCDC clients。防止在校准和电压控制链未就绪时注册半成品 policy。

@@ -587,19 +587,26 @@
 	}
 
 	cell = of_nvmem_cell_get(np, "dvfs_bin");
-	ret = PTR_ERR(cell);
-	if (!IS_ERR(cell))
+	if (IS_ERR(cell)) {
+		ret = PTR_ERR(cell);
+		if (ret == -EPROBE_DEFER)
+			goto exit;
+	} else {
 		nvmem_cell_put(cell);
-	else if (ret == -EPROBE_DEFER)
+	}
+
+	if (!plat_dev ||
+	    (plat_dev->ops.ready && !plat_dev->ops.ready(plat_dev->archdata))) {
+		ret = -EPROBE_DEFER;
 		goto exit;
+	}
 
 	sprd_cpufreq_cpuhp_setup();
 
 	ret = cpufreq_register_driver(&sprd_hardware_cpufreq_driver);
 	if (ret)
-		dev_err(&pdev->dev, "Failed to reigister cpufreq driver\n");
-	else
-		dev_info(&pdev->dev, "Succeeded to register cpufreq driver\n");
+		dev_err(&pdev->dev, "Failed to register cpufreq driver: %d\n",
+			ret);
 
 exit:
 	of_node_put(np);

drivers/cpufreq/sprd-hwdvfs-normal.c:DCDC I2C probe defer、索引校验、runtime-PM 引用和 remove 清理;实现ready。保证 TOP-DVFS 使用外部 DCDC 时控制器保持时钟,并使 client 生命周期可验证。

@@ -20,6 +20,7 @@
 #include <linux/of.h>
 #include <linux/i2c.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/pm_opp.h>
 #include <linux/regulator/consumer.h>
 #include <linux/slab.h>
@@ -70,10 +71,8 @@
 	enum dcdc_name dcdc;
 	int ret;
 
-	if (!platdev) {
-		pr_err("No cpu dvfs device found.\n");
-		return -ENODEV;
-	}
+	if (!platdev)
+		return -EPROBE_DEFER;
 
 	np = client->dev.of_node;
 	if (!np) {
@@ -89,10 +88,42 @@
 
 
 	pri = (struct cpudvfs_archdata *)platdev->archdata;
+	if (!pri || dcdc >= pri->dcdc_num) {
+		pr_err("Invalid DCDC I2C channel %u\n", dcdc);
+		return -EINVAL;
+	}
+
+	ret = pm_runtime_get_sync(client->adapter->dev.parent);
+	if (ret < 0) {
+		pm_runtime_put_noidle(client->adapter->dev.parent);
+		pr_err("Failed to keep DCDC%u I2C controller active: %d\n",
+			dcdc, ret);
+		return ret;
+	}
+
 	pri->pwr[dcdc].i2c_client = client;
 	return 0;
 }
 
+static int cpudvfs_i2c_remove(struct i2c_client *client)
+{
+	struct sprd_cpudvfs_device *platdev = sprd_hardware_dvfs_device_get();
+	struct cpudvfs_archdata *pri;
+	struct device_node *np = client->dev.of_node;
+	u32 dcdc;
+
+	if (platdev && np &&
+	    !of_property_read_u32(np, "dvfs-dcdc-i2c", &dcdc)) {
+		pri = (struct cpudvfs_archdata *)platdev->archdata;
+		if (pri && dcdc < pri->dcdc_num)
+			pri->pwr[dcdc].i2c_client = NULL;
+	}
+
+	pm_runtime_mark_last_busy(client->adapter->dev.parent);
+	pm_runtime_put_autosuspend(client->adapter->dev.parent);
+	return 0;
+}
+
 static const struct of_device_id cpudvfs_dcdc_cpu0_i2c_of_match[] = {
 	{.compatible = "sprd,cpudvfs-regulator-dcdc-cpu0-roc1",},
 	{},
@@ -115,6 +146,7 @@
 			.of_match_table = cpudvfs_dcdc_cpu0_i2c_of_match,
 		},
 		.probe = cpudvfs_i2c_probe,
+		.remove = cpudvfs_i2c_remove,
 	},
 
 	{
@@ -124,6 +156,7 @@
 			.of_match_table = cpudvfs_dcdc_cpu1_i2c_of_match,
 		},
 		.probe = cpudvfs_i2c_probe,
+		.remove = cpudvfs_i2c_remove,
 	}
 
 };
@@ -1762,6 +1795,22 @@
 	return true;
 }
 
+static bool sprd_cpudvfs_ready(void *data)
+{
+	struct cpudvfs_archdata *pdev = data;
+	int i;
+
+	if (!pdev || !pdev->probed || !pdev->pwr)
+		return false;
+
+	for (i = 0; i < pdev->dcdc_num; i++) {
+		if (pdev->pwr[i].i2c_used && !pdev->pwr[i].i2c_client)
+			return false;
+	}
+
+	return true;
+}
+
 static int dvfs_module_dt_parse(struct cpudvfs_archdata *pdev)
 {
 	struct property *prop;
@@ -2989,6 +3038,7 @@
 static struct sprd_cpudvfs_device cpudvfs_plat_dev = {
 	.name = "sprd-cpudvfs-plat",
 	.ops = {
+		.ready = sprd_cpudvfs_ready,
 		.probed = sprd_cpudvfs_probed,
 		.enable = sprd_cpudvfs_enable,
 		.opp_add = sprd_cpudvfs_opp_add,

drivers/i2c/busses/i2c-sprd-hw.c:仅在 dts 声明时请求可选 clk_hw_i2c。修复 C8Pro AON I2C0 因不存在的第四时钟无法 probe,同时不改变其他板的四时钟配置。

@@ -447,12 +447,16 @@
 		i2c_dev->clk = NULL;
 	}
 
-	i2c_dev->clk_hw = devm_clk_get(i2c_dev->dev, "clk_hw_i2c");
-	if (IS_ERR(i2c_dev->clk_hw)) {
-		dev_warn(&i2c_dev->adap.dev,
-			 "i2c%d can't get the clk_hw clock\n",
-			 i2c_dev->adap.nr);
-		i2c_dev->clk_hw = NULL;
+	i2c_dev->clk_hw = NULL;
+	if (of_property_match_string(i2c_dev->dev->of_node, "clock-names",
+				     "clk_hw_i2c") >= 0) {
+		i2c_dev->clk_hw = devm_clk_get(i2c_dev->dev, "clk_hw_i2c");
+		if (IS_ERR(i2c_dev->clk_hw)) {
+			dev_warn(&i2c_dev->adap.dev,
+				 "i2c%d can't get the clk_hw clock\n",
+				 i2c_dev->adap.nr);
+			i2c_dev->clk_hw = NULL;
+		}
 	}
 
 	return 0;

这些修复仅在 iClass C8Pro 进行过测试,其他设备参考前应先复现 CPU hotplug 或 suspend 问题。

Watchdog 和 suspend

防止平板睡觉之后自动醒过来(AP deep sleep 后自动唤起)

注意这里必须使用和我一样使用全套 LLVM 22.1.8 编译才可以这么改

drivers/soc/sprd/debug/hang_debug/sprd_wdf.c

  • PM notifier 在 suspend 前停 feeder,resume 后恢复,防止深睡期间 CPU feeder 停止造成 FIQ watchdog 误判。
  • hrtimer 只初始化一次,所有 start 经 sprd_wdf_start(),避免 queued timer 被重复 hrtimer_init(),并统一检查禁用状态。
@@ -25,6 +25,7 @@
 #include <linux/proc_fs.h>
 #include <uapi/linux/sched/types.h>
 #include <linux/watchdog.h>
+#include <linux/suspend.h>
 
 #ifdef CONFIG_SPRD_WATCHDOG_FIQ
 #include <linux/sprd_wdt_fiq.h>
@@ -43,6 +44,15 @@
 struct watchdog_device *wdd;
 static int g_timeout = 8;
 static int wdt_disable;
+static bool wdt_resume_after_suspend;
+
+static void sprd_wdf_start(void)
+{
+#ifdef CONFIG_SPRD_WATCHDOG_FIQ
+	if (!READ_ONCE(wdt_disable) && wdd && wdd->ops->start)
+		wdd->ops->start(wdd);
+#endif
+}
 
 static enum hrtimer_restart sprd_wdt_timer_func(struct hrtimer *hrtimer)
 {
@@ -56,8 +66,11 @@
 {
 	struct hrtimer *hrtimer = per_cpu_ptr(&sprd_wdt_hrtimer, cpu);
 
-	hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
-	hrtimer->function = sprd_wdt_timer_func;
+	/* hrtimer_init() must never be called while the timer is queued. */
+	if (!hrtimer->function) {
+		hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+		hrtimer->function = sprd_wdt_timer_func;
+	}
 	hrtimer_start(hrtimer, ms_to_ktime(g_timeout * MSEC_PER_SEC),
 		      HRTIMER_MODE_REL_PINNED);
 }
@@ -88,10 +101,7 @@
 	cpu_feed_bitmap = 0;
 	pr_debug("%s cpu = %u\n", __func__, cpu);
 	spin_unlock(&lock);
-#ifdef CONFIG_SPRD_WATCHDOG_FIQ
-	if (wdd->ops->start)
-		wdd->ops->start(wdd);
-#endif
+	sprd_wdf_start();
 	hrtimer_cancel(hrtimer);
 }
 
@@ -102,10 +112,12 @@
 	cpu_feed_bitmap = 0;
 	pr_debug("%s cpu = %u\n", __func__, cpu);
 	spin_unlock(&lock);
-#ifdef CONFIG_SPRD_WATCHDOG_FIQ
-	if (wdd->ops->start)
-		wdd->ops->start(wdd);
-#endif
+
+	/* PM_POST_SUSPEND restarts the timer after every CPU is online. */
+	if (READ_ONCE(wdt_disable))
+		return;
+
+	sprd_wdf_start();
 	sprd_wdf_hrtimer_enable(cpu);
 }
 
@@ -114,15 +126,18 @@
 
 	spin_lock(&lock);
 
+	if (READ_ONCE(wdt_disable)) {
+		__this_cpu_write(g_enable, 0);
+		spin_unlock(&lock);
+		return;
+	}
+
 	cpu_feed_bitmap |= (1 << cpu);
 	if (cpu_feed_mask == cpu_feed_bitmap) {
 		pr_debug("%s feed wdt cpu_feed_bitmap = 0x%08x\n",
 			__func__, cpu_feed_bitmap);
 		cpu_feed_bitmap = 0;
-#ifdef CONFIG_SPRD_WATCHDOG_FIQ
-		if (wdd->ops->start)
-			wdd->ops->start(wdd);
-#endif
+		sprd_wdf_start();
 	} else {
 		pr_debug("%s cpu_feed_bitmap = 0x%08x\n", __func__, cpu_feed_bitmap);
 	}
@@ -195,8 +210,7 @@
 			wake_up_process(per_cpu(hang_debug_task_store, cpu));
 		}
 #ifdef CONFIG_SPRD_WATCHDOG_FIQ
-		if (wdd->ops->start)
-			wdd->ops->start(wdd);
+		sprd_wdf_start();
 #endif
 	}
 
@@ -249,14 +263,39 @@
 			wdd->ops->set_timeout(wdd, SPRD_WDF_TIMOUT);
 		if (wdd->ops->set_pretimeout)
 			wdd->ops->set_pretimeout(wdd, SPRD_WDF_PRETIMOUT);
-		if (wdd->ops->start)
-			wdd->ops->start(wdd);
+		sprd_wdf_start();
 #endif
 	}
 }
 
 EXPORT_SYMBOL(sprd_wdf_wdt_disable);
 
+static int sprd_wdf_pm_notify(struct notifier_block *nb,
+			      unsigned long event, void *unused)
+{
+	switch (event) {
+	case PM_SUSPEND_PREPARE:
+		wdt_resume_after_suspend = !READ_ONCE(wdt_disable);
+		if (wdt_resume_after_suspend)
+			sprd_wdf_wdt_disable(1);
+		break;
+	case PM_POST_SUSPEND:
+		if (wdt_resume_after_suspend) {
+			wdt_resume_after_suspend = false;
+			sprd_wdf_wdt_disable(0);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block sprd_wdf_pm_nb = {
+	.notifier_call = sprd_wdf_pm_notify,
+};
+
 static ssize_t wdt_disable_proc_write(struct file *file, const char *buf,
 	size_t count, loff_t *data)
 {
@@ -316,8 +355,7 @@
 	if (wdd->ops->set_pretimeout)
 		wdd->ops->set_pretimeout(wdd, SPRD_WDF_PRETIMOUT);
 
-	if (wdd->ops->start)
-		wdd->ops->start(wdd);
+	sprd_wdf_start();
 
 	pr_notice("%s\n", __func__);
 #else
@@ -346,6 +384,7 @@
 
 	atomic_notifier_chain_register(&panic_notifier_list,
 					&wdf_panic_event_nb);
+	register_pm_notifier(&sprd_wdf_pm_nb);
 
 	return 0;
 }

drivers/watchdog/sprd_wdt_fiq.c

  • 记录 suspend 前 active 状态,resume 时只恢复原本运行的 watchdog,而不是依赖 suspend 后的状态位。
  • 使用 device PM path,按 eb_always_on 控制时钟,适配 ROC1(UD710)watchdog 时钟在深睡中的实际生命周期。
@@ -68,7 +68,6 @@
 #define SPRD_WDTEN_MAGIC "e551"
 #define SPRD_WDTEN_MAGIC_LEN_MAX  10
 
-#define SPRD_WDT_SYSCORE_SUSPEND_RESUME
 
 struct sprd_wdt_fiq {
 	void __iomem *base;
@@ -86,6 +85,7 @@
 
 static DEFINE_MUTEX(sprd_wdt_mutex);
 struct sprd_wdt_fiq *wdt_fiq;
+static bool wdt_fiq_was_active;
 
 static struct sprd_wdt_fiq_data sprd_wdt_fiq_common = {
 	.eb_always_on = false,
@@ -407,38 +407,39 @@
 
 static int __maybe_unused sprd_wdt_fiq_pm_suspend(struct device *dev)
 {
-#ifndef SPRD_WDT_SYSCORE_SUSPEND_RESUME
-	struct watchdog_device *wdd = dev_get_drvdata(dev);
 	struct sprd_wdt_fiq *wdt = dev_get_drvdata(dev);
+	struct watchdog_device *wdd = &wdt->wdd;
 
-	if (watchdog_active(wdd))
+	wdt_fiq_was_active = watchdog_active(wdd);
+	if (wdt_fiq_was_active)
 		sprd_wdt_fiq_stop(&wdt->wdd);
 
-	if (!wdt_fiq->data->eb_always_on)
-		sprd_wdt_fiq_disable(wdt_fiq);
-#endif
+	if (!wdt->data->eb_always_on)
+		sprd_wdt_fiq_disable(wdt);
+
 	return 0;
 }
 
 static int __maybe_unused sprd_wdt_fiq_pm_resume(struct device *dev)
 {
-#ifndef SPRD_WDT_SYSCORE_SUSPEND_RESUME
-	struct watchdog_device *wdd = dev_get_drvdata(dev);
 	struct sprd_wdt_fiq *wdt = dev_get_drvdata(dev);
 	int ret;
 
-	ret = sprd_wdt_fiq_enable(wdt);
-	if (ret)
-		return ret;
+	if (!wdt->data->eb_always_on) {
+		ret = sprd_wdt_fiq_enable(wdt);
+		if (ret)
+			return ret;
+	}
 
-	if (watchdog_active(wdd)) {
+	if (wdt_fiq_was_active) {
 		ret = sprd_wdt_fiq_start(&wdt->wdd);
 		if (ret) {
-			sprd_wdt_fiq_disable(wdt);
+			if (!wdt->data->eb_always_on)
+				sprd_wdt_fiq_disable(wdt);
 			return ret;
 		}
 	}
-#endif
+
 	return 0;
 }
 
@@ -448,7 +449,8 @@
 	if (!wdt_fiq)
 		return -ENODEV;
 
-	if (watchdog_active(&wdt_fiq->wdd))
+	wdt_fiq_was_active = watchdog_active(&wdt_fiq->wdd);
+	if (wdt_fiq_was_active)
 		sprd_wdt_fiq_stop(&wdt_fiq->wdd);
 
 	if (!wdt_fiq->data->eb_always_on)
@@ -468,7 +470,7 @@
 	if (ret)
 		return;
 
-	if (watchdog_active(&wdt_fiq->wdd)) {
+	if (wdt_fiq_was_active) {
 		ret = sprd_wdt_fiq_start(&wdt_fiq->wdd);
 		if (ret) {
 			sprd_wdt_fiq_disable(wdt_fiq);

触摸屏

iClass C8Pro 使用的触摸 IC 是 Goodix GT738X(B 端设备基本上都是),虽然 BSP 里面没有对应驱动,但是 Goodix 的屏幕驱动一直都是开源的,留给你们自己去写吧。

这个 kernel 里有 Goodix GT738X的驱动,把这个 commit 里的修改 pick 过来再改改就好https://github.com/isning/android_kernel_lenovo_sm8250/commit/ad2bc2350f7eee5c2e5d7dc64f1b734bf3253878

显示

drivers/gpu/drm/sprd/sprd_panel.c

  • 检查 lcd3v3 regulator enable 返回值,避免静默忽略面板电源失败。
  • 修正 for(i = 0; i++; i < 10) 为正常 10 次循环,原表达式第一次判断即为 0,实际不会发送命令;修复后执行预期重试。
  • 删除未使用变量并整理探测代码,无运行语义变化。
@@ -181,8 +181,6 @@
 static int sprd_panel_unprepare(struct drm_panel *p)
 {
     struct sprd_panel *panel = to_sprd_panel(p);
-    struct gpio_timing *timing;
-    int items, i;
 
     DRM_INFO("%s()\n", __func__);
 
@@ -308,7 +306,8 @@
         gpiod_direction_output(panel->info.lcd1v8_gpio, 1);
         mdelay(5);
     }
-    regulator_enable(lcd3v3);
+    if (regulator_enable(lcd3v3))
+        DRM_WARN("enable lcd3v3 failed\n");
    
 #if 0
 	if(panel->info.lcdtp3v3_gpio) {
@@ -838,7 +837,7 @@
     struct sprd_panel *panel = oled_xr->panel;
 
     mutex_lock(&panel_lock);
-    for(i = 0; i++; i< 10)
+    for(i = 0; i < 10; i++)
     {
         sprd_panel_send_cmds(panel->slave,
                 oled_xr->cmds[0],
@@ -857,7 +856,6 @@
 	int level, brightness;
 	struct sprd_oled *oled = bl_get_data(bdev);
 	struct sprd_panel *panel = oled->panel;
-    int i;
 
 	mutex_lock(&panel_lock);
 	if (!panel->is_enabled) {
@@ -1257,10 +1255,10 @@
         return -EFAULT;
     }
     regulator_set_voltage(lcd3v3, 3300000, 3300000);
-    ret = regulator_enable(lcd3v3);
-    if(ret != 0)
-        printk("lcd %s: some error happen, fail to enable regulator!\n", __func__);
-   
+	ret = regulator_enable(lcd3v3);
+	if (ret != 0)
+		printk("lcd %s: some error happen, fail to enable regulator!\n", __func__);
+
 	panel = devm_kzalloc(&slave->dev, sizeof(*panel), GFP_KERNEL);
 	if (!panel)
 		return -ENOMEM;

音频

本来想继续偷懒,给个 commit 就完事的,可惜找到的这个 commit 因为还打了 patch,所以不太好复用,那就放出我写的吧。

iClass C8Pro 使用双 AW8898 SmartPA这是我写的驱动,但是不建议模仿,因为是根据原厂内核 TTL 日志手写出来的 AW8898 最小兼容驱动。它支持:

  • left I2C address 0x34 / DAI aw8898-l-aif
  • right I2C address 0x35 / DAI aw8898-r-aif
  • left/right firmware configuration
  • aw8898_l_speaker_switch mixer control

sound/soc/codecs/aw8898/aw8898.c

@@ -0,0 +1,440 @@
+/*
+ * aw8898.c — Awinic AW8898 SmartPA dual-chip codec driver
+ *
+ * Reconstructed from factory v1.1.5 behaviour:
+ *   Left  (0x34): DAI "aw8898-l-aif", registers codec with both DAIs
+ *   Right (0x35): DAI "aw8898-r-aif", cross-referenced by left
+ *   cfg.bin format: [reg_lo] [reg_hi] [val_lo] [val_hi]
+ *
+ * Both chips are configured together on SMTPA BE open.
+ */
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/workqueue.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+
+#define AW8898_L_I2C_NAME  "aw8898_l_smartpa"
+#define AW8898_R_I2C_NAME  "aw8898_r_smartpa"
+#define AW8898_VERSION     "v1.1.5-iflytek"
+
+#define AW8898_REG_ID      0x00
+#define AW8898_CHIPID      0x1702
+
+/* --- Per-chip state --- */
+struct aw8898 {
+	struct i2c_client *i2c;
+	struct device      *dev;
+	int                 reset_gpio;
+	unsigned int        flags;      /* bit0=START_ON_MUTE, bit1=SKIP_IRQ */
+	int                 chipid;
+	bool                power_on;
+	bool                init_done;
+	char                cfg_name[64];
+};
+
+/* Global reference to the right-channel chip for cross-calls */
+static struct aw8898 *g_aw8898_r;
+
+/* ------------------------------------------------------------------ */
+/*  I2C helpers                                                        */
+/* ------------------------------------------------------------------ */
+static int aw8898_i2c_write(struct aw8898 *aw, u16 reg, u16 val)
+{
+	u8 buf[3];
+	buf[0] = reg & 0xff;
+	buf[1] = (val >> 8) & 0xff;
+	buf[2] = val & 0xff;
+	return i2c_master_send(aw->i2c, buf, 3);
+}
+
+static int aw8898_i2c_read(struct aw8898 *aw, u16 reg, u16 *val)
+{
+	int ret;
+	u8 wbuf[1] = { reg & 0xff };
+	u8 rbuf[2];
+
+	ret = i2c_master_send(aw->i2c, wbuf, 1);
+	if (ret < 0)
+		return ret;
+	ret = i2c_master_recv(aw->i2c, rbuf, 2);
+	if (ret < 0)
+		return ret;
+	*val = (rbuf[0] << 8) | rbuf[1];
+	return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/*  Hardware reset                                                     */
+/* ------------------------------------------------------------------ */
+static void aw8898_hw_reset(struct aw8898 *aw)
+{
+	if (!gpio_is_valid(aw->reset_gpio))
+		return;
+	gpio_set_value_cansleep(aw->reset_gpio, 0);
+	msleep(5);
+	gpio_set_value_cansleep(aw->reset_gpio, 1);
+	msleep(5);
+}
+
+/* ------------------------------------------------------------------ */
+/*  Config-file loading                                               */
+/*  Format (factory-verified): [reg_lo] [reg_hi] [val_lo] [val_hi]    */
+/* ------------------------------------------------------------------ */
+static void aw8898_container_update(struct aw8898 *aw, u8 reg, u16 val)
+{
+	dev_dbg(aw->dev, "container_update: reg=0x%02x, val=0x%04x\n",
+		reg, val);
+	aw8898_i2c_write(aw, reg, val);
+}
+
+static int aw8898_load_cfg(struct aw8898 *aw)
+{
+	const struct firmware *fw;
+	const u8 *p;
+	int i, ret;
+
+	ret = request_firmware(&fw, aw->cfg_name, aw->dev);
+	if (ret < 0) {
+		dev_info(aw->dev, "no cfg %s (deferred), ret=%d\n",
+			 aw->cfg_name, ret);
+		return ret;
+	}
+
+	dev_info(aw->dev, "loaded %s - size: %zu\n", aw->cfg_name, fw->size);
+
+	p = fw->data;
+	/* cfg format: [reg_addr(1)] [0x00(1)] [val_lo(1)] [val_hi(1)] — little-endian */
+	for (i = 0; i + 3 < (int)fw->size; i += 4) {
+		u8 reg = p[i];
+		u16 val = ((u16)p[i + 3] << 8) | p[i + 2];
+		aw8898_container_update(aw, reg, val);
+	}
+
+	release_firmware(fw);
+	return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/*  Cold-start / power / mute — factory sequence                     */
+/* ------------------------------------------------------------------ */
+static void aw8898_cold_start(struct aw8898 *aw)
+{
+	dev_info(aw->dev, "cold_start enter\n");
+	aw8898_hw_reset(aw);
+	usleep_range(2000, 2500);
+	aw8898_load_cfg(aw);
+	aw->init_done = true;
+}
+
+static void aw8898_mute(struct aw8898 *aw, int mute_state)
+{
+	dev_info(aw->dev, "mute: mute state=%d\n", mute_state);
+	/* Mute/unmute via register — cfg handles actual I2S path.
+	 * We just toggle the PA power. */
+	if (mute_state) {
+		/* power-down PA */
+		aw8898_i2c_write(aw, 0x0004, 0x0002);
+	} else {
+		/* power-up PA (read current first) */
+		u16 val = 0;
+		aw8898_i2c_read(aw, 0x0004, &val);
+		val &= ~0x0002u;
+		aw8898_i2c_write(aw, 0x0004, val);
+	}
+}
+
+/* ------------------------------------------------------------------ */
+/*  DAI operations                                                     */
+/* ------------------------------------------------------------------ */
+static int aw8898_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	dev_info(dai->dev, "%s: fmt=0x%04x\n", __func__, fmt);
+	/* factory always passes 0x4001 — accept it */
+	return 0;
+}
+
+static int aw8898_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct aw8898 *aw = snd_soc_codec_get_drvdata(dai->codec);
+
+	dev_info(aw->dev, "%s: enter\n", __func__);
+
+	/* Left chip: also start right chip if present */
+	if (g_aw8898_r && g_aw8898_r != aw) {
+		dev_info(g_aw8898_r->dev, "%s: enter\n", __func__);
+		if (!g_aw8898_r->init_done)
+			aw8898_cold_start(g_aw8898_r);
+		aw8898_mute(g_aw8898_r, 0);
+	}
+
+	if (!aw->init_done)
+		aw8898_cold_start(aw);
+	aw8898_mute(aw, 0);
+
+	aw->power_on = true;
+	return 0;
+}
+
+static void aw8898_shutdown(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct aw8898 *aw = snd_soc_codec_get_drvdata(dai->codec);
+
+	dev_info(aw->dev, "%s: enter\n", __func__);
+	aw8898_mute(aw, 1);
+	aw->power_on = false;
+	aw->init_done = false;
+
+	/* Also mute right */
+	if (g_aw8898_r && g_aw8898_r != aw) {
+		aw8898_mute(g_aw8898_r, 1);
+		g_aw8898_r->power_on = false;
+		g_aw8898_r->init_done = false;
+	}
+}
+
+static struct snd_soc_dai_ops aw8898_dai_ops = {
+	.set_fmt   = aw8898_set_fmt,
+	.startup   = aw8898_startup,
+	.shutdown  = aw8898_shutdown,
+};
+
+/* Left-codec DAIs: aw8898-l-aif (used by SMTPA BE) + aw8898-r-aif  */
+static struct snd_soc_dai_driver aw8898_l_dai[] = {
+	{
+		.name = "aw8898-l-aif",
+		.id   = 0,
+		.playback = {
+			.stream_name    = "AW8898-L Playback",
+			.channels_min   = 1,
+			.channels_max   = 2,
+			.rates          = SNDRV_PCM_RATE_8000_48000,
+			.formats        = SNDRV_PCM_FMTBIT_S16_LE |
+					  SNDRV_PCM_FMTBIT_S24_LE,
+		},
+		.ops = &aw8898_dai_ops,
+	},
+};
+
+/* Right-codec DAI (registered but no BE binds to it directly;
+ * started together with left via cross-call). */
+static struct snd_soc_dai_driver aw8898_r_dai[] = {
+	{
+		.name = "aw8898-r-aif",
+		.id   = 0,
+		.playback = {
+			.stream_name    = "AW8898-R Playback",
+			.channels_min   = 1,
+			.channels_max   = 2,
+			.rates          = SNDRV_PCM_RATE_8000_48000,
+			.formats        = SNDRV_PCM_FMTBIT_S16_LE |
+					  SNDRV_PCM_FMTBIT_S24_LE,
+		},
+		.ops = &aw8898_dai_ops,
+	},
+};
+
+/* ------------------------------------------------------------------ */
+/*  Mixer control: aw8898_l_speaker_switch (for audio HAL)           */
+/* ------------------------------------------------------------------ */
+static int aw8898_spk_switch_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+	struct aw8898 *aw = snd_soc_codec_get_drvdata(codec);
+	ucontrol->value.integer.value[0] = aw->power_on ? 1 : 0;
+	return 0;
+}
+
+static int aw8898_spk_switch_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+	struct aw8898 *aw = snd_soc_codec_get_drvdata(codec);
+	int enable = !!ucontrol->value.integer.value[0];
+
+	if (aw->power_on == enable)
+		return 0;
+
+	aw->power_on = enable;
+	dev_info(aw->dev, "aw8898 speaker switch: %s\n",
+		 enable ? "On" : "Off");
+
+	if (enable) {
+		if (!aw->init_done)
+			aw8898_cold_start(aw);
+		aw8898_mute(aw, 0);
+		if (g_aw8898_r) {
+			if (!g_aw8898_r->init_done)
+				aw8898_cold_start(g_aw8898_r);
+			aw8898_mute(g_aw8898_r, 0);
+		}
+	} else {
+		aw8898_mute(aw, 1);
+		if (g_aw8898_r)
+			aw8898_mute(g_aw8898_r, 1);
+	}
+	return 1;
+}
+
+static const char * const aw8898_switch_text[] = { "Off", "On" };
+static const struct soc_enum aw8898_spk_enum =
+	SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aw8898_switch_text), aw8898_switch_text);
+static const struct snd_kcontrol_new aw8898_controls[] = {
+	SOC_ENUM_EXT("aw8898_l_speaker_switch", aw8898_spk_enum,
+		     aw8898_spk_switch_get, aw8898_spk_switch_put),
+};
+
+/* ------------------------------------------------------------------ */
+/*  Codec probe                                                        */
+/* ------------------------------------------------------------------ */
+static int aw8898_codec_probe(struct snd_soc_codec *codec)
+{
+	struct aw8898 *aw = snd_soc_codec_get_drvdata(codec);
+	struct snd_soc_card *card = codec->component.card;
+
+	dev_info(aw->dev, "%s: card=%s\n", __func__,
+		 card ? card->name : "NULL");
+
+	snd_soc_add_codec_controls(codec, aw8898_controls,
+				   ARRAY_SIZE(aw8898_controls));
+	return 0;
+}
+
+static struct snd_soc_codec_driver aw8898_codec_drv = {
+	.probe = aw8898_codec_probe,
+};
+
+/* ------------------------------------------------------------------ */
+/*  I2C probe                                                          */
+/* ------------------------------------------------------------------ */
+static int aw8898_i2c_probe(struct i2c_client *i2c,
+			    const struct i2c_device_id *id)
+{
+	struct aw8898 *aw;
+	u16 chipid = 0;
+	int ret;
+	enum of_gpio_flags flags;
+	bool is_left;
+
+	is_left = of_device_is_compatible(i2c->dev.of_node,
+					  "awinic,aw8898_l_smartpa");
+
+	dev_info(&i2c->dev, "aw8898_i2c_probe enter (is_left=%d)\n",
+		 is_left);
+
+	aw = devm_kzalloc(&i2c->dev, sizeof(*aw), GFP_KERNEL);
+	if (!aw)
+		return -ENOMEM;
+
+	aw->i2c = i2c;
+	aw->dev = &i2c->dev;
+
+	/* Reset GPIO */
+	aw->reset_gpio = of_get_named_gpio_flags(i2c->dev.of_node,
+						 "reset-gpio", 0, &flags);
+	if (gpio_is_valid(aw->reset_gpio)) {
+		ret = devm_gpio_request_one(&i2c->dev, aw->reset_gpio,
+					    GPIOF_OUT_INIT_LOW,
+					    is_left ? "aw8898_l_rst" :
+						      "aw8898_r_rst");
+		if (ret)
+			dev_warn(&i2c->dev,
+				 "reset gpio request failed: %d\n", ret);
+		else
+			aw8898_hw_reset(aw);
+	}
+
+	/* Read chip ID */
+	ret = aw8898_i2c_read(aw, AW8898_REG_ID, &chipid);
+	if (ret < 0) {
+		dev_err(&i2c->dev, "failed to read chip id\n");
+		return -EIO;
+	}
+	if (chipid != AW8898_CHIPID) {
+		dev_err(&i2c->dev, "unsupported chip id: 0x%04x\n", chipid);
+		return -ENODEV;
+	}
+	aw->chipid  = chipid;
+	aw->flags   = 0x3; /* START_ON_MUTE | SKIP_IRQ — match factory */
+	dev_info(&i2c->dev, "aw8898_%s detected, flags=0x%x\n",
+		 is_left ? "l" : "r", aw->flags);
+
+	i2c_set_clientdata(i2c, aw);
+
+	/* Determine cfg name */
+	if (is_left)
+		snprintf(aw->cfg_name, sizeof(aw->cfg_name),
+			 "aw8898_l_cfg.bin");
+	else
+		snprintf(aw->cfg_name, sizeof(aw->cfg_name),
+			 "aw8898_r_cfg.bin");
+
+	/* Register codec with appropriate DAI name(s) */
+	if (is_left) {
+		ret = snd_soc_register_codec(&i2c->dev, &aw8898_codec_drv,
+					     aw8898_l_dai,
+					     ARRAY_SIZE(aw8898_l_dai));
+	} else {
+		ret = snd_soc_register_codec(&i2c->dev, &aw8898_codec_drv,
+					     aw8898_r_dai,
+					     ARRAY_SIZE(aw8898_r_dai));
+		g_aw8898_r = aw;       /* allow left to cross-call */
+	}
+	if (ret) {
+		dev_err(&i2c->dev, "snd_soc_register_codec failed: %d\n",
+			ret);
+		return ret;
+	}
+
+	dev_info(&i2c->dev, "%s_i2c_probe completed successfully!\n",
+		 is_left ? "aw8898_l" : "aw8898_r");
+	return 0;
+}
+
+static int aw8898_i2c_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_codec(&i2c->dev);
+	if (of_device_is_compatible(i2c->dev.of_node,
+				    "awinic,aw8898_r_smartpa"))
+		g_aw8898_r = NULL;
+	return 0;
+}
+
+static const struct of_device_id aw8898_dt_match[] = {
+	{ .compatible = "awinic,aw8898_l_smartpa" },
+	{ .compatible = "awinic,aw8898_r_smartpa" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aw8898_dt_match);
+
+static struct i2c_driver aw8898_i2c_driver = {
+	.driver = {
+		.name           = AW8898_L_I2C_NAME,
+		.of_match_table = of_match_ptr(aw8898_dt_match),
+	},
+	.probe  = aw8898_i2c_probe,
+	.remove = aw8898_i2c_remove,
+};
+
+static int __init aw8898_i2c_init(void)
+{
+	pr_info("aw8898_l driver version %s\n", AW8898_VERSION);
+	return i2c_add_driver(&aw8898_i2c_driver);
+}
+module_init(aw8898_i2c_init);
+
+static void __exit aw8898_i2c_exit(void)
+{
+	i2c_del_driver(&aw8898_i2c_driver);
+}
+module_exit(aw8898_i2c_exit);
+
+MODULE_DESCRIPTION("Awinic AW8898 L+R SmartPA codec driver");
+MODULE_LICENSE("GPL v2");

配套修改:

sound/soc/codecs/Kconfigsound/soc/codecs/Makefile:接入 AW8898,这个就不放修改了。

sound/soc/sprd/vbc-rxpx-codec-sc27xx.c:suspend 时忽略 inter Spk1 PA,保持第二扬声器 DAPM path。

@@ -110,6 +110,7 @@
 
 	snd_soc_dapm_ignore_suspend(&card->dapm, "inter HP PA");
 	snd_soc_dapm_ignore_suspend(&card->dapm, "inter Spk PA");
+	snd_soc_dapm_ignore_suspend(&card->dapm, "inter Spk1 PA");
 	snd_soc_dapm_ignore_suspend(&card->dapm, "inter Ear PA");
 
 	return 0;

sound/soc/sprd/sprd-asoc-card-utils-hook.c:OCP96011 调用受 Kconfig 保护,未启用该芯片时避免链接错误。

@@ -146,7 +146,9 @@
     audio_sense = (ucontrol->value.integer.value[0] & mask);
 
     if(headset_state && (audio_sense == 0)){
+#ifdef CONFIG_SND_SOC_OCP96011
 	  ocp96011_swap_audio_sense();
+#endif
     }
 	 
 
@@ -183,7 +185,9 @@
      if(on)
      {
 	 if(headset_state && audio_sense){
+#ifdef CONFIG_SND_SOC_OCP96011
 	     	ocp96011_switch_to_usb();
+#endif
 	 }
          aw87xxx_audio_scenne_load(AW87XXX_MUSIC_MODE,0);
          aw87xxx_audio_scenne_load(AW87XXX_MUSIC_MODE,1);
@@ -191,7 +195,9 @@
      else                                                                 
      {
 	 if(headset_state && audio_sense){
+#ifdef CONFIG_SND_SOC_OCP96011
 	        ocp96011_swap_audio_sense();
+#endif
 	 }
          aw87xxx_audio_scenne_load(AW87XXX_OFF_MODE,0);
          aw87xxx_audio_scenne_load(AW87XXX_OFF_MODE,1);

sound/soc/sprd/sprd-asoc-card-utils.c:codec defer 时回退 dummy codec,允许系统继续启动;风险是掩盖真实 codec probe 顺序问题并导致无声。

@@ -526,6 +526,15 @@
 			ret = asoc_sprd_card_dummy_codec_sel(codec, dai_link);
 			if (ret)
 				goto dai_link_of_err;
+		} else if (-EPROBE_DEFER == ret) {
+			/* codec driver not available, use dummy directly */
+			pr_info("%s: codec deferred, using dummy codec.\n",
+			     __func__);
+			dai_link->codec_dai_name = "snd-soc-dummy-dai";
+			dai_link->codec_name = "snd-soc-dummy";
+			of_node_put(dai_link->codec_of_node);
+			dai_link->codec_of_node = NULL;
+			ret = 0;
 		}
 	}

充电、电池和电源

这个属于是把 CONFIG_IFLYTEK_CHARGER_MANAGER 干的事复现了。

drivers/power/supply/charger-manager.cinclude/linux/power/charger-manager.h

  • watchdog feed 增加空指针检查,防止未完整 probe 时访问 cm->desc。
  • 跟踪 wakeup source 生命周期,probe 失败和 remove 时正确 trash,避免 UAF/泄漏。
  • battery info 失败时关闭 capacity tracking,允许系统启动。

drivers/power/supply/charger-manager.c

@@ -1841,6 +1841,9 @@
 	struct power_supply *psy;
 	int err, i;
 
+	if (!cm || !cm->desc)
+		return 0;
+
 	if (!cm->desc->wdt_interval)
 		return 0;
 
@@ -4618,6 +4621,7 @@
 	device_init_wakeup(&pdev->dev, true);
 	device_set_wakeup_capable(&pdev->dev, false);
 	wakeup_source_init(&cm->charge_ws, "charger_manager_wakelock");
+	cm->charge_ws_inited = true;
 	if (cm_event_type)
 		cm_notify_type_handle(cm, cm_event_type, cm_event_msg);
 
@@ -4636,11 +4640,11 @@
 	if (cm->track.cap_tracking) {
 		ret = cm_get_bat_info(cm);
 		if (ret) {
-			dev_err(&pdev->dev, "Failed to get battery information\n");
-			goto err_reg_sysfs;
+			dev_warn(&pdev->dev, "Failed to get battery information, disabling cap tracking\n");
+			cm->track.cap_tracking = false;
+		} else {
+			cm_track_capacity_init(cm);
 		}
-
-		cm_track_capacity_init(cm);
 	}
 
 	queue_delayed_work(system_power_efficient_wq, &cm->cap_update_work, CM_CAP_CYCLE_TRACK_TIME * HZ);
@@ -4649,6 +4653,14 @@
 	return 0;
 
 err_reg_sysfs:
+	/* If wakeup_source was init'd, trash it before cm is freed */
+	if (cm->charge_ws_inited)
+		wakeup_source_trash(&cm->charge_ws);
+	/* Remove from list to avoid use-after-free in cm_monitor_poller */
+	mutex_lock(&cm_list_mtx);
+	list_del(&cm->entry);
+	mutex_unlock(&cm_list_mtx);
+
 	for (i = 0; i < desc->num_charger_regulators; i++) {
 		struct charger_regulator *charger;
 
@@ -4688,6 +4700,8 @@
 
 	power_supply_unregister(cm->charger_psy);
 
+	wakeup_source_trash(&cm->charge_ws);
+
 	try_charger_enable(cm, false);
 
 	return 0;

include/linux/power/charger-manager.h

@@ -422,6 +422,7 @@
 	u32 charging_status;
 	struct cm_track_capacity track;
 	struct wakeup_source charge_ws;
+	bool charge_ws_inited;
 };
 
 #ifdef CONFIG_CHARGER_MANAGER

drivers/power/supply/power_supply_sysfs.c:导出 feed_watchdog 属性,满足 charger-manager/vendor userspace 接口。

@@ -163,6 +163,8 @@
 	else if (off == POWER_SUPPLY_PROP_SCOPE)
 		return sprintf(buf, "%s\n",
 			       power_supply_scope_text[value.intval]);
+	else if (off == POWER_SUPPLY_PROP_FEED_WATCHDOG)
+		return sprintf(buf, "%d\n", value.intval);
 	else if (off >= POWER_SUPPLY_PROP_MODEL_NAME)
 		return sprintf(buf, "%s\n", value.strval);
 
@@ -306,6 +308,7 @@
 	POWER_SUPPLY_ATTR(model_name),
 	POWER_SUPPLY_ATTR(manufacturer),
 	POWER_SUPPLY_ATTR(serial_number),
+	POWER_SUPPLY_ATTR(feed_watchdog),
 };
 
 static struct attribute *

drivers/power/supply/sc2703-charger.c:增加限流、FGU 和寄存器诊断,用于确认充电状态。

@@ -903,11 +903,52 @@
 	return info->charger_type == POWER_SUPPLY_CHARGE_TYPE_FAST;
 }
 
+static void sc2703_charger_diag(struct sc2703_charger_info *info,
+				bool present, int input_limit, int charge_current,
+				int result)
+{
+	static const unsigned int regs[] = {
+		SC2703_STATUS_A, SC2703_STATUS_B, SC2703_STATUS_C,
+		SC2703_STATUS_D, SC2703_EVENT_A, SC2703_EVENT_B,
+		SC2703_EVENT_C, SC2703_EVENT_D, SC2703_DCDC_CTRL_A,
+		SC2703_DCDC_CTRL_D, SC2703_CHG_CTRL_A, SC2703_CHG_CTRL_B,
+	};
+	struct power_supply *psy;
+	union power_supply_propval val;
+	u32 values[ARRAY_SIZE(regs)];
+	int voltage = 0, battery_current = 0;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++) {
+		ret = regmap_read(info->regmap, regs[i], &values[i]);
+		if (ret)
+			values[i] = ~0U;
+	}
+
+	psy = power_supply_get_by_name(SC2703_BATTERY_NAME);
+	if (psy) {
+		if (!power_supply_get_property(psy,
+					       POWER_SUPPLY_PROP_VOLTAGE_NOW, &val))
+			voltage = val.intval;
+		if (!power_supply_get_property(psy,
+					       POWER_SUPPLY_PROP_CURRENT_NOW, &val))
+			battery_current = val.intval;
+		power_supply_put(psy);
+	}
+
+	dev_info_ratelimited(info->dev,
+		"sc2703-diag: limit=%u present=%d charging=%d input=%d charge=%d ret=%d fgu_uv=%d fgu_ua=%d status=%02x/%02x/%02x/%02x event=%02x/%02x/%02x/%02x dcdc=%02x/%02x chg=%02x/%02x\n",
+		info->limit, present, info->charging, input_limit, charge_current,
+		result, voltage, battery_current, values[0], values[1], values[2], values[3],
+		values[4], values[5], values[6], values[7], values[8], values[9],
+		values[10], values[11]);
+}
+
 static void sc2703_charger_work(struct work_struct *data)
 {
 	struct sc2703_charger_info *info =
 		container_of(data, struct sc2703_charger_info, work);
-	int limit_cur, cur, ret;
+	int limit_cur = 0, cur = 0, ret = 0;
 	bool present = sc2703_charger_is_bat_present(info);
 
 	mutex_lock(&info->lock);
@@ -955,6 +996,7 @@
 
 out:
 	mutex_unlock(&info->lock);
+	sc2703_charger_diag(info, present, limit_cur, cur, ret);
 	dev_info(info->dev, "battery present = %d, charger type = %d\n",
 		 present, info->usb_phy->chg_type);
 	cm_notify_event(info->psy_usb, CM_EVENT_CHG_START_STOP, NULL);

drivers/power/supply/sc27xx_fuel_gauge.c:失败日志包含 ret、battery ID、节点名,定位 bat_id=1 映射错误。为什么这么做后面会讲。

@@ -1420,7 +1420,8 @@
 	bat_id = get_battery_id();
 	ret = power_supply_get_battery_info(data->battery, &info, bat_id);
 	if (ret) {
-		dev_err(data->dev, "failed to get battery information\n");
+		dev_err(data->dev, "failed to get battery information (ret=%d bat_id=%d of_node=%s)\n",
+			ret, bat_id, data->battery->of_node ? data->battery->of_node->name : "NULL");
 		return ret;
 	}

drivers/nvmem/sc27xx-efuse.c:hwspinlock 缺失时允许无锁运行,可绕过错误 DT/探测顺序。

@@ -88,6 +88,9 @@
 
 	mutex_lock(&efuse->mutex);
 
+	if (!efuse->hwlock)
+		return 0;
+
 	ret = hwspin_lock_timeout_raw(efuse->hwlock,
 				      SC27XX_EFUSE_HWLOCK_TIMEOUT);
 	if (ret) {
@@ -101,7 +104,8 @@
 
 static void sc27xx_efuse_unlock(struct sc27xx_efuse *efuse)
 {
-	hwspin_unlock_raw(efuse->hwlock);
+	if (efuse->hwlock)
+		hwspin_unlock_raw(efuse->hwlock);
 	mutex_unlock(&efuse->mutex);
 }
 
@@ -209,6 +213,8 @@
 	const struct sc27xx_efuse_variant_data *pdata;
 	int ret;
 
+	dev_info(&pdev->dev, "sc27xx-efuse probe started\n");
+
 	pdata = of_device_get_match_data(&pdev->dev);
 	if (!pdata) {
 		dev_err(&pdev->dev, "No matching driver data found\n");
@@ -233,14 +239,13 @@
 
 	ret = of_hwspin_lock_get_id(np, 0);
 	if (ret < 0) {
-		dev_err(&pdev->dev, "failed to get hwspinlock id\n");
-		return ret;
-	}
-
-	efuse->hwlock = hwspin_lock_request_specific(ret);
-	if (!efuse->hwlock) {
-		dev_err(&pdev->dev, "failed to request hwspinlock\n");
-		return -ENXIO;
+		dev_warn(&pdev->dev, "failed to get hwspinlock id, running without lock\n");
+		efuse->hwlock = NULL;
+	} else {
+		efuse->hwlock = hwspin_lock_request_specific(ret);
+		if (!efuse->hwlock) {
+			dev_warn(&pdev->dev, "failed to request hwspinlock, running without lock\n");
+		}
 	}
 
 	mutex_init(&efuse->mutex);

drivers/hwspinlock/sprd_hwspinlock.c:unnamed clock fallback,改为 device initcall。让 clock controller 先探测,并兼容缺少 clock-names 的 DT:

@@ -111,6 +111,10 @@
 
 	sprd_hwlock->clk = devm_clk_get(&pdev->dev, "enable");
 	if (IS_ERR(sprd_hwlock->clk)) {
+		/* Retry without clock name, some DTs omit clock-names */
+		sprd_hwlock->clk = devm_clk_get(&pdev->dev, NULL);
+	}
+	if (IS_ERR(sprd_hwlock->clk)) {
 		dev_err(&pdev->dev, "get hwspinlock clock failed!\n");
 		return PTR_ERR(sprd_hwlock->clk);
 	}
@@ -132,7 +136,8 @@
 				   &sprd_hwspinlock_ops, 0, SPRD_HWLOCKS_NUM);
 	if (ret) {
 		pm_runtime_disable(&pdev->dev);
-		clk_disable_unprepare(sprd_hwlock->clk);
+		if (sprd_hwlock->clk)
+			clk_disable_unprepare(sprd_hwlock->clk);
 		return ret;
 	}
 
@@ -145,7 +150,8 @@
 
 	hwspin_lock_unregister(&sprd_hwlock->bank);
 	pm_runtime_disable(&pdev->dev);
-	clk_disable_unprepare(sprd_hwlock->clk);
+	if (sprd_hwlock->clk)
+			clk_disable_unprepare(sprd_hwlock->clk);
 	return 0;
 }
 
@@ -171,7 +177,10 @@
 {
 	return platform_driver_register(&sprd_hwspinlock_driver);
 }
-postcore_initcall(sprd_hwspinlock_init);
+/* Use device_initcall so the clock controller (also device_initcall)
+ * probes first via deferred probing. postcore_initcall is too early.
+ */
+device_initcall(sprd_hwspinlock_init);
 
 static void __exit sprd_hwspinlock_exit(void)
 {

C8Pro 的 FGU、SC2703 和 charger-manager 都使用:

monitored-battery = <0x85 0x85>;

两个 phandle 分别覆盖 battery ID 0/1。本来是想要通过 fuel-gauge 驱动回退到 ID 0 快速的一劳永逸解决这个问题的,但是想了想还是要规范一些,毕竟是 DT 的问题,该修就修,反正不这么修你是读不到电量的。drivers/power/supply/sc27xx_fuel_gauge.c 的修改就是为了便于诊断。

WLAN、Bluetooth 和 sensor hub

这个属于是在兼容原厂了。

drivers/soc/sprd/wifi_stub.cdrivers/soc/sprd/Makefile:导出wifi6_oob_wakelock,满足原厂 bcmdhd 闭源模块的未解析符号。

drivers/soc/sprd/wifi_stub.c

@@ -0,0 +1,7 @@
+/*
+ * Compatibility symbol required by the factory bcmdhd module.
+ */
+#include <linux/export.h>
+
+int wifi6_oob_wakelock;
+EXPORT_SYMBOL(wifi6_oob_wakelock);

drivers/soc/sprd/Makefile

@@ -16,3 +16,4 @@
 obj-y				+= autotest/
 obj-$(CONFIG_SPRD_HWFEATURE)	+= sprd_hwfeature.o
 obj-$(CONFIG_SPRD_PIKE2_SLEEP)  += sprd_pike2_sleep.o
+obj-y += wifi_stub.o

ernel/jump_label.c:static_key_slow_inc/dec 从 GPL-only 改为普通 export,允许非 GPL vendor WLAN 模块使用 static key。

ernel/jump_label.c

@@ -124,7 +124,7 @@
 	static_key_slow_inc_cpuslocked(key);
 	cpus_read_unlock();
 }
-EXPORT_SYMBOL_GPL(static_key_slow_inc);
+EXPORT_SYMBOL(static_key_slow_inc);
 
 void static_key_enable_cpuslocked(struct static_key *key)
 {
@@ -227,7 +227,7 @@
 	STATIC_KEY_CHECK_USE();
 	__static_key_slow_dec(key, 0, NULL);
 }
-EXPORT_SYMBOL_GPL(static_key_slow_dec);
+EXPORT_SYMBOL(static_key_slow_dec);
 
 void static_key_slow_dec_cpuslocked(struct static_key *key)
 {

drivers/misc/sprdwcn/platform/wcn_procfs.c:mdbg_proc 为空时返回 0,避免 WCN procfs 尚未初始化时崩溃。

@@ -1145,6 +1145,8 @@
 
 int get_loopcheck_status(void)
 {
+	if (!mdbg_proc)
+		return 0;
 	return mdbg_proc->fail_count;
 }

drivers/iio/sprd_hub/shub_core.c :active scan mask 日志改为 ratelimited debug,避免传感器未启用时刷屏。

@@ -760,7 +760,7 @@
 			   sensor->indio_dev->masklength))) {
 		iio_push_to_buffers(sensor->indio_dev, event);
 	} else if (!sensor->indio_dev->active_scan_mask) {
-		dev_err(&sensor->sensor_pdev->dev,
+		dev_dbg_ratelimited(&sensor->sensor_pdev->dev,
 			"active_scan_mask = NULL, event might be missing\n");
 	}

还有一个关键 DT 修改:

vddwcn: LDO_VDDWCN {
	regulator-always-on;
};

原因是 uboot 已使能共享 WCN 电源,kernel regulator core 若认为无人使用会关闭它,
导致 BCM4362/BCM43752 PCIe WLAN 消失。该值是 iClass C8Pro 专用。

内核标识和模块 ABI 兼容

修改它们才能正常加载 vendor 中的 .ko 文件,不至于被拦下来。

这里我实现了一个 Kconfig 项,名为 CONFIG_MITOCHODRIA_MODULE_SKIP_VERMAGIC_VERSION,Kconfig 修改我就不放了,它的功能是忽略 vermagic 第一段 UTS release,让它可以加载原厂闭源模块。

kernel/module.c

 static inline int same_magic(const char *amagic, const char *bmagic,
 			     bool has_crcs)
 {
+#ifdef CONFIG_MITOCHODRIA_MODULE_SKIP_VERMAGIC_VERSION
+	amagic += strcspn(amagic, " ");
+	bmagic += strcspn(bmagic, " ");
+#else
 	if (has_crcs) {
 		amagic += strcspn(amagic, " ");
 		bmagic += strcspn(bmagic, " ");
 	}
+#endif
 	return strcmp(amagic, bmagic) == 0;
 }
 #else
@@ -1373,6 +1378,10 @@
 static inline int same_magic(const char *amagic, const char *bmagic,
 			     bool has_crcs)
 {
+#ifdef CONFIG_MITOCHODRIA_MODULE_SKIP_VERMAGIC_VERSION
+	amagic += strcspn(amagic, " ");
+	bmagic += strcspn(bmagic, " ");
+#endif
 	return strcmp(amagic, bmagic) == 0;
 }
 #endif /* CONFIG_MODVERSIONS */

include/linux/vermagic.h:在 CONFIG_MODVERSIONS=n 时仍输出字符串 modversions,目的是匹配原厂模块的 vermagic,这不等于实际启用 CRC 检查。

@@ -20,7 +20,11 @@
 #ifdef CONFIG_MODVERSIONS
 #define MODULE_VERMAGIC_MODVERSIONS "modversions "
 #else
-#define MODULE_VERMAGIC_MODVERSIONS ""
+/* Always include "modversions" for vendor module vermagic compat.
+ * CRC checking is still disabled (check_version always returns 1
+ * when CONFIG_MODVERSIONS=n), the string just needs to match.
+ */
+#define MODULE_VERMAGIC_MODVERSIONS "modversions "
 #endif
 #ifndef MODULE_ARCH_VERMAGIC
 #define MODULE_ARCH_VERMAGIC ""

kernel/jump_label.c

@@ -124,7 +124,7 @@
 	static_key_slow_inc_cpuslocked(key);
 	cpus_read_unlock();
 }
-EXPORT_SYMBOL_GPL(static_key_slow_inc);
+EXPORT_SYMBOL(static_key_slow_inc);
 
 void static_key_enable_cpuslocked(struct static_key *key)
 {
@@ -227,7 +227,7 @@
 	STATIC_KEY_CHECK_USE();
 	__static_key_slow_dec(key, 0, NULL);
 }
-EXPORT_SYMBOL_GPL(static_key_slow_dec);
+EXPORT_SYMBOL(static_key_slow_dec);
 
 void static_key_slow_dec_cpuslocked(struct static_key *key)
 {

CONFIG_MODVERSIONS=n 是为了不生成/不校验当前内核的符号 CRC,让 check_version() 直接返回成功,使得厂商模块的 __versions CRC不再构成加载障碍。

SELinux 和 verified boot 策略

这个章节我本来打算不写的,但是还是想骂一下展讯这个特殊的 SELinux 实现,不能关掉,也不能在内核侧设置宽容。一气之下我实现了一个 Kconfig 项,名为 CONFIG_MITOCHODRIA_SELINUX_STAY_PERMISSIVE,开启它会:

  • 启动时强制 selinux_enforcing=0。
  • 忽略 enforcing=1。
  • 接收但不执行 userspace 切换 enforcing 的请求;
  • 仍加载 policy、执行 AVC 检查并记录 denial。

仍然是不放 Kconfig 修改。

security/selinux/hooks.c

@@ -104,6 +104,7 @@
 #ifdef CONFIG_SECURITY_SELINUX_DEVELOP
 int selinux_enforcing;
 
+#ifndef CONFIG_MITOCHODRIA_SELINUX_STAY_PERMISSIVE
 static int __init enforcing_setup(char *str)
 {
 	unsigned long enforcing;
@@ -113,6 +114,7 @@
 }
 __setup("enforcing=", enforcing_setup);
 #endif
+#endif
 
 #ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
 int selinux_enabled = CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE;
@@ -6725,6 +6727,12 @@
 	else
 		printk(KERN_DEBUG "SELinux:  Starting in permissive mode\n");
 
+#ifdef CONFIG_MITOCHODRIA_SELINUX_STAY_PERMISSIVE
+	/* Force permissive mode regardless of command line or userspace */
+	selinux_enforcing = 0;
+	pr_warn("SELinux: MITOCHODRIA_SELINUX_STAY_PERMISSIVE is set - forcing permissive mode\n");
+#endif
+
 	return 0;
 }

security/selinux/selinuxfs.c

@@ -157,6 +157,13 @@
 			new_value, selinux_enforcing,
 			from_kuid(&init_user_ns, audit_get_loginuid(current)),
 			audit_get_sessionid(current));
+#ifdef CONFIG_MITOCHODRIA_SELINUX_STAY_PERMISSIVE
+		/* Force permissive: ignore attempts to switch to enforcing */
+		if (new_value) {
+			length = count;
+			goto out;
+		}
+#endif
 		selinux_enforcing = new_value;
 		if (selinux_enforcing)
 			avc_ss_reset(0);

还需要在 defconfig 修改:

- CONFIG_SECURITY_SELINUX_DISABLE=n
+ CONFIG_SECURITY_SELINUX_DISABLE=y

使其允许启动时禁用 SELinux。

要关闭 dm-verify 的话,需要在 defconfig 里面关闭:

- CONFIG_DM_VERITY
- CONFIG_DM_VERITY_FEC
+ CONFIG_DM_VERITY
+ CONFIG_DM_VERITY_FEC

要彻底关闭在内核层次的 AVB 的话,修改 DT,删除 android,vbmeta,并将 vendor/product 的 fsmgr_flags 改为:

- fsmgr_flags = "wait,avb";
+ fsmgr_flags = "wait";

结语

以上修改小部分具有一定的风险,在它们没被发现之前,我们就当做不存在吧,呵呵。

对了,我在 Github 开源了 Mitochodria Kernel for iFLYTEKCB iClass C8Pro

没有标签
首页      未分类      【全网首发】如何为 iFLYTEKCB iClass C8Pro 移植 JingPad BSP 中的 kernel 4.14
Avatar photo

worryzu

文章作者

发表回复

textsms
account_circle
email

  • SK_Seveyn

    主播好牛逼呀。

    1 月前 回复

LinearTeam

【全网首发】如何为 iFLYTEKCB iClass C8Pro 移植 JingPad BSP 中的 kernel 4.14
警告:关于文章完整性 本篇文章只会讲出能保证你插电能正常启动的修改,其他问题请自己探索,谢谢!本篇文章不具备任何售后! 前言 《易经·乾卦》有言: “同声相应,同气相…
扫描二维码继续阅读
2026-08-11