This repository has been archived on 2026-06-26. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
axi-uart16550/src/lib.rs
T
Robin Mueller 8b59e6128f
ci / Check build (macos-latest) (push) Has been cancelled
ci / Check build (ubuntu-latest) (push) Has been cancelled
ci / Check build (windows-latest) (push) Has been cancelled
ci / Check MSRV (push) Has been cancelled
ci / Check Cross-Compilation (armv7-unknown-linux-gnueabihf) (push) Has been cancelled
ci / Check Cross-Compilation (armv7a-none-eabi) (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (macos-latest) (pull_request) Has been cancelled
ci / Check build (ubuntu-latest) (pull_request) Has been cancelled
ci / Check build (windows-latest) (pull_request) Has been cancelled
ci / Check MSRV (pull_request) Has been cancelled
ci / Check Cross-Compilation (armv7-unknown-linux-gnueabihf) (pull_request) Has been cancelled
ci / Check Cross-Compilation (armv7a-none-eabi) (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
bump AXI uart16550 crate
2026-06-08 21:13:37 +02:00

425 lines
13 KiB
Rust

//! # AMD AXI UART16550 driver
//!
//! This is a native Rust driver for the [AMD AXI UART16550](https://www.amd.com/de/products/adaptive-socs-and-fpgas/intellectual-property/axi_uart16550.html)
//! IP core.
//!
//! # Features
//!
//! If asynchronous TX operations are used, the number of wakers which defaults to 1 waker can
//! also be configured. The [tx_async] module provides more details on the meaning of this number.
//!
//! - `1-waker` which is also a `default` feature
//! - `2-wakers`
//! - `4-wakers`
//! - `8-wakers`
//! - `16-wakers`
//! - `32-wakers`
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
use core::convert::Infallible;
use registers::{FifoControl, InterruptEnable, LineControl, RxFifoTrigger, StopBits, WordLen};
pub mod registers;
pub mod tx;
pub use tx::*;
pub mod tx_async;
pub use tx_async::*;
pub mod rx;
pub use rx::*;
/// Maximum FIFO depth of the AXI UART16550.
pub const FIFO_DEPTH: usize = 16;
/// Default RX FIFO trigger level.
pub const DEFAULT_RX_TRIGGER_LEVEL: RxFifoTrigger = RxFifoTrigger::EightBytes;
/// Clock configuration structure.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ClockConfig {
/// Divisor value.
pub div: u16,
}
/// Divisor is zero error.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("divisor is zero")]
pub struct DivisorZeroError;
/// Calculate the error rate of the baudrate with the given clock frequency, baudrate and
/// divisor as a floating point value between 0.0 and 1.0.
#[inline]
pub fn calculate_error_rate_from_div(
clk_in: fugit::HertzU32,
baudrate: u32,
div: u16,
) -> Result<f32, DivisorZeroError> {
if baudrate == 0 || div == 0 {
return Err(DivisorZeroError);
}
let actual = (clk_in.to_raw() as f32) / (16.0 * div as f32);
Ok(libm::fabsf(actual - baudrate as f32) / baudrate as f32)
}
/// If this error occurs, the calculated baudrate divisor is too large, either because the
/// used clock is too large, or the baudrate is too slow for the used clock frequency.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("divisor too large")]
pub enum ClockConfigError {
/// Divisor too large error.
DivisorTooLargeError(u32),
/// Divisor is zero error.
DivisorZero(#[from] DivisorZeroError),
}
impl ClockConfig {
/// New clock config with the given divisor.
pub fn new(div: u16) -> Self {
Self { div }
}
/// MSB part of the divisor.
#[inline(always)]
pub fn div_msb(&self) -> u8 {
(self.div >> 8) as u8
}
/// LSB part of the divisor.
#[inline(always)]
pub fn div_lsb(&self) -> u8 {
self.div as u8
}
/// This function calculates the required divisor values for a given input clock and baudrate
/// as well as an baud error rate.
#[inline]
pub fn new_autocalc_with_error(
clk_in: fugit::HertzU32,
baudrate: u32,
) -> Result<(Self, f32), ClockConfigError> {
let cfg = Self::new_autocalc(clk_in, baudrate)?;
Ok((cfg, cfg.calculate_error_rate(clk_in, baudrate)?))
}
/// This function calculates the required divisor values for a given input clock and baudrate.
///
/// The function will not calculate the error rate. You can use [Self::calculate_error_rate]
/// to check the error rate, or use the [Self::new_autocalc_with_error] function to get both
/// the clock config and its baud error.
#[inline]
pub fn new_autocalc(clk_in: fugit::HertzU32, baudrate: u32) -> Result<Self, ClockConfigError> {
let div = Self::calc_div_with_integer_div(clk_in, baudrate)?;
if div > u16::MAX as u32 {
return Err(ClockConfigError::DivisorTooLargeError(div));
}
Ok(Self { div: div as u16 })
}
/// Calculate the error rate of the baudrate with the given clock frequency, baudrate and the
/// current clock config as a floating point value between 0.0 and 1.0.
#[inline]
pub fn calculate_error_rate(
&self,
clk_in: fugit::HertzU32,
baudrate: u32,
) -> Result<f32, DivisorZeroError> {
calculate_error_rate_from_div(clk_in, baudrate, self.div)
}
/// Calculate the divisor from an input clock for a give target baudrate.
#[inline(always)]
pub const fn calc_div_with_integer_div(
clk_in: fugit::HertzU32,
baudrate: u32,
) -> Result<u32, DivisorZeroError> {
if baudrate == 0 {
return Err(DivisorZeroError);
}
// Rounding integer division, by adding half the divisor to the dividend.
Ok((clk_in.to_raw() + (8 * baudrate)) / (16 * baudrate))
}
}
/// Parity configuration.
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum Parity {
/// No parity (default).
#[default]
None,
/// Odd parity.
Odd,
/// Even parity.
Even,
}
/// AXI UART16550 peripheral driver.
pub struct AxiUart16550 {
rx: Rx,
tx: Tx,
config: UartConfig,
}
/// UART configuration structure.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct UartConfig {
clk: ClockConfig,
word_len: WordLen,
parity: Parity,
stop_bits: StopBits,
}
impl UartConfig {
/// New with the given clock configuration.
pub const fn new_with_clk_config(clk: ClockConfig) -> Self {
Self {
clk,
word_len: WordLen::Eight,
parity: Parity::None,
stop_bits: StopBits::One,
}
}
/// New with all parameters.
pub const fn new(
clk: ClockConfig,
word_len: WordLen,
parity: Parity,
stop_bits: StopBits,
) -> Self {
Self {
clk,
word_len,
parity,
stop_bits,
}
}
}
impl AxiUart16550 {
/// Create a new AXI UART16550 peripheral driver.
///
/// # Safety
///
/// - The `base_addr` must be a valid memory-mapped register address of an AXI UART 16550
/// peripheral.
/// - Dereferencing an invalid or misaligned address results in **undefined behavior**.
/// - The caller must ensure that no other code concurrently modifies the same peripheral registers
/// in an unsynchronized manner to prevent data races.
/// - This function does not enforce uniqueness of driver instances. Creating multiple instances
/// with the same `base_addr` can lead to unintended behavior if not externally synchronized.
/// - The driver performs **volatile** reads and writes to the provided address.
pub unsafe fn new(base_addr: u32, config: UartConfig) -> Self {
let mut regs = unsafe { registers::Registers::new_mmio_at(base_addr as usize) };
// This unlocks the divisor config registers.
regs.write_lcr(LineControl::new_for_divisor_access());
regs.write_fifo_or_dll(config.clk.div_lsb() as u32);
regs.write_ier_or_dlm(config.clk.div_msb() as u32);
// Configure all other settings and reset the div acess latch. This is important
// for accessing IER and the FIFO control register again.
regs.write_lcr(
LineControl::builder()
.with_div_access_latch(false)
.with_set_break(false)
.with_stick_parity(false)
.with_even_parity(config.parity == Parity::Even)
.with_parity_enable(config.parity != Parity::None)
.with_stop_bits(config.stop_bits)
.with_word_len(config.word_len)
.build(),
);
// Disable all interrupts.
regs.write_ier_or_dlm(InterruptEnable::new_with_raw_value(0x0).raw_value());
// Enable FIFO, configure 8 bytes FIFO trigger by default.
regs.write_iir_or_fcr(
FifoControl::builder()
.with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL)
.with_dma_mode_sel(false)
.with_reset_tx_fifo(true)
.with_reset_rx_fifo(true)
.with_fifo_enable(true)
.build()
.raw_value(),
);
Self {
rx: Rx::new(unsafe { regs.clone() }),
tx: Tx::new(regs),
config,
}
}
/// Raw register access.
#[inline(always)]
pub const fn regs(&mut self) -> &mut registers::MmioRegisters<'static> {
&mut self.rx.regs
}
/// UART configuration.
#[inline(always)]
pub const fn config(&mut self) -> &UartConfig {
&self.config
}
/// Write into the UART Lite.
///
/// Returns [nb::Error::WouldBlock] if the TX FIFO is full.
#[inline]
pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> {
self.tx.write_fifo(data)
}
/// Transmitter Holding Register empty status.
#[inline(always)]
pub fn thr_empty(&self) -> bool {
self.tx.thr_empty()
}
/// Transmitter empty status.
#[inline(always)]
pub fn tx_empty(&self) -> bool {
self.tx.tx_empty()
}
/// Receiver has data.
#[inline(always)]
pub fn rx_has_data(&self) -> bool {
self.rx.has_data()
}
/// Write into the FIFO without checking the FIFO fill status.
///
/// This can be useful to completely fill the FIFO if it is known to be empty.
#[inline(always)]
pub fn write_fifo_unchecked(&mut self, data: u8) {
self.tx.write_fifo_unchecked(data);
}
/// Read the RX FIFO.
///
/// This functions offers a [nb::Result] based API and returns [nb::Error::WouldBlock] if there
/// is nothing to read.
#[inline]
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
self.rx.read_fifo()
}
/// Read from the FIFO without checking the FIFO fill status.
#[inline(always)]
pub fn read_fifo_unchecked(&mut self) -> u8 {
self.rx.read_fifo_unchecked()
}
/// Enable interrupts according to the given interrupt enable configuration.
#[inline(always)]
pub fn enable_interrupts(&mut self, ier: InterruptEnable) {
self.regs().write_ier_or_dlm(ier.raw_value());
}
/// Split into TX and RX halves.
pub fn split(self) -> (Tx, Rx) {
(self.tx, self.rx)
}
}
impl embedded_hal_nb::serial::ErrorType for AxiUart16550 {
type Error = Infallible;
}
impl embedded_hal_nb::serial::Write for AxiUart16550 {
#[inline]
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
self.tx.write(word)
}
#[inline]
fn flush(&mut self) -> nb::Result<(), Self::Error> {
self.tx.flush()
}
}
impl embedded_hal_nb::serial::Read for AxiUart16550 {
#[inline]
fn read(&mut self) -> nb::Result<u8, Self::Error> {
self.rx.read()
}
}
impl embedded_io::ErrorType for AxiUart16550 {
type Error = Infallible;
}
impl embedded_io::Read for AxiUart16550 {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.rx.read(buf)
}
}
impl embedded_io::Write for AxiUart16550 {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.tx.write(buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.tx.flush()
}
}
#[cfg(test)]
mod tests {
use crate::ClockConfigError;
//extern crate std;
use super::{DivisorZeroError, calculate_error_rate_from_div};
use super::ClockConfig;
use approx::abs_diff_eq;
use fugit::RateExtU32;
#[test]
fn test_clk_calc_example_0() {
let clk_cfg = ClockConfig::new_autocalc(100.MHz(), 56000).unwrap();
// For some reason, the Xilinx example rounds up here..
assert_eq!(clk_cfg.div, 0x0070);
assert_eq!(clk_cfg.div_msb(), 0x00);
assert_eq!(clk_cfg.div_lsb(), 0x70);
let error = clk_cfg.calculate_error_rate(100.MHz(), 56000).unwrap();
assert!(abs_diff_eq!(error, 0.0035, epsilon = 0.001));
let (clk_cfg_checked, error_checked) =
ClockConfig::new_autocalc_with_error(100.MHz(), 56000).unwrap();
assert_eq!(clk_cfg, clk_cfg_checked);
assert!(abs_diff_eq!(error, error_checked, epsilon = 0.001));
let error_calc = calculate_error_rate_from_div(100.MHz(), 56000, clk_cfg.div).unwrap();
assert!(abs_diff_eq!(error, error_calc, epsilon = 0.001));
}
#[test]
fn test_clk_calc_example_1() {
let clk_cfg = ClockConfig::new_autocalc(1843200.Hz(), 56000).unwrap();
assert_eq!(clk_cfg.div, 0x0002);
assert_eq!(clk_cfg.div_msb(), 0x00);
assert_eq!(clk_cfg.div_lsb(), 0x02);
}
#[test]
fn test_invalid_baud() {
let clk_cfg = ClockConfig::new_autocalc_with_error(100.MHz(), 0);
assert_eq!(
clk_cfg,
Err(ClockConfigError::DivisorZero(DivisorZeroError))
);
}
#[test]
fn test_invalid_div() {
let error = calculate_error_rate_from_div(100.MHz(), 115200, 0);
assert_eq!(error.unwrap_err(), DivisorZeroError);
let error = calculate_error_rate_from_div(100.MHz(), 0, 0);
assert_eq!(error.unwrap_err(), DivisorZeroError);
let error = calculate_error_rate_from_div(100.MHz(), 0, 16);
assert_eq!(error.unwrap_err(), DivisorZeroError);
}
}