summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Richardson <wfrichar@chromium.org>2013-09-03 10:30:27 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2013-09-05 00:47:37 +0000
commit07af06d5d1691eca151cd63cc03218adbef88227 (patch)
tree01b7f9e398f76821514da27b89a8d99fdedad9a8
parenta767769ce03a88fcfecc4ec4c4aea5b74ffe2c9a (diff)
downloadchrome-ec-07af06d5d1691eca151cd63cc03218adbef88227.tar.gz
Handle multiple independent sources for CPU throttling
When both the thermal task and the power/charger task have different ideas on whether the CPU should be throttled, we need to OR their opinions so that either can start throttling, but both have to agree to stop it. This CL changes the chipset_throttle_cpu() function to also take a "source" argument, so we can handle multiple opinions. As it turns out, this same also problem existed in the Falco-specific power logic by itself, but was largely masked by the threshold settings. We handle that now, too. BUG=chrome-os-partner:20739 BRANCH=falco, ToT TEST=manual This change adds a bunch of tests to ensure that all this works, so try cd src/platform/ec make BOARD=falco runtests And of course, try it on the actual hardware under the appropriate loads. Change-Id: Id651e2cdc8dbd435aad6e21c5d2ce4b932a55f88 Signed-off-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/168088 Reviewed-by: Randall Spangler <rspangler@chromium.org>
-rw-r--r--board/bolt/power_sequence.c2
-rw-r--r--board/host/chipset.c5
-rw-r--r--common/chipset.c36
-rw-r--r--common/chipset_haswell.c2
-rw-r--r--common/chipset_ivybridge.c2
-rw-r--r--common/extpower_falco.c34
-rw-r--r--common/mock_charger.c3
-rw-r--r--common/mock_x86_power.c2
-rw-r--r--common/thermal.c4
-rw-r--r--include/chipset.h18
-rw-r--r--test/adapter.c207
-rw-r--r--test/adapter.tasklist3
-rw-r--r--test/build.mk3
-rw-r--r--test/test_config.h7
-rw-r--r--test/thermal.c2
-rw-r--r--test/thermal.tasklist3
-rw-r--r--test/thermal_falco.c574
-rw-r--r--test/thermal_falco.tasklist20
-rw-r--r--test/thermal_falco_externs.h14
19 files changed, 907 insertions, 34 deletions
diff --git a/board/bolt/power_sequence.c b/board/bolt/power_sequence.c
index ba5e70589c..9d0b8532aa 100644
--- a/board/bolt/power_sequence.c
+++ b/board/bolt/power_sequence.c
@@ -104,7 +104,7 @@ void chipset_reset(int cold_reset)
}
}
-void chipset_throttle_cpu(int throttle)
+void chipset_throttle_cpu_implementation(int throttle)
{
/* FIXME CPRINTF("[%T %s(%d)]\n", __func__, throttle);*/
}
diff --git a/board/host/chipset.c b/board/host/chipset.c
index 4197c46326..daafbe6653 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_implementation(int throttle)
+{
+ /* Do nothing */
+}
+
test_mockable void chipset_force_shutdown(void)
{
/* Do nothing */
diff --git a/common/chipset.c b/common/chipset.c
index 708c6b226a..760c1dd1f4 100644
--- a/common/chipset.c
+++ b/common/chipset.c
@@ -14,9 +14,45 @@
#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 uint32_t throttle_request;
+void chipset_throttle_cpu(int throttle, enum throttle_sources source)
+{
+ if (throttle)
+ throttle_request |= source;
+ else
+ throttle_request &= ~source;
+
+ chipset_throttle_cpu_implementation(throttle_request);
+}
+
/*****************************************************************************/
/* Console commands */
+static int command_apthrottle(int argc, char **argv)
+{
+ uint32_t newval;
+ char *e;
+ if (argc > 1) {
+ newval = strtoi(argv[1], &e, 0);
+ if (*e)
+ return EC_ERROR_PARAM1;
+ throttle_request = newval;
+ chipset_throttle_cpu_implementation(throttle_request);
+ }
+
+ ccprintf("AP throttling is %s (0x%08x)\n",
+ throttle_request ? "on" : "off", throttle_request);
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(apthrottle, command_apthrottle,
+ "[BITMASK]",
+ "Display or set the AP throttling state",
+ NULL);
+
static int command_apreset(int argc, char **argv)
{
int is_cold = 1;
diff --git a/common/chipset_haswell.c b/common/chipset_haswell.c
index 49428ff52f..795164511a 100644
--- a/common/chipset_haswell.c
+++ b/common/chipset_haswell.c
@@ -103,7 +103,7 @@ void chipset_reset(int cold_reset)
}
}
-void chipset_throttle_cpu(int throttle)
+void chipset_throttle_cpu_implementation(int throttle)
{
if (throttle_cpu != throttle)
CPRINTF("[%T %s(%d)]\n", __func__, throttle);
diff --git a/common/chipset_ivybridge.c b/common/chipset_ivybridge.c
index 048d6a5d5b..ede130d734 100644
--- a/common/chipset_ivybridge.c
+++ b/common/chipset_ivybridge.c
@@ -106,7 +106,7 @@ void chipset_reset(int cold_reset)
}
}
-void chipset_throttle_cpu(int throttle)
+void chipset_throttle_cpu_implementation(int throttle)
{
throttle_cpu = throttle;
diff --git a/common/extpower_falco.c b/common/extpower_falco.c
index 1769196c12..947c4c2eac 100644
--- a/common/extpower_falco.c
+++ b/common/extpower_falco.c
@@ -194,22 +194,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)
{
+ if (on)
+ ap_is_throttled |= (1 << whosays);
+ else
+ ap_is_throttled &= ~(1 << whosays);
+
/* Use the big ugly PROCHOT hammer to throttle the CPU */
- chipset_throttle_cpu(on);
- ap_is_throttled = on;
+ chipset_throttle_cpu(ap_is_throttled, 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;
}
@@ -220,7 +232,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;
}
@@ -252,7 +264,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)
@@ -289,13 +302,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 47b2a96079..66033941a6 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/mock_x86_power.c b/common/mock_x86_power.c
index daceee57b1..a1de8386f7 100644
--- a/common/mock_x86_power.c
+++ b/common/mock_x86_power.c
@@ -28,7 +28,7 @@ void chipset_reset(int cold_reset)
}
-void chipset_throttle_cpu(int throttle)
+void chipset_throttle_cpu_implementation(int throttle)
{
/* Print transitions */
static int last_val = 0;
diff --git a/common/thermal.c b/common/thermal.c
index ce79c5e2d3..a7f8444437 100644
--- a/common/thermal.c
+++ b/common/thermal.c
@@ -166,9 +166,9 @@ static void overheated_action(void)
if (overheated[THRESHOLD_WARNING]) {
smi_overheated_warning();
- chipset_throttle_cpu(1);
+ chipset_throttle_cpu(1, THROTTLE_SRC_THERMAL);
} else {
- chipset_throttle_cpu(0);
+ chipset_throttle_cpu(0, THROTTLE_SRC_THERMAL);
}
if (fan_ctrl_on) {
diff --git a/include/chipset.h b/include/chipset.h
index 03f5ef4318..e77d394868 100644
--- a/include/chipset.h
+++ b/include/chipset.h
@@ -64,11 +64,27 @@ static inline void chipset_exit_hard_off(void) { }
#endif
/**
+ * Possible sources for CPU throttling requests.
+ */
+enum throttle_sources {
+ THROTTLE_SRC_THERMAL = (1 << 0),
+ THROTTLE_SRC_POWER = (1 << 1),
+};
+
+/**
* Enable/disable CPU throttling.
*
+ * This is a virtual "OR" operation. Any caller can enable CPU
+ * throttling, but all callers must agree in order to disable it.
+ *
* @param throttle Enable (!=0) or disable(0) throttling
+ * @param source Flag indicating which caller is requesting throttling
*/
-void chipset_throttle_cpu(int throttle);
+void chipset_throttle_cpu(int throttle, enum throttle_sources source);
+
+/* This is the private chipset-specific implementation. Don't call this
+ * directly. */
+void chipset_throttle_cpu_implementation(int throttle);
/**
* Immedaitely shut off power to main processor and chipset.
diff --git a/test/adapter.c b/test/adapter.c
index de927343b6..34a6afa2ee 100644
--- a/test/adapter.c
+++ b/test/adapter.c
@@ -74,7 +74,7 @@ int charger_set_option(int option)
return EC_SUCCESS;
}
-void chipset_throttle_cpu(int throttle)
+void chipset_throttle_cpu_implementation(int throttle)
{
/* PROCHOT, ugh. */
}
@@ -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 a38214b931..a3827e656d 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -31,7 +31,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
+test-list-host+=sbs_charging adapter 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 827a82642f..57df6639c6 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -23,4 +23,11 @@
#define CONFIG_EXTPOWER_FALCO
#endif
+#ifdef TEST_thermal_falco
+#define CONFIG_CHARGER
+#define CONFIG_EXTPOWER_FALCO
+#define CONFIG_BATTERY_MOCK
+#define CONFIG_CHARGER_INPUT_CURRENT 4032
+#endif
+
#endif /* __CROS_EC_TEST_CONFIG_H */
diff --git a/test/thermal.c b/test/thermal.c
index 485e19bfb5..ab3c7b5c54 100644
--- a/test/thermal.c
+++ b/test/thermal.c
@@ -53,7 +53,7 @@ void chipset_force_shutdown(void)
cpu_down = 1;
}
-void chipset_throttle_cpu(int throttled)
+void chipset_throttle_cpu_implementation(int throttled)
{
cpu_throttled = throttled;
}
diff --git a/test/thermal.tasklist b/test/thermal.tasklist
index ab0b5484f5..6978d1aca9 100644
--- a/test/thermal.tasklist
+++ b/test/thermal.tasklist
@@ -15,4 +15,5 @@
* 's' is the stack size in bytes; must be a multiple of 8
*/
#define CONFIG_TEST_TASK_LIST \
- TASK_TEST(THERMAL, thermal_task, NULL, TASK_STACK_SIZE)
+ TASK_TEST(THERMAL, thermal_task, NULL, TASK_STACK_SIZE) \
+ 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..ba976d7a04
--- /dev/null
+++ b/test/thermal_falco.c
@@ -0,0 +1,574 @@
+/* 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 "common.h"
+#include "console.h"
+#include "extpower_falco.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 "thermal_falco_externs.h"
+#include "timer.h"
+#include "util.h"
+
+static int mock_temp[TEMP_SENSOR_COUNT];
+static int fan_rpm;
+static int fan_rpm_mode = 1;
+static int cpu_throttled;
+static int cpu_down;
+static int mock_ac;
+static int mock_id;
+static int mock_current;
+
+extern struct thermal_config_t thermal_config[TEMP_SENSOR_TYPE_COUNT];
+extern const int fan_speed[THERMAL_FAN_STEPS + 1];
+
+/*****************************************************************************/
+/* Mock functions */
+
+int temp_sensor_read(enum temp_sensor_id id, int *temp_ptr)
+{
+ if (mock_temp[id] >= 0) {
+ *temp_ptr = mock_temp[id];
+ return EC_SUCCESS;
+ } else {
+ return -mock_temp[id];
+ }
+}
+
+void pwm_set_fan_rpm_mode(int rpm_mode)
+{
+ fan_rpm_mode = rpm_mode;
+}
+
+void pwm_set_fan_target_rpm(int rpm)
+{
+ fan_rpm = rpm;
+}
+
+void chipset_force_shutdown(void)
+{
+ cpu_down = 1;
+}
+
+void chipset_throttle_cpu_implementation(int throttled)
+{
+ cpu_throttled = throttled;
+}
+
+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_current;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* Test utilities */
+
+/* Test shorthands */
+#define T_CPU TEMP_SENSOR_CPU
+#define T_BOARD TEMP_SENSOR_BOARD
+#define T_CASE TEMP_SENSOR_CASE
+#define THRESHOLD(x, y) (thermal_config[x].thresholds[y])
+#define FAN_THRESHOLD(x, y) THRESHOLD(x, THRESHOLD_COUNT + (y))
+
+static void reset_mock_temp(void)
+{
+ int i;
+ enum temp_sensor_type type;
+ for (i = 0; i < TEMP_SENSOR_COUNT; ++i) {
+ type = temp_sensors[i].type;
+ mock_temp[i] = FAN_THRESHOLD(type, 0) - 1;
+ }
+}
+
+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 int wait_fan_rpm(int rpm, int timeout_secs)
+{
+ do {
+ if (fan_rpm == rpm)
+ return 1;
+ usleep(SECOND);
+ } while (timeout_secs--);
+
+ return 0;
+}
+
+static int wait_value(int *v, int target, int timeout_secs)
+{
+ do {
+ if (*v == target)
+ return 1;
+ usleep(SECOND);
+ } while (timeout_secs--);
+
+ return 0;
+}
+
+static int wait_set(int *v, int timeout_secs)
+{
+ return wait_value(v, 1, timeout_secs);
+}
+
+static int wait_clear(int *v, int timeout_secs)
+{
+ return wait_value(v, 0, timeout_secs);
+}
+
+/*****************************************************************************/
+/* Tests */
+
+static int test_init_val(void)
+{
+ /* Initial mock temperature values are all zero. */
+ TEST_ASSERT(cpu_throttled == 0);
+ TEST_ASSERT(cpu_down == 0);
+ TEST_ASSERT(!(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_OVERLOAD)));
+ TEST_ASSERT(!(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_SHUTDOWN)));
+
+ return EC_SUCCESS;
+}
+
+static int test_cpu_fan(void)
+{
+ reset_mock_temp();
+
+ /*
+ * Increase CPU temperature to first fan step and check if
+ * the fan comes up.
+ */
+ mock_temp[T_CPU] = FAN_THRESHOLD(T_CPU, 0);
+ TEST_ASSERT(wait_fan_rpm(fan_speed[1], 11));
+
+ /* Increase CPU temperature to second fan step */
+ mock_temp[T_CPU] = FAN_THRESHOLD(T_CPU, 1);
+ TEST_ASSERT(wait_fan_rpm(fan_speed[2], 11));
+
+ /* Test threshold hysteresis */
+ mock_temp[T_CPU]--;
+ usleep(15 * SECOND);
+ TEST_ASSERT(fan_rpm == fan_speed[2]);
+
+ /* Test action delay */
+ mock_temp[T_CPU] = FAN_THRESHOLD(T_CPU, 4);
+ usleep((temp_sensors[T_CPU].action_delay_sec - 1) * SECOND);
+ TEST_ASSERT(fan_rpm == fan_speed[2]);
+ mock_temp[T_CPU] = FAN_THRESHOLD(T_CPU, 0);
+
+ return EC_SUCCESS;
+}
+
+static int test_safety(void)
+{
+ reset_mock_temp();
+
+ /* Trigger CPU throttling */
+ mock_temp[T_CPU] = THRESHOLD(T_CPU, THRESHOLD_WARNING);
+ TEST_ASSERT(wait_set(&cpu_throttled, 11));
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_OVERLOAD));
+
+ /* Lower temperature. CPU not throttled anymore. */
+ mock_temp[T_CPU] = THRESHOLD(T_CPU, THRESHOLD_WARNING) - 5;
+ TEST_ASSERT(wait_clear(&cpu_throttled, 2));
+
+ /* Thermal shutdown */
+ mock_temp[T_CPU] = THRESHOLD(T_CPU, THRESHOLD_CPU_DOWN);
+ TEST_ASSERT(wait_set(&cpu_down, 11));
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_SHUTDOWN));
+
+ mock_temp[T_CPU] = 0;
+ usleep(SECOND);
+ cpu_down = 0;
+
+ mock_temp[T_CPU] = THRESHOLD(T_CPU, THRESHOLD_POWER_DOWN);
+ TEST_ASSERT(wait_set(&cpu_down, 11));
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_SHUTDOWN));
+
+ mock_temp[T_CPU] = 0;
+ cpu_down = 0;
+
+ return EC_SUCCESS;
+}
+
+static int test_sensor_failure(void)
+{
+ reset_mock_temp();
+
+ /* Failure due to sensor not powered should be ignored */
+ mock_temp[T_CPU] = -EC_ERROR_NOT_POWERED;
+ usleep(5 * SECOND);
+ TEST_ASSERT(!(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL)));
+
+ /* Other failure should be pumped up to host */
+ mock_temp[T_CPU] = -EC_ERROR_UNKNOWN;
+ usleep(5 * SECOND);
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL));
+
+ return EC_SUCCESS;
+}
+
+static int test_sensor_info(void)
+{
+ struct ec_params_temp_sensor_get_info params;
+ struct ec_response_temp_sensor_get_info resp;
+ int i;
+
+ for (i = 0; i < TEMP_SENSOR_COUNT; ++i) {
+ params.id = i;
+ TEST_ASSERT(test_send_host_command(
+ EC_CMD_TEMP_SENSOR_GET_INFO,
+ 0, &params, sizeof(params),
+ &resp, sizeof(resp)) == EC_RES_SUCCESS);
+ TEST_ASSERT_ARRAY_EQ(resp.sensor_name,
+ temp_sensors[i].name,
+ strlen(resp.sensor_name));
+ TEST_ASSERT(resp.sensor_type == temp_sensors[i].type);
+ }
+
+ params.id = TEMP_SENSOR_COUNT;
+ TEST_ASSERT(test_send_host_command(
+ EC_CMD_TEMP_SENSOR_GET_INFO,
+ 0, &params, sizeof(params),
+ &resp, sizeof(resp)) != EC_RES_SUCCESS);
+
+ return EC_SUCCESS;
+}
+
+static int set_threshold(int type, int threshold_id, int val)
+{
+ struct ec_params_thermal_set_threshold params;
+
+ params.sensor_type = type;
+ params.threshold_id = threshold_id;
+ params.value = val;
+
+ return test_send_host_command(EC_CMD_THERMAL_SET_THRESHOLD, 0, &params,
+ sizeof(params), NULL, 0);
+}
+
+static int get_threshold(int type, int threshold_id, int *val)
+{
+ struct ec_params_thermal_get_threshold params;
+ struct ec_response_thermal_get_threshold resp;
+ int rv;
+
+ params.sensor_type = type;
+ params.threshold_id = threshold_id;
+
+ rv = test_send_host_command(EC_CMD_THERMAL_GET_THRESHOLD, 0, &params,
+ sizeof(params), &resp, sizeof(resp));
+ if (rv != EC_RES_SUCCESS)
+ return rv;
+
+ *val = resp.value;
+ return EC_RES_SUCCESS;
+}
+
+static int verify_threshold(int type, int threshold_id, int val)
+{
+ int actual_val;
+
+ if (get_threshold(type, threshold_id, &actual_val) != EC_RES_SUCCESS)
+ return 0;
+ return val == actual_val;
+}
+
+static int test_threshold_hostcmd(void)
+{
+ reset_mock_temp();
+
+ /* Verify thresholds */
+ TEST_ASSERT(verify_threshold(T_CPU, THRESHOLD_WARNING,
+ THRESHOLD(T_CPU, THRESHOLD_WARNING)));
+ TEST_ASSERT(verify_threshold(T_BOARD, THRESHOLD_WARNING,
+ THRESHOLD(T_BOARD, THRESHOLD_WARNING)));
+ TEST_ASSERT(verify_threshold(T_CPU, THRESHOLD_CPU_DOWN,
+ THRESHOLD(T_CPU, THRESHOLD_CPU_DOWN)));
+
+ /* Lower CPU throttling threshold and trigger */
+ TEST_ASSERT(set_threshold(T_CPU, THRESHOLD_WARNING, 350) ==
+ EC_RES_SUCCESS);
+ mock_temp[T_CPU] = 355;
+ TEST_ASSERT(wait_set(&cpu_throttled, 11));
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_OVERLOAD));
+
+ /* Lower thermal shutdown threshold */
+ TEST_ASSERT(set_threshold(T_CPU, THRESHOLD_CPU_DOWN, 353) ==
+ EC_RES_SUCCESS);
+ TEST_ASSERT(wait_set(&cpu_down, 11));
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_SHUTDOWN));
+
+ /* Clear */
+ mock_temp[T_CPU] = 0;
+ TEST_ASSERT(wait_clear(&cpu_throttled, 2));
+ cpu_down = 0;
+
+ return EC_SUCCESS;
+}
+
+static int test_threshold_console_cmd(void)
+{
+ char buf[100];
+
+ reset_mock_temp();
+
+ /* Lower CPU threshold and trigger */
+ snprintf(buf, 100, "thermalconf %d %d 330\n", T_CPU, THRESHOLD_WARNING);
+ UART_INJECT(buf);
+ msleep(100);
+ mock_temp[T_CPU] = 335;
+ TEST_ASSERT(wait_set(&cpu_throttled, 11));
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_OVERLOAD));
+
+ /* Set first fan step to 280 K */
+ snprintf(buf, 100, "thermalfan %d 0 280\n", T_CPU);
+ UART_INJECT(buf);
+ msleep(100);
+ mock_temp[T_CPU] = 280;
+ TEST_ASSERT(wait_fan_rpm(fan_speed[1], 11));
+
+ return EC_SUCCESS;
+}
+
+static int test_invalid_hostcmd(void)
+{
+ int dummy;
+
+ TEST_ASSERT(set_threshold(TEMP_SENSOR_TYPE_COUNT, THRESHOLD_WARNING,
+ 100) != EC_RES_SUCCESS);
+ TEST_ASSERT(set_threshold(T_CPU, THRESHOLD_COUNT + THERMAL_FAN_STEPS,
+ 100) != EC_RES_SUCCESS);
+ TEST_ASSERT(get_threshold(TEMP_SENSOR_TYPE_COUNT, THRESHOLD_WARNING,
+ &dummy) != EC_RES_SUCCESS);
+ TEST_ASSERT(get_threshold(T_CPU, THRESHOLD_COUNT + THERMAL_FAN_STEPS,
+ &dummy) != EC_RES_SUCCESS);
+
+ return EC_SUCCESS;
+}
+
+static int test_auto_fan_ctrl(void)
+{
+ reset_mock_temp();
+
+ /* Disable fan control */
+ pwm_set_fan_rpm_mode(0);
+ thermal_control_fan(0);
+
+ /*
+ * Increase CPU temperature to first fan step and check the fan
+ * doesn't come up.
+ */
+ mock_temp[T_CPU] = FAN_THRESHOLD(T_CPU, 0);
+ TEST_ASSERT(!wait_fan_rpm(fan_speed[1], 11));
+
+ /* Enable fan control */
+ TEST_ASSERT(test_send_host_command(EC_CMD_THERMAL_AUTO_FAN_CTRL, 0,
+ NULL, 0, NULL, 0) == EC_RES_SUCCESS);
+ TEST_ASSERT(fan_rpm_mode == 1);
+ TEST_ASSERT(wait_fan_rpm(fan_speed[1], 11));
+
+ /* Disable fan control */
+ pwm_set_fan_rpm_mode(0);
+ thermal_control_fan(0);
+
+ /* Increase CPU temperature to second fan step */
+ mock_temp[T_CPU] = FAN_THRESHOLD(T_CPU, 1);
+ TEST_ASSERT(!wait_fan_rpm(fan_speed[2], 11));
+
+ /* Enable fan control by console command */
+ UART_INJECT("autofan\n");
+ msleep(100);
+ TEST_ASSERT(fan_rpm_mode == 1);
+ TEST_ASSERT(wait_fan_rpm(fan_speed[2], 11));
+
+
+ return EC_SUCCESS;
+}
+
+static int test_throttling(void)
+{
+ /* Use ADAPTER_UNKNOWN, Turbo off, softer limits */
+ /* CAUTION: assuming lim->hi_cnt > 2 */
+ struct adapter_limits *lim = &ad_limits[0][0][0];
+ int longtime = MAX(lim->lo_cnt, lim->hi_cnt) + 2;
+
+ reset_mock_temp();
+ mock_id = 0; /* adapter unknown */
+ mock_ac = 1;
+
+ /* Lower temperature. CPU not throttled anymore. */
+ mock_temp[T_CPU] = THRESHOLD(T_CPU, THRESHOLD_WARNING) - 5;
+ TEST_ASSERT(wait_clear(&cpu_throttled, 2));
+
+
+ /* NOTE: ap_is_throttled indicates extpower_falco.c's view of
+ * throttling. cpu_throttled indicates CPU's view. */
+
+ /* above high limit for not quite long enough */
+ mock_current = lim->hi_val + 1;
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (lim->hi_cnt - 1));
+ TEST_ASSERT(lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+
+ /* drop below the high limit once */
+ mock_current = lim->hi_val - 1;
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+
+ /* now back up - that should have reset the count */
+ mock_current = lim->hi_val + 1;
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (lim->hi_cnt - 1));
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(cpu_throttled == 0);
+
+ /* one more ought to do it */
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled);
+
+ /* going midrange for a long time shouldn't change anything */
+ mock_current = (lim->lo_val + lim->hi_val) / 2;
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * longtime);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled);
+
+ /* below low limit for not quite long enough */
+ mock_current = lim->lo_val - 1;
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (lim->lo_cnt - 1));
+ TEST_ASSERT(lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled);
+
+ /* back above the low limit once */
+ mock_current = lim->lo_val + 1;
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+
+ TEST_ASSERT(lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled);
+
+ /* now back down - that should have reset the count */
+ mock_current = lim->lo_val - 1;
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * (lim->lo_cnt - 1));
+ TEST_ASSERT(lim->triggered == 1);
+ TEST_ASSERT(ap_is_throttled);
+ TEST_ASSERT(cpu_throttled);
+
+ /* Trigger CPU throttling */
+ mock_temp[T_CPU] = THRESHOLD(T_CPU, THRESHOLD_WARNING);
+ TEST_ASSERT(wait_set(&cpu_throttled, 11));
+ TEST_ASSERT(host_get_events() &
+ EC_HOST_EVENT_MASK(EC_HOST_EVENT_THERMAL_OVERLOAD));
+
+ /* One more ought to do it for the power */
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * 1);
+ TEST_ASSERT(lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(cpu_throttled); /* but CPU is still throttled */
+
+ /* Waiting doesn't help */
+ usleep(EXTPOWER_FALCO_POLL_PERIOD * longtime);
+ TEST_ASSERT(lim->triggered == 0);
+ TEST_ASSERT(ap_is_throttled == 0);
+ TEST_ASSERT(cpu_throttled);
+
+ /* Lower temperature. CPU not throttled anymore. */
+ mock_temp[T_CPU] = THRESHOLD(T_CPU, THRESHOLD_WARNING) - 5;
+ TEST_ASSERT(wait_clear(&cpu_throttled, 2));
+
+ return EC_SUCCESS;
+}
+
+
+
+static int check_assumption(void)
+{
+ TEST_ASSERT((int)TEMP_SENSOR_CPU == (int)TEMP_SENSOR_TYPE_CPU);
+ TEST_ASSERT((int)TEMP_SENSOR_BOARD == (int)TEMP_SENSOR_TYPE_BOARD);
+ TEST_ASSERT((int)TEMP_SENSOR_CASE == (int)TEMP_SENSOR_TYPE_CASE);
+
+ TEST_ASSERT(temp_sensors[T_CPU].action_delay_sec != 0);
+
+ TEST_ASSERT(thermal_config[T_CPU].config_flags &
+ THERMAL_CONFIG_WARNING_ON_FAIL);
+
+ return EC_SUCCESS;
+}
+
+void run_test(void)
+{
+ test_reset();
+ test_chipset_on();
+
+ /* Test assumptions */
+ RUN_TEST(check_assumption);
+
+ RUN_TEST(test_init_val);
+ RUN_TEST(test_cpu_fan);
+ /* No tests for board and case temp sensors as they are ignored. */
+ RUN_TEST(test_safety);
+ RUN_TEST(test_sensor_failure);
+ RUN_TEST(test_auto_fan_ctrl);
+ RUN_TEST(test_sensor_info);
+ RUN_TEST(test_threshold_hostcmd);
+ RUN_TEST(test_invalid_hostcmd);
+ RUN_TEST(test_threshold_console_cmd);
+
+ /* See if thermal and charger will cooperate on throttling */
+ RUN_TEST(test_throttling);
+
+ test_print_result();
+}
diff --git a/test/thermal_falco.tasklist b/test/thermal_falco.tasklist
new file mode 100644
index 0000000000..1be940d079
--- /dev/null
+++ b/test/thermal_falco.tasklist
@@ -0,0 +1,20 @@
+/* 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(THERMAL, thermal_task, NULL, TASK_STACK_SIZE) \
+ 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..e5cfa16b7d
--- /dev/null
+++ b/test/thermal_falco_externs.h
@@ -0,0 +1,14 @@
+/* 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;
+
+#endif /* __THERMAL_FALCO_EXTERNS_H */