diff --git a/common/build.mk b/common/build.mk index a5b530e9e7..e6b6a2da6c 100644 --- a/common/build.mk +++ b/common/build.mk @@ -72,6 +72,7 @@ common-$(CONFIG_KEYBOARD_PROTOCOL_MKBP)+=keyboard_mkbp.o common-$(CONFIG_KEYBOARD_TEST)+=keyboard_test.o common-$(CONFIG_LED_COMMON)+=led_common.o common-$(CONFIG_LED_POLICY_STD)+=led_policy_std.o +common-$(CONFIG_LED_PWM)+=led_pwm.o common-$(CONFIG_LID_ANGLE)+=motion_lid.o math_util.o common-$(CONFIG_LID_ANGLE_UPDATE)+=lid_angle.o common-$(CONFIG_LID_SWITCH)+=lid_switch.o diff --git a/common/led_pwm.c b/common/led_pwm.c new file mode 100644 index 0000000000..7e86a64bb2 --- /dev/null +++ b/common/led_pwm.c @@ -0,0 +1,206 @@ +/* Copyright 2018 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. + */ + +/* PWM LED control to conform to Chrome OS LED behaviour specification. */ + +/* + * This assumes that a single logical LED is shared between both power and + * charging/battery status. If multiple logical LEDs are present, they all + * follow the same patterns. + */ + +#include "battery.h" +#include "charge_state.h" +#include "chipset.h" +#include "common.h" +#include "console.h" +#include "ec_commands.h" +#include "hooks.h" +#include "led_common.h" +#include "led_pwm.h" +#include "pwm.h" +#include "timer.h" +#include "util.h" + +/* Battery percentage thresholds to blink at different rates. */ +#define CRITICAL_LOW_BATTERY_PERCENTAGE 3 +#define LOW_BATTERY_PERCENTAGE 10 + +#define PULSE_TICK (250 * MSEC) + +void set_pwm_led_color(enum pwm_led_id id, int color) +{ + struct pwm_led duty = { 0 }; + + if ((id > CONFIG_LED_PWM_COUNT) || (id < 0) || + (color > EC_LED_COLOR_COUNT) || (color < -1)) + return; + + if (color != -1) { + duty.ch0 = led_color_map[color].ch0; + duty.ch1 = led_color_map[color].ch1; + duty.ch2 = led_color_map[color].ch2; + } + + if (pwm_leds[id].ch0 != PWM_LED_NO_CHANNEL) + pwm_set_duty(pwm_leds[id].ch0, duty.ch0); + if (pwm_leds[id].ch1 != PWM_LED_NO_CHANNEL) + pwm_set_duty(pwm_leds[id].ch1, duty.ch1); + if (pwm_leds[id].ch2 != PWM_LED_NO_CHANNEL) + pwm_set_duty(pwm_leds[id].ch2, duty.ch2); +} + +static void set_led_color(int color) +{ + /* + * We must check if auto control is enabled since the LEDs may be + * controlled from the AP at anytime. + */ + if ((led_auto_control_is_enabled(EC_LED_ID_POWER_LED)) || + (led_auto_control_is_enabled(EC_LED_ID_LEFT_LED))) + set_pwm_led_color(PWM_LED0, color); + +#if CONFIG_LED_PWM_COUNT >= 2 + if (led_auto_control_is_enabled(EC_LED_ID_RIGHT_LED)) + set_pwm_led_color(PWM_LED1, color); +#endif /* CONFIG_LED_PWM_COUNT >= 2 */ +} + +static uint8_t led_is_pulsing; +static uint8_t pulse_period; +static uint8_t pulse_ontime; +static enum ec_led_colors pulse_color; +static void pulse_leds_deferred(void); +DECLARE_DEFERRED(pulse_leds_deferred); +static void pulse_leds_deferred(void) +{ + static uint8_t tick_count; + + if (!led_is_pulsing) { + tick_count = 0; + return; + } + + if (tick_count < pulse_ontime) + set_led_color(pulse_color); + else + set_led_color(-1); + + tick_count = (tick_count + 1) % pulse_period; + hook_call_deferred(&pulse_leds_deferred_data, PULSE_TICK); +} + +static void pulse_leds(enum ec_led_colors color, int ontime, int period) +{ + pulse_color = color; + pulse_ontime = ontime; + pulse_period = period; + led_is_pulsing = 1; + pulse_leds_deferred(); +} + +static void update_leds(void) +{ + enum charge_state chg_st = charge_get_state(); + int batt_percentage = charge_get_percent(); + + /* + * Reflecting the charge state is the highest priority. + * + * The colors listed below are the default, but can be overridden. + * + * Solid Amber == Charging + * Solid Green == Charging (near full) + * Fast Flash Red == Charging error or battery not present + * Slow Flash Amber == Low Battery + * Fast Flash Amber == Critical Battery + */ + if (chg_st == PWR_STATE_CHARGE) { + led_is_pulsing = 0; + set_led_color(CONFIG_LED_PWM_CHARGE_COLOR); + } else if (chg_st == PWR_STATE_CHARGE_NEAR_FULL) { + led_is_pulsing = 0; + set_led_color(CONFIG_LED_PWM_NEAR_FULL_COLOR); + } else if ((battery_is_present() != BP_YES) || + (chg_st == PWR_STATE_ERROR)) { + /* 500 ms period, 50% duty cycle. */ + pulse_leds(CONFIG_LED_PWM_CHARGE_ERROR_COLOR, 1, 2); + } else if (batt_percentage < CRITICAL_LOW_BATTERY_PERCENTAGE) { + /* Flash amber faster (1 second period, 50% duty cycle) */ + pulse_leds(CONFIG_LED_PWM_LOW_BATT_COLOR, 2, 4); + } else if (batt_percentage < LOW_BATTERY_PERCENTAGE) { + /* Flash amber (4 second period, 50% duty cycle) */ + pulse_leds(CONFIG_LED_PWM_LOW_BATT_COLOR, 8, 16); + } else { + /* Discharging or not charging. Reflect the SoC state. */ + led_is_pulsing = 0; + if (chipset_in_state(CHIPSET_STATE_ON)) { + /* The LED must be on in the Active state. */ + set_led_color(CONFIG_LED_PWM_SOC_ON_COLOR); + } else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) { + /* The power LED must pulse in the suspend state. */ + pulse_leds(CONFIG_LED_PWM_SOC_SUSPEND_COLOR, 4, 16); + } else if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { + /* The LED must be off in the Deep Sleep state. */ + set_led_color(-1); + } + } +} +DECLARE_HOOK(HOOK_TICK, update_leds, HOOK_PRIO_DEFAULT); + +static void init_leds_off(void) +{ + /* Turn off LEDs such that they are in a known state. */ + set_led_color(-1); +} +DECLARE_HOOK(HOOK_INIT, init_leds_off, HOOK_PRIO_INIT_PWM + 1); + +#ifdef CONFIG_CMD_LEDTEST +int command_ledtest(int argc, char **argv) +{ + int enable; + int pwm_led_id; + int led_id; + + if (argc < 3) + return EC_ERROR_PARAM_COUNT; + + if (!parse_bool(argv[2], &enable)) + return EC_ERROR_PARAM2; + + pwm_led_id = atoi(argv[1]); + if ((pwm_led_id < 0) || (pwm_led_id >= CONFIG_LED_PWM_COUNT)) + return EC_ERROR_PARAM1; + + led_id = supported_led_ids[pwm_led_id]; + + /* Inverted because this drives auto control. */ + led_auto_control(led_id, !enable); + + if (argc == 4) { + /* Set the color. */ + if (!strncmp(argv[3], "red", 3)) + set_pwm_led_color(pwm_led_id, EC_LED_COLOR_RED); + else if (!strncmp(argv[3], "green", 5)) + set_pwm_led_color(pwm_led_id, EC_LED_COLOR_GREEN); + else if (!strncmp(argv[3], "amber", 5)) + set_pwm_led_color(pwm_led_id, EC_LED_COLOR_AMBER); + else if (!strncmp(argv[3], "blue", 4)) + set_pwm_led_color(pwm_led_id, EC_LED_COLOR_BLUE); + else if (!strncmp(argv[3], "white", 5)) + set_pwm_led_color(pwm_led_id, EC_LED_COLOR_WHITE); + else if (!strncmp(argv[3], "yellow", 6)) + set_pwm_led_color(pwm_led_id, EC_LED_COLOR_YELLOW); + else if (!strncmp(argv[3], "off", 3)) + set_pwm_led_color(pwm_led_id, -1); + else + return EC_ERROR_PARAM3; + } + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(ledtest, command_ledtest, + " [color|off]", ""); +#endif /* defined(CONFIG_CMD_LEDTEST) */ diff --git a/include/config.h b/include/config.h index b47b1e9892..243b51ca33 100644 --- a/include/config.h +++ b/include/config.h @@ -780,6 +780,7 @@ #define CONFIG_CMD_INA #undef CONFIG_CMD_JUMPTAGS #define CONFIG_CMD_KEYBOARD +#undef CONFIG_CMD_LEDTEST #undef CONFIG_CMD_LID_ANGLE #undef CONFIG_CMD_MCDP #define CONFIG_CMD_MD @@ -1867,6 +1868,29 @@ */ #undef CONFIG_LED_POLICY_STD +/* + * Support common PWM-controlled LEDs that conform to the Chrome OS LED + * behaviour specification. + */ +#undef CONFIG_LED_PWM + +/* + * Here are some recommended color settings by default, but a board can change + * the colors to one of "enum ec_led_colors" as they see fit. + */ +#define CONFIG_LED_PWM_CHARGE_COLOR EC_LED_COLOR_AMBER +#define CONFIG_LED_PWM_NEAR_FULL_COLOR EC_LED_COLOR_GREEN +#define CONFIG_LED_PWM_CHARGE_ERROR_COLOR EC_LED_COLOR_RED +#define CONFIG_LED_PWM_SOC_ON_COLOR EC_LED_COLOR_GREEN +#define CONFIG_LED_PWM_SOC_SUSPEND_COLOR EC_LED_COLOR_GREEN +#define CONFIG_LED_PWM_LOW_BATT_COLOR EC_LED_COLOR_AMBER + +/* + * How many PWM LEDs does the system have that will be controlled by the common + * PWM LED policy? Currently, this may be at most 2. + */ +#undef CONFIG_LED_PWM_COUNT + /* * LEDs for LED_POLICY STD may be inverted. In this case they are active low * and the GPIO names will be GPIO_LED..._L. @@ -3267,6 +3291,11 @@ #define CONFIG_BUTTON_TRIGGERED_RECOVERY #endif /* defined(CONFIG_DEDICATED_RECOVERY_BUTTON) */ + +#ifdef CONFIG_LED_PWM_COUNT +#define CONFIG_LED_PWM +#endif /* defined(CONFIG_LED_PWM_COUNT) */ + /*****************************************************************************/ /* * Define derived configuration options for EC-EC communication diff --git a/include/led_pwm.h b/include/led_pwm.h new file mode 100644 index 0000000000..8814023d42 --- /dev/null +++ b/include/led_pwm.h @@ -0,0 +1,48 @@ +/* Copyright 2018 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. + */ + +#ifndef __CROS_EC_LED_PWM_H +#define __CROS_EC_LED_PWM_H + +#include "ec_commands.h" + +#define PWM_LED_NO_CHANNEL -1 + +struct pwm_led { + int ch0; + int ch1; + int ch2; +}; + +enum pwm_led_id { + PWM_LED0 = 0, +#if CONFIG_LED_PWM_COUNT >= 2 + PWM_LED1, +#endif /* CONFIG_LED_PWM_COUNT > 2 */ +}; + +/* + * A mapping of color to LED duty cycles per channel. + * + * This should be defined by the boards to declare what each color looks like. + * There should be an entry for every enum ec_led_colors value. For colors that + * are impossible for a given board, they should define a duty cycle of 0 for + * all applicable channels. (e.g. A bi-color LED which has a red and green + * channel should define all 0s for EC_LED_COLOR_BLUE and EC_LED_COLOR_WHITE.) + */ +extern struct pwm_led led_color_map[EC_LED_COLOR_COUNT]; + +/* + * A map of the PWM channels to logical PWM LEDs. + * + * A logical PWM LED would be considered as "per diffuser". There may be 1-3 + * channels per diffuser and they should form a single entry in pwm_leds. If a + * channel is not used, simply define that channel as PWM_LED_NO_CHANNEL. + */ +extern struct pwm_led pwm_leds[CONFIG_LED_PWM_COUNT]; + +void set_pwm_led_color(enum pwm_led_id id, int color); + +#endif /* defined(__CROS_EC_LED_PWM_H) */