Linux power supply class hacking
/*************************************************************************** * 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 0; +--------------------------------------------------------+ | | } | | | | | | 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 = 0, 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 = 0; 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 = 0; | | continue; | | } | | | | if (ret < 0) | | goto out; | | | | line = strchr(prop_buf, '\n'); | | if (line) | | *line = 0; | | | | 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 = 0; 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) + 1]; <-----+ --+ | /* 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 = 0; | 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 < 0) { | | | 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, 10, &long_val); | | if (ret < 0) | | return ret; | | | | value.intval = long_val; | | | | ret = psy->set_property(psy, off, &value); | | if (ret < 0) | | 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 0; | | } | | 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 = 0; || u8 *regs; || || /* Get new ID for the new battery device */ || retval = idr_pre_get(&battery_id, GFP_KERNEL); || if (retval == 0) || return -ENOMEM; || mutex_lock(&battery_mutex); || retval = idr_get_new(&battery_id, client, &num); || mutex_unlock(&battery_mutex); || if (retval < 0) || 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 = 0; || || 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, 60 * 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 0; | | | | 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 0; | | | | | } | | | | | | | | | | 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 = 0; | || struct bq27x00_device_info *di = to_bq27x00_device_info(psy); | || | || mutex_lock(&di->lock); | || if (time_is_before_jiffies(di->last_update + 5 * 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 < 0) | || 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 < 0 ? 0 : 1; | || 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 = {0, }; || bool is_bq27500 = di->chip == BQ27500; || || cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, is_bq27500); || if (cache.flags >= 0) { || 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 <= 0) || 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)) != 0) { ------+| 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);