/* Calypso DBB internal UART Driver */

/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
 * (C) 2010 by Ingo Albrecht <prom@berlin.ccc.de>
 *
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <debug.h>

#include <memory.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include <defines.h>
#include <console.h>
#include <comm/sercomm.h>

#include <calypso/irq.h>
#include <uart.h>

#define BASE_ADDR_UART_MODEM	0xffff5000
#define OFFSET_IRDA		0x800

#define UART_REG(n,m)	(BASE_ADDR_UART_MODEM + ((n)*OFFSET_IRDA)+(m))

#define LCR7BIT		0x80
#define LCRBFBIT	0x40
#define MCR6BIT		0x20
#define REG_OFFS(m)	((m) & ~(LCR7BIT|LCRBFBIT|MCR6BIT))
/* read access LCR[7] = 0 */
enum uart_reg {
	RHR	= 0,
	IER	= 1,
	IIR	= 2,
	LCR	= 3,
	MCR	= 4,
	LSR	= 5,
	MSR	= 6,
	SPR	= 7,
	MDR1	= 8,
	DMR2	= 9,
	SFLSR	= 0x0a,
	RESUME	= 0x0b,
	SFREGL	= 0x0c,
	SFREGH	= 0x0d,
	BLR	= 0x0e,
	ACREG	= 0x0f,
	SCR	= 0x10,
	SSR	= 0x11,
	EBLR	= 0x12,
/* read access LCR[7] = 1 */
	DLL	= RHR | LCR7BIT,
	DLH	= IER | LCR7BIT,
	DIV1_6	= ACREG | LCR7BIT,
/* read/write access LCR[7:0] = 0xbf */
	EFR	= IIR | LCRBFBIT,
	XON1	= MCR | LCRBFBIT,
	XON2	= LSR | LCRBFBIT,
	XOFF1	= MSR | LCRBFBIT,
	XOFF2 	= SPR | LCRBFBIT,
/* read/write access if EFR[4] = 1 and MCR[6] = 1 */
	TCR	= MSR | MCR6BIT,
	TLR	= SPR | MCR6BIT,
};
/* write access LCR[7] = 0 */
#define THR	RHR
#define FCR	IIR		/* only if EFR[4] = 1 */
#define TXFLL	SFLSR
#define TXFLH	RESUME
#define RXFLL	SFREGL
#define RXFLH	SFREGH

enum fcr_bits {
	FIFO_EN		= (1 << 0),
	RX_FIFO_CLEAR	= (1 << 1),
	TX_FIFO_CLEAR	= (1 << 2),
	DMA_MODE	= (1 << 3),
};
#define TX_FIFO_TRIG_SHIFT	4
#define RX_FIFO_TRIG_SHIFT	6

enum iir_bits {
	IIR_INT_PENDING			= 0x01,
	IIR_INT_TYPE			= 0x3E,
	IIR_INT_TYPE_RX_STATUS_ERROR 	= 0x06,
	IIR_INT_TYPE_RX_TIMEOUT		= 0x0C,
	IIR_INT_TYPE_RHR		= 0x04,
	IIR_INT_TYPE_THR		= 0x02,
	IIR_INT_TYPE_MSR		= 0x00,
	IIR_INT_TYPE_XOFF		= 0x10,
	IIR_INT_TYPE_FLOW		= 0x20,
	IIR_FCR0_MIRROR			= 0xC0,
};

#define UART_REG_UIR	0xffff6000

/* enable or disable the divisor latch for access to DLL, DLH */
static void uart_set_lcr7bit(int uart, int on)
{
	uint8_t reg;

	reg = readb(UART_REG(uart, LCR));
	if (on)
		reg |= (1 << 7);
	else
		reg &= ~(1 << 7);
	writeb(reg, UART_REG(uart, LCR));
}

static uint8_t old_lcr;
static void uart_set_lcr_bf(int uart, int on)
{
	if (on) {
		old_lcr = readb(UART_REG(uart, LCR));
		writeb(0xBF, UART_REG(uart, LCR));
	} else {
		writeb(old_lcr, UART_REG(uart, LCR));
	}
}

/* Enable or disable the TCR_TLR latch bit in MCR[6] */
static void uart_set_mcr6bit(int uart, int on)
{
	uint8_t mcr;
	/* we assume EFR[4] is always set to 1 */
	mcr = readb(UART_REG(uart, MCR));
	if (on)
		mcr |= (1 << 6);
	else
		mcr &= ~(1 << 6);
	writeb(mcr, UART_REG(uart, MCR));
}

static void uart_reg_write(int uart, enum uart_reg reg, uint8_t val)
{
	if (reg & LCRBFBIT)
		uart_set_lcr_bf(uart, 1);
	else if (reg & LCR7BIT)
		uart_set_lcr7bit(uart, 1);
	else if (reg & MCR6BIT)
		uart_set_mcr6bit(uart, 1);

	writeb(val, UART_REG(uart, REG_OFFS(reg)));

	if (reg & LCRBFBIT)
		uart_set_lcr_bf(uart, 0);
	else if (reg & LCR7BIT)
		uart_set_lcr7bit(uart, 0);
	else if (reg & MCR6BIT)
		uart_set_mcr6bit(uart, 0);
}

/* read from a UART register, applying any required latch bits */
static uint8_t uart_reg_read(int uart, enum uart_reg reg)
{
	uint8_t ret;

	if (reg & LCRBFBIT)
		uart_set_lcr_bf(uart, 1);
	else if (reg & LCR7BIT)
		uart_set_lcr7bit(uart, 1);
	else if (reg & MCR6BIT)
		uart_set_mcr6bit(uart, 1);

	ret = readb(UART_REG(uart, REG_OFFS(reg)));

	if (reg & LCRBFBIT)
		uart_set_lcr_bf(uart, 0);
	else if (reg & LCR7BIT)
		uart_set_lcr7bit(uart, 0);
	else if (reg & MCR6BIT)
		uart_set_mcr6bit(uart, 0);

	return ret;
}

static void uart_irq_handler_cons(__unused enum irq_nr irqnr)
{
	const uint8_t uart = cons_get_uart();
	uint8_t iir;

	//uart_putchar_nb(uart, 'U');

	iir = uart_reg_read(uart, IIR);
	if (iir & IIR_INT_PENDING)
		return;

	switch (iir & IIR_INT_TYPE) {
	case IIR_INT_TYPE_RHR:
		break;
	case IIR_INT_TYPE_THR:
		if (cons_rb_flush() == 1) {
			/* everything was flushed, disable THR IRQ */
			uint8_t ier = uart_reg_read(uart, IER);
			ier &= ~(1 << 1);
			uart_reg_write(uart, IER, ier);
		}
		break;
	case IIR_INT_TYPE_MSR:
		break;
	case IIR_INT_TYPE_RX_STATUS_ERROR:
		break;
	case IIR_INT_TYPE_RX_TIMEOUT:
		break;
	case IIR_INT_TYPE_XOFF:
		break;
	}
}

static void uart_irq_handler_sercomm(__unused enum irq_nr irqnr)
{
	const uint8_t uart = sercomm_get_uart();
	uint8_t iir, ch;

	//uart_putchar_nb(uart, 'U');

	iir = uart_reg_read(uart, IIR);
	if (iir & IIR_INT_PENDING)
		return;

	switch (iir & IIR_INT_TYPE) {
	case IIR_INT_TYPE_RX_TIMEOUT:
	case IIR_INT_TYPE_RHR:
		/* as long as we have rx data available */
		while (uart_getchar_nb(uart, &ch)) {
			if (sercomm_drv_rx_char(ch) < 0) {
				/* sercomm cannot receive more data right now */
				uart_irq_enable(uart, UART_IRQ_RX_CHAR, 0);
			}
		}
		break;
	case IIR_INT_TYPE_THR:
		/* as long as we have space in the FIFO */
		while (!uart_tx_busy(uart)) {
			/* get a byte from sercomm */
			if (!sercomm_drv_pull(&ch)) {
				/* no more bytes in sercomm, stop TX interrupts */
				uart_irq_enable(uart, UART_IRQ_TX_EMPTY, 0);
				break;
			}
			/* write the byte into the TX FIFO */
			uart_putchar_nb(uart, ch);
		}
		break;
	case IIR_INT_TYPE_MSR:
		printf("UART IRQ MSR\n");
		break;
	case IIR_INT_TYPE_RX_STATUS_ERROR:
		printf("UART IRQ RX_SE\n");
		break;
	case IIR_INT_TYPE_XOFF:
		printf("UART IRQXOFF\n");
		break;
	}
}

static const uint8_t uart2irq[] = {
	[0]	= IRQ_UART_IRDA,
	[1]	= IRQ_UART_MODEM,
};

void uart_init(uint8_t uart, uint8_t interrupts)
{
	uint8_t irq = uart2irq[uart];

	uart_reg_write(uart, IER, 0x00);
	if (uart == cons_get_uart()) {
		cons_init();
		if(interrupts) {
			irq_register_handler(irq, &uart_irq_handler_cons);
			irq_config(irq, 0, 0, 0xff);
			irq_enable(irq);
		}
	} else if (uart == sercomm_get_uart()) {
		sercomm_init();
		if(interrupts) {
			irq_register_handler(irq, &uart_irq_handler_sercomm);
			irq_config(irq, 0, 0, 0xff);
			irq_enable(irq);
		}
		uart_irq_enable(uart, UART_IRQ_RX_CHAR, 1);
	} else {
		return;
	}
#if 0
	if (uart == 1) {
		/* assign UART to MCU and unmask interrupts*/
		writeb(UART_REG_UIR, 0x00);
	}
#endif

	/* if we don't initialize these, we get strange corruptions in the
	   received data... :-( */
	uart_reg_write(uart,  MDR1, 0x07); /* turn off UART */
	uart_reg_write(uart,  XON1, 0x00); /* Xon1/Addr Register */
	uart_reg_write(uart,  XON2, 0x00); /* Xon2/Addr Register */
	uart_reg_write(uart, XOFF1, 0x00); /* Xoff1 Register */
	uart_reg_write(uart, XOFF2, 0x00); /* Xoff2 Register */
	uart_reg_write(uart,   EFR, 0x00); /* Enhanced Features Register */

	/* select  UART mode */
	uart_reg_write(uart, MDR1, 0);
	/* no XON/XOFF flow control, ENHANCED_EN, no auto-RTS/CTS */
	uart_reg_write(uart, EFR, (1 << 4));
	/* enable Tx/Rx FIFO, Tx trigger at 56 spaces, Rx trigger at 60 chars */
	uart_reg_write(uart, FCR, FIFO_EN | RX_FIFO_CLEAR | TX_FIFO_CLEAR |
			(3 << TX_FIFO_TRIG_SHIFT) | (3 << RX_FIFO_TRIG_SHIFT));

	/* THR interrupt only when TX FIFO and TX shift register are empty */
	uart_reg_write(uart, SCR, (1 << 0));// | (1 << 3));

	/* 8 bit, 1 stop bit, no parity, no break */
	uart_reg_write(uart, LCR, 0x03);

	uart_set_lcr7bit(uart, 0);
}

void uart_poll(uint8_t uart) {
	if(uart == cons_get_uart()) {
		uart_irq_handler_cons(0);
	} else {
		uart_irq_handler_sercomm(0);
	}
}

void uart_irq_enable(uint8_t uart, enum uart_irq irq, int on)
{
	uint8_t ier = uart_reg_read(uart, IER);
	uint8_t mask = 0;

	switch (irq) {
	case UART_IRQ_TX_EMPTY:
		mask = (1 << 1);
		break;
	case UART_IRQ_RX_CHAR:
		mask = (1 << 0);
		break;
	}

	if (on)
		ier |= mask;
	else
		ier &= ~mask;

	uart_reg_write(uart, IER, ier);
}


void uart_putchar_wait(uint8_t uart, int c)
{
	/* wait while TX FIFO indicates full */
	while (readb(UART_REG(uart, SSR)) & 0x01) { }

	/* put character in TX FIFO */
	writeb(c, UART_REG(uart, THR));
}

int uart_putchar_nb(uint8_t uart, int c)
{
	/* if TX FIFO indicates full, abort */
	if (readb(UART_REG(uart, SSR)) & 0x01)
		return 0;

	writeb(c, UART_REG(uart, THR));
	return 1;
}

int uart_getchar_nb(uint8_t uart, uint8_t *ch)
{
	uint8_t lsr;

	lsr = readb(UART_REG(uart, LSR));

	/* something strange happened */
	if (lsr & 0x02)
		printf("LSR RX_OE\n");
	if (lsr & 0x04)
		printf("LSR RX_PE\n");
	if (lsr & 0x08)
		printf("LSR RX_FE\n");
	if (lsr & 0x10)
		printf("LSR RX_BI\n");
	if (lsr & 0x80)
		printf("LSR RX_FIFO_STS\n");

	/* is the Rx FIFO empty? */
	if (!(lsr & 0x01))
		return 0;

	*ch = readb(UART_REG(uart, RHR));
	//printf("getchar_nb(%u) = %02x\n", uart, *ch);
	return 1;
}

int uart_tx_busy(uint8_t uart)
{
	if (readb(UART_REG(uart, SSR)) & 0x01)
		return 1;
	return 0;
}

static const uint16_t divider[] = {
	[UART_38400]	= 21,	/*   38,690 */
	[UART_57600]	= 14,	/*   58,035 */
	[UART_115200]	= 7,	/*  116,071 */
	[UART_230400]	= 4,	/*  203,125! (-3% would be 223,488) */
	[UART_460800]	= 2,	/*  406,250! (-3% would be 446,976) */
	[UART_921600]	= 1,	/*  812,500! (-3% would be 893,952) */
};

int uart_baudrate(uint8_t uart, enum uart_baudrate bdrt)
{
	uint16_t div;

	if (bdrt >= ARRAY_SIZE(divider))
		return -1;

	div = divider[bdrt];
	uart_set_lcr7bit(uart, 1);
	writeb(div & 0xff, UART_REG(uart, DLL));
	writeb(div >> 8, UART_REG(uart, DLH));
	uart_set_lcr7bit(uart, 0);

	return 0;
}
