Add mini_uart implementation

This commit is contained in:
Berkus Decker 2020-10-27 12:01:08 +02:00
parent 7a46d7d993
commit cecddea41e
6 changed files with 306 additions and 0 deletions

7
Cargo.lock generated
View File

@ -12,6 +12,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cortex-a"
version = "3.0.5"
@ -71,6 +77,7 @@ version = "0.0.1"
dependencies = [
"bit_field",
"bitflags",
"cfg-if",
"cortex-a",
"qemu-exit",
"r0",

View File

@ -30,3 +30,4 @@ usize_conversions = "0.2.0"
bit_field = "0.10.0"
register = "0.5.1"
bitflags = "1.2.1"
cfg-if = "1.0"

View File

@ -4,6 +4,7 @@
#![allow(dead_code)]
use crate::platform;
use core::fmt;
/// A trait that must be implemented by devices that are candidates for the
@ -28,6 +29,7 @@ impl ConsoleOps for NullConsole {}
/// Possible outputs which the console can store.
pub enum Output {
None(NullConsole),
MiniUart(platform::rpi3::mini_uart::PreparedMiniUart),
}
/// Generate boilerplate for converting into one of Output enum values
@ -40,6 +42,8 @@ macro output_from($name:ty, $optname:ident) {
}
output_from!(NullConsole, None);
output_from!(platform::rpi3::mini_uart::PreparedMiniUart, MiniUart);
pub struct Console {
output: Output,
}
@ -63,6 +67,7 @@ impl Console {
fn current_ptr(&self) -> &dyn ConsoleOps {
match &self.output {
Output::None(i) => i,
Output::MiniUart(i) => i,
}
}

View File

@ -7,6 +7,7 @@
use {
super::BcmHost,
crate::arch::loop_delay,
core::{marker::PhantomData, ops},
register::{
mmio::{ReadOnly, ReadWrite, WriteOnly},
@ -130,6 +131,19 @@ impl GPIO {
}
}
pub fn enable_uart_pins(gpio: &GPIO) {
gpio.PUD.set(0);
loop_delay(150);
// enable pins 14 and 15
gpio.PUDCLK[0].write(PUDCLK0::PUDCLK14::AssertClock + PUDCLK0::PUDCLK15::AssertClock);
loop_delay(150);
gpio.PUDCLK[0].set(0);
}
/// An alternative GPIO function.
#[repr(u8)]
pub enum Function {

View File

@ -0,0 +1,278 @@
/*
* SPDX-License-Identifier: MIT OR BlueOak-1.0.0
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
* Original code distributed under MIT, additional changes are under BlueOak-1.0.0
*/
use {
super::{gpio, BcmHost},
crate::devices::ConsoleOps,
cfg_if::cfg_if,
core::{convert::From, fmt, ops},
register::{mmio::*, register_bitfields},
};
// Auxiliary mini UART registers
//
// Descriptions taken from
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
register_bitfields! {
u32,
/// Auxiliary enables
AUX_ENABLES [
/// If set the mini UART is enabled. The UART will immediately
/// start receiving data, especially if the UART1_RX line is
/// low.
/// If clear the mini UART is disabled. That also disables any
/// mini UART register access
MINI_UART_ENABLE OFFSET(0) NUMBITS(1) []
],
/// Mini Uart Interrupt Identify
AUX_MU_IIR [
/// Writing with bit 1 set will clear the receive FIFO
/// Writing with bit 2 set will clear the transmit FIFO
FIFO_CLEAR OFFSET(1) NUMBITS(2) [
Rx = 0b01,
Tx = 0b10,
All = 0b11
]
],
/// Mini Uart Line Control
AUX_MU_LCR [
/// Mode the UART works in
DATA_SIZE OFFSET(0) NUMBITS(2) [
SevenBit = 0b00,
EightBit = 0b11
]
],
/// Mini Uart Line Status
AUX_MU_LSR [
/// This bit is set if the transmit FIFO is empty and the transmitter is
/// idle. (Finished shifting out the last bit).
TX_IDLE OFFSET(6) NUMBITS(1) [],
/// This bit is set if the transmit FIFO can accept at least
/// one byte.
TX_EMPTY OFFSET(5) NUMBITS(1) [],
/// This bit is set if the receive FIFO holds at least 1
/// symbol.
DATA_READY OFFSET(0) NUMBITS(1) []
],
/// Mini Uart Extra Control
AUX_MU_CNTL [
/// If this bit is set the mini UART transmitter is enabled.
/// If this bit is clear the mini UART transmitter is disabled.
TX_EN OFFSET(1) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// If this bit is set the mini UART receiver is enabled.
/// If this bit is clear the mini UART receiver is disabled.
RX_EN OFFSET(0) NUMBITS(1) [
Disabled = 0,
Enabled = 1
]
],
/// Mini Uart Baudrate
AUX_MU_BAUD [
/// Mini UART baudrate counter
RATE OFFSET(0) NUMBITS(16) []
]
}
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
__reserved_0: u32, // 0x00 - AUX_IRQ?
AUX_ENABLES: ReadWrite<u32, AUX_ENABLES::Register>, // 0x04
__reserved_1: [u32; 14], // 0x08
AUX_MU_IO: ReadWrite<u32>, // 0x40 - Mini Uart I/O Data
AUX_MU_IER: WriteOnly<u32>, // 0x44 - Mini Uart Interrupt Enable
AUX_MU_IIR: WriteOnly<u32, AUX_MU_IIR::Register>, // 0x48
AUX_MU_LCR: WriteOnly<u32, AUX_MU_LCR::Register>, // 0x4C
AUX_MU_MCR: WriteOnly<u32>, // 0x50
AUX_MU_LSR: ReadOnly<u32, AUX_MU_LSR::Register>, // 0x54
__reserved_2: [u32; 2], // 0x58 - AUX_MU_MSR, AUX_MU_SCRATCH
AUX_MU_CNTL: WriteOnly<u32, AUX_MU_CNTL::Register>, // 0x60
__reserved_3: u32, // 0x64 - AUX_MU_STAT
AUX_MU_BAUD: WriteOnly<u32, AUX_MU_BAUD::Register>, // 0x68
}
pub struct MiniUart {
base_addr: usize,
}
pub struct PreparedMiniUart(MiniUart);
/// Divisor values for common baud rates
pub enum Rate {
Baud115200 = 270,
}
impl From<Rate> for u32 {
fn from(r: Rate) -> Self {
r as u32
}
}
/// Deref to RegisterBlock
///
/// Allows writing
/// ```
/// self.MU_IER.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*MiniUart::ptr()).MU_IER.read() }
/// ```
impl ops::Deref for MiniUart {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
}
}
// [temporary] Used in mmu.rs to set up local paging
pub const UART1_BASE: usize = BcmHost::get_peripheral_address() + 0x21_5000;
impl Default for MiniUart {
fn default() -> MiniUart {
MiniUart::new(UART1_BASE)
}
}
impl MiniUart {
pub fn new(base_addr: usize) -> MiniUart {
MiniUart { base_addr }
}
/// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock {
self.base_addr as *const _
}
}
impl MiniUart {
cfg_if! {
if #[cfg(not(feature = "noserial"))] {
/// Set baud rate and characteristics (115200 8N1) and map to GPIO
pub fn prepare(self, gpio: &gpio::GPIO) -> PreparedMiniUart {
// initialize UART
self.AUX_ENABLES.modify(AUX_ENABLES::MINI_UART_ENABLE::SET);
self.AUX_MU_IER.set(0);
self.AUX_MU_CNTL.set(0);
self.AUX_MU_LCR.write(AUX_MU_LCR::DATA_SIZE::EightBit);
self.AUX_MU_MCR.set(0);
self.AUX_MU_IER.set(0);
self.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All);
self.AUX_MU_BAUD
.write(AUX_MU_BAUD::RATE.val(Rate::Baud115200.into()));
// Pin 14
const MINI_UART_TXD: gpio::Function = gpio::Function::Alt5;
// Pin 15
const MINI_UART_RXD: gpio::Function = gpio::Function::Alt5;
// map UART1 to GPIO pins
gpio.get_pin(14).into_alt(MINI_UART_TXD);
gpio.get_pin(15).into_alt(MINI_UART_RXD);
gpio::enable_uart_pins(gpio);
self.AUX_MU_CNTL
.write(AUX_MU_CNTL::RX_EN::Enabled + AUX_MU_CNTL::TX_EN::Enabled);
// Clear FIFOs before using the device
self.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All);
PreparedMiniUart(self)
}
} else {
pub fn prepare(self, _gpio: &gpio::GPIO) -> PreparedMiniUart {
PreparedMiniUart(self)
}
}
}
}
impl Drop for PreparedMiniUart {
fn drop(&mut self) {
self.0
.AUX_ENABLES
.modify(AUX_ENABLES::MINI_UART_ENABLE::CLEAR);
// @todo disable gpio.PUD ?
}
}
impl ConsoleOps for PreparedMiniUart {
cfg_if! {
if #[cfg(not(feature = "noserial"))] {
/// Send a character
fn putc(&self, c: char) {
// wait until we can send
crate::arch::loop_until(|| self.0.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_EMPTY));
// write the character to the buffer
self.0.AUX_MU_IO.set(c as u32);
}
/// Display a string
fn puts(&self, string: &str) {
for c in string.chars() {
// convert newline to carriage return + newline
if c == '\n' {
self.putc('\r')
}
self.putc(c);
}
}
/// Receive a character
fn getc(&self) -> char {
// wait until something is in the buffer
crate::arch::loop_until(|| self.0.AUX_MU_LSR.is_set(AUX_MU_LSR::DATA_READY));
// read it and return
let mut ret = self.0.AUX_MU_IO.get() as u8 as char;
// convert carriage return to newline
if ret == '\r' {
ret = '\n'
}
ret
}
/// Wait until the TX FIFO is empty, aka all characters have been put on the
/// line.
fn flush(&self) {
crate::arch::loop_until(|| self.0.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_IDLE));
}
} else {
fn putc(&self, _c: char) {}
fn puts(&self, _string: &str) {}
fn getc(&self) -> char {
'\n'
}
fn flush(&self) {}
}
}
}
impl fmt::Write for PreparedMiniUart {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.puts(s);
Ok(())
}
}

View File

@ -8,6 +8,7 @@
pub mod fb;
pub mod gpio;
pub mod mailbox;
pub mod mini_uart;
/// See BCM2835-ARM-Peripherals.pdf
/// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.