/***************************************************************************
* Linux power supply class hacking
* 声明:
* 本文主要是记录linux电源管理的工作机制是什么,那些供Android jni使用
* 的属性文件是如何生成的,调用机制是什么。
*
* 2016-2-23 深圳 南山平山村 曾剑锋
**************************************************************************/ 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); ------------------*-+
| | |
return ; +--------------------------------------------------------+ | |
} | | |
| | |
static void __exit power_supply_class_exit(void) | | |
{ | | |
class_destroy(power_supply_class); | | |
} | | |
| | |
subsys_initcall(power_supply_class_init); | | |
module_exit(power_supply_class_exit); | | |
| | |
MODULE_DESCRIPTION("Universal power supply monitor class"); | | |
MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, " | | |
"Szabolcs Gyurko, " | | |
"Anton Vorontsov <cbou@mail.ru>"); | | |
MODULE_LICENSE("GPL"); | | |
| | |
int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env) <-*-+ |
{ | |
struct power_supply *psy = dev_get_drvdata(dev); | |
int ret = , j; | |
char *prop_buf; | |
char *attrname; | |
| |
dev_dbg(dev, "uevent\n"); | |
| |
if (!psy || !psy->dev) { | |
dev_dbg(dev, "No power supply yet\n"); | |
return ret; | |
} | |
| |
dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->name); | |
| |
ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->name); | |
if (ret) | |
return ret; | |
| |
prop_buf = (char *)get_zeroed_page(GFP_KERNEL); | |
if (!prop_buf) | |
return -ENOMEM; | |
| |
for (j = ; j < psy->num_properties; j++) { | |
struct device_attribute *attr; | |
char *line; | |
| |
attr = &power_supply_attrs[psy->properties[j]]; | |
| |
ret = power_supply_show_property(dev, attr, prop_buf); | |
if (ret == -ENODEV || ret == -ENODATA) { | |
/* When a battery is absent, we expect -ENODEV. Don't abort; | |
send the uevent with at least the the PRESENT=0 property */ | |
ret = ; | |
continue; | |
} | |
| |
if (ret < ) | |
goto out; | |
| |
line = strchr(prop_buf, '\n'); | |
if (line) | |
*line = ; | |
| |
attrname = kstruprdup(attr->attr.name, GFP_KERNEL); | |
if (!attrname) { | |
ret = -ENOMEM; | |
goto out; | |
} | |
| |
dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf); | |
| |
ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf); | |
kfree(attrname); | |
if (ret) | |
goto out; | |
} | |
| |
out: | |
free_page((unsigned long)prop_buf); | |
| |
return ret; | |
} | |
| |
static struct device_type power_supply_dev_type; <--------------------*---+
^ |
/* +-----------------------------------------------+ |
* The type of device, "struct device" is embedded in. A class | |
* or bus can contain devices of different types | |
* like "partitions" and "disks", "mouse" and "event". | |
* This identifies the device type and carries type-specific | |
* information, equivalent to the kobj_type of a kobject. | |
* If "name" is specified, the uevent will contain it in | |
* the DEVTYPE variable. | |
*/ | |
struct device_type { <----------------------+ |
const char *name; |
const struct attribute_group **groups; |
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); |
char *(*devnode)(struct device *dev, mode_t *mode); |
void (*release)(struct device *dev); |
|
const struct dev_pm_ops *pm; |
}; |
|
void power_supply_init_attrs(struct device_type *dev_type) <------------+
{
int i;
----------+
dev_type->groups = power_supply_attr_groups; |
|
for (i = ; i < ARRAY_SIZE(power_supply_attrs); i++) |
__power_supply_attrs[i] = &power_supply_attrs[i].attr; ------+ |
} | |
| |
static const struct attribute_group *power_supply_attr_groups[] = { <--*-+
&power_supply_attr_group, ------------+ |
NULL, | |
}; | |
| |
static struct attribute_group power_supply_attr_group = { <-----+ |
.attrs = __power_supply_attrs, ------+ |
.is_visible = power_supply_attr_is_visible, | |
}; | |
| |
static struct attribute * | |
__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + ]; <-----+ --+
|
/* Must be in the same order as POWER_SUPPLY_PROP_* */ |
static struct device_attribute power_supply_attrs[] = { <-----------+
/* Properties of type `int' */
POWER_SUPPLY_ATTR(status),
POWER_SUPPLY_ATTR(charge_type),
POWER_SUPPLY_ATTR(health),
POWER_SUPPLY_ATTR(present),
POWER_SUPPLY_ATTR(online),
POWER_SUPPLY_ATTR(technology),
POWER_SUPPLY_ATTR(cycle_count),
POWER_SUPPLY_ATTR(voltage_max),
POWER_SUPPLY_ATTR(voltage_min),
POWER_SUPPLY_ATTR(voltage_max_design),
POWER_SUPPLY_ATTR(voltage_min_design),
POWER_SUPPLY_ATTR(voltage_now),
POWER_SUPPLY_ATTR(voltage_avg),
POWER_SUPPLY_ATTR(current_max),
POWER_SUPPLY_ATTR(current_now),
POWER_SUPPLY_ATTR(current_avg),
POWER_SUPPLY_ATTR(power_now),
POWER_SUPPLY_ATTR(power_avg),
POWER_SUPPLY_ATTR(charge_full_design),
POWER_SUPPLY_ATTR(charge_empty_design),
POWER_SUPPLY_ATTR(charge_full),
POWER_SUPPLY_ATTR(charge_empty),
POWER_SUPPLY_ATTR(charge_now),
POWER_SUPPLY_ATTR(charge_avg),
POWER_SUPPLY_ATTR(charge_counter),
POWER_SUPPLY_ATTR(energy_full_design),
POWER_SUPPLY_ATTR(energy_empty_design),
POWER_SUPPLY_ATTR(energy_full),
POWER_SUPPLY_ATTR(energy_empty),
POWER_SUPPLY_ATTR(energy_now),
POWER_SUPPLY_ATTR(energy_avg),
POWER_SUPPLY_ATTR(capacity),
POWER_SUPPLY_ATTR(capacity_level),
POWER_SUPPLY_ATTR(temp),
POWER_SUPPLY_ATTR(temp_ambient),
POWER_SUPPLY_ATTR(time_to_empty_now),
POWER_SUPPLY_ATTR(time_to_empty_avg),
POWER_SUPPLY_ATTR(time_to_full_now),
POWER_SUPPLY_ATTR(time_to_full_avg),
POWER_SUPPLY_ATTR(type),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(model_name), --------------+
POWER_SUPPLY_ATTR(manufacturer), |
POWER_SUPPLY_ATTR(serial_number), |
}; +--------------------------------------+
v
#define POWER_SUPPLY_ATTR(_name) \
{ \
.attr = { .name = #_name }, \
.show = power_supply_show_property, \ ---------+
.store = power_supply_store_property, \ ---------*-+
} | |
| |
static ssize_t power_supply_show_property(struct device *dev, <-----+ |
struct device_attribute *attr, |
char *buf) { |
static char *type_text[] = { |
"Battery", "UPS", "Mains", "USB", |
"USB_DCP", "USB_CDP", "USB_ACA" |
}; |
static char *status_text[] = { |
"Unknown", "Charging", "Discharging", "Not charging", "Full" |
}; |
static char *charge_type[] = { |
"Unknown", "N/A", "Trickle", "Fast" |
}; |
static char *health_text[] = { |
"Unknown", "Good", "Overheat", "Dead", "Over voltage", |
"Unspecified failure", "Cold", |
}; |
static char *technology_text[] = { |
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", |
"LiMn" |
}; |
static char *capacity_level_text[] = { |
"Unknown", "Critical", "Low", "Normal", "High", "Full" |
}; |
ssize_t ret = ; |
struct power_supply *psy = dev_get_drvdata(dev); --------*-+
const ptrdiff_t off = attr - power_supply_attrs; | |
union power_supply_propval value; | |
| |
if (off == POWER_SUPPLY_PROP_TYPE) | |
value.intval = psy->type; | |
else | |
ret = psy->get_property(psy, off, &value); <-------*-*------+
| | |
if (ret < ) { | | |
if (ret == -ENODATA) | | |
dev_dbg(dev, "driver has no data for `%s' property\n", | | |
attr->attr.name); | | |
else if (ret != -ENODEV) | | |
dev_err(dev, "driver failed to report `%s' property\n", | | |
attr->attr.name); | | |
return ret; | | |
} | | |
| | |
if (off == POWER_SUPPLY_PROP_STATUS) | | |
return sprintf(buf, "%s\n", status_text[value.intval]); | | |
else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE) | | |
return sprintf(buf, "%s\n", charge_type[value.intval]); | | |
else if (off == POWER_SUPPLY_PROP_HEALTH) | | |
return sprintf(buf, "%s\n", health_text[value.intval]); | | |
else if (off == POWER_SUPPLY_PROP_TECHNOLOGY) | | |
return sprintf(buf, "%s\n", technology_text[value.intval]); | | |
else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL) | | |
return sprintf(buf, "%s\n", capacity_level_text[value.intval]); | | |
else if (off == POWER_SUPPLY_PROP_TYPE) | | |
return sprintf(buf, "%s\n", type_text[value.intval]); | | |
else if (off >= POWER_SUPPLY_PROP_MODEL_NAME) | | |
return sprintf(buf, "%s\n", value.strval); | | |
| | |
return sprintf(buf, "%d\n", value.intval); | | |
} | | |
| | |
static ssize_t power_supply_store_property(struct device *dev, <-------+ | |
struct device_attribute *attr, | |
const char *buf, size_t count) { | |
ssize_t ret; | |
struct power_supply *psy = dev_get_drvdata(dev); ----------+ |
const ptrdiff_t off = attr - power_supply_attrs; | |
union power_supply_propval value; | |
long long_val; | |
| |
/* TODO: support other types than int */ | |
ret = strict_strtol(buf, , &long_val); | |
if (ret < ) | |
return ret; | |
| |
value.intval = long_val; | |
| |
ret = psy->set_property(psy, off, &value); | |
if (ret < ) | |
return ret; | |
| |
return count; | |
} | |
| |
void *dev_get_drvdata(const struct device *dev) <-----+ <----------+ |
{ + |
if (dev && dev->p) + |
return dev->p->driver_data; + |
return NULL; + |
} + |
EXPORT_SYMBOL(dev_get_drvdata); + |
+ |
int dev_set_drvdata(struct device *dev, void *data) <-----+ <----------+ |
{ | |
int error; | |
| |
if (!dev->p) { | |
error = device_private_init(dev); | |
if (error) | |
return error; | |
} | |
dev->p->driver_data = data; | |
return ; | |
} | |
EXPORT_SYMBOL(dev_set_drvdata); | |
| |
static inline void i2c_set_clientdata(struct i2c_client *dev, void *data) | ----+|
{ | ||
dev_set_drvdata(&dev->dev, data); -----------------------------+ ||
} ||
||
static int __init bq27x00_battery_probe(struct i2c_client *client, ||
const struct i2c_device_id *id) ||
{ ||
char *name; ||
struct bq27x00_device_info *di; ||
int num; ||
int retval = ; ||
u8 *regs; ||
||
/* Get new ID for the new battery device */ ||
retval = idr_pre_get(&battery_id, GFP_KERNEL); ||
if (retval == ) ||
return -ENOMEM; ||
mutex_lock(&battery_mutex); ||
retval = idr_get_new(&battery_id, client, &num); ||
mutex_unlock(&battery_mutex); ||
if (retval < ) ||
return retval; ||
||
name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); ||
if (!name) { ||
dev_err(&client->dev, "failed to allocate device name\n"); ||
retval = -ENOMEM; ||
goto batt_failed_1; ||
} ||
||
di = kzalloc(sizeof(*di), GFP_KERNEL); ||
if (!di) { ||
dev_err(&client->dev, "failed to allocate device info data\n"); ||
retval = -ENOMEM; ||
goto batt_failed_2; ||
} ||
||
di->id = num; ||
di->dev = &client->dev; ||
di->chip = id->driver_data; ||
di->bat.name = name; ||
di->bus.read = &bq27xxx_read_i2c; ||
di->bus.write = &bq27xxx_write_i2c; ||
di->bus.blk_read = bq27xxx_read_i2c_blk; ||
di->bus.blk_write = bq27xxx_write_i2c_blk; ||
di->dm_regs = NULL; ||
di->dm_regs_count = ; ||
||
if (di->chip == BQ27200) ||
regs = bq27200_regs; ||
else if (di->chip == BQ27500) ||
regs = bq27500_regs; ||
else if (di->chip == BQ27520) ||
regs = bq27520_regs; ||
else if (di->chip == BQ2753X) ||
regs = bq2753x_regs; ||
else if (di->chip == BQ274XX) { ||
regs = bq274xx_regs; ||
di->dm_regs = bq274xx_dm_regs; ||
di->dm_regs_count = ARRAY_SIZE(bq274xx_dm_regs); ||
} else if (di->chip == BQ276XX) { ||
/* commands are same as bq274xx, only DM is different */ ||
regs = bq276xx_regs; ||
di->dm_regs = bq276xx_dm_regs; ||
di->dm_regs_count = ARRAY_SIZE(bq276xx_dm_regs); ||
} else { ||
dev_err(&client->dev, ||
"Unexpected gas gague: %d\n", di->chip); ||
regs = bq27520_regs; ||
} ||
||
memcpy(di->regs, regs, NUM_REGS); ||
||
di->fw_ver = bq27x00_battery_read_fw_version(di); ||
dev_info(&client->dev, "Gas Guage fw version is 0x%04x\n", di->fw_ver); ||
||
retval = bq27x00_powersupply_init(di); --------+ ||
if (retval) | ||
goto batt_failed_3; | ||
| ||
/* Schedule a polling after about 1 min */ | ||
schedule_delayed_work(&di->work, * HZ); | ||
| ||
i2c_set_clientdata(client, di); <-------|----+|
retval = sysfs_create_group(&client->dev.kobj, &bq27x00_attr_group); | |
if (retval) | |
dev_err(&client->dev, "could not create sysfs files\n"); | |
| |
return ; | |
| |
batt_failed_3: | |
kfree(di); | |
batt_failed_2: | |
kfree(name); | |
batt_failed_1: | |
mutex_lock(&battery_mutex); | |
idr_remove(&battery_id, num); | |
mutex_unlock(&battery_mutex); | |
| |
return retval; | |
} | |
| |
static int bq27x00_powersupply_init(struct bq27x00_device_info *di) <-----+ |
{ | |
int ret; +------------------+ |
v--------------------------------------------------------------*------+ |
di->bat.type = POWER_SUPPLY_TYPE_BATTERY; | | |
di->bat.properties = bq27x00_battery_props; | | |
di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props); | | |
di->bat.get_property = bq27x00_battery_get_property; ------------+-*------*-+
di->bat.external_power_changed = bq27x00_external_power_changed; | | |
| | |
INIT_DELAYED_WORK(&di->work, bq27x00_battery_poll); | | |
mutex_init(&di->lock); | | |
| | |
ret = power_supply_register(di->dev, &di->bat); ----------*-*------*----+
if (ret) { | | | |
dev_err(di->dev, "failed to register battery: %d\n", ret); | | | |
return ret; | | | |
} | | | |
| | | |
dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); | | | |
| | | |
bq27x00_update(di); --------------*-*-+ | |
| | | | |
return ; | | | | |
} | | | | |
| | | | |
struct bq27x00_device_info { <--------*-+ | | |
struct device *dev; | | | |
int id; | | | |
enum bq27x00_chip chip; | | | |
| | | |
struct bq27x00_reg_cache cache; ---------+ <--------*---*----*---+|
int charge_design_full; | | | | ||
| | | | ||
unsigned long last_update; | | | | ||
struct delayed_work work; | | | | ||
| | | | ||
struct power_supply bat; | <--------*---*----+ ||
| | | ||
struct bq27x00_access_methods bus; | | | ||
| | | ||
struct mutex lock; | | | ||
}; | | | ||
| | | ||
struct bq27x00_reg_cache { <---------+ | | ||
int temperature; | | ||
int time_to_empty; | | ||
int time_to_empty_avg; | | ||
int time_to_full; | | ||
int charge_full; | | ||
int cycle_count; | | ||
int capacity; | | ||
int flags; | | ||
| | ||
int current_now; | | ||
}; | | ||
| | ||
static int bq27x00_battery_get_property(struct power_supply *psy, <---+ | ||
enum power_supply_property psp, | ||
union power_supply_propval *val) | ||
{ | ||
int ret = ; | ||
struct bq27x00_device_info *di = to_bq27x00_device_info(psy); | ||
| ||
mutex_lock(&di->lock); | ||
if (time_is_before_jiffies(di->last_update + * HZ)) { | ||
cancel_delayed_work_sync(&di->work); | ||
bq27x00_battery_poll(&di->work.work); | ||
} | ||
mutex_unlock(&di->lock); | ||
| ||
if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < ) | ||
return -ENODEV; | ||
| ||
switch (psp) { | ||
case POWER_SUPPLY_PROP_STATUS: | ||
ret = bq27x00_battery_status(di, val); | ||
break; | ||
case POWER_SUPPLY_PROP_VOLTAGE_NOW: | ||
ret = bq27x00_battery_voltage(di, val); | ||
break; | ||
case POWER_SUPPLY_PROP_PRESENT: | ||
val->intval = di->cache.flags < ? : ; | ||
break; | ||
case POWER_SUPPLY_PROP_CURRENT_NOW: | ||
ret = bq27x00_battery_current(di, val); | ||
break; | ||
case POWER_SUPPLY_PROP_CAPACITY: | ||
ret = bq27x00_simple_value(di->cache.capacity, val); | ||
break; | ||
case POWER_SUPPLY_PROP_TEMP: | ||
ret = bq27x00_battery_temperature(di, val); | ||
break; | ||
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: | ||
ret = bq27x00_simple_value(di->cache.time_to_empty, val); | ||
break; | ||
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: | ||
ret = bq27x00_simple_value(di->cache.time_to_empty_avg, val); | ||
break; | ||
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: | ||
ret = bq27x00_simple_value(di->cache.time_to_full, val); | ||
break; | ||
case POWER_SUPPLY_PROP_TECHNOLOGY: | ||
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; | ||
break; | ||
case POWER_SUPPLY_PROP_CHARGE_NOW: | ||
ret = bq27x00_simple_value(bq27x00_battery_read_nac(di), val); | ||
break; | ||
case POWER_SUPPLY_PROP_CHARGE_FULL: | ||
ret = bq27x00_simple_value(di->cache.charge_full, val); | ||
break; | ||
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: | ||
ret = bq27x00_simple_value(di->charge_design_full, val); | ||
break; | ||
case POWER_SUPPLY_PROP_CYCLE_COUNT: | ||
ret = bq27x00_simple_value(di->cache.cycle_count, val); | ||
break; | ||
case POWER_SUPPLY_PROP_ENERGY_NOW: | ||
ret = bq27x00_battery_energy(di, val); | ||
break; | ||
default: | ||
return -EINVAL; | ||
} | ||
| ||
return ret; | ||
} | ||
| ||
static void bq27x00_update(struct bq27x00_device_info *di) <------------+ ||
{ ||
struct bq27x00_reg_cache cache = {, }; ||
bool is_bq27500 = di->chip == BQ27500; ||
||
cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, is_bq27500); ||
if (cache.flags >= ) { ||
cache.capacity = bq27x00_battery_read_rsoc(di); ||
cache.temperature = bq27x00_read(di, BQ27x00_REG_TEMP, false); ||
cache.time_to_empty = bq27x00_battery_read_time(di, BQ27x00_REG_TTE); ||
cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP);||
cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF); ||
cache.charge_full = bq27x00_battery_read_lmd(di); ||
cache.cycle_count = bq27x00_battery_read_cyct(di); ||
||
if (!is_bq27500) ||
cache.current_now = bq27x00_read(di, BQ27x00_REG_AI, false); ||
||
/* We only have to read charge design full once */ ||
if (di->charge_design_full <= ) ||
di->charge_design_full = bq27x00_battery_read_ilmd(di); ||
} ||
||
/* Ignore current_now which is a snapshot of the current battery state ||
* and is likely to be different even between two consecutive reads */ ||
if (memcmp(&di->cache, &cache, sizeof(cache) - sizeof(int)) != ) { ------+|
di->cache = cache; |
power_supply_changed(&di->bat); |
} |
|
di->last_update = jiffies; |
} |
|
int power_supply_register(struct device *parent, struct power_supply *psy) <------+
{
struct device *dev;
int rc; dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM; device_initialize(dev); dev->class = power_supply_class;
dev->type = &power_supply_dev_type;
dev->parent = parent;
dev->release = power_supply_dev_release;
dev_set_drvdata(dev, psy);
psy->dev = dev; INIT_WORK(&psy->changed_work, power_supply_changed_work); rc = kobject_set_name(&dev->kobj, "%s", psy->name);
if (rc)
goto kobject_set_name_failed; spin_lock_init(&psy->changed_lock);
wake_lock_init(&psy->work_wake_lock, WAKE_LOCK_SUSPEND, "power-supply"); rc = device_add(dev);
if (rc)
goto device_add_failed; rc = power_supply_create_triggers(psy);
if (rc)
goto create_triggers_failed; power_supply_changed(psy); goto success; create_triggers_failed:
wake_lock_destroy(&psy->work_wake_lock);
device_del(dev);
kobject_set_name_failed:
device_add_failed:
put_device(dev);
success:
return rc;
}
EXPORT_SYMBOL_GPL(power_supply_register);