summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 */