背景
最近公司某产品用到了TI的电量计芯片BQ40Z50
,我负责为其开发Linux驱动,搜了下,github上有TI为其写好的开源驱动,太好了。
看了下代码,比较简单,连Makefile都没写,不过这也挺好,说明对编译环境没有要求。自己编写好Makefile后编译,出现3个编译错误:
bq40z50_fg.c:609:2: error: 'POWER_SUPPLY_PROP_RESISTANCE_ID' undeclared here (not in a function); did you mean 'POWER_SUPPLY_PROP_VOLTAGE_MIN'?
609 | POWER_SUPPLY_PROP_RESISTANCE_ID,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| POWER_SUPPLY_PROP_VOLTAGE_MIN
bq40z50_fg.c:610:2: error: 'POWER_SUPPLY_PROP_UPDATE_NOW' undeclared here (not in a function); did you mean 'POWER_SUPPLY_PROP_CHARGE_NOW'?
610 | POWER_SUPPLY_PROP_UPDATE_NOW,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
| POWER_SUPPLY_PROP_CHARGE_NOW
bq40z50_fg.c:777:22: error: 'POWER_SUPPLY_TYPE_BMS' undeclared (first use in this function); did you mean 'POWER_SUPPLY_TYPE_UPS'?
777 | bq->fg_psy_d.type = POWER_SUPPLY_TYPE_BMS;
| ^~~~~~~~~~~~~~~~~~~~~
| POWER_SUPPLY_TYPE_UPS
分析一番发现,是因为我们产品是基于5.10内核的,而TI的开源驱动是基于4.4内核的,有些宏定义在5.10内核找不到。
解决过程
首先检查出错的宏是不是改名了,一番对比后确认,不是改名,就是删掉了,估计是觉得跟其他相关宏定义重合度太高。
移植POWER_SUPPLY_TYPE_BMS
先检查POWER_SUPPLY_TYPE_BMS
,感觉意思跟它相近的是POWER_SUPPLY_TYPE_BATTERY
,发现4.4内核只有TI的电量计bq40z50、bq34z100、bq28z610、bq27z860、bq27z561、bq27532、bq27426芯片驱动选的是前者,其他厂家的驱动都选的别的,其中选后者的有84个,甚至TI自己的bq2560x、bq25700、bq2588x、bq27xxx也是。
再看4.4内核drivers/power/power_supply_core.c
对二者的处理,发现压根没处理BMS类型,所以我决定将BMS改成BATTERY。
移植POWER_SUPPLY_PROP_RESISTANCE_ID
该属性没有对应的替代,不过查看TI对该属性的实现,发现get是固定返回0,set没实现,那还有什么意义?直接删除了事。
移植POWER_SUPPLY_PROP_UPDATE_NOW
该属性也没有对应的替代,不过查看TI对该属性的实现,发现get也是固定返回0,set是dump所有I2C寄存器,只是方便调试,删了也没啥影响。
加载运行效果
做了前述修改后,代码在5.10内核上编译通过,加载成功,在sysfs
里能看到各种电量计属性,有些属性的值明显有误,这是因为I2C总线有硬件问题,跟驱动无关。
最终代码补丁
Index: bq40z50_fg.c
===================================================================
--- bq40z50_fg.c (版本 418)
+++ bq40z50_fg.c (版本 419)
@@ -606,8 +593,6 @@
/*POWER_SUPPLY_PROP_HEALTH,*//*implement it in battery power_supply*/
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_TECHNOLOGY,
- POWER_SUPPLY_PROP_RESISTANCE_ID,
- POWER_SUPPLY_PROP_UPDATE_NOW,
};
static int fg_get_property(struct power_supply *psy, enum power_supply_property psp,
@@ -710,13 +695,6 @@
val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO;
break;
- case POWER_SUPPLY_PROP_RESISTANCE_ID:
- val->intval = 0;
- break;
- case POWER_SUPPLY_PROP_UPDATE_NOW:
- val->intval = 0;
- break;
-
default:
return -EINVAL;
}
@@ -738,9 +716,6 @@
bq->fake_soc = val->intval;
power_supply_changed(bq->fg_psy);
break;
- case POWER_SUPPLY_PROP_UPDATE_NOW:
- fg_dump_registers(bq);
- break;
default:
return -EINVAL;
}
@@ -757,7 +732,6 @@
switch (prop) {
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_CAPACITY:
- case POWER_SUPPLY_PROP_UPDATE_NOW:
ret = 1;
break;
default:
@@ -774,7 +748,7 @@
struct power_supply_config fg_psy_cfg = {};
bq->fg_psy_d.name = "bms";
- bq->fg_psy_d.type = POWER_SUPPLY_TYPE_BMS;
+ bq->fg_psy_d.type = POWER_SUPPLY_TYPE_BATTERY;
bq->fg_psy_d.properties = fg_props;
bq->fg_psy_d.num_properties = ARRAY_SIZE(fg_props);
bq->fg_psy_d.get_property = fg_get_property;
总结
芯片厂商开发Linux驱动时,为了节省人力,一般会选择一个内核版本进行开发,开发完毕后一般不会随Linux大版本的发布而更新,因为人家的驱动代码主要是做演示用的,版本发布导致的内核接口变更需要工程师自己去适配。