summaryrefslogtreecommitdiff
path: root/baseboard/honeybuns/baseboard.c
blob: 506eb6f26518f48dd166aa35d6c002b097eac9cd (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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
/* Copyright 2020 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* Honeybuns family-specific configuration */
#include "console.h"
#include "cros_board_info.h"
#include "driver/mp4245.h"
#include "gpio.h"
#include "hooks.h"
#include "i2c.h"
#include "usb_pd.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "usbc_ppc.h"
#include "driver/tcpm/tcpm.h"
#include "util.h"

#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ##args)
#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ##args)

#define POWER_BUTTON_SHORT_USEC (300 * MSEC)
#define POWER_BUTTON_LONG_USEC (5000 * MSEC)
#define POWER_BUTTON_DEBOUNCE_USEC (30)

#define BUTTON_EVT_CHANGE BIT(0)
#define BUTTON_EVT_INFO BIT(1)

enum power { POWER_OFF, POWER_ON };

enum button {
	BUTTON_RELEASE,
	BUTTON_PRESS,
	BUTTON_PRESS_POWER_ON,
	BUTTON_PRESS_SHORT,
	BUTTON_PRESS_LONG,
};

#define LED_ON_OFF_BIT BIT(0)
#define LED_COLOR_BIT BIT(2)
#define LED_FLASH_SEQ_LENGTH 8

enum led_color {
	GREEN,
	YELLOW,
	OFF,
};

static enum power dock_state;
#ifdef SECTION_IS_RW
static int button_level;
static int button_level_pending;
static int dock_mf;
static int led_count;
#endif

/******************************************************************************/

__maybe_unused static void board_power_sequence(int enable)
{
	int i;

	if (enable) {
		for (i = 0; i < board_power_seq_count; i++) {
			gpio_set_level(board_power_seq[i].signal,
				       board_power_seq[i].level);
			CPRINTS("power seq: rail = %d", i);
			if (board_power_seq[i].delay_ms)
				msleep(board_power_seq[i].delay_ms);
		}
	} else {
		for (i = board_power_seq_count - 1; i >= 0; i--) {
			gpio_set_level(board_power_seq[i].signal,
				       !board_power_seq[i].level);
			CPRINTS("sequence[%d]: level = %d", i,
				!board_power_seq[i].level);
		}
	}

	dock_state = enable;
	CPRINTS("board: Power rails %s", dock_state ? "on" : "off");
}

/******************************************************************************/
/* I2C port map configuration */
const struct i2c_port_t i2c_ports[] = {
	{ .name = "i2c1",
	  .port = I2C_PORT_I2C1,
	  .kbps = 400,
	  .scl = GPIO_EC_I2C1_SCL,
	  .sda = GPIO_EC_I2C1_SDA },
	{ .name = "i2c3",
	  .port = I2C_PORT_I2C3,
	  .kbps = 400,
	  .scl = GPIO_EC_I2C3_SCL,
	  .sda = GPIO_EC_I2C3_SDA },
};
const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports);

#ifdef SECTION_IS_RW
static void baseboard_set_led(enum led_color color)
{
	/*
	 * TODO(b/164157329): The power button feature should be connected to a
	 * 2 color LED which is part of the button. Currently, the power button
	 * LED is a single color LED which is controlled by on the of the power
	 * rails. Using the status LED now to demonstrate the LED behavior
	 * associated with a power button press.
	 */
	CPRINTS("led: color = %d", color);

	/* Not all boards may have LEDs under EC control */
#if defined(GPIO_PWR_BUTTON_RED) && defined(GPIO_PWR_BUTTON_GREEN)
	if (color == OFF) {
		gpio_set_level(GPIO_PWR_BUTTON_RED, 1);
		gpio_set_level(GPIO_PWR_BUTTON_GREEN, 1);
	} else if (color == GREEN) {
		gpio_set_level(GPIO_PWR_BUTTON_RED, 1);
		gpio_set_level(GPIO_PWR_BUTTON_GREEN, 0);
	} else if (color == YELLOW) {
		gpio_set_level(GPIO_PWR_BUTTON_RED, 0);
		gpio_set_level(GPIO_PWR_BUTTON_GREEN, 0);
	}
#endif
}

static void baseboard_led_callback(void);
DECLARE_DEFERRED(baseboard_led_callback);

static void baseboard_led_callback(void)
{
	/*
	 * Flash LED on transition using a simple 3 bit counter. Bit 0 controls
	 * LED on/off and bit 2 controls which color to set during the on phase.
	 */
	int color = led_count & LED_COLOR_BIT ? dock_mf : dock_mf ^ 1;

	/*
	 * TODO(b/164157329): This function implements a simple flashing
	 * transition when the MF preference bit is changed via a long power
	 * button press sequence. This might need to move to the board function
	 * if not required/desired on all variants.
	 */

	if (led_count & LED_ON_OFF_BIT)
		baseboard_set_led(color);
	else
		baseboard_set_led(OFF);

	/* Flash sequence is 8 steps */
	if (++led_count < LED_FLASH_SEQ_LENGTH)
		hook_call_deferred(&baseboard_led_callback_data, 150 * MSEC);
}

static void baseboard_change_mf_led(void)
{
	led_count = 0;
	baseboard_led_callback();
}

void baseboard_set_mst_lane_control(int mf)
{
	/*
	 * The parameter mf reflects the desired lane control value. If the
	 * current value does not match the desired, then the MST hub must first
	 * be put into reset, so the MST hub will latch in the correct value
	 * when it's taken out of reset.
	 */
	if (mf != gpio_get_level(GPIO_MST_HUB_LANE_SWITCH)) {
		/* put MST into reset */
		gpio_set_level(GPIO_MST_RST_L, 0);
		msleep(1);
		gpio_set_level(GPIO_MST_HUB_LANE_SWITCH, mf);
		CPRINTS("MST: lane control = %s", mf ? "high" : "low");
		msleep(1);
		/* lane control is set, take MST out of reset */
		gpio_set_level(GPIO_MST_RST_L, 1);
	}
}

static void baseboard_enable_mp4245(void)
{
	int mv;
	int ma;

	mp4245_set_voltage_out(5000);
	mp4245_votlage_out_enable(1);
	msleep(MP4245_VOUT_5V_DELAY_MS);
	mp3245_get_vbus(&mv, &ma);
	CPRINTS("mp4245: vout @ %d mV enabled", mv);
}

#endif /* SECTION_IS_RW */

static void baseboard_init(void)
{
#ifdef SECTION_IS_RW
	uint32_t fw_config;
#endif

	/* Turn on power rails */
	board_power_sequence(1);
	CPRINTS("board: Power rails enabled");

#ifdef SECTION_IS_RW
	/* Force TC state machine to start in TC_ERROR_RECOVERY */
	system_clear_reset_flags(EC_RESET_FLAG_POWER_ON);
	/* Make certain SN5S330 PPC does full initialization */
	system_set_reset_flags(EC_RESET_FLAG_EFS);

	/*
	 * Dock multi function (mf) preference is stored in bit 0 of fw_config
	 * field of the CBI. If this value is programmed, then make sure the
	 * MST_LANE_CONTROL gpio matches the mf bit.
	 */
	if (!cbi_get_fw_config(&fw_config)) {
		dock_mf = CBI_FW_MF_PREFERENCE(fw_config);
		baseboard_set_mst_lane_control(dock_mf);
	} else {
		dock_mf = dock_get_mf_preference();
		cbi_set_fw_config(dock_mf);
		CPRINTS("cbi: setting default result = %s",
			cbi_get_fw_config(&fw_config) ? "pass" : "fail");
	}

#ifdef GPIO_USBC_UF_ATTACHED_SRC
	/* Configure UF usbc ppc and check usbc state */
	baseboard_config_usbc_usb3_ppc();
#endif /* GPIO_USBC_UF_ATTACHED_SRC */

	/* Enable power button interrupt */
	gpio_enable_interrupt(GPIO_PWR_BTN);
	/* Set dock mf preference LED */
	baseboard_set_led(dock_mf);
	/* Setup VBUS to default value */
	baseboard_enable_mp4245();

#else
	/* Set up host port usbc to present Rd on CC lines */
	if (baseboard_usbc_init(USB_PD_PORT_HOST))
		CPRINTS("usbc: Failed to set up sink path");
	else
		CPRINTS("usbc: sink path configure success!");
#endif /* SECTION_IS_RW */
}
/*
 * Power sequencing must run before any other chip init is attempted, so run
 * power sequencing as soon as I2C bus is initialized.
 */
DECLARE_HOOK(HOOK_INIT, baseboard_init, HOOK_PRIO_INIT_I2C + 1);

#ifdef SECTION_IS_RW
static void baseboard_power_on(void)
{
	int port_max = board_get_usb_pd_port_count();
	int port;

	CPRINTS("pwrbtn: power on: mf = %d", dock_mf);
	/* Adjust system flags to full PPC init occurs */
	system_clear_reset_flags(EC_RESET_FLAG_POWER_ON);
	system_set_reset_flags(EC_RESET_FLAG_EFS);
	/* Enable power rails and release reset signals */
	board_power_sequence(1);
	/* Set VBUS to 5V and enable output from mp4245 */
	baseboard_enable_mp4245();
	/* Set dock mf preference LED */
	baseboard_set_led(dock_mf);
	/*
	 * Lane control (realtek MST) must be set prior to releasing MST
	 * reset.
	 */
	baseboard_set_mst_lane_control(dock_mf);
	/*
	 * When the power to the PPC is turned off, then back on, the PPC will
	 * default into dead battery mode. Dead battery resistors are disabled
	 * as part of the full PPC intializaiton sequence. This is required to
	 * force a detach event with port parter which can be attached as usbc
	 * source when honeybuns power rails are off.
	 */
	for (port = 0; port < port_max; port++) {
		ppc_init(port);
		msleep(1000);
		/* Inform TC state machine that it can resume */
		pd_set_suspend(port, 0);
	}
	/* Enable usbc interrupts */
	board_enable_usbc_interrupts();

#ifdef GPIO_USBC_UF_ATTACHED_SRC
	baseboard_config_usbc_usb3_ppc();
#endif
}

static void baseboard_power_off(void)
{
	int port_max = board_get_usb_pd_port_count();
	int port;

	CPRINTS("pwrbtn: power off");
	/* Put ports in TC suspend state */
	for (port = 0; port < port_max; port++)
		pd_set_suspend(port, 1);

	/* Disable ucpd peripheral (prevents interrupts) */
	tcpm_release(USB_PD_PORT_HOST);
	/* Disable PPC/TCPC interrupts */
	board_disable_usbc_interrupts();

#ifdef GPIO_USBC_UF_ATTACHED_SRC
	/* Disable PPC interrupts for PS8803 managed port */
	baseboard_usbc_usb3_enable_interrupts(0);
#endif
	/* Set dock power button/MF preference LED */
	baseboard_set_led(OFF);
	/* Go into power off state */
	board_power_sequence(0);
}

static void baseboard_toggle_mf(void)
{
	uint32_t fw_config;

	if (!cbi_get_fw_config(&fw_config)) {
		/* Update the user MF preference stored in CBI */
		fw_config ^= CBI_FW_MF_MASK;
		cbi_set_fw_config(fw_config);
		/* Update variable used to track user MF preference */
		dock_mf = CBI_FW_MF_PREFERENCE(fw_config);
		/* Flash led for visual indication of user MF change */
		baseboard_change_mf_led();

		/*
		 * Suspend, then release host port to force new MF setting to
		 * take effect.
		 */
		pd_set_suspend(USB_PD_PORT_HOST, 1);
		msleep(250);
		pd_set_suspend(USB_PD_PORT_HOST, 0);
	}
}

/*
 * Main task entry point for UCPD task
 */
void power_button_task(void *u)
{
	int timer_us = POWER_BUTTON_DEBOUNCE_USEC * 4;
	enum button state = BUTTON_RELEASE;
	uint32_t evt;

	/*
	 * Capture current button level in case it's being pressed when the dock
	 * is powered on. Note timer_us is initialized for debounce time to
	 * double check.
	 */
	button_level = gpio_get_level(GPIO_PWR_BTN);

	while (1) {
		evt = task_wait_event(timer_us);
		timer_us = -1;

		if (evt == BUTTON_EVT_INFO) {
			/* Only used for console command for debug */
			CPRINTS("pwrbtn: pwr = %d, state = %d, level = %d",
				dock_state, state, button_level);
			continue;
		}

		switch (state) {
		case BUTTON_RELEASE:
			/*
			 * Default wait state: Only need to check if the button
			 * is pressed and start the short press timer.
			 */
			if (evt & BUTTON_EVT_CHANGE &&
			    button_level == BUTTON_PRESSED_LEVEL) {
				state = BUTTON_PRESS;
				timer_us = (POWER_BUTTON_SHORT_USEC -
					    POWER_BUTTON_DEBOUNCE_USEC);
			}
			break;
		case BUTTON_PRESS:
			/*
			 * Validate short press by ensuring that button is still
			 * pressed after short press timer expires.
			 */
			if (evt & BUTTON_EVT_CHANGE &&
			    button_level == BUTTON_RELEASED_LEVEL) {
				state = BUTTON_RELEASE;
			} else {
				/* Start long press timer */
				timer_us = POWER_BUTTON_LONG_USEC -
					   POWER_BUTTON_SHORT_USEC;
				/*
				 * If dock is currently off, then change to the
				 * power on state. If dock is already on, then
				 * advance to short press state.
				 */
				if (dock_state == POWER_OFF) {
					baseboard_power_on();
					state = BUTTON_PRESS_POWER_ON;
				} else {
					state = BUTTON_PRESS_SHORT;
				}
			}
			break;
		case BUTTON_PRESS_POWER_ON:
			/*
			 * Short press recognized and dock was just powered
			 * on. If button is no longer pressed, then just return
			 * to the default state. Else, button is still pressed
			 * after long press timer has expired.
			 */
			if (evt & BUTTON_EVT_CHANGE &&
			    button_level == BUTTON_RELEASED_LEVEL) {
				state = BUTTON_RELEASE;
			} else {
				state = BUTTON_PRESS_LONG;
				baseboard_toggle_mf();
			}
			break;
		case BUTTON_PRESS_SHORT:
			/*
			 * Short press was recognized and dock power state was
			 * already on. If button is now released, then turn dock
			 * off.
			 */
			if (evt & BUTTON_EVT_CHANGE &&
			    button_level == BUTTON_RELEASED_LEVEL) {
				state = BUTTON_RELEASE;
				baseboard_power_off();
			} else {
				state = BUTTON_PRESS_LONG;
				baseboard_toggle_mf();
			}
			break;
		case BUTTON_PRESS_LONG:
			if (evt & BUTTON_EVT_CHANGE &&
			    button_level == BUTTON_RELEASED_LEVEL) {
				state = BUTTON_RELEASE;
			}
			break;
		}
	}
}

static void baseboard_power_button_debounce(void)
{
	int level = gpio_get_level(GPIO_PWR_BTN);

	/* Sanity check, level should be same after debounce interval */
	if (level != button_level_pending)
		return;

	button_level = level;
	task_set_event(TASK_ID_POWER_BUTTON, BUTTON_EVT_CHANGE);
}
DECLARE_DEFERRED(baseboard_power_button_debounce);

void baseboard_power_button_evt(int level)
{
	button_level_pending = level;

	hook_call_deferred(&baseboard_power_button_debounce_data,
			   POWER_BUTTON_DEBOUNCE_USEC);
}

static int command_pwr_btn(int argc, const char **argv)
{
	if (argc == 1) {
		task_set_event(TASK_ID_POWER_BUTTON, BUTTON_EVT_INFO);
		return EC_SUCCESS;
	}

	if (!strcasecmp(argv[1], "on")) {
		baseboard_power_on();
	} else if (!strcasecmp(argv[1], "off")) {
		baseboard_power_off();
	} else if (!strcasecmp(argv[1], "mf")) {
		baseboard_toggle_mf();
	} else {
		return EC_ERROR_PARAM1;
	}

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(pwr_btn, command_pwr_btn, "<on|off|mf>",
			"Simulate Power Button Press");

#endif