diff --git a/battery/Makefile b/battery/Makefile index 6a6f0b1..d2a3a60 100644 --- a/battery/Makefile +++ b/battery/Makefile @@ -1,4 +1,4 @@ -obj-m += test_power.o +obj-m += oneUpPower.o # Build with: # make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules diff --git a/battery/dummy_battery.c b/battery/dummy_battery.c deleted file mode 100644 index 712e249..0000000 --- a/battery/dummy_battery.c +++ /dev/null @@ -1,328 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Simple dummy battery driver (out-of-tree module) -// Registers a power_supply "dummy-battery" with a few common properties -// and a small work loop to simulate charge/discharge. -// -// Build (out-of-tree): -// make -C /lib/modules/$(uname -r)/build M=$PWD modules -// Use: -// sudo insmod dummy_battery.ko start_capacity=82 discharge_rate=1 -// See: -// ls -l /sys/class/power_supply/dummy-battery -// -// References: -// - Linux power_supply class docs -// - In-tree drivers/power/supply/test_power.c (example test driver) - -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRV_NAME "dummy_battery" -#define DFLT_CAPACITY 75 // percent -#define DFLT_VOLTAGE_UV 4000000 // 4.0V -#define DFLT_CURRENT_UA 50000 // 50 mA (sign: + = charging, - = discharging) -#define DFLT_TEMP_DECIC 300 // 30.0 C -#define TICK_MS 1000 // 1s tick - -struct dummy_batt { - struct power_supply *psy; - struct power_supply_desc desc; - - struct delayed_work sim_work; - struct mutex lock; - - /* Simulated state */ - int capacity; // 0..100 (%) - int voltage_uV; // microvolts - int current_uA; // microamps (signed) - int temp_deciC; // 0.1 C units - bool online_ac; // AC adapter present? - int status; -}; - -static int start_capacity = DFLT_CAPACITY; // initial SoC (%) -module_param(start_capacity, int, 0644); -MODULE_PARM_DESC(start_capacity, "Initial battery capacity in percent (0-100)"); - -static int discharge_rate = 1; // % per tick when discharging -module_param(discharge_rate, int, 0644); -MODULE_PARM_DESC(discharge_rate, "Capacity percent drop per second while discharging"); - -static int charge_rate = 2; // % per tick when charging -module_param(charge_rate, int, 0644); -MODULE_PARM_DESC(charge_rate, "Capacity percent rise per second while charging"); - -static bool start_charging; // start in charging state? -module_param(start_charging, bool, 0644); -MODULE_PARM_DESC(start_charging, "Start as charging (true) or discharging (false)"); - -static bool start_online_ac; // start with AC online? -module_param(start_online_ac, bool, 0644); -MODULE_PARM_DESC(start_online_ac, "Start with AC (mains) online (true/false)"); - -/* --- Property list --- */ -static enum power_supply_property dummy_props[] = { - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, // AC online - POWER_SUPPLY_PROP_CAPACITY, // % - POWER_SUPPLY_PROP_VOLTAGE_NOW, // uV - POWER_SUPPLY_PROP_CURRENT_NOW, // uA - POWER_SUPPLY_PROP_TEMP, // 0.1C - POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_SCOPE, -}; - -/* --- Get properties --- */ -static int dummy_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct dummy_batt *db = power_supply_get_drvdata(psy); - - mutex_lock(&db->lock); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = db->status; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = 1; // battery is always present - break; - case POWER_SUPPLY_PROP_ONLINE: - // represents AC adapter presence for this battery device - val->intval = db->online_ac ? 1 : 0; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = db->capacity; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = db->voltage_uV; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = db->current_uA; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = db->temp_deciC; - break; - case POWER_SUPPLY_PROP_HEALTH: - val->intval = POWER_SUPPLY_HEALTH_GOOD; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_SYSTEM; - break; - default: - mutex_unlock(&db->lock); - return -EINVAL; - } - - mutex_unlock(&db->lock); - return 0; -} - -/* Optional: allow some properties to be set via sysfs writes for fun */ -static int dummy_set_property(struct power_supply *psy, - enum power_supply_property psp, - const union power_supply_propval *val) -{ - struct dummy_batt *db = power_supply_get_drvdata(psy); - - mutex_lock(&db->lock); - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - db->online_ac = val->intval ? true : false; - db->status = db->online_ac ? POWER_SUPPLY_STATUS_CHARGING - : POWER_SUPPLY_STATUS_DISCHARGING; - break; - case POWER_SUPPLY_PROP_STATUS: - /* Allow forcing status. Note: AC ONLINE won't auto-toggle */ - switch (val->intval) { - case POWER_SUPPLY_STATUS_CHARGING: - case POWER_SUPPLY_STATUS_DISCHARGING: - case POWER_SUPPLY_STATUS_NOT_CHARGING: - case POWER_SUPPLY_STATUS_FULL: - db->status = val->intval; - break; - default: - mutex_unlock(&db->lock); - return -EINVAL; - } - break; - case POWER_SUPPLY_PROP_CAPACITY: - if (val->intval < 0 || val->intval > 100) { - mutex_unlock(&db->lock); - return -ERANGE; - } - db->capacity = val->intval; - break; - default: - mutex_unlock(&db->lock); - return -EINVAL; - } - mutex_unlock(&db->lock); - - // Tell userspace things changed - power_supply_changed(psy); - return 0; -} - -static int dummy_property_is_writeable(struct power_supply *psy, - enum power_supply_property psp) -{ - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - case POWER_SUPPLY_PROP_STATUS: - case POWER_SUPPLY_PROP_CAPACITY: - return 1; - default: - return 0; - } -} - -/* --- Simulation loop --- */ -static void dummy_sim_work(struct work_struct *work) -{ - struct dummy_batt *db = container_of(to_delayed_work(work), - struct dummy_batt, sim_work); - bool changed = false; - - mutex_lock(&db->lock); - - if (db->status == POWER_SUPPLY_STATUS_CHARGING && db->capacity < 100) { - db->capacity += charge_rate; - if (db->capacity >= 100) { - db->capacity = 100; - db->status = POWER_SUPPLY_STATUS_FULL; - } - changed = true; - } else if (db->status == POWER_SUPPLY_STATUS_DISCHARGING && db->capacity > 0) { - db->capacity -= discharge_rate; - if (db->capacity <= 0) { - db->capacity = 0; - db->status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } - changed = true; - } - - /* Simple linear voltage model: 3.5V @0% .. 4.2V @100% */ - db->voltage_uV = 3500000 + (db->capacity * 7000); // 3.5V + 0.007V * % - /* Current sign tracks status */ - if (db->status == POWER_SUPPLY_STATUS_CHARGING) - db->current_uA = 80000; // +80 mA - else if (db->status == POWER_SUPPLY_STATUS_DISCHARGING) - db->current_uA = -50000; // -50 mA - else - db->current_uA = 0; - - mutex_unlock(&db->lock); - - if (changed) - power_supply_changed(db->psy); - - /* Reschedule */ - schedule_delayed_work(&db->sim_work, msecs_to_jiffies(TICK_MS)); -} - -/* --- Platform plumbing (we create our own pdev) --- */ -static struct platform_device *dummy_pdev; - -static int dummy_probe(struct platform_device *pdev) -{ - struct power_supply_config cfg = {}; - struct dummy_batt *db; - int ret; - - db = devm_kzalloc(&pdev->dev, sizeof(*db), GFP_KERNEL); - if (!db) - return -ENOMEM; - - mutex_init(&db->lock); - - db->desc.name = "dummy-battery"; - db->desc.type = POWER_SUPPLY_TYPE_BATTERY; - db->desc.properties = dummy_props; - db->desc.num_properties = ARRAY_SIZE(dummy_props); - db->desc.get_property = dummy_get_property; - db->desc.set_property = dummy_set_property; - db->desc.property_is_writeable = dummy_property_is_writeable; - - /* Initial state */ - db->capacity = clamp(start_capacity, 0, 100); - db->voltage_uV = DFLT_VOLTAGE_UV; - db->current_uA = DFLT_CURRENT_UA; - db->temp_deciC = DFLT_TEMP_DECIC; - db->online_ac = start_online_ac; - db->status = start_charging ? POWER_SUPPLY_STATUS_CHARGING - : POWER_SUPPLY_STATUS_DISCHARGING; - - cfg.drv_data = db; - - db->psy = devm_power_supply_register(&pdev->dev, &db->desc, &cfg); - if (IS_ERR(db->psy)) { - ret = PTR_ERR(db->psy); - dev_err(&pdev->dev, "power_supply_register failed: %d\n", ret); - return ret; - } - - INIT_DELAYED_WORK(&db->sim_work, dummy_sim_work); - schedule_delayed_work(&db->sim_work, msecs_to_jiffies(TICK_MS)); - - platform_set_drvdata(pdev, db); - dev_info(&pdev->dev, "dummy-battery registered, start_capacity=%d%%\n", db->capacity); - return 0; -} - -static void dummy_remove(struct platform_device *pdev) -{ - struct dummy_batt *db = platform_get_drvdata(pdev); - cancel_delayed_work_sync(&db->sim_work); -} - -static struct platform_driver dummy_driver = { - .probe = dummy_probe, - .remove = dummy_remove, - .driver = { - .name = DRV_NAME, - }, -}; - -static int __init dummy_init(void) -{ - int ret; - - dummy_pdev = platform_device_register_simple(DRV_NAME, -1, NULL, 0); - if (IS_ERR(dummy_pdev)) - return PTR_ERR(dummy_pdev); - - ret = platform_driver_register(&dummy_driver); - if (ret) { - platform_device_unregister(dummy_pdev); - return ret; - } - pr_info(DRV_NAME ": loaded\n"); - return 0; -} -module_init(dummy_init); - -static void __exit dummy_exit(void) -{ - platform_driver_unregister(&dummy_driver); - platform_device_unregister(dummy_pdev); - pr_info(DRV_NAME ": unloaded\n"); -} -module_exit(dummy_exit); - -MODULE_AUTHOR("You"); -MODULE_DESCRIPTION("Dummy battery power_supply driver"); -MODULE_LICENSE("GPL"); - diff --git a/battery/oneUpPower.c b/battery/oneUpPower.c new file mode 100644 index 0000000..b666a96 --- /dev/null +++ b/battery/oneUpPower.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Power supply driver for testing. + * + * Copyright 2010 Anton Vorontsov + * + * Dynamic module parameter code from the Virtual Battery Driver + * Copyright (C) 2008 Pylone, Inc. + * By: Masashi YOKOTA + * Originally found here: + * http://downloads.pylone.jp/src/virtual_battery/virtual_battery-0.0.1.tar.bz2 + */ + +#include +#include +#include +#include +#include +#include + +enum test_power_id { + TEST_BATTERY, + TEST_AC, + TEST_POWER_NUM, +}; + +// +// +// +#define BLKDRV_NAME "BAT0" +#define FMT_PREFIX ": %s[%d] " +#define DEBUG_INFO( fmt, arg...) printk( KERN_INFO BLKDRV_NAME FMT_PREFIX fmt, __func__, __LINE__, ##arg ) + +#define TOTAL_LIFE_SECONDS (3 * 60 * 60) +#define TOTAL_CHARGE (2000 * 1000) // uAH +#define TOTAL_CHARGE_FULL_SECONDS (60 * 60) + +static int ac_online = 1; +static int battery_status = POWER_SUPPLY_STATUS_CHARGING; +static int battery_level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; +static int battery_health = POWER_SUPPLY_HEALTH_GOOD; +static int battery_present = 1; /* true */ +static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION; +static int battery_capacity = 80; +static int battery_timeleft = TOTAL_LIFE_SECONDS; +static int battery_temperature = 30; +static int battery_voltage = (4200 * 1000); // uV +static bool module_initialized; + +static int power_get_ac_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ac_online; + break; + default: + return -EINVAL; + } + return 0; +} + +static int get_battery_propertiesInts( struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val ) +{ + switch( psp ) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = battery_status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = battery_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = battery_present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = battery_technology; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = battery_level; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = battery_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = battery_capacity * TOTAL_CHARGE / 100; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = TOTAL_CHARGE; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + val->intval = battery_timeleft; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + val->intval = (100 - battery_capacity) * TOTAL_CHARGE_FULL_SECONDS / 100; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = battery_temperature; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battery_voltage; + break; + default: + pr_info("%s: some properties deliberately report errors.\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int get_battery_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "Test battery"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Linux"; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = UTS_RELEASE; + break; + default: + return get_battery_propertiesInts( psy, psp, val ); + } + + return 0 +} + +static enum power_supply_property test_power_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property test_power_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static char *test_power_ac_supplied_to[] = { + "BAT0", +}; + +static struct power_supply *test_power_supplies[TEST_POWER_NUM]; + +static const struct power_supply_desc test_power_desc[] = { + [TEST_BATTERY] = { + .name = "BAT0", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = test_power_battery_props, + .num_properties = ARRAY_SIZE(test_power_battery_props), + .get_property = get_battery_property, + }, + [TEST_AC] = { + .name = "AC0", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = test_power_ac_props, + .num_properties = ARRAY_SIZE(test_power_ac_props), + .get_property = power_get_ac_property, + + }, +}; + +static const struct power_supply_config test_power_configs[] = { + { /* test_battery */ + }, + { + /* test_ac */ + .supplied_to = test_power_ac_supplied_to, + .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), + }, +}; + +static int __init test_power_init(void) +{ + int i; + int ret; + + BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_supplies)); + BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_configs)); + + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) { + test_power_supplies[i] = power_supply_register(NULL, + &test_power_desc[i], + &test_power_configs[i]); + if (IS_ERR(test_power_supplies[i])) { + pr_err("%s: failed to register %s\n", __func__, + test_power_desc[i].name); + ret = PTR_ERR(test_power_supplies[i]); + goto failed; + } + } + + module_initialized = true; + return 0; +failed: + while (--i >= 0) + power_supply_unregister(test_power_supplies[i]); + return ret; +} +module_init(test_power_init); + +static void __exit test_power_exit(void) +{ + int i; + + /* Let's see how we handle changes... */ + ac_online = 0; + battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) + power_supply_changed(test_power_supplies[i]); + pr_info("%s: 'changed' event sent, sleeping for 10 seconds...\n", + __func__); + ssleep(10); + + for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) + power_supply_unregister(test_power_supplies[i]); + + module_initialized = false; +} +module_exit(test_power_exit); + + + +#define MAX_KEYLENGTH 256 +struct battery_property_map { + int value; + char const *key; +}; + +static struct battery_property_map map_ac_online[] = { + { 0, "off" }, + { 1, "on" }, + { -1, NULL }, +}; + +static struct battery_property_map map_health[] = { + { POWER_SUPPLY_HEALTH_GOOD, "good" }, + { POWER_SUPPLY_HEALTH_OVERHEAT, "overheat" }, + { POWER_SUPPLY_HEALTH_DEAD, "dead" }, + { POWER_SUPPLY_HEALTH_OVERVOLTAGE, "overvoltage" }, + { POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, "failure" }, + { -1, NULL }, +}; + +static struct battery_property_map map_present[] = { + { 0, "false" }, + { 1, "true" }, + { -1, NULL }, +}; + +static int map_get_value(struct battery_property_map *map, const char *key, + int def_val) +{ + char buf[MAX_KEYLENGTH]; + int cr; + + strscpy(buf, key, MAX_KEYLENGTH); + + cr = strnlen(buf, MAX_KEYLENGTH) - 1; + if (cr < 0) + return def_val; + if (buf[cr] == '\n') + buf[cr] = '\0'; + + while (map->key) { + if (strncasecmp(map->key, buf, MAX_KEYLENGTH) == 0) + return map->value; + map++; + } + + return def_val; +} + + +static const char *map_get_key(struct battery_property_map *map, int value, + const char *def_key) +{ + while (map->key) { + if (map->value == value) + return map->key; + map++; + } + + return def_key; +} + +static inline void signal_power_supply_changed(struct power_supply *psy) +{ + if (module_initialized) + power_supply_changed(psy); +} + +static int param_set_ac_online(const char *key, const struct kernel_param *kp) +{ + ac_online = map_get_value(map_ac_online, key, ac_online); + signal_power_supply_changed(test_power_supplies[TEST_AC]); + return 0; +} + +static int param_get_ac_online(char *buffer, const struct kernel_param *kp) +{ + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, ac_online, "unknown")); +} + +static int param_set_battery_health(const char *key, + const struct kernel_param *kp) +{ + battery_health = map_get_value(map_health, key, battery_health); + signal_power_supply_changed(test_power_supplies[TEST_BATTERY]); + return 0; +} + +static int param_get_battery_health(char *buffer, const struct kernel_param *kp) +{ + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, battery_health, "unknown")); +} + +static int param_set_battery_present(const char *key, + const struct kernel_param *kp) +{ + battery_present = map_get_value(map_present, key, battery_present); + signal_power_supply_changed(test_power_supplies[TEST_AC]); + return 0; +} + +static int param_get_battery_present(char *buffer, + const struct kernel_param *kp) +{ + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, battery_present, "unknown")); +} + +static const struct kernel_param_ops param_ops_ac_online = { + .set = param_set_ac_online, + .get = param_get_ac_online, +}; + +static const struct kernel_param_ops param_ops_battery_present = { + .set = param_set_battery_present, + .get = param_get_battery_present, +}; + +static const struct kernel_param_ops param_ops_battery_health = { + .set = param_set_battery_health, + .get = param_get_battery_health, +}; + +#define param_check_ac_online(name, p) __param_check(name, p, void); +#define param_check_battery_present(name, p) __param_check(name, p, void); + +module_param(ac_online, ac_online, 0644); +MODULE_PARM_DESC(ac_online, "AC charging state "); + +module_param(battery_present, battery_present, 0644); +MODULE_PARM_DESC(battery_present, + "battery presence state "); + +MODULE_DESCRIPTION("Power supply driver for testing"); +MODULE_AUTHOR("Anton Vorontsov "); +MODULE_LICENSE("GPL"); +