diff --git a/.depend b/.depend index ed6d401..6f47365 100644 --- a/.depend +++ b/.depend @@ -8,6 +8,7 @@ builtin-gconf.o:\ mce-log.h\ mce-setting.h\ mce.h\ + modules/charging.h\ modules/display.h\ modules/doubletap.h\ modules/inactivity.h\ @@ -29,6 +30,7 @@ builtin-gconf.pic.o:\ mce-log.h\ mce-setting.h\ mce.h\ + modules/charging.h\ modules/display.h\ modules/doubletap.h\ modules/inactivity.h\ @@ -714,6 +716,32 @@ modules/camera.pic.o:\ tklock.h\ modules/camera.h\ +modules/charging.o:\ + modules/charging.c\ + builtin-gconf.h\ + datapipe.h\ + datapipe.h\ + mce-conf.h\ + mce-dbus.h\ + mce-log.h\ + mce-setting.h\ + mce.h\ + musl-compatibility.h\ + modules/charging.h\ + +modules/charging.pic.o:\ + modules/charging.c\ + builtin-gconf.h\ + datapipe.h\ + datapipe.h\ + mce-conf.h\ + mce-dbus.h\ + mce-log.h\ + mce-setting.h\ + mce.h\ + musl-compatibility.h\ + modules/charging.h\ + modules/cpu-keepalive.o:\ modules/cpu-keepalive.c\ builtin-gconf.h\ @@ -1512,6 +1540,7 @@ tools/mcetool.o:\ mce-dbus.h\ mce-setting.h\ mce.h\ + modules/charging.h\ modules/display.h\ modules/doubletap.h\ modules/inactivity.h\ @@ -1535,6 +1564,7 @@ tools/mcetool.pic.o:\ mce-dbus.h\ mce-setting.h\ mce.h\ + modules/charging.h\ modules/display.h\ modules/doubletap.h\ modules/inactivity.h\ diff --git a/Makefile b/Makefile index 4ac8f39..12f61b1 100644 --- a/Makefile +++ b/Makefile @@ -156,6 +156,7 @@ MODULES += $(MODULE_DIR)/proximity.so MODULES += $(MODULE_DIR)/keypad.so MODULES += $(MODULE_DIR)/inactivity.so MODULES += $(MODULE_DIR)/camera.so +MODULES += $(MODULE_DIR)/charging.so MODULES += $(MODULE_DIR)/alarm.so MODULES += $(MODULE_DIR)/memnotify.so MODULES += $(MODULE_DIR)/mempressure.so @@ -625,6 +626,8 @@ NORMALIZE_USES_SPC =\ modules/callstate.c\ modules/callstate.h\ modules/camera.h\ + modules/charging.c\ + modules/charging.h\ modules/cpu-keepalive.c\ modules/display.c\ modules/display.h\ @@ -726,7 +729,7 @@ PROTO_CPPFLAGS += -D_Float128x="long double" %.q : %.c ; $(CC) -o $@ -E $< $(PROTO_CPPFLAGS) %.p : %.q ; cproto -s < $< | prettyproto.py | tee $@ -%.g : %.q ; cproto < $< | prettyproto.py | tee $@ +%.g : %.q ; cproto < $< | prettyproto.py -xg_module | tee $@ protos-q: $(patsubst %.c,%.q,$(wildcard *.c modules/*.c)) protos-p: $(patsubst %.c,%.p,$(wildcard *.c modules/*.c)) diff --git a/builtin-gconf.c b/builtin-gconf.c index 5517292..a5df1d0 100644 --- a/builtin-gconf.c +++ b/builtin-gconf.c @@ -2,7 +2,7 @@ * @file builtin-gconf.c * GConf compatibility module - for dynamic mce settings *

- * Copyright (C) 2012-2019 Jolla Ltd. + * Copyright (c) 2012 - 2022 Jolla Ltd. *

* @author Simo Piiroinen * @@ -49,6 +49,7 @@ #include "modules/doubletap.h" #include "modules/led.h" #include "modules/inactivity.h" +#include "modules/charging.h" #include #include @@ -1957,6 +1958,21 @@ static const setting_t gconf_defaults[] = .type = "i", .def = G_STRINGIFY(MCE_DEFAULT_BUTTONBACKLIGHT_OFF_DELAY), }, + { + .key = MCE_SETTING_CHARGING_MODE, + .type = "i", + .def = G_STRINGIFY(MCE_DEFAULT_CHARGING_MODE), + }, + { + .key = MCE_SETTING_CHARGING_LIMIT_DISABLE, + .type = "i", + .def = G_STRINGIFY(MCE_DEFAULT_CHARGING_LIMIT_DISABLE), + }, + { + .key = MCE_SETTING_CHARGING_LIMIT_ENABLE, + .type = "i", + .def = G_STRINGIFY(MCE_DEFAULT_CHARGING_LIMIT_ENABLE), + }, { .key = NULL, } diff --git a/depend_filter.py b/depend_filter.py index d4a2928..daade2d 100755 --- a/depend_filter.py +++ b/depend_filter.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- encoding: utf8 -*- # ---------------------------------------------------------------------------- @@ -13,7 +13,7 @@ def is_local(path): return not path.startswith("/") def print_rule(dest, srce): - print "%s\\\n" % "\\\n\t".join(["%s:" % dest] + srce) + print("%s\\\n" % "\\\n\t".join(["%s:" % dest] + srce)) def set_extension(path, ext): return os.path.splitext(path)[0] + ext @@ -29,7 +29,7 @@ def fix_directory(dest, srce): if __name__ == "__main__": data = sys.stdin.readlines() - data = map(lambda x:x.rstrip(), data) + data = list(map(lambda x:x.rstrip(), data)) data.reverse() deps = [] @@ -52,13 +52,13 @@ def fix_directory(dest, srce): dest = fix_directory(dest, srce) # remove secondary deps with absolute path - temp = filter(is_local, temp) + temp = list(filter(is_local, temp)) # sort secondary sources temp.sort() srce = [srce] + temp - srce = map(normalize_path, srce) + srce = list(map(normalize_path, srce)) deps.append((dest,srce)) diff --git a/inifiles/charging.ini b/inifiles/charging.ini new file mode 100644 index 0000000..1225a15 --- /dev/null +++ b/inifiles/charging.ini @@ -0,0 +1,26 @@ +# Configuration file for MCE - charging hysteresis control -*- mode: sh -*- +# +# Provided that battery kernel driver supports such operation, +# mce can be instructed for example to cease charging when battery +# level reaches 90% and restart charging when battery level drops +# below 50%. +# +# While mce side state logic and settings are generic, how charging +# is actually enabled/disabled can vary from one device type to +# another depending on hw and related kernel drivers. + +[Charging] + +# Devices where kernel utilizes POWER_SUPPLY_PROP_CHARGING_ENABLED +# For example: f5121 (Xperia X), l500d (Jolla C) + +#ControlPath = /sys/class/power_supply/battery/charging_enabled +#EnableValue = 1 +#DisableValue = 0 + +# Devices where kernel utilizes POWER_SUPPLY_PROP_INPUT_SUSPEND +# For example: h3113 (Xperia XA2), i4113 (Xperia 10) + +#ControlPath = /sys/class/power_supply/battery/input_suspend +#EnableValue = 0 +#DisableValue = 1 diff --git a/inifiles/mce.ini b/inifiles/mce.ini index ce0de99..756b224 100644 --- a/inifiles/mce.ini +++ b/inifiles/mce.ini @@ -16,7 +16,7 @@ # to avoid unnecessary brightness fluctuations on mce startup # # Note: the name should not include the "lib"-prefix -Modules=radiostates;filter-brightness-als;display;keypad;led;battery-udev;inactivity;alarm;callstate;audiorouting;proximity;powersavemode;cpu-keepalive;doubletap;packagekit;sensor-gestures;bluetooth;memnotify;mempressure;usbmode;buttonbacklight;fingerprint; +Modules=radiostates;filter-brightness-als;display;keypad;led;battery-udev;inactivity;alarm;callstate;audiorouting;proximity;powersavemode;cpu-keepalive;doubletap;packagekit;sensor-gestures;bluetooth;memnotify;mempressure;usbmode;buttonbacklight;fingerprint;charging; [KeyPad] diff --git a/modules/charging.c b/modules/charging.c new file mode 100644 index 0000000..8a6a44b --- /dev/null +++ b/modules/charging.c @@ -0,0 +1,922 @@ +/** + * @file charging.c + * + * Charging -- this module handles user space charger enable/disable + *

+ * Copyright (c) 2017 - 2022 Jolla Ltd. + *

+ * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#include "charging.h" + +#include "../mce.h" +#include "../mce-conf.h" +#include "../mce-log.h" +#include "../datapipe.h" +#include "../mce-setting.h" +#include "../mce-dbus.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +/* ========================================================================= * + * TYPES & CONSTANTS + * ========================================================================= */ + +/** Module name */ +#define MODULE_NAME "charging" + +/** Minimum battery level where charging can be disabled [%] + * + * Having charger connected but not charging from it can delay (USER mode) + * or inhibit (ACTDEAD mode) battery empty shutdown -> allow charging when + * battery level is approaching battery empty shutdown level - regardless + * of possible user configured limits. + */ +#define MCH_MINIMUM_BATTERY_LEVEL 5 + +/* ========================================================================= * + * Prototypes + * ========================================================================= */ + +/* ------------------------------------------------------------------------- * + * CHARGING_MODE + * ------------------------------------------------------------------------- */ + +static const char *charging_mode_repr(charging_mode_t mode); + +/* ------------------------------------------------------------------------- * + * CHARGING_STATE + * ------------------------------------------------------------------------- */ + +static const char *charging_state_repr(charging_state_t state); + +/* ------------------------------------------------------------------------- * + * UTILITY + * ------------------------------------------------------------------------- */ + +static int mch_clamp(int value, int minval, int maxval); + +/* ------------------------------------------------------------------------- * + * MCH_SYSFS + * ------------------------------------------------------------------------- */ + +static bool mch_sysfs_write(const char *path, const char *text); + +/* ------------------------------------------------------------------------- * + * MCH_POLICY + * ------------------------------------------------------------------------- */ + +static void mch_policy_set_battery_full (bool battery_full); +static void mch_policy_set_charging_state (charging_state_t charging_state); +static void mch_policy_evaluate_charging_state(void); +static void mch_policy_set_charging_mode (charging_mode_t charging_mode); + +/* ------------------------------------------------------------------------- * + * MCH_SETTINGS + * ------------------------------------------------------------------------- */ + +static void mch_settings_cb (GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data); +static void mch_settings_init(void); +static void mch_settings_quit(void); + +/* ------------------------------------------------------------------------- * + * MCH_CONFIG + * ------------------------------------------------------------------------- */ + +static void mch_config_init(void); +static void mch_config_quit(void); + +/* ------------------------------------------------------------------------- * + * MCH_DATAPIPE + * ------------------------------------------------------------------------- */ + +static void mch_datapipe_usb_cable_state_cb(gconstpointer data); +static void mch_datapipe_charger_state_cb (gconstpointer data); +static void mch_datapipe_battery_status_cb (gconstpointer data); +static void mch_datapipe_battery_level_cb (gconstpointer data); +static void mch_datapipe_init (void); +static void mch_datapipe_quit (void); + +/* ------------------------------------------------------------------------- * + * MCH_DBUS + * ------------------------------------------------------------------------- */ + +static void mch_dbus_send_charging_state (DBusMessage *const req); +static gboolean mch_dbus_get_charging_state_cb(DBusMessage *const req); +static gboolean mch_dbus_initial_cb (gpointer aptr); +static void mch_dbus_init (void); +static void mch_dbus_quit (void); + +/* ------------------------------------------------------------------------- * + * G_MODULE + * ------------------------------------------------------------------------- */ + +const gchar *g_module_check_init(GModule *module); +void g_module_unload (GModule *module); + +/* ========================================================================= * + * DATA + * ========================================================================= */ + +/** Functionality provided by this module */ +static const gchar *const provides[] = { MODULE_NAME, NULL }; + +/** Functionality that this module depends on */ +static const gchar *const depends[] = { NULL }; + +/** Functionality that this module recommends */ +static const gchar *const recommends[] = { NULL }; + +/** Module information */ +G_MODULE_EXPORT module_info_struct module_info = { + /** Name of the module */ + .name = MODULE_NAME, + /** Module dependencies */ + .depends = depends, + /** Module recommends */ + .recommends = recommends, + /** Module provides */ + .provides = provides, + /** Module priority */ + .priority = 250 +}; + +/** USB cable status; assume undefined */ +static usb_cable_state_t usb_cable_state = USB_CABLE_UNDEF; + +/** Charger state; assume undefined */ +static charger_state_t charger_state = CHARGER_STATE_UNDEF; + +/** Battery status; assume undefined */ +static battery_status_t battery_status = BATTERY_STATUS_UNDEF; + +/** Battery charge level: assume unknown */ +static gint battery_level = MCE_BATTERY_LEVEL_UNKNOWN; + +/** Policy setting: When to disable/enable charging */ +static charging_mode_t mch_charging_mode = CHARGING_MODE_ENABLE; +static guint mch_charging_mode_id = 0; + +/** Policy decision: Whether charging is disabled/enabled */ +static charging_state_t mch_charging_state = CHARGING_STATE_UNKNOWN; + +/** Battery full seen */ +static bool mch_battery_full = false; + +/** Battery low threshold (allow charging) */ +static gint mch_limit_enable = MCE_DEFAULT_CHARGING_LIMIT_ENABLE; +static guint mch_limit_enable_id = 0; + +/** Battery high threshold (disable charging) */ +static gint mch_limit_disable = MCE_DEFAULT_CHARGING_LIMIT_DISABLE; +static guint mch_limit_disable_id = 0; + +/** Path to charging control sysfs file */ +static gchar *mch_control_path = 0; + +/** Value to write when enabling charging */ +static gchar *mch_control_enable_value = 0; + +/** Value to write when disabling charging */ +static gchar *mch_control_disable_value = 0; + +/* ========================================================================= * + * CHARGING_MODE + * ========================================================================= */ + +static const char * +charging_mode_repr(charging_mode_t mode) +{ + const char *repr = "invalid"; + switch( mode ) { + case CHARGING_MODE_DISABLE: + repr = "disable" ; + break; + case CHARGING_MODE_ENABLE: + repr = "enable"; + break; + case CHARGING_MODE_APPLY_THRESHOLDS: + repr = "apply_thresholds"; + break; + case CHARGING_MODE_APPLY_THRESHOLDS_AFTER_FULL: + repr = "apply_thresholds_after_full"; + break; + default: + break; + } + return repr; +} + +/* ========================================================================= * + * CHARGING_STATE + * ========================================================================= */ + +static const char * +charging_state_repr(charging_state_t state) +{ + const char *repr = "invalid"; + + switch( state ) { + case CHARGING_STATE_UNKNOWN: repr = "unknown"; break; + case CHARGING_STATE_ENABLED: repr = "allowed"; break; + case CHARGING_STATE_DISABLED: repr = "disabled"; break; + default: break; + } + + return repr; +} + +/* ========================================================================= * + * UTILITY + * ========================================================================= */ + +static int +mch_clamp(int value, int minval, int maxval) +{ + return (value < minval) ? minval : (value > maxval) ? maxval : value; +} + +/* ========================================================================= * + * MCH_SYSFS + * ========================================================================= */ + +static bool +mch_sysfs_write(const char *path, const char *text) +{ + bool ack = false; + int fd = -1; + + if( !path || !text ) + goto EXIT; + + if( (fd = open(path, O_WRONLY)) == -1 ) { + mce_log(LL_ERR, "can't open %s: %m", path); + goto EXIT; + } + + size_t todo = strlen(text); + ssize_t done = write(fd, text, todo); + + if( done == -1 ) { + mce_log(LL_ERR, "can't write to %s: %m", path); + goto EXIT; + } + + if( done != (ssize_t)todo ) { + mce_log(LL_ERR, "can't write to %s: partial success", path); + goto EXIT; + } + + mce_log(LL_DEBUG, "set %s to %s", path, text); + + ack = true; + +EXIT: + if( fd != -1 ) + close(fd); + + return ack; +} + +/* ========================================================================= * + * MCH_POLICY + * ========================================================================= */ + +static void +mch_policy_set_battery_full(bool battery_full) +{ + if( mch_battery_full == battery_full ) + goto EXIT; + + mce_log(LL_DEBUG, "mch_battery_full: %s -> %s", + mch_battery_full ? "true" : "false", + battery_full ? "true" : "false"); + + mch_battery_full = battery_full; + + /* No action */ + +EXIT: + return; +} + +static void +mch_policy_set_charging_state(charging_state_t charging_state) +{ + if( charging_state != CHARGING_STATE_DISABLED ) { + /* CHARGING_STATE_UNKNOWN is valid only as initial state */ + charging_state = CHARGING_STATE_ENABLED; + } + else if( !mch_control_path ) { + /* No control path -> can't disable -> report as enabled */ + charging_state = CHARGING_STATE_ENABLED; + } + + if( mch_charging_state == charging_state ) + goto EXIT; + + mce_log(LL_CRUCIAL, "mch_charging_state: %s -> %s", + charging_state_repr(mch_charging_state), + charging_state_repr(charging_state)); + + mch_charging_state = charging_state; + + mch_sysfs_write(mch_control_path, + mch_charging_state == CHARGING_STATE_DISABLED ? + mch_control_disable_value : + mch_control_enable_value); +EXIT: + return; +} + +static void +mch_policy_evaluate_charging_state(void) +{ + /* Default to retaining current state */ + charging_state_t charging_state = mch_charging_state; + + /* Sanitize limits before use */ + int limit_enable = mch_clamp(mch_limit_enable, 0, 100); + int limit_disable = mch_clamp(mch_limit_disable, 0, 100); + if( limit_disable <= limit_enable ) + limit_disable = 100; + + if( usb_cable_state == USB_CABLE_DISCONNECTED ) { + switch( mch_charging_mode ) { + default: + /* Clear battery full seen */ + mch_policy_set_battery_full(false); + + /* Return to defaults */ + charging_state = CHARGING_STATE_ENABLED; + break; + + case CHARGING_MODE_DISABLE: + /* Keep disabled */ + charging_state = CHARGING_STATE_DISABLED; + break; + } + } + else { + /* Remember if battery full has been observed */ + if( battery_status == BATTERY_STATUS_FULL ) + mch_policy_set_battery_full(true); + + /* Evaluate based on active mode */ + switch( mch_charging_mode ) { + case CHARGING_MODE_DISABLE: + /* Keep disabled */ + charging_state = CHARGING_STATE_DISABLED; + break; + + default: + case CHARGING_MODE_ENABLE: + /* Use defaults */ + charging_state = CHARGING_STATE_ENABLED; + break; + + case CHARGING_MODE_APPLY_THRESHOLDS_AFTER_FULL: + if( !mch_battery_full ) { + /* Use defaults while waiting for battery full */ + charging_state = CHARGING_STATE_ENABLED; + break; + } + /* Fall through */ + + case CHARGING_MODE_APPLY_THRESHOLDS: + if( battery_level <= limit_enable ) { + /* Enable when dropped below low limit */ + charging_state = CHARGING_STATE_ENABLED; + } + else if( battery_level >= limit_disable ) { + /* Disable when raises above high limit */ + charging_state = CHARGING_STATE_DISABLED; + } + break; + } + } + + /* In any case, do not allow battery to get too empty */ + if( battery_level < MCH_MINIMUM_BATTERY_LEVEL ) + charging_state = CHARGING_STATE_ENABLED; + + /* Update control value */ + mch_policy_set_charging_state(charging_state); +} + +static void mch_policy_set_charging_mode(charging_mode_t charging_mode) +{ + if( mch_charging_mode == charging_mode ) + goto EXIT; + + mce_log(LL_CRUCIAL, "mch_charging_mode: %s -> %s", + charging_mode_repr(mch_charging_mode), + charging_mode_repr(charging_mode)); + + mch_charging_mode = charging_mode; + + mch_policy_evaluate_charging_state(); + +EXIT: + return; +} + +/* ========================================================================= * + * MCH_SETTINGS + * ========================================================================= */ + +static void +mch_settings_cb(GConfClient *const gcc, const guint id, + GConfEntry *const entry, gpointer const data) +{ + (void)gcc; + (void)data; + + const GConfValue *gcv = gconf_entry_get_value(entry); + + if( !gcv ) { + mce_log(LL_DEBUG, "GConf Key `%s' has been unset", + gconf_entry_get_key(entry)); + goto EXIT; + } + + if( id == mch_charging_mode_id ) { + mch_policy_set_charging_mode(gconf_value_get_int(gcv)); + } + else if( id == mch_limit_disable_id ) { + gint old = mch_limit_disable; + mch_limit_disable = gconf_value_get_int(gcv); + if( old != mch_limit_disable ) + mch_policy_evaluate_charging_state(); + } + else if( id == mch_limit_enable_id ) { + gint old = mch_limit_enable; + mch_limit_enable = gconf_value_get_int(gcv); + if( old != mch_limit_enable ) + mch_policy_evaluate_charging_state(); + } + else { + mce_log(LL_WARN, "Spurious GConf value received; confused!"); + } + +EXIT: + + return; +} + +static void +mch_settings_init(void) +{ + mce_setting_track_int(MCE_SETTING_CHARGING_LIMIT_ENABLE, + &mch_limit_enable, + MCE_DEFAULT_CHARGING_LIMIT_ENABLE, + mch_settings_cb, + &mch_limit_enable_id); + + mce_setting_track_int(MCE_SETTING_CHARGING_LIMIT_DISABLE, + &mch_limit_disable, + MCE_DEFAULT_CHARGING_LIMIT_DISABLE, + mch_settings_cb, + &mch_limit_disable_id); + + gint charging_mode = 0; + mce_setting_track_int(MCE_SETTING_CHARGING_MODE, + &charging_mode, + MCE_DEFAULT_CHARGING_MODE, + mch_settings_cb, + &mch_charging_mode_id); + mch_charging_mode = charging_mode; +} + +static void +mch_settings_quit(void) +{ + mce_setting_notifier_remove(mch_limit_enable_id), + mch_limit_enable_id = 0; + + mce_setting_notifier_remove(mch_limit_disable_id), + mch_limit_disable_id = 0; + + mce_setting_notifier_remove(mch_charging_mode_id), + mch_charging_mode_id = 0; +} + +/* ========================================================================= * + * MCH_CONFIG + * ========================================================================= */ + +static void +mch_config_init(void) +{ + bool ack = false; + + if( !mce_conf_has_group(MCE_CONF_CHARGING_GROUP) ) { + mce_log(LL_DEBUG, "[%s]: config block does not exist", + MCE_CONF_CHARGING_GROUP); + goto EXIT; + } + + mch_control_path = mce_conf_get_string(MCE_CONF_CHARGING_GROUP, + MCE_CONF_CHARGING_CONTROL_PATH, 0); + if( !mch_control_path ) { + mce_log(LL_WARN, "[%s] %s: config item not defined", + MCE_CONF_CHARGING_GROUP, + MCE_CONF_CHARGING_CONTROL_PATH); + goto EXIT; + } + + if( access(mch_control_path, W_OK) == -1 ) { + mce_log(LL_ERR, "%s: not writable: %m", mch_control_path); + goto EXIT; + } + + mch_control_enable_value = mce_conf_get_string(MCE_CONF_CHARGING_GROUP, + MCE_CONF_CHARGING_ENABLE_VALUE, + DEFAULT_CHARGING_ENABLE_VALUE); + + mch_control_disable_value = mce_conf_get_string(MCE_CONF_CHARGING_GROUP, + MCE_CONF_CHARGING_DISABLE_VALUE, + DEFAULT_CHARGING_DISABLE_VALUE); + + ack = true; + +EXIT: + if( !ack ) + mch_config_quit(); + + return; +} + +static void +mch_config_quit(void) +{ + g_free(mch_control_path), + mch_control_path = 0; + + g_free(mch_control_enable_value), + mch_control_enable_value = 0; + + g_free(mch_control_disable_value), + mch_control_disable_value = 0; +} + +/* ========================================================================= * + * MCH_DATAPIPE + * ========================================================================= */ + +/* ------------------------------------------------------------------------- * + * usb_cable_state + * ------------------------------------------------------------------------- */ + +/** Callback for handling usb_cable_state_pipe state changes + * + * @param data usb_cable_state (as void pointer) + */ +static void +mch_datapipe_usb_cable_state_cb(gconstpointer data) +{ + usb_cable_state_t prev = usb_cable_state; + usb_cable_state = GPOINTER_TO_INT(data); + + if( usb_cable_state == prev ) + goto EXIT; + + mce_log(LL_DEBUG, "usb_cable_state = %s -> %s", + usb_cable_state_repr(prev), + usb_cable_state_repr(usb_cable_state)); + + mch_policy_evaluate_charging_state(); + +EXIT: + return; +} + +/* ------------------------------------------------------------------------- * + * charger_state + * ------------------------------------------------------------------------- */ + +/** Callback for handling charger_state_pipe state changes + * + * @param data charger_state (as void pointer) + */ +static void +mch_datapipe_charger_state_cb(gconstpointer data) +{ + charger_state_t prev = charger_state; + charger_state = GPOINTER_TO_INT(data); + + if( charger_state == prev ) + goto EXIT; + + mce_log(LL_DEBUG, "charger_state = %s -> %s", + charger_state_repr(prev), + charger_state_repr(charger_state)); + + mch_policy_evaluate_charging_state(); + +EXIT: + return; +} + +/* ------------------------------------------------------------------------- * + * battery_status + * ------------------------------------------------------------------------- */ + +/** Callback for handling battery_status_pipe state changes + * + * @param data battery_status (as void pointer) + */ +static void +mch_datapipe_battery_status_cb(gconstpointer data) +{ + battery_status_t prev = battery_status; + battery_status = GPOINTER_TO_INT(data); + + if( battery_status == prev ) + goto EXIT; + + mce_log(LL_DEBUG, "battery_status = %s -> %s", + battery_status_repr(prev), + battery_status_repr(battery_status)); + + mch_policy_evaluate_charging_state(); + +EXIT: + return; +} + +/* ------------------------------------------------------------------------- * + * battery_level + * ------------------------------------------------------------------------- */ + +/** Callback for handling battery_level_pipe state changes + * + * @param data battery_level (as void pointer) + */ +static void +mch_datapipe_battery_level_cb(gconstpointer data) +{ + gint prev = battery_level; + battery_level = GPOINTER_TO_INT(data); + + if( battery_level == prev ) + goto EXIT; + + mce_log(LL_DEBUG, "battery_level = %d -> %d", + prev, battery_level); + + mch_policy_evaluate_charging_state(); + +EXIT: + return; +} + +/* ------------------------------------------------------------------------- * + * setup + * ------------------------------------------------------------------------- */ + +/** Array of datapipe handlers */ +static datapipe_handler_t mch_datapipe_handlers[] = +{ + /* Output Triggers */ + { + .datapipe = &usb_cable_state_pipe, + .output_cb = mch_datapipe_usb_cable_state_cb, + }, + { + .datapipe = &charger_state_pipe, + .output_cb = mch_datapipe_charger_state_cb, + }, + { + .datapipe = &battery_status_pipe, + .output_cb = mch_datapipe_battery_status_cb, + }, + { + .datapipe = &battery_level_pipe, + .output_cb = mch_datapipe_battery_level_cb, + }, + /* Sentinel */ + { + .datapipe = 0, + } +}; + +static datapipe_bindings_t mch_datapipe_bindings = +{ + .module = MODULE_NAME, + .handlers = mch_datapipe_handlers, +}; + +/** Append triggers/filters to datapipes + */ +static void mch_datapipe_init(void) +{ + mce_datapipe_init_bindings(&mch_datapipe_bindings); +} + +/** Remove triggers/filters from datapipes */ +static void mch_datapipe_quit(void) +{ + mce_datapipe_quit_bindings(&mch_datapipe_bindings); +} + +/* ========================================================================= * + * MCH_DBUS + * ========================================================================= */ + +/** Send charging_state D-Bus signal / method call reply + * + * @param req method call message to reply, or NULL to send signal + */ +static void +mch_dbus_send_charging_state(DBusMessage *const req) +{ + const char * const lut[] = { + [CHARGING_STATE_DISABLED] = MCE_CHARGING_STATE_DISABLED, + [CHARGING_STATE_ENABLED] = MCE_CHARGING_STATE_ENABLED, + [CHARGING_STATE_UNKNOWN] = MCE_CHARGING_STATE_UNKNOWN, + }; + + static const char *last = 0; + + DBusMessage *msg = NULL; + + const char *value = lut[mch_charging_state]; + + if( req ) { + msg = dbus_new_method_reply(req); + } + else if( last == value ) { + goto EXIT; + } + else { + last = value; + msg = dbus_new_signal(MCE_SIGNAL_PATH, + MCE_SIGNAL_IF, + MCE_CHARGING_STATE_SIG); + } + + if( !dbus_message_append_args(msg, + DBUS_TYPE_STRING, &value, + DBUS_TYPE_INVALID) ) + goto EXIT; + + mce_log(LL_DEBUG, "%s: %s = %s", + req ? "reply" : "broadcast", + "charging_state", value); + + dbus_send_message(msg), msg = 0; + +EXIT: + + if( msg ) + dbus_message_unref(msg); +} + +/** Callback for handling charging_state D-Bus queries + * + * @param req method call message to reply + */ +static gboolean +mch_dbus_get_charging_state_cb(DBusMessage *const req) +{ + mce_log(LL_DEBUG, "charging_state query from: %s", + mce_dbus_get_message_sender_ident(req)); + + if( !dbus_message_get_no_reply(req) ) + mch_dbus_send_charging_state(req); + + return TRUE; +} + +/** Array of dbus message handlers */ +static mce_dbus_handler_t mch_dbus_handlers[] = +{ + /* signals - outbound (for Introspect purposes only) */ + { + .interface = MCE_SIGNAL_IF, + .name = MCE_CHARGING_STATE_SIG, + .type = DBUS_MESSAGE_TYPE_SIGNAL, + .args = + " \n" + }, + /* method calls */ + { + .interface = MCE_REQUEST_IF, + .name = MCE_CHARGING_STATE_GET, + .type = DBUS_MESSAGE_TYPE_METHOD_CALL, + .callback = mch_dbus_get_charging_state_cb, + .args = + " \n" + }, + /* sentinel */ + { + .interface = 0 + } +}; + +/** Timer callback id for broadcasting initial states */ +static guint mch_dbus_initial_id = 0; + +/** Timer callback function for broadcasting initial states + * + * @param aptr (not used) + * + * @return FALSE to stop idle callback from repeating + */ +static gboolean mch_dbus_initial_cb(gpointer aptr) +{ + (void)aptr; + mch_dbus_initial_id = 0; + + mch_dbus_send_charging_state(0); + return FALSE; +} + +/** Add dbus handlers + */ +static void mch_dbus_init(void) +{ + mce_dbus_handler_register_array(mch_dbus_handlers); + + /* To avoid unnecessary jitter on startup, allow dbus service tracking + * and datapipe initialization some time to come up with proper initial + * state values before forcing broadcasting to dbus */ + if( !mch_dbus_initial_id ) + mch_dbus_initial_id = g_timeout_add(1000, mch_dbus_initial_cb, 0); +} + +/** Remove dbus handlers + */ +static void mch_dbus_quit(void) +{ + if( mch_dbus_initial_id ) { + g_source_remove(mch_dbus_initial_id), + mch_dbus_initial_id = 0; + } + + mce_dbus_handler_unregister_array(mch_dbus_handlers); +} + +/* ========================================================================= * + * G_MODULE + * ========================================================================= */ + +/** Init function for the charging module + * + * @param module Unused + * + * @return NULL on success, a string with an error message on failure + */ +const gchar *g_module_check_init(GModule *module) +{ + (void)module; + + mch_config_init(); + mch_settings_init(); + mch_datapipe_init(); + mch_dbus_init(); + + mch_policy_evaluate_charging_state(); + + return NULL; +} + +/** Exit function for the charging module + * + * @param module Unused + */ +void g_module_unload(GModule *module) +{ + (void)module; + + mch_dbus_quit(); + mch_datapipe_quit(); + mch_settings_quit(); + mch_config_quit(); + + return; +} diff --git a/modules/charging.h b/modules/charging.h new file mode 100644 index 0000000..61f7c3c --- /dev/null +++ b/modules/charging.h @@ -0,0 +1,95 @@ +/** + * @file charging.h + * + * Charging -- this module handles user space charger enable/disable + *

+ * Copyright (c) 2017 - 2022 Jolla Ltd. + *

+ * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#ifndef CHARGING_H_ +# define CHARGING_H_ + +/* ========================================================================= * + * Static configuration + * ========================================================================= */ + +/** Group for charging configuration keys */ +# define MCE_CONF_CHARGING_GROUP "Charging" + +/** Control file where to write */ +# define MCE_CONF_CHARGING_CONTROL_PATH "ControlPath" +# define DEFAULT_CHARGING_CONTROL_PATH NULL + +/* Value to write when enabling */ +# define MCE_CONF_CHARGING_ENABLE_VALUE "EnableValue" +# define DEFAULT_CHARGING_ENABLE_VALUE "1" + +/* Value to write when disabling */ +# define MCE_CONF_CHARGING_DISABLE_VALUE "DisableValue" +# define DEFAULT_CHARGING_DISABLE_VALUE "0" + +/* ========================================================================= * + * Dynamic settings + * ========================================================================= */ + +/** Prefix for charging setting keys */ +# define MCE_SETTING_CHARGING_PATH "/system/osso/dsm/charging" + +/** Charging disable/enable mode */ +# define MCE_SETTING_CHARGING_MODE MCE_SETTING_CHARGING_PATH "/charging_mode" +# define MCE_DEFAULT_CHARGING_MODE 1 // = CHARGING_MODE_ENABLE + +/** Battery level at which to disable charging */ +# define MCE_SETTING_CHARGING_LIMIT_DISABLE MCE_SETTING_CHARGING_PATH "/limit_disable" +# define MCE_DEFAULT_CHARGING_LIMIT_DISABLE 90 + +/** Battery level at which to enable charging */ +# define MCE_SETTING_CHARGING_LIMIT_ENABLE MCE_SETTING_CHARGING_PATH "/limit_enable" +# define MCE_DEFAULT_CHARGING_LIMIT_ENABLE 70 + +/* ========================================================================= * + * Types + * ========================================================================= */ + +typedef enum +{ + /* Keep charger disabled */ + CHARGING_MODE_DISABLE, + + /* Keep charger enabled (default behavior) */ + CHARGING_MODE_ENABLE, + + /* Apply thresholds without waiting for battery full */ + CHARGING_MODE_APPLY_THRESHOLDS, + + /* Apply thresholds after battery full is reached */ + CHARGING_MODE_APPLY_THRESHOLDS_AFTER_FULL, +} charging_mode_t; + +typedef enum +{ + /** Battery should not be charged */ + CHARGING_STATE_DISABLED, + + /** Charging logic decides whether to charge or not */ + CHARGING_STATE_ENABLED, + + /** Placeholder values used during initialization */ + CHARGING_STATE_UNKNOWN, +} charging_state_t; + +#endif /* CHARGING_H_ */ diff --git a/rpm/mce.spec b/rpm/mce.spec index bdfa602..854c08a 100644 --- a/rpm/mce.spec +++ b/rpm/mce.spec @@ -19,7 +19,7 @@ BuildRequires: pkgconfig(dsme) >= 0.65.0 BuildRequires: pkgconfig(thermalmanager_dbus_if) BuildRequires: pkgconfig(libiphb) BuildRequires: pkgconfig(glib-2.0) >= 2.36.0 -BuildRequires: pkgconfig(mce) >= 1.29.0 +BuildRequires: pkgconfig(mce) >= 1.30.0 BuildRequires: pkgconfig(libngf0) >= 0.24 BuildRequires: pkgconfig(libsystemd) BuildRequires: kernel-headers >= 2.6.32 diff --git a/tools/mcetool.c b/tools/mcetool.c index e067c43..5ddd3ac 100644 --- a/tools/mcetool.c +++ b/tools/mcetool.c @@ -2,7 +2,7 @@ * Tool to test and remote control the Mode Control Entity *

* Copyright (c) 2005 - 2011 Nokia Corporation and/or its subsidiary(-ies). - * Copyright (c) 2012 - 2020 Jolla Ltd. + * Copyright (c) 2012 - 2022 Jolla Ltd. * Copyright (c) 2019 - 2020 Open Mobile Platform LLC. *

* @author David Weinehall @@ -40,6 +40,7 @@ #include "../modules/proximity.h" #include "../modules/memnotify.h" #include "../modules/led.h" +#include "../modules/charging.h" #include "../systemui/dbus-names.h" #include "../systemui/tklock-dbus-names.h" @@ -137,6 +138,7 @@ static int xmce_parse_powerkeyevent (const ch static unsigned xmce_parse_radio_states (const char *args); static gboolean xmce_parse_enabled (const char *args); static int xmce_parse_integer (const char *args); +static int xmce_parse_memory_limit (const char *args); static double xmce_parse_double (const char *args); static bool xmce_set_verbosity (const char *arg); static void xmce_get_verbosity (void); @@ -153,6 +155,12 @@ static void xmce_get_charger_state (void); static void xmce_get_battery_status (void); static void xmce_get_battery_state (void); static void xmce_get_battery_level (void); +static bool xmce_set_charging_enable_limit (const char *args); +static void xmce_get_charging_enable_limit (void); +static bool xmce_set_charging_disable_limit (const char *args); +static void xmce_get_charging_disable_limit (void); +static bool xmce_set_charging_mode (const char *args); +static void xmce_get_charging_mode (void); static void xmce_get_battery_info (void); static void xmce_parse_notification_args (const char *args, char **title, dbus_int32_t *delay, dbus_int32_t *renew); static bool xmce_notification_begin (const char *args); @@ -2819,6 +2827,79 @@ static void xmce_get_battery_level(void) printf("%-"PAD1"s %d\n","Battery level:", num); } +static bool xmce_set_charging_enable_limit(const char *args) +{ + int val = xmce_parse_integer(args); + if( val < 0 || val > 100 ) { + errorf("%d: invalid battery limit value\n", val); + exit(EXIT_FAILURE); + } + xmce_setting_set_int(MCE_SETTING_CHARGING_LIMIT_ENABLE, val); + return true; +} + +static void xmce_get_charging_enable_limit(void) +{ + gint val = 0; + char txt[32]; + strcpy(txt, "unknown"); + if( xmce_setting_get_int(MCE_SETTING_CHARGING_LIMIT_ENABLE, &val) ) + snprintf(txt, sizeof txt, "%d", (int)val); + printf("%-"PAD1"s %s (%%)\n", "Charging enable limit:", txt); +} + +static bool xmce_set_charging_disable_limit(const char *args) +{ + int val = xmce_parse_integer(args); + if( val < 0 || val > 100 ) { + errorf("%d: invalid battery limit value\n", val); + exit(EXIT_FAILURE); + } + xmce_setting_set_int(MCE_SETTING_CHARGING_LIMIT_DISABLE, val); + return true; +} + +static void xmce_get_charging_disable_limit(void) +{ + gint val = 0; + char txt[32]; + strcpy(txt, "unknown"); + if( xmce_setting_get_int(MCE_SETTING_CHARGING_LIMIT_DISABLE, &val) ) + snprintf(txt, sizeof txt, "%d", (int)val); + printf("%-"PAD1"s %s (%%)\n", "Charging disable limit:", txt); +} + +/** Lookuptable for enable/disable mode values */ +static const symbol_t charging_mode_lut[] = +{ + { "disable", CHARGING_MODE_DISABLE }, + { "enable", CHARGING_MODE_ENABLE }, + { "apply-thresholds", CHARGING_MODE_APPLY_THRESHOLDS }, + { "apply-thresholds-after-full", CHARGING_MODE_APPLY_THRESHOLDS_AFTER_FULL }, + { 0, -1 } +}; + +static bool xmce_set_charging_mode(const char *args) +{ + debugf("%s(%s)\n", __FUNCTION__, args); + int val = lookup(charging_mode_lut, args); + if( val == -1 ) { + errorf("%s: invalid charging mode\n", args); + exit(EXIT_FAILURE); + } + xmce_setting_set_int(MCE_SETTING_CHARGING_MODE, val); + return true; +} + +static void xmce_get_charging_mode(void) +{ + gint val = 0; + const char *txt = 0; + if( xmce_setting_get_int(MCE_SETTING_CHARGING_MODE, &val) ) + txt = rlookup(charging_mode_lut, val); + printf("%-"PAD1"s %s\n", "Charging mode:", txt ?: "unknown"); +} + static void xmce_get_battery_info(void) { xmce_get_cable_state(); @@ -2826,6 +2907,9 @@ static void xmce_get_battery_info(void) xmce_get_battery_level(); xmce_get_battery_status(); xmce_get_battery_state(); + xmce_get_charging_mode(); + xmce_get_charging_enable_limit(); + xmce_get_charging_disable_limit(); } /* ------------------------------------------------------------------------- * @@ -7989,6 +8073,7 @@ static const mce_opt_t options[] = .name = "set-inactivity-shutdown-delay", .with_arg = xmce_set_inactivity_shutdown_delay, .values = "s", + .usage = "set delay in seconds for automatic shutdown\n" "\n" "If the device is not in active use it will be\n" @@ -8036,6 +8121,44 @@ static const mce_opt_t options[] = "Override battery level for debugging purposes\n" }, #endif // ENABLE_BATTERY_SIMULATION + { + .name = "set-charging-mode", + .with_arg = xmce_set_charging_mode, + .values = "" + "enable|" + "disable|" + "apply-thresholds|" + "apply-thresholds-after-full", + .usage = + "Set charging enable/disable mode\n" + "\n" + "Valid modes are:\n" + " enable - charging is always enabled (default)\n" + " disable - charging is always disabled\n" + " apply-thresholds - charging is disabled when level reaches disable limit\n" + " and enabled when level drops to enable limit\n" + " apply-thresholds-after-full - charging is enabled until battery full is\n" + " reached, then as with apply-thresholds\n" + }, + { + .name = "set-charging-enable-limit", + .with_arg = xmce_set_charging_enable_limit, + .values = "percent", + .usage = + "Set charging enable limit\n" + "\n" + "Charging is enabled when battery level drops to enable limit or below.\n" + + }, + { + .name = "set-charging-disable-limit", + .with_arg = xmce_set_charging_disable_limit, + .values = "percent", + .usage = + "Set charging disabled limit\n" + "\n" + "Charging is disabled when battery level reaches disable limit or above.\n" + }, // sentinel { @@ -8049,7 +8172,7 @@ PROG_NAME" v"G_STRINGIFY(PRG_VERSION)"\n" "Written by David Weinehall.\n" "\n" "Copyright (c) 2005 - 2011 Nokia Corporation. All rights reserved.\n" -"Copyright (c) 2012 - 2020 Jolla Ltd.\n" +"Copyright (c) 2012 - 2022 Jolla Ltd.\n" "Copyright (c) 2019 - 2020 Open Mobile Platform LLC.\n" ;