展锐Kernel层电量百分比获取流程

原文网址(转载请注明出处):

http://t.csdnimg.cn/OIfb7

源码基于:Android  Q

目录

1.介绍

2.节点注册流程

3.节点回调方法的初始化流程

4.写入capacity节点流程

1.介绍

查看sys目录下battery下节点信息,主要节点的功能如下:

/sys/class/power_supply/battery

    • battery_charging_enabled: 表示电池在充电,插着充电器,不一定给电池供电的( 可能只是给设备供电,没有通过电池)
    • capacity:电池百分比
    • capacity_raw
    • charging_enabled :表示充电器存在,即充电器是插入状态,系统有闪电图标也表示充电器在,但是不表示在充电
    • health : 电池健康状态
    • status : 电池的状态,是否充电等
    • voltage_max : 电池最大电压(满电时的电压)
    • voltage_now : 电池当前电压
    • ...

2.节点注册流程

每个节点的注册都在bsp/kernel/kernel4.14/drivers/power/supply/power_supply_sysfs.c代码中:

bsp/kernel/kernel4.14/drivers/power/supply/power_supply_sysfs.c

#define POWER_SUPPLY_ATTR(_name)                    \
{                                   \
    .attr = { .name = #_name },                 \
    .show = power_supply_show_property,             \
    .store = power_supply_store_property,               \
}

static struct device_attribute power_supply_attrs[] = {
    POWER_SUPPLY_ATTR(status),
    POWER_SUPPLY_ATTR(charge_type),
    POWER_SUPPLY_ATTR(health),
    POWER_SUPPLY_ATTR(capacity),
    ...
    POWER_SUPPLY_ATTR(capacity_raw),
    ...
};

在节点中,当我们使用echo 向节点写值时会调用.store 指向的方法,通过cat 读取节点时会调用.show方法。

3.节点回调方法的初始化流程

bsp/kernel/kernel4.14/drivers/power/supply/power_supply_core.c
 
static int __init power_supply_class_init(void)
{
    power_supply_class = class_create(THIS_MODULE, "power_supply");
 
    if (IS_ERR(power_supply_class))
        return PTR_ERR(power_supply_class);
 
    power_supply_class->dev_uevent = power_supply_uevent;
    power_supply_init_attrs(&power_supply_dev_type);   // 初始化power_supply子节点信息和回调函数
 
    return 0;
}
 
subsys_initcall(power_supply_class_init);  // 该驱动加载时会首先调用此方法调用power_supply_class_init() 方法
module_exit(power_supply_class_exit);      // kernel 退出时会调用此此昂发
 
MODULE_DESCRIPTION("Universal power supply monitor class");
MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, "
          "Szabolcs Gyurko, "
          "Anton Vorontsov <cbou@mail.ru>");
MODULE_LICENSE("GPL");
bsp/kernel/kernel4.14/drivers/power/supply/power_supply_sysfs.c
 
...
static struct attribute *__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
...
void power_supply_init_attrs(struct device_type *dev_type)
{
    int i;
 
    dev_type->groups = power_supply_attr_groups;
 
    for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++)
        __power_supply_attrs[i] = &power_supply_attrs[i].attr;
}
 
static struct attribute_group power_supply_attr_group = {
    .attrs = __power_supply_attrs,
    .is_visible = power_supply_attr_is_visible,
};
 
static const struct attribute_group *power_supply_attr_groups[] = {
    &power_supply_attr_group,
    NULL,
};

会将所有节点的attr 保存到__power_supply_attrs 数组中,传给power_supply_attr_groups 结构体中完成之后的注册流程。

4.写入capacity节点流程

bsp/kernel/kernel4.14/drivers/power/supply/charger-manager.c
 
static struct platform_driver charger_manager_driver = {
    .driver = {
        .name = "charger-manager",
        .pm = &charger_manager_pm,
        .of_match_table = charger_manager_match,
    },
    .probe = charger_manager_probe,
    .remove = charger_manager_remove,
    .shutdown = charger_manager_shutdown,
    .id_table = charger_manager_id,
};
 
static int charger_manager_probe(struct platform_device *pdev)
{
    ...
    INIT_DELAYED_WORK(&cm->cap_update_work, cm_batt_works); // 添加一个事务,定时执行cm_batt_works 方法
    ...
    // CM_CAP_CYCLE_TRACK_TIME = 15
    // HZ = 100
    queue_delayed_work(system_power_efficient_wq, &cm->cap_update_work, CM_CAP_CYCLE_TRACK_TIME * HZ);
    ...
}

INIT_DELAYED_WORK 会初始化一个工作队列来执行cm_batt_works() 方法
queue_delayed_work 方法会延时执行工作队列,每1500ms 执行一次。

接下来接着查看cm_batt_works 方法做了什么事儿,

bsp/kernel/kernel4.14/drivers/power/supply/charger-manager.c
 
static void cm_batt_works(struct work_struct *work)
{
    ...
    ret = get_batt_cap(cm, &fuel_cap); // 计算出当前电池剩余容量的大小,保存到fuel_cap 中
    ...
    if (fuel_cap != cm->desc->cap) { // 判断当前计算出来的容量是否和读出来的一样(电池容量是否有改变)
        /*power_supply_changed use ui_cap update strategy*/
        if (DIV_ROUND_CLOSEST(fuel_cap * 101, 960)
            != DIV_ROUND_CLOSEST(cm->desc->cap * 101, 960)) {
            cm->desc->cap = fuel_cap;
            cm->desc->update_capacity_time = cur_time.tv_sec;
            power_supply_changed(cm->charger_psy);
        }
        cm->desc->cap = fuel_cap;
        set_batt_cap(cm, cm->desc->cap); // 设置新的电池容量
    }
    ...
}

接下来我们看看get_batt_cap() 方法做了什么,

bsp/kernel/kernel4.14/drivers/power/supply/charger-manager.c
 
static int get_batt_cap(struct charger_manager *cm, int *cap)
{
    union power_supply_propval val;
    struct power_supply *fuel_gauge;
    int ret;
 
    fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);  // 获取到相应的节点信息
    if (!fuel_gauge)
        return -ENODEV;
 
    val.intval = CM_CAPACITY;
    ret = power_supply_get_property(fuel_gauge, POWER_SUPPLY_PROP_CAPACITY, &val); // 获取节点信息
    power_supply_put(fuel_gauge);
    if (ret)
        return ret;
 
    *cap = val.intval;
 
    dev_info(cm->dev, "%s: fuel_cap = %d\n", __func__, *cap);
    return 0;
}
bsp/kernel/kernel4.14/drivers/power/supply/power_supply_core.c
int power_supply_get_property(struct power_supply *psy,
                enum power_supply_property psp,
                union power_supply_propval *val)
{
    if (atomic_read(&psy->use_cnt) <= 0) {
        if (!psy->initialized)
            return -EAGAIN;
        return -ENODEV;
    }
 
    return psy->desc->get_property(psy, psp, val); // 调用get_property 方法
}

我们会使用sc27xx_fuel_gauge 这个电池驱动,所以使用的方法为sc27xx_fgu_get_property() 方法

bsp/kernel/kernel4.14/drivers/power/supply/sc27xx_fuel_gauge.c
 
static const struct power_supply_desc sc27xx_fgu_desc = {
    .name           = "sc27xx-fgu",
    .type           = POWER_SUPPLY_TYPE_UNKNOWN,
    .properties     = sc27xx_fgu_props,
    .num_properties     = ARRAY_SIZE(sc27xx_fgu_props),
    .get_property       = sc27xx_fgu_get_property,
    .set_property       = sc27xx_fgu_set_property,
    .external_power_changed = sc27xx_fgu_external_power_changed,
    .property_is_writeable  = sc27xx_fgu_property_is_writeable,
    .no_thermal     = true,
};
 
static int sc27xx_fgu_probe(struct platform_device *pdev)
{
    ...
    data->battery = devm_power_supply_register(&pdev->dev, &sc27xx_fgu_desc, &fgu_cfg);
    ...
}
 
static struct platform_driver sc27xx_fgu_driver = {
    .probe = sc27xx_fgu_probe,
    .driver = {
        .name = "sc27xx-fgu",
        .of_match_table = sc27xx_fgu_of_match,
        .pm = &sc27xx_fgu_pm_ops,
    }
};
 
module_platform_driver(sc27xx_fgu_driver);  // 驱动的初始化方法,这个方法是宏定义,最终展开就是module_init 和 module_exit 方法

同理,set_batt_cap 方法最终也会走到sc27xx_fuel_gauge 的 sc27xx_fgu_set_property 方法

bsp/kernel/kernel4.14/drivers/power/supply/charger-manager.c
 
static int set_batt_cap(struct charger_manager *cm, int cap)
{
    union power_supply_propval val;
    struct power_supply *fuel_gauge;
    int ret;
 
    dev_info(cm->dev, "cap = %d\n", cap);
    fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
    if (!fuel_gauge) {
        dev_err(cm->dev, "can not find fuel gauge device\n");
        return -ENODEV;
    }
 
    val.intval = cap;
    ret = power_supply_set_property(fuel_gauge, POWER_SUPPLY_PROP_CAPACITY,
                    &val);
    power_supply_put(fuel_gauge);
    if (ret)
        dev_err(cm->dev, "failed to save current battery capacity\n");
 
    return ret;
}
bsp/kernel/kernel4.14/drivers/power/supply/power_supply_core.c
 
int power_supply_set_property(struct power_supply *psy,
                enum power_supply_property psp,
                const union power_supply_propval *val)
{
    if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property)
        return -ENODEV;
    return psy->desc->set_property(psy, psp, val);
}

接着查看sc27xx_fgu_get_property 方法的实现

bsp/kernel/kernel4.14/drivers/power/supply/sc27xx_fuel_gauge.c
 
static int sc27xx_fgu_get_property(struct power_supply *psy,
                   enum power_supply_property psp,
                   union power_supply_propval *val)
{
    struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy);
    int ret = 0;
    int value, chg_sts;
    ...
    mutex_lock(&data->lock);
 
    switch (psp) {
        ...
        case POWER_SUPPLY_PROP_CAPACITY:
            if (val->intval == CM_BOOT_CAPACITY) {
                val->intval = data->boot_cap;
                break;
            }
     
            ret = sc27xx_fgu_get_capacity(data, &value, chg_sts);
            if (ret)
                goto error;
     
            val->intval = value;
            break;
        ...
    }
error:
    mutex_unlock(&data->lock);
    return ret;
}
 
 
static int sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap,
                   int chg_sts)
{
    int ret, cur_clbcnt, delta_clbcnt, delta_cap, temp, temp_cap;
    int saved_cap = 500, cap_gap = 0, cap_smooth_start = 500, cap_ocv = 500;
    static bool charging_phase_normal = false;
    static bool charging_phase_cool = false;
    /* Get current coulomb counters firstly */
    ret = sc27xx_fgu_get_clbcnt(data, &cur_clbcnt);
    if (ret)
        return ret;
 
    delta_clbcnt = cur_clbcnt - data->init_clbcnt;
 
    /*
     * Convert coulomb counter to delta capacity (mAh), and set multiplier
     * as 10 to improve the precision.
     */
    temp = DIV_ROUND_CLOSEST(delta_clbcnt * 10, 36 * SC27XX_FGU_SAMPLE_HZ);
    if (temp > 0)
        temp = temp + data->cur_1000ma_adc / 2;
    else
        temp = temp - data->cur_1000ma_adc / 2;
 
    temp = div_s64(temp, data->cur_1000ma_adc);
 
    /*
     * Convert to capacity percent of the battery total capacity,
     * and multiplier is 100 too.
     */
    delta_cap = DIV_ROUND_CLOSEST(temp * 1000, data->total_cap);
    *cap = delta_cap + data->init_cap;
    data->normal_temperature_cap = *cap;
    if (data->normal_temperature_cap < 0)
        data->normal_temperature_cap = 0;
    else if (data->normal_temperature_cap > 1000)
        data->normal_temperature_cap = 1000;
 
    SPRD_FGU_DEBUG("%s: cap: %d, cur_clbcnt: %d, init_clbcnt: %d, delta_cap: %d, init_cap: %d\n",
        __func__, *cap, cur_clbcnt, data->init_clbcnt, delta_cap, data->init_cap);
 
    if (data->boot_cap_gap > 0) {
        cap_gap = data->boot_cap_gap;
        cap_smooth_start = data->boot_cap_save;
 
        if (chg_sts == POWER_SUPPLY_STATUS_CHARGING) {
            if (charging_phase_normal == false) {
                ret = sc27xx_fgu_read_last_cap(data, &saved_cap);
                if (ret) {
                    dev_err(data->dev, "%s: failed to read last cap\n", __func__);
                    goto temp_cap;
                }
                sc27xx_fgu_adjust_cap(data, saved_cap);
                *cap = saved_cap;
                charging_phase_normal = true;
                SPRD_FGU_DEBUG("%s: turned to charge, reg saved cap: %d, boot_cap_gap %d\n",
                    __func__, saved_cap, data->boot_cap_gap);
            }
        } else {
            if (charging_phase_normal) {
                charging_phase_normal = false;
                cap_smooth_start = *cap;
                ret = sc27xx_fgu_get_ocv_cap(data, &cap_ocv);
                if (ret) {
                    dev_err(data->dev, "%s: failed to read last ocv cap\n", __func__);
                    goto temp_cap;
                }
                if (cap_smooth_start > cap_ocv * FULL_RAW_CAP_FACTOR)
                    cap_gap = cap_smooth_start - cap_ocv * FULL_RAW_CAP_FACTOR;
                else {
                    SPRD_FGU_DEBUG("%s: no cap gap anymore! cap now %d, cap_gap %d\n",
                        __func__, cap_smooth_start, cap_gap);
                    data->boot_cap_gap = -1;
                    goto temp_cap;
                }
 
                SPRD_FGU_DEBUG("%s: turned to discharge, cap now %d, cap_gap %d\n",
                    __func__, cap_smooth_start, cap_gap);
            }
            if (cap_smooth_start > cap_gap)
                *cap = (*cap - cap_gap) * cap_smooth_start / (cap_smooth_start - cap_gap);
        }
    }
 
temp_cap:
    if (data->cap_table_len > 0) {
        temp_cap = sc27xx_fgu_temp_to_cap(data->cap_temp_table,
                          data->cap_table_len,
                          data->bat_temp);
        /*
         * Battery capacity at different temperatures, we think
         * the change is linear, the follow the formula: y = ax + k
         *
         * for example: display 100% at 25 degrees need to display
         * 100% at -10 degrees, display 10% at 25 degrees need to
         * display 0% at -10 degrees, substituting the above special
         * points will deduced follow formula.
         * formula 1:
         * Capacity_Delta = 100 - Capacity_Percentage(T1)
         * formula 2:
         * Capacity_temp = (Capacity_Percentage(current) -
         * Capacity_Delta) * 100 /(100 - Capacity_Delta)
         */
        temp_cap *= 10;
 
        if (chg_sts == POWER_SUPPLY_STATUS_CHARGING && data->boot_cap_cold_gap > 0) {
            if (charging_phase_cool == false) {
                ret = sc27xx_fgu_read_last_cap(data, &saved_cap);
                if (ret) {
                    dev_err(data->dev, "%s: failed to read last cap\n", __func__);
                    goto normal_cal;
                }
                sc27xx_fgu_adjust_cap(data, saved_cap);
                *cap = saved_cap;
                charging_phase_cool = true;
                SPRD_FGU_DEBUG("%s: turned to charge, reg saved cap: %d\n", __func__, saved_cap);
            }
        } else {
            if (charging_phase_cool) {
                charging_phase_cool = false;
                SPRD_FGU_DEBUG("%s: turned to discharge, temp_cap %d, bat_temp %d\n",
                __func__, temp_cap, data->bat_temp);
            }
 
            *cap = DIV_ROUND_CLOSEST((*cap + temp_cap - 1000) * 1000, temp_cap);
        }
 
        SPRD_FGU_DEBUG("%s: cap %d, temp_cap %d, bat_temp %d\n",
            __func__, *cap, temp_cap, data->bat_temp);
    }
 
normal_cal:
    if (*cap < 0) {
        *cap = 0;
        sc27xx_fgu_adjust_cap(data, 0);
        data->boot_cap_gap = -1;
        data->boot_cap_cold_gap = -1;
        data->boot_cap_save = -1;
        return 0;
    } else if (*cap > 1000) {
        *cap = 1000;
        data->init_cap = 1000 - delta_cap;
        return 0;
    }
 
    sc27xx_fgu_low_capacity_calibration(data, *cap, false, chg_sts);
 
    return 0;
}

在sc27xx_fgu_get_capacity 方法中会通过电流、电压等数值计算出当前的电池容量。 之后再做分析,接着我们再看 sc27xx_fgu_set_property() 方法,

bsp/kernel/kernel4.14/drivers/power/supply/sc27xx_fuel_gauge.c
 
static int sc27xx_fgu_set_property(struct power_supply *psy,
                   enum power_supply_property psp,
                   const union power_supply_propval *val)
{
    struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy);
    int ret;
 
    mutex_lock(&data->lock);
 
    switch (psp) {
    case POWER_SUPPLY_PROP_CAPACITY:
        ret = sc27xx_fgu_save_last_cap(data, val->intval);         // 这里会设置电池容量大小
        if (ret < 0) {
            dev_err(data->dev, "failed to save battery capacity\n");
            goto error;
        }
 
        ret = sc27xx_fgu_save_normal_temperature_cap(data, data->normal_temperature_cap);
        if (ret < 0)
            dev_err(data->dev, "failed to save normal temperature capacity\n");
        break;
    ...
    default:
        ret = -EINVAL;
    }
 
error:
    mutex_unlock(&data->lock);
    return ret;
}
 
 
static int sc27xx_fgu_save_last_cap(struct sc27xx_fgu_data *data, int cap)
{
    int ret;
    u32 value;
 
    SPRD_FGU_DEBUG("%s: cap:%d\n", __func__, cap);
    ret = regmap_update_bits(data->regmap,
                 data->base + SC27XX_FGU_USER_AREA_CLEAR,
                 SC27XX_FGU_CAP_AREA_MASK,
                 SC27XX_FGU_CAP_AREA_MASK);
    if (ret)
        return ret;
 
    /*
     * Since the user area registers are put on power always-on region,
     * then these registers changing time will be a little long. Thus
     * here we should delay 200us to wait until values are updated
     * successfully.
     */
    udelay(200);
 
    value = (cap / 10) & SC27XX_FGU_CAP_INTEGER_MASK;
    value |= ((cap % 10) & SC27XX_FGU_CAP_DECIMAL_MASK) << SC27XX_FGU_CAP_DECIMAL_SHIFT;
 
    // 在这里更新值到节点中
    ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_USER_AREA_SET, SC27XX_FGU_CAP_AREA_MASK, value);
    if (ret)
        return ret;
 
    udelay(200);
 
    return regmap_update_bits(data->regmap,
                  data->base + SC27XX_FGU_USER_AREA_CLEAR,
                  SC27XX_FGU_CAP_AREA_MASK, 0);
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lion_heart001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值