| /* |
| * 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. |
| */ |
| |
| #ifdef LIGHTBAR_SIMULATION |
| #include "simulation.h" |
| #else |
| #include "battery.h" |
| #include "charge_state.h" |
| #include "common.h" |
| #include "console.h" |
| #include "ec_commands.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "lb_common.h" |
| #include "lightbar.h" |
| #include "lid_switch.h" |
| #include "motion_sense.h" |
| #include "pwm.h" |
| #include "system.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| #endif |
| |
| /* |
| * The Link lightbar had no version command, so defaulted to zero. We have |
| * added a couple of new commands, so we've updated the version. Any |
| * optional features in the current version should be marked with flags. |
| */ |
| #define LIGHTBAR_IMPLEMENTATION_VERSION 1 |
| #define LIGHTBAR_IMPLEMENTATION_FLAGS 0 |
| |
| /* Console output macros */ |
| #define CPUTS(outstr) cputs(CC_LIGHTBAR, outstr) |
| #define CPRINTS(format, args...) cprints(CC_LIGHTBAR, format, ## args) |
| |
| #define FP_SCALE 10000 |
| |
| /******************************************************************************/ |
| /* 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. */ |
| static struct p_state { |
| /* 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; |
| int battery_percent; |
| |
| /* It's either charging or discharging. */ |
| int battery_is_charging; |
| |
| /* Is power-on prevented due to battery level? */ |
| int battery_is_power_on_prevented; |
| |
| /* Pattern variables for state S0. */ |
| uint16_t w0; /* primary phase */ |
| uint8_t ramp; /* ramp-in for S3->S0 */ |
| |
| uint8_t _pad0; /* next item is __packed */ |
| |
| /* Tweakable parameters. */ |
| union { |
| struct lightbar_params_v1 p; |
| struct { |
| struct lightbar_params_v2_timing timing; |
| struct lightbar_params_v2_tap tap; |
| struct lightbar_params_v2_oscillation osc; |
| struct lightbar_params_v2_brightness bright; |
| struct lightbar_params_v2_thresholds thlds; |
| struct lightbar_params_v2_colors colors; |
| } p_v2; |
| }; |
| } st; |
| |
| /* Each of the parameters must be less than 120 bytes |
| * (crbug.com/467716) |
| */ |
| #define MAX_PARAM_SIZE 120 |
| BUILD_ASSERT(sizeof(struct lightbar_params_v2_timing) <= MAX_PARAM_SIZE); |
| BUILD_ASSERT(sizeof(struct lightbar_params_v2_tap) <= MAX_PARAM_SIZE); |
| BUILD_ASSERT(sizeof(struct lightbar_params_v2_oscillation) <= MAX_PARAM_SIZE); |
| BUILD_ASSERT(sizeof(struct lightbar_params_v2_brightness) <= MAX_PARAM_SIZE); |
| BUILD_ASSERT(sizeof(struct lightbar_params_v2_thresholds) <= MAX_PARAM_SIZE); |
| BUILD_ASSERT(sizeof(struct lightbar_params_v2_colors) <= MAX_PARAM_SIZE); |
| #undef MAX_PARAM_SIZE |
| |
| #define PRIMARY_BLUE 4 |
| #define PRIMARY_RED 5 |
| #define PRIMARY_YELLOW 6 |
| #define PRIMARY_GREEN 7 |
| |
| static const struct lightbar_params_v1 default_params = { |
| .google_ramp_up = 2500, |
| .google_ramp_down = 10000, |
| .s3s0_ramp_up = 2000, |
| .s0_tick_delay = { 45000, 30000 }, /* battery, AC */ |
| .s0a_tick_delay = { 5000, 3000 }, /* battery, AC */ |
| .s0s3_ramp_down = 2000, |
| .s3_sleep_for = 5 * SECOND, /* between checks */ |
| .s3_ramp_up = 2500, |
| .s3_ramp_down = 10000, |
| .s5_ramp_up = 2500, |
| .s5_ramp_down = 10000, |
| .tap_tick_delay = 5000, /* oscillation step time */ |
| .tap_gate_delay = 200 * MSEC, /* segment gating delay */ |
| .tap_display_time = 3 * SECOND, /* total sequence time */ |
| |
| /* TODO (crosbug.com/p/36996): remove unused tap_pct_red */ |
| .tap_pct_red = 14, /* below this is red */ |
| .tap_pct_green = 94, /* above this is green */ |
| .tap_seg_min_on = 35, /* min intensity (%) for "on" */ |
| .tap_seg_max_on = 100, /* max intensity (%) for "on" */ |
| .tap_seg_osc = 50, /* amplitude for charging osc */ |
| .tap_idx = {PRIMARY_RED, PRIMARY_YELLOW, PRIMARY_GREEN}, /* color */ |
| |
| .osc_min = { 0x60, 0x60 }, /* battery, AC */ |
| .osc_max = { 0xd0, 0xd0 }, /* battery, AC */ |
| .w_ofs = {24, 24}, /* phase offset, 256 == 2*PI */ |
| |
| .bright_bl_off_fixed = {0xcc, 0xff}, /* backlight off: battery, AC */ |
| .bright_bl_on_min = {0xcc, 0xff}, /* backlight on: battery, AC */ |
| .bright_bl_on_max = {0xcc, 0xff}, /* backlight on: battery, AC */ |
| |
| .battery_threshold = { 14, 40, 99 }, /* percent, lowest to highest */ |
| .s0_idx = { |
| /* battery: 0 = red, other = blue */ |
| { PRIMARY_RED, PRIMARY_BLUE, PRIMARY_BLUE, PRIMARY_BLUE }, |
| /* AC: always blue */ |
| { PRIMARY_BLUE, PRIMARY_BLUE, PRIMARY_BLUE, PRIMARY_BLUE } |
| }, |
| .s3_idx = { |
| /* battery: 0 = red, else off */ |
| { PRIMARY_RED, 0xff, 0xff, 0xff }, |
| /* AC: do nothing */ |
| { 0xff, 0xff, 0xff, 0xff } |
| }, |
| .s5_idx = PRIMARY_RED, /* flash red */ |
| .color = { |
| /* |
| * These values have been optically calibrated for the |
| * Samus LEDs to best match the official colors, described at |
| * https://sites.google.com/a/google.com/brandsite/the-colours |
| * See crosbug.com/p/33017 before making any changes. |
| */ |
| {0x34, 0x70, 0xb4}, /* 0: Google blue */ |
| {0xbc, 0x50, 0x2c}, /* 1: Google red */ |
| {0xd0, 0xe0, 0x00}, /* 2: Google yellow */ |
| {0x50, 0xa0, 0x40}, /* 3: Google green */ |
| /* These are primary colors */ |
| {0x00, 0x00, 0xff}, /* 4: full blue */ |
| {0xff, 0x00, 0x00}, /* 5: full red */ |
| {0xff, 0xff, 0x00}, /* 6: full yellow */ |
| {0x00, 0xff, 0x00}, /* 7: full green */ |
| }, |
| }; |
| |
| #define LB_SYSJUMP_TAG 0x4c42 /* "LB" */ |
| static void lightbar_preserve_state(void) |
| { |
| system_add_jump_tag(LB_SYSJUMP_TAG, 0, sizeof(st), &st); |
| } |
| DECLARE_HOOK(HOOK_SYSJUMP, lightbar_preserve_state, HOOK_PRIO_DEFAULT); |
| |
| static void lightbar_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); |
| CPRINTS("LB state restored: %d %d - %d %d/%d", |
| st.cur_seq, st.prev_seq, |
| st.battery_is_charging, |
| st.battery_percent, |
| st.battery_level); |
| } else { |
| st.cur_seq = st.prev_seq = LIGHTBAR_S5; |
| st.battery_percent = 100; |
| st.battery_level = LB_BATTERY_LEVELS - 1; |
| st.w0 = 0; |
| st.ramp = 0; |
| memcpy(&st.p, &default_params, sizeof(st.p)); |
| CPRINTS("LB state initialized"); |
| } |
| } |
| |
| /******************************************************************************/ |
| /* The patterns are generally dependent on the current battery level and AC |
| * state. These functions obtain that information, generally by querying the |
| * power manager task. In demo mode, the keyboard task forces changes to the |
| * state by calling the demo_* functions directly. */ |
| /******************************************************************************/ |
| |
| #ifdef CONFIG_PWM_KBLIGHT |
| static int last_backlight_level; |
| #endif |
| #ifdef CONFIG_ALS_LIGHTBAR_DIMMING |
| test_export_static int google_color_id; |
| #endif |
| |
| static int demo_mode = DEMO_MODE_DEFAULT; |
| |
| static int quantize_battery_level(int pct) |
| { |
| int i, bl = 0; |
| for (i = 0; i < LB_BATTERY_LEVELS - 1; i++) |
| if (pct >= st.p.battery_threshold[i]) |
| bl++; |
| return bl; |
| } |
| |
| #ifdef CONFIG_ALS_LIGHTBAR_DIMMING |
| test_export_static int lux_level_to_google_color(const int lux) |
| { |
| int i; |
| |
| if (!lid_is_open()) { |
| /* The lid shades the light sensor, use full brightness. */ |
| if (google_color_id != 0) { |
| google_color_id = 0; |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| /* See if we need to decrease brightness */ |
| for (i = google_color_id; i < lb_brightness_levels_count ; i++) |
| if (lux >= lb_brightness_levels[i].lux_down) |
| break; |
| if (i > google_color_id) { |
| google_color_id = i; |
| return 1; |
| } |
| /* See if we need to increase brightness */ |
| for (i = google_color_id; i > 0; i--) |
| if (lux < lb_brightness_levels[i - 1].lux_up) |
| break; |
| if (i < google_color_id) { |
| google_color_id = i; |
| return 1; |
| } |
| return 0; |
| } |
| #endif |
| |
| /* |
| * Update the known state. |
| * Return 1 if something changes. |
| */ |
| static int get_battery_level(void) |
| { |
| int pct = 0; |
| int bl, change = 0; |
| |
| if (demo_mode) |
| return 0; |
| |
| #ifdef HAS_TASK_CHARGER |
| st.battery_percent = pct = charge_get_percent(); |
| st.battery_is_charging = (PWR_STATE_DISCHARGE != charge_get_state()); |
| st.battery_is_power_on_prevented = charge_prevent_power_on(0); |
| #endif |
| |
| /* Find the new battery level */ |
| bl = quantize_battery_level(pct); |
| |
| /* Use some hysteresis to avoid flickering */ |
| if (bl < st.battery_level || |
| (bl > st.battery_level |
| && pct >= (st.p.battery_threshold[st.battery_level] + 1))) { |
| st.battery_level = bl; |
| change = 1; |
| } |
| |
| #ifdef CONFIG_PWM_KBLIGHT |
| /* |
| * With nothing else to go on, use the keyboard backlight level to * |
| * set the brightness. In general, if the keyboard backlight |
| * is OFF (which it is when ambient is bright), use max brightness for |
| * lightbar. If keyboard backlight is ON, use keyboard backlight |
| * brightness. That fails if the keyboard backlight is off because |
| * someone's watching a movie in the dark, of course. Ideally we should |
| * just let the AP control it directly. |
| */ |
| if (pwm_get_enabled(PWM_CH_KBLIGHT)) { |
| pct = pwm_get_duty(PWM_CH_KBLIGHT); |
| pct = (255 * pct) / 100; /* 00 - FF */ |
| if (pct > st.p.bright_bl_on_max[st.battery_is_charging]) |
| pct = st.p.bright_bl_on_max[st.battery_is_charging]; |
| else if (pct < st.p.bright_bl_on_min[st.battery_is_charging]) |
| pct = st.p.bright_bl_on_min[st.battery_is_charging]; |
| } else |
| pct = st.p.bright_bl_off_fixed[st.battery_is_charging]; |
| |
| if (pct != last_backlight_level) { |
| last_backlight_level = pct; |
| lb_set_brightness(pct); |
| change = 1; |
| } |
| #endif |
| #ifdef CONFIG_ALS_LIGHTBAR_DIMMING |
| /* Read last value (in lux) collected by the motion sensor. */ |
| /* Convert lux into brightness percentage */ |
| if (lux_level_to_google_color(MOTION_SENSE_LUX)) { |
| memcpy(st.p.color, lb_brightness_levels[google_color_id].color, |
| sizeof(lb_brightness_levels[google_color_id].color)); |
| change = 1; |
| } |
| #endif |
| return change; |
| } |
| |
| /* Forcing functions for demo mode, called by the keyboard task. */ |
| |
| /* Up/Down keys */ |
| #define DEMO_CHARGE_STEP 1 |
| void demo_battery_level(int inc) |
| { |
| if (!demo_mode) |
| return; |
| |
| st.battery_percent += DEMO_CHARGE_STEP * inc; |
| |
| if (st.battery_percent > 100) |
| st.battery_percent = 100; |
| else if (st.battery_percent < 0) |
| st.battery_percent = 0; |
| |
| st.battery_level = quantize_battery_level(st.battery_percent); |
| |
| CPRINTS("LB demo: battery_percent = %d%%, battery_level=%d", |
| st.battery_percent, st.battery_level); |
| } |
| |
| /* Left/Right keys */ |
| |
| void demo_is_charging(int ischarge) |
| { |
| if (!demo_mode) |
| return; |
| |
| st.battery_is_charging = ischarge; |
| CPRINTS("LB demo: battery_is_charging=%d", |
| st.battery_is_charging); |
| } |
| |
| /* Bright/Dim keys */ |
| void demo_brightness(int inc) |
| { |
| int b; |
| |
| if (!demo_mode) |
| return; |
| |
| b = lb_get_brightness() + (inc * 16); |
| if (b > 0xff) |
| b = 0xff; |
| else if (b < 0) |
| b = 0; |
| lb_set_brightness(b); |
| } |
| |
| /* T key */ |
| void demo_tap(void) |
| { |
| if (!demo_mode) |
| return; |
| lightbar_sequence(LIGHTBAR_TAP); |
| } |
| |
| /******************************************************************************/ |
| /* Helper functions and data. */ |
| /******************************************************************************/ |
| |
| #define F(x) (x * FP_SCALE) |
| static const uint16_t _ramp_table[] = { |
| F(0.000000), F(0.002408), F(0.009607), F(0.021530), F(0.038060), |
| F(0.059039), F(0.084265), F(0.113495), F(0.146447), F(0.182803), |
| F(0.222215), F(0.264302), F(0.308658), F(0.354858), F(0.402455), |
| F(0.450991), F(0.500000), F(0.549009), F(0.597545), F(0.645142), |
| F(0.691342), F(0.735698), F(0.777785), F(0.817197), F(0.853553), |
| F(0.886505), F(0.915735), F(0.940961), F(0.961940), F(0.978470), |
| F(0.990393), F(0.997592), F(1.000000), |
| }; |
| #undef F |
| |
| /* This function provides a smooth ramp up from 0.0 to 1.0 and back to 0.0, |
| * for input from 0x00 to 0xff. */ |
| static inline int cycle_010(uint8_t i) |
| { |
| uint8_t bucket, index; |
| |
| if (i == 128) |
| return FP_SCALE; |
| else if (i > 128) |
| i = 256 - i; |
| |
| bucket = i >> 2; |
| index = i & 0x3; |
| |
| return _ramp_table[bucket] + |
| ((_ramp_table[bucket + 1] - _ramp_table[bucket]) * index >> 2); |
| } |
| |
| /******************************************************************************/ |
| /* Here's where we keep messages waiting to be delivered to the 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); \ |
| uint32_t p_msg = pending_msg; \ |
| if (TASK_EVENT_CUSTOM(msg) == PENDING_MSG && \ |
| p_msg != st.cur_seq) \ |
| return p_msg; } while (0) |
| |
| /******************************************************************************/ |
| /* Here are the preprogrammed sequences. */ |
| /******************************************************************************/ |
| |
| /* Pulse google colors once, off to on to off. */ |
| static uint32_t pulse_google_colors(void) |
| { |
| int w, i, r, g, b; |
| int f; |
| |
| for (w = 0; w < 128; w += 2) { |
| f = cycle_010(w); |
| for (i = 0; i < NUM_LEDS; i++) { |
| r = st.p.color[i].r * f / FP_SCALE; |
| g = st.p.color[i].g * f / FP_SCALE; |
| b = st.p.color[i].b * f / FP_SCALE; |
| lb_set_rgb(i, r, g, b); |
| } |
| WAIT_OR_RET(st.p.google_ramp_up); |
| } |
| for (w = 128; w <= 256; w++) { |
| f = cycle_010(w); |
| for (i = 0; i < NUM_LEDS; i++) { |
| r = st.p.color[i].r * f / FP_SCALE; |
| g = st.p.color[i].g * f / FP_SCALE; |
| b = st.p.color[i].b * f / FP_SCALE; |
| lb_set_rgb(i, r, g, b); |
| } |
| WAIT_OR_RET(st.p.google_ramp_down); |
| } |
| |
| return 0; |
| } |
| |
| /* CPU is waking from sleep. */ |
| static uint32_t sequence_S3S0(void) |
| { |
| int w, r, g, b; |
| int f, fmin; |
| int ci; |
| uint32_t res; |
| |
| lb_init(1); |
| lb_on(); |
| get_battery_level(); |
| |
| res = pulse_google_colors(); |
| if (res) |
| return res; |
| |
| #ifndef BLUE_PULSING |
| /* next sequence */ |
| return LIGHTBAR_S0; |
| #endif |
| |
| /* Ramp up to starting brightness, using S0 colors */ |
| ci = st.p.s0_idx[st.battery_is_charging][st.battery_level]; |
| if (ci >= ARRAY_SIZE(st.p.color)) |
| ci = 0; |
| |
| fmin = st.p.osc_min[st.battery_is_charging] * FP_SCALE / 255; |
| |
| for (w = 0; w <= 128; w++) { |
| f = cycle_010(w) * fmin / FP_SCALE; |
| r = st.p.color[ci].r * f / FP_SCALE; |
| g = st.p.color[ci].g * f / FP_SCALE; |
| b = st.p.color[ci].b * f / FP_SCALE; |
| lb_set_rgb(NUM_LEDS, r, g, b); |
| WAIT_OR_RET(st.p.s3s0_ramp_up); |
| } |
| |
| /* Initial conditions */ |
| st.w0 = -256; /* start cycle_npn() quietly */ |
| st.ramp = 0; |
| |
| /* Ready for S0 */ |
| return LIGHTBAR_S0; |
| } |
| |
| #ifdef BLUE_PULSING |
| |
| /* This function provides a pulsing oscillation between -0.5 and +0.5. */ |
| static inline int cycle_npn(uint16_t i) |
| { |
| if ((i / 256) % 4) |
| return -FP_SCALE / 2; |
| return cycle_010(i) - FP_SCALE / 2; |
| } |
| |
| /* CPU is fully on */ |
| static uint32_t sequence_S0(void) |
| { |
| int tick, last_tick; |
| timestamp_t start, now; |
| uint8_t r, g, b; |
| int i, ci; |
| uint8_t w_ofs; |
| uint16_t w; |
| int f, fmin, fmax, base_s0, osc_s0, f_ramp; |
| |
| start = get_time(); |
| tick = last_tick = 0; |
| |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| lb_on(); |
| |
| while (1) { |
| now = get_time(); |
| |
| /* Only check the battery state every few seconds. 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) / SECOND; |
| if (tick % 4 == 3 && tick != last_tick) { |
| get_battery_level(); |
| last_tick = tick; |
| } |
| |
| /* Calculate the colors */ |
| ci = st.p.s0_idx[st.battery_is_charging][st.battery_level]; |
| if (ci >= ARRAY_SIZE(st.p.color)) |
| ci = 0; |
| w_ofs = st.p.w_ofs[st.battery_is_charging]; |
| fmin = st.p.osc_min[st.battery_is_charging] * FP_SCALE / 255; |
| fmax = st.p.osc_max[st.battery_is_charging] * FP_SCALE / 255; |
| base_s0 = (fmax + fmin) / 2; |
| osc_s0 = fmax - fmin; |
| f_ramp = st.ramp * FP_SCALE / 255; |
| |
| for (i = 0; i < NUM_LEDS; i++) { |
| w = st.w0 - i * w_ofs * f_ramp / FP_SCALE; |
| f = base_s0 + osc_s0 * cycle_npn(w) / FP_SCALE; |
| r = st.p.color[ci].r * f / FP_SCALE; |
| g = st.p.color[ci].g * f / FP_SCALE; |
| b = st.p.color[ci].b * f / FP_SCALE; |
| lb_set_rgb(i, r, g, b); |
| } |
| |
| /* Increment the phase */ |
| if (st.battery_is_charging) |
| st.w0--; |
| else |
| st.w0++; |
| |
| /* Continue ramping in if needed */ |
| if (st.ramp < 0xff) |
| st.ramp++; |
| |
| i = st.p.s0a_tick_delay[st.battery_is_charging]; |
| WAIT_OR_RET(i); |
| } |
| return 0; |
| } |
| |
| #else /* just simple google colors */ |
| |
| static uint32_t sequence_S0(void) |
| { |
| int w, i, r, g, b; |
| int f, change; |
| |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| lb_on(); |
| |
| /* Ramp up */ |
| for (w = 0; w < 128; w += 2) { |
| f = cycle_010(w); |
| for (i = 0; i < NUM_LEDS; i++) { |
| r = st.p.color[i].r * f / FP_SCALE; |
| g = st.p.color[i].g * f / FP_SCALE; |
| b = st.p.color[i].b * f / FP_SCALE; |
| lb_set_rgb(i, r, g, b); |
| } |
| WAIT_OR_RET(st.p.google_ramp_up); |
| } |
| |
| while (1) { |
| change = get_battery_level(); |
| |
| if (change) { |
| /* Not really low use google colors */ |
| if (st.battery_level) { |
| for (i = 0; i < NUM_LEDS; i++) { |
| r = st.p.color[i].r; |
| g = st.p.color[i].g; |
| b = st.p.color[i].b; |
| lb_set_rgb(i, r, g, b); |
| } |
| } else { |
| r = st.p.color[PRIMARY_RED].r; |
| g = st.p.color[PRIMARY_RED].g; |
| b = st.p.color[PRIMARY_RED].b; |
| lb_set_rgb(4, r, g, b); |
| } |
| } |
| |
| WAIT_OR_RET(1 * SECOND); |
| } |
| return 0; |
| } |
| |
| #endif |
| |
| /* CPU is going to sleep. */ |
| static uint32_t sequence_S0S3(void) |
| { |
| int w, i, r, g, b; |
| int f; |
| uint8_t drop[NUM_LEDS][3]; |
| uint32_t res; |
| |
| /* Grab current colors */ |
| for (i = 0; i < NUM_LEDS; i++) |
| lb_get_rgb(i, &drop[i][0], &drop[i][1], &drop[i][2]); |
| |
| /* Fade down to black */ |
| for (w = 128; w <= 256; w++) { |
| f = cycle_010(w); |
| for (i = 0; i < NUM_LEDS; i++) { |
| r = drop[i][0] * f / FP_SCALE; |
| g = drop[i][1] * f / FP_SCALE; |
| b = drop[i][2] * f / FP_SCALE; |
| lb_set_rgb(i, r, g, b); |
| } |
| WAIT_OR_RET(st.p.s0s3_ramp_down); |
| } |
| |
| /* pulse once and done */ |
| res = pulse_google_colors(); |
| if (res) |
| return res; |
| |
| /* next sequence */ |
| return LIGHTBAR_S3; |
| } |
| |
| /* CPU is sleeping */ |
| static uint32_t sequence_S3(void) |
| { |
| int r, g, b; |
| int w; |
| int f; |
| int ci; |
| |
| lb_off(); |
| lb_init(1); |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| get_battery_level(); |
| while (1) { |
| WAIT_OR_RET(st.p.s3_sleep_for); |
| |
| /* only pulse if we've been given a valid color index */ |
| ci = st.p.s3_idx[st.battery_is_charging][st.battery_level]; |
| if (ci >= ARRAY_SIZE(st.p.color)) |
| continue; |
| |
| /* pulse once */ |
| lb_on(); |
| |
| for (w = 0; w < 128; w += 2) { |
| f = cycle_010(w); |
| r = st.p.color[ci].r * f / FP_SCALE; |
| g = st.p.color[ci].g * f / FP_SCALE; |
| b = st.p.color[ci].b * f / FP_SCALE; |
| lb_set_rgb(NUM_LEDS, r, g, b); |
| WAIT_OR_RET(st.p.s3_ramp_up); |
| } |
| for (w = 128; w <= 256; w++) { |
| f = cycle_010(w); |
| r = st.p.color[ci].r * f / FP_SCALE; |
| g = st.p.color[ci].g * f / FP_SCALE; |
| b = st.p.color[ci].b * f / FP_SCALE; |
| lb_set_rgb(NUM_LEDS, r, g, b); |
| WAIT_OR_RET(st.p.s3_ramp_down); |
| } |
| |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| lb_off(); |
| } |
| return 0; |
| } |
| |
| |
| /* CPU is powering up. We generally boot fast enough that we don't have time |
| * to do anything interesting in the S3 state, but go straight on to S0. */ |
| 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); |
| lb_init(1); |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| lb_on(); |
| /* next sequence */ |
| return LIGHTBAR_S3; |
| } |
| |
| /* Sleep to off. The S3->S5 transition takes about 10msec, so just wait. */ |
| static uint32_t sequence_S3S5(void) |
| { |
| lb_off(); |
| /* next sequence */ |
| return LIGHTBAR_S5; |
| } |
| |
| /* Pulse S5 color to indicate that the battery is so critically low that it |
| * must charge first before the system can power on. */ |
| static uint32_t pulse_s5_color(void) |
| { |
| int r, g, b; |
| int f; |
| int w; |
| struct rgb_s *color = &st.p.color[st.p.s5_idx]; |
| |
| for (w = 0; w < 128; w += 2) { |
| f = cycle_010(w); |
| r = color->r * f / FP_SCALE; |
| g = color->g * f / FP_SCALE; |
| b = color->b * f / FP_SCALE; |
| lb_set_rgb(NUM_LEDS, r, g, b); |
| WAIT_OR_RET(st.p.s5_ramp_up); |
| } |
| for (w = 128; w <= 256; w++) { |
| f = cycle_010(w); |
| r = color->r * f / FP_SCALE; |
| g = color->g * f / FP_SCALE; |
| b = color->b * f / FP_SCALE; |
| lb_set_rgb(NUM_LEDS, r, g, b); |
| WAIT_OR_RET(st.p.s5_ramp_down); |
| } |
| |
| return 0; |
| } |
| |
| /* CPU is off. Pulse the lightbar if a charger is attached and the battery is |
| * so low that the system cannot power on. Otherwise, the lightbar loses power |
| * when the CPU is in S5, so there's nothing to do. We'll just wait here until |
| * the state changes. */ |
| static uint32_t sequence_S5(void) |
| { |
| int initialized = 0; |
| uint32_t res = 0; |
| |
| get_battery_level(); |
| while (1) { |
| if (!st.battery_is_power_on_prevented || |
| !st.battery_is_charging) |
| break; |
| |
| if (!initialized) { |
| #ifdef CONFIG_LIGHTBAR_POWER_RAILS |
| /* Request that lightbar power rails be turned on. */ |
| if (lb_power(1)) { |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| } |
| #endif |
| lb_on(); |
| initialized = 1; |
| } |
| |
| res = pulse_s5_color(); |
| if (res) |
| break; |
| } |
| |
| #ifdef CONFIG_LIGHTBAR_POWER_RAILS |
| if (initialized) |
| /* Suggest that the lightbar power rails can be shut down. */ |
| lb_power(0); |
| #endif |
| lb_off(); |
| if (!res) |
| WAIT_OR_RET(-1); |
| return res; |
| } |
| |
| /* The AP 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 (or until we think the AP is shutting down). |
| */ |
| static uint32_t sequence_STOP(void) |
| { |
| uint32_t msg; |
| |
| do { |
| msg = TASK_EVENT_CUSTOM(task_wait_event(-1)); |
| CPRINTS("LB %s() got pending_msg %d", __func__, pending_msg); |
| } while (msg != PENDING_MSG || ( |
| pending_msg != LIGHTBAR_RUN && |
| pending_msg != LIGHTBAR_S0S3 && |
| pending_msg != LIGHTBAR_S3 && |
| pending_msg != LIGHTBAR_S3S5 && |
| pending_msg != LIGHTBAR_S5)); |
| 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. This |
| * sequence is to indicate an internal error in the lightbar logic, not an |
| * error with the Chromebook itself. |
| */ |
| static uint32_t sequence_ERROR(void) |
| { |
| lb_init(1); |
| lb_on(); |
| |
| lb_set_rgb(0, 255, 255, 255); |
| lb_set_rgb(1, 255, 0, 255); |
| lb_set_rgb(2, 0, 255, 255); |
| lb_set_rgb(3, 255, 255, 255); |
| |
| WAIT_OR_RET(10 * SECOND); |
| 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_inner(void) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(konami); i++) { |
| lb_set_rgb(konami[i].led, |
| konami[i].r, konami[i].g, konami[i].b); |
| if (konami[i].delay) |
| WAIT_OR_RET(konami[i].delay); |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t sequence_KONAMI(void) |
| { |
| int tmp; |
| uint32_t r; |
| |
| /* First clear all segments */ |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| |
| /* Force brightness to max, then restore it */ |
| tmp = lb_get_brightness(); |
| lb_set_brightness(255); |
| r = sequence_KONAMI_inner(); |
| lb_set_brightness(tmp); |
| return r; |
| } |
| |
| #ifdef CONFIG_LIGHTBAR_TAP_DIM_LAST_SEGMENT |
| /* Returns 0.0 to 1.0 for val in [min, min + ofs] */ |
| static int range(int val, int min, int ofs) |
| { |
| if (val <= min) |
| return 0; |
| if (val >= min+ofs) |
| return FP_SCALE; |
| return (val - min) * FP_SCALE / ofs; |
| } |
| #endif |
| |
| /* Handy constant */ |
| #define CUT (100 / NUM_LEDS) |
| |
| static uint32_t sequence_TAP_inner(int dir) |
| { |
| enum { RED, YELLOW, GREEN } base_color; |
| timestamp_t start, now; |
| uint32_t elapsed_time = 0; |
| int i, l, ci, max_led; |
| int f_osc, f_mult; |
| int gi, gr, gate[NUM_LEDS] = {0, 0, 0, 0}; |
| uint8_t w = 0; |
| #ifdef CONFIG_LIGHTBAR_TAP_DIM_LAST_SEGMENT |
| int f_min, f_delta, f_power; |
| |
| f_min = st.p.tap_seg_min_on * FP_SCALE / 100; |
| f_delta = (st.p.tap_seg_max_on - st.p.tap_seg_min_on) * FP_SCALE / 100; |
| #endif |
| f_osc = st.p.tap_seg_osc * FP_SCALE / 100; |
| |
| get_battery_level(); |
| |
| if (st.battery_level == 0) |
| base_color = RED; |
| else if (st.battery_percent > st.p.tap_pct_green) |
| base_color = GREEN; |
| else |
| base_color = YELLOW; |
| |
| ci = st.p.tap_idx[base_color]; |
| max_led = st.battery_percent / CUT; |
| |
| start = get_time(); |
| while (1) { |
| /* Enable the segments gradually */ |
| gi = elapsed_time / st.p.tap_gate_delay; |
| gr = elapsed_time % st.p.tap_gate_delay; |
| if (gi < NUM_LEDS) |
| gate[gi] = FP_SCALE * gr / st.p.tap_gate_delay; |
| if (gi && gi <= NUM_LEDS) |
| gate[gi - 1] = FP_SCALE; |
| |
| for (i = 0; i < NUM_LEDS; i++) { |
| |
| #ifdef CONFIG_LIGHTBAR_TAP_DIM_LAST_SEGMENT |
| if (max_led > i) { |
| f_mult = FP_SCALE; |
| } else if (max_led < i) { |
| f_mult = 0; |
| } else { |
| switch (base_color) { |
| case RED: |
| f_power = range(st.battery_percent, 0, |
| st.p.battery_threshold[0] - 1); |
| break; |
| case YELLOW: |
| f_power = range(st.battery_percent, |
| i * CUT, CUT - 1); |
| break; |
| case GREEN: |
| /* green is always full on */ |
| f_power = FP_SCALE; |
| } |
| f_mult = f_min + f_power * f_delta / FP_SCALE; |
| } |
| #else |
| if (max_led >= i) |
| f_mult = FP_SCALE; |
| else if (max_led < i) |
| f_mult = 0; |
| #endif |
| |
| f_mult = f_mult * gate[i] / FP_SCALE; |
| |
| /* Pulse when charging and not yet full */ |
| if (st.battery_is_charging && |
| st.battery_percent <= st.p.tap_pct_green) { |
| int scale = (FP_SCALE - |
| f_osc * cycle_010(w++) / FP_SCALE); |
| f_mult = f_mult * scale / FP_SCALE; |
| } |
| |
| l = dir ? i : NUM_LEDS - 1 - i; |
| lb_set_rgb(l, f_mult * st.p.color[ci].r / FP_SCALE, |
| f_mult * st.p.color[ci].g / FP_SCALE, |
| f_mult * st.p.color[ci].b / FP_SCALE); |
| } |
| |
| WAIT_OR_RET(st.p.tap_tick_delay); |
| |
| /* Return after some time has elapsed */ |
| now = get_time(); |
| elapsed_time = now.le.lo - start.le.lo; |
| if (elapsed_time > st.p.tap_display_time) |
| break; |
| } |
| return 0; |
| } |
| |
| /* Override the tap direction for testing. -1 means ask the PD MCU. */ |
| static int force_dir = -1; |
| |
| /* Return 0 (left or none) or 1 (right) */ |
| static int get_tap_direction(void) |
| { |
| static int last_dir; |
| int dir = 0; |
| |
| if (force_dir >= 0) |
| dir = force_dir; |
| #ifdef HAS_TASK_PDCMD |
| else |
| dir = pd_get_active_charge_port(); |
| #endif |
| if (dir < 0) |
| dir = last_dir; |
| else if (dir != 1) |
| dir = 0; |
| |
| CPRINTS("LB tap direction %d", dir); |
| last_dir = dir; |
| return dir; |
| } |
| |
| static uint32_t sequence_TAP(void) |
| { |
| int i; |
| uint32_t r; |
| uint8_t br, save[NUM_LEDS][3]; |
| int dir; |
| |
| /* |
| * There's a lot of unavoidable glitchiness on the AC_PRESENT interrupt |
| * each time the EC boots, resulting in fights between the TAP sequence |
| * and the S5S3->S3->S3S0->S0 sequences. This delay prevents the lights |
| * from flickering without reducing the responsiveness to manual taps. |
| */ |
| WAIT_OR_RET(100 * MSEC); |
| |
| /* Which direction should the power meter go? */ |
| dir = get_tap_direction(); |
| |
| #ifdef CONFIG_LIGHTBAR_POWER_RAILS |
| /* Request that the lightbar power rails be turned on. */ |
| if (lb_power(1)) { |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| } |
| #endif |
| /* First clear all segments */ |
| lb_set_rgb(NUM_LEDS, 0, 0, 0); |
| |
| lb_on(); |
| |
| for (i = 0; i < NUM_LEDS; i++) |
| lb_get_rgb(i, &save[i][0], &save[i][1], &save[i][2]); |
| br = lb_get_brightness(); |
| lb_set_brightness(255); |
| |
| r = sequence_TAP_inner(dir); |
| |
| lb_set_brightness(br); |
| for (i = 0; i < NUM_LEDS; i++) |
| lb_set_rgb(i, save[i][0], save[i][1], save[i][2]); |
| |
| #ifdef CONFIG_LIGHTBAR_POWER_RAILS |
| /* Suggest that the lightbar power rails can be shut down again. */ |
| lb_power(0); |
| #endif |
| return r; |
| } |
| |
| /****************************************************************************/ |
| /* Lightbar bytecode interpreter: Lightbyte. */ |
| /****************************************************************************/ |
| |
| /* When a program halts, return this. */ |
| #define PROGRAM_FINISHED 2 |
| |
| static struct lightbar_program cur_prog; |
| static struct lightbar_program next_prog; |
| static uint8_t pc; |
| |
| static uint8_t led_desc[NUM_LEDS][LB_CONT_MAX][3]; |
| static uint32_t lb_wait_delay; |
| 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; |
| } |
| |
| /* ON - turn on lightbar */ |
| static uint32_t lightbyte_ON(void) |
| { |
| lb_on(); |
| return EC_SUCCESS; |
| } |
| |
| /* OFF - turn off lightbar */ |
| static uint32_t lightbyte_OFF(void) |
| { |
| lb_off(); |
| return EC_SUCCESS; |
| } |
| |
| /* JUMP xx - jump to immediate location |
| * Changes the pc to the one-byte immediate argument. |
| */ |
| static uint32_t lightbyte_JUMP(void) |
| { |
| return decode_8(&pc); |
| } |
| |
| /* JUMP_BATTERY aa bb - switch on battery level |
| * If the battery is low, changes pc to aa. |
| * If the battery is high, changes pc to bb. |
| * Otherwise, continues execution as normal. |
| */ |
| static uint32_t lightbyte_JUMP_BATTERY(void) |
| { |
| uint8_t low_pc, high_pc; |
| if (decode_8(&low_pc) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| if (decode_8(&high_pc) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| |
| get_battery_level(); |
| if (st.battery_level == 0) |
| pc = low_pc; |
| else if (st.battery_level == 3) |
| pc = high_pc; |
| |
| return EC_SUCCESS; |
| } |
| |
| /* JUMP_IF_CHARGING xx - conditional jump to location |
| * Changes the pc to xx if the device is charging. |
| */ |
| static uint32_t lightbyte_JUMP_IF_CHARGING(void) |
| { |
| uint8_t charge_pc; |
| if (decode_8(&charge_pc) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| |
| if (st.battery_is_charging) |
| pc = charge_pc; |
| |
| return EC_SUCCESS; |
| } |
| |
| /* SET_WAIT_DELAY xx xx xx xx - set up to yield processor |
| * Sets the wait delay to the given four-byte immediate, in |
| * microseconds. Future WAIT instructions will wait for this |
| * much time. |
| */ |
| static uint32_t lightbyte_SET_WAIT_DELAY(void) |
| { |
| return decode_32(&lb_wait_delay); |
| } |
| |
| /* SET_RAMP_DELAY 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_RAMP_DELAY(void) |
| { |
| return decode_32(&lb_ramp_delay); |
| } |
| |
| /* WAIT - yield processor for some time |
| * Yields the processor for some amount of time set by the most |
| * recent SET_WAIT_DELAY instruction. |
| */ |
| static uint32_t lightbyte_WAIT(void) |
| { |
| if (lb_wait_delay != 0) |
| WAIT_OR_RET(lb_wait_delay); |
| |
| 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_SINGLE cc xx |
| * SET_COLOR_RGB 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 a bitset for which LEDs to operate on. |
| * LED 0 is the lowest of the four bits. |
| * |
| * 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 instruction is |
| * SET_COLOR_SINGLE. They correspond to a LB_COL value for the |
| * channel to set the color for using the next immediate byte. |
| * In SET_COLOR_RGB, these bits are don't-cares, as there should |
| * always be three bytes that follow, which correspond to a |
| * complete RGB specification. |
| */ |
| static uint32_t lightbyte_SET_COLOR_SINGLE(void) |
| { |
| |
| uint8_t packed_loc, led, control, color, value; |
| int i; |
| if (decode_8(&packed_loc) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| if (decode_8(&value) != 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; |
| |
| for (i = 0; i < NUM_LEDS; i++) |
| if (led & (1 << i)) |
| led_desc[i][control][color] = value; |
| |
| return EC_SUCCESS; |
| } |
| |
| static uint32_t lightbyte_SET_COLOR_RGB(void) |
| { |
| uint8_t packed_loc, r, g, b, led, control; |
| int i; |
| |
| /* gross */ |
| if (decode_8(&packed_loc) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| if (decode_8(&r) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| if (decode_8(&g) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| if (decode_8(&b) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| |
| led = packed_loc >> 4; |
| control = (packed_loc >> 2) & 0x3; |
| |
| if (control >= LB_CONT_MAX) |
| return EC_RES_INVALID_PARAM; |
| |
| for (i = 0; i < NUM_LEDS; i++) |
| if (led & (1 << i)) { |
| led_desc[i][control][LB_COL_RED] = r; |
| led_desc[i][control][LB_COL_GREEN] = g; |
| led_desc[i][control][LB_COL_BLUE] = b; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /* GET_COLORS - take current colors and push them to the state |
| * Gets the current state of the LEDs and puts them in COLOR0. |
| * Good for the beginning of a program if you need to fade in. |
| */ |
| static uint32_t lightbyte_GET_COLORS(void) |
| { |
| int i; |
| for (i = 0; i < NUM_LEDS; i++) |
| lb_get_rgb(i, &led_desc[i][LB_CONT_COLOR0][LB_COL_RED], |
| &led_desc[i][LB_CONT_COLOR0][LB_COL_GREEN], |
| &led_desc[i][LB_CONT_COLOR0][LB_COL_BLUE]); |
| |
| return EC_SUCCESS; |
| } |
| |
| /* SWAP_COLORS - swaps beginning and end colors in state |
| * Exchanges COLOR0 and COLOR1 on all LEDs. |
| */ |
| static uint32_t lightbyte_SWAP_COLORS(void) |
| { |
| int i, j, tmp; |
| for (i = 0; i < NUM_LEDS; i++) |
| for (j = 0; j < 3; j++) { |
| tmp = led_desc[i][LB_CONT_COLOR0][j]; |
| led_desc[i][LB_CONT_COLOR0][j] = |
| led_desc[i][LB_CONT_COLOR1][j]; |
| led_desc[i][LB_CONT_COLOR1][j] = tmp; |
| } |
| |
| 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); |
| } |
| |
| static void set_all_leds(int color) |
| { |
| int i, r, g, b; |
| for (i = 0; i < NUM_LEDS; i++) { |
| r = led_desc[i][color][LB_COL_RED]; |
| g = led_desc[i][color][LB_COL_GREEN]; |
| b = led_desc[i][color][LB_COL_BLUE]; |
| lb_set_rgb(i, r, g, b); |
| } |
| } |
| |
| static uint32_t ramp_all_leds(int stop_at) |
| { |
| int w, i, r, g, b, f; |
| for (w = 0; w < stop_at; 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; |
| } |
| |
| /* 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) |
| { |
| /* special case for instantaneous set */ |
| if (lb_ramp_delay == 0) { |
| set_all_leds(LB_CONT_COLOR1); |
| return EC_SUCCESS; |
| } |
| |
| return ramp_all_leds(128); |
| } |
| |
| /* 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) |
| { |
| /* special case for instantaneous set */ |
| if (lb_ramp_delay == 0) { |
| set_all_leds(LB_CONT_COLOR0); |
| return EC_SUCCESS; |
| } |
| |
| return ramp_all_leds(256); |
| } |
| |
| /* 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; |
| } |
| |
| /* HALT - return with success |
| * Show's over. Go back to what you were doing before. |
| */ |
| static uint32_t lightbyte_HALT(void) |
| { |
| return PROGRAM_FINISHED; |
| } |
| |
| #undef GET_INTERP_VALUE |
| |
| #define OP(NAME, BYTES, MNEMONIC) NAME, |
| #include "lightbar_opcode_list.h" |
| enum lightbyte_opcode { |
| LIGHTBAR_OPCODE_TABLE |
| MAX_OPCODE |
| }; |
| #undef OP |
| |
| #define OP(NAME, BYTES, MNEMONIC) lightbyte_ ## NAME, |
| #include "lightbar_opcode_list.h" |
| static uint32_t (*lightbyte_dispatch[])(void) = { |
| LIGHTBAR_OPCODE_TABLE |
| }; |
| #undef OP |
| |
| #define OP(NAME, BYTES, MNEMONIC) MNEMONIC, |
| #include "lightbar_opcode_list.h" |
| static const char * const lightbyte_names[] = { |
| LIGHTBAR_OPCODE_TABLE |
| }; |
| #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 lightbar_program)); |
| |
| /* reset program state */ |
| saved_brightness = lb_get_brightness(); |
| pc = 0; |
| memset(led_desc, 0, sizeof(led_desc)); |
| lb_wait_delay = 0; |
| lb_ramp_delay = 0; |
| |
| lb_on(); |
| lb_set_brightness(255); |
| |
| /* decode-execute loop */ |
| for (;;) { |
| old_pc = pc; |
| if (decode_8(&next_inst) != EC_SUCCESS) |
| return EC_RES_INVALID_PARAM; |
| |
| 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. */ |
| /****************************************************************************/ |
| |
| /* Distinguish "normal" sequences from one-shot sequences */ |
| static inline int is_normal_sequence(enum lightbar_sequence seq) |
| { |
| return (seq >= LIGHTBAR_S5 && seq <= LIGHTBAR_S3S5); |
| } |
| |
| /* 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 next_seq; |
| |
| CPRINTS("LB task starting"); |
| |
| lightbar_restore_state(); |
| |
| while (1) { |
| CPRINTS("LB running cur_seq %d %s. prev_seq %d %s", |
| st.cur_seq, lightbar_cmds[st.cur_seq].string, |
| st.prev_seq, lightbar_cmds[st.prev_seq].string); |
| next_seq = lightbar_cmds[st.cur_seq].sequence(); |
| if (next_seq) { |
| CPRINTS("LB cur_seq %d %s returned pending msg %d %s", |
| st.cur_seq, lightbar_cmds[st.cur_seq].string, |
| next_seq, lightbar_cmds[next_seq].string); |
| if (st.cur_seq != next_seq) { |
| if (is_normal_sequence(st.cur_seq)) |
| st.prev_seq = st.cur_seq; |
| st.cur_seq = next_seq; |
| } |
| } else { |
| CPRINTS("LB cur_seq %d %s returned value 0", |
| st.cur_seq, lightbar_cmds[st.cur_seq].string); |
| 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_STOP: |
| case LIGHTBAR_RUN: |
| case LIGHTBAR_ERROR: |
| case LIGHTBAR_KONAMI: |
| case LIGHTBAR_TAP: |
| case LIGHTBAR_PROGRAM: |
| st.cur_seq = st.prev_seq; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| /* Function to request a preset sequence from the lightbar task. */ |
| void lightbar_sequence_f(enum lightbar_sequence num, const char *f) |
| { |
| if (num > 0 && num < LIGHTBAR_NUM_SEQUENCES) { |
| CPRINTS("LB %s() requests %d %s", f, num, |
| lightbar_cmds[num].string); |
| pending_msg = num; |
| task_set_event(TASK_ID_LIGHTBAR, |
| TASK_EVENT_CUSTOM(PENDING_MSG), |
| 0); |
| } else |
| CPRINTS("LB %s() requests %d - ignored", f, num); |
| } |
| |
| /****************************************************************************/ |
| /* Get notifications from other parts of the system */ |
| |
| static uint8_t manual_suspend_control; |
| |
| static void lightbar_startup(void) |
| { |
| manual_suspend_control = 0; |
| lightbar_sequence(LIGHTBAR_S5S3); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_STARTUP, lightbar_startup, HOOK_PRIO_DEFAULT); |
| |
| static void lightbar_resume(void) |
| { |
| if (!manual_suspend_control) |
| lightbar_sequence(LIGHTBAR_S3S0); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_RESUME, lightbar_resume, HOOK_PRIO_DEFAULT); |
| |
| static void lightbar_suspend(void) |
| { |
| if (!manual_suspend_control) |
| lightbar_sequence(LIGHTBAR_S0S3); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, lightbar_suspend, HOOK_PRIO_DEFAULT); |
| |
| static void lightbar_shutdown(void) |
| { |
| lightbar_sequence(LIGHTBAR_S3S5); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, lightbar_shutdown, HOOK_PRIO_DEFAULT); |
| |
| /****************************************************************************/ |
| /* Host commands via LPC bus */ |
| /****************************************************************************/ |
| |
| static int lpc_cmd_lightbar(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_lightbar *in = args->params; |
| struct ec_response_lightbar *out = args->response; |
| int rv; |
| |
| switch (in->cmd) { |
| case LIGHTBAR_CMD_DUMP: |
| lb_hc_cmd_dump(out); |
| args->response_size = sizeof(out->dump); |
| break; |
| case LIGHTBAR_CMD_OFF: |
| lb_off(); |
| break; |
| case LIGHTBAR_CMD_ON: |
| lb_on(); |
| break; |
| case LIGHTBAR_CMD_INIT: |
| lb_init(1); |
| break; |
| case LIGHTBAR_CMD_SET_BRIGHTNESS: |
| lb_set_brightness(in->set_brightness.num); |
| break; |
| case LIGHTBAR_CMD_GET_BRIGHTNESS: |
| out->get_brightness.num = lb_get_brightness(); |
| args->response_size = sizeof(out->get_brightness); |
| break; |
| case LIGHTBAR_CMD_SEQ: |
| lightbar_sequence(in->seq.num); |
| break; |
| case LIGHTBAR_CMD_REG: |
| lb_hc_cmd_reg(in); |
| break; |
| case LIGHTBAR_CMD_SET_RGB: |
| lb_set_rgb(in->set_rgb.led, |
| in->set_rgb.red, |
| in->set_rgb.green, |
| in->set_rgb.blue); |
| break; |
| case LIGHTBAR_CMD_GET_RGB: |
| rv = lb_get_rgb(in->get_rgb.led, |
| &out->get_rgb.red, |
| &out->get_rgb.green, |
| &out->get_rgb.blue); |
| if (rv == EC_RES_SUCCESS) |
| args->response_size = sizeof(out->get_rgb); |
| return rv; |
| case LIGHTBAR_CMD_GET_SEQ: |
| out->get_seq.num = st.cur_seq; |
| args->response_size = sizeof(out->get_seq); |
| break; |
| case LIGHTBAR_CMD_DEMO: |
| demo_mode = in->demo.num ? 1 : 0; |
| CPRINTS("LB_demo %d", demo_mode); |
| break; |
| case LIGHTBAR_CMD_GET_DEMO: |
| out->get_demo.num = demo_mode; |
| args->response_size = sizeof(out->get_demo); |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V0: |
| CPRINTS("LB_get_params_v0 not supported"); |
| return EC_RES_INVALID_VERSION; |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V0: |
| CPRINTS("LB_set_params_v0 not supported"); |
| return EC_RES_INVALID_VERSION; |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V1: |
| CPRINTS("LB_get_params_v1"); |
| memcpy(&out->get_params_v1, &st.p, sizeof(st.p)); |
| args->response_size = sizeof(out->get_params_v1); |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V1: |
| 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 lightbar_program)); |
| break; |
| case LIGHTBAR_CMD_VERSION: |
| CPRINTS("LB_version"); |
| out->version.num = LIGHTBAR_IMPLEMENTATION_VERSION; |
| out->version.flags = LIGHTBAR_IMPLEMENTATION_FLAGS; |
| args->response_size = sizeof(out->version); |
| break; |
| case LIGHTBAR_CMD_MANUAL_SUSPEND_CTRL: |
| CPRINTS("LB_manual_suspend_ctrl"); |
| manual_suspend_control = in->manual_suspend_ctrl.enable; |
| break; |
| case LIGHTBAR_CMD_SUSPEND: |
| CPRINTS("LB_suspend"); |
| lightbar_sequence(LIGHTBAR_S0S3); |
| break; |
| case LIGHTBAR_CMD_RESUME: |
| CPRINTS("LB_resume"); |
| lightbar_sequence(LIGHTBAR_S3S0); |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V2_TIMING: |
| CPRINTS("LB_get_params_v2_timing"); |
| memcpy(&out->get_params_v2_timing, |
| &st.p_v2.timing, |
| sizeof(st.p_v2.timing)); |
| args->response_size = sizeof(out->get_params_v2_timing); |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V2_TIMING: |
| CPRINTS("LB_set_params_v2_timing"); |
| memcpy(&st.p_v2.timing, |
| &in->set_v2par_timing, |
| sizeof(struct lightbar_params_v2_timing)); |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V2_TAP: |
| CPRINTS("LB_get_params_v2_tap"); |
| memcpy(&out->get_params_v2_tap, |
| &st.p_v2.tap, |
| sizeof(struct lightbar_params_v2_tap)); |
| args->response_size = sizeof(out->get_params_v2_tap); |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V2_TAP: |
| CPRINTS("LB_set_params_v2_tap"); |
| memcpy(&st.p_v2.tap, |
| &in->set_v2par_tap, |
| sizeof(struct lightbar_params_v2_tap)); |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V2_OSCILLATION: |
| CPRINTS("LB_get_params_v2_oscillation"); |
| memcpy(&out->get_params_v2_osc, &st.p_v2.osc, |
| sizeof(struct lightbar_params_v2_oscillation)); |
| args->response_size = sizeof(out->get_params_v2_osc); |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V2_OSCILLATION: |
| CPRINTS("LB_set_params_v2_oscillation"); |
| memcpy(&st.p_v2.osc, |
| &in->set_v2par_osc, |
| sizeof(struct lightbar_params_v2_oscillation)); |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V2_BRIGHTNESS: |
| CPRINTS("LB_get_params_v2_brightness"); |
| memcpy(&out->get_params_v2_bright, |
| &st.p_v2.bright, |
| sizeof(struct lightbar_params_v2_brightness)); |
| args->response_size = sizeof(out->get_params_v2_bright); |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V2_BRIGHTNESS: |
| CPRINTS("LB_set_params_v2_brightness"); |
| memcpy(&st.p_v2.bright, |
| &in->set_v2par_bright, |
| sizeof(struct lightbar_params_v2_brightness)); |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V2_THRESHOLDS: |
| CPRINTS("LB_get_params_v2_thlds"); |
| memcpy(&out->get_params_v2_thlds, |
| &st.p_v2.thlds, |
| sizeof(struct lightbar_params_v2_thresholds)); |
| args->response_size = sizeof(out->get_params_v2_thlds); |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V2_THRESHOLDS: |
| CPRINTS("LB_set_params_v2_thlds"); |
| memcpy(&st.p_v2.thlds, |
| &in->set_v2par_thlds, |
| sizeof(struct lightbar_params_v2_thresholds)); |
| break; |
| case LIGHTBAR_CMD_GET_PARAMS_V2_COLORS: |
| CPRINTS("LB_get_params_v2_colors"); |
| memcpy(&out->get_params_v2_colors, |
| &st.p_v2.colors, |
| sizeof(struct lightbar_params_v2_colors)); |
| args->response_size = sizeof(out->get_params_v2_colors); |
| break; |
| case LIGHTBAR_CMD_SET_PARAMS_V2_COLORS: |
| CPRINTS("LB_set_params_v2_colors"); |
| memcpy(&st.p_v2.colors, |
| &in->set_v2par_colors, |
| sizeof(struct lightbar_params_v2_colors)); |
| break; |
| default: |
| CPRINTS("LB bad cmd 0x%x", 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 CONFIG_CONSOLE_CMDHELP |
| 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=%d for all)\n", cmd, NUM_LEDS); |
| ccprintf(" %s LED - get current LED color\n", cmd); |
| ccprintf(" %s demo [0|1] - turn demo mode on & off\n", cmd); |
| #ifdef LIGHTBAR_SIMULATION |
| ccprintf(" %s program filename - load lightbyte program\n", cmd); |
| #endif |
| ccprintf(" %s version - show current version\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, led, r = 0, g = 0, b = 0; |
| struct ec_response_lightbar out; |
| char *e; |
| |
| if (argc == 1) { /* no args = dump 'em all */ |
| lb_hc_cmd_dump(&out); |
| for (i = 0; i < ARRAY_SIZE(out.dump.vals); i++) |
| ccprintf(" %02x %02x %02x\n", |
| out.dump.vals[i].reg, |
| out.dump.vals[i].ic0, |
| out.dump.vals[i].ic1); |
| |
| return EC_SUCCESS; |
| } |
| |
| if (!strcasecmp(argv[1], "init")) { |
| lb_init(1); |
| return EC_SUCCESS; |
| } |
| |
| if (!strcasecmp(argv[1], "off")) { |
| lb_off(); |
| return EC_SUCCESS; |
| } |
| |
| if (!strcasecmp(argv[1], "on")) { |
| lb_on(); |
| return EC_SUCCESS; |
| } |
| |
| if (!strcasecmp(argv[1], "version")) { |
| ccprintf("version %d flags 0x%x\n", |
| LIGHTBAR_IMPLEMENTATION_VERSION, |
| LIGHTBAR_IMPLEMENTATION_FLAGS); |
| return EC_SUCCESS; |
| } |
| |
| if (!strcasecmp(argv[1], "brightness")) { |
| if (argc > 2) { |
| num = 0xff & strtoi(argv[2], &e, 16); |
| lb_set_brightness(num); |
| } |
| ccprintf("brightness is %02x\n", lb_get_brightness()); |
| return EC_SUCCESS; |
| } |
| |
| if (!strcasecmp(argv[1], "demo")) { |
| if (argc > 2) { |
| 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 (!strcasecmp(argv[1], "seq")) { |
| 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; |
| if (argc > 3) /* for testing TAP direction */ |
| force_dir = strtoi(argv[3], 0, 0); |
| lightbar_sequence(num); |
| return EC_SUCCESS; |
| } |
| |
| #ifdef LIGHTBAR_SIMULATION |
| /* Load a program. */ |
| if (argc >= 3 && !strcasecmp(argv[1], "program")) { |
| return lb_load_program(argv[2], &next_prog); |
| } |
| #endif |
| |
| if (argc == 4) { |
| struct ec_params_lightbar in; |
| in.reg.ctrl = strtoi(argv[1], &e, 16); |
| in.reg.reg = strtoi(argv[2], &e, 16); |
| in.reg.value = strtoi(argv[3], &e, 16); |
| lb_hc_cmd_reg(&in); |
| return EC_SUCCESS; |
| } |
| |
| if (argc == 5) { |
| led = strtoi(argv[1], &e, 16); |
| r = strtoi(argv[2], &e, 16); |
| g = strtoi(argv[3], &e, 16); |
| b = strtoi(argv[4], &e, 16); |
| lb_set_rgb(led, r, g, b); |
| return EC_SUCCESS; |
| } |
| |
| /* Only thing left is to try to read an LED value */ |
| num = strtoi(argv[1], &e, 16); |
| if (!(e && *e)) { |
| if (num >= NUM_LEDS) { |
| for (i = 0; i < NUM_LEDS; i++) { |
| lb_get_rgb(i, &r, &g, &b); |
| ccprintf("%x: %02x %02x %02x\n", i, r, g, b); |
| } |
| } else { |
| lb_get_rgb(num, &r, &g, &b); |
| ccprintf("%02x %02x %02x\n", r, g, b); |
| } |
| return EC_SUCCESS; |
| } |
| |
| |
| #ifdef CONFIG_CONSOLE_CMDHELP |
| help(argv[0]); |
| #endif |
| |
| return EC_ERROR_INVAL; |
| } |
| DECLARE_CONSOLE_COMMAND(lightbar, command_lightbar, |
| "[help | COMMAND [ARGS]]", |
| "Get/set lightbar state"); |