aboutsummaryrefslogtreecommitdiff
path: root/src/drivers/i2c/ww_ring/ww_ring.c
blob: b07457691415f575ee3559aec1104b7cb80b75c1 (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
/*
 * Copyright (C) 2015 Google, Inc.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

/*
 * This is a driver for the Whirlwind LED ring, which is equipped with two LED
 * microcontrollers TI LP55231 (http://www.ti.com/product/lp55231), each of
 * them driving three multicolor LEDs.
 *
 * The only connection between the ring and the main board is an i2c bus.
 *
 * This driver imitates a depthcharge display device. On initialization the
 * driver sets up the controllers to prepare them to accept programs to run.
 *
 * When a certain vboot state needs to be indicated, the program for that
 * state is loaded into the controllers, resulting in the state appropriate
 * LED behavior.
 */

#include <console/console.h>
#include <device/i2c.h>
#include <string.h>

#include "drivers/i2c/ww_ring/ww_ring.h"

/* Number of lp55321 controllers on the ring */
#define WW_RING_NUM_LED_CONTROLLERS 2

/* I2c address of the first of the controllers, the rest are contiguous. */
#define WW_RING_BASE_ADDR	0x32

/* Key lp55231 registers. */
#define LP55231_ENGCTRL1_REG	0x00
#define LP55231_ENGCTRL2_REG	0x01
#define LP55231_D1_CRT_CTRL_REG	0x26
#define LP55231_MISC_REG	0x36
#define LP55231_RESET_REG	0x3d
#define LP55231_ENG1_PROG_START	0x4c
#define LP55231_PROG_PAGE_REG	0x4f
#define LP55231_PROG_BASE_REG	0x50

/* LP55231_D1_CRT_CTRL_REG, default value, applies to all nine of them */
#define LP55231_CRT_CTRL_DEFAULT 0xaf

/* LP55231_ENGCTRL1_REG	fields */
#define LP55231_ENGCTRL1_CHIP_EN     0x40
#define LP55231_ENGCTRL1_ALL_ENG_GO  0x2a

/* LP55231_MISC_REG fields. */
#define LP55231_MISC_AUTOINCR  (1 << 6)
#define LP55231_MISC_PUMP_1X   (1 << 3)
#define LP55231_MISC_INT_CLK   (3 << 0)

/* Goes into LP55231_RESET_REG to reset the chip. */
#define LP55231_RESET_VALUE	0xff

/*
 * The controller has 192 bytes of SRAM for code/data, availabe as six 32 byte
 * pages.
 */
#define LP55231_PROG_PAGE_SIZE  32
#define LP55231_PROG_PAGES      6
#define LP55231_MAX_PROG_SIZE  (LP55231_PROG_PAGE_SIZE * LP55231_PROG_PAGES)

/* There are threee independent engines/cores in the controller. */
#define LP55231_NUM_OF_ENGINES 3

/*
 * Structure to cache data relevant to accessing one controller. I2c interface
 * to use, device address on the i2c bus and a data buffer for write
 * transactions. The most bytes sent at a time is the register address plus
 * the program page size.
 */
typedef struct {
	unsigned i2c_bus;
	uint8_t  dev_addr;
	uint8_t  data_buffer[LP55231_PROG_PAGE_SIZE + 1];
} TiLp55231;

/*
 * Structure to describe an lp55231 program: pointer to the text of the
 * program, its size and load address (load addr + size sould not exceed
 * LP55231_MAX_PROG_SIZE), and start addresses for all of the three
 * engines.
 */
typedef struct {
	const uint8_t *program_text;
	uint8_t program_size;
	uint8_t  load_addr;
	uint8_t  engine_start_addr[LP55231_NUM_OF_ENGINES];
} TiLp55231Program;

/* A structure to bind controller programs to a vboot state. */
typedef struct {
	enum VbScreenType_t    vb_screen;
	const TiLp55231Program * programs[WW_RING_NUM_LED_CONTROLLERS];
} WwRingStateProg;

static void ww_ring_init(unsigned i2c_bus);

/****************************************************************/
/*   LED ring program definitions for different vboot states.	*/

static const uint8_t blink_program_text[] = {
	0x40, 0x40, 0x9D, 0x04, 0x40, 0x40, 0x7E,
	0x00, 0x9D, 0x07, 0x40, 0x00, 0x9D, 0x04,
	0x40, 0x00, 0x7E, 0x00, 0xA0, 0x00, 0x00,
	0x00 };

static const TiLp55231Program led_blink_program = {
	blink_program_text,
	sizeof(blink_program_text),
	0,
	{0,
	 sizeof(blink_program_text) - 2,
	 sizeof(blink_program_text) - 2}
};

static const WwRingStateProg state_programs[] = {
	/*
	 * for test purposes the blank screen program is set to blinking, will
	 * be changed soon.
	 */
	{VB_SCREEN_BLANK, {&led_blink_program, &led_blink_program} },
	/* Other vboot state programs are coming. */
};
/*								*/
/****************************************************************/

/* Controller descriptors. */
static TiLp55231 lp55231s[WW_RING_NUM_LED_CONTROLLERS];

/*
 * i2c transfer function for the driver. To keep things simple, the function
 * repeats the transfer, if the first attempt fails. This is OK with the
 * controller and makes it easier to handle errors.
 *
 * Note that the reset register accesses are expected to fail on writes, but
 * due to a bug in the ipq806x i2c controller, the error is reported on the
 * following read attempt.
 *
 * To work around this the driver writes and then reads the reset register,
 * the transfer function ignores errors when accessing the reset register.
 */

static int ledc_transfer(TiLp55231 *ledc, struct i2c_seg *segs,
			 int seg_count, int reset)
{
	int rv, max_attempts = 2;

	while (max_attempts--) {
		rv = i2c_transfer(ledc->i2c_bus, segs, seg_count);

		/* Accessing reset regsiter is expected to fail. */
		if (!rv || reset)
			break;
	}

	if (rv) {
		if (!reset)
			printk(BIOS_WARNING,
			       "%s: dev %#x, reg %#x, %s transaction error.\n",
			       __func__, segs->chip, segs->buf[0],
			       seg_count == 1 ? "write" : "read");
		else
			rv = 0;
	}

	return rv;
}

/*
 * The controller is programmed to autoincrement on writes, so up to page size
 * bytes can be transmitted in one write transaction.
 */
static int ledc_write(TiLp55231 *ledc, uint8_t start_addr,
		      const uint8_t *data, unsigned count)
{
	struct i2c_seg seg;

	if (count > (sizeof(ledc->data_buffer) - 1)) {
		printk(BIOS_WARNING, "%s: transfer size too large (%d bytes)\n",
		       __func__, count);
		return -1;
	}

	memcpy(ledc->data_buffer + 1, data, count);
	ledc->data_buffer[0] = start_addr;

	seg.read = 0;
	seg.chip = ledc->dev_addr;
	seg.buf = ledc->data_buffer;
	seg.len = count + 1;

	return ledc_transfer(ledc, &seg, 1, start_addr == LP55231_RESET_REG);
}

/* To keep things simple, read is limited to one byte at a time. */
static int ledc_read(TiLp55231 *ledc, uint8_t addr, uint8_t *data)
{
	struct i2c_seg seg[2];

	seg[0].read = 0;
	seg[0].chip = ledc->dev_addr;
	seg[0].buf = &addr;
	seg[0].len = 1;

	seg[1].read = 1;
	seg[1].chip = ledc->dev_addr;
	seg[1].buf = data;
	seg[1].len = 1;

	return ledc_transfer(ledc, seg, ARRAY_SIZE(seg),
			     addr == LP55231_RESET_REG);
}

/*
 * Reset transaction is expected to result in a failing i2c command,
 * no need to return a value.
 */
static void ledc_reset(TiLp55231 *ledc)
{
	uint8_t data;

	data = LP55231_RESET_VALUE;
	ledc_write(ledc, LP55231_RESET_REG, &data, 1);

	/*
	 * This read is not necessary for the chip reset, but is required to
	 * work around the i2c driver bug where the missing ACK on the last
	 * byte of the write transaction is ignored, but the next transaction
	 * fails.
	 */
	ledc_read(ledc, LP55231_RESET_REG, &data);
}

/*
 * Write a program into the internal lp55231 memory. Split write transactions
 * into sections fitting into memory pages.
 */
static void ledc_write_program(TiLp55231 *ledc, uint8_t load_addr,
			       const uint8_t *program, unsigned count)
{
	uint8_t page_num = load_addr / LP55231_PROG_PAGE_SIZE;
	unsigned page_offs = load_addr % LP55231_PROG_PAGE_SIZE;

	if ((load_addr + count) > LP55231_MAX_PROG_SIZE) {
		printk(BIOS_WARNING,
		       "%s: program of size %#x does not fit at addr %#x\n",
		       __func__, count, load_addr);
		return;
	}

	while (count) {
		unsigned segment_size = LP55231_PROG_PAGE_SIZE - page_offs;

		if (segment_size > count)
			segment_size = count;

		ledc_write(ledc, LP55231_PROG_PAGE_REG, &page_num, 1);
		ledc_write(ledc, LP55231_PROG_BASE_REG + page_offs,
			   program, segment_size);

		count -= segment_size;
		page_offs = 0;
		page_num++;
	}
}

/* Run an lp55231 program on a controller. */
static void ledc_run_program(TiLp55231 *ledc,
			     const TiLp55231Program *program_desc)
{
	uint8_t data;
	int i;

	data = 0;
	ledc_write(ledc, LP55231_ENGCTRL2_REG, &data, 1);
	data = 0x15;
	ledc_write(ledc, LP55231_ENGCTRL2_REG, &data, 1);
	ledc_write_program(ledc, program_desc->load_addr,
			   program_desc->program_text,
			   program_desc->program_size);

	for (i = 0; i < sizeof(program_desc->engine_start_addr); i++)
		ledc_write(ledc, LP55231_ENG1_PROG_START + i,
			   program_desc->engine_start_addr + i, 1);

	data = 0;
	ledc_write(ledc, LP55231_ENGCTRL2_REG, &data, 1);
	data = 0x2a;
	ledc_write(ledc, LP55231_ENGCTRL2_REG, &data, 1);
}

/*
 * Initialize a controller to a state were it is ready to accept programs, and
 * try to confirm that we are in fact talking to a lp55231
 */
static int ledc_init_validate(TiLp55231 *ledc)
{
	const uint8_t ctrl1_reset[] = {
		0,
		LP55231_ENGCTRL1_CHIP_EN,
		LP55231_ENGCTRL1_CHIP_EN | LP55231_ENGCTRL1_ALL_ENG_GO
	};
	uint8_t data;
	int i;

	ledc_reset(ledc);

	/* Set up all engines to run. */
	for (i = 0; i < ARRAY_SIZE(ctrl1_reset); i++)
		ledc_write(ledc, LP55231_ENGCTRL1_REG, ctrl1_reset + i, 1);

	/*
	 * Internal clock, 3.3V output (pump 1x), autoincrement on multibyte
	 * writes.
	 */
	data = LP55231_MISC_AUTOINCR |
		LP55231_MISC_PUMP_1X | LP55231_MISC_INT_CLK;
	ledc_write(ledc, LP55231_MISC_REG, &data, 1);

	/*
	 * All nine current control registers are supposed to return the same
	 * value at reset.
	 */
	for (i = 0; i < 9; i++) {
		data = 0;
		ledc_read(ledc, LP55231_D1_CRT_CTRL_REG + i, &data);
		if (data != LP55231_CRT_CTRL_DEFAULT) {
			printk(BIOS_WARNING,
			       "%s: read %#2.2x from register %#x\n", __func__,
			       data, LP55231_D1_CRT_CTRL_REG + i);
			return -1;
		}
	}

	return 0;
}

/*
 * Find a program matching screen type, and run it on both controllers, if
 * found.
 */
int ww_ring_display_pattern(unsigned i2c_bus, enum VbScreenType_t screen_type)
{
	int i;
	static int initted;

	if (!initted) {
		ww_ring_init(i2c_bus);
		initted = 1;
	}

	for (i = 0; i < ARRAY_SIZE(state_programs); i++)
		if (state_programs[i].vb_screen == screen_type) {
			int j;
			for (j = 0; j < WW_RING_NUM_LED_CONTROLLERS; j++)
				ledc_run_program(lp55231s + j,
						 state_programs[i].programs[j]);
			return 0;
		}

	printk(BIOS_WARNING, "%s: did not find program for screen %d\n",
	       __func__, screen_type);

	return -1;
}


#define LP55231_I2C_BASE_ADDR 0x32

static void ww_ring_init(unsigned i2c_bus)
{
	TiLp55231 *ledc;
	int i, count = 0;

	for (i = 0, ledc = lp55231s;
	     i < WW_RING_NUM_LED_CONTROLLERS;
	     i++, ledc++) {

		ledc->i2c_bus = i2c_bus;
		ledc->dev_addr = LP55231_I2C_BASE_ADDR + i;

		if (!ledc_init_validate(ledc))
			count++;
	}

	printk(BIOS_INFO, "WW_RING: initialized %d out of %d\n", count, i);
	if (count != i)
		printk(BIOS_WARNING, "WW_RING: will keep going anyway\n");
}