From a86f458c0e4a5d142c9345e99b082bdfa7cffee8 Mon Sep 17 00:00:00 2001 From: Jeff Curless Date: Sun, 5 Oct 2025 13:07:56 -0400 Subject: [PATCH] Add dummy battery module to the system... Hopefully this can be modified to create the actual code needed to monitor the battery of the argon-oneUp. --- battery/Makefile | 7 + battery/build | 4 + battery/dummy_battery.c | 328 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 battery/Makefile create mode 100755 battery/build create mode 100644 battery/dummy_battery.c diff --git a/battery/Makefile b/battery/Makefile new file mode 100644 index 0000000..5808b8e --- /dev/null +++ b/battery/Makefile @@ -0,0 +1,7 @@ +obj-m += dummy_battery.o + +# Build with: +# make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules +# Clean: +# make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + diff --git a/battery/build b/battery/build new file mode 100755 index 0000000..3ac0e83 --- /dev/null +++ b/battery/build @@ -0,0 +1,4 @@ +#!/usr/bin/bash + +make -C /lib/modules/$(uname -r)/build M=$PWD modules + diff --git a/battery/dummy_battery.c b/battery/dummy_battery.c new file mode 100644 index 0000000..712e249 --- /dev/null +++ b/battery/dummy_battery.c @@ -0,0 +1,328 @@ +// 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"); +