/* Copyright (c) 2016 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.
 */

/* UART module for ISH */
#include "common.h"
#include "console.h"
#include "uart_defs.h"
#include "atomic.h"
#include "task.h"
#include "registers.h"
#include "uart.h"
#include "uart_defs.h"
#include "interrupts.h"

#define CPUTS(outstr) cputs(CC_LPC, outstr)
#define CPRINTS(format, args...) cprints(CC_LPC, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_LPC, format, ## args)

static const uint32_t baud_conf[][BAUD_TABLE_MAX] = {
	{B9600, 9600},
	{B57600, 57600},
	{B115200, 115200},
	{B921600, 921600},
	{B2000000, 2000000},
	{B3000000, 3000000},
	{B3250000, 3250000},
	{B3500000, 3500000},
	{B4000000, 4000000},
	{B19200, 19200},
};

static struct uart_ctx uart_ctx[UART_DEVICES] = {
	{
		.id = 0,
		.base = UART0_BASE,
		.input_freq = UART_ISH_INPUT_FREQ,
		.addr_interval = UART_ISH_ADDR_INTERVAL,
		.uart_state = UART_STATE_CG,
	},
	{
		.id = 1,
		.base = UART1_BASE,
		.input_freq = UART_ISH_INPUT_FREQ,
		.addr_interval = UART_ISH_ADDR_INTERVAL,
		.uart_state = UART_STATE_CG,
	}
};

static int init_done;

int uart_init_done(void)
{
	return init_done;
}

void uart_tx_start(void)
{
#if !defined(CONFIG_POLLING_UART)
	enum UART_PORT id = UART_PORT_1; /* UART1 for ISH */

	if ( REG8(IER(id) & IER_TDRQ) )
		return;

	/* TODO: disable low power mode while transmit */

	REG8(IER(id)) |= IER_TDRQ;

	task_trigger_irq(ISH_UART1_IRQ);
#endif
}

void uart_tx_stop(void)
{
#if !defined(CONFIG_POLLING_UART)
	enum UART_PORT id = UART_PORT_1; /* UART1 for ISH */

	REG8(IER(id)) &= ~IER_TDRQ;

	/* TODO: re-enable low power mode */
#endif
}

void uart_tx_flush(void)
{
#if !defined(CONFIG_POLLING_UART)
	enum UART_PORT id = UART_PORT_1; /* UART1 for ISH */

	while (!(REG8(LSR(id)) & LSR_TEMT) )
		;
#endif
}

int uart_tx_ready(void)
{
	return 1;
}

int uart_rx_available(void)
{
#if !defined(CONFIG_POLLING_UART)
	enum UART_PORT id = UART_PORT_1; /* UART1 for ISH */

	return REG8(LSR(id)) & LSR_DR;
#else
	return 0;
#endif
}

void uart_write_char(char c)
{
	enum UART_PORT id = UART_PORT_1; /* UART1 for ISH */

	/* Wait till reciever is ready */
	while ((REG8(LSR(id)) & LSR_TEMT) == 0)
		;

	REG8(THR(id)) = c;
}

#if !defined(CONFIG_POLLING_UART)
int uart_read_char(void)
{
	enum UART_PORT id = UART_PORT_1; /* UART1 for ISH */

	return REG8(RBR(id));
}

void uart_ec_interrupt(void)
{
	/* Read input FIFO until empty, then fill output FIFO */
	uart_process_input();
	uart_process_output();
}
DECLARE_IRQ(ISH_UART1_IRQ, uart_ec_interrupt); /* TODO: 'priority' */
#endif /* !defined(CONFIG_POLLING_UART) */

static int uart_return_baud_rate_by_id(int baud_rate_id)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(baud_conf); i++) {
		if (baud_conf[i][BAUD_IDX] == baud_rate_id)
			return baud_conf[i][BAUD_SPEED];
	}

	return -1;
}

static void uart_hw_init(enum UART_PORT id)
{
	uint32_t divisor;	/* baud rate divisor */
	uint8_t mcr = 0;
	uint8_t fcr = 0;
	struct uart_ctx *ctx = &uart_ctx[id];

	interrupt_disable();

	/* Calculate baud rate divisor */
	divisor = (ctx->input_freq / ctx->baud_rate) >> 4;

	REG32(MUL(ctx->id)) = (divisor * ctx->baud_rate);
	REG32(DIV(ctx->id)) = (ctx->input_freq / 16);
	REG32(PS(ctx->id)) = 16;

	/* Set the DLAB to access the baud rate divisor registers */
	REG8(LCR(ctx->id)) = LCR_DLAB;
	REG8(DLL(ctx->id)) = (divisor & 0xff);
	REG8(DLH(ctx->id)) = ((divisor >> 8) & 0xff);

	/* 8 data bits, 1 stop bit, no parity, clear DLAB */
	REG8(LCR(ctx->id)) = LCR_8BIT_CHR;

	if (ctx->client_flags & UART_CONFIG_HW_FLOW_CONTROL)
		mcr = MCR_AUTO_FLOW_EN;

	mcr |= MCR_INTR_ENABLE;	/* needs to be set regardless of flow control */

	mcr |= (MCR_RTS | MCR_DTR);
	REG8(MCR(ctx->id)) = mcr;

	fcr = FCR_FIFO_SIZE_64 | FCR_ITL_FIFO_64_BYTES_1;

	/* configure FIFOs */
	REG8(FCR(ctx->id)) = (fcr | FCR_FIFO_ENABLE
			      | FCR_RESET_RX | FCR_RESET_TX);

	/* enable UART unit */
	REG32(ABR(ctx->id)) = ABR_UUE;

	/* clear the port */
	REG8(RBR(ctx->id));
#ifdef CONFIG_POLLING_UART
	REG8(IER(ctx->id)) = 0x00;
#else
	REG8(IER(ctx->id)) = IER_RECV;
#endif
	interrupt_enable();
}

static void uart_stop_hw(enum UART_PORT id)
{
	int i;
	uint32_t fifo_len;

	/* Manually clearing the fifo from possible noise.
	 * Entering D0i3 when fifo is not cleared may result in a hang.
	 */
	fifo_len = (REG32(FOR(id)) & FOR_OCCUPANCY_MASK) >> FOR_OCCUPANCY_OFFS;

	for (i = 0; i < fifo_len; i++)
		(void)REG8(RBR(id));

	/* No interrupts are enabled */
	REG8(IER(id)) = 0;
	REG8(MCR(id)) = 0;

	/* Clear and disable FIFOs */
	REG8(FCR(id)) = (FCR_RESET_RX | FCR_RESET_TX);

	/* Disable uart unit */
	REG32(ABR(id)) = 0;
}

static int uart_client_init(enum UART_PORT id, uint32_t baud_rate_id, int flags)
{

	if ((uart_ctx[id].base == 0) || (id >= UART_DEVICES))
		return UART_ERROR;

	if (!bool_compare_and_swap_u32(&uart_ctx[id].is_open, 0, 1))
		return UART_BUSY;

	uart_ctx[id].baud_rate = uart_return_baud_rate_by_id(baud_rate_id);

	if ((uart_ctx[id].baud_rate == -1) || (uart_ctx[id].baud_rate == 0))
		uart_ctx[id].baud_rate = UART_DEFAULT_BAUD_RATE;

	uart_ctx[id].client_flags = flags;

	atomic_and(&uart_ctx[id].uart_state, ~UART_STATE_CG);
	uart_hw_init(id);

	return EC_SUCCESS;
}

static void uart_drv_init(void)
{
	int i;
	uint32_t fifo_len;

	/* Disable UART */
	for (i = 0; i < UART_DEVICES; i++)
		uart_stop_hw(i);

	/* Enable HSU global interrupts (DMA/U0/U1) and set PMEN bit
	 * to allow PMU to clock gate ISH
	 */
	REG32(HSU_BASE + HSU_REG_GIEN) = (GIEN_DMA_EN | GIEN_UART0_EN
					  | GIEN_UART1_EN | GIEN_PWR_MGMT);

	/* There is a by design HW "bug" where all UARTs are enabled by default
	 * but they must be disbled to enter clock gating.
	 * UART0 and UART1 are disabled during their init - but we don't init
	 * UART2 so as a w/a we disable UART2 even though it isn't being used.
	 * we also clear UART 2 fifo, which may cause problem entrying TCG is
	 * not empty (we do the same for UART0 and 1 in "uart_stop_hw"
	 */

	fifo_len = (REG32(UART2_BASE + UART_REG_FOR)
		    & FOR_OCCUPANCY_MASK) >> FOR_OCCUPANCY_OFFS;

	for (i = 0; i < fifo_len; i++)
		(void)REG8((UART2_BASE + UART_REG_RBR));

	REG32(UART2_BASE + UART_REG_ABR) = 0;

	task_enable_irq(ISH_UART0_IRQ);
	task_enable_irq(ISH_UART1_IRQ);
}

void uart_init(void)
{

	uart_drv_init();

	uart_client_init(UART_PORT_1, B115200, 0);
	init_done = 1;
}
