/* 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.
 */

/* Tigertail board configuration */

#include "adc.h"
#include "adc_chip.h"
#include "common.h"
#include "console.h"
#include "ec_version.h"
#include "gpio.h"
#include "hooks.h"
#include "i2c.h"
#include "ina2xx.h"
#include "queue_policies.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "update_fw.h"
#include "usart-stm32f0.h"
#include "usart_tx_dma.h"
#include "usart_rx_dma.h"
#include "usb_i2c.h"
#include "usb-stream.h"
#include "util.h"

#include "gpio_list.h"


#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)


/******************************************************************************
 * Forward UARTs as a USB serial interface.
 */

#define USB_STREAM_RX_SIZE	16
#define USB_STREAM_TX_SIZE	16

/******************************************************************************
 * Forward USART1 as a simple USB serial interface.
 */
static struct usart_config const usart1;
struct usb_stream_config const usart1_usb;

static struct queue const usart1_to_usb = QUEUE_DIRECT(64, uint8_t,
	usart1.producer, usart1_usb.consumer);
static struct queue const usb_to_usart1 = QUEUE_DIRECT(64, uint8_t,
	usart1_usb.producer, usart1.consumer);

static struct usart_config const usart1 =
	USART_CONFIG(usart1_hw,
		usart_rx_interrupt,
		usart_tx_interrupt,
		115200,
		0,
		usart1_to_usb,
		usb_to_usart1);

USB_STREAM_CONFIG(usart1_usb,
	USB_IFACE_USART1_STREAM,
	USB_STR_USART1_STREAM_NAME,
	USB_EP_USART1_STREAM,
	USB_STREAM_RX_SIZE,
	USB_STREAM_TX_SIZE,
	usb_to_usart1,
	usart1_to_usb)


/******************************************************************************
 * Define the strings used in our USB descriptors.
 */
const void *const usb_strings[] = {
	[USB_STR_DESC]         = usb_string_desc,
	[USB_STR_VENDOR]       = USB_STRING_DESC("Google Inc."),
	[USB_STR_PRODUCT]      = USB_STRING_DESC("Tigertail"),
	[USB_STR_SERIALNO]     = 0,
	[USB_STR_VERSION]      = USB_STRING_DESC(CROS_EC_VERSION32),
	[USB_STR_I2C_NAME]     = USB_STRING_DESC("I2C"),
	[USB_STR_USART1_STREAM_NAME]  = USB_STRING_DESC("DUT UART"),
	[USB_STR_CONSOLE_NAME] = USB_STRING_DESC("Tigertail Console"),
	[USB_STR_UPDATE_NAME]  = USB_STRING_DESC("Firmware update"),
};

BUILD_ASSERT(ARRAY_SIZE(usb_strings) == USB_STR_COUNT);

/******************************************************************************
 * ADC support for SBU flip detect.
 */
/* ADC channels */
const struct adc_t adc_channels[] = {
	[ADC_SBU1] = {"SBU1", 3300, 4096, 0, STM32_AIN(6)},
	[ADC_SBU2] = {"SBU2", 3300, 4096, 0, STM32_AIN(7)},
};
BUILD_ASSERT(ARRAY_SIZE(adc_channels) == ADC_CH_COUNT);



/******************************************************************************
 * Support I2C bridging over USB.
 */

/* I2C ports */
const struct i2c_port_t i2c_ports[] = {
	{"master", I2C_PORT_MASTER, 100,
		GPIO_MASTER_I2C_SCL, GPIO_MASTER_I2C_SDA},
};
const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports);

int usb_i2c_board_is_enabled(void) { return 1; }

/******************************************************************************
 * Console commands.
 */

/* State to indicate current GPIO config. */
static int uart_state = UART_OFF;
/* State to indicate current autodetect mode. */
static int uart_detect = UART_DETECT_AUTO;

const char *const uart_state_names[] = {
	[UART_OFF] = "off",
	[UART_ON_PP1800] = "on @ 1.8v",
	[UART_FLIP_PP1800] = "flip @ 1.8v",
	[UART_ON_PP3300] = "on @ 3.3v",
	[UART_FLIP_PP3300] = "flip @ 3.3v",
	[UART_AUTO] = "auto",
};

/* Set GPIOs to configure UART mode. */
static void set_uart_gpios(int state)
{
	int uart = GPIO_INPUT;
	int dir = 0;
	int voltage = 1;  /* 1: 1.8v, 0: 3.3v */
	int enabled = 0;

	gpio_set_level(GPIO_ST_UART_LVL_DIS, 1);

	switch (state) {
	case UART_ON_PP1800:
		uart = GPIO_ALTERNATE;
		dir = 1;
		voltage = 1;
		enabled = 1;
		break;

	case UART_FLIP_PP1800:
		uart = GPIO_ALTERNATE;
		dir = 0;
		voltage = 1;
		enabled = 1;
		break;

	case UART_ON_PP3300:
		uart = GPIO_ALTERNATE;
		dir = 1;
		voltage = 0;
		enabled = 1;
		break;

	case UART_FLIP_PP3300:
		uart = GPIO_ALTERNATE;
		dir = 0;
		voltage = 0;
		enabled = 1;
		break;

	default:
		/* Default to UART_OFF. */
		uart = GPIO_INPUT;
		dir = 0;
		enabled = 0;
	}

	/* Set level shifter direction and voltage. */
	gpio_set_level(GPIO_ST_UART_VREF, voltage);
	gpio_set_level(GPIO_ST_UART_TX_DIR, dir);
	gpio_set_level(GPIO_ST_UART_TX_DIR_N, !dir);

	/* Enable STM pinmux */
	gpio_set_flags(GPIO_USART1_TX, uart);
	gpio_set_flags(GPIO_USART1_RX, uart);

	/* Flip uart orientation if necessary. */
	STM32_USART_CR1(STM32_USART1_BASE) &= ~(STM32_USART_CR1_UE);
	if (dir)
		STM32_USART_CR2(STM32_USART1_BASE) &= ~(STM32_USART_CR2_SWAP);
	else
		STM32_USART_CR2(STM32_USART1_BASE) |= (STM32_USART_CR2_SWAP);
	STM32_USART_CR1(STM32_USART1_BASE) |= STM32_USART_CR1_UE;

	/* Enable level shifter. */
	usleep(1000);
	gpio_set_level(GPIO_ST_UART_LVL_DIS, !enabled);
}

/*
 * Detect if a UART is plugged into SBU. Tigertail UART must be off
 * for this to return useful info.
 */
static int is_low(int mv)
{
	return (mv < 190);
}

static int is_3300(int mv)
{
	return ((mv > 3000) && (mv < 3400));
}

static int is_1800(int mv)
{
	return ((mv > 1600) && (mv < 1900));
}

static int detect_uart_orientation(void)
{
	int sbu1 = adc_read_channel(ADC_SBU1);
	int sbu2 = adc_read_channel(ADC_SBU2);
	int state = UART_OFF;

	/*
	 * Here we check if one or the other SBU is 1.8v, as DUT
	 * TX should idle high.
	 */
	if (is_low(sbu1) && is_1800(sbu2))
		state = UART_ON_PP1800;
	else if (is_low(sbu2) && is_1800(sbu1))
		state = UART_FLIP_PP1800;
	else if (is_low(sbu1) && is_3300(sbu2))
		state = UART_ON_PP3300;
	else if (is_low(sbu2) && is_3300(sbu1))
		state = UART_FLIP_PP3300;
	else
		state = UART_OFF;

	return state;
}

/*
 * Detect if UART has been unplugged. Normal UARTs should
 * have both lines idling high at 1.8v.
 */
static int detect_uart_idle(void)
{
	int sbu1 = adc_read_channel(ADC_SBU1);
	int sbu2 = adc_read_channel(ADC_SBU2);
	int enabled = 0;

	if (is_1800(sbu1) && is_1800(sbu2))
		enabled = 1;

	if (is_3300(sbu1) && is_3300(sbu2))
		enabled = 1;

	return enabled;
}

/* Set the UART state and gpios, and autodetect if necessary. */
void set_uart_state(int state)
{
	if (state == UART_AUTO) {
		set_uart_gpios(UART_OFF);
		msleep(10);

		uart_detect = UART_DETECT_AUTO;
		state = detect_uart_orientation();
	} else {
		uart_detect = UART_DETECT_OFF;
	}

	uart_state = state;
	set_uart_gpios(state);
}

/*
 * Autodetect UART state:
 * We will check every 250ms, and change state if 1 second has passed
 * in the new state.
 */
void uart_sbu_tick(void)
{
	static int debounce;  /* = 0 */

	if (uart_detect != UART_DETECT_AUTO)
		return;

	if (uart_state == UART_OFF) {
		int state = detect_uart_orientation();

		if (state != UART_OFF) {
			debounce++;
			if (debounce > 4) {
				debounce = 0;
				CPRINTS("UART autoenable %s",
					uart_state_names[state]);
				uart_state = state;
				set_uart_gpios(state);
			}
			return;
		}
	} else {
		int enabled = detect_uart_idle();

		if (!enabled) {
			debounce++;
			if (debounce > 4) {
				debounce = 0;
				CPRINTS("UART autodisable");
				uart_state = UART_OFF;
				set_uart_gpios(UART_OFF);
			}
			return;
		}
	}
	debounce = 0;
}
DECLARE_HOOK(HOOK_TICK, uart_sbu_tick, HOOK_PRIO_DEFAULT);

static int command_uart(int argc, char **argv)
{
	const char *uart_state_str = "off";
	const char *uart_detect_str = "manual";

	if (argc > 1) {
		if (!strcasecmp("off", argv[1]))
			set_uart_state(UART_OFF);
		else if (!strcasecmp("on18", argv[1]))
			set_uart_state(UART_ON_PP1800);
		else if (!strcasecmp("on33", argv[1]))
			set_uart_state(UART_ON_PP3300);
		else if (!strcasecmp("flip18", argv[1]))
			set_uart_state(UART_FLIP_PP1800);
		else if (!strcasecmp("flip33", argv[1]))
			set_uart_state(UART_FLIP_PP3300);
		else if (!strcasecmp("auto", argv[1]))
			set_uart_state(UART_AUTO);
		else
			return EC_ERROR_PARAM1;
	}

	uart_state_str = uart_state_names[uart_state];
	if (uart_detect == UART_DETECT_AUTO)
		uart_detect_str = "auto";
	ccprintf("UART mux is: %s, setting: %s\n",
		uart_state_str, uart_detect_str);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(uart, command_uart,
	"[off|on18|on33|flip18|flip33|auto]",
	"Set the sbu uart state\n"
	"WARNING: 3.3v may damage 1.8v devices.\n");

static void set_led_a(int r, int g, int b)
{
	/* LEDs are active low */
	gpio_set_level(GPIO_LED_R_L, !r);
	gpio_set_level(GPIO_LED_G_L, !g);
	gpio_set_level(GPIO_LED_B_L, !b);
}

static void set_led_b(int r, int g, int b)
{
	gpio_set_level(GPIO_LED2_R_L, !r);
	gpio_set_level(GPIO_LED2_G_L, !g);
	gpio_set_level(GPIO_LED2_B_L, !b);
}

/* State we intend the mux GPIOs to be set. */
static int mux_state = MUX_OFF;
static int last_mux_state = MUX_OFF;

/* Set the state variable and GPIO configs to mux as requested. */
void set_mux_state(int state)
{
	int enabled = (state == MUX_A) || (state == MUX_B);
	/* dir: 0 -> A, dir: 1 -> B */
	int dir = (state == MUX_B);

	if (mux_state != state)
		last_mux_state = mux_state;

	/* Disconnect first. */
	gpio_set_level(GPIO_USB_C_OE_N, 1);
	gpio_set_level(GPIO_SEL_RELAY_A, 0);
	gpio_set_level(GPIO_SEL_RELAY_B, 0);

	/* Let USB disconnect. */
	msleep(100);

	/* Reconnect VBUS/CC in the requested direction. */
	gpio_set_level(GPIO_SEL_RELAY_A, !dir && enabled);
	gpio_set_level(GPIO_SEL_RELAY_B, dir && enabled);

	/* Reconnect data. */
	msleep(10);

	gpio_set_level(GPIO_USB_C_SEL_B, dir);
	gpio_set_level(GPIO_USB_C_OE_N, !enabled);

	if (!enabled)
		mux_state = MUX_OFF;
	else
		mux_state = state;

	if (state == MUX_A)
		set_led_a(0, 1, 0);
	else
		set_led_a(1, 0, 0);

	if (state == MUX_B)
		set_led_b(0, 1, 0);
	else
		set_led_b(1, 0, 0);
}


/* On button press, toggle between mux A, B, off. */
static int button_ready = 1;
void button_interrupt_deferred(void)
{
	switch (mux_state) {
	case MUX_OFF:
		if (last_mux_state == MUX_A)
			set_mux_state(MUX_B);
		else
			set_mux_state(MUX_A);
		break;

	case MUX_A:
	case MUX_B:
	default:
		set_mux_state(MUX_OFF);
		break;
	}

	button_ready = 1;
}
DECLARE_DEFERRED(button_interrupt_deferred);

/* On button press, toggle between mux A, B, off. */
void button_interrupt(enum gpio_signal signal)
{
	if (!button_ready)
		return;

	button_ready = 0;
	/*
	 * button_ready is not set until set_mux_state completes,
	 * which has ~100ms settle time for the mux, which also
	 * provides for debouncing.
	 */
	hook_call_deferred(&button_interrupt_deferred_data, 0);
}

static int command_mux(int argc, char **argv)
{
	char *mux_state_str = "off";

	if (argc > 1) {
		if (!strcasecmp("off", argv[1]))
			set_mux_state(MUX_OFF);
		else if (!strcasecmp("a", argv[1]))
			set_mux_state(MUX_A);
		else if (!strcasecmp("b", argv[1]))
			set_mux_state(MUX_B);
		else
			return EC_ERROR_PARAM1;
	}

	if (mux_state == MUX_A)
		mux_state_str = "A";
	if (mux_state == MUX_B)
		mux_state_str = "B";
	ccprintf("TYPE-C mux is %s\n", mux_state_str);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(mux, command_mux,
	"[off|A|B]",
	"Get/set the mux and enable state of the TYPE-C mux");

/******************************************************************************
 * Initialize board.
 */
static void board_init(void)
{
	/* USB to serial queues */
	queue_init(&usart1_to_usb);
	queue_init(&usb_to_usart1);

	/* UART init */
	usart_init(&usart1);

	/*
	 * Default to port A, to allow easier charging and
	 * detection of unconfigured devices.
	 */
	set_mux_state(MUX_A);

	/* Note that we can't enable AUTO until after init. */
	set_uart_gpios(UART_OFF);

	/* Calibrate INA0 (VBUS) with 1mA/LSB scale */
	ina2xx_init(0, 0x8000, INA2XX_CALIB_1MA(15 /*mOhm*/));
	ina2xx_init(1, 0x8000, INA2XX_CALIB_1MA(15 /*mOhm*/));
	ina2xx_init(4, 0x8000, INA2XX_CALIB_1MA(15 /*mOhm*/));

	gpio_enable_interrupt(GPIO_BUTTON_L);
}
DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT);
