CW2015电量计驱动分析


Chip:CW2015
SoC:RK3288
Platform:Android 5.1


PSY

一般power supply分为三种:DC,USB,battery
本文着重分析battery,并介绍CW2015电量计调试的相关经验

probe()函数

分析驱动毫无疑问从probe()函数开始,函数原型:

static int cw_bat_probe(struct i2c_client *client,
            const struct i2c_device_id *id)

进入probe()后,首先需要获取电量计相关gpio,调用函数cw2015_parse_dt(),目的:

  • 获取电池信息
  • DC检测脚
  • 低电量中断脚
  • 是否支持DC充电
  • 是否支持USB充电

然后调用cw_bat_gpio_init()初始化各gpio的值。随后,进入电量计的初始化函数cw_init()。

cw_init()

cw2015的初始化流程可参考datasheet,其操作流程如下:

1. WAKE UP使其退出上电默认的sleep状态
2. 设置容量门限值ATHD
3. 检查UPDATE_FLAG标志位
   - 如果UPDATE_FLAG有置位,检查电池信息是否一致
   - 如果没有置位,写入电池信息,并置位UPDATE_FLAG
4. 判断SOC是否合法,不合法cw2015进入sleep状态

流程图如下
CW2015电量计驱动分析_第1张图片

CW2015初始化成功后,填充struct power_supply各字段,注册三种power_supply设备,这里只关注battery部分:

    cw_bat->rk_bat.name = "rk-bat";
    cw_bat->rk_bat.type = POWER_SUPPLY_TYPE_BATTERY;
    cw_bat->rk_bat.properties = rk_battery_properties;
    cw_bat->rk_bat.num_properties = ARRAY_SIZE(rk_battery_properties);
    cw_bat->rk_bat.get_property = rk_battery_get_property;
    ret = power_supply_register(&client->dev, &cw_bat->rk_bat);
    if (ret < 0) {
        dev_err(&cw_bat->client->dev,
            "power supply register rk_bat error\n");
        goto rk_bat_register_fail;
    }

get_property:get_property方法提供了sys用户接口获取电池信息,调用rk_battery_get_property。该方法通过power_supply_register注册进power supply core,用户层读取时再回调。

get_property的sys接口可参考power supply sys

power_supply_register:初始化电池uevent change工作队列,注册power supply设备。

INIT_WORK(&psy->changed_work, power_supply_changed_work);

注册完power supply设备,各个功能就可以正常工作了,然后通过工作队列更新电池信息。

电池相关处理

电池信息相关处理主要由几个工作队列和中断来完成:

  • cw_bat_work-更新电池信息
  • dc_detect_do_wakeup-DC状态中断函数
  • bat_low_detect_do_wakeup-低电压处理

本文只分析cw_bat_work()电池信息更新部分。

cw_bat_work

首先会判断是否支持DC充电和USB充电(dts配置),如下:

    if (cw_bat->plat_data.is_dc_charge == 1) {   //支持DC充电
        ret = rk_ac_update_online(cw_bat);
        if (ret == 1)
            power_supply_changed(&cw_bat->rk_ac);
    }

    if (cw_bat->plat_data.is_usb_charge == 1) {  //支持usb充电
        ret = rk_usb_update_online(cw_bat);
        if (ret == 1) {
            power_supply_changed(&cw_bat->rk_usb);
            power_supply_changed(&cw_bat->rk_ac);
        }
    }

支持DC充电时(usb充电暂不分析),调用rk_ac_update_online(),进行状态的切换,如下:

    /*判断dc_det_pin是否有效*/
    if (!gpio_is_valid(cw_bat->plat_data.dc_det_pin)) {  
        cw_bat->dc_online = 0;
        pr_info("%s dc charger without dc_det_pin\n", __func__);
        return 0;
    }
    /*
    判断dc是否插入,接入DC时该脚拉低,只接电池或OTG时该脚为高电平
    dc_online只判断一次,也就是DC插拔时电池status只切换一次

    */
    if (gpio_get_value(cw_bat->plat_data.dc_det_pin) ==    //为0表示接入了DC,DC充电模式
        cw_bat->plat_data.dc_det_level) {   //dc_det_level为GPIO_ACTIVE_LOW
        if (cw_bat->dc_online != 1) {
            cw_update_time_member_charge_start(cw_bat);
            cw_bat->dc_online = 1;
            if (cw_bat->charger_mode != AC_CHARGER_MODE)
                cw_bat->charger_mode = AC_CHARGER_MODE;

            ret = 1;
        }
    } else {    //为1表示未接入DC,bat放电模式
        if (cw_bat->dc_online != 0) {
            cw_update_time_member_charge_start(cw_bat);
            cw_bat->dc_online = 0;
            if (cw_bat->usb_online == 0)
                cw_bat->charger_mode = 0;
            ret = 1;
        }
    }

然后会调用rk_bat_update_status()更新电池状态:充电,充满,放电

    if (cw_bat->charger_mode > 0) {
        if (cw_bat->capacity >= 100)
            status = POWER_SUPPLY_STATUS_FULL;    //充满
        else
            status = POWER_SUPPLY_STATUS_CHARGING;   //充电
    } else {
        status = POWER_SUPPLY_STATUS_NOT_CHARGING;   //放电
    }

    if (cw_bat->status != status) {
        cw_bat->status = status;
        cw_bat->bat_change = 1;
    }

如果电池状态发生改变时,调用power_supply_changed(),该函数中调度changed_work工作队列,实际任务函数为power_supply_changed_work(),在注册power supply时初始化。
调用rk_bat_update_capacity()更新电池容量SOC,电池容量的获取主要是读取CW2015的0x4,0x5寄存器。实现逻辑为函数cw_get_capacity()
调用rk_bat_update_vol()更新电池电压VCELL,实现逻辑为函数cw_get_vol()
调用rk_bat_update_time_to_empty()更新系统可运行时间RRT,实现逻辑为函数cw_get_time_to_empty()
最后判断电池状态是否改变,若电池状态改变则调度changed_work工作队列。

power_supply_changed_work

每当电池状态或电池容量SOC,电压VCELL等发生改变时,最终都会调用power_supply_changed_work()通知uevent事件给上层。Android HAL由healthd负责监听。函数如下:

static void power_supply_changed_work(struct work_struct *work)
{
    unsigned long flags;
    struct power_supply *psy = container_of(work, struct power_supply,
                        changed_work);

    dev_dbg(psy->dev, "%s\n", __func__);

    spin_lock_irqsave(&psy->changed_lock, flags);
    if (psy->changed) {
        psy->changed = false;
        spin_unlock_irqrestore(&psy->changed_lock, flags);

        class_for_each_device(power_supply_class, NULL, psy,
                      __power_supply_changed_work);

        power_supply_update_leds(psy);

        kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
        spin_lock_irqsave(&psy->changed_lock, flags);
    }
    if (!psy->changed)
        pm_relax(psy->dev);
    spin_unlock_irqrestore(&psy->changed_lock, flags);
}

至此,CW2015驱动主要部分已分析完。但是在调试时需要注意:CW2015电量计需要配置电池信息,电池信息bat_config_info需由原厂配合电量计调校得出正确的电池信息,并在初始化时将电池信息写入CW2015,否则重启后获取到的电池容量SOC不准确。

SYS节点

CW201X驱动提供了以下几个字段来获取battery状态,实际上HAL uevent也是获取这几个接口的值,接口如下:

ls  /sys/class/power_supply/rk-bat/
capacity
device
health
power
present
status
subsystem
technology
time_to_empty_now
type
uevent
voltage_now

Capacity:电池容量百分比,该值会上报Android,设置中打开时,状态栏即显示该值。
Health:电池健康情况,平台默认为POWER_SUPPLY_HEALTH_GOOD,即返回Good
Status:电池充电状态。
Technology:电池采用的技术,平台默认为POWER_SUPPLY_TECHNOLOGY_LION,即返回Li-ion
Time_to_empty_now:电池电压。
Type:电池充电类型。
以上值除了平台默认的,其余的均从struct cw_battery中获取,电池信息的工作队列会一直更新struct cw_battery中的相关值。

问题

  1. 系统重启后电量计显示不准确
    写入CW2015中的电池信息bat_config_info不正确
  2. 拔掉电池与DC,再重新接上电池上电,电量计获取的值不准确。
    拔掉电池后PMIC完全掉电,此时再重新上电只能根据bat_config_info反推电量,会进行一次重新校准,和关机前有一定误差。如果希望电池电量和重新上电之前一致,则只能通过软件处理,比如在关机时将电池电量保存至文件中,开机时再去读。

你可能感兴趣的:(RK3288)