mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-08 16:41:55 +00:00
I keep getting asked to build an EC image to manually control the lightbar patterns so that the Powers That Be can look at it. This change just makes it possible to turn that mode on and off for yourself. You'll need a root shell or the EC console to do it, though. BUG=chrome-os-partner:8039 BRANCH=link TEST=manual From the EC console, type lightbar demo 1 OR from the root shell run ectool lightbar demo 1 After that, these keys should change the lightbar appearance (transitions may be slow and subtle - that's intended): UP = battery is more fully charged DOWN = battery is less fully charged RIGHT = battery is charging LEFT = battery is discharging BRIGHT = increase lightbar brightness DIM = decrase lightbar brightness Note that this does not interfere with the normal function of any keys. It only adds some additional EC behavior. Change-Id: Ia1a9855188244d74b670f9dbfdf60e3ac0343460 Signed-off-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/30899
1203 lines
30 KiB
C
1203 lines
30 KiB
C
/* Copyright (c) 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.
|
|
*
|
|
* LED controls.
|
|
*/
|
|
|
|
#include "battery.h"
|
|
#include "battery_pack.h"
|
|
#include "charge_state.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "i2c.h"
|
|
#include "lightbar.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_LIGHTBAR, outstr)
|
|
#define CPRINTF(format, args...) cprintf(CC_LIGHTBAR, format, ## args)
|
|
|
|
#define CONSOLE_COMMAND_LIGHTBAR_HELP
|
|
|
|
/******************************************************************************/
|
|
/* 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 uint8_t i2c_addr[] = { 0x54, 0x56 };
|
|
|
|
static inline void controller_write(int ctrl_num, uint8_t reg, uint8_t val)
|
|
{
|
|
ctrl_num = ctrl_num % ARRAY_SIZE(i2c_addr);
|
|
i2c_write8(I2C_PORT_LIGHTBAR, i2c_addr[ctrl_num], reg, val);
|
|
}
|
|
|
|
static inline uint8_t controller_read(int ctrl_num, uint8_t reg)
|
|
{
|
|
int val = 0;
|
|
ctrl_num = ctrl_num % ARRAY_SIZE(i2c_addr);
|
|
i2c_read8(I2C_PORT_LIGHTBAR, i2c_addr[ctrl_num], reg, &val);
|
|
return val;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* Controller details. We have an ADP8861 and and ADP8863, but we can treat
|
|
* them identically for our purposes */
|
|
/******************************************************************************/
|
|
|
|
/* 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
|
|
|
|
/* How many LEDs do we have? */
|
|
#define NUM_LEDS 4
|
|
|
|
/* 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) */
|
|
};
|
|
|
|
static void set_from_array(const struct initdata_s *data, int count)
|
|
{
|
|
int i;
|
|
for (i = 0; i < count; i++) {
|
|
controller_write(0, data[i].reg, data[i].val);
|
|
controller_write(1, data[i].reg, data[i].val);
|
|
}
|
|
}
|
|
|
|
/* Controller register lookup tables. */
|
|
static const uint8_t led_to_ctrl[] = { 0, 0, 1, 1 };
|
|
static const uint8_t led_to_isc[] = { 0x15, 0x18, 0x15, 0x18 };
|
|
|
|
/* Scale 0-255 into max value */
|
|
static inline uint8_t scale_abs(int val, int max)
|
|
{
|
|
return (val * max)/255 + max/256;
|
|
}
|
|
|
|
/* It will often be simpler to provide an overall brightness control. */
|
|
int brightness = 0x80;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
static void lightbar_init_vals(void)
|
|
{
|
|
CPRINTF("[%T LB_init_vals]\n");
|
|
set_from_array(init_vals, ARRAY_SIZE(init_vals));
|
|
memset(current, 0, sizeof(current));
|
|
}
|
|
|
|
/* Helper function. */
|
|
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];
|
|
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));
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
/* Basic LED control functions. */
|
|
/******************************************************************************/
|
|
|
|
static void lightbar_off(void)
|
|
{
|
|
CPRINTF("[%T LB_off]\n");
|
|
/* Just go into standby mode. No register values should change. */
|
|
controller_write(0, 0x01, 0x00);
|
|
controller_write(1, 0x01, 0x00);
|
|
}
|
|
|
|
static void lightbar_on(void)
|
|
{
|
|
CPRINTF("[%T LB_on]\n");
|
|
/* Come out of standby mode. */
|
|
controller_write(0, 0x01, 0x20);
|
|
controller_write(1, 0x01, 0x20);
|
|
}
|
|
|
|
|
|
/* LEDs are numbered 0-3, RGB values should be in 0-255.
|
|
* If you specify too large an LED, it sets them all. */
|
|
static void lightbar_setrgb(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);
|
|
}
|
|
|
|
static void lightbar_brightness(int newval)
|
|
{
|
|
int i;
|
|
CPRINTF("[%T LB_bright 0x%02x]\n", newval);
|
|
brightness = newval;
|
|
for (i = 0; i < NUM_LEDS; i++)
|
|
lightbar_setrgb(i, current[i][0],
|
|
current[i][1], current[i][2]);
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
/* Major colors */
|
|
static const struct {
|
|
uint8_t r, g, b;
|
|
} testy[] = {
|
|
{0xff, 0x00, 0x00},
|
|
{0x00, 0xff, 0x00},
|
|
{0x00, 0x00, 0xff},
|
|
{0xff, 0xff, 0x00}, /* The first four are Google colors */
|
|
{0x00, 0xff, 0xff},
|
|
{0xff, 0x00, 0xff},
|
|
{0xff, 0xff, 0xff},
|
|
};
|
|
|
|
|
|
/******************************************************************************/
|
|
/* Now for the pretty patterns */
|
|
/******************************************************************************/
|
|
|
|
/* Here's some state that we might want to maintain across sysjumps, just to
|
|
* prevent the lightbar from flashing during normal boot as the EC jumps from
|
|
* RO to RW. FIXME: This doesn't quite stop the problems. */
|
|
static struct {
|
|
/* What patterns are we showing? */
|
|
enum lightbar_sequence cur_seq;
|
|
enum lightbar_sequence prev_seq;
|
|
|
|
/* Quantized battery charge level: 0=low 1=med 2=high 3=full. */
|
|
int battery_level;
|
|
|
|
/* We'll pulse slightly faster when charging */
|
|
int battery_is_charging;
|
|
} st;
|
|
|
|
#define LB_SYSJUMP_TAG 0x4c42 /* "LB" */
|
|
static int lb_preserve_state(void)
|
|
{
|
|
system_add_jump_tag(LB_SYSJUMP_TAG, 0, sizeof(st), &st);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOOK(HOOK_SYSJUMP, lb_preserve_state, HOOK_PRIO_DEFAULT);
|
|
|
|
static void lb_restore_state(void)
|
|
{
|
|
const uint8_t *old_state = 0;
|
|
int size;
|
|
|
|
old_state = system_get_jump_tag(LB_SYSJUMP_TAG, 0, &size);
|
|
if (old_state && size == sizeof(st)) {
|
|
memcpy(&st, old_state, size);
|
|
} else {
|
|
st.cur_seq = st.prev_seq = LIGHTBAR_S5;
|
|
st.battery_level = 2;
|
|
}
|
|
CPRINTF("[%T LB state: %d %d - %d/%d]\n",
|
|
st.cur_seq, st.prev_seq,
|
|
st.battery_is_charging, st.battery_level);
|
|
}
|
|
|
|
/* Here's where we keep messages waiting to be delivered to lightbar task. If
|
|
* more than one is sent before the task responds, we only want to deliver the
|
|
* latest one. */
|
|
static uint32_t pending_msg;
|
|
/* And here's the task event that we use to trigger delivery. */
|
|
#define PENDING_MSG 1
|
|
|
|
/* Interruptible delay */
|
|
#define WAIT_OR_RET(A) do { \
|
|
uint32_t msg = task_wait_event(A); \
|
|
if (TASK_EVENT_CUSTOM(msg) == PENDING_MSG) \
|
|
return PENDING_MSG; } while (0)
|
|
|
|
/****************************************************************************/
|
|
/* Demo sequence */
|
|
|
|
struct rgb_s {
|
|
uint8_t r, g, b;
|
|
};
|
|
enum {
|
|
COLOR_LOW, COLOR_MEDIUM, COLOR_HIGH, COLOR_FULL, COLOR_BLACK,
|
|
};
|
|
static const struct rgb_s colors[] = {
|
|
{0xff, 0x00, 0x00}, /* low = red */
|
|
{0xff, 0xff, 0x00}, /* med = yellow */
|
|
{0x00, 0x00, 0xff}, /* high = blue */
|
|
{0x00, 0xff, 0x00}, /* full = green */
|
|
{0x00, 0x00, 0x00}, /* black */
|
|
};
|
|
|
|
static int demo_mode;
|
|
|
|
void demo_battery_level(int inc)
|
|
{
|
|
if ((!demo_mode) ||
|
|
(st.battery_level == COLOR_LOW && inc < 0) ||
|
|
(st.battery_level == COLOR_FULL && inc > 0))
|
|
return;
|
|
|
|
st.battery_level += inc;
|
|
|
|
CPRINTF("[%T LB demo: battery_level=%d]\n", st.battery_level);
|
|
}
|
|
|
|
void demo_is_charging(int ischarge)
|
|
{
|
|
if (!demo_mode)
|
|
return;
|
|
st.battery_is_charging = ischarge;
|
|
CPRINTF("[%T LB demo: battery_is_charging=%d]\n",
|
|
st.battery_is_charging);
|
|
}
|
|
|
|
void demo_brightness(int inc)
|
|
{
|
|
int b;
|
|
|
|
if (!demo_mode)
|
|
return;
|
|
|
|
b = brightness + (inc * 16);
|
|
if (b > 0xff)
|
|
b = 0xff;
|
|
else if (b < 0)
|
|
b = 0;
|
|
lightbar_brightness(b);
|
|
}
|
|
|
|
static int last_battery_is_charging;
|
|
static int last_battery_level;
|
|
static void get_battery_level(void)
|
|
{
|
|
int pct = 0;
|
|
|
|
if (demo_mode)
|
|
return;
|
|
|
|
#ifdef CONFIG_TASK_POWERSTATE
|
|
pct = charge_get_percent();
|
|
st.battery_is_charging = (PWR_STATE_DISCHARGE != charge_get_state());
|
|
#endif
|
|
if (pct > LIGHTBAR_POWER_THRESHOLD_FULL)
|
|
st.battery_level = COLOR_FULL;
|
|
else if (pct > LIGHTBAR_POWER_THRESHOLD_HIGH)
|
|
st.battery_level = COLOR_HIGH;
|
|
else if (pct > LIGHTBAR_POWER_THRESHOLD_MEDIUM)
|
|
st.battery_level = COLOR_MEDIUM;
|
|
else
|
|
st.battery_level = COLOR_LOW;
|
|
}
|
|
|
|
static struct {
|
|
timestamp_t start_time;
|
|
timestamp_t end_time;
|
|
struct rgb_s prev;
|
|
struct rgb_s next;
|
|
} led_state[NUM_LEDS];
|
|
|
|
#define MSECS(a) (a * 1000)
|
|
#define SEC(a) (a * 1000000)
|
|
|
|
static const uint64_t transition_time = SEC(3);
|
|
static const uint64_t transition_stagger[NUM_LEDS] = {
|
|
MSECS(0), MSECS(200), MSECS(733), MSECS(450),
|
|
};
|
|
|
|
static const int pulse_period[2] = { SEC(20), /* discharging */
|
|
SEC(10) }; /* charging */
|
|
|
|
static const int pulse_stagger[2][NUM_LEDS] = {
|
|
{ MSECS(0), MSECS(4800), MSECS(16000), MSECS(11000) }, /* discharging */
|
|
{ MSECS(0), MSECS(2400), MSECS(8000), MSECS(5500) } /* charging */
|
|
};
|
|
|
|
static struct rgb_s tmp_color;
|
|
static int tmp_percent;
|
|
static void interpolate(timestamp_t now, int i)
|
|
{
|
|
int range, sofar;
|
|
if (now.val <= led_state[i].start_time.val) {
|
|
tmp_color = led_state[i].prev;
|
|
tmp_percent = 0;
|
|
return;
|
|
}
|
|
|
|
if (now.val >= led_state[i].end_time.val) {
|
|
tmp_percent = 100;
|
|
tmp_color = led_state[i].next;
|
|
return;
|
|
}
|
|
|
|
range = (int)(led_state[i].end_time.val - led_state[i].start_time.val);
|
|
sofar = (int)(now.val - led_state[i].start_time.val);
|
|
|
|
tmp_percent = (sofar * 100) / range;
|
|
tmp_color.r = ((100 - tmp_percent) * led_state[i].prev.r) / 100 +
|
|
(tmp_percent * led_state[i].next.r) / 100;
|
|
tmp_color.g = ((100 - tmp_percent) * led_state[i].prev.g) / 100 +
|
|
(tmp_percent * led_state[i].next.g) / 100;
|
|
tmp_color.b = ((100 - tmp_percent) * led_state[i].prev.b) / 100 +
|
|
(tmp_percent * led_state[i].next.b) / 100;
|
|
}
|
|
|
|
|
|
/* 8-bit fixed-point sin(x). domain 0-PI == 0-127, range 0-1 == 0-255.
|
|
* This is just the first half cycle. */
|
|
const uint8_t sin_table[] = {
|
|
0, 6, 13, 19, 25, 31, 37, 44, 50, 56, 62, 68, 74, 80, 86, 92, 98,
|
|
103, 109, 115, 120, 126, 131, 136, 142, 147, 152, 157, 162, 167,
|
|
171, 176, 180, 185, 189, 193, 197, 201, 205, 208, 212, 215, 219,
|
|
222, 225, 228, 231, 233, 236, 238, 240, 242, 244, 246, 247, 249,
|
|
250, 251, 252, 253, 254, 254, 255, 255, 255, 255, 255, 254, 254,
|
|
253, 252, 251, 250, 249, 247, 246, 244, 242, 240, 238, 236, 233,
|
|
231, 228, 225, 222, 219, 215, 212, 208, 205, 201, 197, 193, 189,
|
|
185, 180, 176, 171, 167, 162, 157, 152, 147, 142, 136, 131, 126,
|
|
120, 115, 109, 103, 98, 92, 86, 80, 74, 68, 62, 56, 50, 44, 37, 31,
|
|
25, 19, 13, 6
|
|
};
|
|
|
|
/* This provides the other half. */
|
|
int sini(uint8_t i)
|
|
{
|
|
if (i < 128)
|
|
return sin_table[i];
|
|
return -sin_table[i-128];
|
|
}
|
|
|
|
static void pulse(timestamp_t now, int period_offset)
|
|
{
|
|
int t;
|
|
uint8_t i;
|
|
int j;
|
|
|
|
/* Bound time to one cycle */
|
|
t = (now.le.lo + period_offset) % pulse_period[st.battery_is_charging];
|
|
/* Convert phase to 0-255 */
|
|
i = ((t >> 8) / (pulse_period[st.battery_is_charging] >> 16));
|
|
/* Compute sinusoidal for phase, as [-255:255] */
|
|
j = sini(i);
|
|
j = j * sini((int)i * 3 / 2) / 255;
|
|
j = j * sini((int)i * 16 / 10) / 255;
|
|
/* Cut it down a bit if we're plugged in. */
|
|
j = j / (1 + st.battery_is_charging);
|
|
|
|
/* Luminize current color using sinusoidal */
|
|
t = j + tmp_color.r;
|
|
if (t > 255)
|
|
tmp_color.r = 255;
|
|
else if (t < 0)
|
|
tmp_color.r = 0;
|
|
else
|
|
tmp_color.r = t;
|
|
|
|
t = j + tmp_color.g;
|
|
if (t > 255)
|
|
tmp_color.g = 255;
|
|
else if (t < 0)
|
|
tmp_color.g = 0;
|
|
else
|
|
tmp_color.g = t;
|
|
|
|
t = j + tmp_color.b;
|
|
if (t > 255)
|
|
tmp_color.b = 255;
|
|
else if (t < 0)
|
|
tmp_color.b = 0;
|
|
else
|
|
tmp_color.b = t;
|
|
}
|
|
|
|
|
|
/* CPU is fully on */
|
|
static uint32_t sequence_S0(void)
|
|
{
|
|
int i, tick, last_tick;
|
|
timestamp_t start, now;
|
|
|
|
start = get_time();
|
|
tick = last_tick = 0;
|
|
|
|
lightbar_on();
|
|
|
|
/* start black, we'll fade in first thing */
|
|
lightbar_setrgb(NUM_LEDS, 0, 0, 0);
|
|
for (i = 0; i < NUM_LEDS; i++)
|
|
led_state[i].prev = colors[COLOR_BLACK];
|
|
last_battery_is_charging = !st.battery_is_charging; /* force update */
|
|
|
|
while (1) {
|
|
now = get_time();
|
|
|
|
/* Only check the battery state every so often. The battery
|
|
* charging task doesn't update as quickly as we do, and isn't
|
|
* always valid for a bit after jumping from RO->RW. */
|
|
tick = (now.le.lo - start.le.lo) / SEC(1);
|
|
if (tick % 4 == 3 && tick != last_tick) {
|
|
get_battery_level();
|
|
last_tick = tick;
|
|
}
|
|
|
|
/* Has something changed? */
|
|
if (st.battery_is_charging != last_battery_is_charging ||
|
|
st.battery_level != last_battery_level) {
|
|
/* yes */
|
|
for (i = 0; i < NUM_LEDS; i++) {
|
|
led_state[i].start_time.val = now.val +
|
|
transition_stagger[i];
|
|
led_state[i].end_time.val =
|
|
led_state[i]. start_time.val +
|
|
transition_time;
|
|
led_state[i].prev = led_state[i].next;
|
|
led_state[i].next = colors[st.battery_level];
|
|
}
|
|
last_battery_is_charging = st.battery_is_charging;
|
|
last_battery_level = st.battery_level;
|
|
}
|
|
|
|
/* Figure out what colors to show now */
|
|
for (i = 0; i < NUM_LEDS; i++) {
|
|
/* Compute transition between prev and next colors. */
|
|
interpolate(now, i);
|
|
|
|
/* Pulse sinusoidally */
|
|
pulse(now, pulse_stagger[st.battery_is_charging][i]);
|
|
|
|
/* Show it */
|
|
lightbar_setrgb(i, tmp_color.r, tmp_color.g,
|
|
tmp_color.b);
|
|
}
|
|
|
|
WAIT_OR_RET(MSECS(15));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* CPU is off */
|
|
static uint32_t sequence_S5(void)
|
|
{
|
|
/* Just wait forever. */
|
|
lightbar_off();
|
|
WAIT_OR_RET(-1);
|
|
return 0;
|
|
}
|
|
|
|
/* CPU is powering up. The lightbar loses power when the CPU is in S5, so this
|
|
* might not be useful. */
|
|
static uint32_t sequence_S5S3(void)
|
|
{
|
|
/* The controllers need 100us after power is applied before they'll
|
|
* respond. Don't return early, because we still want to initialize the
|
|
* lightbar even if another message comes along while we're waiting. */
|
|
usleep(100);
|
|
lightbar_init_vals();
|
|
|
|
/* For now, do something to indicate this transition.
|
|
* We might see it. */
|
|
lightbar_on();
|
|
lightbar_setrgb(NUM_LEDS, 0, 0, 0);
|
|
WAIT_OR_RET(500000);
|
|
return 0;
|
|
}
|
|
|
|
/* CPU is going to sleep */
|
|
static uint32_t sequence_S0S3(void)
|
|
{
|
|
int i;
|
|
lightbar_on();
|
|
for (i = 0; i < NUM_LEDS; i++) {
|
|
lightbar_setrgb(i, 0, 0, 0);
|
|
WAIT_OR_RET(200000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* CPU is sleeping */
|
|
static uint32_t sequence_S3(void)
|
|
{
|
|
int r, g, b;
|
|
int i;
|
|
|
|
lightbar_off();
|
|
lightbar_init_vals();
|
|
lightbar_setrgb(NUM_LEDS, 0, 0, 0);
|
|
while (1) {
|
|
WAIT_OR_RET(SEC(15));
|
|
get_battery_level();
|
|
lightbar_on();
|
|
r = colors[st.battery_level].r;
|
|
g = colors[st.battery_level].g;
|
|
b = colors[st.battery_level].b;
|
|
for (i = 0; i < 255; i += 5) {
|
|
lightbar_setrgb(NUM_LEDS,
|
|
(r * i) / 255,
|
|
(g * i) / 255,
|
|
(b * i) / 255);
|
|
WAIT_OR_RET(15000);
|
|
}
|
|
for (i = 255; i > 0; i -= 5) {
|
|
lightbar_setrgb(NUM_LEDS,
|
|
(r * i) / 255,
|
|
(g * i) / 255,
|
|
(b * i) / 255);
|
|
WAIT_OR_RET(15000);
|
|
}
|
|
lightbar_setrgb(NUM_LEDS, 0, 0, 0);
|
|
lightbar_off();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* CPU is waking from sleep */
|
|
static uint32_t sequence_S3S0(void)
|
|
{
|
|
int i;
|
|
lightbar_init_vals();
|
|
lightbar_on();
|
|
for (i = 0; i < NUM_LEDS; i++) {
|
|
lightbar_setrgb(i, testy[i].r, testy[i].g, testy[i].b);
|
|
WAIT_OR_RET(200000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Sleep to off. */
|
|
static uint32_t sequence_S3S5(void)
|
|
{
|
|
/* For now, do something to indicate this transition.
|
|
* We might see it. */
|
|
lightbar_off();
|
|
WAIT_OR_RET(500000);
|
|
return 0;
|
|
}
|
|
|
|
/* Used by factory. */
|
|
static uint32_t sequence_TEST_inner(void)
|
|
{
|
|
int i, j, k, r, g, b;
|
|
int kmax = 254;
|
|
int kstep = 8;
|
|
|
|
lightbar_init_vals();
|
|
lightbar_on();
|
|
for (i = 0; i < ARRAY_SIZE(testy); i++) {
|
|
for (k = 0; k <= kmax; k += kstep) {
|
|
for (j = 0; j < NUM_LEDS; j++) {
|
|
r = testy[i].r ? k : 0;
|
|
g = testy[i].g ? k : 0;
|
|
b = testy[i].b ? k : 0;
|
|
lightbar_setrgb(j, r, g, b);
|
|
}
|
|
WAIT_OR_RET(10000);
|
|
}
|
|
for (k = kmax; k >= 0; k -= kstep) {
|
|
for (j = 0; j < NUM_LEDS; j++) {
|
|
r = testy[i].r ? k : 0;
|
|
g = testy[i].g ? k : 0;
|
|
b = testy[i].b ? k : 0;
|
|
lightbar_setrgb(j, r, g, b);
|
|
}
|
|
WAIT_OR_RET(10000);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t sequence_TEST(void)
|
|
{
|
|
int tmp;
|
|
uint32_t r;
|
|
|
|
tmp = brightness;
|
|
brightness = 255;
|
|
r = sequence_TEST_inner();
|
|
brightness = tmp;
|
|
return r;
|
|
}
|
|
|
|
/* This uses the auto-cycling features of the controllers to make a semi-random
|
|
* pattern of slowly fading colors. This is interesting only because it doesn't
|
|
* require any effort from the EC. */
|
|
static uint32_t sequence_PULSE(void)
|
|
{
|
|
uint32_t msg;
|
|
int r = scale(255, MAX_RED);
|
|
int g = scale(255, MAX_BLUE);
|
|
int b = scale(255, MAX_GREEN);
|
|
struct initdata_s pulse_vals[] = {
|
|
{0x11, 0xce},
|
|
{0x12, 0x67},
|
|
{0x13, 0xef},
|
|
{0x15, b},
|
|
{0x16, r},
|
|
{0x17, g},
|
|
{0x18, b},
|
|
{0x19, r},
|
|
{0x1a, g},
|
|
};
|
|
|
|
lightbar_init_vals();
|
|
lightbar_on();
|
|
|
|
set_from_array(pulse_vals, ARRAY_SIZE(pulse_vals));
|
|
controller_write(1, 0x13, 0xcd); /* this one's different */
|
|
|
|
/* Not using WAIT_OR_RET() here, because we want to clean up when we're
|
|
* done. The only way out is to get a message. */
|
|
msg = task_wait_event(-1);
|
|
lightbar_init_vals();
|
|
return TASK_EVENT_CUSTOM(msg);
|
|
}
|
|
|
|
|
|
|
|
/* The host CPU (or someone) is going to poke at the lightbar directly, so we
|
|
* don't want the EC messing with it. We'll just sit here and ignore all
|
|
* other messages until we're told to continue. */
|
|
static uint32_t sequence_STOP(void)
|
|
{
|
|
uint32_t msg;
|
|
|
|
do {
|
|
msg = TASK_EVENT_CUSTOM(task_wait_event(-1));
|
|
CPRINTF("[%T LB_stop got pending_msg %d]\n", pending_msg);
|
|
} while (msg != PENDING_MSG || pending_msg != LIGHTBAR_RUN);
|
|
/* FIXME: What should we do if the host shuts down? */
|
|
|
|
CPRINTF("[%T LB_stop->running]\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Telling us to run when we're already running should do nothing. */
|
|
static uint32_t sequence_RUN(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* We shouldn't come here, but if we do it shouldn't hurt anything */
|
|
static uint32_t sequence_ERROR(void)
|
|
{
|
|
lightbar_init_vals();
|
|
lightbar_on();
|
|
|
|
lightbar_setrgb(0, 255, 255, 255);
|
|
lightbar_setrgb(1, 255, 0, 255);
|
|
lightbar_setrgb(2, 0, 255, 255);
|
|
lightbar_setrgb(3, 255, 255, 255);
|
|
|
|
WAIT_OR_RET(10000000);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct {
|
|
uint8_t led;
|
|
uint8_t r, g, b;
|
|
unsigned int delay;
|
|
} konami[] = {
|
|
|
|
{1, 0xff, 0xff, 0x00, 0},
|
|
{2, 0xff, 0xff, 0x00, 100000},
|
|
{1, 0x00, 0x00, 0x00, 0},
|
|
{2, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{1, 0xff, 0xff, 0x00, 0},
|
|
{2, 0xff, 0xff, 0x00, 100000},
|
|
{1, 0x00, 0x00, 0x00, 0},
|
|
{2, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{0, 0x00, 0x00, 0xff, 0},
|
|
{3, 0x00, 0x00, 0xff, 100000},
|
|
{0, 0x00, 0x00, 0x00, 0},
|
|
{3, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{0, 0x00, 0x00, 0xff, 0},
|
|
{3, 0x00, 0x00, 0xff, 100000},
|
|
{0, 0x00, 0x00, 0x00, 0},
|
|
{3, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{0, 0xff, 0x00, 0x00, 0},
|
|
{1, 0xff, 0x00, 0x00, 100000},
|
|
{0, 0x00, 0x00, 0x00, 0},
|
|
{1, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{2, 0x00, 0xff, 0x00, 0},
|
|
{3, 0x00, 0xff, 0x00, 100000},
|
|
{2, 0x00, 0x00, 0x00, 0},
|
|
{3, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{0, 0xff, 0x00, 0x00, 0},
|
|
{1, 0xff, 0x00, 0x00, 100000},
|
|
{0, 0x00, 0x00, 0x00, 0},
|
|
{1, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{2, 0x00, 0xff, 0x00, 0},
|
|
{3, 0x00, 0xff, 0x00, 100000},
|
|
{2, 0x00, 0x00, 0x00, 0},
|
|
{3, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{0, 0x00, 0xff, 0xff, 0},
|
|
{2, 0x00, 0xff, 0xff, 100000},
|
|
{0, 0x00, 0x00, 0x00, 0},
|
|
{2, 0x00, 0x00, 0x00, 150000},
|
|
|
|
{1, 0xff, 0x00, 0xff, 0},
|
|
{3, 0xff, 0x00, 0xff, 100000},
|
|
{1, 0x00, 0x00, 0x00, 0},
|
|
{3, 0x00, 0x00, 0x00, 250000},
|
|
|
|
{4, 0xff, 0xff, 0xff, 100000},
|
|
{4, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{4, 0xff, 0xff, 0xff, 100000},
|
|
{4, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{4, 0xff, 0xff, 0xff, 100000},
|
|
{4, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{4, 0xff, 0xff, 0xff, 100000},
|
|
{4, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{4, 0xff, 0xff, 0xff, 100000},
|
|
{4, 0x00, 0x00, 0x00, 100000},
|
|
|
|
{4, 0xff, 0xff, 0xff, 100000},
|
|
{4, 0x00, 0x00, 0x00, 100000},
|
|
|
|
};
|
|
|
|
static uint32_t sequence_KONAMI(void)
|
|
{
|
|
int i;
|
|
int tmp;
|
|
|
|
lightbar_init_vals();
|
|
lightbar_on();
|
|
|
|
tmp = brightness;
|
|
brightness = 255;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(konami); i++) {
|
|
lightbar_setrgb(konami[i].led,
|
|
konami[i].r, konami[i].g, konami[i].b);
|
|
if (konami[i].delay)
|
|
usleep(konami[i].delay);
|
|
}
|
|
|
|
brightness = tmp;
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* The main lightbar task. It just cycles between various pretty patterns. */
|
|
/****************************************************************************/
|
|
|
|
/* Link each sequence with a command to invoke it. */
|
|
struct lightbar_cmd_t {
|
|
const char * const string;
|
|
uint32_t (*sequence)(void);
|
|
};
|
|
|
|
#define LBMSG(state) { #state, sequence_##state }
|
|
#include "lightbar_msg_list.h"
|
|
static struct lightbar_cmd_t lightbar_cmds[] = {
|
|
LIGHTBAR_MSG_LIST
|
|
};
|
|
#undef LBMSG
|
|
|
|
|
|
void lightbar_task(void)
|
|
{
|
|
uint32_t msg;
|
|
|
|
CPRINTF("[%T LB task starting]\n");
|
|
|
|
lb_restore_state();
|
|
|
|
while (1) {
|
|
CPRINTF("[%T LB task %d = %s]\n",
|
|
st.cur_seq, lightbar_cmds[st.cur_seq].string);
|
|
msg = lightbar_cmds[st.cur_seq].sequence();
|
|
if (TASK_EVENT_CUSTOM(msg) == PENDING_MSG) {
|
|
CPRINTF("[%T LB msg %d = %s]\n", pending_msg,
|
|
lightbar_cmds[pending_msg].string);
|
|
st.prev_seq = st.cur_seq;
|
|
st.cur_seq = pending_msg;
|
|
} else {
|
|
CPRINTF("[%T LB msg 0x%x]\n", msg);
|
|
switch (st.cur_seq) {
|
|
case LIGHTBAR_S5S3:
|
|
st.cur_seq = LIGHTBAR_S3;
|
|
break;
|
|
case LIGHTBAR_S3S0:
|
|
st.cur_seq = LIGHTBAR_S0;
|
|
break;
|
|
case LIGHTBAR_S0S3:
|
|
st.cur_seq = LIGHTBAR_S3;
|
|
break;
|
|
case LIGHTBAR_S3S5:
|
|
st.cur_seq = LIGHTBAR_S5;
|
|
break;
|
|
case LIGHTBAR_TEST:
|
|
case LIGHTBAR_STOP:
|
|
case LIGHTBAR_RUN:
|
|
case LIGHTBAR_ERROR:
|
|
case LIGHTBAR_KONAMI:
|
|
st.cur_seq = st.prev_seq;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Function to request a preset sequence from the lightbar task. */
|
|
void lightbar_sequence(enum lightbar_sequence num)
|
|
{
|
|
if (num > 0 && num < LIGHTBAR_NUM_SEQUENCES) {
|
|
CPRINTF("[%T LB_seq %d = %s]\n", num,
|
|
lightbar_cmds[num].string);
|
|
pending_msg = num;
|
|
task_set_event(TASK_ID_LIGHTBAR,
|
|
TASK_EVENT_WAKE | TASK_EVENT_CUSTOM(PENDING_MSG),
|
|
0);
|
|
} else
|
|
CPRINTF("[%T LB_seq %d - ignored]\n", num);
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* Get notifications from other parts of the system */
|
|
|
|
static int lightbar_startup(void)
|
|
{
|
|
lightbar_sequence(LIGHTBAR_S5S3);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, lightbar_startup, HOOK_PRIO_DEFAULT);
|
|
|
|
static int lightbar_resume(void)
|
|
{
|
|
lightbar_sequence(LIGHTBAR_S3S0);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_RESUME, lightbar_resume, HOOK_PRIO_DEFAULT);
|
|
|
|
static int lightbar_suspend(void)
|
|
{
|
|
lightbar_sequence(LIGHTBAR_S0S3);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, lightbar_suspend, HOOK_PRIO_DEFAULT);
|
|
|
|
static int lightbar_shutdown(void)
|
|
{
|
|
lightbar_sequence(LIGHTBAR_S3S5);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, lightbar_shutdown, HOOK_PRIO_DEFAULT);
|
|
|
|
/****************************************************************************/
|
|
/* Generic command-handling (should work the same for both console & LPC) */
|
|
/****************************************************************************/
|
|
|
|
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
|
|
};
|
|
|
|
static void do_cmd_dump(struct ec_params_lightbar_cmd *ptr)
|
|
{
|
|
int i;
|
|
uint8_t reg;
|
|
|
|
BUILD_ASSERT(ARRAY_SIZE(dump_reglist) ==
|
|
ARRAY_SIZE(ptr->out.dump.vals));
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dump_reglist); i++) {
|
|
reg = dump_reglist[i];
|
|
ptr->out.dump.vals[i].reg = reg;
|
|
ptr->out.dump.vals[i].ic0 = controller_read(0, reg);
|
|
ptr->out.dump.vals[i].ic1 = controller_read(1, reg);
|
|
}
|
|
}
|
|
|
|
static void do_cmd_rgb(uint8_t led,
|
|
uint8_t red, uint8_t green, uint8_t blue)
|
|
{
|
|
int i;
|
|
|
|
if (led >= NUM_LEDS)
|
|
for (i = 0; i < NUM_LEDS; i++)
|
|
lightbar_setrgb(i, red, green, blue);
|
|
else
|
|
lightbar_setrgb(led, red, green, blue);
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
/* Host commands via LPC bus */
|
|
/****************************************************************************/
|
|
|
|
static int lpc_cmd_lightbar(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_params_lightbar_cmd *ptr = args->response;
|
|
|
|
/*
|
|
* TODO: (crosbug.com/p/11277) Now that params and response are
|
|
* separate pointers, they need to be propagated to the lightbar
|
|
* sub-commands. For now, just copy params to response so the
|
|
* sub-commands above will work unchanged.
|
|
*/
|
|
if (args->params != args->response)
|
|
memcpy(args->response, args->params, args->params_size);
|
|
|
|
switch (ptr->in.cmd) {
|
|
case LIGHTBAR_CMD_DUMP:
|
|
do_cmd_dump(ptr);
|
|
args->response_size = sizeof(struct ec_params_lightbar_cmd);
|
|
break;
|
|
case LIGHTBAR_CMD_OFF:
|
|
lightbar_off();
|
|
break;
|
|
case LIGHTBAR_CMD_ON:
|
|
lightbar_on();
|
|
break;
|
|
case LIGHTBAR_CMD_INIT:
|
|
lightbar_init_vals();
|
|
break;
|
|
case LIGHTBAR_CMD_BRIGHTNESS:
|
|
lightbar_brightness(ptr->in.brightness.num);
|
|
break;
|
|
case LIGHTBAR_CMD_SEQ:
|
|
lightbar_sequence(ptr->in.seq.num);
|
|
break;
|
|
case LIGHTBAR_CMD_REG:
|
|
controller_write(ptr->in.reg.ctrl,
|
|
ptr->in.reg.reg,
|
|
ptr->in.reg.value);
|
|
break;
|
|
case LIGHTBAR_CMD_RGB:
|
|
do_cmd_rgb(ptr->in.rgb.led,
|
|
ptr->in.rgb.red,
|
|
ptr->in.rgb.green,
|
|
ptr->in.rgb.blue);
|
|
break;
|
|
case LIGHTBAR_CMD_GET_SEQ:
|
|
ptr->out.get_seq.num = st.cur_seq;
|
|
args->response_size = sizeof(struct ec_params_lightbar_cmd);
|
|
break;
|
|
case LIGHTBAR_CMD_DEMO:
|
|
demo_mode = ptr->in.demo.num ? 1 : 0;
|
|
CPRINTF("[%T LB_demo %d]\n", demo_mode);
|
|
break;
|
|
default:
|
|
CPRINTF("[%T LB bad cmd 0x%x]\n", ptr->in.cmd);
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
|
|
return EC_RES_SUCCESS;
|
|
}
|
|
|
|
DECLARE_HOST_COMMAND(EC_CMD_LIGHTBAR_CMD,
|
|
lpc_cmd_lightbar,
|
|
EC_VER_MASK(0));
|
|
|
|
|
|
|
|
/****************************************************************************/
|
|
/* EC console commands */
|
|
/****************************************************************************/
|
|
|
|
#ifdef CONSOLE_COMMAND_LIGHTBAR_HELP
|
|
static int help(const char *cmd)
|
|
{
|
|
ccprintf("Usage:\n");
|
|
ccprintf(" %s - dump all regs\n", cmd);
|
|
ccprintf(" %s off - enter standby\n", cmd);
|
|
ccprintf(" %s on - leave standby\n", cmd);
|
|
ccprintf(" %s init - load default vals\n", cmd);
|
|
ccprintf(" %s brightness NUM - set intensity (0-ff)\n", cmd);
|
|
ccprintf(" %s seq [NUM|SEQUENCE] - run given pattern"
|
|
" (no arg for list)\n", cmd);
|
|
ccprintf(" %s CTRL REG VAL - set LED controller regs\n", cmd);
|
|
ccprintf(" %s LED RED GREEN BLUE - set color manually"
|
|
" (LED=4 for all)\n", cmd);
|
|
ccprintf(" %s demo [0|1] - turn demo mode on & off\n", cmd);
|
|
return EC_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
static uint8_t find_msg_by_name(const char *str)
|
|
{
|
|
uint8_t i;
|
|
for (i = 0; i < LIGHTBAR_NUM_SEQUENCES; i++)
|
|
if (!strcasecmp(str, lightbar_cmds[i].string))
|
|
return i;
|
|
|
|
return LIGHTBAR_NUM_SEQUENCES;
|
|
}
|
|
|
|
static void show_msg_names(void)
|
|
{
|
|
int i;
|
|
ccprintf("Sequences:");
|
|
for (i = 0; i < LIGHTBAR_NUM_SEQUENCES; i++)
|
|
ccprintf(" %s", lightbar_cmds[i].string);
|
|
ccprintf("\nCurrent = 0x%x %s\n", st.cur_seq,
|
|
lightbar_cmds[st.cur_seq].string);
|
|
}
|
|
|
|
static int command_lightbar(int argc, char **argv)
|
|
{
|
|
int i;
|
|
uint8_t num;
|
|
struct ec_params_lightbar_cmd params;
|
|
|
|
if (1 == argc) { /* no args = dump 'em all */
|
|
do_cmd_dump(¶ms);
|
|
for (i = 0; i < ARRAY_SIZE(dump_reglist); i++)
|
|
ccprintf(" %02x %02x %02x\n",
|
|
params.out.dump.vals[i].reg,
|
|
params.out.dump.vals[i].ic0,
|
|
params.out.dump.vals[i].ic1);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 2 && !strcasecmp(argv[1], "init")) {
|
|
lightbar_init_vals();
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 2 && !strcasecmp(argv[1], "off")) {
|
|
lightbar_off();
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 2 && !strcasecmp(argv[1], "on")) {
|
|
lightbar_on();
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 3 && !strcasecmp(argv[1], "brightness")) {
|
|
char *e;
|
|
num = 0xff & strtoi(argv[2], &e, 16);
|
|
lightbar_brightness(num);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 3 && !strcasecmp(argv[1], "demo")) {
|
|
if (!strcasecmp(argv[2], "on") || argv[2][0] == '1')
|
|
demo_mode = 1;
|
|
else if (!strcasecmp(argv[2], "off") || argv[2][0] == '0')
|
|
demo_mode = 0;
|
|
else
|
|
return EC_ERROR_PARAM1;
|
|
ccprintf("demo mode is %s\n", demo_mode ? "on" : "off");
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc >= 2 && !strcasecmp(argv[1], "seq")) {
|
|
char *e;
|
|
uint8_t num;
|
|
if (argc == 2) {
|
|
show_msg_names();
|
|
return 0;
|
|
}
|
|
num = 0xff & strtoi(argv[2], &e, 16);
|
|
if (*e)
|
|
num = find_msg_by_name(argv[2]);
|
|
if (num >= LIGHTBAR_NUM_SEQUENCES)
|
|
return EC_ERROR_PARAM2;
|
|
lightbar_sequence(num);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 4) {
|
|
char *e;
|
|
uint8_t ctrl, reg, val;
|
|
ctrl = 0xff & strtoi(argv[1], &e, 16);
|
|
reg = 0xff & strtoi(argv[2], &e, 16);
|
|
val = 0xff & strtoi(argv[3], &e, 16);
|
|
controller_write(ctrl, reg, val);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 5) {
|
|
char *e;
|
|
uint8_t led, r, g, b;
|
|
led = strtoi(argv[1], &e, 16);
|
|
r = strtoi(argv[2], &e, 16);
|
|
g = strtoi(argv[3], &e, 16);
|
|
b = strtoi(argv[4], &e, 16);
|
|
do_cmd_rgb(led, r, g, b);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
#ifdef CONSOLE_COMMAND_LIGHTBAR_HELP
|
|
help(argv[0]);
|
|
#endif
|
|
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(lightbar, command_lightbar,
|
|
"[on | off | init | brightness | seq] | [ctrl reg val]",
|
|
"Get/set lightbar state",
|
|
NULL);
|