summaryrefslogtreecommitdiff
path: root/board/lindar/led.c
diff options
context:
space:
mode:
Diffstat (limited to 'board/lindar/led.c')
-rw-r--r--board/lindar/led.c328
1 files changed, 326 insertions, 2 deletions
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);