summaryrefslogtreecommitdiff
path: root/zephyr/test/drivers/chargesplash/src/chargesplash.c
blob: 1b89262ce4e62a81c279c16038026801be104af9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/* Copyright 2022 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <zephyr/kernel.h>
#include <stdbool.h>
#include <zephyr/drivers/gpio/gpio_emul.h>
#include <zephyr/shell/shell.h>
#include <zephyr/shell/shell_uart.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/ztest.h>

#include "chipset.h"
#include "config.h"
#include "ec_commands.h"
#include "extpower.h"
#include "hooks.h"
#include "host_command.h"
#include "lid_switch.h"
#include "timer.h"
#include "test/drivers/test_state.h"
#include "test/drivers/utils.h"

/* Do a chargesplash host cmd */
static enum ec_status
chargesplash_hostcmd(enum ec_chargesplash_cmd cmd,
		     struct ec_response_chargesplash *response)
{
	struct ec_params_chargesplash params = { .cmd = cmd };
	struct host_cmd_handler_args args =
		BUILD_HOST_COMMAND(EC_CMD_CHARGESPLASH, 0, *response, params);

	return host_command_process(&args);
}

static bool is_chargesplash_requested(void)
{
	struct ec_response_chargesplash response;

	zassert_ok(chargesplash_hostcmd(EC_CHARGESPLASH_GET_STATE, &response),
		   NULL);

	return response.requested;
}

static struct k_poll_signal s0_signal = K_POLL_SIGNAL_INITIALIZER(s0_signal);
static struct k_poll_event s0_event = K_POLL_EVENT_INITIALIZER(
	K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &s0_signal);

static void handle_chipset_s0_event(void)
{
	k_poll_signal_raise(&s0_signal, 0);
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, handle_chipset_s0_event, HOOK_PRIO_LAST);

static void wait_for_chipset_startup(void)
{
	if (!chipset_in_state(CHIPSET_STATE_ON)) {
		k_poll_signal_reset(&s0_signal);
		k_poll(&s0_event, 1, K_FOREVER);
	}

	/*
	 * TODO(b/230362548): We need to give the EC a bit to "calm down"
	 * after reaching S0.
	 */
	msleep(2000);
}

#define GPIO_LID_OPEN_EC_NODE DT_NODELABEL(gpio_lid_open_ec)
#define GPIO_LID_OPEN_EC_CTLR DT_GPIO_CTLR(GPIO_LID_OPEN_EC_NODE, gpios)
#define GPIO_LID_OPEN_EC_PORT DT_GPIO_PIN(GPIO_LID_OPEN_EC_NODE, gpios)

static void set_lid(bool open, bool inhibit_boot)
{
	const struct device *lid_switch_dev =
		DEVICE_DT_GET(GPIO_LID_OPEN_EC_CTLR);

	__ASSERT(lid_is_open() != open,
		 "Lid change was requested, but it's already in that state");

	if (!open) {
		__ASSERT(!inhibit_boot,
			 "inhibit_boot should not be used with a lid close");
	}

	zassume_ok(gpio_emul_input_set(lid_switch_dev, GPIO_LID_OPEN_EC_PORT,
				       open),
		   "Failed to set lid switch GPIO");

	while (lid_is_open() != open) {
		usleep(LID_DEBOUNCE_US + 1);
	}

	if (inhibit_boot) {
		wait_for_chipset_startup();
		test_set_chipset_to_g3();
	}
}

/* Simulate a regular power button press */
static void pulse_power_button(void)
{
	zassert_ok(shell_execute_cmd(get_ec_shell(), "powerbtn"), NULL);
}

static void reset_state(void *unused)
{
	test_set_chipset_to_g3();

	/*
	 * Prevent critical low battery from moving us back to G3 when
	 * lid is opened.
	 */
	test_set_battery_level(75);

	if (lid_is_open()) {
		set_lid(false, false);
	}

	if (extpower_is_present()) {
		set_ac_enabled(false);
	}

	zassume_ok(shell_execute_cmd(get_ec_shell(), "chargesplash reset"),
		   "'chargesplash reset' shell command failed");
}

ZTEST_SUITE(chargesplash, drivers_predicate_post_main, NULL, reset_state, NULL,
	    reset_state);

/*
 * When the lid is open and AC is connected, the chargesplash should
 * be requested.
 */
ZTEST_USER(chargesplash, test_connect_ac)
{
	set_lid(true, true);

	set_ac_enabled(true);
	zassert_true(is_chargesplash_requested(),
		     "chargesplash should be requested");
	wait_for_chipset_startup();
}

/*
 * When AC is not connected and we open the lid, the chargesplash
 * should not be requested.
 */
ZTEST_USER(chargesplash, test_no_connect_ac)
{
	set_lid(true, false);
	zassert_false(is_chargesplash_requested(),
		      "chargesplash should not be requested");
	wait_for_chipset_startup();
}

/*
 * When we connect AC with the lid closed, the chargesplash should not
 * be requested.
 */
ZTEST_USER(chargesplash, test_ac_connect_when_lid_closed)
{
	set_ac_enabled(true);
	zassert_false(is_chargesplash_requested(),
		      "chargesplash should not be requested");
}

/*
 * Test that, after many repeated requests, the chargesplash
 * feature becomes locked and non-functional.  This condition
 * replicates a damaged charger or port which cannot maintain a
 * reliable connection.
 *
 * Then, ensure the lockout clears after the chargesplash period
 * passes.
 */
ZTEST_USER(chargesplash, test_lockout)
{
	int i;

	set_lid(true, true);

	for (i = 0; i < CONFIG_CHARGESPLASH_MAX_REQUESTS_PER_PERIOD; i++) {
		set_ac_enabled(true);

		zassert_true(is_chargesplash_requested(),
			     "chargesplash should be requested");
		wait_for_chipset_startup();

		set_ac_enabled(false);
		test_set_chipset_to_g3();
	}

	set_ac_enabled(true);
	zassert_false(is_chargesplash_requested(),
		      "chargesplash should be locked out");
	set_ac_enabled(false);

	sleep(CONFIG_CHARGESPLASH_PERIOD);

	set_ac_enabled(true);
	zassert_true(is_chargesplash_requested(),
		     "lockout should have cleared");
	wait_for_chipset_startup();
}

/* Test cancel chargesplash request by power button push */
ZTEST_USER(chargesplash, test_power_button)
{
	set_lid(true, true);

	set_ac_enabled(true);
	zassert_true(is_chargesplash_requested(),
		     "chargesplash should be requested");
	wait_for_chipset_startup();
	zassert_true(is_chargesplash_requested(),
		     "chargesplash should still be requested");

	pulse_power_button();
	zassert_false(is_chargesplash_requested(),
		      "chargesplash should be canceled by power button push");
	zassert_true(chipset_in_state(CHIPSET_STATE_ON),
		     "chipset should be on");
}

/* Manually lockout the feature via the shell */
ZTEST_USER(chargesplash, test_manual_lockout_via_console)
{
	/*
	 * Put an entry in the request log so the lockout has
	 * something to wait on to clear.
	 */
	zassert_ok(shell_execute_cmd(get_ec_shell(), "chargesplash request"),
		   NULL);
	zassert_true(is_chargesplash_requested(),
		     "chargesplash should be requested");
	wait_for_chipset_startup();
	test_set_chipset_to_g3();

	zassert_ok(shell_execute_cmd(get_ec_shell(), "chargesplash lockout"),
		   NULL);
	zassert_ok(shell_execute_cmd(get_ec_shell(), "chargesplash request"),
		   NULL);
	zassert_false(is_chargesplash_requested(),
		      "chargesplash should be not requested due to lockout");

	sleep(CONFIG_CHARGESPLASH_PERIOD);

	zassert_ok(shell_execute_cmd(get_ec_shell(), "chargesplash request"),
		   NULL);
	zassert_true(is_chargesplash_requested(),
		     "lockout should have cleared");
	wait_for_chipset_startup();
}

/* Manually lockout the feature via host command */
ZTEST_USER(chargesplash, test_manual_lockout_via_hostcmd)
{
	struct ec_response_chargesplash response;

	zassert_ok(chargesplash_hostcmd(EC_CHARGESPLASH_REQUEST, &response),
		   NULL);
	zassert_true(is_chargesplash_requested(),
		     "chargesplash should be requested");
	wait_for_chipset_startup();
	test_set_chipset_to_g3();

	zassert_ok(chargesplash_hostcmd(EC_CHARGESPLASH_LOCKOUT, &response),
		   NULL);
	zassert_ok(chargesplash_hostcmd(EC_CHARGESPLASH_REQUEST, &response),
		   NULL);
	zassert_false(is_chargesplash_requested(),
		      "chargesplash should be not requested due to lockout");

	sleep(CONFIG_CHARGESPLASH_PERIOD);

	zassert_ok(chargesplash_hostcmd(EC_CHARGESPLASH_REQUEST, &response),
		   NULL);
	zassert_true(is_chargesplash_requested(),
		     "lockout should have cleared");
	wait_for_chipset_startup();
}

/* Simulate an actual run of the display loop */
ZTEST_USER(chargesplash, test_display_loop)
{
	struct ec_response_chargesplash response;

	set_lid(true, true);
	set_ac_enabled(true);
	zassert_true(is_chargesplash_requested(), NULL);
	wait_for_chipset_startup();

	zassert_ok(chargesplash_hostcmd(EC_CHARGESPLASH_DISPLAY_READY,
					&response),
		   NULL);

	zassert_true(is_chargesplash_requested(), NULL);
	pulse_power_button();
	zassert_false(is_chargesplash_requested(), NULL);
}