From 3c57c6e2df750f0500fdbd1acacca4693c5b4596 Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Sat, 22 Jan 2022 02:13:41 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Calculate=20PL011=20divis?= =?UTF-8?q?ors,=20fix=20PullUp/Down=20init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename registers in an attempt to make them more readable. --- machine/src/platform/rpi3/pl011_uart.rs | 299 ++++++++++++++++++------ nucleus/src/main.rs | 3 +- 2 files changed, 227 insertions(+), 75 deletions(-) diff --git a/machine/src/platform/rpi3/pl011_uart.rs b/machine/src/platform/rpi3/pl011_uart.rs index 7b83b2a..5bce434 100644 --- a/machine/src/platform/rpi3/pl011_uart.rs +++ b/machine/src/platform/rpi3/pl011_uart.rs @@ -11,17 +11,17 @@ use { super::{ gpio, - mailbox::{self, MailboxOps}, + mailbox::{self, Mailbox, MailboxOps}, BcmHost, }, crate::{ - arch::loop_until, + arch::loop_while, devices::{ConsoleOps, SerialOps}, platform::MMIODerefWrapper, }, snafu::Snafu, tock_registers::{ - interfaces::{Readable, Writeable}, + interfaces::{ReadWriteable, Readable, Writeable}, register_bitfields, register_structs, registers::{ReadOnly, ReadWrite, WriteOnly}, }, @@ -36,19 +36,41 @@ register_bitfields! { /// Flag Register FR [ + /// Transmit FIFO empty. The meaning of this bit depends on the + /// state of the FEN bit in the Line Control Register, If the + /// FIFO is disabled, this bit is set when the transmit holding + /// register is empty. If the FIFO is enabled, the TXFE bit is + /// set when the transmit FIFO is empty. This bit does not indicate + /// if there is data in the transmit shift register. + TXFE OFFSET(7) NUMBITS(1) [], + + /// Receive FIFO full. The meaning of this bit depends on the + /// state of the FEN bit in the LCRH Register. If the FIFO is + /// disabled, this bit is set when the receive holding register + /// is full. If the FIFO is enabled, the RXFF bit is set when + /// the receive FIFO is full. + RXFF OFFSET(6) NUMBITS(1) [], + /// Transmit FIFO full. The meaning of this bit depends on the - /// state of the FEN bit in the UARTLCR_ LCRH Register. If the + /// state of the FEN bit in the 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 + /// state of the FEN bit in the LCRH 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) [] + RXFE OFFSET(4) NUMBITS(1) [], + + /// UART busy. If this bit is set to 1, the UART is busy + /// transmitting data. This bit remains set until the complete + /// byte, including all the stop bits, has been sent from the + /// shift register. This bit is set as soon as the transmit FIFO + /// becomes non-empty, regardless of whether the UART is enabled or not. + BUSY OFFSET(3) NUMBITS(1) [] ], /// Integer Baud rate divisor @@ -65,9 +87,25 @@ register_bitfields! { /// Line Control register LCRH [ + Parity OFFSET(1) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// Use 2 stop bits + Stop2 OFFSET(3) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + Fifo OFFSET(4) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + /// Word length. These bits indicate the number of data bits /// transmitted or received in a frame. - WLEN OFFSET(5) NUMBITS(2) [ + WordLength OFFSET(5) NUMBITS(2) [ FiveBit = 0b00, SixBit = 0b01, SevenBit = 0b10, @@ -111,33 +149,51 @@ register_bitfields! { ICR [ /// Meta field for all pending interrupts ALL OFFSET(0) NUMBITS(11) [] + ], + + /// Interupt Mask Set/Clear Register + IMSC [ + /// Meta field for all interrupts + ALL OFFSET(0) NUMBITS(11) [] + ], + + /// DMA Control Register + DMACR [ + // RX DMA enabled + RXDMAE OFFSET(0) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + // TX DMA enabled + TXDMAE OFFSET(0) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], ] } +// https://developer.arm.com/documentation/ddi0183/g/programmers-model/summary-of-registers?lang=en register_structs! { #[allow(non_snake_case)] RegisterBlock { - (0x00 => DR: ReadWrite), - (0x04 => __reserved_1), // (UART0_RSRECR=0x04) - (0x18 => FR: ReadOnly), + (0x00 => Data: ReadWrite), // DR + (0x04 => Status: ReadWrite), // RSR/ECR + (0x08 => __reserved_1), + (0x18 => Flag: ReadOnly), (0x1c => __reserved_2), - (0x20 => ILPR: u32), - (0x24 => IBRD: WriteOnly), - (0x28 => FBRD: WriteOnly), - (0x2c => LCRH: WriteOnly), - (0x30 => CR: WriteOnly), - (0x34 => IFLS: u32), - (0x38 => IMSC: u32), - (0x3c => RIS: u32), - (0x40 => MIS: u32), - (0x44 => ICR: WriteOnly), - (0x48 => DMACR: u32), + (0x24 => IntegerBaudRate: ReadWrite), + (0x28 => FractionalBaudRate: ReadWrite), + (0x2c => LineControl: ReadWrite), + (0x30 => Control: ReadWrite), + (0x34 => InterruptFifoLevelSelect: ReadWrite), + (0x38 => InterruptMaskSetClear: ReadWrite), + (0x3c => RawInterruptStatus: ReadOnly), + (0x40 => MaskedInterruptStatus: ReadOnly), + (0x44 => InterruptClear: WriteOnly), + (0x48 => DmaControl: ReadWrite), (0x4c => __reserved_3), - (0x80 => ITCR: u32), - (0x84 => ITIP: u32), - (0x88 => ITOP: u32), - (0x8c => TDR: u32), - (0x90 => @END), + (0x1000 => @END), } } @@ -145,6 +201,16 @@ register_structs! { pub enum PL011UartError { #[snafu(display("PL011 UART setup failed in mailbox operation"))] MailboxError, + #[snafu(display( + "PL011 UART setup failed due to integer baud rate divisor out of range ({})", + ibrd + ))] + InvalidIntegerDivisor { ibrd: u32 }, + #[snafu(display( + "PL011 UART setup failed due to fractional baud rate divisor out of range ({})", + fbrd + ))] + InvalidFractionalDivisor { fbrd: u32 }, } pub type Result = ::core::result::Result; @@ -157,22 +223,46 @@ pub struct PL011Uart { pub struct PreparedPL011Uart(PL011Uart); -/// Divisor values for common baud rates -pub enum Rate { - Baud115200 = 2, +pub struct RateDivisors { + integer_baud_rate_divisor: u32, + fractional_baud_rate_divisor: u32, } -impl From for u32 { - fn from(r: Rate) -> Self { - r as u32 +impl RateDivisors { + // Set integer & fractional part of baud rate. + // Integer = clock/(16 * Baud) + // e.g. 3000000 / (16 * 115200) = 1.627 = ~1. + // Fraction = (Fractional part * 64) + 0.5 + // e.g. (.627 * 64) + 0.5 = 40.6 = ~40. + // + // Use integer-only calculation based on [this page](https://krinkinmu.github.io/2020/11/29/PL011.html) + // Calculate 64 * clock / (16 * rate) = 4 * clock / rate, then extract 6 lowest bits for fractional part + // and the next 16 bits for integer part. + pub fn from_clock_and_rate(clock: u64, baud_rate: u32) -> Result { + let value = 4 * clock / baud_rate as u64; + let i = ((value >> 6) & 0xffff) as u32; + let f = (value & 0x3f) as u32; + // TODO: check for integer overflow, i.e. any bits set above the 0x3fffff mask. + // FIXME: can't happen due to calculation above + if i > 65535 { + return Err(PL011UartError::InvalidIntegerDivisor { ibrd: i }); + } + // FIXME: can't happen due to calculation above + if f > 63 { + return Err(PL011UartError::InvalidFractionalDivisor { fbrd: f }); + } + Ok(RateDivisors { + integer_baud_rate_divisor: i, + fractional_baud_rate_divisor: f, + }) } } -pub const UART_START: usize = 0x20_1000; +pub const UART0_START: usize = 0x20_1000; impl Default for PL011Uart { fn default() -> Self { - const UART0_BASE: usize = BcmHost::get_peripheral_address() + UART_START; + const UART0_BASE: usize = BcmHost::get_peripheral_address() + UART0_START; unsafe { PL011Uart::new(UART0_BASE) } } } @@ -188,20 +278,26 @@ impl PL011Uart { } /// Set baud rate and characteristics (115200 8N1) and map to GPIO - pub fn prepare( - self, - mut mbox: mailbox::Mailbox, - gpio: &gpio::GPIO, - ) -> Result { - // turn off UART0 - self.registers.CR.set(0); + pub fn prepare(self, gpio: &gpio::GPIO) -> Result { + // Turn off UART + self.registers.Control.set(0); + + // Wait for any ongoing transmissions to complete + self.flush_internal(); + + // Flush TX FIFO + self.registers.LineControl.modify(LCRH::Fifo::Disabled); // 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); + const CLOCK: u32 = 4_000_000; // 4Mhz + const BAUD_RATE: u32 = 115_200; - if mbox.call(mailbox::channel::PropertyTagsArmToVc).is_err() { + let mut mailbox = Mailbox::<9>::default(); + let index = mailbox.request(); + let index = mailbox.set_clock_rate(index, mailbox::clock::UART, CLOCK); + let mailbox = mailbox.end(index); + + if mailbox.call(mailbox::channel::PropertyTagsArmToVc).is_err() { return Err(PL011UartError::MailboxError); // Abort if UART clocks couldn't be set }; @@ -210,57 +306,99 @@ impl PL011Uart { // 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); + // Map UART0 to GPIO pins and enable pull-ups + gpio.get_pin(14) + .into_alt(UART_TXD) + .set_pull_up_down(gpio::PullUpDown::Up); + gpio.get_pin(15) + .into_alt(UART_RXD) + .set_pull_up_down(gpio::PullUpDown::Up); - gpio.enable_uart_pins(); + // Clear pending interrupts + self.registers.InterruptClear.write(ICR::ALL::SET); - self.registers.ICR.write(ICR::ALL::CLEAR); - // @todo Configure divisors more sanely + // From the PL011 Technical Reference Manual: + // + // The LCR_H, IBRD, and FBRD registers form the single 30-bit wide LCR Register that is + // updated on a single write strobe generated by a LCR_H write. So, to internally update the + // contents of IBRD or FBRD, a LCR_H write must always be performed at the end. + // + // Set the baud rate divisors, 8N1 and FIFO enabled. + let divisors = RateDivisors::from_clock_and_rate(CLOCK.into(), BAUD_RATE)?; self.registers - .IBRD - .write(IBRD::IBRD.val(Rate::Baud115200.into())); - self.registers.FBRD.write(FBRD::FBRD.val(0xB)); // Results in 115200 baud - self.registers.LCRH.write(LCRH::WLEN::EightBit); // 8N1 - + .IntegerBaudRate + .write(IBRD::IBRD.val(divisors.integer_baud_rate_divisor & 0xffff)); self.registers - .CR + .FractionalBaudRate + .write(FBRD::FBRD.val(divisors.fractional_baud_rate_divisor & 0b11_1111)); + self.registers.LineControl.write( + LCRH::WordLength::EightBit + + LCRH::Fifo::Enabled + + LCRH::Parity::Disabled + + LCRH::Stop2::Disabled, + ); + + // Mask all interrupts by setting corresponding bits to 1 + self.registers.InterruptMaskSetClear.write(IMSC::ALL::SET); + + // Disable DMA + self.registers + .DmaControl + .write(DMACR::RXDMAE::Disabled + DMACR::TXDMAE::Disabled); + + // Turn on UART + self.registers + .Control .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); Ok(PreparedPL011Uart(self)) } + + fn flush_internal(&self) { + loop_while(|| self.registers.Flag.is_set(FR::BUSY)); + } } impl Drop for PreparedPL011Uart { fn drop(&mut self) { - self.0 - .registers - .CR - .write(CR::UARTEN::Disabled + CR::TXE::Disabled + CR::RXE::Disabled); + self.0.registers.Control.set(0); } } impl SerialOps for PreparedPL011Uart { - fn write_byte(&self, b: u8) { - // wait until we can send - loop_until(|| !self.0.registers.FR.is_set(FR::TXFF)); - - // write the character to the buffer - self.0.registers.DR.set(b as u32); - } - fn read_byte(&self) -> u8 { // wait until something is in the buffer - loop_until(|| !self.0.registers.FR.is_set(FR::RXFE)); + loop_while(|| self.0.registers.Flag.is_set(FR::RXFE)); // read it and return - self.0.registers.DR.get() as u8 + self.0.registers.Data.get() as u8 } - fn flush(&self) {} + fn write_byte(&self, b: u8) { + // wait until we can send + loop_while(|| self.0.registers.Flag.is_set(FR::TXFF)); - fn clear_rx(&self) {} + // write the character to the buffer + self.0.registers.Data.set(b as u32); + } + + /// Wait until the TX FIFO is empty, aka all characters have been put on the + /// line. + fn flush(&self) { + self.0.flush_internal(); + } + + /// Consume input until RX FIFO is empty, aka all pending characters have been + /// consumed. + fn clear_rx(&self) { + loop_while(|| { + let pending = !self.0.registers.Flag.is_set(FR::RXFE); + if pending { + self.read_byte(); + } + pending + }); + } } impl ConsoleOps for PreparedPL011Uart { @@ -293,3 +431,18 @@ impl ConsoleOps for PreparedPL011Uart { ret } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test_case] + fn test_divisors() { + const CLOCK: u64 = 3_000_000; + const BAUD_RATE: u32 = 115_200; + + let divisors = RateDivisors::from_clock_and_rate(CLOCK, BAUD_RATE); + assert_eq!(divisors.integer_baud_rate_divisor, 1); + assert_eq!(divisors.fractional_baud_rate_divisor, 40); + } +} diff --git a/nucleus/src/main.rs b/nucleus/src/main.rs index 7755f36..7f352c4 100644 --- a/nucleus/src/main.rs +++ b/nucleus/src/main.rs @@ -85,7 +85,6 @@ fn init_uart_serial() { // 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 @@ -100,7 +99,7 @@ fn init_uart_serial() { // is just a special case. CONSOLE.lock(|c| c.flush()); - match uart.prepare(mbox, &gpio) { + match uart.prepare(&gpio) { Ok(uart) => { CONSOLE.lock(|c| { // Move uart into the global CONSOLE.