将TI的电量计Linux驱动从4.4内核移植到5.10

背景

最近公司某产品用到了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总线有硬件问题,跟驱动无关。
bq40z50驱动的应用层接口

最终代码补丁

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大版本的发布而更新,因为人家的驱动代码主要是做演示用的,版本发布导致的内核接口变更需要工程师自己去适配。