1. 项目背景与核心问题
在移动设备开发领域,电源管理一直是影响用户体验的关键环节。最近在Unisoc(紫光展锐)平台基于Kernel 5.15进行开发时,遇到了一个关于USB状态更新与充电事件处理的典型问题:当设备连接充电器时,系统未能正确识别充电事件并更新USB状态,导致充电指示灯异常、充电速度显示不准确等问题。
这个问题的本质在于Unisoc平台对Linux内核5.15版本中USB Type-C和电源子系统接口的适配存在不足。具体表现为:
- USB PD协议协商完成后,充电状态未同步到sysfs接口
- 内核事件通知链(notifier chain)未正确触发uevent事件
- 用户空间守护进程获取的充电电流/电压数据滞后
2. 技术原理深度解析
2.1 Unisoc电源管理架构
Unisoc平台的电源管理子系统采用分层设计:
code复制应用层
├── Charger Manager Daemon
├── Uevent监听服务
└── 状态栏服务
内核层
├── Power Supply Core
├── USB PHY驱动
├── Charger IC驱动
└── Type-C控制器
硬件层
├── PMIC
├── USB Type-C接口
└── 电池管理IC
在Kernel 5.15中,关键的变更包括:
- 引入了新的typec_class结构体
- 电源供应类(power_supply)属性文件重组
- USB PD 3.1规范支持更新
2.2 充电状态机流程
正常的充电事件处理应遵循以下状态转换:
code复制[断开状态]
│
├── USB插入 → [检测阶段]
│ │
│ ├── SDP检测 → [USB 2.0模式]
│ ├── CDP检测 → [USB充电模式]
│ └── DCP检测 → [AC充电模式]
│
└── Type-C插入 → [PD协商阶段]
│
├── PD合约建立 → [PD充电模式]
└── 默认5V → [Type-C 1.5A/3A模式]
问题出现在PD协商完成后,内核未能正确更新/sys/class/power_supply/usb/online状态位。
3. 问题定位与修复方案
3.1 内核日志分析
通过以下命令捕获关键日志:
bash复制adb shell "dmesg | grep -E 'usb|charger|power_supply'"
典型错误日志显示:
code复制[ 125.367845] usb 1-1: new high-speed USB device number 2 using xhci-hcd
[ 125.518672] usb 1-1: config 1 interface 0 altsetting 0 endpoint 0x81 has invalid maxpacket 512
[ 125.527894] power_supply usb: driver failed to report `online` property
3.2 关键代码修改
需要修改的驱动文件位于:
drivers/power/supply/unisoc_usb_charger.c
主要修复点:
- 补充typec_port注册回调:
c复制static struct typec_ops unisoc_typec_ops = {
.dr_set = unisoc_dr_set,
.pr_set = unisoc_pr_set,
.vconn_set = unisoc_vconn_set,
.port_type_set = unisoc_port_type_set,
};
- 更新power_supply变更通知:
c复制static void unisoc_update_online_status(struct unisoc_charger *chg)
{
int online = typec_get_online_status(chg->typec);
if (chg->online != online) {
chg->online = online;
power_supply_changed(chg->psy);
sysfs_notify(&chg->psy->dev.kobj, NULL, "online");
}
}
3.3 设备树配置更新
在设备树中需要确保USB PHY节点包含充电检测参数:
code复制&usb {
extcon = <&extcon_gpio>;
vbus-supply = <&vbus_reg>;
dr_mode = "peripheral";
unisoc,charger-detection = <1>;
unisoc,pd-negotiation-timeout-ms = <5000>;
};
4. 完整验证流程
4.1 内核空间验证步骤
- 检查sysfs节点状态:
bash复制cat /sys/class/power_supply/usb/online
cat /sys/class/power_supply/usb/current_max
- 触发人工uevent:
bash复制echo 1 > /sys/class/power_supply/usb/uevent
- 监控内核通知链:
bash复制adb shell "cat /proc/kmsg | grep power_supply"
4.2 用户空间测试方案
编写测试脚本监控充电状态:
python复制#!/usr/bin/python3
import pyudev
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='power_supply')
for device in iter(monitor.poll, None):
if device.sys_name == 'usb':
print(f"USB状态变更: {device.attributes.asstring('online')}")
print(f"当前电流: {device.attributes.asstring('current_now')}mA")
5. 常见问题与解决方案
5.1 充电类型识别错误
现象:DCP充电器被识别为SDP
解决方法:
- 检查USB PHY的DP/DM分压电阻配置
- 验证设备树中
unisoc,charger-detection参数 - 更新BC1.2检测固件
5.2 PD协商超时
现象:日志显示PD negotiation timeout
排查步骤:
- 测量CC线电压(正常应1.5-3.3V)
- 检查Type-C控制器供电
- 验证RP电阻值(默认56kΩ)
5.3 充电电流波动
根本原因:USB枚举过程中电流限制被重置
稳定方案:
c复制static void unisoc_usb_enum_done(struct unisoc_charger *chg)
{
/* 保持充电电流不受USB枚举影响 */
if (chg->online) {
unisoc_set_charge_current(chg->chg_curr);
}
}
6. 性能优化建议
- 中断处理优化:
c复制// 原代码:每次中断都触发完整状态更新
irqreturn_t unisoc_vbus_irq(int irq, void *data)
{
schedule_work(&chg->vbus_work);
return IRQ_HANDLED;
}
// 优化后:去抖动处理
irqreturn_t unisoc_vbus_irq(int irq, void *data)
{
if (time_after(jiffies, chg->last_vbus_jiffies + msecs_to_jiffies(500))) {
schedule_work(&chg->vbus_work);
}
chg->last_vbus_jiffies = jiffies;
return IRQ_HANDLED;
}
- 状态缓存机制:
c复制static ssize_t online_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct unisoc_charger *chg = dev_get_drvdata(dev);
/* 使用缓存值减少锁竞争 */
return sprintf(buf, "%d\n", READ_ONCE(chg->online));
}
- 用户空间通知优化:
bash复制# 使用inotify替代轮询
inotifywait -m -e modify /sys/class/power_supply/usb/online
7. 相关工具与调试技巧
- 实时充电监控工具:
bash复制watch -n 1 "cat /sys/class/power_supply/*/uevent"
- Type-C状态分析:
bash复制cat /sys/class/typec/port0/*
- 电源跟踪命令:
bash复制trace-cmd record -e power -e usb
- 关键函数插桩:
c复制#define DEBUG_CHG_STATE
#ifdef DEBUG_CHG_STATE
static void dump_charger_state(struct unisoc_charger *chg)
{
dev_info(chg->dev, "online=%d vbus=%d chg_curr=%d\n",
chg->online, chg->vbus_present, chg->chg_curr);
}
#endif
在实际项目开发中,我们发现当USB PHY初始化时序与PMIC上电时序存在竞争时,会导致初始充电状态检测失败。通过在内核启动参数添加usbcore.quirks=unisoc_phy_init_delay=200可有效解决。这个经验来自于三次硬件异常复位后的波形分析,建议在类似平台设计中特别注意电源域的上电顺序设计。