/*
 * \brief  UART driver for the x86 PC
 * \author Norman Feske
 * \author Alexander Boettcher
 * \date   2009-12-28
 */

/*
 * Copyright (C) 2009-2017 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _INCLUDE__DRIVERS__UART__X86_PC_H_
#define _INCLUDE__DRIVERS__UART__X86_PC_H_

/* Genode includes */
#include <base/stdint.h>
#include <cpu/memory_barrier.h>

namespace Genode { class X86_uart; }

class Genode::X86_uart
{
	private:

		uint16_t const _port;
		unsigned const _baud_rate;

		enum {
			COMPORT_DATA_OFFSET   = 0,
			COMPORT_STATUS_OFFSET = 5,

			STATUS_THR_EMPTY = 0x20,  /* transmitter hold register empty */
			STATUS_DHR_EMPTY = 0x40,  /* data hold register empty - data completely sent */
		};


		/**
		 * Read byte from I/O port
		 */
		uint8_t _inb(uint16_t port)
		{
			uint8_t res;
			asm volatile ("inb %%dx, %0" :"=a"(res) :"Nd"(port));
			return res;
		}


		/**
		 * Write byte to I/O port
		 */
		void _outb(uint16_t port, uint8_t val)
		{
			asm volatile ("outb %b0, %w1" : : "a" (val), "Nd" (port));
		}

	public:

		X86_uart(uint16_t const port, unsigned /* clock */,
		         unsigned const baud_rate)
		:
			_port(port), _baud_rate(baud_rate)
		{
			init();
		}

		void init()
		{
			/**
			 * Initialize serial port
			 *
			 * Based on 'init_serial' of L4ka::Pistachio's 'kdb/platform/pc99/io.cc'
			 */

			if (!_port)
				return;

			uint16_t const
				IER  = (uint16_t)(_port + 1),
				EIR  = (uint16_t)(_port + 2),
				LCR  = (uint16_t)(_port + 3),
				MCR  = (uint16_t)(_port + 4),
				LSR  = (uint16_t)(_port + 5),
				MSR  = (uint16_t)(_port + 6),
				DLLO = (uint16_t)(_port + 0),
				DLHI = (uint16_t)(_port + 1);

			_outb(LCR, 0x80);  /* select bank 1 */
			for (int i = 10000000; i; --i) memory_barrier();
			_outb(DLLO, (uint8_t)((115200/_baud_rate) >> 0));
			_outb(DLHI, (uint8_t)((115200/_baud_rate) >> 8));
			_outb(LCR, 0x03);  /* set 8,N,1 */
			_outb(IER, 0x00);  /* disable interrupts */
			_outb(EIR, 0x07);  /* enable FIFOs */
			_outb(MCR, 0x0b);  /* force data terminal ready */
			_outb(IER, 0x01);  /* enable RX interrupts */
			_inb(IER);
			_inb(EIR);
			_inb(LCR);
			_inb(MCR);
			_inb(LSR);
			_inb(MSR);
		}

		void put_char(char const c)
		{
			if (!_port)
				return;

			/* wait until serial port is ready */
			uint8_t ready = STATUS_THR_EMPTY;
			while ((_inb((uint16_t)(_port + COMPORT_STATUS_OFFSET)) & ready) != ready);

			/* output character */
			_outb(_port + COMPORT_DATA_OFFSET, c);
		}
};

#endif /* _INCLUDE__DRIVERS__UART__X86_PC_H_ */
