From 86a4ca10a8c74b175417ae3a38aff4e4097ed97c Mon Sep 17 00:00:00 2001 From: "reno.wang" Date: Sat, 6 Mar 2021 04:19:44 +0800 Subject: Lindar: Lindar's lightbar support in S0ix/S3 1. Lid is closed, lightbar keep off 2. Lid is opened, its behavior follow below. AC+Battery < 20%, lightbar solid 2 amber led on. AC+Battery < 40%, lightbar solid 4 amber led on. AC+Battery < 60%, lightbar solid 6 amber led on. AC+Battery < 80%, lightbar solid 8 amber led on. AC+Battery < 97%, lightbar solid 10 amber led on. AC+Battery >= 97%, lightbar solid 10 green led on. Battery only >= 15%, lightbar keep off. Battery low < 15%, lightbar blink amber color, 1s on, 5s off. 3. Some SKU un-support lightbar, and shouldn't run lightbar task. 4. Lightbar is powered by PP3300_A, and shouldn't run it in S4/S5/G3. 5. Add debounce time for lightbar state change. BUG=b:174133147 BRANCH=volteer TEST=make buildall, test lightbar behavior in S0ix/S3. Signed-off-by: reno.wang Change-Id: Ibbdc17627f7b2d1b2abbbad23b6c06024455e66b Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2739008 Reviewed-by: Keith Short --- board/lindar/board.c | 12 ++ board/lindar/board.h | 1 + board/lindar/ktd20xx.h | 141 +++++++++++++++++++++ board/lindar/led.c | 328 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 board/lindar/ktd20xx.h diff --git a/board/lindar/board.c b/board/lindar/board.c index c65421b08b..57d9fdeb14 100644 --- a/board/lindar/board.c +++ b/board/lindar/board.c @@ -91,6 +91,18 @@ static void board_init(void) } DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT); +int board_is_i2c_port_powered(int port) +{ + if (port != I2C_PORT_LIGHTBAR) + return 1; + + /* + * Lightbar rails are off in S5/G3 + * Refer CL-2739008. + */ + return chipset_in_state(CHIPSET_STATE_ANY_OFF) ? 0 : 1; +} + int board_is_lid_angle_tablet_mode(void) { return ec_cfg_has_tabletmode(); diff --git a/board/lindar/board.h b/board/lindar/board.h index 17c6fea8ff..2b78c25249 100644 --- a/board/lindar/board.h +++ b/board/lindar/board.h @@ -118,6 +118,7 @@ /* I2C Bus Configuration */ #define CONFIG_I2C +#define CONFIG_I2C_BUS_MAY_BE_UNPOWERED #define I2C_PORT_SENSOR NPCX_I2C_PORT0_0 #define I2C_PORT_USB_C0 NPCX_I2C_PORT1_0 #define I2C_PORT_USB_C1 NPCX_I2C_PORT2_0 diff --git a/board/lindar/ktd20xx.h b/board/lindar/ktd20xx.h new file mode 100644 index 0000000000..ad93ee3de8 --- /dev/null +++ b/board/lindar/ktd20xx.h @@ -0,0 +1,141 @@ +/* Copyright 2021 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. + * + * Public header for Kinetic 36-Channel RGB LED Drivers with I2C control, + * including KTD2061/58/59/60. + */ + +#ifndef __CROS_EC_DRIVER_RGB_LED_DRIVER_KTD20XX_PUBLIC_H +#define __CROS_EC_DRIVER_RGB_LED_DRIVER_KTD20XX_PUBLIC_H + +/* + * KTD20xx Register Definition + * + * Reg0x00: ID Data Register + * skip... + * Reg0x01: MONITOR Status Register + * skip... + * Reg0x02: CONTROL Configuration Register + * BIT7:6 is EN_MODE[1:0] + * 00 = global off, 01 = Night mode, + * 10 = Normal mode, 11 = reset as default + * BIT5 is BrightExtendTM Enable + * 0 = disable/1 = enable + * BIT4:3 is CoolExtendTM Temperature Setting + * 00 = 135°C rising, 01 = 120°C + * 10 = 105°C, 11 = 90°C + * BIT2:0 is Fade Rate Exponential Time-Constant Setting + * 000 = 31ms, 001 = 63ms, 010 = 125ms, 011 = 250ms + * 100 = 500ms, 101 = 1s, 110 = 2s, 111 = 4s + * + * Reg0x03: IRED0 Color Configuration Register + * IRED_SET0[7:0] Red Current Setting 0 + * 0000 0000 = 0μA + * 0000 0001 = 125μA + * ... + * 0010 1000 = 5mA + * ... + * 1100 0000 = 24mA + * 1100 0001 = 24mA (reads back as 1100 0000) + * ... + * 1111 1111 = 24mA (reads back as 1100 0000) + * Reg0x04: IGRN0 Color Configuration Register + * IGRN_SET0[7:0] Green Current Setting 0 + * Reg0x05: IBLU0 Color Configuration Register + * IBLU_SET0[7:0] Blue Current Setting 0 + * Reg0x06: IRED1 Color Configuration Register + * IRED_SET1[7:0] Red Current Setting 1 + * Reg0x07: IGRN1 Color Configuration Register + * IGRN_SET1[7:0] Green Current Setting 1 + * Reg0x08: IBLU1 Color Configuration Register + * IBLU_SET1[7:0] Blue Current Setting 1 + * + * Reg0x09: ISELA12 Selection Configuration Register + * BIT7 is ENA1, Enable RGB with anode connected to LEDA1 pin + * 0 = use 0μA for these LEDs (includes fade to 0μA) + * 1 = use the settings selected by RGBA1_SEL[2:0] + * BIT6:4 is RGBA1_SEL[2:0] + * Current Selection for RGB with anode connected to LEDA1 pin + * 0XX = I LEDA3 selects IRED_SET0[7:0] + * 1XX = I LEDA3 selects IRED_SET1[7:0] + * X0X = I LEDA2 selects IGRN_SET0[7:0] + * X1X = I LEDA2 selects IGRN_SET1[7:0] + * XX0 = I LEDA4 selects IBLU_SET0[7:0] + * XX1 = I LEDA4 selects IBLU_SET1[7:0] + * BIT3 IS ENA2 + * 0 = use 0μA for these LEDs (includes fade to 0μA) + * 1 = use the settings selected by RGBA2_SEL[2:0] + * BIT2:0 is RGBA2_SEL[2:0] + * Current Selection for RGB with anode connected to LEDA2 pin + * 0XX = I LEDA4 selects IRED_SET0[7:0] + * 1XX = I LEDA4 selects IRED_SET1[7:0] + * X0X = I LEDA3 selects IGRN_SET0[7:0] + * X1X = I LEDA3 selects IGRN_SET1[7:0] + * XX0 = I LEDA1 selects IBLU_SET0[7:0] + * XX1 = I LEDA1 selects IBLU_SET1[7:0] + * Reg0x0A: ISELA34 Selection Configuration Register + * BIT7 is ENA3, Enable RGB with anode connected to LEDA3 pin + * 0 = use 0μA for these LEDs (includes fade to 0μA) + * 1 = use the settings selected by RGBA3_SEL[2:0] + * BIT6:4 is RGBA3_SEL[2:0] + * Current Selection for RGB with anode connected to LEDA3 pin + * 0XX = I LEDA1 selects IRED_SET0[7:0] + * 1XX = I LEDA1 selects IRED_SET1[7:0] + * X0X = I LEDA4 selects IGRN_SET0[7:0] + * X1X = I LEDA4 selects IGRN_SET1[7:0] + * XX0 = I LEDA2 selects IBLU_SET0[7:0] + * XX1 = I LEDA2 selects IBLU_SET1[7:0] + * BIT3 IS ENA4 + * 0 = use 0μA for these LEDs (includes fade to 0μA) + * 1 = use the settings selected by RGBA4_SEL[2:0] + * BIT2:0 is RGBA4_SEL[2:0] + * Current Selection for RGB with anode connected to LEDA4 pin + * 0XX = I LEDA2 selects IRED_SET0[7:0] + * 1XX = I LEDA2 selects IRED_SET1[7:0] + * X0X = I LEDA1 selects IGRN_SET0[7:0] + * X1X = I LEDA1 selects IGRN_SET1[7:0] + * XX0 = I LEDA3 selects IBLU_SET0[7:0] + * XX1 = I LEDA3 selects IBLU_SET1[7:0] + * Reg0x0B: ISELB12 Selection Configuration Register + * BIT7 is ENB1, Enable RGB with anode connected to LEDB1 pin + * 0 = use 0μA for these LEDs (includes fade to 0μA) + * 1 = use the settings selected by RGB1_SEL[2:0] + * BIT6:4 is RGBB1_SEL[2:0] + * Current Selection for RGB with anode connected to LEDB1 pin + * 0XX = I LEDB3 selects IRED_SET0[7:0] + * 1XX = I LEDB3 selects IRED_SET1[7:0] + * X0X = I LEDB2 selects IGRN_SET0[7:0] + * X1X = I LEDB2 selects IGRN_SET1[7:0] + * XX0 = I LEDB4 selects IBLU_SET0[7:0] + * XX1 = I LEDB4 selects IBLU_SET1[7:0] + * BIT3 IS ENB2 + * ... + * Reg0x0C: ISELB34 Selection Configuration Register + * ... + * Reg0x0D: ISELC12 Selection Configuration Register + * ... + * Reg0x0E: ISELC34 Selection Configuration Register + * ... + */ + +enum ktd20xx_register { + KTD20XX_ID_DATA = 0x00, + KTD20XX_STATUS_REG = 0x01, + KTD20XX_CTRL_CFG = 0x02, + KTD20XX_IRED_SET0 = 0x03, + KTD20XX_IGRN_SET0 = 0x04, + KTD20XX_IBLU_SET0 = 0x05, + KTD20XX_IRED_SET1 = 0x06, + KTD20XX_IGRN_SET1 = 0x07, + KTD20XX_IBLU_SET1 = 0x08, + KTD20XX_ISEL_A12 = 0x09, + KTD20XX_ISEL_A34 = 0x0A, + KTD20XX_ISEL_B12 = 0x0B, + KTD20XX_ISEL_B34 = 0x0C, + KTD20XX_ISEL_C12 = 0x0D, + KTD20XX_ISEL_C34 = 0x0E, + KTD20XX_TOTOAL_REG +}; + +#endif /* __CROS_EC_DRIVER_RGB_LED_DRIVER_KTD20XX_PUBLIC_H */ diff --git a/board/lindar/led.c b/board/lindar/led.c index 0b65213052..890f309842 100644 --- a/board/lindar/led.c +++ b/board/lindar/led.c @@ -5,10 +5,23 @@ * Power and battery LED control for Malefor */ +#include "charge_state.h" #include "common.h" -#include "led_onoff_states.h" -#include "led_common.h" +#include "cros_board_info.h" +#include "extpower.h" #include "gpio.h" +#include "hooks.h" +#include "i2c.h" +#include "ktd20xx.h" +#include "led_common.h" +#include "led_onoff_states.h" +#include "lid_switch.h" +#include "stdbool.h" +#include "task.h" +#include "timer.h" + +#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args) #define LED_OFF_LVL 1 #define LED_ON_LVL 0 @@ -109,3 +122,314 @@ int led_set_brightness(enum ec_led_id led_id, const uint8_t *brightness) return EC_SUCCESS; } + +static const uint16_t lightbar_i2c_addr = 0x68; +static void controller_write(uint8_t reg, uint8_t val) +{ + uint8_t buf[2]; + + buf[0] = reg; + buf[1] = val; + + i2c_xfer_unlocked(I2C_PORT_LIGHTBAR, lightbar_i2c_addr, + buf, 2, 0, 0, + I2C_XFER_SINGLE); +} + +enum lightbar_states { + LB_STATE_OFF, + LB_STATE_LID_CLOSE, + LB_STATE_AC_ONLY, + LB_STATE_AC_BAT_LOW, + LB_STATE_AC_BAT_20, + LB_STATE_AC_BAT_40, + LB_STATE_AC_BAT_60, + LB_STATE_AC_BAT_80, + LB_STATE_AC_BAT_100, + LB_STATE_BAT_LOW, + LB_STATE_BAT_ONLY, + LB_NUM_STATES +}; + +/* + * All lightbar states should have one phase defined, + * and an additional phase can be defined for blinking + */ +enum lightbar_phase { + LIGHTBAR_PHASE_0 = 0, + LIGHTBAR_PHASE_1 = 1, + LIGHTBAR_NUM_PHASES +}; + +enum ec_lightbar_colors { + BAR_RESET = 0x00, + BAR_OFF = 0x01, + BAR_COLOR_ORG_20_PERCENT = 0x02, + BAR_COLOR_ORG_40_PERCENT = 0x03, + BAR_COLOR_ORG_60_PERCENT = 0x04, + BAR_COLOR_ORG_80_PERCENT = 0x05, + BAR_COLOR_ORG_FULL = 0x06, + BAR_COLOR_GRN_FULL = 0x07, + LIGHTBAR_COLOR_TOTAL +}; + +struct lightbar_descriptor { + enum ec_lightbar_colors color; + uint8_t ticks; +}; + +#define BAR_INFINITE UINT8_MAX +#define LIGHTBAR_ONE_SEC (1000 / HOOK_TICK_INTERVAL_MS) +const struct lightbar_descriptor + lb_table[LB_NUM_STATES][LIGHTBAR_NUM_PHASES] = { + [LB_STATE_OFF] = {{BAR_OFF, BAR_INFINITE} }, + [LB_STATE_LID_CLOSE] = {{BAR_OFF, BAR_INFINITE} }, + [LB_STATE_AC_ONLY] = {{BAR_OFF, BAR_INFINITE} }, + [LB_STATE_AC_BAT_LOW] = {{BAR_COLOR_ORG_20_PERCENT, BAR_INFINITE} }, + [LB_STATE_AC_BAT_20] = {{BAR_COLOR_ORG_40_PERCENT, BAR_INFINITE} }, + [LB_STATE_AC_BAT_40] = {{BAR_COLOR_ORG_60_PERCENT, BAR_INFINITE} }, + [LB_STATE_AC_BAT_60] = {{BAR_COLOR_ORG_80_PERCENT, BAR_INFINITE} }, + [LB_STATE_AC_BAT_80] = {{BAR_COLOR_ORG_FULL, BAR_INFINITE} }, + [LB_STATE_AC_BAT_100] = {{BAR_COLOR_GRN_FULL, BAR_INFINITE} }, + [LB_STATE_BAT_LOW] = {{BAR_OFF, 5*LIGHTBAR_ONE_SEC}, + {BAR_COLOR_ORG_FULL, LIGHTBAR_ONE_SEC} }, + [LB_STATE_BAT_ONLY] = {{BAR_OFF, BAR_INFINITE} }, +}; + +/* + * From EE's information, lindar only support two colors lightbar, + * Orange (Amber) and Green. And they connect KTD20xx's red color + * channel to orange color led, and green color + * channel to green color led. + * Blue color channel is unused. + */ +#define DISABLE_LIGHTBAR 0x00 +#define ENABLE_LIGHTBAR 0x80 +#define I_OFF 0x00 +#define I_ON 0x02 +#define SEL_OFF 0x00 +#define SEL_1ST_LED BIT(7) +#define SEL_2ND_LED BIT(3) +#define SEL_BOTH (SEL_1ST_LED | SEL_2ND_LED) +#define SKU_ID_NONE 0x00 +#define SKU_ID_INVALID 0x01 +#define LB_SUPPORTED_SKUID_LOWER 458700 +#define LB_SUPPORTED_SKUID_UPPER 458800 + +static bool lightbar_is_supported(void) +{ + static uint32_t skuid = SKU_ID_NONE; + bool result; + + /* + * TODO(b/183826778): + * [Lillipup/Lindar] Move to SSFC/FW_CONFIG for lightbar supporting + * check + */ + if (skuid == SKU_ID_NONE) { + if (cbi_get_sku_id(&skuid)) { + CPRINTS("Cannot get skuid for lightbar supported"); + skuid = SKU_ID_INVALID; + } + } + if (skuid >= LB_SUPPORTED_SKUID_LOWER && + skuid <= LB_SUPPORTED_SKUID_UPPER) + result = true; + else + result = false; + return result; +} + +/* + * Todo. + * Maybe, we need to provide some command to tool kit to test lightbar + * in factory. So, it may need a way to stop lightbar_update(). + */ +static bool lightbar_is_enabled(void) +{ + if (!lightbar_is_supported()) + return false; + + /* + * Lightbar's I2C is powered by PP3300_A, and its power will be turn + * when system enter S4/S5. It may get I2C error if EC keep polling + * lightbar. We should stop it when EC doesn't turn on PP330_A. + */ + if (!board_is_i2c_port_powered(I2C_PORT_LIGHTBAR)) + return false; + + return true; +} + +const uint8_t lightbar_ctrl[LIGHTBAR_COLOR_TOTAL][KTD20XX_TOTOAL_REG] = { + [BAR_RESET] = { + 0x00, 0x00, DISABLE_LIGHTBAR, + I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF + }, + [BAR_OFF] = { + 0x00, 0x00, DISABLE_LIGHTBAR, + I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF + }, + [BAR_COLOR_ORG_20_PERCENT] = { + 0x00, 0x00, ENABLE_LIGHTBAR, + I_ON, I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_BOTH, SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF + }, + [BAR_COLOR_ORG_40_PERCENT] = { + 0x00, 0x00, ENABLE_LIGHTBAR, + I_ON, I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_BOTH, SEL_BOTH, SEL_OFF, SEL_OFF, SEL_OFF, SEL_OFF + }, + [BAR_COLOR_ORG_60_PERCENT] = { + 0x00, 0x00, ENABLE_LIGHTBAR, + I_ON, I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_OFF, SEL_OFF, SEL_OFF + }, + [BAR_COLOR_ORG_80_PERCENT] = { + 0x00, 0x00, ENABLE_LIGHTBAR, + I_ON, I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_OFF, SEL_OFF + }, + [BAR_COLOR_ORG_FULL] = { + 0x00, 0x00, ENABLE_LIGHTBAR, + I_ON, I_OFF, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_OFF + }, + [BAR_COLOR_GRN_FULL] = { + 0x00, 0x00, ENABLE_LIGHTBAR, + I_OFF, I_ON, I_OFF, I_OFF, I_OFF, I_OFF, + SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_BOTH, SEL_OFF + } +}; + +static void lightbar_set_color(enum ec_lightbar_colors color) +{ + enum ktd20xx_register i; + + if (color >= LIGHTBAR_COLOR_TOTAL) { + CPRINTS("Lightbar Error! Incorrect lightbard color %d", color); + color = BAR_RESET; + } + + i2c_lock(I2C_PORT_LIGHTBAR, 1); + for (i = KTD20XX_IRED_SET0; i <= KTD20XX_ISEL_C34; i++) + controller_write(i, lightbar_ctrl[color][i]); + + controller_write(KTD20XX_CTRL_CFG, + lightbar_ctrl[color][KTD20XX_CTRL_CFG]); + + i2c_lock(I2C_PORT_LIGHTBAR, 0); +} + +static void lightbar_init(void) +{ + if (!lightbar_is_enabled()) + return; + + lightbar_set_color(BAR_RESET); +} + +DECLARE_HOOK(HOOK_CHIPSET_STARTUP, lightbar_init, HOOK_PRIO_DEFAULT); +DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, lightbar_init, HOOK_PRIO_DEFAULT); + +const int lightbar_bat_low = 15; +static enum lightbar_states lightbar_get_state(void) +{ + enum lightbar_states new_state = LB_NUM_STATES; + int cur_bat_percent; + + cur_bat_percent = charge_get_percent(); + + if (!chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) + return LB_STATE_OFF; + + if (!lid_is_open()) + return LB_STATE_LID_CLOSE; + + if (extpower_is_present()) { + if (charge_get_display_charge()) { + if (cur_bat_percent < 20) + new_state = LB_STATE_AC_BAT_LOW; + else if (cur_bat_percent < 40) + new_state = LB_STATE_AC_BAT_20; + else if (cur_bat_percent < 60) + new_state = LB_STATE_AC_BAT_40; + else if (cur_bat_percent < 80) + new_state = LB_STATE_AC_BAT_60; + else if (cur_bat_percent < 97) + new_state = LB_STATE_AC_BAT_80; + else + new_state = LB_STATE_AC_BAT_100; + } else + new_state = LB_STATE_AC_ONLY; + } else { + if (cur_bat_percent < lightbar_bat_low) + new_state = LB_STATE_BAT_LOW; + else + new_state = LB_STATE_BAT_ONLY; + } + + return new_state; +} + +#define LIGHTBAR_DEBOUNCE_TICKS 1 +static void lightbar_update(void) +{ + static uint8_t ticks, period; + static enum lightbar_states lb_cur_state = LB_NUM_STATES; + static int debounce_lightbar_state_update; + enum lightbar_states desired_state; + int phase; + + if (!lightbar_is_enabled()) + return; + + desired_state = lightbar_get_state(); + if (desired_state != lb_cur_state && + desired_state < LB_NUM_STATES) { + /* State is changing */ + lb_cur_state = desired_state; + /* Reset ticks and period when state changes */ + ticks = 0; + + period = lb_table[lb_cur_state][LIGHTBAR_PHASE_0].ticks + + lb_table[lb_cur_state][LIGHTBAR_PHASE_1].ticks; + + /* + * System will be waken up when AC status change in S0ix. Due to + * EC may be late to update chipset state and cause lightbar + * flash a while when system transfer to S0. We add to debounce + * for any lightbar status change. + * It can make sure lightbar state is ready to to update. + */ + debounce_lightbar_state_update = LIGHTBAR_DEBOUNCE_TICKS; + } + + /* If this state is undefined, turn lightbar off */ + if (period == 0) { + CPRINTS("Undefined lightbar behavior for lightbar state %d," + "turning off lightbar", lb_cur_state); + lightbar_set_color(BAR_OFF); + return; + } + + if (debounce_lightbar_state_update != 0) { + debounce_lightbar_state_update--; + return; + } + + /* + * Determine which phase of the state table to use. The phase is + * determined if it falls within first phase time duration. + */ + phase = ticks < lb_table[lb_cur_state][LIGHTBAR_PHASE_0].ticks ? 0 : 1; + ticks = (ticks + 1) % period; + + /* Set the color for the given state and phase */ + lightbar_set_color(lb_table[lb_cur_state][phase].color); + +} + +DECLARE_HOOK(HOOK_TICK, lightbar_update, HOOK_PRIO_DEFAULT); -- cgit v1.2.1