summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaisuke Nojiri <dnojiri@chromium.org>2021-12-04 13:08:04 -0800
committerCommit Bot <commit-bot@chromium.org>2022-03-01 19:41:59 +0000
commit14b34644fe16349af348d21f20d3897707132896 (patch)
tree1439a332e77c942d809b2ebfc128d690d72fe8e8
parent10a7a0a72017fe15633fa563977b748e88ec8726 (diff)
downloadchrome-ec-14b34644fe16349af348d21f20d3897707132896.tar.gz
RGBKBD: Add RGB Keyboard task
This patch introduces a RGB keyboard task and a IS31FL3743B driver. BUG=b:203664745, b:213921985, b:199995751 BRANCH=None TEST=Vell and unit test. Change-Id: Iefc1714efca9a4dc70db5a024d1ab020ec7b69b6 Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/3342506 Reviewed-by: Jack Rosenthal <jrosenth@chromium.org> Reviewed-by: Aaron Massey <aaronmassey@google.com>
-rw-r--r--common/build.mk1
-rw-r--r--common/rgb_keyboard.c315
-rw-r--r--driver/build.mk1
-rw-r--r--driver/led/is31fl3743b.c189
-rw-r--r--include/config.h24
-rw-r--r--include/console_channel.inc3
-rw-r--r--include/ec_commands.h15
-rw-r--r--include/rgb_keyboard.h121
-rw-r--r--test/build.mk2
-rw-r--r--test/rgb_keyboard.c348
-rw-r--r--test/rgb_keyboard.tasklist10
-rw-r--r--test/test_config.h5
-rw-r--r--zephyr/CMakeLists.txt2
-rw-r--r--zephyr/Kconfig.keyboard49
14 files changed, 1085 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk
index 25dbce2333..3c5c90faff 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -133,6 +133,7 @@ common-$(CONFIG_PSTORE)+=pstore_commands.o
common-$(CONFIG_PWM)+=pwm.o
common-$(CONFIG_PWM_KBLIGHT)+=pwm_kblight.o
common-$(CONFIG_KEYBOARD_BACKLIGHT)+=keyboard_backlight.o
+common-$(CONFIG_RGB_KEYBOARD)+=rgb_keyboard.o
common-$(CONFIG_RSA)+=rsa.o
common-$(CONFIG_ROLLBACK)+=rollback.o
common-$(CONFIG_RWSIG)+=rwsig.o vboot/common.o
diff --git a/common/rgb_keyboard.c b/common/rgb_keyboard.c
new file mode 100644
index 0000000000..2a2e05b43a
--- /dev/null
+++ b/common/rgb_keyboard.c
@@ -0,0 +1,315 @@
+/* Copyright 2022 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.
+ */
+#include <stdbool.h>
+
+#include "atomic.h"
+#include "common.h"
+#include "console.h"
+#include "ec_commands.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "registers.h"
+#include "rgb_keyboard.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_RGBKBD, outstr)
+#define CPRINTF(fmt, args...) cprintf(CC_RGBKBD, "RGBKBD: " fmt, ##args)
+#define CPRINTS(fmt, args...) cprints(CC_RGBKBD, "RGBKBD: " fmt, ##args)
+
+test_export_static enum rgbkbd_demo demo =
+#if defined(CONFIG_RGBKBD_DEMO_FLOW)
+ RGBKBD_DEMO_FLOW
+#elif defined(CONFIG_RGBKBD_DEMO_DOT)
+ RGBKBD_DEMO_DOT
+#else
+ RGBKBD_DEMO_OFF
+#endif
+ ;
+
+static int set_color_single(struct rgb_s color, int x, int y)
+{
+ struct rgbkbd *ctx = &rgbkbds[0];
+ uint8_t gid;
+ uint8_t col = 0;
+ uint8_t offset;
+
+ if (rgbkbd_hsize <= x || rgbkbd_vsize <= y) {
+ return EC_ERROR_OVERFLOW;
+ }
+
+ /* Search the grid where x belongs to. */
+ for (gid = 0; gid < rgbkbd_count; gid++, ctx++) {
+ if (x < col + ctx->cfg->col_len)
+ break;
+ col += ctx->cfg->col_len;
+ }
+
+ offset = ctx->cfg->row_len * (x - col) + y;
+ ctx->buf[offset] = color;
+
+ CPRINTS("Set [%d,%d] to color=[%d,%d,%d] (gid=%u offset=%u)",
+ x, y, color.r, color.g, color.b, gid, offset);
+ return ctx->cfg->drv->set_color(ctx, offset, &ctx->buf[offset], 1);
+}
+
+test_export_static uint8_t get_grid_size(const struct rgbkbd *ctx)
+{
+ return ctx->cfg->col_len * ctx->cfg->row_len;
+}
+
+static void sync_grids(void)
+{
+ struct rgbkbd *ctx;
+ uint8_t len;
+ int i;
+
+ for (i = 0; i < rgbkbd_count; i++) {
+ ctx = &rgbkbds[i];
+ len = get_grid_size(ctx);
+ ctx->cfg->drv->set_color(ctx, 0, ctx->buf, len);
+ }
+}
+
+test_export_static struct rgb_s rotate_color(struct rgb_s color, int step)
+{
+ color.r += step;
+ if (color.r == 0) {
+ color.g += step;
+ if (color.g == 0) {
+ color.b += step;
+ }
+ }
+
+ return color;
+}
+
+static void rgbkbd_reset_color(struct rgb_s color)
+{
+ struct rgbkbd *ctx;
+ int i, j;
+
+ for (i = 0; i < rgbkbd_count; i++) {
+ ctx = &rgbkbds[i];
+ for (j = 0; j < get_grid_size(ctx); j++)
+ ctx->buf[j] = color;
+ }
+
+ sync_grids();
+}
+
+static void rgbkbd_demo_flow(void)
+{
+ struct rgbkbd *ctx = &rgbkbds[0];
+ static struct rgb_s color;
+ uint8_t len;
+ int i, g;
+
+ for (g = rgbkbd_count - 1; g >= 0; g--) {
+ ctx = &rgbkbds[g];
+ len = get_grid_size(ctx);
+ for (i = len - 1; i > 0; i--)
+ ctx->buf[i] = ctx->buf[i - 1];
+ if (g > 0) {
+ /* Copy the last dot of the g-1 grid to the 1st. */
+ len = get_grid_size(&rgbkbds[g - 1]);
+ ctx->buf[0] = rgbkbds[g - 1].buf[len - 1];
+ }
+ }
+
+ /* Create a new color by shifting R by <step>. */
+ color = rotate_color(color, 32);
+
+ /* Finally, insert a new color to (0, 0). */
+ ctx->buf[0] = color;
+
+ sync_grids();
+
+#ifdef TEST_BUILD
+ task_wake(TASK_ID_TEST_RUNNER);
+#else
+ msleep(250);
+#endif
+}
+
+static void rgbkbd_demo_dot(void)
+{
+ static struct rgb_s color = { 0x80, 0, 0 };
+ const struct rgb_s off = { 0, 0, 0 };
+ static uint8_t x, y;
+
+ /* Turn off previous dot. */
+ set_color_single(off, x, y);
+
+ /* Move position. */
+ y++;
+ if (y >= rgbkbd_vsize) {
+ y = 0;
+ x++;
+ if (x >= rgbkbd_hsize) {
+ x = 0;
+ color = rotate_color(color, 0x80);
+ }
+ }
+
+ /* Turn on next dot. */
+ set_color_single(color, x, y);
+
+#ifdef TEST_BUILD
+ task_wake(TASK_ID_TEST_RUNNER);
+#else
+ msleep(250);
+#endif
+}
+
+static void rgbkbd_demo(enum rgbkbd_demo id)
+{
+ switch (id) {
+ case RGBKBD_DEMO_FLOW:
+ rgbkbd_demo_flow();
+ break;
+ case RGBKBD_DEMO_DOT:
+ rgbkbd_demo_dot();
+ break;
+ case RGBKBD_DEMO_OFF:
+ default:
+ break;
+ }
+}
+
+__overridable void board_enable_rgb_keyboard(bool enable) {}
+
+void rgbkbd_task(void *u)
+{
+ uint32_t event;
+ int i, rv;
+
+ board_enable_rgb_keyboard(true);
+
+ for (i = 0; i < rgbkbd_count; i++) {
+ struct rgbkbd *ctx = &rgbkbds[i];
+ rv = ctx->cfg->drv->init(ctx);
+ if (rv)
+ CPRINTS("Failed to init GRID%d (%d)", i, rv);
+ rv = ctx->cfg->drv->set_gcc(ctx, 0x80);
+ rv |= ctx->cfg->drv->set_scale(ctx, 0, 0x80,
+ get_grid_size(ctx));
+ if (rv)
+ CPRINTS("Failed to set GCC or scale (%d)", rv);
+ }
+
+ while (1) {
+ event = task_wait_event(100 * MSEC);
+ if (IS_ENABLED(CONFIG_RGB_KEYBOARD_DEBUG))
+ CPRINTS("event=0x%08x", event);
+ if (demo)
+ rgbkbd_demo(demo);
+ }
+}
+
+test_export_static int cc_rgbk(int argc, char **argv)
+{
+ struct rgbkbd *ctx;
+ char *end, *comma;
+ struct rgb_s color;
+ int gcc, x, y, val;
+ int i, rv = EC_SUCCESS;
+
+ if (argc < 2 || 5 < argc) {
+ return EC_ERROR_PARAM_COUNT;
+ }
+
+ comma = strstr(argv[1], ",");
+ if (comma && strlen(comma) > 1) {
+ /* Usage 2 */
+ /* Found ',' and more string after that. Split it into two. */
+ *comma = '\0';
+ x = strtoi(argv[1], &end, 0);
+ if (*end || x >= rgbkbd_hsize)
+ return EC_ERROR_PARAM1;
+ y = strtoi(comma + 1, &end, 0);
+ if (*end || y >= rgbkbd_vsize)
+ return EC_ERROR_PARAM1;
+ } else if (!strcasecmp(argv[1], "all")) {
+ /* Usage 3 */
+ x = -1;
+ y = -1;
+ } else if (!strcasecmp(argv[1], "demo")) {
+ /* Usage 4 */
+ val = strtoi(argv[2], &end, 0);
+ if (*end || val >= RGBKBD_DEMO_COUNT)
+ return EC_ERROR_PARAM1;
+ demo = val;
+ rgbkbd_reset_color((struct rgb_s){.r = 0, .g = 0, .b = 0});
+ ccprintf("Demo set to %d\n", demo);
+ return EC_SUCCESS;
+ } else {
+ /* Usage 1 */
+ if (argc != 2)
+ return EC_ERROR_PARAM_COUNT;
+ gcc = strtoi(argv[1], &end, 0);
+ if (*end || gcc < 0 || gcc > UINT8_MAX)
+ return EC_ERROR_PARAM1;
+ demo = RGBKBD_DEMO_OFF;
+ for (i = 0; i < rgbkbd_count; i++) {
+ ctx = &rgbkbds[i];
+ ctx->cfg->drv->set_gcc(ctx, gcc);
+ }
+ return EC_SUCCESS;
+ }
+
+ if (argc != 5)
+ return EC_ERROR_PARAM_COUNT;
+
+ val = strtoi(argv[2], &end, 0);
+ if (*end || val < 0 || val > UINT8_MAX)
+ return EC_ERROR_PARAM2;
+ color.r = val;
+ val = strtoi(argv[3], &end, 0);
+ if (*end || val < 0 || val > UINT8_MAX)
+ return EC_ERROR_PARAM3;
+ color.g = val;
+ val = strtoi(argv[4], &end, 0);
+ if (*end || val < 0 || val > UINT8_MAX)
+ return EC_ERROR_PARAM4;
+ color.b = val;
+
+ demo = RGBKBD_DEMO_OFF;
+ if (y < 0 && x < 0) {
+ /* Usage 3 */
+ rgbkbd_reset_color(color);
+ } else if (y < 0) {
+ /* Usage 2: Set all LEDs on column x. */
+ ccprintf("Set column %d to 0x%02x%02x%02x\n",
+ x, color.r, color.g, color.b);
+ for (i = 0; i < rgbkbd_vsize; i++)
+ rv = set_color_single(color, x, i);
+ } else if (x < 0) {
+ /* Usage 2: Set all LEDs on row y. */
+ ccprintf("Set row %d to 0x%02x%02x%02x\n",
+ y, color.r, color.g, color.b);
+ for (i = 0; i < rgbkbd_hsize; i++)
+ rv = set_color_single(color, i, y);
+ } else {
+ /* Usage 2 */
+ ccprintf("Set (%d,%d) to 0x%02x%02x%02x\n",
+ x, y, color.r, color.g, color.b);
+ rv = set_color_single(color, x, y);
+ }
+
+ return rv;
+}
+#ifndef TEST_BUILD
+DECLARE_CONSOLE_COMMAND(rgbk, cc_rgbk,
+ "\n"
+ "1. rgbk <global-brightness>\n"
+ "2. rgbk <col,row> <r-bright> <g-bright> <b-bright>\n"
+ "3. rgbk all <r-bright> <g-bright> <b-bright>\n"
+ "4. rgbk demo <id>\n",
+ "Set color of RGB keyboard"
+ );
+#endif
diff --git a/driver/build.mk b/driver/build.mk
index ce32cda4b0..a1927e7e82 100644
--- a/driver/build.mk
+++ b/driver/build.mk
@@ -110,6 +110,7 @@ driver-$(CONFIG_LED_DRIVER_LM3630A)+=led/lm3630a.o
driver-$(CONFIG_LED_DRIVER_LP5562)+=led/lp5562.o
driver-$(CONFIG_LED_DRIVER_MP3385)+=led/mp3385.o
driver-$(CONFIG_LED_DRIVER_OZ554)+=led/oz554.o
+driver-$(CONFIG_LED_DRIVER_IS31FL3743B)+=led/is31fl3743b.o
# 7-segment display
driver-$(CONFIG_MAX695X_SEVEN_SEGMENT_DISPLAY)+=led/max695x.o
diff --git a/driver/led/is31fl3743b.c b/driver/led/is31fl3743b.c
new file mode 100644
index 0000000000..f1a8833318
--- /dev/null
+++ b/driver/led/is31fl3743b.c
@@ -0,0 +1,189 @@
+/* Copyright 2022 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.
+ */
+#include <string.h>
+
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "rgb_keyboard.h"
+#include "spi.h"
+#include "stddef.h"
+#include "timer.h"
+
+#define CPRINTF(fmt, args...) cprintf(CC_RGBKBD, "RGBKBD: " fmt, ##args)
+#define CPRINTS(fmt, args...) cprints(CC_RGBKBD, "RGBKBD: " fmt, ##args)
+
+#define SPI(id) (&(spi_devices[id]))
+
+#define IS31FL3743B_ROW_SIZE 6
+#define IS31FL3743B_COL_SIZE 11
+#define IS31FL3743B_GRID_SIZE (IS31FL3743B_COL_SIZE * IS31FL3743B_ROW_SIZE)
+#define IS31FL3743B_BUF_SIZE (SIZE_OF_RGB * IS31FL3743B_GRID_SIZE)
+
+#define IS31FL3743B_CMD_ID 0b101
+#define IS31FL3743B_PAGE_PWM 0
+#define IS31FL3743B_PAGE_SCALE 1
+#define IS31FL3743B_PAGE_FUNC 2
+
+#define IS31FL3743B_REG_CONFIG 0x00
+#define IS31FL3743B_REG_GCC 0x01
+#define IS31FL3743B_REG_PD_PU 0x02
+#define IS31FL3743B_REG_SPREAD_SPECTRUM 0x25
+#define IS31FL3743B_REG_RSTN 0x2f
+
+struct is31fl3743b_cmd {
+ uint8_t page: 4;
+ uint8_t id: 3;
+ uint8_t read: 1;
+} __packed;
+
+struct is31fl3743b_msg {
+ struct is31fl3743b_cmd cmd;
+ uint8_t addr;
+ uint8_t payload[];
+} __packed;
+
+static int is31fl3743b_read(struct rgbkbd *ctx, uint8_t addr, uint8_t *value)
+{
+ uint8_t buf[8];
+ struct is31fl3743b_msg *msg = (void *)buf;
+ const int frame_len = sizeof(*msg);
+
+ msg->cmd.read = 1;
+ msg->cmd.id = IS31FL3743B_CMD_ID;
+ msg->cmd.page = IS31FL3743B_PAGE_FUNC;
+ msg->addr = addr;
+
+ return spi_transaction(SPI(ctx->cfg->spi), buf, frame_len, value, 1);
+}
+
+static int is31fl3743b_write(struct rgbkbd *ctx, uint8_t addr, uint8_t value)
+{
+ uint8_t buf[8];
+ struct is31fl3743b_msg *msg = (void *)buf;
+ const int frame_len = sizeof(*msg) + 1;
+
+ msg->cmd.read = 0;
+ msg->cmd.id = IS31FL3743B_CMD_ID;
+ msg->cmd.page = IS31FL3743B_PAGE_FUNC;
+ msg->addr = addr;
+ msg->payload[0] = value;
+
+ return spi_transaction(SPI(ctx->cfg->spi), buf, frame_len, NULL, 0);
+}
+
+static int is31fl3743b_reset(struct rgbkbd *ctx)
+{
+ return is31fl3743b_write(ctx, IS31FL3743B_REG_RSTN, 0xae);
+}
+
+static int is31fl3743b_enable(struct rgbkbd *ctx, bool enable)
+{
+ uint8_t u8;
+ int rv;
+
+ gpio_set_level(GPIO_RGBKBD_SDB_L, enable ? 1 : 0);
+
+ rv = is31fl3743b_read(ctx, IS31FL3743B_REG_CONFIG, &u8);
+ if (rv) {
+ return rv;
+ }
+
+ return is31fl3743b_write(ctx, IS31FL3743B_REG_CONFIG,
+ u8 | BIT(3) | (enable ? BIT(0) : 0));
+}
+
+static int is31fl3743b_set_color(struct rgbkbd *ctx, uint8_t offset,
+ struct rgb_s *color, uint8_t len)
+{
+ uint8_t buf[sizeof(struct is31fl3743b_msg) + IS31FL3743B_BUF_SIZE];
+ struct is31fl3743b_msg *msg = (void *)buf;
+ const int frame_len = len * SIZE_OF_RGB + sizeof(*msg);
+ const int frame_offset = offset * SIZE_OF_RGB;
+ int i;
+
+ msg->cmd.read = 0;
+ msg->cmd.id = IS31FL3743B_CMD_ID;
+ msg->cmd.page = IS31FL3743B_PAGE_PWM;
+
+ if (frame_offset + frame_len > sizeof(buf)) {
+ return EC_ERROR_OVERFLOW;
+ }
+
+ msg->addr = frame_offset + 1; /* Register addr base is 1. */
+ for (i = 0; i < len; i++) {
+ msg->payload[i * SIZE_OF_RGB +0] = color[i].r;
+ msg->payload[i * SIZE_OF_RGB +1] = color[i].g;
+ msg->payload[i * SIZE_OF_RGB +2] = color[i].b;
+ }
+
+ return spi_transaction(SPI(ctx->cfg->spi), buf, frame_len, NULL, 0);
+}
+
+static int is31fl3743b_set_scale(struct rgbkbd *ctx, uint8_t offset,
+ uint8_t scale, uint8_t len)
+{
+ uint8_t buf[sizeof(struct is31fl3743b_msg) + IS31FL3743B_BUF_SIZE];
+ struct is31fl3743b_msg *msg = (void *)buf;
+ const int frame_len = len * SIZE_OF_RGB + sizeof(*msg);
+
+ msg->cmd.read = 0;
+ msg->cmd.id = IS31FL3743B_CMD_ID;
+ msg->cmd.page = IS31FL3743B_PAGE_SCALE;
+
+ if (offset + frame_len > sizeof(buf)) {
+ return EC_ERROR_OVERFLOW;
+ }
+
+ msg->addr = offset + 1; /* Address base is 1. */
+ memset(msg->payload, scale, len * SIZE_OF_RGB);
+
+ return spi_transaction(SPI(ctx->cfg->spi), buf, frame_len, NULL, 0);
+}
+
+static int is31fl3743b_set_gcc(struct rgbkbd *ctx, uint8_t level)
+{
+ uint8_t buf[8];
+ struct is31fl3743b_msg *msg = (void *)buf;
+ const int frame_len = sizeof(*msg) + 1;
+
+ msg->cmd.read = 0;
+ msg->cmd.id = IS31FL3743B_CMD_ID;
+ msg->cmd.page = IS31FL3743B_PAGE_FUNC;
+ msg->addr = IS31FL3743B_REG_GCC;
+ msg->payload[0] = level;
+
+ return spi_transaction(SPI(ctx->cfg->spi), buf, frame_len, NULL, 0);
+}
+
+static int is31fl3743b_init(struct rgbkbd *ctx)
+{
+ int rv;
+
+ rv = is31fl3743b_reset(ctx);
+ msleep(3);
+ rv |= is31fl3743b_enable(ctx, true);
+ if (rv) {
+ CPRINTS("Failed to enable or reset (%d)", rv);
+ return rv;
+ }
+
+ if (IS_ENABLED(CONFIG_RGB_KEYBOARD_DEBUG)) {
+ uint8_t val;
+ rv = is31fl3743b_read(ctx, IS31FL3743B_REG_PD_PU, &val);
+ CPRINTS("PD/PU. val=0x%02x (rv=%d)", val, rv);
+ }
+
+ return EC_SUCCESS;
+}
+
+const struct rgbkbd_drv is31fl3743b_drv = {
+ .reset = is31fl3743b_reset,
+ .init = is31fl3743b_init,
+ .enable = is31fl3743b_enable,
+ .set_color = is31fl3743b_set_color,
+ .set_scale = is31fl3743b_set_scale,
+ .set_gcc = is31fl3743b_set_gcc,
+};
diff --git a/include/config.h b/include/config.h
index f7857f034b..576480f250 100644
--- a/include/config.h
+++ b/include/config.h
@@ -3114,6 +3114,7 @@
#undef CONFIG_LED_DRIVER_LP5562 /* LP5562, on I2C interface */
#undef CONFIG_LED_DRIVER_MP3385 /* MPS MP3385, on I2C */
#undef CONFIG_LED_DRIVER_OZ554 /* O2Micro OZ554, on I2C */
+#undef CONFIG_LED_DRIVER_IS31FL3743B /* Lumissil IS31FL3743B on SPI */
/* Offset in flash where little firmware will live. */
#undef CONFIG_LFW_OFFSET
@@ -3635,6 +3636,29 @@
*/
#undef CONFIG_KBLIGHT_ENABLE_PIN
+/*
+ * RGB Keyboard
+ */
+#undef CONFIG_RGB_KEYBOARD
+
+/*
+ * Enable debug messages from a RGB keyboard task.
+ */
+#undef CONFIG_RGB_KEYBOARD_DEBUG
+
+/*
+ * Enable demo for RGB keyboard to run on reset.
+ *
+ * FLOW: In each iteration, a new color is placed in (0,0) and the rest of LEDs
+ * copy colors from adjacent LEDs.
+ *
+ * DOT: A red dot is placed on (0,0) and traverses the grid from top to bottom
+ * left to right. After the entire matrix is traversed, it's repeated with a
+ * new color.
+ */
+#undef CONFIG_RGBKBD_DEMO_FLOW
+#undef CONFIG_RGBKBD_DEMO_DOT
+
/* Support Real-Time Clock (RTC) */
#undef CONFIG_RTC
diff --git a/include/console_channel.inc b/include/console_channel.inc
index 0a6d84509c..96691c21c3 100644
--- a/include/console_channel.inc
+++ b/include/console_channel.inc
@@ -62,6 +62,9 @@ CONSOLE_CHANNEL(CC_LOGOLED, "logoled")
#ifdef HAS_TASK_LIGHTBAR
CONSOLE_CHANNEL(CC_LIGHTBAR, "lightbar")
#endif
+#ifdef HAS_TASK_RGBKBD
+CONSOLE_CHANNEL(CC_RGBKBD, "rgbkbd")
+#endif
/*
Use CC_LPC for any host communication channels to x86,
like LPC, eSPI, or IPC...
diff --git a/include/ec_commands.h b/include/ec_commands.h
index 99fbddd3f2..919215a085 100644
--- a/include/ec_commands.h
+++ b/include/ec_commands.h
@@ -7074,6 +7074,21 @@ struct ec_response_i2c_control {
} cmd_response;
} __ec_align_size1;
+#define EC_CMD_RGB_KEYBOARD 0x13a
+
+enum rgbkbd_state {
+ /* RGB keyboard is reset and not initialized. */
+ RGBKBD_STATE_RESET = 0,
+ /* RGB keyboard is initialized but not enabled. */
+ RGBKBD_STATE_INITIALIZED,
+ /* RGB keyboard is enabled and ready to receive a command. */
+ RGBKBD_STATE_ENABLED,
+
+ /* Put no more entry below */
+ RGBKBD_STATE_COUNT,
+};
+
+
/*****************************************************************************/
/* The command range 0x200-0x2FF is reserved for Rotor. */
diff --git a/include/rgb_keyboard.h b/include/rgb_keyboard.h
new file mode 100644
index 0000000000..793ee62f48
--- /dev/null
+++ b/include/rgb_keyboard.h
@@ -0,0 +1,121 @@
+/* Copyright 2022 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.
+ */
+
+#include <stdbool.h>
+
+#include "common.h"
+#include "ec_commands.h"
+
+/* Use this instead of '3' for readability where applicable. */
+#define SIZE_OF_RGB sizeof(struct rgb_s)
+
+enum rgbkbd_demo {
+ RGBKBD_DEMO_OFF = 0,
+ RGBKBD_DEMO_FLOW = 1,
+ RGBKBD_DEMO_DOT = 2,
+ RGBKBD_DEMO_COUNT
+};
+
+struct rgbkbd_cfg {
+ /* Driver for LED IC */
+ const struct rgbkbd_drv * const drv;
+ /* SPI/I2C port (i.e. index of spi_devices[], i2c_ports[]) */
+ union {
+ const uint8_t i2c;
+ const uint8_t spi;
+ };
+ /* Grid size */
+ const uint8_t col_len;
+ const uint8_t row_len;
+};
+
+struct rgbkbd {
+ /* Static configuration */
+ const struct rgbkbd_cfg * const cfg;
+ /* Current state of the port */
+ enum rgbkbd_state state;
+ /* Buffer containing color info for each dot. */
+ struct rgb_s *buf;
+};
+
+struct rgbkbd_drv {
+ /* Reset charger chip. */
+ int (*reset)(struct rgbkbd *ctx);
+ /* Initialize the charger. */
+ int (*init)(struct rgbkbd *ctx);
+ /* Enable/disable the charger. Usually disabled means stand-by. */
+ int (*enable)(struct rgbkbd *ctx, bool enable);
+
+ /**
+ * Set the colors of multiple RGB-LEDs.
+ *
+ * @param ctx Context.
+ * @param offset Starting LED position.
+ * @param color Array of colors to set. Must be as long as <len>.
+ * @param len Length of <color> array.
+ * @return enum ec_error_list.
+ */
+ int (*set_color)(struct rgbkbd *ctx, uint8_t offset,
+ struct rgb_s *color, uint8_t len);
+ /**
+ * Set the scale of multiple LEDs
+ *
+ * @param ctx Context.
+ * @param offset Starting LED position.
+ * @param scale Scale to be set.
+ * @param len Length of LEDs to be set.
+ * @return enum ec_error_list
+ */
+ int (*set_scale)(struct rgbkbd *ctx, uint8_t offset,
+ uint8_t scale, uint8_t len);
+ /**
+ * Set global current control.
+ *
+ * @param level Global current control to set.
+ * @return enum ec_error_list.
+ */
+ int (*set_gcc)(struct rgbkbd *ctx, uint8_t level);
+};
+
+/*
+ * The matrix consists of multiple grids:
+ *
+ * +=========-== Matrix =============+
+ * | +--- Grid1 ---+ +--- Grid2 ---+ | ^
+ * | | A C | | E | | |
+ * | | B | | | | rgbkbd_vsize
+ * | | D | | | | |
+ * | +-------------+ +-------------+ | v
+ * +=================================+
+ *
+ * <-------- rgbkbd_hsize ------->
+ *
+ * Grids are assumed to be horizontally adjacent. That is, the matrix row size
+ * is fixed and the matrix column size is a multiple of the grid's column size.
+ *
+ * Grid coordinate format is (column, row). In the diagram above,
+ * A = (0, 0)
+ * B = (0, 1)
+ * C = (1, 0)
+ *
+ * In each grid, LEDs are also sequentially indexed. That is,
+ * A = 0
+ * B = 1
+ * C = rgbkbd_vsize
+ * E = 0
+ *
+ * Matrix coordinate format is (x, y), where x is a horizontal position and y
+ * is a vertical position.
+ * E = (grid0_hsize, 0)
+ */
+extern struct rgbkbd rgbkbds[];
+extern const uint8_t rgbkbd_count;
+extern const uint8_t rgbkbd_hsize;
+extern const uint8_t rgbkbd_vsize;
+
+/*
+ * Called to power on or off the RGB keyboard module.
+ */
+__override_proto void board_enable_rgb_keyboard(bool enable);
diff --git a/test/build.mk b/test/build.mk
index 7051e98496..e57f6e5c8e 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -67,6 +67,7 @@ test-list-host += pingpong
test-list-host += power_button
test-list-host += printf
test-list-host += queue
+test-list-host += rgb_keyboard
test-list-host += rsa
test-list-host += rsa3
test-list-host += rtc
@@ -192,6 +193,7 @@ motion_lid-y=motion_lid.o
motion_sense_fifo-y=motion_sense_fifo.o
online_calibration-y=online_calibration.o
online_calibration_spoof-y=online_calibration_spoof.o gyro_cal_init_for_test.o
+rgb_keyboard-y=rgb_keyboard.o
kasa-y=kasa.o
mpu-y=mpu.o
mutex-y=mutex.o
diff --git a/test/rgb_keyboard.c b/test/rgb_keyboard.c
new file mode 100644
index 0000000000..2c36bf9030
--- /dev/null
+++ b/test/rgb_keyboard.c
@@ -0,0 +1,348 @@
+/* Copyright 2022 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.
+ *
+ * Tests for RGB keyboard.
+ */
+#include <stdio.h>
+
+#include "common.h"
+#include "console.h"
+#include "rgb_keyboard.h"
+#include "task.h"
+#include "test_util.h"
+#include "timer.h"
+#include "util.h"
+
+#define RGB_GRID0_COL 11
+#define RGB_GRID0_ROW 6
+#define RGB_GRID1_COL 11
+#define RGB_GRID1_ROW 6
+#define SPI_RGB0_DEVICE_ID 0
+#define SPI_RGB1_DEVICE_ID 1
+
+static struct rgb_s grid0[RGB_GRID0_COL * RGB_GRID0_ROW];
+static struct rgb_s grid1[RGB_GRID1_COL * RGB_GRID1_ROW];
+
+const struct rgbkbd_drv test_drv;
+
+struct rgbkbd rgbkbds[] = {
+ [0] = {
+ .cfg = &(const struct rgbkbd_cfg) {
+ .drv = &test_drv,
+ .spi = SPI_RGB0_DEVICE_ID,
+ .col_len = RGB_GRID0_COL,
+ .row_len = RGB_GRID0_ROW,
+ },
+ .buf = grid0,
+ },
+ [1] = {
+ .cfg = &(const struct rgbkbd_cfg) {
+ .drv = &test_drv,
+ .spi = SPI_RGB1_DEVICE_ID,
+ .col_len = RGB_GRID1_COL,
+ .row_len = RGB_GRID1_ROW,
+ },
+ .buf = grid1,
+ },
+};
+const uint8_t rgbkbd_count = ARRAY_SIZE(rgbkbds);
+const uint8_t rgbkbd_hsize = RGB_GRID0_COL + RGB_GRID1_COL;
+const uint8_t rgbkbd_vsize = RGB_GRID0_ROW;
+
+static struct rgbkbd_mock {
+ uint32_t count_drv_reset;
+ uint32_t count_drv_init;
+ uint32_t count_drv_enable;
+ uint32_t count_drv_set_color;
+ uint32_t count_drv_set_scale;
+ uint32_t count_drv_set_gcc;
+ uint32_t gcc_level;
+} mock_state;
+
+void before_test(void)
+{
+ memset(&mock_state, 0, sizeof(mock_state));
+}
+
+static int test_drv_reset(struct rgbkbd *ctx)
+{
+ mock_state.count_drv_reset++;
+ return EC_SUCCESS;
+}
+
+static int test_drv_init(struct rgbkbd *ctx)
+{
+ mock_state.count_drv_init++;
+ return EC_SUCCESS;
+}
+
+static int test_drv_enable(struct rgbkbd *ctx, bool enable)
+{
+ mock_state.count_drv_enable++;
+ return EC_SUCCESS;
+}
+
+static int test_drv_set_color(struct rgbkbd *ctx, uint8_t offset,
+ struct rgb_s *color, uint8_t len)
+{
+ mock_state.count_drv_set_color++;
+ return EC_SUCCESS;
+}
+
+
+static int test_drv_set_scale(struct rgbkbd *ctx, uint8_t offset,
+ uint8_t scale, uint8_t len)
+{
+ mock_state.count_drv_set_scale++;
+ return EC_SUCCESS;
+}
+
+static int test_drv_set_gcc(struct rgbkbd *ctx, uint8_t level)
+{
+ mock_state.count_drv_set_gcc++;
+ mock_state.gcc_level = level;
+ return EC_SUCCESS;
+}
+
+const struct rgbkbd_drv test_drv = {
+ .reset = test_drv_reset,
+ .init = test_drv_init,
+ .enable = test_drv_enable,
+ .set_color = test_drv_set_color,
+ .set_scale = test_drv_set_scale,
+ .set_gcc = test_drv_set_gcc,
+};
+
+static int test_rgbkbd_startup(void)
+{
+ struct rgbkbd *ctx;
+ struct rgb_s color;
+ int g, x, y, c, r;
+
+ /* Let RGBKBD task run. */
+ task_wait_event(-1);
+
+ zassert_equal(mock_state.count_drv_init, rgbkbd_count,
+ "init() called");
+ zassert_equal(mock_state.count_drv_set_gcc, rgbkbd_count,
+ "set_gcc() called");
+ zassert_equal(mock_state.count_drv_set_scale, rgbkbd_count,
+ "set_scale() called");
+
+ /* Check 'DOT' demo. */
+ for (x = 0; x < rgbkbd_hsize; x++) {
+ g = x / rgbkbds[0].cfg->col_len;
+ c = x % rgbkbds[0].cfg->col_len;
+ ctx = &rgbkbds[g];
+ for (y = 0; y < ctx->cfg->row_len; y++) {
+ r = y;
+ color = ctx->buf[ctx->cfg->row_len * c + r];
+ zassert_equal(color.r, 0, "R = 0");
+ zassert_equal(color.g, 0, "G = 0");
+ zassert_equal(color.b, 0, "B = 0");
+
+ r++;
+ if (r >= ctx->cfg->row_len) {
+ r = 0;
+ c++;
+ if (c >= rgbkbds[0].cfg->col_len) {
+ task_wait_event(-1);
+ break;
+ }
+ }
+ color = ctx->buf[ctx->cfg->row_len * c + r];
+ zassert_equal(color.r, 0x80, "R = 0x80");
+ zassert_equal(color.g, 0, "G = 0");
+ zassert_equal(color.b, 0, "B = 0");
+
+ task_wait_event(-1);
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+int cc_rgbk(int argc, char **argv);
+extern enum rgbkbd_demo demo;
+
+static int test_rgbkbd_console_command(void)
+{
+ struct rgbkbd *ctx;
+ int argc;
+ char buf[8];
+ int i, x, y, r, c;
+ uint8_t offset;
+ char *argv_demo[] = {"rgbk", "demo", "0"};
+ char *argv_gcc[] = {"rgbk", "100"};
+ char *argv_color[] = {"rgbk", buf, "1", "2", "3"};
+ char *argv_all[] = {"rgbk", "all", "1", "2", "3"};
+
+ /* Test 'rgbk demo 0'. */
+ before_test();
+ argc = ARRAY_SIZE(argv_demo);
+ zassert_equal(demo, 2, "demo == 2");
+ zassert_equal(cc_rgbk(argc, argv_demo), EC_SUCCESS, "rgbk demo 0");
+ zassert_equal(demo, 0, "demo == 0");
+
+ /* Test 'rgbk 100'. */
+ before_test();
+ argc = ARRAY_SIZE(argv_gcc);
+ zassert_equal(cc_rgbk(argc, argv_gcc), EC_SUCCESS, "rgbk 100");
+ zassert_equal(mock_state.count_drv_set_gcc, rgbkbd_count,
+ "set_gcc() called");
+ zassert_equal(mock_state.gcc_level, 100, "gcc == 100");
+
+ /* Test 'rgbk 1,1 1 2 3'. */
+ before_test();
+ ctx = &rgbkbds[0];
+ x = 1;
+ y = 1;
+ offset = rgbkbd_vsize * x + y;
+ sprintf(buf, "%d,%d", x, y);
+ argc = ARRAY_SIZE(argv_color);
+ zassert_equal(cc_rgbk(argc, argv_color), EC_SUCCESS,
+ "rgbk %s 1 2 3", buf);
+ zassert_equal(ctx->buf[offset].r, 1, "R = 1");
+ zassert_equal(ctx->buf[offset].g, 2, "G = 2");
+ zassert_equal(ctx->buf[offset].b, 3, "B = 3");
+
+ /* Test 'rgbk 1,-1 1 2 3'. */
+ before_test();
+ ctx = &rgbkbds[0];
+ x = 1;
+ y = -1;
+ sprintf(buf, "%d,%d", x, y);
+ argc = ARRAY_SIZE(argv_color);
+ zassert_equal(cc_rgbk(argc, argv_color), EC_SUCCESS,
+ "rgbk %s 1 2 3", buf);
+ for (r = 0; r < rgbkbd_vsize; r++) {
+ offset = rgbkbd_vsize * x + r;
+ zassert_equal(ctx->buf[offset].r, 1, "R = 1");
+ zassert_equal(ctx->buf[offset].g, 2, "G = 2");
+ zassert_equal(ctx->buf[offset].b, 3, "B = 3");
+ }
+
+ /* Test 'rgbk -1,1 1 2 3'. */
+ before_test();
+ x = -1;
+ y = 1;
+ sprintf(buf, "%d,%d", x, y);
+ argc = ARRAY_SIZE(argv_color);
+ zassert_equal(cc_rgbk(argc, argv_color), EC_SUCCESS,
+ "rgbk %s 1 2 3", buf);
+ for (c = 0; c < rgbkbd_hsize; c++) {
+ ctx = &rgbkbds[c / rgbkbds[0].cfg->col_len];
+ offset = rgbkbd_vsize * (c % ctx->cfg->col_len) + y;
+ zassert_equal(ctx->buf[offset].r, 1, "R = 1");
+ zassert_equal(ctx->buf[offset].g, 2, "G = 2");
+ zassert_equal(ctx->buf[offset].b, 3, "B = 3");
+ }
+
+ /* Test 'rgbk all 1 2 3'. */
+ before_test();
+ argc = ARRAY_SIZE(argv_all);
+ zassert_equal(cc_rgbk(argc, argv_all), EC_SUCCESS, "rgbk all 1 2 3");
+ for (i = 0; i < rgbkbd_count; i++) {
+ ctx = &rgbkbds[i];
+ for (c = 0; c < ctx->cfg->col_len; c++) {
+ for (r = 0; r < ctx->cfg->row_len; r++) {
+ offset = rgbkbd_vsize * c + r;
+ zassert_equal(ctx->buf[offset].r, 1, "R = 1");
+ zassert_equal(ctx->buf[offset].g, 2, "G = 2");
+ zassert_equal(ctx->buf[offset].b, 3, "B = 3");
+ }
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+struct rgb_s rotate_color(struct rgb_s color, int step);
+uint8_t get_grid_size(const struct rgbkbd *ctx);
+
+static int test_rgbkbd_rotate_color(void)
+{
+ struct rgb_s color = {};
+ const int step = 32;
+ int r, g, b;
+
+ for (b = 0; b < 0x100 / step; b++) {
+ zassert_equal(color.r, 0, "R = 0");
+ zassert_equal(color.g, 0, "G = 0");
+ zassert_equal(color.b, b * step, "B += 32");
+ for (g = 0; g < 0x100 / step; g++) {
+ zassert_equal(color.r, 0, "R = 0");
+ zassert_equal(color.g, g * step, "G += 32");
+ zassert_equal(color.b, b * step, "B = B");
+ for (r = 0; r < 0x100 / step; r++) {
+ zassert_equal(color.r, r * step, "R += 32");
+ zassert_equal(color.g, g * step, "G = G");
+ zassert_equal(color.b, b * step, "B = B");
+ color = rotate_color(color, step);
+ }
+ }
+ }
+ zassert_equal(color.r, 0, "R = 0");
+ zassert_equal(color.g, 0, "G = 0");
+ zassert_equal(color.b, 0, "B = 0");
+
+ return EC_SUCCESS;
+}
+
+static int test_rgbkbd_demo_flow(void)
+{
+ struct rgb_s copy[ARRAY_SIZE(rgbkbds)][RGB_GRID0_COL * RGB_GRID0_ROW];
+ char *argv_demo[] = {"rgbk", "demo", "1"};
+ struct rgb_s *p;
+ int argc;
+ struct rgbkbd *ctx;
+ struct rgb_s color = {};
+ const int step = 32;
+ uint8_t len;
+ int i, j, g;
+
+ argc = ARRAY_SIZE(argv_demo);
+ zassert_equal(cc_rgbk(argc, argv_demo), EC_SUCCESS, "rgbk demo flow");
+
+ for (j = 0; j < 0x100 / step; j++) {
+ /* Take a snapshot. */
+ memcpy(&copy[0], rgbkbds[0].buf, sizeof(copy[0]));
+ memcpy(&copy[1], rgbkbds[1].buf, sizeof(copy[1]));
+
+ /* Let demo run one iteration. */
+ task_wait_event(-1);
+
+ /* Compare with the snapshot. */
+ for (g = rgbkbd_count - 1; g >= 0; g--) {
+ ctx = &rgbkbds[g];
+ p = copy[g];
+ len = get_grid_size(ctx);
+ for (i = len - 1; i > 0; i--) {
+ zassert_equal(ctx->buf[i].r, p[i - 1].r,
+ "i <- i-1");
+ }
+ if (g > 0) {
+ len = get_grid_size(&rgbkbds[g - 1]);
+ p = copy[g - 1];
+ zassert_equal(ctx->buf[0].r, p[len - 1].r,
+ "grid1[0] <- grid0[last]");
+ }
+ }
+
+ /* After one run, a new color will be injected to (0,0). */
+ color = rotate_color(color, step);
+ zassert_equal(ctx->buf[0].r, color.r, "(0,0) <- new color");
+ }
+
+ return EC_SUCCESS;
+}
+
+void run_test(int argc, char **argv)
+{
+ RUN_TEST(test_rgbkbd_startup);
+ RUN_TEST(test_rgbkbd_console_command);
+ RUN_TEST(test_rgbkbd_rotate_color);
+ RUN_TEST(test_rgbkbd_demo_flow);
+ test_print_result();
+}
diff --git a/test/rgb_keyboard.tasklist b/test/rgb_keyboard.tasklist
new file mode 100644
index 0000000000..fb85751609
--- /dev/null
+++ b/test/rgb_keyboard.tasklist
@@ -0,0 +1,10 @@
+/* Copyright 2022 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.
+ */
+
+/**
+ * See CONFIG_TASK_LIST in config.h for details.
+ */
+#define CONFIG_TEST_TASK_LIST \
+ TASK_TEST(RGBKBD, rgbkbd_task, NULL, 2048)
diff --git a/test/test_config.h b/test/test_config.h
index 7da4d817d6..095011f2e4 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -137,6 +137,11 @@
#define CONFIG_MKBP_USE_GPIO
#endif
+#ifdef TEST_RGB_KEYBOARD
+#define CONFIG_RGB_KEYBOARD
+#define CONFIG_RGBKBD_DEMO_DOT
+#endif
+
#ifdef TEST_STILLNESS_DETECTOR
#define CONFIG_MKBP_EVENT
#define CONFIG_MKBP_USE_GPIO
diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt
index 3a83b4a440..65534d3572 100644
--- a/zephyr/CMakeLists.txt
+++ b/zephyr/CMakeLists.txt
@@ -273,6 +273,8 @@ zephyr_library_sources_ifdef(CONFIG_PLATFORM_EC_IOEX_TCA64XXA
"${PLATFORM_EC}/driver/ioexpander/tca64xxa.c")
zephyr_library_sources_ifdef(CONFIG_PLATFORM_EC_KEYBOARD
"${PLATFORM_EC}/common/keyboard_scan.c")
+zephyr_library_sources_ifdef(CONFIG_PLATFORM_EC_RGB_KEYBOARD
+ "${PLATFORM_EC}/common/rgb_keyboard.c")
zephyr_library_sources_ifdef(CONFIG_PLATFORM_EC_KEYBOARD_PROTOCOL_8042
"${PLATFORM_EC}/common/keyboard_8042.c"
"${PLATFORM_EC}/common/keyboard_8042_sharedlib.c")
diff --git a/zephyr/Kconfig.keyboard b/zephyr/Kconfig.keyboard
index 12e1817de0..d2d7efc8af 100644
--- a/zephyr/Kconfig.keyboard
+++ b/zephyr/Kconfig.keyboard
@@ -198,3 +198,52 @@ config PLATFORM_EC_KBLIGHT_ENABLE_PIN
disable pin. This pin must be defined as GPIO_EN_KEYBOARD_BACKLIGHT.
endif # PLATFORM_EC_KEYBOARD
+
+
+menuconfig PLATFORM_EC_RGB_KEYBOARD
+ bool "RGB keyboard support"
+ select HAS_TASK_RGBKBD
+ help
+ Enable RGB keyboard backlight control. This consists of a RGB matrix
+ control task. Note this feature does not include key scanning.
+
+if PLATFORM_EC_RGB_KEYBOARD
+
+config PLATFORM_EC_RGB_KEYBOARD_DEBUG
+ bool "RGB Keyboard Debug"
+ default n
+ help
+ Enable debug messages from a RGB keyboard task.
+
+choice PLATFORM_EC_RGBKBD_DEMO
+ prompt "RGB Keyboard demo to run on reset"
+ optional
+ help
+ Enable demo of RGB keyboard to run on reset.
+
+config PLATFORM_EC_RGBKBD_DEMO_FLOW
+ bool "Flow"
+ help
+ In each iteration, a new color is placed in (0,0) and the rest of
+ LEDs copy colors from adjacent LEDs.
+
+config PLATFORM_EC_RGBKBD_DEMO_DOT
+ bool "Dot"
+ help
+ A red dot is placed on (0,0) and traverses the grid from top to buttom
+ left to right. After the entire matrix is traversed, it's repeated with
+ a new color.
+
+endchoice # PLATFORM_EC_RGBKBD_DEMO
+
+config PLATFORM_EC_LED_DRIVER_IS31FL3743B
+ bool "Driver for IS31FL3743B LED controller"
+ help
+ Enable driver for IS31FL3743B LED controller.
+
+config PLATFORM_EC_LED_DRIVER_AW20198
+ bool "Driver for AW20198 LED controller"
+ help
+ Enable driver for AW20198 LED controller.
+
+endif # PLATFORM_EC_RGB_KEYBOARD