From 2b0c6133365547d52cae15109b9aa81503af866c Mon Sep 17 00:00:00 2001
From: Martin Stein <martin.stein@genode-labs.com>
Date: Thu, 24 May 2012 12:58:33 +0200
Subject: [PATCH] Basic drivers for UART modules PL011 and TL16C750

---
 base/include/drivers/uart/pl011_base.h    | 189 ++++++++++++++++++
 base/include/drivers/uart/tl16c750_base.h | 225 ++++++++++++++++++++++
 2 files changed, 414 insertions(+)
 create mode 100644 base/include/drivers/uart/pl011_base.h
 create mode 100644 base/include/drivers/uart/tl16c750_base.h

diff --git a/base/include/drivers/uart/pl011_base.h b/base/include/drivers/uart/pl011_base.h
new file mode 100644
index 0000000000..4d0d374508
--- /dev/null
+++ b/base/include/drivers/uart/pl011_base.h
@@ -0,0 +1,189 @@
+/*
+ * \brief  Driver base for the PrimeCell UART PL011 Revision r1p3
+ * \author Martin stein
+ * \date   2011-10-17
+ */
+
+/*
+ * Copyright (C) 2011-2012 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+#ifndef _BASE__INCLUDE__DRIVERS__UART__PL011_BASE_H_
+#define _BASE__INCLUDE__DRIVERS__UART__PL011_BASE_H_
+
+/* Genode includes */
+#include <util/mmio.h>
+
+namespace Genode
+{
+	/**
+	 * Driver base for the PrimeCell UART PL011 Revision r1p3
+	 */
+	class Pl011_base : Mmio
+	{
+		protected:
+
+			enum {
+				MAX_BAUD_RATE = 0xfffffff,
+
+				ASCII_LINE_FEED = 10,
+				ASCII_CARRIAGE_RETURN = 13,
+			};
+
+			/**
+			 * Data register
+			 */
+			struct Uartdr : public Register<0x00, 16>
+			{
+				struct Data : Bitfield<0,8>  { };
+				struct Fe   : Bitfield<8,1>  { };
+				struct Pe   : Bitfield<9,1>  { };
+				struct Be   : Bitfield<10,1> { };
+				struct Oe   : Bitfield<11,1> { };
+			};
+
+			/**
+			 * Flag register
+			 */
+			struct Uartfr : public Register<0x18, 16>
+			{
+				struct Cts  : Bitfield<0,1> { };
+				struct Dsr  : Bitfield<1,1> { };
+				struct Dcd  : Bitfield<2,1> { };
+				struct Busy : Bitfield<3,1> { };
+				struct Rxfe : Bitfield<4,1> { };
+				struct Txff : Bitfield<5,1> { };
+				struct Rxff : Bitfield<6,1> { };
+				struct Txfe : Bitfield<7,1> { };
+				struct Ri   : Bitfield<8,1> { };
+			};
+
+			/**
+			 * Integer baud rate register
+			 */
+			struct Uartibrd : public Register<0x24, 16>
+			{
+				struct Ibrd : Bitfield<0,15> { };
+			};
+
+			/**
+			 * Fractional Baud Rate Register
+			 */
+			struct Uartfbrd : public Register<0x28, 8>
+			{
+				struct Fbrd : Bitfield<0,6> { };
+			};
+
+			/**
+			 * Line Control Register
+			 */
+			struct Uartlcrh : public Register<0x2c, 16>
+			{
+				struct Wlen : Bitfield<5,2> {
+					enum {
+						WORD_LENGTH_8BITS = 3,
+						WORD_LENGTH_7BITS = 2,
+						WORD_LENGTH_6BITS = 1,
+						WORD_LENGTH_5BITS = 0,
+					};
+				};
+			};
+
+			/**
+			 * Control Register
+			 */
+			struct Uartcr : public Register<0x30, 16>
+			{
+				struct Uarten : Bitfield<0,1> { };
+				struct Txe    : Bitfield<8,1> { };
+				struct Rxe    : Bitfield<9,1> { };
+			};
+
+			/**
+			 * Interrupt Mask Set/Clear
+			 */
+			struct Uartimsc : public Register<0x38, 16>
+			{
+				struct Imsc : Bitfield<0,11> { };
+			};
+
+			/**
+			 * Idle until the device is ready for action
+			 */
+			void _wait_until_ready() { while (read<Uartfr::Busy>()) ; }
+
+		public:
+
+			/**
+			 * Constructor
+			 * \param  base       Device MMIO base
+			 * \param  clock      Device reference clock frequency
+			 * \param  baud_rate  Targeted UART baud rate
+			 */
+			inline Pl011_base(addr_t const base, uint32_t const clock,
+			                  uint32_t const baud_rate);
+
+			/**
+			 * Send ASCII char 'c' over the UART interface
+			 */
+			inline void put_char(char const c);
+	};
+}
+
+
+Genode::Pl011_base::Pl011_base(addr_t const base, uint32_t const clock,
+                               uint32_t const baud_rate) : Mmio(base)
+{
+	write<Uartcr>(Uartcr::Uarten::bits(1) |
+	              Uartcr::Txe::bits(1)    |
+	              Uartcr::Rxe::bits(1));
+
+	/**
+	 * We can't print an error or throw C++ exceptions
+	 * because we must expect both to be uninitialized yet,
+	 * so its better to hold the program counter in here for debugging
+	 */
+	if (baud_rate > MAX_BAUD_RATE) while(1) ;
+
+	/**
+	 * Calculate fractional and integer part of baud rate
+	 * divisor to initialize IBRD and FBRD
+	 */
+	uint32_t           const adjusted_br = baud_rate << 4;
+	double             const divisor = (double)clock / adjusted_br;
+	Uartibrd::access_t const ibrd = (Uartibrd::access_t)divisor;
+	Uartfbrd::access_t const fbrd = (Uartfbrd::access_t)(((divisor - ibrd)
+	                                * 64) + 0.5);
+
+	write<Uartfbrd::Fbrd>(fbrd);
+	write<Uartibrd::Ibrd>(ibrd);
+
+	write<Uartlcrh::Wlen>(Uartlcrh::Wlen::WORD_LENGTH_8BITS);
+
+	/**
+	 * Unmask all interrupts
+	 */
+	write<Uartimsc::Imsc>(0);
+
+	_wait_until_ready();
+}
+
+
+void Genode::Pl011_base::put_char(char const c)
+{
+	/* Wait as long as the transmission buffer is full */
+	while (read<Uartfr::Txff>()) ;
+
+	/* Auto complete new line commands */
+	if (c == ASCII_LINE_FEED) write<Uartdr::Data>(ASCII_CARRIAGE_RETURN);
+
+	/* Transmit character */
+	write<Uartdr::Data>(c);
+	_wait_until_ready();
+}
+
+
+#endif /* _BASE__INCLUDE__DRIVERS__UART__PL011_BASE_H_ */
diff --git a/base/include/drivers/uart/tl16c750_base.h b/base/include/drivers/uart/tl16c750_base.h
new file mode 100644
index 0000000000..b013266c1e
--- /dev/null
+++ b/base/include/drivers/uart/tl16c750_base.h
@@ -0,0 +1,225 @@
+/*
+ * \brief  Base UART driver for the Texas instruments TL16C750 module
+ * \author Martin stein
+ * \date   2011-10-17
+ */
+
+/*
+ * Copyright (C) 2011-2012 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+#ifndef _BASE__INCLUDE__DRIVERS__UART__TL16C750_BASE_H_
+#define _BASE__INCLUDE__DRIVERS__UART__TL16C750_BASE_H_
+
+/* Genode includes */
+#include <util/mmio.h>
+
+namespace Genode
+{
+	enum {
+		ASCII_LINE_FEED = 10,
+		ASCII_CARRIAGE_RETURN = 13,
+	};
+
+	/**
+	 * Base driver Texas instruments TL16C750 UART module
+	 *
+	 * \detail  In contrast to the abilities of the TL16C750, this driver
+	 *          targets only the basic UART functionalities.
+	 */
+	class Tl16c750_base : public Mmio
+	{
+		/**
+		 * Least significant divisor part
+		 */
+		struct Uart_dll : Register<0x0, 32>
+		{
+			struct Clock_lsb : Bitfield<0, 8> { };
+		};
+
+		/**
+		 * Transmit holding register
+		 */
+		struct Uart_thr : Register<0x0, 32>
+		{
+			struct Thr : Bitfield<0, 8> { };
+		};
+
+		/**
+		 * Most significant divisor part
+		 */
+		struct Uart_dlh : Register<0x4, 32>
+		{
+			struct Clock_msb : Bitfield<0, 6> { };
+		};
+
+		/**
+		 * Interrupt enable register
+		 */
+		struct Uart_ier : Register<0x4, 32>
+		{
+			struct Rhr_it : Bitfield<0, 1> { };
+			struct Thr_it : Bitfield<1, 1> { };
+			struct Line_sts_it : Bitfield<2, 1> { };
+			struct Modem_sts_it : Bitfield<3, 1> { };
+			struct Sleep_mode : Bitfield<4, 1> { };
+			struct Xoff_it : Bitfield<5, 1> { };
+			struct Rts_it : Bitfield<6, 1> { };
+			struct Cts_it : Bitfield<7, 1> { };
+		};
+
+		/**
+		 * FIFO control register
+		 */
+		struct Uart_fcr : Register<0x8, 32>
+		{
+			struct Fifo_enable   : Bitfield<0, 1> { };
+		};
+
+		/**
+		 * Line control register
+		 */
+		struct Uart_lcr : Register<0xc, 32>
+		{
+			struct Char_length : Bitfield<0, 2>
+			{
+				enum { _8_BIT = 3 };
+			};
+			struct Nb_stop : Bitfield<2, 1>
+			{
+				enum { _1_STOP_BIT = 0 };
+			};
+			struct Parity_en : Bitfield<3, 1> { };
+			struct Break_en  : Bitfield<6, 1> { };
+			struct Div_en    : Bitfield<7, 1> { };
+			struct Reg_mode  : Bitfield<0, 8>
+			{
+				enum { OPERATIONAL = 0, CONFIG_A = 0x80, CONFIG_B = 0xbf };
+			};
+		};
+
+		/**
+		 * Modem control register
+		 */
+		struct Uart_mcr : Register<0x10, 32>
+		{
+			struct Tcr_tlr : Bitfield<6, 1> { };
+		};
+
+		/**
+		 * Line status register
+		 */
+		struct Uart_lsr : Register<0x14, 32>
+		{
+			struct Tx_fifo_empty : Bitfield<5, 1> { };
+		};
+
+		/**
+		 * Mode definition register 1
+		 */
+		struct Uart_mdr1 : Register<0x20, 32>
+		{
+			struct Mode_select : Bitfield<0, 3>
+			{
+				enum { UART_16X = 0, DISABLED = 7 };
+			};
+		};
+
+		/**
+		 * System control register
+		 */
+		struct Uart_sysc : Register<0x54, 32>
+		{
+			struct Softreset : Bitfield<1, 1> { };
+		};
+
+		/**
+		 * System status register
+		 */
+		struct Uart_syss : Register<0x58, 32>
+		{
+			struct Resetdone : Bitfield<0, 1> { };
+		};
+
+		public:
+
+			/**
+			 * Constructor
+			 *
+			 * \param  base       MMIO base address
+			 * \param  clock      Reference clock
+			 * \param  baud_rate  Targeted baud rate
+			 */
+			Tl16c750_base(addr_t const base, unsigned long const clock,
+			              unsigned long const baud_rate) : Mmio(base)
+			{
+				/* Reset and disable UART */
+				write<Uart_sysc::Softreset>(1);
+				while (!read<Uart_syss::Resetdone>()) ;
+				write<Uart_mdr1::Mode_select>(Uart_mdr1::Mode_select::DISABLED);
+
+				/* Enable access to 'Uart_fcr' and 'Uart_ier' */
+				write<Uart_lcr::Reg_mode>(Uart_lcr::Reg_mode::OPERATIONAL);
+
+				/* Configure FIFOs, we don't use any interrupts or DMA,
+				 * thus FIFO trigger and DMA configurations are dispensable */
+				write<Uart_fcr::Fifo_enable>(1);
+
+				/* Disable interrupts and sleep mode */
+				write<Uart_ier>(Uart_ier::Rhr_it::bits(0)
+				              | Uart_ier::Thr_it::bits(0)
+				              | Uart_ier::Line_sts_it::bits(0)
+				              | Uart_ier::Modem_sts_it::bits(0)
+				              | Uart_ier::Sleep_mode::bits(0)
+				              | Uart_ier::Xoff_it::bits(0)
+				              | Uart_ier::Rts_it::bits(0)
+				              | Uart_ier::Cts_it::bits(0));
+
+				/* Enable access to 'Uart_dlh' and 'Uart_dll' */
+				write<Uart_lcr::Reg_mode>(Uart_lcr::Reg_mode::CONFIG_B);
+
+				/* Load the new divisor value (this driver solely uses
+				 * 'UART_16X' mode )*/
+				enum { UART_16X_DIVIDER_LOG2 = 4 };
+				unsigned long const adjusted_br = baud_rate << UART_16X_DIVIDER_LOG2;
+				double const divisor = (double)clock / adjusted_br;
+				unsigned long const divisor_uint = (unsigned long)divisor;
+				write<Uart_dll::Clock_lsb>(divisor_uint);
+				write<Uart_dlh::Clock_msb>(divisor_uint>>Uart_dll::Clock_lsb::WIDTH);
+
+				/* Configure protocol formatting and thereby return to
+				 * operational mode */
+				write<Uart_lcr>(Uart_lcr::Char_length::bits(Uart_lcr::Char_length::_8_BIT)
+				              | Uart_lcr::Nb_stop::bits(Uart_lcr::Nb_stop::_1_STOP_BIT)
+				              | Uart_lcr::Parity_en::bits(0)
+				              | Uart_lcr::Break_en::bits(0)
+				              | Uart_lcr::Div_en::bits(0));
+
+				/* Switch to UART mode, we don't use hardware or software flow
+				 * control, thus according configurations are dispensable*/
+				write<Uart_mdr1::Mode_select>(Uart_mdr1::Mode_select::UART_16X);
+			}
+
+			/**
+			 * Transmit ASCII char 'c'
+			 */
+			void put_char(char const c)
+			{
+				/* Wait as long as the transmission buffer is full */
+				while (!read<Uart_lsr::Tx_fifo_empty>()) ;
+
+				/* Auto complete new line commands */
+				if (c == ASCII_LINE_FEED)
+					write<Uart_thr::Thr>(ASCII_CARRIAGE_RETURN);
+
+				/* Transmit character */
+				write<Uart_thr::Thr>(c);
+			}
+	};
+}
+
+#endif /* _BASE__INCLUDE__DRIVERS__UART__TL16C750_BASE_H_ */
+