[coreboot-gerrit] Patch set updated for coreboot: 951eb6a i2c: add support for ww_ring

Patrick Georgi (pgeorgi@google.com) gerrit at coreboot.org
Tue Apr 21 11:18:37 CEST 2015


Patrick Georgi (pgeorgi at google.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/9858

-gerrit

commit 951eb6a732d90bc98ab34fe0191666d186382a65
Author: Vadim Bendebury <vbendeb at chromium.org>
Date:   Mon Mar 9 15:50:56 2015 -0700

    i2c: add support for ww_ring
    
    This is a copy of the depthcharge ww ring driver implementation ported
    into coreboot. The main differences are:
    
     - direct use of the i2c driver instead of using the callback driver
       description
    
     - no dynamic memory allocation for the controller structures
    
    BRANCH=storm
    BUG=chrome-os-partner:36059
    TEST=with the rest of the patches applied the LED ring gets
         initialized to the default pattern at coreboot start.
    
    Change-Id: I6902c8b76fc173ad2ec28b8cc94695e892df338a
    Signed-off-by: Patrick Georgi <pgeorgi at chromium.org>
    Original-Commit-Id: eda24b78f8aff311dd6296d458bdfecf26c3d65a
    Original-Change-Id: I5660dc3f255aab8fbe3a87041c72916a645c193b
    Original-Signed-off-by: Vadim Bendebury <vbendeb at chromium.org>
    Original-Reviewed-on: https://chromium-review.googlesource.com/257730
    Original-Reviewed-by: Aaron Durbin <adurbin at chromium.org>
---
 src/drivers/i2c/Kconfig              |   3 +-
 src/drivers/i2c/Makefile.inc         |   5 +-
 src/drivers/i2c/ww_ring/Kconfig      |   3 +
 src/drivers/i2c/ww_ring/Makefile.inc |   1 +
 src/drivers/i2c/ww_ring/ww_ring.c    | 401 +++++++++++++++++++++++++++++++++++
 src/drivers/i2c/ww_ring/ww_ring.h    |  30 +++
 6 files changed, 440 insertions(+), 3 deletions(-)

diff --git a/src/drivers/i2c/Kconfig b/src/drivers/i2c/Kconfig
index 5c55c44..1949fa6d 100644
--- a/src/drivers/i2c/Kconfig
+++ b/src/drivers/i2c/Kconfig
@@ -6,5 +6,6 @@ source src/drivers/i2c/i2cmux2/Kconfig
 source src/drivers/i2c/lm63/Kconfig
 source src/drivers/i2c/rtd2132/Kconfig
 source src/drivers/i2c/tpm/Kconfig
-source src/drivers/i2c/w83795/Kconfig
 source src/drivers/i2c/w83793/Kconfig
+source src/drivers/i2c/w83795/Kconfig
+source src/drivers/i2c/ww_ring/Kconfig
diff --git a/src/drivers/i2c/Makefile.inc b/src/drivers/i2c/Makefile.inc
index 81ca409..c69dc02 100644
--- a/src/drivers/i2c/Makefile.inc
+++ b/src/drivers/i2c/Makefile.inc
@@ -1,11 +1,12 @@
 subdirs-y += adm1026
 subdirs-y += adm1027
 subdirs-y += adt7463
+subdirs-y += at24rf08c
 subdirs-y += i2cmux
 subdirs-y += i2cmux2
 subdirs-y += lm63
 subdirs-y += rtd2132
 subdirs-y += tpm
-subdirs-y += w83795
 subdirs-y += w83793
-subdirs-y += at24rf08c
+subdirs-y += w83795
+subdirs-y += ww_ring
diff --git a/src/drivers/i2c/ww_ring/Kconfig b/src/drivers/i2c/ww_ring/Kconfig
new file mode 100644
index 0000000..67a12eb
--- /dev/null
+++ b/src/drivers/i2c/ww_ring/Kconfig
@@ -0,0 +1,3 @@
+config DRIVERS_I2C_WW_RING
+	bool
+	depends on CHROMEOS
diff --git a/src/drivers/i2c/ww_ring/Makefile.inc b/src/drivers/i2c/ww_ring/Makefile.inc
new file mode 100644
index 0000000..963411a
--- /dev/null
+++ b/src/drivers/i2c/ww_ring/Makefile.inc
@@ -0,0 +1 @@
+verstage-$(CONFIG_DRIVERS_I2C_WW_RING) += ww_ring.c
diff --git a/src/drivers/i2c/ww_ring/ww_ring.c b/src/drivers/i2c/ww_ring/ww_ring.c
new file mode 100644
index 0000000..b074576
--- /dev/null
+++ b/src/drivers/i2c/ww_ring/ww_ring.c
@@ -0,0 +1,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");
+}
diff --git a/src/drivers/i2c/ww_ring/ww_ring.h b/src/drivers/i2c/ww_ring/ww_ring.h
new file mode 100644
index 0000000..628cd6c
--- /dev/null
+++ b/src/drivers/i2c/ww_ring/ww_ring.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef __SRC_DRIVERS_VIDEO_WW_RING__H__
+#define __SRC_DRIVERS_VIDEO_WW_RING__H__
+
+#if IS_ENABLED(CONFIG_CHROMEOS)
+#include <vboot_api.h>
+
+/*
+ * ww_ring_display_pattern
+ *
+ * Display pattern on the ring LEDs.
+ */
+int ww_ring_display_pattern(unsigned i2c_bus, enum VbScreenType_t screen_type);
+
+#else
+static inline int ww_ring_display_pattern(unsigned i2c_bus, int screen_type) { return 0;}
+#endif
+#endif



More information about the coreboot-gerrit mailing list