原文网址(转载请注明出处):
源码基于:Android Q
目录
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);
}