/**
 * \file
 *
 * \brief Serial console driver.
 *
 * This is a very simple serial driver serving as a backend for the
 * console layer. Everything that the console layer writes is dumped
 * over the serial line.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All devices
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
#include <assert.h>
#include <interrupt.h>
#include <status-codes.h>
#include <util.h>

#include <chip/clk.h>
#include <console/core.h>
#include <console/driver.h>
#include <console/serial.h>
#include <hardware/usart.h>

struct usart_port {
	void			*regs;
	unsigned long		pclk_hz;
	struct console_driver	drv;
};

static inline struct usart_port *usart_port_of(struct console_driver *drv)
{
	return container_of(drv, struct usart_port, drv);
}

static void usart_tx_commit(struct console_driver *drv)
{
	struct usart_port	*usart = usart_port_of(drv);
	void			*regs = usart->regs;
	unsigned long		nchars;
	unsigned long		iflags;
	char			c;

	iflags = cpu_irq_save();
	while (true) {
		nchars = console_buf_used(&drv->tx_buf);
		assert(nchars <= CONSOLE_BUF_SIZE);
		if (nchars == 0)
			break;

		c = console_buf_extract_char(&drv->tx_buf);
		cpu_irq_restore(iflags);

		while (!(usart_read(regs, CSR) & USART_BIT(TXRDY)))
			barrier();

		usart_write(regs, THR, c);
		iflags = cpu_irq_save();
	}
	cpu_irq_restore(iflags);
}

static void usart_tx_make_room(struct console_driver *drv, size_t goal)
{
	/* Keep it simple for now */
	usart_tx_commit(drv);
}

/* FIXME: Support multiple ports */
static struct usart_port the_usart_port = {
	.drv.tx_commit = usart_tx_commit,
	.drv.tx_make_room = usart_tx_make_room,
};

/**
 * Calculate the value to be written to the BRGR register given a baud
 * rate and assuming 8x oversampling. To calculate the baud rate with
 * 16x oversampling, use @a base_hz = peripheral_clock / 2.
 *
 * @param base_hz The input clock rate after accounting for oversampling
 * @param baud Desired baud rate.
 * @return BRGR value that will generate approximately the baud rate
 *	specified by @a baud, or 0 if this is not possible with the
 *	specified input clock rate.
 */
static uint32_t usart_calc_brgr(unsigned long base_hz, unsigned long baud)
{
	unsigned long div;
	unsigned long cd;
	unsigned long fp;

	div = (base_hz + baud / 2) / baud;
	cd = div / 8;
	fp = div % 8;

	if (cd < 2 || (cd & ((1 << USART_CD_SIZE) - 1)) != cd)
		return 0;

	return USART_BF(CD, cd) | USART_BF(FP, fp);
}

int __serial_console_init(struct console *con, unsigned int port, void *regs,
		unsigned long baud_rate, unsigned long flags)
{
	struct usart_port	*usart = &the_usart_port;
	unsigned long		pclk_hz;
	uint32_t		cr_flags = 0;

	if (flags & SERIAL_ENABLE_TX)
		cr_flags |= USART_BIT(TXEN);

	/* We don't actually support RX yet... */
	if (flags & SERIAL_ENABLE_RX)
		cr_flags |= USART_BIT(RXEN);

	pclk_hz = get_usart_pclk_rate(port);
	usart->pclk_hz = pclk_hz;
	usart->regs = regs;

	/* If the USART has already been initialized, don't touch anything */
	if (!usart_read(regs, BRGR)) {
		uint32_t	brgr;
		uint32_t	mr;

		mr = USART_BF(MODE, USART_MODE_NORMAL)
			| USART_BF(USCLKS, USART_USCLKS_MCK)
			| USART_BF(CHRL, USART_CHRL_8)
			| USART_BF(PAR, USART_PAR_NONE)
			| USART_BF(NBSTOP, USART_NBSTOP_1);

		brgr = usart_calc_brgr((pclk_hz + 1) / 2, baud_rate);
		if (!brgr) {
			brgr = usart_calc_brgr(pclk_hz, baud_rate);
			if (!brgr)
				return -STATUS_INVALID_PARAM;
			mr |= USART_BIT(OVER);
		}

		usart_write(regs, CR, USART_BIT(RSTRX) | USART_BIT(RSTTX));
		usart_write(regs, BRGR, brgr);
		usart_write(regs, MR, mr);
	}

	usart_write(regs, CR, cr_flags);

	con->drv = &usart->drv;

	return 0;
}
