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"
;