警告:关于文章完整性
本篇文章只会讲出能保证你插电能正常启动的修改,其他问题请自己探索,谢谢!本篇文章不具备任何售后!
前言
《易经·乾卦》有言:
“同声相应,同气相求。”
选择相近的硬件平台(同架构、同 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 里写的 codename 是 ud710_21c10_native,可是我们的 BSP 里面没有完整 target,这下怎么办?很简单,在 BSP 里面找个可用的替代品。但是在我们这,它在这个 BSP 里面对应的可用的 codename 都是一代代人传下来的,没有人告诉我为什么他的替代品是 ud710_2h10_native,可能是因为他们都是 UD710-AB 而且 iClass C6 这个同机型的 codename 是 ud710_2h10_native?
但是我要告诉你,iClass C8Pro 是一辆登记型号为 ud710_21c10_native 的定制车;我们借用 ud710_2h10_native 的维修手册和工位来编译维护,但它的发动机供油控制这一页,很可能抄自 ud710_5h10 的图纸,哈哈,我被这个坑惨了。为什么我敢这么说?ud710_5h10 写错的地方 iClass C8Pro 的 DT 错的都一模一样,笑死我了。在 BSP 层,iClass C8Pro 对应的可用 codename 是 ud710_2h10_native;在 kernel 配置层,iClass C8Pro 的关键 kernel 配置血缘指向的是 ud710_5h10。
有幸亲眼见证老一辈人确定的方法。在确认主板为特殊定制的 codename 为 tx20_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 driver;CONFIG_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 和调度器
这个东西不修一下导致的后果在我这里还挺严重的:
- 有几率卡开机动画第二阶段。原厂也有这个问题,只不过发生的概率小。
- 会让 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/Kconfig、sound/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.c、include/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.c、drivers/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。
发表回复