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

/* SCP UART module */

#include "csr.h"
#include "system.h"
#include "uart.h"
#include "uart_regs.h"
#include "util.h"

/*
 * UARTN == 0, SCP UART0
 * UARTN == 1, SCP UART1
 * UARTN == 2, AP UART1
 */
#define UARTN CONFIG_UART_CONSOLE
#define UART_IDLE_WAIT_US 500
#define UART_INTC_GROUP 12

static uint8_t init_done, tx_started;

void uart_init(void)
{
	const uint32_t baud_rate = CONFIG_UART_BAUD_RATE;
	/* TODO: use ULPOSC1 for S3 */
	const uint32_t uart_clock = 26000000;
	const uint32_t div = DIV_ROUND_NEAREST(uart_clock, baud_rate * 16);

#if UARTN == 0
	SCP_UART_CK_SEL |= UART0_CK_SEL_VAL(UART_CK_SEL_26M);
	SCP_SET_CLK_CG |= CG_UART0_MCLK | CG_UART0_BCLK | CG_UART0_RST;

	/* set AP GPIO164 and GPIO165 to alt func 3 */
	AP_GPIO_MODE20_CLR = 0x00770000;
	AP_GPIO_MODE20_SET = 0x00330000;
#elif UARTN == 1
	SCP_UART_CK_SEL |= UART1_CK_SEL_VAL(UART_CK_SEL_26M);
	SCP_SET_CLK_CG |= CG_UART1_MCLK | CG_UART1_BCLK | CG_UART1_RST;
#endif

	/* Clear FIFO */
	UART_FCR(UARTN) = UART_FCR_ENABLE_FIFO
		| UART_FCR_CLEAR_RCVR
		| UART_FCR_CLEAR_XMIT;
	/* Line control: parity none, 8 bit, 1 stop bit */
	UART_LCR(UARTN) = UART_LCR_WLEN8;
	/* For baud rate <= 115200 */
	UART_HIGHSPEED(UARTN) = 0;

	/* DLAB start */
	UART_LCR(UARTN) |= UART_LCR_DLAB;
	UART_DLL(UARTN) = div & 0xff;
	UART_DLH(UARTN) = (div >> 8) & 0xff;
	UART_LCR(UARTN) &= ~UART_LCR_DLAB;
	/* DLAB end */

	/* Enable received data interrupt */
	UART_IER(UARTN) |= UART_IER_RDI;

#if (UARTN < SCP_UART_COUNT)
	task_enable_irq(UART_TX_IRQ(UARTN));
	task_enable_irq(UART_RX_IRQ(UARTN));
#endif

	init_done = 1;
}

int uart_init_done(void)
{
	return init_done;
}

void uart_tx_flush(void)
{
	while (!(UART_LSR(UARTN) & UART_LSR_TEMT))
		;
}

int uart_tx_ready(void)
{
	return UART_LSR(UARTN) & UART_LSR_THRE;
}

int uart_rx_available(void)
{
	return UART_LSR(UARTN) & UART_LSR_DR;
}

void uart_write_char(char c)
{
	while (!uart_tx_ready())
		;

	UART_THR(UARTN) = c;
}

int uart_read_char(void)
{
	return UART_RBR(UARTN);
}

void uart_tx_start(void)
{
	tx_started = 1;
	if (UART_IER(UARTN) & UART_IER_THRI)
		return;
	disable_sleep(SLEEP_MASK_UART);
	UART_IER(UARTN) |= UART_IER_THRI;
}

void uart_tx_stop(void)
{
	/*
	 * Workaround for b/157541273.
	 * Don't unset the THRI flag unless we are in the UART ISR.
	 *
	 * Note:
	 * MICAUSE denotes current INTC group number.
	 */
	if (in_interrupt_context() &&
	    read_csr(CSR_VIC_MICAUSE) != UART_INTC_GROUP)
		return;

	tx_started = 0;
	UART_IER(UARTN) &= ~UART_IER_THRI;
	enable_sleep(SLEEP_MASK_UART);
}

static void uart_process(void)
{
	uart_process_input();
	uart_process_output();
}

#if (UARTN < SCP_UART_COUNT)
static void uart_irq_handler(void)
{
	extern volatile int ec_int;

	switch (ec_int) {
	case UART_TX_IRQ(UARTN):
		uart_process();
		task_clear_pending_irq(ec_int);
		break;
	case UART_RX_IRQ(UARTN):
		uart_process();
		SCP_CORE0_INTC_UART_RX_IRQ(UARTN) = BIT(0);
		task_clear_pending_irq(ec_int);
		break;
	}
}
DECLARE_IRQ(UART_INTC_GROUP, uart_irq_handler, 0);
#else

#ifndef HAS_TASK_APUART
#error "APUART task hasn't defined in ec.tasklist."
#endif

void uart_task(void)
{
	while (1) {
		if (uart_rx_available() || tx_started)
			uart_process();
		else
			task_wait_event(UART_IDLE_WAIT_US);
	}
}
#endif
