欢迎食用『主界面』~,这里是赵苦瓜的看板娘desu~

#
小米平板 5(nabu)手写笔无线充电:从“吸上去没反应”到 6.14 内核自编驱动
首页 > >    作者:赵苦瓜   2026年11月4日 22:50 星期三   热度:84°   百度已收录  
时间:2026-11-4 22:50   热度:84° 

环境:小米平板 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. 踩坑清单 / 给后来者

  1. 设备树有节点 ≠ 有驱动:先看 /sys/bus/i2c/devices/*/driver 是否悬空。
  2. 先确认内核版本与驱动提交时间的先后:6.14.11 比驱动早两个月,不可能有。
  3. DT 绑定在新旧树之间会漂移:BSP 属性名 vs mainline 属性名,移植时要么改驱动、要么重打 dtb/UKI。
  4. devm_gpiod_get_from_of_node 在 6.14 已不存在:用 of_get_named_gpio + devm_gpio_request_one + gpio_to_desc。
  5. vermagic 必须逐字匹配;CONFIG_MODVERSIONS=n 时不需要 Module.symvers,KBUILD_MODPOST_WARN=1 可绕过 modpost。
  6. 驱动可能在笔刚上电时读到 0xFF:给重试窗口,别第一次失败就放弃。
  7. 无线充的 SoC 是粗粒度、会跳;BLE 电量更细但充电时可能不刷新。两者差 10~20% 正常。
  8. GNOME 扩展改完必须重新登录,disable/enable 不重载模块。
  9. 启动额度:nabu 的 ABL 每启动一次扣 slot-retry-count,7 次上限;频繁切内核有风险, 能接 PC 时用 fastboot set_active a 补满。
  10. 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。

本文作者:赵苦瓜      文章标题: 小米平板 5(nabu)手写笔无线充电:从“吸上去没反应”到 6.14 内核自编驱动
本文地址:https://blog.zhaokugua.com/?post=122
版权声明:若无注明,本文皆为“赵苦瓜のBlog~”原创,转载请保留文章出处。

返回顶部    首页    后花园  
版权所有:赵苦瓜のBlog~    站长: 赵苦瓜    程序:emlog   鲁ICP备20030743号-4   鲁公网安备37048102006726 萌ICP备20260614号    sitemap