/* Copyright 2017 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.
 *
 * Power/Battery LED control for Eve
 */

#include "charge_manager.h"
#include "charge_state.h"
#include "chipset.h"
#include "console.h"
#include "extpower.h"
#include "gpio.h"
#include "hooks.h"
#include "led_common.h"
#include "pwm.h"
#include "registers.h"
#include "util.h"

#define CPRINTF(format, args...) cprintf(CC_PWM, format, ## args)
#define CPRINTS(format, args...) cprints(CC_PWM, format, ## args)

#define LED_TOTAL_TICKS 16
#define LED_ON_TICKS 8

static int led_debug;

const enum ec_led_id supported_led_ids[] = {
	EC_LED_ID_POWER_LED, EC_LED_ID_BATTERY_LED};
const int supported_led_ids_count = ARRAY_SIZE(supported_led_ids);

enum led_color {
	LED_OFF = 0,
	LED_RED,
	LED_GREEN,
	LED_BLUE,
	LED_WHITE,
	LED_AMBER,

	/* Number of colors, not a color itself */
	LED_COLOR_COUNT
};

enum led_side {
	LED_LEFT = 0,
	LED_RIGHT,
	LED_BOTH
};

/* Brightness vs. color, in the order of off, red, green and blue */
#define PWM_CHAN_PER_LED 3
static const uint8_t color_brightness[LED_COLOR_COUNT][PWM_CHAN_PER_LED] = {
	/* {Red, Green, Blue}, */
	[LED_OFF]   = {100, 100, 100},
	[LED_RED]   = {20,  100, 100},
	[LED_GREEN] = {100, 20, 100},
	[LED_BLUE]  = {100, 100, 20},
	[LED_WHITE]  = {0, 0, 0},
	[LED_AMBER]  = {0, 87, 100},
};

/**
 * Set LED color
 *
 * @param color	Enumerated color value
 * @param side		Left LED, Right LED, or both LEDs
 */
static void set_color(enum led_color color, enum led_side side)
{
	int i;

	/* Set color for left LED */
	if (side == LED_LEFT || side == LED_BOTH)
		for (i = 0; i < PWM_CHAN_PER_LED; i++)
			pwm_set_duty(PWM_CH_LED_L_RED + i,
				     color_brightness[color][i]);

	/* Set color for right LED */
	if (side == LED_RIGHT || side == LED_BOTH)
		for (i = 0; i < PWM_CHAN_PER_LED; i++)
			pwm_set_duty(PWM_CH_LED_R_RED + i,
				     color_brightness[color][i]);
}

void led_get_brightness_range(enum ec_led_id led_id, uint8_t *brightness_range)
{
	brightness_range[EC_LED_COLOR_RED] = 100;
	brightness_range[EC_LED_COLOR_BLUE] = 100;
	brightness_range[EC_LED_COLOR_GREEN] = 100;
}

int led_set_brightness(enum ec_led_id led_id, const uint8_t *brightness)
{
	/* Set brightness for left LED */
	pwm_set_duty(PWM_CH_LED_L_RED, brightness[EC_LED_COLOR_RED]);
	pwm_set_duty(PWM_CH_LED_L_BLUE, brightness[EC_LED_COLOR_BLUE]);
	pwm_set_duty(PWM_CH_LED_L_GREEN, brightness[EC_LED_COLOR_GREEN]);

	/* Set brightness for right LED */
	pwm_set_duty(PWM_CH_LED_R_RED, brightness[EC_LED_COLOR_RED]);
	pwm_set_duty(PWM_CH_LED_R_BLUE, brightness[EC_LED_COLOR_BLUE]);
	pwm_set_duty(PWM_CH_LED_R_GREEN, brightness[EC_LED_COLOR_GREEN]);
	return EC_SUCCESS;
}

static void eve_led_set_power_battery(void)
{
	static int power_ticks;
	enum charge_state chg_state = charge_get_state();
	int side;

	/* Get active charge port which maps directly to left/right LED */
	side = charge_manager_get_active_charge_port();
	/* Ensure that side can be safely used as an index */
	if (side < 0 || side >= CONFIG_USB_PD_PORT_COUNT)
		side = LED_BOTH;

	if (chipset_in_state(CHIPSET_STATE_ON)) {
		set_color(LED_BLUE, LED_BOTH);
		if (chg_state == PWR_STATE_CHARGE)
			set_color(LED_AMBER, side);
		else if (chg_state == PWR_STATE_IDLE || chg_state ==
			 PWR_STATE_CHARGE_NEAR_FULL)
			set_color(LED_GREEN, side);
		return;
	}

	/* Flash red on critical battery, which usually inhibits AP power-on. */
	if (battery_is_present() != BP_YES ||
	    charge_get_percent() < CONFIG_CHARGER_MIN_BAT_PCT_FOR_POWER_ON) {
		set_color(((power_ticks++ % LED_TOTAL_TICKS) < LED_ON_TICKS) ?
			  LED_RED : LED_OFF, LED_BOTH);
		return;
	}

	/* Suspend or Standby state */
	if (chipset_in_state(CHIPSET_STATE_SUSPEND) ||
	    chipset_in_state(CHIPSET_STATE_STANDBY)) {
		enum led_side blinkside = LED_BOTH;

		if (chg_state == PWR_STATE_CHARGE) {
			set_color(LED_AMBER, side);
			blinkside = !side;
		}
		if (chg_state == PWR_STATE_DISCHARGE ||
		    chg_state == PWR_STATE_CHARGE) {
			/*
			 * If in S3/S0iX and not in some error
			 * state, then flash non-charging LEDs white.
			 */
			set_color(((power_ticks++ % LED_TOTAL_TICKS) <
				   LED_ON_TICKS) ?
				  LED_WHITE : LED_OFF, blinkside);
			return;
		}
	}

	/* CHIPSET_STATE_OFF */
	switch (chg_state) {
	case PWR_STATE_DISCHARGE:
		set_color(LED_OFF, LED_BOTH);
		break;
	case PWR_STATE_CHARGE:
		set_color(LED_OFF, LED_BOTH);
		set_color(LED_AMBER, side);
		break;
	case PWR_STATE_ERROR:
		set_color(((power_ticks++ % LED_TOTAL_TICKS)
			   < LED_ON_TICKS) ? LED_RED : LED_GREEN, LED_BOTH);
		break;
	case PWR_STATE_CHARGE_NEAR_FULL:
	case PWR_STATE_IDLE: /* External power connected in IDLE. */
		set_color(LED_OFF, LED_BOTH);
		set_color(LED_GREEN, side);
		break;
	default:
		set_color(LED_RED, LED_BOTH);
		break;
	}
	if (chg_state != PWR_STATE_ERROR)
		power_ticks = 0;
}

static void led_init(void)
{
	/*
	 * Enable PWMs and set to 0% duty cycle.  If they're disabled,
	 * seems to ground the pins instead of letting them float.
	 */
	/* Initialize PWM channels for left LED */
	pwm_enable(PWM_CH_LED_L_RED, 1);
	pwm_enable(PWM_CH_LED_L_GREEN, 1);
	pwm_enable(PWM_CH_LED_L_BLUE, 1);

	/* Initialize PWM channels for right LED */
	pwm_enable(PWM_CH_LED_R_RED, 1);
	pwm_enable(PWM_CH_LED_R_GREEN, 1);
	pwm_enable(PWM_CH_LED_R_BLUE, 1);

	set_color(LED_OFF, LED_BOTH);
}
/* 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)
{
	if (led_debug)
		return;

	if (led_auto_control_is_enabled(EC_LED_ID_POWER_LED) &&
	    led_auto_control_is_enabled(EC_LED_ID_BATTERY_LED)) {
		eve_led_set_power_battery();
		return;
	}
}
DECLARE_HOOK(HOOK_TICK, led_tick, HOOK_PRIO_DEFAULT);

/******************************************************************/
/* Console commands */
static int command_led(int argc, char **argv)
{
	int side = LED_BOTH;
	char *e;

	if (argc > 1) {
		if (argc > 2) {
			side = strtoi(argv[2], &e, 10);
			if (*e)
				return EC_ERROR_PARAM2;
			if (side > 1)
				return EC_ERROR_PARAM2;
		}

		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 if (!strcasecmp(argv[1], "amber")) {
			set_color(LED_AMBER, side);
		} else {
			/* maybe handle charger_discharge_on_ac() too? */
			return EC_ERROR_PARAM1;
		}
	}
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(led, command_led,
			"[debug|red|green|blue|white|amber|off <0|1>]",
			"Change LED color");
