summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaisuke Nojiri <dnojiri@chromium.org>2020-05-11 14:58:01 -0700
committerCommit Bot <commit-bot@chromium.org>2020-06-22 15:33:35 +0000
commit750f29e54a9a294871cffd73f00fce9dd5284434 (patch)
treee618930afd5789f46187b962151334c12ba77f60
parentbbe41280e3c302e9e8f2c70e46a68867681f9629 (diff)
downloadchrome-ec-750f29e54a9a294871cffd73f00fce9dd5284434.tar.gz
Battery: Implement smart discharge system
Currently, CrOS EC chooses only one of the two powre-saving states when the system is left idle. One is to hibernate and the other is to cut off the battery. And these are determined at compile time. If the system hibernates, EC will not have a chance to cut off the battery before the state of charge reaches critical low. If the system is in cutoff, it requires an AC adapter to wake up. So, neither behavior is ideal. This patch introduces the smart discharge system. Given the number of hours to zero capacity as a target, it tries to choose the better state for idling. For example, if the state of charge is high, it will hibernate the system because the target can be met before the battery completely drains. If the state of charge is low, it will keep the EC up so that it can cutoff the battery. Tests are done on Bloog as follows: Verify EC selects not to hibernate when the remaining capacity is below the stay-up threshold. The ectool smartdischarge command is tested as follows: localhost ~ # ectool smartdischarge Hours to zero capacity: 0 h Stay-up threshold: 0 mAh Cutoff threshold: 0 mAh Hibernation discharge rate: 0 uA Cutoff discharge rate: 0 uA localhost ~ # ectool smartdischarge 2160 Hours to zero capacity: 2160 h Stay-up threshold: 0 mAh Cutoff threshold: 0 mAh Hibernation discharge rate: 0 uA Cutoff discharge rate: 0 uA localhost ~ # ectool smartdischarge 2160 200 1500 EC result 3 (INVALID_PARAM) localhost ~ # ectool smartdischarge 2160 1500 200 Hours to zero capacity: 2160 h Stay-up threshold: 3240 mAh Cutoff threshold: 432 mAh Hibernation discharge rate: 1500 uA Cutoff discharge rate: 200 uA localhost ~ # ectool smartdischarge 2160 1500 0 EC result 3 (INVALID_PARAM) localhost ~ # ectool smartdischarge 0 Hours to zero capacity: 0 h Stay-up threshold: 0 mAh Cutoff threshold: 0 mAh Hibernation discharge rate: 1500 uA Cutoff discharge rate: 200 uA Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> BUG=b:152431365, b:157602162 BRANCH=none TEST=See above Change-Id: I1470b13203f3653ae0e495cd5ec8ed05f3c5102f Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2255322 Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org> Commit-Queue: Daisuke Nojiri <dnojiri@chromium.org> Tested-by: Daisuke Nojiri <dnojiri@chromium.org> Auto-Submit: Daisuke Nojiri <dnojiri@chromium.org>
-rw-r--r--include/ec_commands.h38
-rw-r--r--power/common.c97
-rw-r--r--util/ectool.c65
3 files changed, 197 insertions, 3 deletions
diff --git a/include/ec_commands.h b/include/ec_commands.h
index 69a78ac47f..eba10b6f4f 100644
--- a/include/ec_commands.h
+++ b/include/ec_commands.h
@@ -5226,6 +5226,44 @@ struct ec_response_locate_chip {
};
} __ec_align2;
+/*
+ * Configure smart discharge
+ */
+#define EC_CMD_SMART_DISCHARGE 0x012B
+
+#define EC_SMART_DISCHARGE_FLAGS_SET BIT(0)
+
+/* Discharge rates when the system is in cutoff or hibernation. */
+struct discharge_rate {
+ uint16_t cutoff; /* Discharge rate (uA) in cutoff */
+ uint16_t hibern; /* Discharge rate (uA) in hibernation */
+};
+
+struct smart_discharge_zone {
+ /* When the capacity (mAh) goes below this, EC cuts off the battery. */
+ int cutoff;
+ /* When the capacity (mAh) is below this, EC stays up. */
+ int stayup;
+};
+
+struct ec_params_smart_discharge {
+ uint8_t flags; /* EC_SMART_DISCHARGE_FLAGS_* */
+ /*
+ * Desired hours for the battery to survive before reaching 0%. Set to
+ * zero to disable smart discharging. That is, the system hibernates as
+ * soon as the G3 idle timer expires.
+ */
+ uint16_t hours_to_zero;
+ /* Set both to zero to keep the current rates. */
+ struct discharge_rate drate;
+};
+
+struct ec_response_smart_discharge {
+ uint16_t hours_to_zero;
+ struct discharge_rate drate;
+ struct smart_discharge_zone dzone;
+};
+
/*****************************************************************************/
/* The command range 0x200-0x2FF is reserved for Rotor. */
diff --git a/power/common.c b/power/common.c
index cccb34911a..c924056e0a 100644
--- a/power/common.c
+++ b/power/common.c
@@ -282,13 +282,104 @@ static void power_set_active_wake_mask(void)
static void power_set_active_wake_mask(void) { }
#endif
-__attribute__((weak))
-enum critical_shutdown board_system_is_idle(uint64_t last_shutdown_time,
- uint64_t *target, uint64_t now)
+#ifdef CONFIG_HIBERNATE
+#ifdef CONFIG_BATTERY
+/*
+ * Smart discharge system
+ *
+ * EC controls how the system discharges differently depending on the remaining
+ * capacity and the expected hours to zero.
+ *
+ * 0 X1 X2 full
+ * |----------|-------------------|------------------------------------|
+ * cutoff stay-up safe
+ *
+ * EC cuts off the battery at X1 mAh and hibernates the system at X2 mAh. X1 and
+ * X2 are derived from the cutoff and hibernation discharge rate, respectively.
+ *
+ * TODO: Learn discharge rates dynamically.
+ *
+ * TODO: Save sdzone in non-volatile memory and restore it when waking up from
+ * cutoff or hibernation.
+ */
+static struct smart_discharge_zone sdzone;
+
+static int hc_smart_discharge(struct host_cmd_handler_args *args)
+{
+ static uint16_t hours_to_zero;
+ static struct discharge_rate drate;
+ const struct ec_params_smart_discharge *p = args->params;
+ struct ec_response_smart_discharge *r = args->response;
+
+ if (p->flags & EC_SMART_DISCHARGE_FLAGS_SET) {
+ int cap;
+
+ if (battery_full_charge_capacity(&cap))
+ return EC_RES_UNAVAILABLE;
+
+ if (p->drate.hibern < p->drate.cutoff)
+ /* Hibernation discharge rate should be always higher */
+ return EC_RES_INVALID_PARAM;
+ else if (p->drate.cutoff > 0 && p->drate.hibern > 0)
+ drate = p->drate;
+ else if (p->drate.cutoff == 0 && p->drate.hibern == 0)
+ ; /* no-op. use the current drate. */
+ else
+ return EC_RES_INVALID_PARAM;
+
+ /* Commit */
+ hours_to_zero = p->hours_to_zero;
+ sdzone.stayup = MIN(hours_to_zero * drate.hibern / 1000, cap);
+ sdzone.cutoff = MIN(hours_to_zero * drate.cutoff / 1000,
+ sdzone.stayup);
+ }
+
+ /* Return the effective values. */
+ r->hours_to_zero = hours_to_zero;
+ r->dzone = sdzone;
+ r->drate = drate;
+ args->response_size = sizeof(*r);
+
+ return EC_RES_SUCCESS;
+}
+DECLARE_HOST_COMMAND(EC_CMD_SMART_DISCHARGE,
+ hc_smart_discharge,
+ EC_VER_MASK(0));
+
+__overridable enum critical_shutdown board_system_is_idle(
+ uint64_t last_shutdown_time, uint64_t *target, uint64_t now)
+{
+ int remain;
+
+ if (now < *target)
+ return CRITICAL_SHUTDOWN_IGNORE;
+
+ if (battery_remaining_capacity(&remain)) {
+ CPRINTS("SDC Failed to get remaining capacity");
+ return CRITICAL_SHUTDOWN_HIBERNATE;
+ }
+
+ if (remain < sdzone.cutoff) {
+ CPRINTS("SDC Cutoff");
+ return CRITICAL_SHUTDOWN_CUTOFF;
+ } else if (remain < sdzone.stayup) {
+ CPRINTS("SDC Stay-up");
+ return CRITICAL_SHUTDOWN_IGNORE;
+ }
+
+ CPRINTS("SDC Safe");
+ return CRITICAL_SHUTDOWN_HIBERNATE;
+}
+#else
+/* Default implementation for battery-less systems */
+__overridable enum critical_shutdown board_system_is_idle(
+ uint64_t last_shutdown_time, uint64_t *target, uint64_t now)
{
return now > *target ?
CRITICAL_SHUTDOWN_HIBERNATE : CRITICAL_SHUTDOWN_IGNORE;
}
+#endif /* CONFIG_BATTERY */
+#endif /* CONFIG_HIBERNATE */
/**
* Common handler for steady states
diff --git a/util/ectool.c b/util/ectool.c
index 64bb7fe056..7d02a82932 100644
--- a/util/ectool.c
+++ b/util/ectool.c
@@ -251,6 +251,8 @@ const char help_str[] =
" Run RW signature verification and get status.\n"
" sertest\n"
" Serial output test for COM2\n"
+ " smartdischarge\n"
+ " Set/Get smart discharge parameters\n"
" switches\n"
" Prints current EC switch positions\n"
" temps <sensorid>\n"
@@ -2061,6 +2063,68 @@ int cmd_port_80_flood(int argc, char *argv[])
}
#endif
+static void cmd_smart_discharge_usage(const char *command)
+{
+ printf("Usage: %s [hours_to_zero [hibern] [cutoff]]\n", command);
+ printf("\n");
+ printf("Set/Get smart discharge parameters\n");
+ printf("hours_to_zero: Desired hours for state of charge to zero\n");
+ printf("hibern: Discharge rate in hibernation (uA)\n");
+ printf("cutoff: Discharge rate in battery cutoff (uA)\n");
+}
+
+int cmd_smart_discharge(int argc, char *argv[])
+{
+ struct ec_params_smart_discharge *p = ec_outbuf;
+ struct ec_response_smart_discharge *r = ec_inbuf;
+ char *e;
+ int rv;
+
+ if (argc > 1) {
+ if (strcmp(argv[1], "help") == 0) {
+ cmd_smart_discharge_usage(argv[0]);
+ return 0;
+ }
+ p->flags = EC_SMART_DISCHARGE_FLAGS_SET;
+ p->hours_to_zero = strtol(argv[1], &e, 0);
+ if (p->hours_to_zero < 0 || (e && *e)) {
+ perror("Bad value for [hours_to_zero]");
+ return -1;
+ }
+ if (argc == 4) {
+ p->drate.hibern = strtol(argv[2], &e, 0);
+ if (p->drate.hibern < 0 || (e && *e)) {
+ perror("Bad value for [hibern]");
+ return -1;
+ }
+ p->drate.cutoff = strtol(argv[3], &e, 0);
+ if (p->drate.cutoff < 0 || (e && *e)) {
+ perror("Bad value for [cutoff]");
+ return -1;
+ }
+ } else if (argc != 2) {
+ /* If argc != 4, it has to be 2. */
+ perror("Invalid number of parameters");
+ return -1;
+ }
+ }
+
+ rv = ec_command(EC_CMD_SMART_DISCHARGE, 0, p, sizeof(*p),
+ r, ec_max_insize);
+ if (rv < 0) {
+ perror("ERROR: EC_CMD_SMART_DISCHARGE failed");
+ return rv;
+ }
+ printf("%-27s %5d h\n", "Hours to zero capacity:", r->hours_to_zero);
+ printf("%-27s %5d mAh\n", "Stay-up threshold:", r->dzone.stayup);
+ printf("%-27s %5d mAh\n", "Cutoff threshold:", r->dzone.cutoff);
+ printf("%-27s %5d uA\n", "Hibernation discharge rate:",
+ r->drate.hibern);
+ printf("%-27s %5d uA\n", "Cutoff discharge rate:", r->drate.cutoff);
+
+ return 0;
+}
+
int read_mapped_temperature(int id)
{
int rv;
@@ -8592,6 +8656,7 @@ const struct command commands[] = {
{"rwsigaction", cmd_rwsig_action},
{"rwsigstatus", cmd_rwsig_status},
{"sertest", cmd_serial_test},
+ {"smartdischarge", cmd_smart_discharge},
{"port80flood", cmd_port_80_flood},
{"switches", cmd_switches},
{"temps", cmd_temperature},