summaryrefslogtreecommitdiff
path: root/common/lightbar.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/lightbar.c')
-rw-r--r--common/lightbar.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/common/lightbar.c b/common/lightbar.c
index efd7d797a7..6684949c8a 100644
--- a/common/lightbar.c
+++ b/common/lightbar.c
@@ -973,6 +973,365 @@ static uint32_t sequence_TAP(void)
}
/****************************************************************************/
+/* Lightbar bytecode interpreter: Lightbyte. */
+/****************************************************************************/
+
+static struct lb_program cur_prog;
+static struct lb_program next_prog;
+static uint8_t pc;
+
+enum lb_color {
+ LB_COL_RED,
+ LB_COL_GREEN,
+ LB_COL_BLUE,
+ LB_COL_ALL
+};
+
+enum lb_control {
+ LB_CONT_COLOR0,
+ LB_CONT_COLOR1,
+ LB_CONT_PHASE,
+ LB_CONT_MAX
+};
+
+static uint8_t led_desc[NUM_LEDS][LB_CONT_MAX][3];
+static uint32_t lb_ramp_delay;
+
+/* Get one byte of data pointed to by the pc and advance
+ * the pc forward.
+ */
+static inline uint32_t decode_8(uint8_t *dest)
+{
+ if (pc >= cur_prog.size) {
+ CPRINTS("pc 0x%02x out of bounds", pc);
+ return EC_RES_INVALID_PARAM;
+ }
+ *dest = cur_prog.data[pc++];
+ return EC_SUCCESS;
+}
+
+/* Get four bytes of data pointed to by the pc and advance
+ * the pc forward that amount.
+ */
+static inline uint32_t decode_32(uint32_t *dest)
+{
+ if (pc >= cur_prog.size - 3) {
+ CPRINTS("pc 0x%02x near or out of bounds", pc);
+ return EC_RES_INVALID_PARAM;
+ }
+ *dest = cur_prog.data[pc++] << 24;
+ *dest |= cur_prog.data[pc++] << 16;
+ *dest |= cur_prog.data[pc++] << 8;
+ *dest |= cur_prog.data[pc++];
+ return EC_SUCCESS;
+}
+
+/* JUMP xx - jump to immediate location
+ * Changes the pc to the one-byte immediate argument.
+ */
+static uint32_t lightbyte_JUMP(void)
+{
+ uint8_t new_pc;
+ if (decode_8(&new_pc) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ pc = new_pc;
+ return EC_SUCCESS;
+}
+
+/* DELAY xx xx xx xx - yield processor for some time
+ * Performs an interruptible wait for a number of microseconds
+ * given in the four-byte immediate.
+ */
+static uint32_t lightbyte_DELAY(void)
+{
+ uint32_t delay_us;
+ if (decode_32(&delay_us) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ WAIT_OR_RET(delay_us);
+ return EC_SUCCESS;
+}
+
+/* SET_BRIGHTNESS xx
+ * Sets the current brightness to the given one-byte
+ * immediate argument.
+ */
+static uint32_t lightbyte_SET_BRIGHTNESS(void)
+{
+ uint8_t val;
+ if (decode_8(&val) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ lb_set_brightness(val);
+ return EC_SUCCESS;
+}
+
+/* SET_COLOR cc xx
+ * SET_COLOR cc rr gg bb
+ * Stores a color value in the led_desc structure.
+ * cc is a bit-packed location to perform the action on.
+ *
+ * The high four bits are used to describe an LED. If the
+ * value is less than NUM_LEDS, it describes a particular LED,
+ * and if it is greater than or equal to that value, it
+ * will perform the action on all LEDs.
+ *
+ * The next two bits are the control bits. This should be a value
+ * in lb_control that is not LB_CONT_MAX, and the corresponding
+ * color will be the one the action is performed on.
+ *
+ * The last two bits are the color bits. If this is LB_COL_RED,
+ * LB_COL_GREEN, or LB_COL_BLUE, then there is only one more byte
+ * to decode and this is a color value for that specific color
+ * channel. If it is LB_COL_ALL, then there are three more bytes,
+ * and it reads like a standard 24-bit color value.
+ */
+static uint32_t lightbyte_SET_COLOR(void)
+{
+
+ uint8_t packed_loc, led, control, color, value;
+ int start_led, end_led, color_mask, i, j;
+ if (decode_8(&packed_loc) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ led = packed_loc >> 4;
+ control = (packed_loc >> 2) & 0x3;
+ color = packed_loc & 0x3;
+
+ if (control >= LB_CONT_MAX)
+ return EC_RES_INVALID_PARAM;
+
+ if (led >= NUM_LEDS) {
+ start_led = 0;
+ end_led = NUM_LEDS - 1;
+ } else
+ start_led = end_led = led;
+
+ color_mask = color == LB_COL_ALL ? 7 : (1 << color);
+
+ for (i = 0; i < 3; i++) {
+ if (color_mask & (1 << i)) {
+ if (decode_8(&value) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+ for (j = start_led; j <= end_led; j++)
+ led_desc[j][control][i] = value;
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+/* SET_DELAY_TIME xx xx xx xx - change ramp speed
+ * This sets the length of time between ramp/cycle steps to
+ * the four-byte immediate argument, which represents a duration
+ * in milliseconds.
+ */
+static uint32_t lightbyte_SET_DELAY_TIME(void)
+{
+ uint32_t delay_us;
+ if (decode_32(&delay_us) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ lb_ramp_delay = delay_us;
+ return EC_SUCCESS;
+}
+
+static inline int get_interp_value(int led, int color, int interp)
+{
+ int base = led_desc[led][LB_CONT_COLOR0][color];
+ int delta = led_desc[led][LB_CONT_COLOR1][color] - base;
+ return base + (delta * interp / FP_SCALE);
+}
+
+/* RAMP_ONCE - simple gradient or color set
+ * If the ramp delay is set to zero, then this sets the color of
+ * all LEDs to their respective COLOR1.
+ * If the ramp delay is nonzero, then this sets their color to
+ * their respective COLOR0, and takes them via interpolation to
+ * COLOR1, with the delay time passing in between each step.
+ */
+static uint32_t lightbyte_RAMP_ONCE(void)
+{
+ int w, i, r, g, b;
+ float f;
+
+ /* special case for instantaneous set */
+ if (lb_ramp_delay == 0) {
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = led_desc[i][LB_CONT_COLOR1][LB_COL_RED];
+ g = led_desc[i][LB_CONT_COLOR1][LB_COL_GREEN];
+ b = led_desc[i][LB_CONT_COLOR1][LB_COL_BLUE];
+ lb_set_rgb(i, r, g, b);
+ }
+ return EC_SUCCESS;
+ }
+
+ for (w = 0; w < 128; w++) {
+ f = cycle_010(w);
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = get_interp_value(i, LB_COL_RED, f);
+ g = get_interp_value(i, LB_COL_GREEN, f);
+ b = get_interp_value(i, LB_COL_BLUE, f);
+ lb_set_rgb(i, r, g, b);
+ }
+ WAIT_OR_RET(lb_ramp_delay);
+ }
+ return EC_SUCCESS;
+}
+
+/* CYCLE_ONCE - simple cycle or color set
+ * If the ramp delay is zero, then this sets the color of all LEDs
+ * to their respective COLOR0.
+ * If the ramp delay is nonzero, this sets the color of all LEDs
+ * to COLOR0, then performs a ramp (as in RAMP_ONCE) to COLOR1,
+ * and finally back to COLOR0.
+ */
+static uint32_t lightbyte_CYCLE_ONCE(void)
+{
+ int w, i, r, g, b;
+ float f;
+
+ /* special case for instantaneous set */
+ if (lb_ramp_delay == 0) {
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = led_desc[i][LB_CONT_COLOR0][LB_COL_RED];
+ g = led_desc[i][LB_CONT_COLOR0][LB_COL_GREEN];
+ b = led_desc[i][LB_CONT_COLOR0][LB_COL_BLUE];
+ lb_set_rgb(i, r, g, b);
+ }
+ return EC_SUCCESS;
+ }
+
+ for (w = 0; w < 256; w++) {
+ f = cycle_010(w);
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = get_interp_value(i, LB_COL_RED, f);
+ g = get_interp_value(i, LB_COL_GREEN, f);
+ b = get_interp_value(i, LB_COL_BLUE, f);
+ lb_set_rgb(i, r, g, b);
+ }
+ WAIT_OR_RET(lb_ramp_delay);
+ }
+ return EC_SUCCESS;
+}
+
+/* CYCLE - repeating cycle
+ * Indefinitely ramps from COLOR0 to COLOR1, taking into
+ * account the PHASE of each component of each color when
+ * interpolating. (Different LEDs and different color channels
+ * on a single LED can start at different places in the cycle,
+ * though they will advance at the same rate.)
+ *
+ * If the ramp delay is zero, this instruction will error out.
+ */
+static uint32_t lightbyte_CYCLE(void)
+{
+ int w, i, r, g, b;
+
+ /* what does it mean to cycle indefinitely with 0 delay? */
+ if (lb_ramp_delay == 0)
+ return EC_RES_INVALID_PARAM;
+
+ for (w = 0;; w++) {
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = get_interp_value(i, LB_COL_RED,
+ cycle_010((w & 0xff) +
+ led_desc[i][LB_CONT_PHASE][LB_COL_RED]));
+ g = get_interp_value(i, LB_COL_GREEN,
+ cycle_010((w & 0xff) +
+ led_desc[i][LB_CONT_PHASE][LB_COL_GREEN]));
+ b = get_interp_value(i, LB_COL_BLUE,
+ cycle_010((w & 0xff) +
+ led_desc[i][LB_CONT_PHASE][LB_COL_BLUE]));
+ lb_set_rgb(i, r, g, b);
+ }
+ WAIT_OR_RET(lb_ramp_delay);
+ }
+ return EC_SUCCESS;
+}
+
+#undef GET_INTERP_VALUE
+
+#define OPCODE_TABLE \
+ OP(JUMP), \
+ OP(DELAY), \
+ OP(SET_BRIGHTNESS), \
+ OP(SET_COLOR), \
+ OP(SET_DELAY_TIME), \
+ OP(RAMP_ONCE), \
+ OP(CYCLE_ONCE), \
+ OP(CYCLE),
+
+#define OP(X) X
+enum lightbyte_opcode {
+ OPCODE_TABLE
+ HALT,
+ MAX_OPCODE
+};
+#undef OP
+
+#define OP(X) lightbyte_ ## X
+static uint32_t (*lightbyte_dispatch[])(void) = {
+ OPCODE_TABLE
+};
+#undef OP
+
+#define OP(X) # X
+static const char * const lightbyte_names[] = {
+ OPCODE_TABLE
+ "HALT"
+};
+#undef OP
+
+static uint32_t sequence_PROGRAM(void)
+{
+ uint8_t saved_brightness;
+ uint8_t next_inst;
+ uint32_t rc;
+ uint8_t old_pc;
+
+ /* load next program */
+ memcpy(&cur_prog, &next_prog, sizeof(struct lb_program));
+
+ /* reset program state */
+ saved_brightness = lb_get_brightness();
+ pc = 0;
+ memset(led_desc, 0, sizeof(led_desc));
+ lb_ramp_delay = 0;
+
+ /* decode-execute loop */
+ for (;;) {
+ old_pc = pc;
+ if (decode_8(&next_inst) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ if (next_inst == HALT) {
+ CPRINTS("LB PROGRAM pc: 0x%02x, halting", old_pc);
+ lb_set_brightness(saved_brightness);
+ return 0;
+ } else if (next_inst >= MAX_OPCODE) {
+ CPRINTS("LB PROGRAM pc: 0x%02x, "
+ "found invalid opcode 0x%02x",
+ old_pc, next_inst);
+ lb_set_brightness(saved_brightness);
+ return EC_RES_INVALID_PARAM;
+ } else {
+ CPRINTS("LB PROGRAM pc: 0x%02x, opcode 0x%02x -> %s",
+ old_pc, next_inst, lightbyte_names[next_inst]);
+ rc = lightbyte_dispatch[next_inst]();
+ if (rc) {
+ lb_set_brightness(saved_brightness);
+ return rc;
+ }
+ }
+
+ /* yield processor in case we are stuck in a tight loop */
+ WAIT_OR_RET(100);
+ }
+}
+
+/****************************************************************************/
/* The main lightbar task. It just cycles between various pretty patterns. */
/****************************************************************************/
@@ -1036,6 +1395,7 @@ void lightbar_task(void)
case LIGHTBAR_ERROR:
case LIGHTBAR_KONAMI:
case LIGHTBAR_TAP:
+ case LIGHTBAR_PROGRAM:
st.cur_seq = st.prev_seq;
default:
break;
@@ -1165,6 +1525,10 @@ static int lpc_cmd_lightbar(struct host_cmd_handler_args *args)
CPRINTS("LB_set_params_v1");
memcpy(&st.p, &in->set_params_v1, sizeof(st.p));
break;
+ case LIGHTBAR_CMD_SET_PROGRAM:
+ CPRINTS("LB_set_program");
+ memcpy(&next_prog, &in->set_program, sizeof(struct lb_program));
+ break;
case LIGHTBAR_CMD_VERSION:
CPRINTS("LB_version");
out->version.num = LIGHTBAR_IMPLEMENTATION_VERSION;
@@ -1204,6 +1568,7 @@ static int help(const char *cmd)
ccprintf(" %s LED - get current LED color\n", cmd);
ccprintf(" %s demo [0|1] - turn demo mode on & off\n", cmd);
ccprintf(" %s params - show current params\n", cmd);
+ ccprintf(" %s program filename - load lightbyte program\n", cmd);
ccprintf(" %s version - show current version\n", cmd);
return EC_SUCCESS;
}
@@ -1375,6 +1740,15 @@ static int command_lightbar(int argc, char **argv)
return EC_SUCCESS;
}
+ if (argc >= 3 && !strcasecmp(argv[1], "program")) {
+#ifdef LIGHTBAR_SIMULATION
+ return lb_load_program(argv[2], &next_prog);
+#else
+ ccprintf("can't load program from console\n");
+ return EC_ERROR_INVAL;
+#endif
+ }
+
if (argc == 4) {
struct ec_params_lightbar in;
in.reg.ctrl = strtoi(argv[1], &e, 16);