summaryrefslogtreecommitdiff
path: root/common/led_pwm.c
blob: cc946ba522c3a115ca48e4e6aca538efb03a650d (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
/* Copyright 2018 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.
 */

/* PWM LED control to conform to Chrome OS LED behaviour specification. */

/*
 * This assumes that a single logical LED is shared between both power and
 * charging/battery status.  If multiple logical LEDs are present, they all
 * follow the same patterns.
 */

#include "battery.h"
#include "charge_manager.h"
#include "charge_state.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "hooks.h"
#include "led_common.h"
#include "led_pwm.h"
#include "pwm.h"
#include "timer.h"
#include "util.h"

/* Battery percentage thresholds to blink at different rates. */
#define CRITICAL_LOW_BATTERY_PERCENTAGE 3
#define LOW_BATTERY_PERCENTAGE 10

#define PULSE_TICK (250 * MSEC)

static uint8_t led_is_pulsing;

static int get_led_id_color(enum pwm_led_id id, int color)
{
#ifdef CONFIG_LED_PWM_ACTIVE_CHARGE_PORT_ONLY
	int active_chg_port = charge_manager_get_active_charge_port();

	/* We should always be able to turn off a LED. */
	if (color == -1)
		return -1;

	if (led_is_pulsing)
		return color;

	/* The inactive charge port LEDs should be off. */
	if ((int)id != active_chg_port)
		return -1;
#endif /* CONFIG_LED_PWM_ACTIVE_CHARGE_PORT_ONLY */
	return color;
}

void set_pwm_led_color(enum pwm_led_id id, int color)
{
	struct pwm_led duty = { 0 };
	const struct pwm_led *led = &pwm_leds[id];

	if ((id >= CONFIG_LED_PWM_COUNT) || (id < 0) ||
	    (color >= EC_LED_COLOR_COUNT) || (color < -1))
		return;

	if (color != -1) {
		duty.ch0 = led_color_map[color].ch0;
		duty.ch1 = led_color_map[color].ch1;
		duty.ch2 = led_color_map[color].ch2;
	}

	if (led->ch0 != (enum pwm_channel)PWM_LED_NO_CHANNEL)
		led->set_duty(led->ch0, duty.ch0);
	if (led->ch1 != (enum pwm_channel)PWM_LED_NO_CHANNEL)
		led->set_duty(led->ch1, duty.ch1);
	if (led->ch2 != (enum pwm_channel)PWM_LED_NO_CHANNEL)
		led->set_duty(led->ch2, duty.ch2);
}

static void set_led_color(int color)
{
	/*
	 *  We must check if auto control is enabled since the LEDs may be
	 *  controlled from the AP at anytime.
	 */
	if ((led_auto_control_is_enabled(EC_LED_ID_POWER_LED)) ||
	    (led_auto_control_is_enabled(EC_LED_ID_LEFT_LED)))
		set_pwm_led_color(PWM_LED0, get_led_id_color(PWM_LED0, color));

#if CONFIG_LED_PWM_COUNT >= 2
	if (led_auto_control_is_enabled(EC_LED_ID_RIGHT_LED))
		set_pwm_led_color(PWM_LED1, get_led_id_color(PWM_LED1, color));
#endif /* CONFIG_LED_PWM_COUNT >= 2 */
}

static void set_pwm_led_enable(enum pwm_led_id id, int enable)
{
	const struct pwm_led *led = &pwm_leds[id];

	if ((id >= CONFIG_LED_PWM_COUNT) || (id < 0))
		return;

	if (led->ch0 != (enum pwm_channel)PWM_LED_NO_CHANNEL)
		led->enable(led->ch0, enable);
	if (led->ch1 != (enum pwm_channel)PWM_LED_NO_CHANNEL)
		led->enable(led->ch1, enable);
	if (led->ch2 != (enum pwm_channel)PWM_LED_NO_CHANNEL)
		led->enable(led->ch2, enable);
}

static void init_leds_off(void)
{
	/* Turn off LEDs such that they are in a known state with zero duty. */
	set_led_color(-1);

	/* Enable pwm modules for each channels of LEDs */
	set_pwm_led_enable(PWM_LED0, 1);

#if CONFIG_LED_PWM_COUNT >= 2
	set_pwm_led_enable(PWM_LED1, 1);
#endif /* CONFIG_LED_PWM_COUNT >= 2 */
}
DECLARE_HOOK(HOOK_INIT, init_leds_off, HOOK_PRIO_INIT_PWM + 1);

static uint8_t pulse_period;
static uint8_t pulse_ontime;
static enum ec_led_colors pulse_color;
static void update_leds(void);
static void pulse_leds_deferred(void);
DECLARE_DEFERRED(pulse_leds_deferred);
static void pulse_leds_deferred(void)
{
	static uint8_t tick_count;

	if (!led_is_pulsing) {
		tick_count = 0;
		/*
		 * Since we're not pulsing anymore, turn the colors off in case
		 * we were in the "on" time.
		 */
		set_led_color(-1);
		/* Then show the desired state. */
		update_leds();
		return;
	}

	if (tick_count < pulse_ontime)
		set_led_color(pulse_color);
	else
		set_led_color(-1);

	tick_count = (tick_count + 1) % pulse_period;
	hook_call_deferred(&pulse_leds_deferred_data, PULSE_TICK);
}

static void pulse_leds(enum ec_led_colors color, int ontime, int period)
{
	pulse_color = color;
	pulse_ontime = ontime;
	pulse_period = period;
	led_is_pulsing = 1;
	pulse_leds_deferred();
}

static int show_charge_state(void)
{
	enum charge_state chg_st = charge_get_state();

	/*
	 * The colors listed below are the default, but can be overridden.
	 *
	 * Solid Amber == Charging
	 * Solid Green == Charging (near full)
	 * Fast Flash Red == Charging error or battery not present
	 */
	if (chg_st == PWR_STATE_CHARGE) {
		led_is_pulsing = 0;
		set_led_color(CONFIG_LED_PWM_CHARGE_COLOR);
	} else if (chg_st == PWR_STATE_CHARGE_NEAR_FULL ||
		   chg_st == PWR_STATE_DISCHARGE_FULL) {
		led_is_pulsing = 0;
		set_led_color(CONFIG_LED_PWM_NEAR_FULL_COLOR);
	} else if ((battery_is_present() != BP_YES) ||
		   (chg_st == PWR_STATE_ERROR)) {
		/* 500 ms period, 50% duty cycle. */
		pulse_leds(CONFIG_LED_PWM_CHARGE_ERROR_COLOR, 1, 2);
	} else {
		/* Discharging or not charging. */
#ifdef CONFIG_LED_PWM_CHARGE_STATE_ONLY
		/*
		 * If we only show the charge state, the only reason we
		 * would pulse the LEDs is if we had an error.  If it no longer
		 * exists, stop pulsing the LEDs.
		 */
		led_is_pulsing = 0;
#endif /* CONFIG_LED_PWM_CHARGE_STATE_ONLY */
		return 0;
	}
	return 1;
}

#ifndef CONFIG_LED_PWM_CHARGE_STATE_ONLY
static int show_battery_state(void)
{
	int batt_percentage = charge_get_percent();

	/*
	 * The colors listed below are the default, but can be overridden.
	 *
	 * Fast Flash Amber == Critical Battery
	 * Slow Flash Amber == Low Battery
	 */
	if (batt_percentage < CRITICAL_LOW_BATTERY_PERCENTAGE) {
		/* Flash amber faster (1 second period, 50% duty cycle) */
		pulse_leds(CONFIG_LED_PWM_LOW_BATT_COLOR, 2, 4);
	} else if (batt_percentage < LOW_BATTERY_PERCENTAGE) {
		/* Flash amber (4 second period, 50% duty cycle) */
		pulse_leds(CONFIG_LED_PWM_LOW_BATT_COLOR, 8, 16);
	} else {
		/* Sufficient charge, no need to show anything for this. */
		return 0;
	}
	return 1;
}

static int show_chipset_state(void)
{
	/* Reflect the SoC state. */
	led_is_pulsing = 0;
	if (chipset_in_state(CHIPSET_STATE_ON)) {
		/* The LED must be on in the Active state. */
		set_led_color(CONFIG_LED_PWM_SOC_ON_COLOR);
	} else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) {
		/* The power LED must pulse in the suspend state. */
		pulse_leds(CONFIG_LED_PWM_SOC_SUSPEND_COLOR, 4, 16);
	} else {
		/* Chipset is off, no need to show anything for this. */
		return 0;
	}
	return 1;
}
#endif /* CONFIG_LED_PWM_CHARGE_STATE_ONLY */

static void update_leds(void)
{
	/* Reflecting the charge state is the highest priority. */
	if (show_charge_state())
		return;

#ifndef CONFIG_LED_PWM_CHARGE_STATE_ONLY
	if (show_battery_state())
		return;

	if (show_chipset_state())
		return;
#endif /* CONFIG_LED_PWM_CHARGE_STATE_ONLY */

	set_led_color(-1);
}
DECLARE_HOOK(HOOK_TICK, update_leds, HOOK_PRIO_DEFAULT);

#ifdef CONFIG_CMD_LEDTEST
int command_ledtest(int argc, char **argv)
{
	int enable;
	int pwm_led_id;
	int led_id;

	if (argc < 2)
		return EC_ERROR_PARAM_COUNT;

	pwm_led_id = atoi(argv[1]);
	if ((pwm_led_id < 0) || (pwm_led_id >= CONFIG_LED_PWM_COUNT))
		return EC_ERROR_PARAM1;
	led_id = supported_led_ids[pwm_led_id];

	if (argc == 2) {
		ccprintf("PWM LED %d: led_id=%d, auto_control=%d\n",
			 pwm_led_id, led_id,
			 led_auto_control_is_enabled(led_id) != 0);
		return EC_SUCCESS;
	}
	if (!parse_bool(argv[2], &enable))
		return EC_ERROR_PARAM2;

	/* Inverted because this drives auto control. */
	led_auto_control(led_id, !enable);

	if (argc == 4) {
		/* Set the color. */
		if (!strncmp(argv[3], "red", 3))
			set_pwm_led_color(pwm_led_id, EC_LED_COLOR_RED);
		else if (!strncmp(argv[3], "green", 5))
			set_pwm_led_color(pwm_led_id, EC_LED_COLOR_GREEN);
		else if (!strncmp(argv[3], "amber", 5))
			set_pwm_led_color(pwm_led_id, EC_LED_COLOR_AMBER);
		else if (!strncmp(argv[3], "blue", 4))
			set_pwm_led_color(pwm_led_id, EC_LED_COLOR_BLUE);
		else if (!strncmp(argv[3], "white", 5))
			set_pwm_led_color(pwm_led_id, EC_LED_COLOR_WHITE);
		else if (!strncmp(argv[3], "yellow", 6))
			set_pwm_led_color(pwm_led_id, EC_LED_COLOR_YELLOW);
		else if (!strncmp(argv[3], "off", 3))
			set_pwm_led_color(pwm_led_id, -1);
		else
			return EC_ERROR_PARAM3;
	}

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(ledtest, command_ledtest,
			"<pwm led idx> <enable|disable> [color|off]", "");
#endif /* defined(CONFIG_CMD_LEDTEST) */