Merge pull request #13 from JeffCurless/allowSOCShutdown

Add support to change the shutdown threshold.
This commit is contained in:
Jeff Curless
2025-11-01 17:15:33 -04:00
committed by GitHub
3 changed files with 201 additions and 155 deletions

View File

@@ -1,8 +1,10 @@
#!/bin/bash
sudo cp -vf oneUpPower.ko /lib/modules/`uname -r`/kernel/drivers/power/supply/oneUpPower.ko sudo cp -vf oneUpPower.ko /lib/modules/`uname -r`/kernel/drivers/power/supply/oneUpPower.ko
if ! grep -qF "oneUpPower" /etc/modules
then
sudo sh -c 'echo "oneUpPower" >> /etc/modules'
fi
sudo sh -c 'echo "options oneUpPower soc_shutdown=5" > /etc/modprobe.d/oneUpPower.conf'
sudo depmod -a sudo depmod -a
ls /lib/modules/`uname -r`/kernel/drivers/power/supply/
sudo insmod oneUpPower.ko sudo insmod oneUpPower.ko
sync sync

View File

@@ -25,9 +25,9 @@
#include <generated/utsrelease.h> #include <generated/utsrelease.h>
enum test_power_id { enum test_power_id {
ONEUP_BATTERY, ONEUP_BATTERY,
ONEUP_AC, ONEUP_AC,
ONEUP_POWER_NUM, ONEUP_POWER_NUM,
}; };
// //
@@ -36,20 +36,20 @@ enum test_power_id {
#define DRV_NAME "oneUpPower" #define DRV_NAME "oneUpPower"
#define PR_INFO( fmt, arg...) printk( KERN_INFO DRV_NAME ":" fmt, ##arg ) #define PR_INFO( fmt, arg...) printk( KERN_INFO DRV_NAME ":" fmt, ##arg )
#define PR_ERR( fmt, arg... ) printk( KERN_ERR DRV_NAME ":" fmt, ##arg ) #define PR_ERR( fmt, arg... ) printk( KERN_ERR DRV_NAME ":" fmt, ##arg )
#define TOTAL_LIFE_SECONDS (6 * 60 * 60) // Time in seconds #define TOTAL_LIFE_SECONDS (6 * 60 * 60) // Time in seconds
#define TOTAL_CHARGE (4800 * 1000) // Power in micro Amp Hours, uAH #define TOTAL_CHARGE (4800 * 1000) // Power in micro Amp Hours, uAH
#define TOTAL_CHARGE_FULL_SECONDS (((2*60)+30) * 60) // Time to full charge in seconds #define TOTAL_CHARGE_FULL_SECONDS (((2*60)+30) * 60) // Time to full charge in seconds
// //
// I2C Addresses // I2C Addresses
// //
#define I2C_BUS 0x01 #define I2C_BUS 0x01
#define BATTERY_ADDR 0x64 #define BATTERY_ADDR 0x64
#define CURRENT_HIGH_REG 0x0E #define CURRENT_HIGH_REG 0x0E
#define CURRENT_LOW_REG 0x0F #define CURRENT_LOW_REG 0x0F
#define SOC_HIGH_REG 0x04 #define SOC_HIGH_REG 0x04
#define SOC_LOW_REG 0x05 #define SOC_LOW_REG 0x05
// //
// Needed data structures // Needed data structures
@@ -69,12 +69,12 @@ struct PowerStatus {
.status = POWER_SUPPLY_STATUS_DISCHARGING, .status = POWER_SUPPLY_STATUS_DISCHARGING,
.capacity = 100, .capacity = 100,
.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH, .capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH,
.health = POWER_SUPPLY_HEALTH_GOOD, .health = POWER_SUPPLY_HEALTH_GOOD,
.present = 1, .present = 1,
.technology = POWER_SUPPLY_TECHNOLOGY_LION, .technology = POWER_SUPPLY_TECHNOLOGY_LION,
.timeleft = TOTAL_LIFE_SECONDS, .timeleft = TOTAL_LIFE_SECONDS,
.temperature = 30, .temperature = 30,
.voltage = (4200 * 1000), // uV .voltage = (4200 * 1000), // uV
}; };
// //
@@ -83,6 +83,7 @@ struct PowerStatus {
static int get_battery_property(struct power_supply *psy, static int get_battery_property(struct power_supply *psy,
enum power_supply_property psp, enum power_supply_property psp,
union power_supply_propval *val); union power_supply_propval *val);
static int get_ac_property(struct power_supply *psy, static int get_ac_property(struct power_supply *psy,
enum power_supply_property psp, enum power_supply_property psp,
union power_supply_propval *val ); union power_supply_propval *val );
@@ -90,7 +91,7 @@ static int get_ac_property(struct power_supply *psy,
// //
// Globals // Globals
// //
static int critical_power_level = 5; // Default setting is 5% of power left for critical static int soc_shutdown = 5; // Default setting is 5% of power left for critical
static int ac_online = 1; // Are we connected to an external power source? static int ac_online = 1; // Are we connected to an external power source?
static bool module_initialized = false; // Has the driver been initialized? static bool module_initialized = false; // Has the driver been initialized?
static struct task_struct *monitor_task = NULL; // Place to store the monito task... static struct task_struct *monitor_task = NULL; // Place to store the monito task...
@@ -99,38 +100,38 @@ static struct task_struct *monitor_task = NULL; // Place to store the monit
// Properties for AC // Properties for AC
// //
static enum power_supply_property power_ac_props[] = { static enum power_supply_property power_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_ONLINE,
}; };
// //
// Properties supported to the Battery // Properties supported to the Battery
// //
static enum power_supply_property power_battery_props[] = { static enum power_supply_property power_battery_props[] = {
POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_EMPTY, POWER_SUPPLY_PROP_CHARGE_EMPTY,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_SERIAL_NUMBER, POWER_SUPPLY_PROP_SERIAL_NUMBER,
POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_NOW,
}; };
// //
// What Battery does the the AC object supply power to... // What Battery does the the AC object supply power to...
// //
static char *ac_power_supplied_to[] = { static char *ac_power_supplied_to[] = {
"BAT0", "BAT0",
}; };
// //
@@ -142,21 +143,21 @@ static struct power_supply *power_supplies[ONEUP_POWER_NUM];
// The power descriptions for the supplies // The power descriptions for the supplies
// //
static const struct power_supply_desc power_descriptions[] = { static const struct power_supply_desc power_descriptions[] = {
[ONEUP_BATTERY] = { [ONEUP_BATTERY] = {
.name = "BAT0", .name = "BAT0",
.type = POWER_SUPPLY_TYPE_BATTERY, .type = POWER_SUPPLY_TYPE_BATTERY,
.properties = power_battery_props, .properties = power_battery_props,
.num_properties = ARRAY_SIZE(power_battery_props), .num_properties = ARRAY_SIZE(power_battery_props),
.get_property = get_battery_property, .get_property = get_battery_property,
}, },
[ONEUP_AC] = { [ONEUP_AC] = {
.name = "AC0", .name = "AC0",
.type = POWER_SUPPLY_TYPE_MAINS, .type = POWER_SUPPLY_TYPE_MAINS,
.properties = power_ac_props, .properties = power_ac_props,
.num_properties = ARRAY_SIZE(power_ac_props), .num_properties = ARRAY_SIZE(power_ac_props),
.get_property = get_ac_property, .get_property = get_ac_property,
}, },
}; };
// //
@@ -164,12 +165,12 @@ static const struct power_supply_desc power_descriptions[] = {
// //
static const struct power_supply_config power_configs[] = { static const struct power_supply_config power_configs[] = {
{ /* battery */ { /* battery */
}, },
{ {
/* ac */ /* ac */
.supplied_to = ac_power_supplied_to, .supplied_to = ac_power_supplied_to,
.num_supplicants = ARRAY_SIZE(ac_power_supplied_to), .num_supplicants = ARRAY_SIZE(ac_power_supplied_to),
}, },
}; };
// //
@@ -186,25 +187,25 @@ static void set_power_states( void )
battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
} }
else if( capacity > 85 ){ else if( capacity > 85 ){
battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
} }
else if( capacity > 75 ){ else if( capacity > 75 ){
battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
} }
else if( capacity > 40 ){ else if( capacity > 40 ){
battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
} }
else { else {
battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; battery.capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
} }
if( ac_online ){ if( ac_online ){
if( capacity > 95 ){ if( capacity > 95 ){
battery.status = POWER_SUPPLY_STATUS_FULL; battery.status = POWER_SUPPLY_STATUS_FULL;
} }
else { else {
battery.status = POWER_SUPPLY_STATUS_CHARGING; battery.status = POWER_SUPPLY_STATUS_CHARGING;
} }
} }
else { else {
battery.status = POWER_SUPPLY_STATUS_DISCHARGING; battery.status = POWER_SUPPLY_STATUS_DISCHARGING;
@@ -233,19 +234,19 @@ static int check_ac_power( struct i2c_client *client )
plugged_in = 0; plugged_in = 0;
} }
else{ else{
plugged_in = 1; plugged_in = 1;
} }
if( ac_online != plugged_in ){ if( ac_online != plugged_in ){
ac_online = plugged_in; ac_online = plugged_in;
set_power_states(); set_power_states();
if( ac_online ){ if( ac_online ){
PR_INFO( "AC Power is connected.\n" ); PR_INFO( "AC Power is connected.\n" );
} }
else { else {
PR_INFO( "AC Power is disconnected.\n" ); PR_INFO( "AC Power is disconnected.\n" );
} }
power_supply_changed( power_supplies[ONEUP_AC] ); power_supply_changed( power_supplies[ONEUP_AC] );
} }
return plugged_in; return plugged_in;
@@ -270,13 +271,13 @@ static int check_battery_state( struct i2c_client *client )
if( SOCPercent > 100 ) if( SOCPercent > 100 )
SOCPercent = 100; SOCPercent = 100;
if( SOCPercent < 0 ) if( SOCPercent < 0 )
SOCPercent = 0; SOCPercent = 0;
if( battery.capacity != SOCPercent ){ if( battery.capacity != SOCPercent ){
battery.capacity = SOCPercent; battery.capacity = SOCPercent;
set_power_states(); set_power_states();
PR_INFO( "Battery State of charge is %d%%\n",SOCPercent ); PR_INFO( "Battery State of charge is %d%%\n",SOCPercent );
power_supply_changed( power_supplies[ONEUP_BATTERY] ); power_supply_changed( power_supplies[ONEUP_BATTERY] );
} }
return SOCPercent; return SOCPercent;
@@ -333,16 +334,16 @@ static int system_monitor( void *args )
// get an adapter // get an adapter
// //
set_current_state( TASK_INTERRUPTIBLE ); set_current_state( TASK_INTERRUPTIBLE );
adapter = i2c_get_adapter( I2C_BUS ); adapter = i2c_get_adapter( I2C_BUS );
PR_INFO( "Adapter = %p\n",adapter); PR_INFO( "Adapter = %p\n",adapter);
} }
else if( client == NULL ){ else if( client == NULL ){
// //
// Get a i2c client // Get a i2c client
// //
set_current_state( TASK_INTERRUPTIBLE ); set_current_state( TASK_INTERRUPTIBLE );
client = i2c_new_client_device( adapter, &board_info ); client = i2c_new_client_device( adapter, &board_info );
PR_INFO( "Client = %p\n",client); PR_INFO( "Client = %p\n",client);
} }
else{ else{
set_current_state( TASK_UNINTERRUPTIBLE ); set_current_state( TASK_UNINTERRUPTIBLE );
@@ -354,7 +355,7 @@ static int system_monitor( void *args )
soc = check_battery_state( client ); soc = check_battery_state( client );
set_current_state( TASK_INTERRUPTIBLE ); set_current_state( TASK_INTERRUPTIBLE );
if( !plugged_in && (soc < critical_power_level) ){ if( !plugged_in && (soc < soc_shutdown) ){
// not pluggged in and below critical state, shutdown // not pluggged in and below critical state, shutdown
PR_INFO( "Performing system shutdown unplugged and power is at %d\n",soc); PR_INFO( "Performing system shutdown unplugged and power is at %d\n",soc);
shutdown_helper(); shutdown_helper();
@@ -374,8 +375,8 @@ static int system_monitor( void *args )
if( adapter ) if( adapter )
{ {
i2c_put_adapter( adapter ); i2c_put_adapter( adapter );
adapter = NULL; adapter = NULL;
} }
PR_INFO( "System monitor is stopping...\n" ); PR_INFO( "System monitor is stopping...\n" );
@@ -407,8 +408,8 @@ static int get_ac_property(struct power_supply *psy,
break; break;
default: default:
return -EINVAL; return -EINVAL;
} }
return 0; return 0;
} }
// //
@@ -479,9 +480,9 @@ static int get_battery_int_property( struct power_supply *psy,
default: default:
PR_INFO("%s: some properties deliberately report errors.\n",__func__); PR_INFO("%s: some properties deliberately report errors.\n",__func__);
return -EINVAL; return -EINVAL;
} }
return 0; return 0;
} }
// //
@@ -531,45 +532,45 @@ static int get_battery_property(struct power_supply *psy,
// //
static int __init oneup_power_init(void) static int __init oneup_power_init(void)
{ {
int i; int i;
int ret; int ret;
PR_INFO( "Starting Power monitor..." ); PR_INFO( "Starting Power monitor..." );
BUILD_BUG_ON(ONEUP_POWER_NUM != ARRAY_SIZE(power_supplies)); BUILD_BUG_ON(ONEUP_POWER_NUM != ARRAY_SIZE(power_supplies));
BUILD_BUG_ON(ONEUP_POWER_NUM != ARRAY_SIZE(power_configs)); BUILD_BUG_ON(ONEUP_POWER_NUM != ARRAY_SIZE(power_configs));
for (i = 0; i < ARRAY_SIZE(power_supplies); i++) { for (i = 0; i < ARRAY_SIZE(power_supplies); i++) {
power_supplies[i] = power_supply_register(NULL, power_supplies[i] = power_supply_register(NULL,
&power_descriptions[i], &power_descriptions[i],
&power_configs[i]); &power_configs[i]);
if (IS_ERR(power_supplies[i])) { if (IS_ERR(power_supplies[i])) {
PR_ERR("%s: failed to register %s\n", __func__, power_descriptions[i].name); PR_ERR("%s: failed to register %s\n", __func__, power_descriptions[i].name);
ret = PTR_ERR(power_supplies[i]); ret = PTR_ERR(power_supplies[i]);
goto failed; goto failed;
} }
}
monitor_task = kthread_run( system_monitor, NULL, "argon40_monitor" );
if( monitor_task == NULL ){
PR_ERR( "Could not start system_monitor, terminating.\n" );
ret = -EINVAL;
goto failed;
}
set_power_states();
module_initialized = true;
return 0;
failed:
if( monitor_task ){
kthread_stop( monitor_task );
monitor_task = NULL;
}
while (--i >= 0){
power_supply_unregister(power_supplies[i]);
} }
return ret; monitor_task = kthread_run( system_monitor, NULL, "argon40_monitor" );
if( monitor_task == NULL ){
PR_ERR( "Could not start system_monitor, terminating.\n" );
ret = -EINVAL;
goto failed;
}
set_power_states();
module_initialized = true;
return 0;
failed:
if( monitor_task ){
kthread_stop( monitor_task );
monitor_task = NULL;
}
while (--i >= 0){
power_supply_unregister(power_supplies[i]);
}
return ret;
} }
module_init(oneup_power_init); module_init(oneup_power_init);
@@ -580,33 +581,70 @@ module_init(oneup_power_init);
// //
static void __exit oneup_power_exit(void) static void __exit oneup_power_exit(void)
{ {
int i; int i;
// //
// First up, stop the monitor task as its using resources // First up, stop the monitor task as its using resources
// //
if( monitor_task ){ if( monitor_task ){
kthread_stop( monitor_task ); kthread_stop( monitor_task );
monitor_task = NULL; monitor_task = NULL;
} }
/* Let's see how we handle changes... */ /* Let's see how we handle changes... */
ac_online = 0; ac_online = 0;
battery.status = POWER_SUPPLY_STATUS_DISCHARGING; battery.status = POWER_SUPPLY_STATUS_DISCHARGING;
for (i = 0; i < ARRAY_SIZE(power_supplies); i++) for (i = 0; i < ARRAY_SIZE(power_supplies); i++)
power_supply_changed(power_supplies[i]); power_supply_changed(power_supplies[i]);
//PR_INFO("%s: 'changed' event sent, sleeping for 10 seconds...\n", __func__); //PR_INFO("%s: 'changed' event sent, sleeping for 10 seconds...\n", __func__);
//ssleep(10); //ssleep(10);
for (i = 0; i < ARRAY_SIZE(power_supplies); i++) for (i = 0; i < ARRAY_SIZE(power_supplies); i++)
power_supply_unregister(power_supplies[i]); power_supply_unregister(power_supplies[i]);
module_initialized = false; module_initialized = false;
} }
module_exit(oneup_power_exit); module_exit(oneup_power_exit);
static int param_set_soc_shutdown( const char *key, const struct kernel_param *kp )
{
long soc;
if( kstrtol( key, 10, &soc ) == 0 ){
if( soc == 0 ){
PR_INFO( "Disabling automatic shutdown when battery is below threshold.\n");
soc_shutdown = 0;
return 0;
}
else if( (soc > 1) && (soc < 20)){
PR_INFO( "Changing automatic shutdown when battery is below %ld%%\n",soc);
soc_shutdown = soc;
return 0;
} else {
PR_INFO( "Invalid value, 0 to disable, 1 -> 20 to shutdown.\n" );
}
} else {
PR_INFO( "Could not convert to integer\n" );
}
return -ENOENT;
}
static int param_get_soc_shutdown( char *buffer, const struct kernel_param *kp )
{
return sprintf( buffer, "%d", soc_shutdown );
}
static const struct kernel_param_ops param_ops_soc_shutdown = {
.set = param_set_soc_shutdown,
.get = param_get_soc_shutdown,
};
#define param_check_soc_shutdown(name,p) __param_check(name,p,void);
module_param( soc_shutdown, soc_shutdown, 0644 );
MODULE_PARM_DESC(soc_shutdown, "Shutdown system when the battery state of charge is lower than this value.");
MODULE_DESCRIPTION("Power supply driver for Argon40 1UP"); MODULE_DESCRIPTION("Power supply driver for Argon40 1UP");
MODULE_AUTHOR("Jeff Curless <jeff@thecurlesses.com>"); MODULE_AUTHOR("Jeff Curless <jeff@thecurlesses.com>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");

View File

@@ -1 +1,7 @@
#!/bin/bash
sudo rmmod oneUpPower sudo rmmod oneUpPower
sudo rm -vf /lib/modules/`uname -r`/kernel/drivers/power/supply/oneUpPower.ko
sudo sh -c "sed -i '/oneUpPower/d' /etc/modules"
sudo rm -vf /etc/modprobe.d/oneUpPower.conf
sudo depmod -a
sync