| /* 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 GPIO extpower module. |
| */ |
| |
| #include "adc.h" |
| #include "chipset.h" |
| #include "common.h" |
| #include "console.h" |
| #include "extpower.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "test_util.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "power.h" |
| |
| /* Normally private stuff from the modules we're going to test */ |
| #include "adapter_externs.h" |
| |
| /* Local state */ |
| static int mock_id; |
| static int mock_current; |
| static struct charge_state_context ctx; |
| |
| /* Mocked functions from the rest of the EC */ |
| |
| 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; |
| } |
| |
| int charger_set_input_current(int input_current) |
| { |
| return EC_SUCCESS; |
| } |
| |
| int charger_get_option(int *option) |
| { |
| return EC_SUCCESS; |
| } |
| |
| |
| int charger_set_option(int option) |
| { |
| return EC_SUCCESS; |
| } |
| |
| void chipset_throttle_cpu(int throttle) |
| { |
| /* PROCHOT, ugh. */ |
| } |
| |
| /* Local functions to control the mocked functions. */ |
| |
| static void change_ac(int val) |
| { |
| gpio_set_level(GPIO_AC_PRESENT, val); |
| msleep(50); |
| } |
| |
| static void set_id(int val) |
| { |
| mock_id = val; |
| } |
| |
| static void test_reset_mocks(void) |
| { |
| change_ac(0); |
| set_id(0); |
| mock_current = 0; |
| memset(&ctx, 0, sizeof(ctx)); |
| } |
| |
| /* Specify as discharge current */ |
| static void mock_batt(int cur) |
| { |
| ctx.curr.batt.current = -cur; |
| } |
| |
| /* And the tests themselves... */ |
| |
| /* |
| * Run through the known ID ranges, making sure that values inside are |
| * correctly identified, and values outside are not. We'll skip the default |
| * ADAPTER_UNKNOWN range, of course. |
| * |
| * NOTE: This assumes that the ranges have a gap between them. |
| */ |
| static int test_identification(void) |
| { |
| int i; |
| |
| test_reset_mocks(); |
| |
| for (i = 1; i < NUM_ADAPTER_TYPES; i++) { |
| |
| change_ac(0); |
| TEST_ASSERT(ac_adapter == ADAPTER_UNKNOWN); |
| |
| set_id(ad_id_vals[i].lo - 1); |
| change_ac(1); |
| TEST_ASSERT(ac_adapter == ADAPTER_UNKNOWN); |
| |
| change_ac(0); |
| TEST_ASSERT(ac_adapter == ADAPTER_UNKNOWN); |
| |
| set_id(ad_id_vals[i].lo); |
| change_ac(1); |
| TEST_ASSERT(ac_adapter == i); |
| |
| change_ac(0); |
| TEST_ASSERT(ac_adapter == ADAPTER_UNKNOWN); |
| |
| set_id(ad_id_vals[i].hi); |
| change_ac(1); |
| TEST_ASSERT(ac_adapter == i); |
| |
| change_ac(0); |
| TEST_ASSERT(ac_adapter == ADAPTER_UNKNOWN); |
| |
| set_id(ad_id_vals[i].hi + 1); |
| change_ac(1); |
| TEST_ASSERT(ac_adapter == ADAPTER_UNKNOWN); |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /* Helper function */ |
| static void test_turbo_init(void) |
| { |
| /* Battery is awake and in good shape */ |
| ctx.curr.error = 0; |
| ctx.curr.batt.state_of_charge = 25; |
| |
| /* Adapter is present and known */ |
| set_id(ad_id_vals[1].lo + 1); |
| change_ac(1); |
| } |
| |
| /* Test all the things that can turn turbo mode on and off */ |
| static int test_turbo(void) |
| { |
| test_reset_mocks(); |
| |
| /* There's only one path that can enable turbo. Check it first. */ |
| test_turbo_init(); |
| watch_adapter_closely(&ctx); |
| TEST_ASSERT(ac_turbo == 1); |
| |
| /* Now test things that turn turbo off. */ |
| |
| test_turbo_init(); |
| ctx.curr.error = 1; |
| watch_adapter_closely(&ctx); |
| TEST_ASSERT(ac_turbo == 0); |
| |
| test_turbo_init(); |
| ctx.curr.batt.state_of_charge = 5; |
| watch_adapter_closely(&ctx); |
| TEST_ASSERT(ac_turbo == 0); |
| |
| test_turbo_init(); |
| change_ac(0); |
| set_id(ad_id_vals[1].lo - 1); |
| change_ac(1); |
| watch_adapter_closely(&ctx); |
| TEST_ASSERT(ac_turbo == 0); |
| |
| test_turbo_init(); |
| change_ac(0); |
| watch_adapter_closely(&ctx); |
| TEST_ASSERT(ac_turbo == -1); |
| |
| return EC_SUCCESS; |
| } |
| |
| /* Check the detection logic on one set of struct adapter_limits */ |
| static int test_thresholds_sequence(int entry) |
| { |
| struct adapter_limits *lim = &ad_limits[ac_adapter][ac_turbo][entry]; |
| int longtime = MAX(lim->lo_cnt, lim->hi_cnt) + 2; |
| int i; |
| |
| /* reset, by staying low for a long time */ |
| mock_current = lim->lo_val - 1; |
| for (i = 1; i < longtime; i++) |
| check_threshold(mock_current, lim); |
| TEST_ASSERT(lim->triggered == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* midrange for a long time shouldn't do 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 == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* above high limit for not quite long enough */ |
| mock_current = lim->hi_val + 1; |
| for (i = 1; i < lim->hi_cnt; i++) |
| check_threshold(mock_current, lim); |
| TEST_ASSERT(lim->triggered == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* drop below the high limit once */ |
| mock_current = lim->hi_val - 1; |
| check_threshold(mock_current, lim); |
| TEST_ASSERT(lim->triggered == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* now back up - that should have reset the count */ |
| mock_current = lim->hi_val + 1; |
| for (i = 1; i < lim->hi_cnt; i++) |
| check_threshold(mock_current, lim); |
| TEST_ASSERT(lim->triggered == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* one more ought to do it */ |
| check_threshold(mock_current, lim); |
| TEST_ASSERT(lim->triggered == 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); |
| |
| /* 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); |
| |
| /* 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); |
| |
| /* 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); |
| |
| /* One more ought to do it */ |
| check_threshold(mock_current, lim); |
| TEST_ASSERT(lim->triggered == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| return EC_SUCCESS; |
| } |
| |
| /* |
| * Check all sets of thresholds. This probably doesn't add much value, but at |
| * least it ensures that they're somewhat sane. |
| */ |
| static int test_thresholds(void) |
| { |
| int e; |
| |
| for (ac_adapter = 0; ac_adapter < NUM_ADAPTER_TYPES; ac_adapter++) |
| for (ac_turbo = 0; ac_turbo < NUM_AC_TURBO_STATES; ac_turbo++) |
| for (e = 0; e < NUM_AC_THRESHOLDS; e++) |
| TEST_ASSERT(EC_SUCCESS == |
| test_thresholds_sequence(e)); |
| |
| return EC_SUCCESS; |
| } |
| |
| static int test_batt(void) |
| { |
| struct adapter_limits *l, *h; |
| int longtime; |
| int i; |
| |
| /* 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 */ |
| if (batt_limits[0].hi_val > batt_limits[1].hi_val) { |
| h = &batt_limits[0]; |
| l = &batt_limits[1]; |
| } else { |
| h = &batt_limits[1]; |
| 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; |
| |
| test_reset_mocks(); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* reset, by staying low for a long time */ |
| for (i = 1; i < longtime; i++) |
| watch_battery_closely(&ctx); |
| 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(-(h->hi_val + 2)); |
| for (i = 1; i < longtime; i++) |
| watch_battery_closely(&ctx); |
| 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); |
| for (i = 1; i < longtime; i++) |
| watch_battery_closely(&ctx); |
| 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); |
| for (i = 1; i < l->hi_cnt; i++) |
| watch_battery_closely(&ctx); |
| 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); |
| watch_battery_closely(&ctx); |
| 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); |
| for (i = 1; i < l->hi_cnt; i++) |
| watch_battery_closely(&ctx); |
| TEST_ASSERT(l->count != 0); |
| TEST_ASSERT(l->triggered == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* one more ought to do it */ |
| watch_battery_closely(&ctx); |
| 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); |
| for (i = 1; i < longtime; i++) |
| watch_battery_closely(&ctx); |
| TEST_ASSERT(l->triggered == 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); |
| |
| /* 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); |
| |
| /* 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); |
| |
| /* One more ought to do it */ |
| watch_battery_closely(&ctx); |
| TEST_ASSERT(l->triggered == 0); |
| TEST_ASSERT(ap_is_throttled == 0); |
| |
| /* Check the high limits too, just for fun */ |
| mock_batt(h->hi_val + 1); |
| for (i = 1; i < h->hi_cnt; i++) |
| watch_battery_closely(&ctx); |
| TEST_ASSERT(h->triggered == 0); |
| /* one more */ |
| watch_battery_closely(&ctx); |
| TEST_ASSERT(h->triggered == 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(); |
| } |