diff options
-rw-r--r-- | board/eve/ec.tasklist | 1 | ||||
-rw-r--r-- | board/eve/led.c | 277 |
2 files changed, 237 insertions, 41 deletions
diff --git a/board/eve/ec.tasklist b/board/eve/ec.tasklist index 16f7ec7ec5..a3dd5b05fd 100644 --- a/board/eve/ec.tasklist +++ b/board/eve/ec.tasklist @@ -22,6 +22,7 @@ #define CONFIG_TASK_LIST \ TASK_ALWAYS(HOOKS, hook_task, NULL, LARGER_TASK_STACK_SIZE) \ + TASK_ALWAYS(LED, led_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CHG_RAMP, chg_ramp_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(USB_CHG, usb_charger_task, NULL, TASK_STACK_SIZE) \ TASK_ALWAYS(CHARGER, charger_task, NULL, LARGER_TASK_STACK_SIZE) \ diff --git a/board/eve/led.c b/board/eve/led.c index 1295dc8cc9..9574ff1922 100644 --- a/board/eve/led.c +++ b/board/eve/led.c @@ -14,21 +14,27 @@ #include "hooks.h" #include "led_common.h" #include "pwm.h" +#include "math_util.h" #include "registers.h" +#include "task.h" #include "util.h" #define CPRINTF(format, args...) cprintf(CC_PWM, format, ## args) #define CPRINTS(format, args...) cprints(CC_PWM, format, ## args) -#define LED_TICKS_PER_BEAT 2 +#define LED_TICK_TIME (500 * MSEC) +#define LED_TICKS_PER_BEAT 1 #define NUM_PHASE 2 #define DOUBLE_TAP_TICK_LEN (LED_TICKS_PER_BEAT * 8) +#define LED_FRAC_BITS 4 +#define LED_STEP_MSEC 45 static int led_debug; static int double_tap; static int double_tap_tick_count; static int led_pattern; static int led_ticks; +static enum led_color led_current_color; const enum ec_led_id supported_led_ids[] = { EC_LED_ID_LEFT_LED, EC_LED_ID_RIGHT_LED}; @@ -106,8 +112,8 @@ static const uint8_t color_brightness[LED_COLOR_COUNT][PWM_CHAN_PER_LED] = { [LED_GREEN] = {0, 80, 0}, [LED_BLUE] = {0, 0, 80}, [LED_WHITE] = {100, 100, 100}, - [LED_RED_2_3] = {53, 0, 0}, - [LED_RED_1_3] = {27, 0, 0}, + [LED_RED_2_3] = {40, 0, 0}, + [LED_RED_1_3] = {20, 0, 0}, }; /* @@ -136,13 +142,29 @@ static const struct range_map pattern_tbl[] = { {100, SOLID_GREEN}, }; +enum led_state_change { + LED_STATE_INTENSITY_DOWN, + LED_STATE_INTENSITY_UP, + LED_STATE_DONE, +}; + +/* + * The PWM % on levels to transition from intensity 0 (black) to intensity 1.0 + * (white) in the HSI color space converted back to RGB space (0 - 255) and + * converted to a % for PWM. This table is used for Red <--> White and Green + * <--> Transitions. In HSI space white = (0, 0, 1), red = (0, .5, .33), green = + * (120, .5, .33). For the transitions of interest only S and I are changed and + * they are changed linearly in HSI space. + */ +static const uint8_t trans_steps[] = {0, 4, 9, 16, 24, 33, 44, 56, 69, 84, 100}; + /** * Set LED color * - * @param color Enumerated color value + * @param pwm Pointer to 3 element RGB color level (0 -> 100) * @param side Left LED, Right LED, or both LEDs */ -static void set_color(enum led_color color, enum led_side side) +static void set_color(const uint8_t *pwm, enum led_side side) { int i; static uint8_t saved_duty[LED_BOTH][PWM_CHAN_PER_LED]; @@ -150,12 +172,10 @@ static void set_color(enum led_color color, enum led_side side) /* Set color for left LED */ if (side == LED_LEFT || side == LED_BOTH) { for (i = 0; i < PWM_CHAN_PER_LED; i++) { - if (saved_duty[LED_LEFT][i] != - color_brightness[color][i]) { + if (saved_duty[LED_LEFT][i] != pwm[i]) { pwm_set_duty(PWM_CH_LED_L_RED + i, - 100 - color_brightness[color][i]); - saved_duty[LED_LEFT][i] = - color_brightness[color][i]; + 100 - pwm[i]); + saved_duty[LED_LEFT][i] = pwm[i]; } } } @@ -163,12 +183,10 @@ static void set_color(enum led_color color, enum led_side side) /* Set color for right LED */ if (side == LED_RIGHT || side == LED_BOTH) { for (i = 0; i < PWM_CHAN_PER_LED; i++) { - if (saved_duty[LED_RIGHT][i] != - color_brightness[color][i]) { + if (saved_duty[LED_RIGHT][i] != pwm[i]) { pwm_set_duty(PWM_CH_LED_R_RED + i, - 100 - color_brightness[color][i]); - saved_duty[LED_RIGHT][i] = - color_brightness[color][i]; + 100 - pwm[i]); + saved_duty[LED_RIGHT][i] = pwm[i]; } } } @@ -213,6 +231,164 @@ void led_register_double_tap(void) double_tap = 1; } +static void led_change_color(int old_idx, int new_idx, enum led_side side) +{ + int i; + int step; + int state; + uint8_t rgb_current[3]; + const uint8_t *rgb_target; + uint8_t trans[ARRAY_SIZE(trans_steps)]; + int increase = 0; + + /* + * Using the color indices, poplulate the current and target R, G, B + * arrays. The arrays are indexed R = 0, G = 1, B = 2. If the target of + * any of the 3 is greater than the current, then this color change is + * an increase in intensity. Otherwise, it's a decrease. + */ + rgb_target = color_brightness[new_idx]; + for (i = 0; i < PWM_CHAN_PER_LED; i++) { + rgb_current[i] = color_brightness[old_idx][i]; + if (rgb_current[i] < rgb_target[i]) { + /* increase in color */ + increase = 1; + } + } + /* Check to see if increasing or decreasing color */ + if (increase) { + state = LED_STATE_INTENSITY_UP; + /* First entry of transition table == current level */ + step = 1; + } else { + /* Last entry of transition table == current level */ + step = ARRAY_SIZE(trans_steps) - 2; + state = LED_STATE_INTENSITY_DOWN; + } + + /* + * Populate transition table based on the number of R, G, B components + * changing. If only 1 componenet is changing, then can just do linear + * steps over the range. If more than 1 component is changing, then + * this is a white <--> color transition and will use + * the precomputed steps which are derived by converting to HSI space + * and then linearly transitioning S and I to go from the starting color + * to white and vice versa. + */ + if (old_idx == LED_WHITE || new_idx == LED_WHITE) { + for (i = 0; i < ARRAY_SIZE(trans_steps); i++) + trans[i] = trans_steps[i]; + } else { + int delta_per_step; + int step_value; + int start_lvl; + int total_change; + /* Assume that the R component (index = 0) is changing */ + int rgb_index = 0; + + /* + * Since the new or old color is not white, then this change + * must involve only either red or green. There are no red <--> + * green transitions. So only 1 color is being changed in this + * case. Assume it's red (index = 0), but check if it's green + * (index = 1). + */ + + if (old_idx == LED_GREEN || new_idx == LED_GREEN) + rgb_index = 1; + + /* + * Determine the total change assuming current level is higher + * than target level. The transitions steps are always ordered + * lower to higher. The starting index is adjusted if intensity + * is decreasing. + */ + start_lvl = rgb_target[rgb_index]; + + if (state == LED_STATE_INTENSITY_UP) + /* + * Increasing in intensity, current level or R/G is + * the starting level. + */ + start_lvl = rgb_current[rgb_index]; + + /* + * Compute change per step using fractional bits. The step + * change accumulates fractional bits and is truncated after + * rounding before being added to the starting value. + */ + total_change = ABS(rgb_current[rgb_index] - + rgb_target[rgb_index]); + delta_per_step = (total_change << LED_FRAC_BITS) + / (ARRAY_SIZE(trans_steps) - 1); + step_value = 0; + for (i = 0; i < ARRAY_SIZE(trans_steps); i++) { + trans[i] = start_lvl + + ((step_value + + (1 << (LED_FRAC_BITS - 1))) + >> LED_FRAC_BITS); + step_value += delta_per_step; + } + } + + /* Will loop here until the color change is complete. */ + while (state != LED_STATE_DONE) { + int change = 0; + + if (state == LED_STATE_INTENSITY_DOWN) { + /* + * Colors are going from higher to lower level. If the + * current level of R, G, or B is higher than both + * the next step in the transition table and and the + * target level, then move to the larger of the two. The + * MAX is used to make sure that it doens't drop below + * the target level. + */ + for (i = 0; i < PWM_CHAN_PER_LED; i++) { + if ((rgb_current[i] > rgb_target[i]) && + (rgb_current[i] >= trans[step])) { + rgb_current[i] = MAX(trans[step], + rgb_target[i]); + change = 1; + } + } + /* + * If nothing changed this iteration, or if lowest table + * entry has been used, then the change is complete. + */ + if (!change || --step < 0) + state = LED_STATE_DONE; + + } else if (state == LED_STATE_INTENSITY_UP) { + /* + * Colors are going from lower to higher level. If the + * current level of R, G, B is lower than both the + * target level and the transition table entry for a + * given color, then move up to the MIN of next + * transition step and target level. + */ + for (i = 0; i < PWM_CHAN_PER_LED; i++) { + if ((rgb_current[i] < rgb_target[i]) && + (rgb_current[i] <= trans[step])) { + rgb_current[i] = MIN(trans[step], + rgb_target[i]); + change = 1; + } + } + /* + * If nothing changed this iteration, or if highest + * table entry has been used, then the change is + * complete. + */ + if (!change || ++step >= ARRAY_SIZE(trans_steps)) + state = LED_STATE_DONE; + } + /* Apply current R, G, B levels */ + set_color(rgb_current, side); + msleep(LED_STEP_MSEC); + } +} + static void led_manage_pattern(int side) { int color; @@ -222,8 +398,13 @@ static void led_manage_pattern(int side) phase = led_ticks < LED_TICKS_PER_BEAT * pattern[led_pattern].len[0] ? 0 : 1; color = pattern[led_pattern].color[phase]; + /* If color is changing, then manage the transition */ + if (led_current_color != color) { + led_change_color(led_current_color, color, side); + led_current_color = color; + } /* Set color for the current phase */ - set_color(color, side); + set_color(color_brightness[color], side); /* * Update led_ticks. If the len field is 0, then the pattern @@ -313,7 +494,7 @@ static void eve_led_set_power_battery(void) * on the side with the charger is turned on. */ if (side != LED_BOTH) - set_color(LED_OFF, side ^ 1); + set_color(color_brightness[LED_OFF], side ^ 1); /* Update LED pattern */ led_manage_pattern(side); } @@ -334,7 +515,7 @@ static void led_init(void) pwm_enable(PWM_CH_LED_R_GREEN, 1); pwm_enable(PWM_CH_LED_R_BLUE, 1); - set_color(LED_OFF, LED_BOTH); + set_color(color_brightness[LED_OFF], LED_BOTH); led_pattern = OFF; led_ticks = 0; double_tap_tick_count = 0; @@ -342,21 +523,30 @@ static void led_init(void) /* After pwm_pin_init() */ DECLARE_HOOK(HOOK_INIT, led_init, HOOK_PRIO_DEFAULT); -/** - * Called by hook task every 250 ms - */ -static void led_tick(void) +void led_task(void) { - if (led_debug == 1) - return; + uint32_t start_time; + uint32_t task_duration; + + while (1) { - if (led_auto_control_is_enabled(EC_LED_ID_LEFT_LED) && - led_auto_control_is_enabled(EC_LED_ID_RIGHT_LED)) { - eve_led_set_power_battery(); - return; + start_time = get_time().le.lo; + + if (led_auto_control_is_enabled(EC_LED_ID_LEFT_LED) && + led_auto_control_is_enabled(EC_LED_ID_RIGHT_LED) && + led_debug != 1) { + eve_led_set_power_battery(); + } + /* Compute time for this iteration */ + task_duration = get_time().le.lo - start_time; + /* + * Compute wait time required to for next desired LED tick. If + * the duration exceeds the tick time, then don't sleep. + */ + if (task_duration < LED_TICK_TIME) + usleep(LED_TICK_TIME - task_duration); } } -DECLARE_HOOK(HOOK_TICK, led_tick, HOOK_PRIO_DEFAULT); /******************************************************************/ /* Console commands */ @@ -364,6 +554,7 @@ static int command_led(int argc, char **argv) { int side = LED_BOTH; char *e; + enum led_color color; if (argc > 1) { if (argc > 2) { @@ -377,19 +568,23 @@ static int command_led(int argc, char **argv) if (!strcasecmp(argv[1], "debug")) { led_debug ^= 1; CPRINTF("led_debug = %d\n", led_debug); - } else if (!strcasecmp(argv[1], "off")) { - set_color(LED_OFF, side); - } else if (!strcasecmp(argv[1], "red")) { - set_color(LED_RED, side); - } else if (!strcasecmp(argv[1], "green")) { - set_color(LED_GREEN, side); - } else if (!strcasecmp(argv[1], "blue")) { - set_color(LED_BLUE, side); - } else if (!strcasecmp(argv[1], "white")) { - set_color(LED_WHITE, side); - } else { - return EC_ERROR_PARAM1; + return EC_SUCCESS; } + + if (!strcasecmp(argv[1], "off")) + color = LED_OFF; + else if (!strcasecmp(argv[1], "red")) + color = LED_RED; + else if (!strcasecmp(argv[1], "green")) + color = LED_GREEN; + else if (!strcasecmp(argv[1], "blue")) + color = LED_BLUE; + else if (!strcasecmp(argv[1], "white")) + color = LED_WHITE; + else + return EC_ERROR_PARAM1; + + set_color(color_brightness[color], side); } return EC_SUCCESS; } |