summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Richardson <wfrichar@chromium.org>2013-09-06 13:01:48 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2013-09-11 01:49:48 +0000
commit793b52f327b20ee5897dc04093ac30a5d000d6e9 (patch)
treeb85f745c8c3f0d3d877733d18caadb626e56927b
parente6401d2e83939a63cbd156fa193f9768063d9325 (diff)
downloadchrome-ec-793b52f327b20ee5897dc04093ac30a5d000d6e9.tar.gz
Handle multiple independent sources and types of CPU throttling
Depending on the system, the AP can be throttled in at least two different ways - politely, where it's just asked to slow down a bit, and forcefully using a hardware signal (like PROCHOT). In addition, the request for throttling can come from multiple tasks. This CL provides a single interface, specifying both the type of throttling desired and the source of the throttling request. For each type, any source can can start throttling, but all sources must agree before it stops. The changes are protected by a mutex, so that requests from multiple tasks don't interfere with each other. BUG=chrome-os-partner:20739,chromium:287985,chromium:287983 BRANCH=ToT TEST=manual Build-time test: cd src/platform/ec make BOARD=falco runtests Run-time test: Lower the temp thresholds, turn the fan off, and watch the throttling turn off and on as things heat up. For example, on the EC console: > temps PECI : 339 K = 66 C ECInternal : 324 K = 51 C G781Internal : 328 K = 55 C G781External : 327 K = 54 C > thermalset 0 341 343 sensor warn high halt fan_off fan_max name 0 341 343 383 333 363 PECI 1 0 0 0 0 0 ECInternal 2 0 0 0 0 0 G781Internal 3 0 0 0 0 0 G781External > > temps PECI : 339 K = 66 C ECInternal : 324 K = 51 C G781Internal : 328 K = 55 C G781External : 327 K = 54 C > > fanduty 0 Setting fan duty cycle to 0% > > apthrottle AP throttling type 0 is off (0x00000000) AP throttling type 1 is off (0x00000000) > [430.152000 thermal WARN] [430.152233 event set 0x00020000] [430.152497 event clear 0x00020000] [430.152714 ACPI query = 18] [430.152444 sci 0x00020000] [430.153051 set AP throttling type 0 to on (0x00000001)] > gpioget CPU_PROCHOT 0 CPU_PROCHOT > [436.153742 thermal HIGH] [436.153979 set AP throttling type 1 to on (0x00000001)] > gpioget CPU_PROCHOT 1* CPU_PROCHOT > [441.155319 thermal no longer high] [441.155587 set AP throttling type 1 to off (0x00000000)] [442.155604 thermal HIGH] [442.155841 set AP throttling type 1 to on (0x00000001)] [446.156623 thermal no longer high] [446.156890 set AP throttling type 1 to off (0x00000000)] temps PECI : 343 K = 70 C ECInternal : 324 K = 51 C G781Internal : 328 K = 55 C G781External : 327 K = 54 C > [447.156827 thermal HIGH] [447.157064 set AP throttling type 1 to on (0x00000001)] apthrottle AP throttling type 0 is on (0x00000001) AP throttling type 1 is on (0x00000001) > gpioget CPU_PROCHOT 1 CPU_PROCHOT > Now turn the fan back on: > fanauto > [456.159306 thermal no longer high] [456.159574 set AP throttling type 1 to off (0x00000000)] > apthrottle AP throttling type 0 is on (0x00000001) AP throttling type 1 is off (0x00000000) > temps PECI : 341 K = 68 C ECInternal : 324 K = 51 C G781Internal : 328 K = 55 C G781External : 327 K = 54 C > [473.163905 thermal no longer warn] [473.164168 event set 0x00040000] [473.164453 event clear 0x00040000] [473.164670 ACPI query = 19] [473.164379 sci 0x00040000] [473.164987 set AP throttling type 0 to off (0x00000000)] temps PECI : 340 K = 67 C ECInternal : 324 K = 51 C G781Internal : 328 K = 55 C G781External : 327 K = 54 C > > apthrottle AP throttling type 0 is off (0x00000000) AP throttling type 1 is off (0x00000000) > Change-Id: I9ee1491a637d7766395c71e57483fbd9177ea554 Signed-off-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/168802
-rw-r--r--board/bolt/board.h1
-rw-r--r--board/falco/board.h1
-rw-r--r--board/host/chipset.c5
-rw-r--r--board/link/board.h1
-rw-r--r--board/peppy/board.h1
-rw-r--r--board/slippy/board.h1
-rw-r--r--common/build.mk1
-rw-r--r--common/chipset_haswell.c3
-rw-r--r--common/extpower_falco.c36
-rw-r--r--common/mock_charger.c3
-rw-r--r--common/thermal.c9
-rw-r--r--common/throttle_ap.c96
-rw-r--r--include/chipset.h8
-rw-r--r--include/config.h3
-rw-r--r--include/throttle_ap.h51
-rw-r--r--test/adapter.c205
-rw-r--r--test/adapter.tasklist3
-rw-r--r--test/build.mk3
-rw-r--r--test/test_config.h15
-rw-r--r--test/thermal.tasklist3
-rw-r--r--test/thermal_falco.c874
-rw-r--r--test/thermal_falco.tasklist19
-rw-r--r--test/thermal_falco_externs.h15
23 files changed, 1322 insertions, 35 deletions
diff --git a/board/bolt/board.h b/board/bolt/board.h
index a9c417f73b..98e8c3f42f 100644
--- a/board/bolt/board.h
+++ b/board/bolt/board.h
@@ -16,6 +16,7 @@
/* Optional features */
#define CONFIG_CHIPSET_X86
+#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_CUSTOM_KEYSCAN
#define CONFIG_EXTPOWER_GPIO
#define CONFIG_KEYBOARD_BOARD_CONFIG
diff --git a/board/falco/board.h b/board/falco/board.h
index 2c2e6a054a..4c9e2c0201 100644
--- a/board/falco/board.h
+++ b/board/falco/board.h
@@ -14,6 +14,7 @@
#define CONFIG_CHARGER
#define CONFIG_CHARGER_BQ24738
#define CONFIG_CHARGER_DISCHARGE_ON_AC
+#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_CHIPSET_HASWELL
#define CONFIG_CHIPSET_X86
#define CONFIG_CMD_GSV
diff --git a/board/host/chipset.c b/board/host/chipset.c
index 4197c46326..678d5a97e0 100644
--- a/board/host/chipset.c
+++ b/board/host/chipset.c
@@ -21,6 +21,11 @@ test_mockable void chipset_reset(int cold_reset)
fprintf(stderr, "Chipset reset!\n");
}
+test_mockable void chipset_throttle_cpu(int throttle)
+{
+ /* Do nothing */
+}
+
test_mockable void chipset_force_shutdown(void)
{
/* Do nothing */
diff --git a/board/link/board.h b/board/link/board.h
index 7d0a8ae268..668736b0b1 100644
--- a/board/link/board.h
+++ b/board/link/board.h
@@ -15,6 +15,7 @@
#define CONFIG_BOARD_VERSION
#define CONFIG_CHARGER
#define CONFIG_CHARGER_BQ24725
+#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_CHIPSET_IVYBRIDGE
#define CONFIG_CHIPSET_X86
#define CONFIG_EXTPOWER_GPIO
diff --git a/board/peppy/board.h b/board/peppy/board.h
index 23f12fc3f0..ff51c73936 100644
--- a/board/peppy/board.h
+++ b/board/peppy/board.h
@@ -16,6 +16,7 @@
#define CONFIG_CHARGER
#define CONFIG_CHARGER_BQ24707A
#define CONFIG_CHARGER_DISCHARGE_ON_AC
+#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_CHIPSET_HASWELL
#define CONFIG_CHIPSET_X86
#define CONFIG_CMD_GSV
diff --git a/board/slippy/board.h b/board/slippy/board.h
index 118f346045..3ea81d97f4 100644
--- a/board/slippy/board.h
+++ b/board/slippy/board.h
@@ -16,6 +16,7 @@
#define CONFIG_CHARGER
#define CONFIG_CHARGER_BQ24707A
#define CONFIG_CHARGER_DISCHARGE_ON_AC
+#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_CHIPSET_HASWELL
#define CONFIG_CHIPSET_X86
#define CONFIG_CMD_GSV
diff --git a/common/build.mk b/common/build.mk
index a97d9fdf2e..3256982ef0 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -9,6 +9,7 @@
common-y=main.o util.o console_output.o uart_buffering.o
common-y+=memory_commands.o shared_mem.o system_common.o hooks.o
common-y+=gpio_common.o version.o printf.o queue.o getset.o
+common-y+=throttle_ap.o
common-$(BOARD_bolt)+=battery_link.o
common-$(BOARD_daisy)+=extpower_snow.o
diff --git a/common/chipset_haswell.c b/common/chipset_haswell.c
index 1885f76d3c..fcdd00bdc4 100644
--- a/common/chipset_haswell.c
+++ b/common/chipset_haswell.c
@@ -106,7 +106,8 @@ void chipset_reset(int cold_reset)
void chipset_throttle_cpu(int throttle)
{
- /* FIXME CPRINTF("[%T %s(%d)]\n", __func__, throttle);*/
+ if (chipset_in_state(CHIPSET_STATE_ON))
+ gpio_set_level(GPIO_CPU_PROCHOT, throttle);
}
enum x86_state x86_chipset_init(void)
diff --git a/common/extpower_falco.c b/common/extpower_falco.c
index 2730a06e3c..ff1166f778 100644
--- a/common/extpower_falco.c
+++ b/common/extpower_falco.c
@@ -26,6 +26,7 @@
#include "hooks.h"
#include "host_command.h"
#include "smart_battery.h"
+#include "throttle_ap.h"
#include "util.h"
/* Console output macros */
@@ -194,21 +195,34 @@ bad:
CPRINTF("[%T ERROR: can't talk to charger: %d]\n", r);
}
-test_export_static int ap_is_throttled;
-static void set_throttle(int on)
+
+/* We need to OR all the possible reasons to throttle in order to decide
+ * whether it should happen or not. Use one bit per reason.
+ */
+#define BATT_REASON_OFFSET 0
+#define AC_REASON_OFFSET NUM_BATT_THRESHOLDS
+BUILD_ASSERT(NUM_BATT_THRESHOLDS + NUM_AC_THRESHOLDS < 32);
+
+test_export_static uint32_t ap_is_throttled;
+static void set_throttle(int on, int whosays)
{
- host_throttle_cpu(on);
- ap_is_throttled = on;
+ if (on)
+ ap_is_throttled |= (1 << whosays);
+ else
+ ap_is_throttled &= ~(1 << whosays);
+
+ throttle_ap(ap_is_throttled ? THROTTLE_ON : THROTTLE_OFF,
+ THROTTLE_SOFT, THROTTLE_SRC_POWER);
}
test_export_static
-void check_threshold(int current, struct adapter_limits *lim)
+void check_threshold(int current, struct adapter_limits *lim, int whoami)
{
if (lim->triggered) {
/* watching for current to drop */
if (current < lim->lo_val) {
if (++lim->count >= lim->lo_cnt) {
- set_throttle(0);
+ set_throttle(0, whoami);
lim->count = 0;
lim->triggered = 0;
}
@@ -219,7 +233,7 @@ void check_threshold(int current, struct adapter_limits *lim)
/* watching for current to rise */
if (current > lim->hi_val) {
if (++lim->count >= lim->hi_cnt) {
- set_throttle(1);
+ set_throttle(1, whoami);
lim->count = 0;
lim->triggered = 1;
}
@@ -251,7 +265,8 @@ void watch_battery_closely(struct power_state_context *ctx)
/* Check limits against DISCHARGE current, not CHARGE current! */
for (i = 0; i < NUM_BATT_THRESHOLDS; i++)
- check_threshold(-current, &batt_limits[i]); /* invert sign! */
+ check_threshold(-current, &batt_limits[i], /* invert sign! */
+ i + BATT_REASON_OFFSET);
}
void watch_adapter_closely(struct power_state_context *ctx)
@@ -288,13 +303,14 @@ void watch_adapter_closely(struct power_state_context *ctx)
/* Check all the thresholds. */
current = adc_read_channel(ADC_CH_CHARGER_CURRENT);
for (i = 0; i < NUM_AC_THRESHOLDS; i++)
- check_threshold(current, &ad_limits[ac_adapter][ac_turbo][i]);
+ check_threshold(current, &ad_limits[ac_adapter][ac_turbo][i],
+ i + AC_REASON_OFFSET);
}
static int command_adapter(int argc, char **argv)
{
enum adapter_type v = identify_adapter();
- ccprintf("Adapter %s (%dmv), turbo %d, AP_throttled %d\n",
+ ccprintf("Adapter %s (%dmv), turbo %d, ap_is_throttled 0x%08x\n",
ad_name[v], last_mv, ac_turbo, ap_is_throttled);
return EC_SUCCESS;
}
diff --git a/common/mock_charger.c b/common/mock_charger.c
index ebbf5e87bb..f63eebbea3 100644
--- a/common/mock_charger.c
+++ b/common/mock_charger.c
@@ -73,8 +73,9 @@ int charger_set_current(int current)
if (current > info->current_max)
current = info->current_max;
+ if (mock_current != current)
+ uart_printf("Charger set current: %d\n", current);
mock_current = current;
- uart_printf("Charger set current: %d\n", current);
return EC_SUCCESS;
}
diff --git a/common/thermal.c b/common/thermal.c
index 638bd0c0f7..c51b5dc01a 100644
--- a/common/thermal.c
+++ b/common/thermal.c
@@ -15,6 +15,7 @@
#include "host_command.h"
#include "temp_sensor.h"
#include "thermal.h"
+#include "throttle_ap.h"
#include "timer.h"
#include "util.h"
@@ -131,18 +132,18 @@ static void thermal_control(void)
if (cond_went_true(&cond_hot[EC_TEMP_THRESH_HIGH])) {
CPRINTF("[%T thermal HIGH]\n");
- chipset_throttle_cpu(1);
+ throttle_ap(THROTTLE_ON, THROTTLE_HARD, THROTTLE_SRC_THERMAL);
} else if (cond_went_false(&cond_hot[EC_TEMP_THRESH_HIGH])) {
CPRINTF("[%T thermal no longer high]\n");
- chipset_throttle_cpu(0);
+ throttle_ap(THROTTLE_OFF, THROTTLE_HARD, THROTTLE_SRC_THERMAL);
}
if (cond_went_true(&cond_hot[EC_TEMP_THRESH_WARN])) {
CPRINTF("[%T thermal WARN]\n");
- host_throttle_cpu(1);
+ throttle_ap(THROTTLE_ON, THROTTLE_SOFT, THROTTLE_SRC_THERMAL);
} else if (cond_went_false(&cond_hot[EC_TEMP_THRESH_WARN])) {
CPRINTF("[%T thermal no longer warn]\n");
- host_throttle_cpu(0);
+ throttle_ap(THROTTLE_OFF, THROTTLE_SOFT, THROTTLE_SRC_THERMAL);
}
/* Max fan needed is what's needed. */
diff --git a/common/throttle_ap.c b/common/throttle_ap.c
new file mode 100644
index 0000000000..0c671842db
--- /dev/null
+++ b/common/throttle_ap.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Common chipset throttling code for Chrome EC */
+
+#include "chipset.h"
+#include "common.h"
+#include "console.h"
+#include "host_command.h"
+#include "task.h"
+#include "throttle_ap.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CHIPSET, format, ## args)
+
+/*****************************************************************************/
+/* This enforces the virtual OR of all throttling sources. */
+static struct mutex throttle_mutex;
+static uint32_t throttle_request[NUM_THROTTLE_TYPES];
+
+void throttle_ap(enum throttle_level level,
+ enum throttle_type type,
+ enum throttle_sources source)
+{
+ uint32_t tmpval, bitmask;
+
+ mutex_lock(&throttle_mutex);
+
+ bitmask = (1 << source);
+
+ switch (level) {
+ case THROTTLE_ON:
+ throttle_request[type] |= bitmask;
+ break;
+ case THROTTLE_OFF:
+ throttle_request[type] &= ~bitmask;
+ break;
+ }
+
+ tmpval = throttle_request[type]; /* save for printing */
+
+ switch (type) {
+ case THROTTLE_SOFT:
+#ifdef HAS_TASK_HOSTCMD
+ host_throttle_cpu(tmpval);
+#endif
+ break;
+ case THROTTLE_HARD:
+#ifdef CONFIG_CHIPSET_CAN_THROTTLE
+ chipset_throttle_cpu(tmpval);
+#endif
+ break;
+
+ case NUM_THROTTLE_TYPES:
+ /* Make the compiler shut up. Don't use 'default', because
+ * we still want to catch any new types.
+ */
+ break;
+ }
+
+ mutex_unlock(&throttle_mutex);
+
+ /* print outside the mutex */
+ CPRINTF("[%T set AP throttling type %d to %s (0x%08x)]\n",
+ type, tmpval ? "on" : "off", tmpval);
+
+}
+
+/*****************************************************************************/
+/* Console commands */
+
+static int command_apthrottle(int argc, char **argv)
+{
+ int i;
+ uint32_t tmpval;
+
+ for (i = 0; i < NUM_THROTTLE_TYPES; i++) {
+ mutex_lock(&throttle_mutex);
+ tmpval = throttle_request[i];
+ mutex_unlock(&throttle_mutex);
+
+ ccprintf("AP throttling type %d is %s (0x%08x)\n", i,
+ tmpval ? "on" : "off", tmpval);
+ }
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(apthrottle, command_apthrottle,
+ NULL,
+ "Display the AP throttling state",
+ NULL);
+
diff --git a/include/chipset.h b/include/chipset.h
index 03f5ef4318..932f1aec3e 100644
--- a/include/chipset.h
+++ b/include/chipset.h
@@ -63,15 +63,13 @@ void chipset_exit_hard_off(void);
static inline void chipset_exit_hard_off(void) { }
#endif
-/**
- * Enable/disable CPU throttling.
- *
- * @param throttle Enable (!=0) or disable(0) throttling
+/* This is a private chipset-specific implementation for use only by
+ * throttle_ap() . Don't call this directly!
*/
void chipset_throttle_cpu(int throttle);
/**
- * Immedaitely shut off power to main processor and chipset.
+ * Immediately shut off power to main processor and chipset.
*
* This is intended for use when the system is too hot or battery power is
* critical.
diff --git a/include/config.h b/include/config.h
index 8b5e2a9214..a5263c1985 100644
--- a/include/config.h
+++ b/include/config.h
@@ -181,6 +181,9 @@
#define CONFIG_CHIPSET_HAS_PP1350
#define CONFIG_CHIPSET_HAS_PP5000
+/* Compile support for chipset throttling */
+#undef CONFIG_CHIPSET_CAN_THROTTLE
+
/*****************************************************************************/
/*
* Optional console commands
diff --git a/include/throttle_ap.h b/include/throttle_ap.h
new file mode 100644
index 0000000000..e66a5c6bec
--- /dev/null
+++ b/include/throttle_ap.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Common interface to throttle the AP */
+
+#ifndef __CROS_EC_THROTTLE_AP_H
+#define __CROS_EC_THROTTLE_AP_H
+
+/**
+ * Level of throttling desired.
+ */
+enum throttle_level {
+ THROTTLE_OFF = 0,
+ THROTTLE_ON,
+};
+
+/**
+ * Types of throttling desired. These are independent.
+ */
+enum throttle_type {
+ THROTTLE_SOFT = 0, /* for example, host events */
+ THROTTLE_HARD, /* for example, PROCHOT */
+ NUM_THROTTLE_TYPES
+};
+
+/**
+ * Possible sources for CPU throttling requests.
+ */
+enum throttle_sources {
+ THROTTLE_SRC_THERMAL = 0,
+ THROTTLE_SRC_POWER,
+};
+
+
+/**
+ * Enable/disable CPU throttling.
+ *
+ * This is a virtual "OR" operation. Any caller can enable CPU throttling of
+ * any type, but all callers must agree in order to disable that type.
+ *
+ * @param level Level of throttling desired
+ * @param type Type of throttling desired
+ * @param source Which task is requesting throttling
+ */
+void throttle_ap(enum throttle_level level,
+ enum throttle_type type,
+ enum throttle_sources source);
+
+#endif /* __CROS_EC_THROTTLE_AP_H */
diff --git a/test/adapter.c b/test/adapter.c
index de927343b6..4a93e6089a 100644
--- a/test/adapter.c
+++ b/test/adapter.c
@@ -240,34 +240,34 @@ static int test_thresholds_sequence(int entry)
/* one more ought to do it */
check_threshold(mock_current, lim);
TEST_ASSERT(lim->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* going midrange for a long time shouldn't change anything */
mock_current = (lim->lo_val + lim->hi_val) / 2;
for (i = 1; i < longtime; i++)
check_threshold(mock_current, lim);
TEST_ASSERT(lim->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* below low limit for not quite long enough */
mock_current = lim->lo_val - 1;
for (i = 1; i < lim->lo_cnt; i++)
check_threshold(mock_current, lim);
TEST_ASSERT(lim->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* back above the low limit once */
mock_current = lim->lo_val + 1;
check_threshold(mock_current, lim);
TEST_ASSERT(lim->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* now back down - that should have reset the count */
mock_current = lim->lo_val - 1;
for (i = 1; i < lim->lo_cnt; i++)
check_threshold(mock_current, lim);
TEST_ASSERT(lim->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* One more ought to do it */
check_threshold(mock_current, lim);
@@ -374,34 +374,34 @@ static int test_batt(void)
/* one more ought to do it */
watch_battery_closely(&ctx);
TEST_ASSERT(l->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* going midrange for a long time shouldn't change anything */
mock_batt((l->lo_val + l->hi_val) / 2);
for (i = 1; i < longtime; i++)
watch_battery_closely(&ctx);
TEST_ASSERT(l->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* charge for not quite long enough */
mock_batt(-1);
for (i = 1; i < l->lo_cnt; i++)
watch_battery_closely(&ctx);
TEST_ASSERT(l->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* back above the low limit once */
mock_batt(l->lo_val + 1);
watch_battery_closely(&ctx);
TEST_ASSERT(l->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* now charge again - that should have reset the count */
mock_batt(-1);
for (i = 1; i < l->lo_cnt; i++)
watch_battery_closely(&ctx);
TEST_ASSERT(l->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
/* One more ought to do it */
watch_battery_closely(&ctx);
@@ -416,20 +416,203 @@ static int test_batt(void)
/* one more */
watch_battery_closely(&ctx);
TEST_ASSERT(h->triggered == 1);
- TEST_ASSERT(ap_is_throttled == 1);
+ TEST_ASSERT(ap_is_throttled);
return EC_SUCCESS;
}
+static int test_batt_vs_adapter(void)
+{
+ /* Only need one set of adapter thresholds for this test */
+ struct adapter_limits *a_lim;
+
+ /* Same structs are used for battery thresholds */
+ struct adapter_limits *b_lim;
+
+ int longtime;
+ int i;
+
+ /* For adapter, we'll use ADAPTER_UNKNOWN, Turbo off, softer limits */
+ a_lim = &ad_limits[0][0][0];
+
+ /* NB: struct adapter_limits assumes hi_val > lo_val, so the values in
+ * batt_limits[] indicate discharge current (mA). However, the value
+ * returned from battery_current() is postive for charging, and
+ * negative for discharging.
+ */
+
+ /* We're assuming two limits, mild and urgent. */
+ TEST_ASSERT(NUM_BATT_THRESHOLDS == 2);
+ /* Find out which is which. We want the mild one. */
+ if (batt_limits[0].hi_val > batt_limits[1].hi_val)
+ b_lim = &batt_limits[1];
+ else
+ b_lim = &batt_limits[0];
+
+
+ /* DANGER: we need these two to not be in sync. */
+ TEST_ASSERT(a_lim->hi_cnt == 16);
+ TEST_ASSERT(b_lim->hi_cnt == 16);
+ /* Now that we know they're the same, let's change one */
+ b_lim->hi_cnt = 12;
+
+ /* DANGER: We also rely on these values */
+ TEST_ASSERT(a_lim->lo_cnt == 80);
+ TEST_ASSERT(b_lim->lo_cnt == 50);
+
+
+ /* Find a time longer than all sample count limits */
+ longtime = MAX(MAX(a_lim->lo_cnt, a_lim->hi_cnt),
+ MAX(b_lim->lo_cnt, b_lim->hi_cnt)) + 2;
+
+
+ test_reset_mocks(); /* everything == 0 */
+ ctx.curr.batt.state_of_charge = 25;
+ change_ac(1);
+
+ /* make sure no limits are triggered by staying low for a long time */
+ mock_current = a_lim->lo_val - 1; /* charger current is safe */
+ mock_batt(-1); /* battery is charging */
+
+ for (i = 1; i < longtime; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+
+ /* battery discharge current is too high for almost long enough */
+ mock_batt(b_lim->hi_val + 1);
+ for (i = 1; i < b_lim->hi_cnt; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(b_lim->count != 0);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* one more ought to do it */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled);
+
+
+ /* charge for not quite long enough */
+ mock_batt(-1);
+ for (i = 1; i < b_lim->lo_cnt; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* and one more */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+
+ /* Now adapter current is too high for almost long enough */
+ mock_current = a_lim->hi_val + 1;
+ for (i = 1; i < a_lim->hi_cnt; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* one more ought to do it */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 1);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* below low limit for not quite long enough */
+ mock_current = a_lim->lo_val - 1;
+ for (i = 1; i < a_lim->lo_cnt; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 1);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* One more ought to do it */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+
+ /* Now both are high, but battery hi_cnt is shorter */
+ mock_batt(b_lim->hi_val + 1);
+ mock_current = a_lim->hi_val + 1;
+ for (i = 1; i < b_lim->hi_cnt; i++) /* just under battery limit */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* one more should kick the battery threshold */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* don't quite reach the adapter threshold */
+ for (i = 1; i < a_lim->hi_cnt - b_lim->hi_cnt; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* okay, now */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 1);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* Leave battery high, let the adapter come down */
+ mock_current = a_lim->lo_val - 1;
+ for (i = 1; i < a_lim->lo_cnt; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 1);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* One more ought to do it for the adapter */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* now charge the battery again */
+ mock_batt(-1);
+ for (i = 1; i < b_lim->lo_cnt; i++)
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(b_lim->triggered == 1);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* and one more */
+ watch_adapter_closely(&ctx);
+ TEST_ASSERT(b_lim->triggered == 0);
+ TEST_ASSERT(a_lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ return EC_SUCCESS;
+}
+
+
void run_test(void)
{
test_reset();
+ test_chipset_on();
RUN_TEST(test_identification);
RUN_TEST(test_turbo);
RUN_TEST(test_thresholds);
RUN_TEST(test_batt);
+ RUN_TEST(test_batt_vs_adapter);
+
test_print_result();
}
diff --git a/test/adapter.tasklist b/test/adapter.tasklist
index c90588cbf5..8ef20bab32 100644
--- a/test/adapter.tasklist
+++ b/test/adapter.tasklist
@@ -14,4 +14,5 @@
* 'd' in an opaque parameter passed to the routine at startup
* 's' is the stack size in bytes; must be a multiple of 8
*/
-#define CONFIG_TEST_TASK_LIST /* No test tasks */
+#define CONFIG_TEST_TASK_LIST \
+ TASK_TEST(CHIPSET, chipset_task, NULL, TASK_STACK_SIZE)
diff --git a/test/build.mk b/test/build.mk
index 470e74dc75..e38e0ee11c 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -30,7 +30,7 @@ test-list-$(BOARD_bolt)=
# Emulator tests
test-list-host=mutex pingpong utils kb_scan kb_mkbp lid_sw power_button hooks
test-list-host+=thermal flash queue kb_8042 extpwr_gpio console_edit system
-test-list-host+=sbs_charging adapter host_command
+test-list-host+=sbs_charging adapter host_command thermal_falco
adapter-y=adapter.o
console_edit-y=console_edit.o
@@ -51,6 +51,7 @@ sbs_charging-y=sbs_charging.o
stress-y=stress.o
system-y=system.o
thermal-y=thermal.o
+thermal_falco-y=thermal_falco.o
timer_calib-y=timer_calib.o
timer_dos-y=timer_dos.o
utils-y=utils.o
diff --git a/test/test_config.h b/test/test_config.h
index 0dd63b9dd8..0310e80e40 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -26,11 +26,26 @@ int board_discharge_on_ac(int enabled);
#endif
#ifdef TEST_adapter
+#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_EXTPOWER_FALCO
#endif
#ifdef TEST_thermal
+#define CONFIG_CHIPSET_CAN_THROTTLE
#define CONFIG_TEMP_SENSOR
#endif
+#ifdef TEST_thermal_falco
+#define CONFIG_BATTERY_MOCK
+#define CONFIG_BATTERY_SMART
+#define CONFIG_CHARGER
+#define CONFIG_CHARGER_INPUT_CURRENT 4032
+#define CONFIG_CHIPSET_CAN_THROTTLE
+#define CONFIG_EXTPOWER_FALCO
+#define CONFIG_TEMP_SENSOR
+#define I2C_PORT_BATTERY 1
+#define I2C_PORT_CHARGER 1
+#define I2C_PORT_HOST 1
+#endif
+
#endif /* __CROS_EC_TEST_CONFIG_H */
diff --git a/test/thermal.tasklist b/test/thermal.tasklist
index aaa89ce45c..8ef20bab32 100644
--- a/test/thermal.tasklist
+++ b/test/thermal.tasklist
@@ -14,4 +14,5 @@
* 'd' in an opaque parameter passed to the routine at startup
* 's' is the stack size in bytes; must be a multiple of 8
*/
- #define CONFIG_TEST_TASK_LIST /* No test tasks */
+#define CONFIG_TEST_TASK_LIST \
+ TASK_TEST(CHIPSET, chipset_task, NULL, TASK_STACK_SIZE)
diff --git a/test/thermal_falco.c b/test/thermal_falco.c
new file mode 100644
index 0000000000..0d56f4fe16
--- /dev/null
+++ b/test/thermal_falco.c
@@ -0,0 +1,874 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Test thermal engine.
+ */
+
+#include "battery_pack.h"
+#include "chipset.h"
+#include "common.h"
+#include "console.h"
+#include "extpower.h"
+#include "extpower_falco.h"
+#include "fan.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "printf.h"
+#include "smart_battery.h"
+#include "temp_sensor.h"
+#include "test_util.h"
+#include "thermal.h"
+#include "throttle_ap.h"
+#include "timer.h"
+#include "util.h"
+
+/* Normally private stuff from the modules we're going to test */
+#include "thermal_falco_externs.h"
+
+
+/*****************************************************************************/
+/* Exported data */
+
+struct ec_thermal_config thermal_params[TEMP_SENSOR_COUNT];
+
+/* The tests below make some assumptions. */
+BUILD_ASSERT(TEMP_SENSOR_COUNT == 4);
+BUILD_ASSERT(EC_TEMP_THRESH_COUNT == 3);
+
+/*****************************************************************************/
+/* Mock functions */
+
+static int mock_temp[TEMP_SENSOR_COUNT];
+static int host_throttled;
+static int cpu_throttled;
+static int cpu_shutdown;
+static int fan_pct;
+static int no_temps_read;
+static int mock_id;
+static int mock_ac;
+static int mock_charger_current;
+static int mock_battery_discharge_current;
+
+/* constants to match against throttling sources */
+static const int t_s_therm = (1 << THROTTLE_SRC_THERMAL);
+static const int t_s_power = (1 << THROTTLE_SRC_POWER);
+static const int t_s_both = ((1 << THROTTLE_SRC_THERMAL) |
+ (1 << THROTTLE_SRC_POWER));
+
+int dummy_temp_get_val(int idx, int *temp_ptr)
+{
+ if (mock_temp[idx] >= 0) {
+ *temp_ptr = mock_temp[idx];
+ return EC_SUCCESS;
+ }
+
+ return EC_ERROR_NOT_POWERED;
+}
+
+void chipset_force_shutdown(void)
+{
+ cpu_shutdown = 1;
+}
+
+void chipset_throttle_cpu(int throttled)
+{
+ cpu_throttled = throttled;
+}
+
+void host_throttle_cpu(int throttled)
+{
+ host_throttled = throttled;
+}
+
+void pwm_fan_set_percent_needed(int pct)
+{
+ fan_pct = pct;
+}
+
+void smi_sensor_failure_warning(void)
+{
+ no_temps_read = 1;
+}
+
+static void change_ac(int val)
+{
+ mock_ac = val;
+ extpower_interrupt(GPIO_AC_PRESENT);
+ sleep(1);
+}
+
+int gpio_get_level(enum gpio_signal signal)
+{
+ if (signal == GPIO_AC_PRESENT)
+ return mock_ac;
+ return 0;
+}
+
+int adc_read_channel(enum adc_channel ch)
+{
+ switch (ch) {
+ case ADC_AC_ADAPTER_ID_VOLTAGE:
+ return mock_id;
+ case ADC_CH_CHARGER_CURRENT:
+ return mock_charger_current;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* Test utilities */
+
+static void set_temps(int t0, int t1, int t2, int t3)
+{
+ mock_temp[0] = t0;
+ mock_temp[1] = t1;
+ mock_temp[2] = t2;
+ mock_temp[3] = t3;
+}
+
+static void all_temps(int t)
+{
+ set_temps(t, t, t, t);
+}
+
+static void reset_mock_battery(void)
+{
+ const struct battery_info *bat_info = battery_get_info();
+
+ /* 50% of charge */
+ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 50);
+ sb_write(SB_ABSOLUTE_STATE_OF_CHARGE, 50);
+ /* 25 degree Celsius */
+ sb_write(SB_TEMPERATURE, 250 + 2731);
+ /* Normal voltage */
+ sb_write(SB_VOLTAGE, bat_info->voltage_normal);
+ sb_write(SB_CHARGING_VOLTAGE, bat_info->voltage_max);
+ sb_write(SB_CHARGING_CURRENT, 4000);
+ /* Discharging at 100mAh */
+ sb_write(SB_CURRENT, -100);
+}
+DECLARE_HOOK(HOOK_INIT, reset_mock_battery, HOOK_PRIO_DEFAULT);
+
+static void mock_batt(int cur)
+{
+ sb_write(SB_CURRENT, -cur); /* discharge current is neg here */
+}
+
+static void reset_mocks(void)
+{
+ /* Ignore all sensors */
+ memset(thermal_params, 0, sizeof(thermal_params));
+
+ /* All sensors report error anyway */
+ set_temps(-1, -1 , -1, -1);
+
+ /* Reset expectations */
+ host_throttled = 0;
+ cpu_throttled = 0;
+ cpu_shutdown = 0;
+ fan_pct = 0;
+ no_temps_read = 0;
+
+ /* other mocked inputs */
+ mock_id = 0;
+ mock_ac = 0;
+ mock_charger_current = 0;
+ mock_battery_discharge_current = 0;
+}
+
+/*****************************************************************************/
+/* Tests */
+
+static int test_init_val(void)
+{
+ reset_mocks();
+ sleep(2);
+
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+ TEST_ASSERT(fan_pct == 0);
+ TEST_ASSERT(no_temps_read);
+
+ sleep(2);
+
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+ TEST_ASSERT(fan_pct == 0);
+ TEST_ASSERT(no_temps_read);
+
+ return EC_SUCCESS;
+}
+
+static int test_sensors_can_be_read(void)
+{
+ reset_mocks();
+ mock_temp[2] = 100;
+
+ sleep(2);
+
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+ TEST_ASSERT(fan_pct == 0);
+ TEST_ASSERT(no_temps_read == 0);
+
+ return EC_SUCCESS;
+}
+
+
+static int test_one_fan(void)
+{
+ reset_mocks();
+ thermal_params[2].temp_fan_off = 100;
+ thermal_params[2].temp_fan_max = 200;
+
+ all_temps(50);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 0);
+
+ all_temps(100);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 0);
+
+ all_temps(101);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 1);
+
+ all_temps(130);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 30);
+
+ all_temps(150);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 50);
+
+ all_temps(170);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 70);
+
+ all_temps(200);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ all_temps(300);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ return EC_SUCCESS;
+}
+
+static int test_two_fans(void)
+{
+ reset_mocks();
+
+ thermal_params[1].temp_fan_off = 120;
+ thermal_params[1].temp_fan_max = 160;
+ thermal_params[2].temp_fan_off = 100;
+ thermal_params[2].temp_fan_max = 200;
+
+ all_temps(50);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 0);
+
+ all_temps(100);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 0);
+
+ all_temps(101);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 1);
+
+ all_temps(130);
+ sleep(2);
+ /* fan 2 is still higher */
+ TEST_ASSERT(fan_pct == 30);
+
+ all_temps(150);
+ sleep(2);
+ /* now fan 1 is higher: 150 = 75% of [120-160] */
+ TEST_ASSERT(fan_pct == 75);
+
+ all_temps(170);
+ sleep(2);
+ /* fan 1 is maxed now */
+ TEST_ASSERT(fan_pct == 100);
+
+ all_temps(200);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ all_temps(300);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ return EC_SUCCESS;
+}
+
+static int test_all_fans(void)
+{
+ reset_mocks();
+
+ thermal_params[0].temp_fan_off = 20;
+ thermal_params[0].temp_fan_max = 60;
+ thermal_params[1].temp_fan_off = 120;
+ thermal_params[1].temp_fan_max = 160;
+ thermal_params[2].temp_fan_off = 100;
+ thermal_params[2].temp_fan_max = 200;
+ thermal_params[3].temp_fan_off = 300;
+ thermal_params[3].temp_fan_max = 500;
+
+ set_temps(1, 1, 1, 1);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 0);
+
+ /* Each sensor has its own range */
+ set_temps(40, 0, 0, 0);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 50);
+
+ set_temps(0, 140, 0, 0);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 50);
+
+ set_temps(0, 0, 150, 0);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 50);
+
+ set_temps(0, 0, 0, 400);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 50);
+
+ set_temps(60, 0, 0, 0);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ set_temps(0, 160, 0, 0);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ set_temps(0, 0, 200, 0);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ set_temps(0, 0, 0, 500);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ /* But sensor 0 needs the most cooling */
+ all_temps(20);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 0);
+
+ all_temps(21);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 2);
+
+ all_temps(30);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 25);
+
+ all_temps(40);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 50);
+
+ all_temps(50);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 75);
+
+ all_temps(60);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ all_temps(65);
+ sleep(2);
+ TEST_ASSERT(fan_pct == 100);
+
+ return EC_SUCCESS;
+}
+
+static int test_one_limit(void)
+{
+ reset_mocks();
+ thermal_params[2].temp_host[EC_TEMP_THRESH_WARN] = 100;
+ thermal_params[2].temp_host[EC_TEMP_THRESH_HIGH] = 200;
+ thermal_params[2].temp_host[EC_TEMP_THRESH_HALT] = 300;
+
+ all_temps(50);
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(100);
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(101);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(100);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(99);
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(199);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(200);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(201);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(200);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(199);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(99);
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(201);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(99);
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ all_temps(301);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 1);
+
+ /* We probably won't be able to read the CPU temp while shutdown,
+ * so nothing will change. */
+ all_temps(-1);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ /* cpu_shutdown is only set for testing purposes. The thermal task
+ * doesn't do anything that could clear it. */
+
+ all_temps(50);
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+
+ return EC_SUCCESS;
+}
+
+static int test_several_limits(void)
+{
+ reset_mocks();
+
+ thermal_params[1].temp_host[EC_TEMP_THRESH_WARN] = 150;
+ thermal_params[1].temp_host[EC_TEMP_THRESH_HIGH] = 200;
+ thermal_params[1].temp_host[EC_TEMP_THRESH_HALT] = 250;
+
+ thermal_params[2].temp_host[EC_TEMP_THRESH_WARN] = 100;
+ thermal_params[2].temp_host[EC_TEMP_THRESH_HIGH] = 200;
+ thermal_params[2].temp_host[EC_TEMP_THRESH_HALT] = 300;
+
+ thermal_params[3].temp_host[EC_TEMP_THRESH_WARN] = 20;
+ thermal_params[3].temp_host[EC_TEMP_THRESH_HIGH] = 30;
+ thermal_params[3].temp_host[EC_TEMP_THRESH_HALT] = 40;
+
+ set_temps(500, 100, 150, 10);
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm); /* 1=low, 2=warn, 3=low */
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ set_temps(500, 50, -1, 10); /* 1=low, 2=X, 3=low */
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ set_temps(500, 170, 210, 10); /* 1=warn, 2=high, 3=low */
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ set_temps(500, 100, 50, 40); /* 1=low, 2=low, 3=high */
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ set_temps(500, 100, 50, 41); /* 1=low, 2=low, 3=shutdown */
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 1);
+
+ all_temps(0); /* reset from shutdown */
+ sleep(2);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+
+
+ return EC_SUCCESS;
+}
+
+static int test_batt(void)
+{
+ struct adapter_limits *l;
+ int longtime;
+ int i;
+
+ reset_mocks();
+ /* We're assuming two limits, mild and urgent. */
+ TEST_ASSERT(NUM_BATT_THRESHOLDS == 2);
+ /* Find out which is which, only use the lower one */
+ if (batt_limits[0].hi_val > batt_limits[1].hi_val)
+ l = &batt_limits[1];
+ else
+ l = &batt_limits[0];
+
+ /* Find a time longer than all sample count limits */
+ for (i = longtime = 0; i < NUM_BATT_THRESHOLDS; i++)
+ longtime = MAX(longtime,
+ MAX(batt_limits[i].lo_cnt,
+ batt_limits[i].hi_cnt));
+ longtime += 2;
+
+ /* On AC, but this doesn't actually matter for this test */
+ mock_batt(0);
+ change_ac(1);
+
+ TEST_ASSERT(ap_is_throttled == 0);
+ change_ac(0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* reset, by staying low for a long time */
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* mock_batt() specifies the DISCHARGE current. Charging
+ * should do nothing, no matter how high. */
+ mock_batt(-1);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* midrange for a long time shouldn't do anything */
+ mock_batt((l->lo_val + l->hi_val) / 2);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* above high limit for not quite long enough */
+ mock_batt(l->hi_val + 1);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (l->hi_cnt - 1));
+ TEST_ASSERT(l->count != 0);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* drop below the high limit once */
+ mock_batt(l->hi_val - 1);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+ TEST_ASSERT(l->count == 0);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* now back up */
+ mock_batt(l->hi_val + 1);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (l->hi_cnt - 1));
+ TEST_ASSERT(l->count != 0);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* one more ought to do it */
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* going midrange for a long time shouldn't change anything */
+ mock_batt((l->lo_val + l->hi_val) / 2);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * longtime);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* charge for not quite long enough */
+ mock_batt(-1);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (l->lo_cnt - 1));
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* back above the low limit once */
+ mock_batt(l->lo_val + 1);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* now charge again - that should have reset the count */
+ mock_batt(-1);
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (l->lo_cnt - 1));
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+
+ /* One more ought to do it */
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ return EC_SUCCESS;
+}
+
+static int test_several_limits_with_batt(void)
+{
+ struct adapter_limits *l;
+ int longtime;
+ int i;
+
+ /* We're assuming two limits, mild and urgent. */
+ TEST_ASSERT(NUM_BATT_THRESHOLDS == 2);
+ /* Find out which is which, only use the lower one */
+ if (batt_limits[0].hi_val > batt_limits[1].hi_val)
+ l = &batt_limits[1];
+ else
+ l = &batt_limits[0];
+
+ /* Find a time longer than all sample count limits */
+ for (i = longtime = 0; i < NUM_BATT_THRESHOLDS; i++)
+ longtime = MAX(longtime,
+ MAX(batt_limits[i].lo_cnt,
+ batt_limits[i].hi_cnt));
+ longtime += 2;
+ longtime *= EXTPOWER_FALCO_POLL_PERIOD;
+
+ reset_mocks();
+
+ /* Set some thermal limits */
+ thermal_params[1].temp_host[EC_TEMP_THRESH_WARN] = 150;
+ thermal_params[1].temp_host[EC_TEMP_THRESH_HIGH] = 200;
+ thermal_params[1].temp_host[EC_TEMP_THRESH_HALT] = 250;
+
+ thermal_params[2].temp_host[EC_TEMP_THRESH_WARN] = 100;
+ thermal_params[2].temp_host[EC_TEMP_THRESH_HIGH] = 200;
+ thermal_params[2].temp_host[EC_TEMP_THRESH_HALT] = 300;
+
+ thermal_params[3].temp_host[EC_TEMP_THRESH_WARN] = 20;
+ thermal_params[3].temp_host[EC_TEMP_THRESH_HIGH] = 30;
+ thermal_params[3].temp_host[EC_TEMP_THRESH_HALT] = 40;
+
+
+ /* On AC, charging */
+ mock_batt(-1);
+ all_temps(0);
+ change_ac(1);
+ usleep(longtime);
+ /* Everything is ready */
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+
+ set_temps(500, 100, 150, 10);
+ usleep(longtime);
+ TEST_ASSERT(host_throttled == t_s_therm); /* 1=low, 2=warn, 3=low */
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ /* battery up and down */
+ mock_batt(l->hi_val + 1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(host_throttled == t_s_both); /* 1=low, 2=warn, 3=low */
+ TEST_ASSERT(cpu_shutdown == 0);
+ mock_batt(-1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(host_throttled == t_s_therm); /* 1=low, 2=warn, 3=low */
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+
+ set_temps(500, 50, -1, 10); /* 1=low, 2=X, 3=low */
+ usleep(longtime);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* battery up and down */
+ mock_batt(l->hi_val + 1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(host_throttled == t_s_power);
+ TEST_ASSERT(cpu_shutdown == 0);
+ mock_batt(-1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+
+ set_temps(500, 170, 210, 10); /* 1=warn, 2=high, 3=low */
+ sleep(2);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ /* battery up and down */
+ mock_batt(l->hi_val + 1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(host_throttled == t_s_both);
+ TEST_ASSERT(cpu_shutdown == 0);
+ mock_batt(-1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+
+ set_temps(500, 100, 50, 40); /* 1=low, 2=low, 3=high */
+ usleep(longtime);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+ /* battery up and down */
+ mock_batt(l->hi_val + 1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(host_throttled == t_s_both);
+ TEST_ASSERT(cpu_shutdown == 0);
+ mock_batt(-1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 0);
+
+
+ set_temps(500, 100, 50, 41); /* 1=low, 2=low, 3=shutdown */
+ usleep(longtime);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 1);
+
+ /* battery up and down */
+ mock_batt(l->hi_val + 1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(host_throttled == t_s_both);
+ TEST_ASSERT(cpu_shutdown == 1);
+ mock_batt(-1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(host_throttled == t_s_therm);
+ TEST_ASSERT(cpu_throttled == t_s_therm);
+ TEST_ASSERT(cpu_shutdown == 1);
+
+
+ all_temps(0);
+ usleep(longtime);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+
+ /* battery up and down */
+ mock_batt(l->hi_val + 1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(host_throttled == t_s_power);
+ mock_batt(-1);
+ usleep(longtime);
+ TEST_ASSERT(l->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(host_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+
+ return EC_SUCCESS;
+}
+
+
+void run_test(void)
+{
+ test_chipset_on();
+
+ RUN_TEST(test_init_val);
+ RUN_TEST(test_sensors_can_be_read);
+ RUN_TEST(test_one_fan);
+ RUN_TEST(test_two_fans);
+ RUN_TEST(test_all_fans);
+
+ RUN_TEST(test_one_limit);
+ RUN_TEST(test_several_limits);
+
+ RUN_TEST(test_batt);
+ RUN_TEST(test_several_limits_with_batt);
+
+ test_print_result();
+}
diff --git a/test/thermal_falco.tasklist b/test/thermal_falco.tasklist
new file mode 100644
index 0000000000..6ca1e65553
--- /dev/null
+++ b/test/thermal_falco.tasklist
@@ -0,0 +1,19 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * List of enabled tasks in the priority order
+ *
+ * The first one has the lowest priority.
+ *
+ * For each task, use the macro TASK_TEST(n, r, d, s) where :
+ * 'n' in the name of the task
+ * 'r' in the main routine of the task
+ * 'd' in an opaque parameter passed to the routine at startup
+ * 's' is the stack size in bytes; must be a multiple of 8
+ */
+#define CONFIG_TEST_TASK_LIST \
+ TASK_TEST(CHARGER, charger_task, NULL, TASK_STACK_SIZE) \
+ TASK_TEST(CHIPSET, chipset_task, NULL, TASK_STACK_SIZE)
diff --git a/test/thermal_falco_externs.h b/test/thermal_falco_externs.h
new file mode 100644
index 0000000000..6f202e0d5c
--- /dev/null
+++ b/test/thermal_falco_externs.h
@@ -0,0 +1,15 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef __THERMAL_FALCO_EXTERNS_H
+#define __THERMAL_FALCO_EXTERNS_H
+
+/* Normally private symbols from the modules that we're testing. */
+extern struct adapter_limits
+ ad_limits[][NUM_AC_TURBO_STATES][NUM_AC_THRESHOLDS];
+extern int ap_is_throttled;
+extern struct adapter_limits batt_limits[NUM_BATT_THRESHOLDS];
+
+#endif /* __THERMAL_FALCO_EXTERNS_H */