summaryrefslogtreecommitdiff
path: root/common/lb_common.c
blob: 019e0e254fe04dfd5f9ae4456b406484b826e8d9 (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
/* Copyright 2012 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.
 *
 * Lightbar IC interface
 *
 * Here's the API provided by this file.
 *
 * Looking at it from the outside, the lightbar has four "segments", each of
 * which can be independently adjusted to display a unique color such as blue,
 * purple, yellow, pinkish-white, etc. Segment 0 is on the left (looking
 * straight at it from behind).
 *
 * The lb_set_rgb() and lb_get_rgb() functions let you specify the color of a
 * segment using individual Red, Green, and Blue values in the 0x00 to 0xFF
 * range (see https://en.wikipedia.org/wiki/Web_color for background info).
 *
 * The lb_set_brightness() function provides a simple way to set the intensity,
 * over a range of 0x00 (off) to 0xFF (full brightness). It does this by
 * scaling each RGB value proportionally. For example, an RGB value of #FF8000
 * appears orange. To make the segment half as bright, you could specify a RGB
 * value of #7f4000, or you could leave the RGB value unchanged and just set
 * the brightness to 0x80.
 *
 * That covers most of the lb_* functions found in include/lb_common.h, and
 * those functions are what are used to implement the various colors and
 * sequences for displaying power state changes and other events.
 *
 * The internals are a little more messy.
 *
 * Each segment has three individual color emitters - red, green, and blue. A
 * single emitter may consist of 3 to 7 physical LEDs, but they are all wired
 * in parallel so there is only one wire that provides current for any one
 * color emitter. That makes a total of 12 current control wires for the
 * lightbar: four segments, three color emitters per segment.
 *
 * The ICs that we use each have seven independently adjustable
 * current-limiters. We use six of those current limiters (called "Independent
 * Sink Controls", or "ISC"s ) from each of two ICs to control the 12 color
 * emitters in the lightbar. The ICs are not identical, but they're close
 * enough that we can treat them the same. We call the ICs "controller 0" and
 * "controller 1".
 *
 * For no apparent reason, each Chromebook has wired the ICs and the ISCs
 * differently, so there are a couple of lookup tables that ensure that when we
 * call lb_set_rgb() to make segment 1 yellow, it looks the same on all
 * Chromebooks.
 *
 * Each ISC has a control register to set the amount of current that passes
 * through the color emitter control wire. We need to limit the max current so
 * that the current through each of the emitter's LEDs doesn't exceed the
 * manufacturer's specifications. For example, if a particular LED can't handle
 * more than 5 mA, and the emitter is made up of four LEDs in parallel, the
 * maxiumum limit for that particular ISC would be 20 mA.
 *
 * Although the specified maximum currents are usually similar, the three
 * different colors of LEDs have different brightnesses. For any given current,
 * green LEDs are pretty bright, red LEDS are medium, and blue are fairly dim.
 * So we calibrate the max current per ISC differently, depending on which
 * color it controls.
 *
 * First we set one segment to red, one to green, and one to blue, using the
 * ISC register to allow the max current per LED that the LED manufacturer
 * recommends. Then we adjust the current of the brighter segments downward
 * until all three segments appear equally bright to the eye. The MAX_RED,
 * MAX_BLUE, and MAX_GREEN values are the ISC control register values at this
 * point. This means that if we set all ISCs to their MAX_* values, all
 * segments should appear white.
 *
 * To translate the RGB values passed to lb_set_rgb() into ISC values, we
 * perform two transformations. The color value is first scaled according to
 * the current brightness setting, and then that intensity is scaled according
 * to the MAX_* value for the particular color. The result is the ISC register
 * value to use.
 *
 * To add lightbar support for a new Chromebook, you do the following:
 *
 * 1. Figure out the segment-to-IC and color-to-ISC mappings so that
 *    lb_set_rgb() does the same thing as on the other Chromebooks.
 *
 * 2. Calibrate the MAX_RED, MAX_GREEN, and MAX_BLUE values so that white looks
 *    white, and solid red, green, and blue all appear to be the same
 *    brightness.
 *
 * 3. Use lb_set_rgb() to set the colors to what *should be* the Google colors
 *    (at maximum brightness). Tweak the RGB values until the colors match,
 *    then edit common/lightbar.c to set them as the defaults.
 *
 * 4. Curse because the physical variation between the LEDs prevents you from
 *    getting everything exactly right: white looks bluish, yellow turns
 *    orange at lower brightness, segment 3 has a bright spot when displaying
 *    solid red, etc. Go back to step 2, and repeat until deadline.
 */

#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "i2c.h"
#include "lb_common.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_LIGHTBAR, outstr)
#define CPRINTF(format, args...) cprintf(CC_LIGHTBAR, format, ## args)
#define CPRINTS(format, args...) cprints(CC_LIGHTBAR, format, ## args)

/******************************************************************************/
/* How to talk to the controller */
/******************************************************************************/

/* Since there's absolutely nothing we can do about it if an I2C access
 * isn't working, we're completely ignoring any failures. */

static const uint16_t i2c_addr_flags[] = { 0x2A, 0x2B };

static inline void controller_write(int ctrl_num, uint8_t reg, uint8_t val)
{
	uint8_t buf[2];

	buf[0] = reg;
	buf[1] = val;
	ctrl_num = ctrl_num % ARRAY_SIZE(i2c_addr_flags);
	i2c_xfer_unlocked(I2C_PORT_LIGHTBAR, i2c_addr_flags[ctrl_num],
			buf, 2, 0, 0,
			I2C_XFER_SINGLE);
}

static inline uint8_t controller_read(int ctrl_num, uint8_t reg)
{
	uint8_t buf[1];
	int rv;

	ctrl_num = ctrl_num % ARRAY_SIZE(i2c_addr_flags);
	rv = i2c_xfer_unlocked(I2C_PORT_LIGHTBAR, i2c_addr_flags[ctrl_num],
			&reg, 1, buf, 1, I2C_XFER_SINGLE);
	return rv ? 0 : buf[0];
}

/******************************************************************************/
/* Controller details. We have an ADP8861 and and ADP8863, but we can treat
 * them identically for our purposes */
/******************************************************************************/

#ifdef BOARD_BDS
/* We need to limit the total current per ISC to no more than 20mA (5mA per
 * color LED, but we have four LEDs in parallel on each ISC). Any more than
 * that runs the risk of damaging the LED component. A value of 0x67 is as high
 * as we want (assuming Square Law), but the blue LED is the least bright, so
 * I've lowered the other colors until they all appear approximately equal
 * brightness when full on. That's still pretty bright and a lot of current
 * drain on the battery, so we'll probably rarely go that high. */
#define MAX_RED   0x5c
#define MAX_GREEN 0x30
#define MAX_BLUE  0x67
#endif
#ifdef BOARD_HOST
/* For testing only */
#define MAX_RED   0xff
#define MAX_GREEN 0xff
#define MAX_BLUE  0xff
#endif

/* How we'd like to see the driver chips initialized. The controllers have some
 * auto-cycling capability, but it's not much use for our purposes. For now,
 * we'll just control all color changes actively. */
struct initdata_s {
	uint8_t reg;
	uint8_t val;
};

static const struct initdata_s init_vals[] = {
	{0x04, 0x00},				/* no backlight function */
	{0x05, 0x3f},				/* xRGBRGB per chip */
	{0x0f, 0x01},				/* square law looks better */
	{0x10, 0x3f},				/* enable independent LEDs */
	{0x11, 0x00},				/* no auto cycling */
	{0x12, 0x00},				/* no auto cycling */
	{0x13, 0x00},				/* instant fade in/out */
	{0x14, 0x00},				/* not using LED 7 */
	{0x15, 0x00},				/* current for LED 6 (blue) */
	{0x16, 0x00},				/* current for LED 5 (red) */
	{0x17, 0x00},				/* current for LED 4 (green) */
	{0x18, 0x00},				/* current for LED 3 (blue) */
	{0x19, 0x00},				/* current for LED 2 (red) */
	{0x1a, 0x00},				/* current for LED 1 (green) */
};

/* Controller register lookup tables. */
static const uint8_t led_to_ctrl[] = { 1, 1, 0, 0 };
#ifdef BOARD_BDS
static const uint8_t led_to_isc[] = { 0x18, 0x15, 0x18, 0x15 };
#endif
#ifdef BOARD_HOST
/* For testing only */
static const uint8_t led_to_isc[] = { 0x15, 0x18, 0x15, 0x18 };
#endif

/* Scale 0-255 into max value */
static inline uint8_t scale_abs(int val, int max)
{
	return (val * max)/255;
}

/* This is the overall brightness control. */
static int brightness = 0xc0;

/* So that we can make brightness changes happen instantly, we need to track
 * the current values. The values in the controllers aren't very helpful. */
static uint8_t current[NUM_LEDS][3];

/* Scale 0-255 by brightness */
static inline uint8_t scale(int val, int max)
{
	return scale_abs((val * brightness)/255, max);
}

/* Helper function to set one LED color and remember it for later */
static void setrgb(int led, int red, int green, int blue)
{
	int ctrl, bank;
	current[led][0] = red;
	current[led][1] = green;
	current[led][2] = blue;
	ctrl = led_to_ctrl[led];
	bank = led_to_isc[led];
	i2c_lock(I2C_PORT_LIGHTBAR, 1);
	controller_write(ctrl, bank, scale(blue, MAX_BLUE));
	controller_write(ctrl, bank+1, scale(red, MAX_RED));
	controller_write(ctrl, bank+2, scale(green, MAX_GREEN));
	i2c_lock(I2C_PORT_LIGHTBAR, 0);
}

/* LEDs are numbered 0-3, RGB values should be in 0-255.
 * If you specify too large an LED, it sets them all. */
void lb_set_rgb(unsigned int led, int red, int green, int blue)
{
	int i;
	if (led >= NUM_LEDS)
		for (i = 0; i < NUM_LEDS; i++)
			setrgb(i, red, green, blue);
	else
		setrgb(led, red, green, blue);
}

/* Get current LED values, if the LED number is in range. */
int lb_get_rgb(unsigned int led, uint8_t *red, uint8_t *green, uint8_t *blue)
{
	if (led < 0 || led >= NUM_LEDS)
		return EC_RES_INVALID_PARAM;

	*red = current[led][0];
	*green = current[led][1];
	*blue = current[led][2];

	return EC_RES_SUCCESS;
}

/* Change current display brightness (0-255) */
void lb_set_brightness(unsigned int newval)
{
	int i;
	CPRINTS("LB_bright 0x%02x", newval);
	brightness = newval;
	for (i = 0; i < NUM_LEDS; i++)
		setrgb(i, current[i][0], current[i][1], current[i][2]);
}

/* Get current display brightness (0-255) */
uint8_t lb_get_brightness(void)
{
	return brightness;
}

/* Initialize the controller ICs after reset */
void lb_init(int use_lock)
{
	int i;

	CPRINTF("[%pT LB_init_vals ", PRINTF_TIMESTAMP_NOW);
	for (i = 0; i < ARRAY_SIZE(init_vals); i++) {
		CPRINTF("%c", '0' + i % 10);
		if (use_lock)
			i2c_lock(I2C_PORT_LIGHTBAR, 1);
		controller_write(0, init_vals[i].reg, init_vals[i].val);
		controller_write(1, init_vals[i].reg, init_vals[i].val);
		if (use_lock)
			i2c_lock(I2C_PORT_LIGHTBAR, 0);
	}
	CPRINTF("]\n");
	memset(current, 0, sizeof(current));
}

/* Just go into standby mode. No register values should change. */
void lb_off(void)
{
	CPRINTS("LB_off");
	i2c_lock(I2C_PORT_LIGHTBAR, 1);
	controller_write(0, 0x01, 0x00);
	controller_write(1, 0x01, 0x00);
	i2c_lock(I2C_PORT_LIGHTBAR, 0);
}

/* Come out of standby mode. */
void lb_on(void)
{
	CPRINTS("LB_on");
	i2c_lock(I2C_PORT_LIGHTBAR, 1);
	controller_write(0, 0x01, 0x20);
	controller_write(1, 0x01, 0x20);
	i2c_lock(I2C_PORT_LIGHTBAR, 0);
}

static const uint8_t dump_reglist[] = {
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	0x08, 0x09, 0x0a,			  0x0f,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
	0x18, 0x19, 0x1a
};

/* Helper for host command to dump controller registers */
void lb_hc_cmd_dump(struct ec_response_lightbar *out)
{
	int i;
	uint8_t reg;

	BUILD_ASSERT(ARRAY_SIZE(dump_reglist) ==
		     ARRAY_SIZE(out->dump.vals));

	for (i = 0; i < ARRAY_SIZE(dump_reglist); i++) {
		reg = dump_reglist[i];
		out->dump.vals[i].reg = reg;
		i2c_lock(I2C_PORT_LIGHTBAR, 1);
		out->dump.vals[i].ic0 = controller_read(0, reg);
		out->dump.vals[i].ic1 = controller_read(1, reg);
		i2c_lock(I2C_PORT_LIGHTBAR, 0);
	}
}

/* Helper for host command to write controller registers directly */
void lb_hc_cmd_reg(const struct ec_params_lightbar *in)
{
	i2c_lock(I2C_PORT_LIGHTBAR, 1);
	controller_write(in->reg.ctrl, in->reg.reg, in->reg.value);
	i2c_lock(I2C_PORT_LIGHTBAR, 0);
}