You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
3.6 KiB
Rust

//! Driver for the UART.
use core::arch::asm;
use core::fmt;
use core::ptr::{read_volatile, write_volatile};
use crate::utils::field::Field;
use crate::traits::console::{Write, Console};
use super::memory_map::uart::*;
pub struct Uart {
initialized: bool,
}
const TMP_UARTEN: Field = Field::new(CR, 0, 1);
const TMP_TXE: Field = Field::new(CR, 8, 1);
const TMP_BUSY: Field = Field::new(FR, 3, 1);
const TMP_TXFF: Field = Field::new(FR, 5, 1);
const TMP_FEN: Field = Field::new(LCRH, 4, 1);
const TMP_WLEN: Field = Field::new(LCRH, 5, 2);
const TMP_IBRD: Field = Field::new(IBRD, 0, 16);
const TMP_FBRD: Field = Field::new(FBRD, 0, 6);
const TMP_DATA: Field = Field::new(DR, 0, 8);
impl Uart {
// TODO: not sure this should be public outside of bsp.
/// Constructor for [`Uart`].
pub const fn new() -> Self {
Uart { initialized: false }
}
/// Initialise the UART.
pub fn init(&mut self) {
// TODO: Recover from possible previous test.
self.flush();
// Stop UART
TMP_UARTEN.read_and_write(0);
// Flush the FIFOs
TMP_FEN.read_and_write(0);
// Clear all interrupt
unsafe { write_volatile(ICR as *mut u32, 0); }
// Config UART
// 8N1 115_200 bauds.
// divbaud = freq/16/baudrate = 48_000_000 / 16 / 115_200 = 26.041666666666668
// => IBRD = 26
// => FBRD = round(0.041666666666668 * 64) = 3 // TODO: why 64?
TMP_IBRD.write_without_read(26);
TMP_FBRD.write_without_read(3);
// Set word len to 8
let lcrh_val = TMP_WLEN.read_and_write_to_u32(0b11, 0);
// Reenable the FIFOs
let lcrh_val = TMP_FEN.read_and_write_to_u32(1, lcrh_val);
unsafe { write_volatile(LCRH as *mut u32, lcrh_val); }
let cr_val = TMP_TXE.read_and_write_to_u32(1, 0);
// TODO: let cr_val = TMP_RXE.read_and_write_to_u32(1, 0); to enable read
unsafe { write_volatile(CR.get_address() as *mut u32, cr_val); }
// Start the UART
self.initialized = true;
TMP_UARTEN.read_and_write(1);
}
/// Test if the UART is busy.
fn is_busy(&self) -> bool {
TMP_BUSY.read() != 0
}
/// Test if the the TX FIFO is full.
fn is_tx_full(&self) -> bool {
TMP_TXFF.read() != 0
}
}
// Allow the use of uart for fmt::Write::write_fmt.
impl fmt::Write for Uart {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
if c == '\n' {
Write::write_char(self, '\r');
}
Write::write_char(self, c);
}
Ok(())
}
}
// TODO: add sync
impl Write for Uart {
fn write_char(&self, c: char) {
if !self.initialized {
//panic!("Cannot write to a non initialized UART");
}
while self.is_tx_full() {
use super::gpio;
let _ = gpio::set_pin_output_state(20, gpio::PinOutputState::High);
unsafe { asm!("nop") };
}
TMP_DATA.write_without_read(c as u32);
}
fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result {
// TODO: use syncronisation here
let mut new_uart = Uart { initialized: self.initialized };
fmt::Write::write_fmt(&mut new_uart, args)
}
fn flush(&self) {
while self.is_busy() {
unsafe { asm!("nop") };
}
}
}
impl Console for Uart {}
static UART: Uart = Uart { initialized: true }; // TODO: use sync
pub fn init() {
let mut uart = Uart::new();
uart.init();
}
// TODO: move?
/// Return a reference to the Uart Output.
pub fn console() -> &'static dyn Console {
&UART
}