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

#include "adc.h"
#include "common.h"
#include "console.h"
#include "dma.h"
#include "gpio.h"
#include "hooks.h"
#include "hwtimer.h"
#include "injector.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "usb_pd.h"
#include "usb_pd_config.h"
#include "util.h"

/* PD packet text tracing state : TRACE_MODE_OFF/RAW/ON */
int trace_mode;

/* The FSM is waiting for the following command (0 == None) */
uint8_t expected_cmd;

static const char * const ctrl_msg_name[] = {
	[0]                      = "RSVD-C0",
	[PD_CTRL_GOOD_CRC]       = "GOODCRC",
	[PD_CTRL_GOTO_MIN]       = "GOTOMIN",
	[PD_CTRL_ACCEPT]         = "ACCEPT",
	[PD_CTRL_REJECT]         = "REJECT",
	[PD_CTRL_PING]           = "PING",
	[PD_CTRL_PS_RDY]         = "PSRDY",
	[PD_CTRL_GET_SOURCE_CAP] = "GSRCCAP",
	[PD_CTRL_GET_SINK_CAP]   = "GSNKCAP",
	[PD_CTRL_DR_SWAP]        = "DRSWAP",
	[PD_CTRL_PR_SWAP]        = "PRSWAP",
	[PD_CTRL_VCONN_SWAP]     = "VCONNSW",
	[PD_CTRL_WAIT]           = "WAIT",
	[PD_CTRL_SOFT_RESET]     = "SFT-RST",
	[14]                     = "RSVD-C14",
	[15]                     = "RSVD-C15",
};

static const char * const data_msg_name[] = {
	[0]                      = "RSVD-D0",
	[PD_DATA_SOURCE_CAP]     = "SRCCAP",
	[PD_DATA_REQUEST]        = "REQUEST",
	[PD_DATA_BIST]           = "BIST",
	[PD_DATA_SINK_CAP]       = "SNKCAP",
	/* 5-14 Reserved */
	[PD_DATA_VENDOR_DEF]     = "VDM",
};

static const char * const svdm_cmd_name[] = {
	[CMD_DISCOVER_IDENT]     = "DISCID",
	[CMD_DISCOVER_SVID]	 = "DISCSVID",
	[CMD_DISCOVER_MODES]	 = "DISCMODE",
	[CMD_ENTER_MODE]	 = "ENTER",
	[CMD_EXIT_MODE]		 = "EXIT",
	[CMD_ATTENTION]		 = "ATTN",
	[CMD_DP_STATUS]          = "DPSTAT",
	[CMD_DP_CONFIG]          = "DPCFG",
};

static const char * const svdm_cmdt_name[] = {
	[CMDT_INIT]     = "INI",
	[CMDT_RSP_ACK]  = "ACK",
	[CMDT_RSP_NAK]  = "NAK",
	[CMDT_RSP_BUSY] = "BSY",
};

static void print_pdo(uint32_t word)
{
	if ((word & PDO_TYPE_MASK) == PDO_TYPE_BATTERY)
		ccprintf(" %dmV/%dmW", ((word>>10)&0x3ff)*50,
			 (word&0x3ff)*250);
	else
		ccprintf(" %dmV/%dmA", ((word>>10)&0x3ff)*50,
			 (word&0x3ff)*10);
}

static void print_rdo(uint32_t word)
{
	ccprintf("{%d} %08x", RDO_POS(word), word);
}

static void print_vdo(int idx, uint32_t word)
{
	if (idx == 0 && (word & VDO_SVDM_TYPE)) {
		const char *cmd = svdm_cmd_name[PD_VDO_CMD(word)];
		const char *cmdt = svdm_cmdt_name[PD_VDO_CMDT(word)];
		uint16_t vid = PD_VDO_VID(word);
		if (!cmd)
			cmd = "????";
		ccprintf(" V%04x:%s,%s:%08x", vid, cmd, cmdt, word);
	} else {
		ccprintf(" %08x", word);
	}
}

static void print_packet(int head, uint32_t *payload)
{
	int i;
	int cnt = PD_HEADER_CNT(head);
	int typ = PD_HEADER_TYPE(head);
	int id = PD_HEADER_ID(head);
	const char *name;
	const char *prole;

	if (trace_mode == TRACE_MODE_RAW) {
		ccprintf("%pT[%04x]", PRINTF_TIMESTAMP_NOW, head);
		for (i = 0; i < cnt; i++)
			ccprintf(" %08x", payload[i]);
		ccputs("\n");
		return;
	}
	name = cnt ? data_msg_name[typ] : ctrl_msg_name[typ];
	prole = head & (PD_ROLE_SOURCE << 8) ? "SRC" : "SNK";
	ccprintf("%pT %s/%d [%04x]%s",
		 PRINTF_TIMESTAMP_NOW, prole, id, head, name);
	if (!cnt) { /* Control message : we are done */
		ccputs("\n");
		return;
	}
	/* Print payload for data message */
	for (i = 0; i < cnt; i++)
		switch (typ) {
		case PD_DATA_SOURCE_CAP:
		case PD_DATA_SINK_CAP:
			print_pdo(payload[i]);
			break;
		case PD_DATA_REQUEST:
			print_rdo(payload[i]);
			break;
		case PD_DATA_BIST:
			ccprintf("mode %d cnt %04x", payload[i] >> 28,
				 payload[i] & 0xffff);
			break;
		case PD_DATA_VENDOR_DEF:
			print_vdo(i, payload[i]);
			break;
		default:
			ccprintf(" %08x", payload[i]);
	}
	ccputs("\n");
}

static void print_error(enum pd_rx_errors err)
{
	if (err == PD_RX_ERR_INVAL)
		ccprintf("%pT TMOUT\n", PRINTF_TIMESTAMP_NOW);
	else if (err == PD_RX_ERR_HARD_RESET)
		ccprintf("%pT HARD-RST\n", PRINTF_TIMESTAMP_NOW);
	else if (err == PD_RX_ERR_UNSUPPORTED_SOP)
		ccprintf("%pT SOP*\n", PRINTF_TIMESTAMP_NOW);
	else
		ccprintf("ERR %d\n", err);
}

/* keep track of RX edge timing in order to trigger receive */
static timestamp_t rx_edge_ts[2][PD_RX_TRANSITION_COUNT];
static int rx_edge_ts_idx[2];

void rx_event(void)
{
	int pending, i;
	int next_idx;
	pending = STM32_EXTI_PR;

	/* Iterate over the 2 CC lines */
	for (i = 0; i < 2; i++) {
		if (pending & (1 << (21 + i))) {
			rx_edge_ts[i][rx_edge_ts_idx[i]].val = get_time().val;
			next_idx = (rx_edge_ts_idx[i] ==
					PD_RX_TRANSITION_COUNT - 1) ?
						0 : rx_edge_ts_idx[i] + 1;

			/*
			 * If we have seen enough edges in a certain amount of
			 * time, then trigger RX start.
			 */
			if ((rx_edge_ts[i][rx_edge_ts_idx[i]].val -
			     rx_edge_ts[i][next_idx].val)
			     < PD_RX_TRANSITION_WINDOW) {
				/* acquire the message only on the active CC */
				STM32_COMP_CSR &= ~(i ? STM32_COMP_CMP1EN
						      : STM32_COMP_CMP2EN);
				/* start sampling */
				pd_rx_start(0);
				/*
				 * ignore the comparator IRQ until we are done
				 * with current message
				 */
				pd_rx_disable_monitoring(0);
				/* trigger the analysis in the task */
#ifdef HAS_TASK_SNIFFER
				task_set_event(TASK_ID_SNIFFER, 1 << i);
#endif
				/* start reception only one CC line */
				break;
			} else {
				/* do not trigger RX start, just clear int */
				STM32_EXTI_PR = EXTI_COMP_MASK(0);
			}
			rx_edge_ts_idx[i] = next_idx;
		}
	}
}
#ifdef HAS_TASK_SNIFFER
DECLARE_IRQ(STM32_IRQ_COMP, rx_event, 1);
#endif

void trace_packets(void)
{
	int head;
	uint32_t payload[7];

#ifdef HAS_TASK_SNIFFER
	/* Disable sniffer DMA configuration */
	dma_disable(STM32_DMAC_CH6);
	dma_disable(STM32_DMAC_CH7);
	task_disable_irq(STM32_IRQ_DMA_CHANNEL_4_7);
	/* remove TIM1 CH1/2/3 DMA remapping */
	STM32_SYSCFG_CFGR1 &= ~BIT(28);
#endif

	/* "classical" PD RX configuration */
	pd_hw_init_rx(0);
	pd_select_polarity(0, 0);
	/* detect messages on both CCx lines */
	STM32_COMP_CSR |= STM32_COMP_CMP2EN | STM32_COMP_CMP1EN;
	/* Enable the RX interrupts */
	pd_rx_enable_monitoring(0);

	while (1) {
		task_wait_event(-1);
		if (trace_mode == TRACE_MODE_OFF)
			break;
		/* incoming packet processing */
		head = pd_analyze_rx(0, payload);
		pd_rx_complete(0);
		/* re-enabled detection on both CCx lines */
		STM32_COMP_CSR |= STM32_COMP_CMP2EN | STM32_COMP_CMP1EN;
		pd_rx_enable_monitoring(0);
		/* print the last packet content */
		if (head > 0)
			print_packet(head, payload);
		else
			print_error(head);
		if (head > 0 && expected_cmd == PD_HEADER_TYPE(head))
			task_wake(TASK_ID_CONSOLE);
	}

	task_disable_irq(STM32_IRQ_COMP);
	/* Disable tracer DMA configuration */
	dma_disable(STM32_DMAC_CH2);
	/* Put back : sniffer RX hardware configuration */
#ifdef HAS_TASK_SNIFFER
	sniffer_init();
#endif
}

int expect_packet(int pol, uint8_t cmd, uint32_t timeout_us)
{
	uint32_t evt;

	expected_cmd = cmd;
	evt = task_wait_event(timeout_us);

	return !(evt == TASK_EVENT_TIMER);
}

void set_trace_mode(int mode)
{
	/* No change */
	if (mode == trace_mode)
		return;

	trace_mode = mode;
	/* kick the task to take into account the new value */
#ifdef HAS_TASK_SNIFFER
	task_wake(TASK_ID_SNIFFER);
#endif
}
