Add PL011 UART
This commit is contained in:
parent
d124b02e7c
commit
5dffa9eb8e
|
@ -30,6 +30,7 @@ impl ConsoleOps for NullConsole {}
|
|||
pub enum Output {
|
||||
None(NullConsole),
|
||||
MiniUart(platform::rpi3::mini_uart::PreparedMiniUart),
|
||||
Uart(platform::rpi3::pl011_uart::PreparedPL011Uart),
|
||||
}
|
||||
|
||||
/// Generate boilerplate for converting into one of Output enum values
|
||||
|
@ -43,6 +44,7 @@ macro output_from($name:ty, $optname:ident) {
|
|||
|
||||
output_from!(NullConsole, None);
|
||||
output_from!(platform::rpi3::mini_uart::PreparedMiniUart, MiniUart);
|
||||
output_from!(platform::rpi3::pl011_uart::PreparedPL011Uart, Uart);
|
||||
|
||||
pub struct Console {
|
||||
output: Output,
|
||||
|
@ -68,6 +70,7 @@ impl Console {
|
|||
match &self.output {
|
||||
Output::None(i) => i,
|
||||
Output::MiniUart(i) => i,
|
||||
Output::Uart(i) => i,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,9 @@ fn init_exception_traps() {
|
|||
|
||||
#[cfg(not(feature = "noserial"))]
|
||||
fn init_uart_serial() {
|
||||
use crate::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart};
|
||||
use crate::platform::rpi3::{
|
||||
gpio::GPIO, mailbox::Mailbox, mini_uart::MiniUart, pl011_uart::PL011Uart,
|
||||
};
|
||||
let gpio = GPIO::default();
|
||||
let uart = MiniUart::default();
|
||||
let uart = uart.prepare(&gpio);
|
||||
|
@ -104,6 +106,36 @@ fn init_uart_serial() {
|
|||
});
|
||||
|
||||
println!("[0] MiniUART is live!");
|
||||
|
||||
// Then immediately switch to PL011 (just as an example)
|
||||
|
||||
let uart = PL011Uart::default();
|
||||
let mbox = Mailbox::default();
|
||||
|
||||
// uart.init() will reconfigure the GPIO, which causes a race against
|
||||
// the MiniUart that is still putting out characters on the physical
|
||||
// line that are already buffered in its TX FIFO.
|
||||
//
|
||||
// To ensure the CPU doesn't rewire the GPIO before the MiniUart has put
|
||||
// its last character, explicitly flush it before rewiring.
|
||||
//
|
||||
// If you switch to an output that happens to not use the same pair of
|
||||
// physical wires (e.g. the Framebuffer), you don't need to do this,
|
||||
// because flush() is anyways called implicitly by replace_with(). This
|
||||
// is just a special case.
|
||||
use crate::devices::console::ConsoleOps;
|
||||
CONSOLE.lock(|c| c.flush());
|
||||
|
||||
match uart.prepare(mbox, &gpio) {
|
||||
Ok(uart) => {
|
||||
CONSOLE.lock(|c| {
|
||||
// Move uart into the global CONSOLE.
|
||||
c.replace_with(uart.into());
|
||||
});
|
||||
println!("[0] UART0 is live!");
|
||||
}
|
||||
Err(_) => println!("[0] Error switching to PL011 UART, continue with MiniUART"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Kernel entry point.
|
||||
|
|
|
@ -9,6 +9,7 @@ pub mod fb;
|
|||
pub mod gpio;
|
||||
pub mod mailbox;
|
||||
pub mod mini_uart;
|
||||
pub mod pl011_uart;
|
||||
|
||||
/// See BCM2835-ARM-Peripherals.pdf
|
||||
/// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf
|
||||
* https://docs.rs/embedded-serial/0.5.0/embedded_serial/
|
||||
*/
|
||||
|
||||
use {
|
||||
super::{
|
||||
gpio,
|
||||
mailbox::{self, MailboxOps},
|
||||
BcmHost,
|
||||
},
|
||||
crate::{arch::loop_until, devices::ConsoleOps},
|
||||
core::ops,
|
||||
register::{mmio::*, register_bitfields},
|
||||
snafu::Snafu,
|
||||
};
|
||||
|
||||
// PL011 UART registers.
|
||||
//
|
||||
// Descriptions taken from
|
||||
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
|
||||
register_bitfields! {
|
||||
u32,
|
||||
|
||||
/// Flag Register
|
||||
FR [
|
||||
/// Transmit FIFO full. The meaning of this bit depends on the
|
||||
/// state of the FEN bit in the UARTLCR_ LCRH Register. If the
|
||||
/// FIFO is disabled, this bit is set when the transmit
|
||||
/// holding register is full. If the FIFO is enabled, the TXFF
|
||||
/// bit is set when the transmit FIFO is full.
|
||||
TXFF OFFSET(5) NUMBITS(1) [],
|
||||
|
||||
/// Receive FIFO empty. The meaning of this bit depends on the
|
||||
/// state of the FEN bit in the UARTLCR_H Register. If the
|
||||
/// FIFO is disabled, this bit is set when the receive holding
|
||||
/// register is empty. If the FIFO is enabled, the RXFE bit is
|
||||
/// set when the receive FIFO is empty.
|
||||
RXFE OFFSET(4) NUMBITS(1) []
|
||||
],
|
||||
|
||||
/// Integer Baud rate divisor
|
||||
IBRD [
|
||||
/// Integer Baud rate divisor
|
||||
IBRD OFFSET(0) NUMBITS(16) []
|
||||
],
|
||||
|
||||
/// Fractional Baud rate divisor
|
||||
FBRD [
|
||||
/// Fractional Baud rate divisor
|
||||
FBRD OFFSET(0) NUMBITS(6) []
|
||||
],
|
||||
|
||||
/// Line Control register
|
||||
LCRH [
|
||||
/// Word length. These bits indicate the number of data bits
|
||||
/// transmitted or received in a frame.
|
||||
WLEN OFFSET(5) NUMBITS(2) [
|
||||
FiveBit = 0b00,
|
||||
SixBit = 0b01,
|
||||
SevenBit = 0b10,
|
||||
EightBit = 0b11
|
||||
]
|
||||
],
|
||||
|
||||
/// Control Register
|
||||
CR [
|
||||
/// Receive enable. If this bit is set to 1, the receive
|
||||
/// section of the UART is enabled. Data reception occurs for
|
||||
/// UART signals. When the UART is disabled in the middle of
|
||||
/// reception, it completes the current character before
|
||||
/// stopping.
|
||||
RXE OFFSET(9) NUMBITS(1) [
|
||||
Disabled = 0,
|
||||
Enabled = 1
|
||||
],
|
||||
|
||||
/// Transmit enable. If this bit is set to 1, the transmit
|
||||
/// section of the UART is enabled. Data transmission occurs
|
||||
/// for UART signals. When the UART is disabled in the middle
|
||||
/// of transmission, it completes the current character before
|
||||
/// stopping.
|
||||
TXE OFFSET(8) NUMBITS(1) [
|
||||
Disabled = 0,
|
||||
Enabled = 1
|
||||
],
|
||||
|
||||
/// UART enable
|
||||
UARTEN OFFSET(0) NUMBITS(1) [
|
||||
/// If the UART is disabled in the middle of transmission
|
||||
/// or reception, it completes the current character
|
||||
/// before stopping.
|
||||
Disabled = 0,
|
||||
Enabled = 1
|
||||
]
|
||||
],
|
||||
|
||||
/// Interupt Clear Register
|
||||
ICR [
|
||||
/// Meta field for all pending interrupts
|
||||
ALL OFFSET(0) NUMBITS(11) []
|
||||
]
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C)]
|
||||
pub struct RegisterBlock {
|
||||
DR: ReadWrite<u32>, // 0x00
|
||||
__reserved_0: [u32; 5], // 0x04 (UART0_RSRECR=0x04)
|
||||
FR: ReadOnly<u32, FR::Register>, // 0x18
|
||||
__reserved_1: [u32; 1], // 0x1c
|
||||
ILPR: u32, // 0x20
|
||||
IBRD: WriteOnly<u32, IBRD::Register>, // 0x24
|
||||
FBRD: WriteOnly<u32, FBRD::Register>, // 0x28
|
||||
LCRH: WriteOnly<u32, LCRH::Register>, // 0x2C
|
||||
CR: WriteOnly<u32, CR::Register>, // 0x30
|
||||
IFLS: u32, // 0x34
|
||||
IMSC: u32, // 0x38
|
||||
RIS: u32, // 0x3C
|
||||
MIS: u32, // 0x40
|
||||
ICR: WriteOnly<u32, ICR::Register>, // 0x44
|
||||
DMACR: u32, // 0x48
|
||||
__reserved_2: [u32; 14], // 0x4c-0x7c
|
||||
ITCR: u32, // 0x80
|
||||
ITIP: u32, // 0x84
|
||||
ITOP: u32, // 0x88
|
||||
TDR: u32, // 0x8C
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum PL011UartError {
|
||||
#[snafu(display("PL011 UART setup failed in mailbox operation"))]
|
||||
MailboxError,
|
||||
}
|
||||
pub type Result<T> = ::core::result::Result<T, PL011UartError>;
|
||||
|
||||
pub struct PL011Uart {
|
||||
base_addr: usize,
|
||||
}
|
||||
|
||||
pub struct PreparedPL011Uart(PL011Uart);
|
||||
|
||||
/// Divisor values for common baud rates
|
||||
pub enum Rate {
|
||||
Baud115200 = 2,
|
||||
}
|
||||
|
||||
impl From<Rate> for u32 {
|
||||
fn from(r: Rate) -> Self {
|
||||
r as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for PL011Uart {
|
||||
type Target = RegisterBlock;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for PreparedPL011Uart {
|
||||
type Target = RegisterBlock;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.0.ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PL011Uart {
|
||||
fn default() -> Self {
|
||||
const UART0_BASE: usize = BcmHost::get_peripheral_address() + 0x20_1000;
|
||||
PL011Uart::new(UART0_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
impl PL011Uart {
|
||||
pub fn new(base_addr: usize) -> PL011Uart {
|
||||
PL011Uart { base_addr }
|
||||
}
|
||||
|
||||
/// Returns a pointer to the register block
|
||||
fn ptr(&self) -> *const RegisterBlock {
|
||||
self.base_addr as *const _
|
||||
}
|
||||
|
||||
/// Set baud rate and characteristics (115200 8N1) and map to GPIO
|
||||
pub fn prepare(
|
||||
self,
|
||||
mut mbox: mailbox::Mailbox,
|
||||
gpio: &gpio::GPIO,
|
||||
) -> Result<PreparedPL011Uart> {
|
||||
// turn off UART0
|
||||
self.CR.set(0);
|
||||
|
||||
// set up clock for consistent divisor values
|
||||
let index = mbox.request();
|
||||
let index = mbox.set_clock_rate(index, mailbox::clock::UART, 4_000_000 /* 4Mhz */);
|
||||
let mbox = mbox.end(index);
|
||||
|
||||
if mbox.call(mailbox::channel::PropertyTagsArmToVc).is_err() {
|
||||
return Err(PL011UartError::MailboxError); // Abort if UART clocks couldn't be set
|
||||
};
|
||||
|
||||
// Pin 14
|
||||
const UART_TXD: gpio::Function = gpio::Function::Alt0;
|
||||
// Pin 15
|
||||
const UART_RXD: gpio::Function = gpio::Function::Alt0;
|
||||
|
||||
// map UART0 to GPIO pins
|
||||
gpio.get_pin(14).into_alt(UART_TXD);
|
||||
gpio.get_pin(15).into_alt(UART_RXD);
|
||||
|
||||
gpio::enable_uart_pins(gpio);
|
||||
|
||||
self.ICR.write(ICR::ALL::CLEAR);
|
||||
// @todo Configure divisors more sanely
|
||||
self.IBRD.write(IBRD::IBRD.val(Rate::Baud115200.into()));
|
||||
self.FBRD.write(FBRD::FBRD.val(0xB)); // Results in 115200 baud
|
||||
self.LCRH.write(LCRH::WLEN::EightBit); // 8N1
|
||||
|
||||
self.CR
|
||||
.write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
|
||||
|
||||
Ok(PreparedPL011Uart(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PreparedPL011Uart {
|
||||
fn drop(&mut self) {
|
||||
self.CR
|
||||
.write(CR::UARTEN::Disabled + CR::TXE::Disabled + CR::RXE::Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
impl ConsoleOps for PreparedPL011Uart {
|
||||
/// Send a character
|
||||
fn putc(&self, c: char) {
|
||||
// wait until we can send
|
||||
loop_until(|| !self.FR.is_set(FR::TXFF));
|
||||
|
||||
// write the character to the buffer
|
||||
self.DR.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
|
||||
loop_until(|| !self.FR.is_set(FR::RXFE));
|
||||
|
||||
// read it and return
|
||||
let mut ret = self.DR.get() as u8 as char;
|
||||
|
||||
// convert carriage return to newline
|
||||
if ret == '\r' {
|
||||
ret = '\n'
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue