/* * 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); } /* This function provides a smooth oscillation between -0.5 and +0.5. * Zero starts at 0x00. */ static inline int cycle_0p0n0(uint8_t i) { return cycle_010(i + 64) - FP_SCALE / 2; } /* 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; } /******************************************************************************/ /* 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 /* 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");