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_ */ +