做RK3588平板项目,电源链路从来不是“能开机就行”那么简单。CPU、内存、屏幕、外设的功耗叠在一起,电池要能充得进、放得出,还要配合Type-C PD协议把快充跑起来,这时候光靠主PMIC根本应付不过来。我最近一块RK3588平板方案里就同时用了TI的BQ25703做充电管理、赛微电子的CW2015做电量计,两个器件都要在设备树(DTS)里把节点配好,系统才能正确识别、充电、上报电量。这篇就按我实际移植的过程,把这套DTS配置从头到尾捋一遍。
写这篇的目的很简单:很多朋友在RK3588上移植Ubuntu、调外设的时候,一翻到电源相关DTS就头疼,网上资料东一句西一句,寄存器级的东西又多。其实BQ25703和CW2015的配置没有想象中那么玄,只要你把硬件连接、驱动解析的字段、以及调试验证路径这三个点串起来,基本就能一次跑通。
需要说明一下,本文不针对某一个固定SDK版本。我会以我手头这份RK3588 Linux 5.10内核的写法为主,同时告诉你哪些字段在不同版本里可能改名、该如何自查。这样你换一套SDK、换一块板子,也能沿着同样的方法把它搞定。文中涉及的具体节点参数,都是基于常见实践的合理方案,你拿到自己的板子后,一定要对着原理图逐个核对。
1. 先弄清楚:BQ25703和CW2015在这块板子上到底各管什么
1.1 BQ25703:充电控制器,而不是一颗简单的充电IC
很多人第一次见BQ25703,都会把它和TP4056这类线性充电IC相比,但两者完全不是一个量级。BQ25703是TI的NVDC架构开关模式充电控制器,支持1到4节锂离子/锂聚合物电池。它的核心价值在于“路径管理”:当适配器接入时,系统负载优先由适配器供电,剩余电流再充给电池;适配器移除时,电池无间断接管系统供电。这个特性对平板这种频繁拔插适配器的产品太重要了,系统不会因为电源切换而重启。
BQ25703的所有充电参数,包括充电电流、截止电压、输入电流限制、OTG输出电压等等,都是通过I2C/SMBus接口写寄存器配置的。DTS的作用,就是把驱动需要的信息——挂在哪个I2C总线、中断脚是哪根、默认电流电压多少——告诉内核,驱动起来后再通过寄存器完成具体控制。这里有个点值得特别注意:BQ25703内部有看门狗机制,寄存器配置完成后,如果一段时间内没有通过I2C刷新,芯片会主动复位寄存器到默认状态,充电过程可能因此中断。所以DTS里watchdog的时长、驱动是否持续刷新,都要仔细设计。
1.2 CW2015:真正意义上的“电量计”
CW2015是赛微电子(CellWise)的一颗单节锂电池电量计,I2C接口,默认7位地址是0x62。它和直接用ADC采电压、再查表推测电池百分比的做法完全不同。CW2015会根据电池的电压、电流、温度,以及内置的电池特性参数(profile),用厂商的算法估算剩余容量。充电或放电过程中,它还能对库仑计做动态补偿,让SOC不会因为负载变化剧烈而疯狂跳变。
不过,CW2015的算法依赖一份电池profile配置。这份配置里包含满充电压、空电电压、内阻、OCV曲线等参数。如果DTS里没有正确写入profile,或者写的是别的电芯的参数,电量计工作起来的表现就是:刚充到100%,一拔充电器就掉到90%;或者放完电自动关机之后,再开机又显示20%。这些现象我后面排错部分会细讲,这里先立个flag:CW2015的DTS配置,绝大多数坑都出在profile上。
1.3 为什么RK3588平板方案喜欢这对组合
做RK3588平板,核心供电一般交给RK806这颗PMIC,它负责给CPU、LPDDR、eMMC等提供多路电源。但RK806并不包含锂电池充电管理和电量计功能。平板产品需要有Type-C口充电,需要支持PD快充,需要显示电池百分比,所以必须在外围搭charger加电量计,BQ25703加CW2015就是非常典型的一套方案。
把接线逻辑理清之后,分工就非常清楚:
| 功能 | BQ25703 | CW2015 |
|---|---|---|
| 充电电压/电流控制 | 负责 | 不参与 |
| 输入限流、PD配合 | 负责 | 不参与 |
| OTG反向供电 | 支持 | 不参与 |
| 剩余电量SOC估算 | 不参与 | 负责 |
| 电池健康/温度采样 | 部分ADC能力 | 支持温度、电压、电流采样 |
搞清楚了分工,后续DTS里每个节点该放哪些属性,心里就不会乱。这里多说一句:有些方案会把电量计功能集成在charger里,连I2C都省了,但BQ25703本身不做电量算法,所以需要CW2015这类专用电量计来补位,这也是这套组合在RK3588公板上出现频率高的原因。
2. 改DTS前,先花十分钟做三件事
2.1 先看原理图,别急着打开dtsi
改DTS最大的误区,就是拿别人板子的节点过来直接抄。即便都是RK3588,BQ25703可能挂在I2C0上,也可能挂在I2C4上;中断脚可能接GPIO1_B2,也可能接GPIO3_C5。所以第一步永远是打开自己板子的原理图,确认三件事:
- 两颗芯片分别挂在哪条I2C总线上。
- I2C地址是多少(BQ25703一般是0x6B,CW2015一般是0x62,但硬件上的地址引脚不同,实际地址可能有变化)。
- 有没有中断脚/Alert脚,接到了SoC的哪个GPIO,是高有效还是低有效。
这些信息会直接对应DTS里的reg、interrupts、pinctrl三个字段。我自己就犯过一次错:BQ25703的中断脚在原理图上是低有效,但我在DTS里写成了IRQ_TYPE_LEVEL_HIGH,现象是内核启动后中断风暴,dmesg刷屏,整机卡顿。这种问题不查原理图,光靠猜,能折腾你一下午。
2.2 确认内核里有没有对应驱动
Rockchip的Linux SDK和Android内核里,通常会带BQ25703和CW2015的驱动,但不同版本的SDK不一定都齐全。改节点之前,先在内核源码里搜一下:
grep -rn "bq25703" kernel/drivers/power/supply/ grep -rn "cw2015" kernel/drivers/power/supply/如果没有对应驱动,即使DTS写得再完美,设备也挂不起来。这时候要么从上游Linux内核移植驱动,要么看SDK里是否有相近的芯片驱动可以替换。这里提醒一点:BQ25703在标准内核里有时候写作bq25700,compatible可能是“ti,bq25700”,这种细节要在驱动源码里看到实际解析的compatible再来决定DTS怎么写。
2.3 想清楚设备树会烧到哪里
RK3588平台常见有两种情况:如果烧的是Linux SDK,设备树一般会被打进boot.img里,或者在单独的dtb分区;如果用的是Android,那通常还有一个dtbo overlay分区。改DTS之后,编译和烧录方式完全不同。
所以我每次改设备树前,都会先确认当前板子的烧录方式,避免出现“改了DTS、编译了内核、烧进去却没生效”的尴尬。具体怎么确定,可以先看SDK的文档,也可以直接把当前boot分区里的设备树dump出来反编译对比。这一步花不了几分钟,但能省下后面大量的无效重启时间。
3. BQ25703充电节点的DTS配置逐字段拆解
3.1 一份可参考的BQ25703节点
下面这份节点以我手头RK3588 Linux 5.10内核的写法为例,主控I2C我放在i2c3上,实际以你原理图为准:
&i2c3 { status = "okay"; bq25703: bq25703@6b { compatible = "ti,bq25703"; reg = <0x6b>; interrupt-parent = <&gpio1>; interrupts = <RK_PB2 IRQ_TYPE_LEVEL_LOW>; pinctrl-names = "default"; pinctrl-0 = <&bq25703_int_l>; ti,charge-current = <2000000>; ti,max-charge-voltage = <4400000>; ti,input-current-limit = <3000000>; ti,watchdog-timeout-ms = <40000>; ti,otg-voltage = <5000000>; ti,otg-current = <500000>; }; };先说明一点:不同SDK版本的BQ25703驱动,of_property的命名不一定完全一致。你拿到的源码里可能叫ti,charge-current,也可能叫ti,charge_current,这是正常的。我建议先打开bq25703_charger.c文件,搜索“of_property_read_u32”,看看驱动到底解析了哪些字段,再用实际字段名来填。下面的解释主要是帮你理解每个字段的业务含义,理解之后,不管字段名怎么变,你都能认出来。
3.2 字段含义:不是“填个数”,背后是电源策略
reg = <0x6b>是芯片的7位I2C地址。BQ25703的地址一般由ADR引脚的电平决定。如果你确定走的是0x6B这条地址,但i2cdetect扫不出设备,先别怀疑地址错误,优先查供电和I2C上拉。芯片没上电,地址写对了也扫不到。
interrupts和pinctrl-0是配套的。BQ25703有一个INT引脚,充电状态变化、输入源插入、看门狗超时都会拉这个脚。DTS里必须把对应的GPIO复用配好,否则驱动无法收到中断,系统会出现“插上充电器,界面没反应”的情况。中断有效电平的极性一定要对着原理图查,这地方我踩过坑,后面排错部分会详细说。
ti,charge-current = <2000000>:设置默认充电电流,单位uA。默认2000mA对大多数平板电芯来说比较适中。如果你的电池规格是5000mAh,常规0.5C充电就是2500mA,可以设到2500000;但前提是板子的充电散热要压得住。量产产品做高倍率充电时,一定要实测电池温度和MOS管温度,这个参数往往不是电池决定的,而是整机热设计决定的。
ti,max-charge-voltage = <4400000>:这是截止电压。单节锂电常见4.2V、4.35V、4.4V,取决于电芯化学体系。写高了对电池寿命有影响,写低了充不满,续航打折。这个值必须跟电池规格书核对,千万不能拍脑袋。如果电池标注的充电限制电压是4.35V,你给个4.4V,短期看不出问题,几十个循环后电池鼓包风险剧增。
ti,input-current-limit = <3000000>:输入电流限制,单位uA。这个值决定了从适配器最大能抽多少电流,对PD快充尤其关键。一般PD适配器会通过Type-C协商给到一个电流值,但BQ25703本身还有一个静态的输入限流值兜底。两者之间驱动会按合理逻辑切换,DTS里给的是一个默认档位。
ti,watchdog-timeout-ms = <40000>:看门狗超时时间。BQ25703的看门狗机制我前面提过,如果超过设定时间没有I2C写操作,芯片寄存器会全部回到默认状态。Rockchip的驱动一般会周期性刷新看门狗,所以DTS里给一个40秒的超时是常见做法。如果设得太短,比如5秒,而又恰好碰到I2C总线拥堵,芯片容易无故复位,充电电流突然归零,你会看到充电状态像抽风一样一下有一下没有。
OTG相关字段:如果平板支持OTG,也就是要通过Type-C口反向给U盘、手柄这类设备供电,就需要配置OTG电压和电流。BQ25703可以把电池电压升压从VBUS输出,输出电压范围挺宽。不需要OTG的产品,这里可以留空,但如果你SDK的DTS模板里默认打开了OTG,而硬件上又没有走BQ25703的VBUS路径,一定要关掉,否则可能影响正常充电。
3.3 编译、烧录、快速验证
DTS文件改完后,在RK3588的Linux SDK中,内核编译一般是:
cd kernel make ARCH=arm64 rockchip_linux_defconfig make ARCH=arm64 rk3588-evb.img -j$(nproc)这里rk3588-evb.img会包含内核和设备树。如果你想单独编译dtb,可以用:
make ARCH=arm64 dtbs编译产物在arch/arm64/boot/dts/rockchip/下,对应你板子dts文件名。烧录方式要看你板子的引导策略。如果设备树是独立分区,可以用fastboot或rkdeveloptool单独烧dtb分区;如果是放在boot分区里,就要整包烧boot。
烧完开机后,第一件事看dmesg:
dmesg | grep -i bq25703如果DTS节点成功匹配,你会看到驱动probe成功、power_supply注册的日志。然后看sysfs:
ls /sys/class/power_supply/ cat /sys/class/power_supply/bq25703-0/statusstatus能读到Charging / Not charging / Full这类状态,就说明驱动已经正常工作。里面涉及的节点名因内核版本而异,有可能叫bq25703-0,也可能直接叫charger,先ls再cat,不要假设名字一定一样。
4. CW2015电量计DTS配置:参数对了,百分比才靠谱
4.1 基本节点结构
CW2015我放在i2c2上,节点写法如下:
&i2c2 { status = "okay"; cw2015: cw2015@62 { compatible = "cellwise,cw2015"; reg = <0x62>; cm-i2c-tx-error-disable; interrupt-parent = <&gpio0>; interrupts = <RK_PB1 IRQ_TYPE_LEVEL_LOW>; cw2015,irq-gpio = <&gpio0 RK_PB1 GPIO_ACTIVE_LOW>; bat_config_info = < 0x15 0x42 0x88 0x5e 0x70 0x60 0x55 0x5a 0x5d 0x66 0x65 0x66 0x6d 0x75 0x74 0x78 0x75 0x5f 0x4e 0x44 0x42 0x3f 0x3d 0x3c 0x3b 0x3a 0x39 0x38 0x37 0x36 >; }; };同样,属性名要以你SDK里cw2015驱动为准。我见过内核里不同的写法,有的用bat_config_info,有的用cw2015,bat_config_info。但核心思路一致:驱动需要从DTS拿到一份电池特性配置数组。
4.2 最关键的bat_config_info是怎么来的
这是CW2015配置里最核心、也最容易让人一头雾水的一步。上面那一长串十六进制数组,不是随便写的,而是根据电池厂商提供的电芯参数,经过换算得到的一份“profile”。
一般流程是:
- 找电池厂要电芯的OCV表、内阻数据和容量参数。
- 用CellWise官方的配置工具(CW2015 Configuration Tool)导入这些数据,工具会生成一个可以写入芯片RAM的配置块。
- 在工具里导出为十六进制数组,填进DTS的bat_config_info。
如果你拿不到官方工具,也可以找同容量、同体系电芯的接近参数先顶上,但之后一定要实测校准。这里的实测校准包括:充满到4.4V时电量计显示100%,放电到截止电压时显示0%,而且整个放电过程SOC曲线不能出现大跳变。我遇到过客户把3.7V平台的电芯参数硬套到4.4V电芯上,结果电量计在40%时直接跳到0%,导致平板提前关机,这个体验是灾难级的。
4.3 校准、报警阈值与上报间隔
CW2015节点里如果驱动支持,通常还有这几个参数可以调:
- 设计容量:mAh单位,用于SOC归一化计算。
- 低电量报警阈值:比如剩余10%时触发中断。
- 采样/上报间隔:多久更新一次容量、电压、电流值。
平板产品上,我一般会把低电量阈值设到8%到12%之间。设太高,系统会频繁弹低电量提醒;设太低,用户在低电量下玩游戏,电池瞬时跌到截止电压,直接关机,体验更差。这个值要跟产品定义走,没有绝对标准。如果你做的是行业平板或者带备用电池的设备,阈值策略还会更复杂,但DTS层面能做的就是把中断阈值和initial capacity两个参数设对。
4.4 如何用sysfs验证电量计真的在工作
启动后先确认节点:
ls /sys/class/power_supply/cw2015-0/ cat /sys/class/power_supply/cw2015-0/capacity cat /sys/class/power_supply/cw2015-0/voltage_now cat /sys/class/power_supply/cw2015-0/current_now容量显示、电压值、充放电电流数值都在合理范围内,就说明I2C链路没问题。接下来重点看动态表现:插上充电器,capacity应该缓慢上升;拔掉充电器,跑一个大型应用给电池放电,capacity应该平滑下降。任何一步出现“卡住不动”或者“跳水”,基本就是profile参数与电芯不匹配,回到4.2去校准。这里有个小技巧:用脚本每隔几秒记录一次voltage_now和capacity,边放视频边跑,能看出很明显的对应关系。
5. 排错实录:从“插电没反应”到“百分比跳变”的完整排查链
5.1 i2cdetect扫不到芯片,先别怀疑“DTS写错”
现象:开机后dmesg没有两个驱动的probe日志,用i2cdetect扫也看不到设备地址。我的排查链路是这样走的:
- 确认I2C总线已使能。看DTS里对应I2C节点有没有status = "okay",如果没有,扫描当然一片空白。
- 确认扫描的总线号正确。打开/sys/bus/i2c/devices/,看控制器注册成i2c-几。很多人扫错总线,查半天发现根本不是那一路。
- 量硬件供电。BQ25703的VIN来自VBUS,有些开发板上不插适配器时芯片整颗都不工作,自然扫不到地址。CW2015通常由系统电源供电,只要系统起来了,它就该在总线上。
- 查I2C上拉电阻。这个最容易忽略。I2C总线需要上拉电阻,如果PCB上漏贴或者焊错,总线被拉死或者信号畸形,i2cdetect会扫到一堆随机地址,或者直接超时。
- 确认地址硬件无偏移。CW2015的地址引脚如果被外部拉高拉低,0x62可能变掉,用工具扫描整段总线是最快的方法。
5.2 能充电但电流始终上不去
现象:插上支持快充的适配器,BQ25703能充电,但电流只有500mA。这个问题在RK3588平台上很典型,常见原因有三类:
- USB PD协商没有完成。Type-C口的PD协商是由SoC内部的PD控制器完成的,协商结果要传递给充电驱动,充电驱动再去设置BQ25703的输入电流和充电电流。如果PD驱动和charger驱动之间没有正确关联(很多内核里是通过extcon或usb_role机制联动),系统会一直按默认的USB 500mA来限流。
- DTS的input-current-limit设置过低。直接改大DTS里的默认输入限流,通常能立竿见影。但要注意,这个值不能盲目超过适配器能力,否则适配器会被拉挂。
- BQ25703寄存器处于watchdog复位后的默认值。这个我遇到过一次,驱动probe正常,但看门狗没被周期性喂,芯片寄存器回到默认的小电流模式。
排查这类问题时,我通常是边充电边反复cat几个节点:
cat /sys/class/power_supply/bq25703-0/status cat /sys/class/power_supply/bq25703-0/current_now cat /sys/class/power_supply/bq25703-0/input_current_limit如果input_current_limit显示只有500000,而你的适配器是20W以上,那问题多半在PD联动或者DTS默认值上。先在DTS里把默认input-current-limit提高到2000000以上,通电看电流是否跟着变。如果不变,说明驱动没有真正写入寄存器,要去查I2C通信和看门狗刷新逻辑。
5.3 电量百分比跳变,问题基本锁定在profile
现象:电池充满显示100%,拔掉充电器五分钟内掉到95%;或者边充边玩时,百分比从80%直接跳到87%又跳回来。这不是RK3588的问题,而是CW2015这类电量计在profile不匹配时的典型症状。排查时我会做一次完整的充放电循环:
- 用恒流恒压电源把电池充到截止电压,并保持到电流小于截止电流,记录电量计显示的容量。
- 静置1小时,记录电压和SOC。
- 以固定电流放电到截止电压,记录放电曲线和SOC变化。
- 把实测数据和DTS里的profile参数对比。
最直接的修复方式,就是拿到正确的电芯数据,重新生成profile填进DTS。如果没有官方工具,也可以用对比法:选一个容量接近、电压平台一致的已有profile试跑一个循环,看SOC曲线是否合理。注意,不同电芯的OCV曲线在高低端差异很大,不能靠猜。
5.4 中断风暴与内核日志刷屏
现象:开机后系统卡顿,dmesg里不停刷充电中断相关的日志,/proc/interrupts里对应中断计数飞涨。这是中断配置极性不对的典型表现。BQ25703和CW2015的INT脚通常默认高电平,事件发生时拉低。如果你的DTS里把IRQ_TYPE写成了LEVEL_HIGH,芯片空闲时反而一直触发中断,驱动在处理正常中断,却永远清不掉。
排查方式:
- 先在DTS里把中断改成IRQ_TYPE_LEVEL_LOW,重启看症状是否消失。
- 若还不行,把该中断脚对应的pinctrl配置确认一遍,看看GPIO是不是被复用成了其他功能。
- 还可以在驱动里临时把这个中断请求去掉,确认是硬件中断问题还是驱动中断处理函数的问题。
我自己在处理BQ25703时,还遇到过中断脚被另一个外设的pinctrl覆盖,导致probe失败。最终定位的方式是把对应GPIO在DTS里单独建一个pinctrl group,并在BQ25703节点里明确引用,彻底断开和其他外设的复用冲突。
下面这张表是我常用的排查对照:
| 现象 | 可能原因 | 优先检查项 | DTS里的解决位置 |
|---|---|---|---|
| i2cdetect扫不到 | I2C未使能/地址不对/硬件没供电 | 总线号、地址、供电 | I2C节点status、reg |
| 充电电流只有500mA | PD联动没建立/限流默认太低 | extcon/usb_role关联、input_current_limit | input-current-limit、PD相关节点 |
| 电量百分比跳变 | profile不匹配 | 充放电曲线对比 | bat_config_info |
| 中断刷屏 | 中断极性/GPIO复用错误 | /proc/interrupts、dmesg极性 | interrupts、pinctrl-0 |
6. 从“能充”到“充得好”:几个值得记录的调优经验
把BQ25703和CW2015的DTS配到能开机、能充电、电量能显示,这只是第一步。真正到量产阶段,有几个点我会反复折腾。
充电电流要和整机散热一起看。平板产品内部空间紧张,BQ25703周边的电感、MOS管在2A以上电流时升温明显。我测过某块板子在3A充电时,充电IC附近温度能到60多度。所以DTS里的charge-current不能只按电池能承受多大电流来填,还要考虑壳温、充电时长和电池循环寿命。量产前的温度测试一定要做,不能只在常温下看数据。
CW2015的profile不是一劳永逸的。电池随着循环次数增加,内阻会变大,容量会衰减,原始profile的精度会下降。产品如果支持OTA,建议在系统里预留电量计校准入口;如果不支持,就要在出厂前把老化后的电芯数据也测一遍,至少在配置上做一个折中。这里我个人的做法是:拿全新的电芯和循环200次的电芯各测一组数据,取一个中间配置,保证整个生命周期里的SOC误差不会太离谱。
DTS参数命名在不同SDK中真的会变。我见过同一个TI驱动,在Rockchip 4.19内核里解析的是ti,charge-current,在5.10里就换成了别的名字,甚至有驱动直接不解析外部属性,强制用宏定义。所以每次换SDK,都要重新grep一遍驱动源码,确认字段名、默认值、编译开关,不要盲目沿用旧DTS。这个习惯能省下大量重复踩坑的时间。
还有一个容易被忽略的点:RK3588平台上的Type-C PD和充电驱动是两套子系统,它们的耦合关系在不同内核版本里差异很大。如果你发现DTS配置都正确、驱动也加载正常,但PD快充就是跑不起来,多半要去查PD驱动的policy engine里,有没有把协商得到的电流传递给充电驱动的回调。这个回调有时候是通过extcon事件上报,有时候是通过direct charger接口,方向找对了,才能把“快充”这最后一公里走通。
以我个人的实测感受,这组充电和电量计配置,只要把硬件原理图、驱动源码、sysfs验证三条线串起来,DTS主体通常两个小时就能搞定。剩下的大量时间,基本都花在profile精度和充电策略的细调上。这也是我一直强调的:DTS不是抄完就结束,关键是要理解每个字段背后的电源策略,再用实测数据去校正它。希望这篇能把你在RK3588电源适配路上的弯路子都垫平,一次就把BQ25703和CW2015跑稳。