summaryrefslogtreecommitdiff
path: root/common/led_lp5562.c
blob: a35332be9ba91a53014df4bc2896105f60552c7e (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
/* 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.
 *
 * Battery LED state machine to drive RGB LED on LP5562
 */

#include "common.h"
#include "ec_commands.h"
#include "extpower.h"
#include "hooks.h"
#include "host_command.h"
#include "lp5562.h"
#include "pmu_tpschrome.h"
#include "smart_battery.h"
#include "timer.h"
#include "util.h"

#define GREEN_LED_THRESHOLD 94

/* Minimal interval between changing LED color to green and yellow. */
#define LED_WAIT_INTERVAL (15 * SECOND)

/* We use yellow LED instead of blue LED. Re-map colors here. */
#define LED_COLOR_NONE   LP5562_COLOR_NONE
#define LED_COLOR_GREEN  LP5562_COLOR_GREEN(0x10)
#define LED_COLOR_YELLOW LP5562_COLOR_BLUE(0x40)
#define LED_COLOR_RED    LP5562_COLOR_RED(0x80)

/* LED states */
enum led_state_t {
	LED_STATE_SOLID_RED,
	LED_STATE_SOLID_GREEN,
	LED_STATE_SOLID_YELLOW,

	/* Not an actual state */
	LED_STATE_OFF,
};

static enum led_state_t last_state = LED_STATE_OFF;
static int led_auto_control = 1;

static int set_led_color(enum led_state_t state)
{
	int rv = EC_SUCCESS;

	if (!led_auto_control || state == last_state)
		return EC_SUCCESS;

	switch (state) {
	case LED_STATE_SOLID_RED:
		rv = lp5562_set_color(LED_COLOR_RED);
		break;
	case LED_STATE_SOLID_GREEN:
		rv = lp5562_set_color(LED_COLOR_GREEN);
		break;
	case LED_STATE_SOLID_YELLOW:
		rv = lp5562_set_color(LED_COLOR_YELLOW);
		break;
	case LED_STATE_OFF:
		break;
	}

	if (rv == EC_SUCCESS)
		last_state = state;
	return rv;
}

/*****************************************************************************/
/* Host commands */

static int led_command_control(struct host_cmd_handler_args *args)
{
	const struct ec_params_led_control *p = args->params;
	struct ec_response_led_control *r = args->response;
	int i;
	uint8_t clipped[EC_LED_COLOR_COUNT];

	/* Only support battery LED control */
	if (p->led_id != EC_LED_ID_BATTERY_LED)
		return EC_RES_INVALID_PARAM;

	if (p->flags & EC_LED_FLAGS_AUTO) {
		if (!extpower_is_present())
			lp5562_poweroff();
		last_state = LED_STATE_OFF;
		led_auto_control = 1;
	} else if (!(p->flags & EC_LED_FLAGS_QUERY)) {
		for (i = 0; i < EC_LED_COLOR_COUNT; ++i)
			clipped[i] = MIN(p->brightness[i], 0x80);
		led_auto_control = 0;
		if (!extpower_is_present())
			lp5562_poweron();
		if (lp5562_set_color((clipped[EC_LED_COLOR_RED] << 16) +
				     (clipped[EC_LED_COLOR_GREEN] << 8) +
				     clipped[EC_LED_COLOR_YELLOW]))
			return EC_RES_ERROR;
	}

	r->brightness_range[EC_LED_COLOR_RED] = 0x80;
	r->brightness_range[EC_LED_COLOR_GREEN] = 0x80;
	r->brightness_range[EC_LED_COLOR_BLUE] = 0x0;
	r->brightness_range[EC_LED_COLOR_YELLOW] = 0x80;
	r->brightness_range[EC_LED_COLOR_WHITE] = 0x0;
	args->response_size = sizeof(struct ec_response_led_control);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_LED_CONTROL,
		     led_command_control,
		     EC_VER_MASK(1));

/*****************************************************************************/
/* Hooks */

static void battery_led_update(void)
{
	int rv;
	int state_of_charge;
	enum led_state_t state = LED_STATE_OFF;

	/* Current states and next states */
	static int led_power = -1;
	int new_led_power;

	/*
	 * The time before which we should not change LED
	 * color between green and yellow.
	 */
	static timestamp_t led_update_deadline = {.val = 0};

	/* Determine LED power */
	new_led_power = extpower_is_present();
	if (new_led_power != led_power) {
		if (new_led_power) {
			rv = lp5562_poweron();
		} else {
			rv = lp5562_poweroff();
			set_led_color(LED_STATE_OFF);
			led_update_deadline.val = 0;
		}
		if (!rv)
			led_power = new_led_power;
	}
	if (!new_led_power)
		return;

	/*
	 * LED power is controlled by accessory detection. We only
	 * set color here.
	 */
	switch (charge_get_state()) {
	case ST_IDLE0:
	case ST_BAD_COND:
	case ST_PRE_CHARGING:
		state = LED_STATE_SOLID_YELLOW;
		break;
	case ST_IDLE:
	case ST_DISCHARGING:
	case ST_CHARGING:
		if (battery_state_of_charge(&state_of_charge)) {
			/* Cannot talk to the battery. Set LED to red. */
			state = LED_STATE_SOLID_RED;
			break;
		}

		if (state_of_charge < GREEN_LED_THRESHOLD)
			state = LED_STATE_SOLID_YELLOW;
		else
			state = LED_STATE_SOLID_GREEN;
		break;
	case ST_CHARGING_ERROR:
		state = LED_STATE_SOLID_RED;
		break;
	}

	if (state == LED_STATE_SOLID_GREEN ||
			state == LED_STATE_SOLID_YELLOW) {
		if (!timestamp_expired(led_update_deadline, NULL))
			return;
		led_update_deadline.val =
			get_time().val + LED_WAIT_INTERVAL;
	} else {
		led_update_deadline.val = 0;
	}

	set_led_color(state);
}
DECLARE_HOOK(HOOK_SECOND, battery_led_update, HOOK_PRIO_DEFAULT);