
环境:小米平板 5(codename nabu,SM8150,三星 UFS
KLUFG8RHGB-B0E1)· Ubuntu 26.04 ARM64 内核:6.14.11-nabu-tmm(日常)/6.17.0-xiaomi-nabu-tmm-UFS-slowdown+(备用) 时间:2026-11-04 授权:文中驱动源码来自 sm8150-mainline/linux(GPL-2.0,作者 Nicola Guerrera)
TL;DR
- 平板顶部的磁吸无线充电芯片是 IDT P9418(实际读出的 Chip ID 是
0x9415),挂在i2c-3 / 0x3b。 - 6.14.11 内核根本没有编这个驱动,所以笔吸上去毫无反应——不是硬件坏了。
- 6.17 内核自带驱动,但那个内核对这台机器只有 5V 慢充(平板充电几乎不可用),且 UFS 被降频。
- 最终方案:把 6.17 的
idtp9418驱动移植到 6.14.11,自己编译一个 out-of-tree 模块, 让“快充 + 手写笔充电”共存。驱动侧改了两处,另外给上游驱动补了一个 SoC 重试逻辑。 - 顺带打通:手写笔蓝牙配对、GATT 信息读取、桌面小组件与 GNOME 顶栏插件的电量展示。
1. 背景与硬件
小米平板 5 的 Smart Pen 通过磁吸吸附在平板顶部短边,由平板内的线圈无线供电。Linux 下这条链路涉及三部分:
| 部分 | 说明 |
|---|---|
| 无线充芯片 | IDT P9418,i2c-3 地址 0x3b,设备树节点 idtp9418@3b |
| 霍尔传感器 | 检测笔是否吸附:gpio101(active-high)、gpio134(active-low) |
| 驱动 | idtp9418(drivers/power/supply/idtp9418.c),2025-10-23 才进 sm8150-mainline |
相关 GPIO(TLMM,/sys/kernel/debug/gpio 可看):
| 功能 | GPIO |
|---|---|
| IRQ | 95 |
| 使能 enable | 3 |
| 升压 boost-enable | 47 |
| 霍尔 high / low | 101 / 134 |
| WPC-DET(旧 dtb 有,驱动没用) | 94 |
驱动暴露的节点:
/sys/class/power_supply/idtp9418/→online、capacity、charge_control_end_threshold/sys/bus/i2c/devices/3-003b/→chip_version、chip_status(vout/iin/temp/soc)、chip_powered
2. 第一层:为什么“没反应”
设备树里有节点,不代表有驱动。检查:
ls /sys/bus/i2c/devices/3-003b/ # 设备存在
readlink /sys/bus/i2c/devices/3-003b/driver # 悬空 → 没有驱动绑定
modinfo idtp9418 # 6.14.11 上:Module not found
sudo i2cdetect -y 3 # 0x3b 无应答(芯片没被使能,正常)
结论:6.14.11 的模块目录里没有 idtp9418.ko,内核也没编进去。设备树节点成了“孤儿”。
小提示:芯片未上电时 i2c 不 ACK 是正常现象,不能据此判断硬件坏。
3. 第二层:6.17 上跑通
ESP 里已经装了 6.17 的 UKI(EFI/ubuntu/6.17.0-UFS-slowdown+.efi),rEFInd 选它启动,然后:
sudo modprobe idtp9418
笔吸上去(笔尖朝左),dmesg 立刻出现:
[idtp] Pen attached! Turning on reverse charging...
reverse charging start success
[charge-monitor] pen=1 soc=5
/sys/class/power_supply/idtp9418 出现,online=1,笔从 0% 一路充到 90%(约 18 分钟)。
为什么 6.14.11 没有、6.17 有?
- 6.14.11 构建于 2025-08-14,驱动首个提交在 2025-10-23 —— 时间上就不可能有。
- 6.17 构建于 2026-04-11,自然包含。
- 更麻烦的是设备树绑定也换了:6.14 的 dtb 用小米 BSP 风格属性名
(
idt,irq/hall,int3/hall,int4/idt,reverse-enable/idt,reverse-booset-enable), 驱动读的是新绑定(irq-gpios/pen-det-active-high-gpios/ … /enable-gpios/boost-enable-gpios)。 所以直接拷.ko也 probe 不起来。
4. 笔的坑:SoC 0xFF、充电上限、电量口径
4.1 SoC 读到 0xFF 就停充
驱动 charge-monitor 每 2 秒读一次笔的电量,原逻辑:
if (!attached || soc < 0 || soc >= di->charge_limit)
/* 停充 */
笔在刚上电的一小段时间内不响应电量查询(寄存器 0x3a 返回 0xFF),
于是驱动 2 秒内就判定失败并停充——表现就是“吸上去闪一下就没反应”。
4.2 充电上限 85%
charge_control_end_threshold 默认 85,写值上限 95:
echo 95 | sudo tee /sys/class/power_supply/idtp9418/charge_control_end_threshold
笔的电量按 10% 台阶上报(80 → 90),会一步跨过 85,所以在 90 停下是正常的。
4.3 停充后 capacity 归 0
驱动的 POWER_SUPPLY_PROP_CAPACITY 在 !is_charging 时直接返回 0,所以停充后读 capacity 是 0,
不是电量真的掉了。
4.4 两种电量的口径
| 来源 | 什么时候有 | 特点 |
|---|---|---|
WPC(idtp9418/capacity) |
只在无线充电时 | 实时,但粒度粗、会跳(0→5→10→20→40) |
BLE(GATT 0x2A19 / UPower) |
只要蓝牙连着 | 细,但充电时可能长时间不刷新 |
实测两者会差 10~20%,充满后(90%)又能对上——是刷新节奏不同,不是两套电量计。
5. 蓝牙:配对与能读到的信息
为什么 MIUI 吸上自动连,Linux 不会:MIUI 有小米笔系统服务,霍尔检测到吸附就主动唤醒并配对; Linux/BlueZ 没有这个服务,笔平时不广播(省电),必须长按笔上的按键约 5 秒进入配对模式。
bluetoothctl --timeout 120 scan on
# 长按笔按键,出现 Xiaomi Smart Pen 后:
bluetoothctl pair C0:2B:3D:70:C6:E9
bluetoothctl trust C0:2B:3D:70:C6:E9
bluetoothctl connect C0:2B:3D:70:C6:E9
笔的蓝牙地址是随机地址,重新配对会变,脚本里别硬编码,按名字匹配。
能读到的 GATT 信息(GATT 读需要 root,BlueZ 的 D-Bus 策略):
sudo busctl --system call org.bluez \
/org/bluez/hci0/dev_XX/serviceXXXX/charXXXX \
org.bluez.GattCharacteristic1 ReadValue 'a{sv}' 0
| 服务/特征 | 内容 | 实测值 |
|---|---|---|
| 0x180F / 0x2A19 | 电量 | 20% |
| 0x180A / 0x2A29 | 厂商 | Xiaomi |
| 0x180A / 0x2A24 | 型号 | wPI07U81PC |
| 0x180A / 0x2A26 | 固件版本 | 00.00.0703 |
| 0x180A / 0x2A28 | 软件版本 | 00.00.0703 |
| 0x180A / 0x2A50 | PnP ID | vendor 0x1915(Nordic)/ product 0xEAEA / ver 0x0001 |
| 0x1812 HID | Report Map | Report 0x10:4 个按键位;0x21:1 字节 feature |
| 0xFE59 | Nordic Secure DFU | 固件升级服务,别乱刷 |
没有序列号(0x2A25)和硬件版本(0x2A27);压感/倾角不走蓝牙(走 NVT 触控)。
6. 第三层:给 6.14.11 编驱动
6.1 准备
# 1) 保存当前 6.14.11 的 config(IKCONFIG)
zcat /proc/config.gz > ~/nabu-build/config-6.14.11
# 2) 下载 6.14.11 源码(约 143MB)
curl -L -o linux-6.14.11.tar.xz https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.14.11.tar.xz
tar -xJf linux-6.14.11.tar.xz
# 3) 配置 + 准备模块构建(本机 arm64 原生编译)
cd linux-6.14.11
cp ../config-6.14.11 .config
make ARCH=arm64 olddefconfig
make -j8 ARCH=arm64 modules_prepare
make -s kernelrelease # 6.14.11-nabu-tmm
关键点:kernelrelease 必须和运行内核一致(6.14.11-nabu-tmm),否则模块加载时 vermagic 不匹配。
本机 CONFIG_MODVERSIONS=n、CONFIG_MODULE_SIG=n,所以不需要 Module.symvers 的 CRC、也不校验签名。
6.2 设备树绑定差异(改驱动,不改 dtb)
6.14 的 dtb 用旧名字,所以把驱动的 GPIO 获取换成“按旧属性名查找”。
6.14 没有 devm_gpiod_get_from_of_node,改用 legacy API:
#include <linux/gpio.h>
#include <linux/of_gpio.h>
struct { const char *name; const char *prop; struct gpio_desc **gpio; unsigned long flags; }
gpio_map[] = {
{"irq", "idt,irq", &di->gpios.idt_irq, GPIOF_IN},
{"enable", "idt,reverse-enable", &di->gpios.enable, GPIOF_OUT_INIT_HIGH},
{"boost-enable", "idt,reverse-booset-enable",&di->gpios.boost_enable, GPIOF_OUT_INIT_HIGH},
{"pen-det-active-high", "hall,int3", &di->gpios.pen_det_active_high, GPIOF_IN},
{"pen-det-active-low", "hall,int4", &di->gpios.pen_det_active_low, GPIOF_IN},
};
for (int i = 0; i < ARRAY_SIZE(gpio_map); i++) {
int gpio = of_get_named_gpio(di->dev->of_node, gpio_map[i].prop, 0);
if (gpio < 0) return gpio;
ret = devm_gpio_request_one(di->dev, gpio, gpio_map[i].flags, gpio_map[i].name);
if (ret) return ret;
*gpio_map[i].gpio = gpio_to_desc(gpio);
}
注意
idt,reverse-booset-enable里的 booset 是 dtb 里就有的拼写,别手滑改掉。
6.3 编译与加载
源码树里没有 Module.symvers 时,modpost 会把所有符号报成 undefined。
MODVERSIONS=n 时这些符号在加载阶段由内核解析,所以可以直接:
make -C ~/nabu-build/linux-6.14.11 M=$PWD ARCH=arm64 KBUILD_MODPOST_WARN=1 modules
产物 idtp9418.ko 的 vermagic 必须是 6.14.11-nabu-tmm SMP preempt mod_unload aarch64。
安装并加载:
sudo cp idtp9418.ko /lib/modules/6.14.11-nabu-tmm/extra/
sudo depmod -a 6.14.11-nabu-tmm
sudo insmod idtp9418.ko # 或 modprobe
加载后驱动自动绑定到 3-003b,/sys/class/power_supply/idtp9418 出现。
开机自动加载:/etc/modules-load.d/charge-stylus.conf 写一行 idtp9418。
6.4 重试补丁 v2
解决 4.1 的“0xFF 立刻停充”:
/* 笔刚上电时 SoC 可能暂时读不到(0xFF),先重试 ~30 秒再放弃 */
if (soc >= 0)
di->soc_retry = 0;
if (!attached || soc >= di->charge_limit || (soc < 0 && ++di->soc_retry > 15)) {
/* 停充,soc_retry = 0 */
}
实测:吸附后第 2 秒笔就报出 soc=20,充电继续。
7. 展示层:终端小组件与顶栏插件
7.1 终端小组件
~/.local/bin/watch_battery.sh(桌面上有启动器“电池实时电压”):
- 充电中:
● 充电中 10% 0.85 W 54 °C 上限 85% - 未充电:
○ 未充电(蓝牙电量 80%) - 没有驱动:
不可用(本内核无 idtp9418 驱动)
功率 = vout × iin(从 chip_status 读,raw 值按 mV/mA 解释)。
注意:驱动每读一次 chip_status 会 dev_info 打日志,所以每 60 秒才读一次。
7.2 GNOME 顶栏插件
本地扩展 batteryvitals@local,在电池数值旁显示 笔XX%:
- 充电中:绿色,用 WPC 值
- 未充电但蓝牙连着:灰色,用 BLE 值(
upower,3 分钟刷新一次) - 都没有:隐藏(原来的
笔—容易被看成“笔一”,已去掉)
大坑:GNOME Shell 对扩展 JS 有会话级模块缓存。
gnome-extensions disable/enable、重装 zip、改version都不会重新加载代码, 必须注销重登(或重启)才生效。
8. 两个内核的对比与取舍
| 6.14.11-nabu-tmm | 6.17.0-UFS-slowdown+ | |
|---|---|---|
| 构建时间 | 2025-08-14 | 2026-04-11 |
| 充电栈 | ln8000 电荷泵 + PD 9V/3A(快) | smb5,约 5V(慢,平板充满要 8~10 小时) |
| UFS 时钟 | 300MHz | 201.5MHz(为三星 UFS 稳定性降频) |
| 手写笔驱动 | 无(可自编模块补上) | 自带 |
| 电流符号 | 负 = 充电 | 正 = 充电(相反) |
| 模块数 | 594 | 656(+76 / −14) |
结论:6.14.11 自编模块后是“快充 + 笔充电”两全,日常用它;6.17 退居备用。
9. 踩坑清单 / 给后来者
- 设备树有节点 ≠ 有驱动:先看
/sys/bus/i2c/devices/*/driver是否悬空。 - 先确认内核版本与驱动提交时间的先后:6.14.11 比驱动早两个月,不可能有。
- DT 绑定在新旧树之间会漂移:BSP 属性名 vs mainline 属性名,移植时要么改驱动、要么重打 dtb/UKI。
devm_gpiod_get_from_of_node在 6.14 已不存在:用of_get_named_gpio + devm_gpio_request_one + gpio_to_desc。- vermagic 必须逐字匹配;
CONFIG_MODVERSIONS=n时不需要 Module.symvers,KBUILD_MODPOST_WARN=1可绕过 modpost。 - 驱动可能在笔刚上电时读到 0xFF:给重试窗口,别第一次失败就放弃。
- 无线充的 SoC 是粗粒度、会跳;BLE 电量更细但充电时可能不刷新。两者差 10~20% 正常。
- GNOME 扩展改完必须重新登录,disable/enable 不重载模块。
- 启动额度:nabu 的 ABL 每启动一次扣
slot-retry-count,7 次上限;频繁切内核有风险, 能接 PC 时用fastboot set_active a补满。 - 6.17 在三星 UFS 上要降频补丁(201.5MHz),否则可能黑屏/不稳。
10. 附录
10.1 常用命令
# 笔是否在充
cat /sys/class/power_supply/idtp9418/{online,capacity}
# 芯片遥测(vout/iin/temp/soc,raw)
cat /sys/bus/i2c/devices/3-003b/chip_status
# 芯片版本(OTP/APP 固件版本)
cat /sys/bus/i2c/devices/3-003b/chip_version
# 驱动日志
sudo dmesg | grep -i idtp | tail -20
# 蓝牙电量
upower -e | grep bluetooth
upower -i /org/freedesktop/UPower/devices/bluetooth_generic_dev_XX
# 模块
modinfo idtp9418
lsmod | grep idtp
10.2 关键文件
| 文件 | 作用 |
|---|---|
/etc/modules-load.d/charge-stylus.conf |
开机自动加载 idtp9418 |
/lib/modules/6.14.11-nabu-tmm/extra/idtp9418.ko |
自编模块 |
~/nabu-build/{idtp9418.c,idtp9418.h} |
打补丁后的驱动源码 |
~/.local/bin/watch_battery.sh |
终端小组件 |
~/.local/share/gnome-shell/extensions/batteryvitals@local/ |
顶栏插件 |
10.3 实测数据
| 场景 | 数值 |
|---|---|
| 无线充输出 | 0.85W(6.75V × 128mA)→ 2.3W(9.2V × 248mA) |
| 笔充电 | 0% → 90% 约 18 分钟 |
| 芯片温度 | 48 ~ 57 °C(无线充工作范围) |
| 平板快充(6.14) | 9V/3A PD PPS,电池 −4.2A,约 2%/分钟 |
本文记录于 2026-11-04,所有命令均在小米平板 5(nabu)+ Ubuntu 26.04 上实测。 驱动源码来自 sm8150-mainline 社区,遵循 GPL-2.0。
本文地址:https://blog.zhaokugua.com/?post=122
版权声明:若无注明,本文皆为“赵苦瓜のBlog~”原创,转载请保留文章出处。







