做高通平台的BSP或者内核驱动开发,很多朋友早晚会遇到这么一件事:项目里不想用高通自带PMIC的那套充电方案,转而外挂一颗第三方的充电IC,比如TI的BQ25601、BQ25896,RICHTEK的RT9467,NXP的SC8xxx这些。客户给的理由往往五花八门,但归结起来无非是成本、供货、私有快充协议这几个方向。不管哪种原因,落到驱动工程师头上就是一句话:把这颗IC接到Linux的power_supply框架里,让Android上层、HAL层、甚至充电状态机都把它当成一颗"标准充电器"来用。
本文就是我个人在高通平台(骁龙6系到8系都调过)上接入第三方充电IC的完整思路和实操记录,从框架原理、设备树配置、驱动probe、回调实现、中断处理,到和高通原生充电功能打架时怎么收场,全流程拆开讲。适合正在做类似事情的驱动工程师,或者刚接触高通BSP但被分到充电相关bug的新手参考。看完你至少能明白:一颗充电IC要进power_supply框架,到底需要写什么、注意什么、踩坑点在哪。
1. 为什么高通平台要"外挂"第三方充电IC
1.1 高通原生充电方案的限制
高通骁龙平台的PMIC(比如PM660、PM8150、PM8250)内部集成了完整的充电管理功能,包括BC1.2检测、USB输入限流、恒压恒流充电、终止电流控制这些。原生方案的好处是软件配套成熟,Android的healthd、charger服务、高通的电源管理框架(如QPNP、SMBB)都是围绕它设计的,驱动基本不用动。
但实际项目里不用原生方案的情况非常普遍。最常见的原因就是私有快充协议——现在很多手机、平板、移动POS机项目要支持自家私有协议,比如低压大电流充电、UFCS融合快充这类,高通PMIC原生往往做不到那么灵活的协议适配,外挂一颗专用充电IC反而方便,芯片原厂直接把协议栈和驱动都给你。其次是成本和供货:第三方IC量产后单价低、供应商多、不像高通交期那么紧。最后还有一些设计上的考虑,比如把充电电路独立成小板方便复用,或者充电功率做得比较大(65W往上),单独用一颗charge pump IC更安全。
1.2 第三方充电IC的接入形态与选型要点
接入形态上,第三方充电IC几乎都是I2C接口控制,配合若干GPIO(中断、使能、STAT指示),挂在某个空闲I2C总线上。典型的如BQ25601,从机地址0x6B,使用I2C配置充电电压、电流、终止电流、输入限流,通过INT引脚通知插入、拔出、充电完成、故障事件。
选型时驱动工程师需要重点关注几个点:I2C从机地址和寄存器映射是否清晰,有没有read-modify-write的陷阱;芯片的中断源是否足够丰富(插拔、充电状态跳变、看门狗超时、过温过压);是否内置ADC能直接读VBUS、IBUS、V BAT;是否带看门狗——这个非常关键,很多芯片看门狗一旦开启而驱动没定时喂狗,芯片会自动关闭充电,导致"充电一会就断了"的诡异问题。选IC不是硬件的活,驱动工程师必须参与评审,不然到后面全是驱动在填坑。
2. power_supply框架核心设计拆解
2.1 框架定位:内核里的"充电设备抽象层"
linux的power_supply框架本质上就是一个类设备模型。它把所有与电源相关的东西——电池、充电器、USB、无线充电、电量计——抽象成一个统一的、带属性的设备节点,暴露在/sys/class/power_supply/目录下面。上层不管是Android的healthd、charger服务,还是你们自研的监控程序,统一通过读属性文件来获取状态,通过写属性文件来下发指令,完全不用关心底层到底是哪颗芯片在干活。
这就好比家里用的"智能插座"——插座下面是什么电器不重要,几个标准插孔摆在那,谁来都能用。power_supply框架就是给充电设备做的一套标准接口,上层只认这套接口,驱动负责把芯片的真实行为翻译成接口语义。
在高通平台上,这个框架还承担着"多电源协调"的职责。比如系统里同时存在USB检测器(Type-C port controller)、充电IC、电量计、电池温度监测,这些设备通过power_supply的supply关系组成一张供电拓扑,上层要查"当前是谁在给电池充电"时,沿着这条拓扑就能找到真正的充电器节点。
2.2 核心数据结构与属性回调
驱动要做的核心事情,就是填充一个power_supply_desc结构体,然后调用power_supply_register()把它注册进内核。这个结构体是框架对接驱动的唯一契约:
static const struct power_supply_desc bq25601_charger_desc = { .name = "bq25601", .type = POWER_SUPPLY_TYPE_USB, .properties = bq25601_charger_props, .num_properties = ARRAY_SIZE(bq25601_charger_props), .get_property = bq25601_get_property, .set_property = bq25601_set_property, .property_is_writeable = bq25601_property_is_writeable, };properties是一个power_supply_property枚举数组,它定义了该设备支持哪些属性。get_property和set_property是真正的读写回调,框架层在收到上层sysfs访问时,会根据属性ID分发到这两个函数里。比如上层读/sys/class/power_supply/bq25601/status,内核最终会调用bq25601_get_property()并传入POWER_SUPPLY_PROP_STATUS,驱动需要回复一个整数值,对应充电状态枚举。
这里必须注意:不是所有属性都要实现,你只declare数组里列出来的那些。如果上层读一个你没实现的属性,get_property会返回-EINVAL,这是正常的,别慌。但一个充电器至少要实现STATUS、HEALTH、ONLINE、PRESENT这四大件,否则Android的charger服务会显示异常。
2.3 事件上报机制:power_supply_changed的秘密
驱动在检测到充电状态变化时,需要主动调用power_supply_changed(psy)通知内核和上层。这个函数会触发一次uevent事件,Android上层接收到后重新读取所有属性,刷新状态。注意:它不会携带"哪个属性变了"的信息,上层是全部重读的。所以驱动里不要在每个中断里都调用,把标志位记下来,统一在中断底半部调用一次即可。
实际调试中我发现,过于频繁地调用power_supply_changed会让上层日志刷屏、CPU占用升高,特别是在芯片有持续中断源(比如每次ADC转换都触发一次中断)的情况下。正确做法是:中断里先读寄存器确认到底是什么事件,只有充电状态、在线状态、健康状态这些关键信息变化时才上报。
3. 驱动开发实操全流程
3.1 从芯片手册先抓这些信息
拿到一颗充电IC,别急着看示例代码,先翻手册的这几页:
- I2C从机地址和寄存器映射表:确认地址,以及哪些寄存器是只读、哪些是R/W、哪些需要read-modify-write。
- 上电默认状态:确认芯片上电后充电是否自动开启、看门狗是否默认开启、INT输出极性是什么。
- 充电状态机定义:很多IC把"未充电、预充电、恒流充电、恒压充电、充电完成"编码在状态寄存器的某几个bit里,驱动需要把这些bit映射成power_supply的
STATUS枚举(NOT_CHARGING/CHARGING/FULL)。 - 中断寄存器和mask:确认有哪些中断源、INT是电平触发还是边沿触发、读哪个寄存器能清除中断。
这一步省不得,我见过太多人跳过手册直接抄驱动,结果连"芯片在充电"和"芯片上报FULL"都没分清楚,上层永远显示"未充电"。
3.2 设备树配置:先把硬件描述清楚
设备树是驱动和硬件之间的桥梁。以BQ25601挂在I2C3、中断接在GPIO77为例,典型配置如下:
&i2c_3 { status = "okay"; bq25601@6b { compatible = "ti,bq25601"; reg = <0x6b>; interrupt-parent = <&tlmm>; interrupts = <77 IRQ_TYPE_EDGE_FALLING>; ti,input-current-limit-microamp = <2000000>; ti,charge-current-microamp = <1024000>; ti,charge-voltage-microvolt = <4400000>; ti,termination-current-microamp = <128000>; status = "okay"; }; };关于compatible字段,芯片原厂提供的驱动通常会自带一个match表,比如{"ti,bq25601", ...},你要保证设备树和驱动里match的字符串完全一致。reg必须和芯片手册里的从机地址一致。中断触发类型要特别注意:BQ25601的INT脚是开漏输出,默认低有效,一般配IRQ_TYPE_EDGE_FALLING或IRQ_TYPE_LEVEL_LOW都行,但要结合硬件上有没有上拉、以及你对边沿丢失的容忍度来定。如果硬件设计有问题导致中断粘连,你会看到中断风暴或者干脆没中断。
平台相关的还有一件事:如果这颗IC要作为系统唯一的charger,需要把高通的PMIC充电节点禁用掉。具体节点名要看平台,比如&pm660_charger或者&pmi8998_charger,在设备树里加上status = "disabled";。这一步漏了,就会出现两个充电器同时存在的局面,上层会随机挑一个去读,状态五花八门。
3.3 probe流程:从I2C探测到注册power_supply
驱动probe函数是核心,它决定了整个驱动能否正常运行。标准流程我总结为六步:
第一步,通过devm_i2c_new_client_device或者设备树匹配拿到struct i2c_client,然后从client里拿struct device和of_node。
第二步,解析设备树参数,把充电限流、目标电压、终止电流等值读出来,后面设置寄存器要用。用of_property_read_s32或者of_property_read_u32即可,注意单位换算——设备树里我习惯用microamp/microvolt表示,寄存器里单位往往不是这个,需要按芯片手册换算。
第三步,分配驱动私有结构体struct bq25601,里面至少要有:struct i2c_client *client、struct power_supply *psy、struct power_supply_desc psy_desc、struct power_supply_config psy_cfg、int irq、以及用来缓存寄存器值和状态位的字段。
第四步,对芯片做初始化:写入基本配置寄存器、设置充电电流电压、配置中断mask、清掉遗留的中断标志位。如果芯片有看门狗,要么关闭它,要么配置好喂狗定时器,否则芯片会在几十秒后自动关断充电。
static int bq25601_hw_init(struct bq25601 *charger) { /* 设置充电电压为4.4V */ bq25601_update_byte(charger, BQ25601_REG_04, 0x8C, 0xFC); /* 高位字节 */ bq25601_update_byte(charger, BQ25601_REG_05, 0x00, 0x03); /* 低位字节 */ /* 设置快速充电电流 */ bq25601_write_byte(charger, BQ25601_REG_02, 0x66); /* 使能充电,禁用看门狗 */ bq25601_update_byte(charger, BQ25601_REG_01, 0x00, BIT(4)); return 0; }第五步,注册中断。推荐用devm_request_threaded_irq,因为充电IC状态查询往往涉及I2C操作,I2C通信不能放在中断上下文里,必须用线程化中断:
static int bq25601_irq_init(struct bq25601 *charger) { int ret; charger->irq = client->irq; if (!charger->irq) { dev_err(&client->dev, "no IRQ specified\n"); return -EINVAL; } ret = devm_request_threaded_irq(&client->dev, charger->irq, NULL, bq25601_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "bq25601_irq", charger); if (ret) { dev_err(&client->dev, "request irq failed: %d\n", ret); return ret; } return 0; }第六步,注册power_supply设备:
charger->psy_desc.name = "bq25601"; charger->psy_desc.type = POWER_SUPPLY_TYPE_USB; charger->psy_desc.properties = bq25601_charger_props; charger->psy_desc.num_properties = ARRAY_SIZE(bq25601_charger_props); charger->psy_desc.get_property = bq25601_get_property; charger->psy_desc.set_property = bq25601_set_property; charger->psy_desc.property_is_writeable = bq25601_property_is_writeable; charger->psy_cfg.drv_data = charger; charger->psy_cfg.of_node = client->dev.of_node; charger->psy = devm_power_supply_register(&client->dev, &charger->psy_desc, &charger->psy_cfg); if (IS_ERR(charger->psy)) { dev_err(&client->dev, "failed to register power supply\n"); return PTR_ERR(charger->psy); }注意devm_power_supply_register是带资源管理的版本,驱动unbind时自动注销,省心。注册成功之后,/sys/class/power_supply/bq25601/就会自动出现。
3.4 get_property:把芯片寄存器翻译成上层语言
get_property是驱动里最繁琐的部分,它是上层理解"这颗IC当前是什么状态"的唯一通道。我会把所有属性用switch语句分发给单独的函数,这样可读性和维护性都好。
以STATUS属性为例——这是最容易被误判的地方。BQ25601的充电状态在REG08的bit5和bit4:
- 00:未充电
- 01:预充电
- 10:快速充电
- 11:充电完成
驱动代码大致这样:
static int bq25601_get_status(struct bq25601 *charger, int *status) { u8 reg_val; int ret; ret = bq25601_read_byte(charger, BQ25601_REG_08, ®_val); if (ret) return ret; switch ((reg_val & BQ25601_CHRG_STAT_MASK) >> 4) { case BQ25601_CHRG_STAT_NOT_CHARGING: *status = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case BQ25601_CHRG_STAT_PRE_CHARGING: case BQ25601_CHRG_STAT_FAST_CHARGING: *status = POWER_SUPPLY_STATUS_CHARGING; break; case BQ25601_CHRG_STAT_CHARGE_DONE: *status = POWER_SUPPLY_STATUS_FULL; break; default: *status = POWER_SUPPLY_STATUS_UNKNOWN; break; } return 0; }这个映射千万不要拍脑袋。POWER_SUPPLY_STATUS_FULL表示"充满",只有芯片明确上报终止完成后才能设置。很多人把"连接到适配器但未充电"的状态映射成FULL,导致上层一直显示100%,用户直接懵。
其他常用属性也得照顾周全。ONLINE属性表示适配器是否连接;HEALTH属性从故障寄存器读出来映射为GOOD、OVER_VOLTAGE、OVER_HEAT等;PRESENT属性表示电池是否在位。CURRENT_NOW和VOLTAGE_NOW如果芯片内置ADC,可以从ADC寄存器读;如果没有,可以返回-ENODATA,类不会报错,只是相关调试工具看不到实时数值。
3.5 set_property:下发改机指令
set_property用于上层主动设置充电参数,比如POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT和POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE。上层(特别是维修模式、工厂模式)调整充电电流时,就走这条路。
关键点:修改寄存器之前,先判断当前是否在充电。很多IC在充电过程中修改电流电压寄存器的时机有讲究,某些bit需要先关闭充电再改。另外,写入参数后要立刻缓存到驱动私有结构体里,因为get_property时如果只是读寄存器,芯片可能因为状态机的原因还没更新,导致读写不对称。
property_is_writeable回调也很重要,它告诉内核sysfs"这个属性文件可写"。如果不实现这个回调,即使你在set_property里写了逻辑,上层写文件时也会被拒。
3.6 中断处理:把芯片的"喊话"变成框架的"广播"
中断是充电IC驱动的心脏。充电状态的切换、插拔检测、故障报警,全都靠INT引脚通知主控。中断处理函数里要做的事是"最小化的":
static irqreturn_t bq25601_irq_handler(int irq, void *dev_id) { struct bq25601 *charger = dev_id; u8 reg_val; /* 读中断状态寄存器,既确认事件也顺带清中断 */ bq25601_read_byte(charger, BQ25601_REG_0C, ®_val); bq25601_read_byte(charger, BQ25601_REG_0D, ®_val); /* 读充电状态,上报变化 */ power_supply_changed(charger->psy); return IRQ_HANDLED; }注意:不要在中断里做任何耗时操作,比如循环读多个寄存器、打印寄存器值、调用msleep。用devm_request_threaded_irq注册时,handler传NULL、thread_fn传bq25601_irq_handler,这样中断处理自动放到内核线程里执行,I2C操作就是安全的。
清中断标志位这个动作容易被忽略。如果中断标志没有清掉,芯片INT脚可能一直拉低,触发条件变成电平触发,会造成中断风暴。所以每次中断处理,读一次中断状态寄存器是必须的,既确认事件类型又顺带清标志。
4. 与高通平台协同:避让、供电关系和电量计
4.1 禁用高通原生charger:避免"两个爸爸"抢着报状态
高通平台上,如果PMIC内部集成了充电管理,那它就是内核里的一个power_supply设备,名字通常是charger或者main_charger。你外挂第三方IC后,如果不做任何处理,sysfs下就会同时存在两个充电器节点,Android的charger服务会优先选择注册顺序靠前的那个来读状态——到底读谁,完全取决于驱动的加载时机,这不可控。
我吃过一次亏:外挂BQ25896后,上层时而显示充电时而显示未充电,用cat /sys/class/power_supply/*/type一看,发现PMIC的charger节点和BQ25896节点同时在,而且高通的charger节点先注册占了"主位"。上层读到的状态是PMIC死的状态,完全无视BQ25896。解决办法就是在设备树里把PMIC充电节点禁掉:
&pm660_charger { status = "disabled"; };具体节点名,要去你的平台设备树里搜,一般搜"qcom,qpnp-smb2"或者"qcom,smb"之类的compatible,然后把对应节点disable。还有一点,禁用PMIC charger后,USB插入检测也要确认是否依赖PMIC的extcon,如果断了这条路,上层就收不到"USB插入"事件了。这时要么让第三方IC自己检测适配器插入,通过中断上报ONLINE属性,要么在高通的USB驱动里做绑定,后者往往更复杂,最好在硬件设计时就把Type-C/CC检测和充电IC打通。
4.2 供电关系:supplied_to和电源拓扑
power_supply框架支持设备之间的"供电关系"。比如"电池"是由"充电器"供电的,可以用power_supply_config里的supplied_to指定:
static char *bq25601_supplied_to[] = { "battery", }; charger->psy_cfg.supplied_to = bq25601_supplied_to; charger->psy_cfg.num_supplicants = ARRAY_SIZE(bq25601_supplied_to);这样注册后,内核会把bq25601标记为battery的supplier。上层通过power_supply_get_by_name("battery")查询电池的supplier列表时,就能找到bq25601,从而知道当前是哪颗充电器在给电池充电。在高通平台上,电池节点通常由PMIC电量计驱动提供,名字就是battery。如果你的硬件方案里电池由第三方电量计IC维护,那battery节点会由那个驱动注册,名字需要按实际情况调整。
4.3 电量计分配:charger只管充电,容量交给专业选手
很多第三方充电IC不集成电量计,无法直接给出电池容量百分比。这种情况下,我强烈建议把capacity属性的实现单独放给电量计IC,充电IC只提供STATUS、ONLINE、HEALTH这些状态属性,不要自己去估容量。
如果在dts里不引入电量计,上层会发现battery的capacity一直是0或者恒定值,这是因为没人实现POWER_SUPPLY_PROP_CAPACITY属性。最直接的排查方法:
cat /sys/class/power_supply/battery/capacity cat /sys/class/power_supply/battery/uevent如果返回为空或者-ENODEV,说明battery节点没有正确的容量来源。这时候查一下当前电池节点是谁注册的,如果是第三方电量计驱动(比如CW2015、BQ27426),看看它的驱动有没有正确注册;如果压根没有电量计,那就得自己写一个小的"电池模拟"节点,用电压查表法估算容量,但这个精度很有限,不适合量产。
4.4 Type-C检测和VBUS状态
高通平台现在的USB-C检测通常由独立的CC逻辑芯片(如高通自家的WCD9385、FSA4480,或者第三方TUSB321、FUSB302)负责。第三方充电IC一般只关心"适配器插没插",它通过VBUS引脚直接判断。我在驱动里通常把ONLINE属性和"VBUS在位"绑定,不依赖USB协议栈。
这时候就有一个配合问题:上层除了charger服务在轮询充电状态,USB驱动也会通过power_supply查询外接电源在线状态。如果充电IC和USB检测器之间的ONLINE上报不一致,会出现"插上线但系统认为没插电"的怪问题。解决思路:以USB检测器的插拔事件为总闸,当它上报"已插入"时再使能充电IC的充电路径;以充电IC的VBUS状态为辅助,两者同时上报才行。简单的做法是在充电IC的ONLINE属性里先读一下USB检测器状态:
case POWER_SUPPLY_PROP_ONLINE: usb_psy = power_supply_get_by_name("usb"); online = usb_psy ? usb_psy->online : 0; val->intval = online && bq25601_is_vbus_present(charger); break;虽然多一层依赖,但能避免很多单人调试时看不到的系统级矛盾。
5. 常见问题与排查技巧实录
5.1 问题速查表
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| sysfs下没有节点 | probe失败、I2C通信失败、power_supply_register返回错误 | 看dmesg有没有probe错误打印;用i2cdetect确认设备在总线上 |
| systerm显示一直"未充电" | STATUS属性映射错误、充电路径未使能、看门狗超时关闭充电 | 先读寄存器确认芯片实际在不在充电;排查充电使能bit;确认看门狗配置 |
| 充电到一半自动断 | 看门狗未喂、芯片过温保护、电流设置超限 | 检查驱动有没有定时喂狗;看HEALTH属性;读故障寄存器 |
| 插入适配器无事件 | 中断没触发、中断mask配置错误、VBUS检测引脚硬件问题 | 用cat /sys/kernel/debug/gpio看中断GPIO电平;读芯片中断状态寄存器确认事件有没有发生 |
| 上层读到重复或错误的charger | 高通的PMIC charger没禁用 | 设备树里禁用PMIC充电节点;或者确认你的第三方IC名字和原有节点不冲突 |
| capacity一直0或恒定 | 没有电量计驱动,或者battery节点不是由电量计注册的 | 检查/sys/class/power_supply/下的设备列表;补电量计驱动 |
| 中断风暴,CPU占用高 | 中断标志未清除、IRQ触发类型不对、共享中断处理不对 | 中断函数里读完状态寄存器;确认使用了线程化irq;用irq_domain检查中断号 |
| I2C读写一直失败 | 从机地址错、I2C总线编号错、硬件上拉缺失、I2C被低功耗状态挂起 | 用i2cdetect扫描地址;马上cat /sys/kernel/debug/i2c/3查看总线状态 |
5.2 排查步骤:从dmesg到寄存器值
遇到问题别急着改代码,按顺序来:先看dmesg里驱动probe有没有报错,再看/sys/class/power_supply/下有哪些设备,然后通过sysfs读各属性看有没有异常值,最后用i2cget直接读芯片寄存器对照手册验证。这三步能覆盖90%的问题。
比如我调BQ25896时遇到过一个非常恶心的bug:插上适配器后,系统时而识别、时而不识别。用dmesg -w观察,发现USB driver和charger driver在抢一个extcon设备。后来看到高通平台里type-c的VBUS检测事件和charger的VBUS检测事件是两个独立线程,我在dts里把USB驱动对charger的依赖注释掉后,问题就消失了——这属于平台协调问题,不是芯片问题,所以第一次调试这种问题不要怀疑芯片坏了。
5.3 我踩过的几个坑
第一个坑是I2C地址。BQ25601的7位地址是0x6B,但是i2cdetect扫出来的地址显示是0xD6——因为i2cdetect默认显示8位地址(左移一位)。很多新手看到0xD6以为芯片地址错了,其实没错。这个冤枉路我走过,现在看到0x6B的寄存器,第一反应就是去手册确认一下芯片是7位还是8位寻址。
第二个坑是芯片看门狗。某颗RICHTEK的IC上电默认开启看门狗,我一开始没配,结果充电5分钟就断一次,上层以为是接触不良。查了两天才发现是看门狗超时触发了输出关断。从那以后我写驱动,第一步先把看门狗配置好,不管默认关没关。
第三个坑是寄存器initialization只在probe里做了一次。有时候系统休眠唤醒后,IC寄存器会被硬件复位,回到默认状态,充电参数全丢了。后来我在resume回调里同样调用bq25601_hw_init(),并在pm_ops里注册dev_pm_ops,才彻底解决。所以记住:probe里写的初始化,suspend/resume里也要考虑补一遍。
第四个坑是中断触发类型。有一次我把BQ25601的中断配成IRQ_TYPE_LEVEL_LOW,结果只要芯片中断标志没清,就会一直进中断,系统负载飙升。后来改成IRQ_TYPE_EDGE_FALLING,同时在中断线程里读掉所有中断状态寄存器清了标志,才算稳了。实际这是开漏INT脚的经典问题:边沿触发要看硬件有没有外部上拉,没有上拉的话电平恢复不了,边沿就丢了,只能配level触发配合mask。
最后一个建议:调试期间,在驱动的probe函数里多打dev_info,把读取到的芯片ID、寄存器初始值、识别到的电源状态全打出来。等驱动稳定后再prune掉。这套日志会帮你省下大把抓头发的时间,尤其当你的板子是"昨天还好好的,今天就坏了"这种薛定谔状态时,全靠这些日志定位是不是硬件配置被改动了。
做驱动这些年,我越来越觉得充电IC驱动这事儿,难的不是怎么适配,而是怎么理解和处理"这颗IC在高通平台整个电源体系里的角色"。你把power_supply框架想清楚了,把芯片从手册到寄存器都吃透了,剩下就是耐心调试和记录。以上就是我个人的全部实操经验和踩坑记录,希望对正在跟充电IC搏斗的同学有帮助。