prep v0.1.0
Some checks failed
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

This commit is contained in:
Robin Mueller
2025-11-28 11:43:07 +01:00
parent e09c42587a
commit df99db3895
10 changed files with 368 additions and 72 deletions

65
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: ci
on: [push, pull_request]
jobs:
check:
name: Check build
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo check
msrv:
name: Check MSRV
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.85.1
- run: cargo build
cross-check:
name: Check Cross-Compilation
runs-on: ubuntu-latest
strategy:
matrix:
target:
- armv7-unknown-linux-gnueabihf
- armv7a-none-eabi
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: "armv7-unknown-linux-gnueabihf, armv7a-none-eabi"
- run: cargo build --target=${{matrix.target}}
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
docs:
name: Check Documentation Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --no-deps
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy -- -D warnings

16
CHANGELOG.md Normal file
View File

@@ -0,0 +1,16 @@
Change Log
=======
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.0] 2025-11-28
Initial release.
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/axi-uart16550/compare/v0.1.1...HEAD
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/axi-uart16550/tag/v0.1.0

View File

@@ -1,12 +1,17 @@
[package] [package]
name = "axi-uart16550" name = "axi-uart16550"
version = "0.1.0" version = "0.1.0"
description = "AXI UART16550 IP core driver"
author = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
rust-version = "1.85.1"
edition = "2024" edition = "2024"
homepage = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550"
repository = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
derive-mmio = "0.6" derive-mmio = "0.6"
bitbybit = "1.3" bitbybit = "1.4"
arbitrary-int = "2" arbitrary-int = "2"
nb = "1" nb = "1"
libm = "0.2" libm = "0.2"

28
README.md Normal file
View File

@@ -0,0 +1,28 @@
[![Crates.io](https://img.shields.io/crates/v/axi-uart16550)](https://crates.io/crates/axi-uart16550)
[![docs.rs](https://img.shields.io/docsrs/axi-uart16550)](https://docs.rs/axi-uart16550)
[![ci](https://github.com/us-irs/axi-uartlite/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/us-irs/axi-uartlite/actions/workflows/ci.yml)
AXI UART16550 driver
========
This is a native Rust driver for the
[AMD AXI UART16550 IP core](https://www.amd.com/de/products/adaptive-socs-and-fpgas/intellectual-property/axi_uart16550.html).
# Core features
- Basic driver which can be created with a given IP core base address and supports a basic
byte-level read and write API.
- Support for [`embedded-io`](https://docs.rs/embedded-io/latest/embedded_io/) and
[`embedded-io-async`](https://docs.rs/embedded-io-async/latest/embedded_io_async/)
# Features
If the asynchronous support for the TX side is used, the number of statically provided wakers
can be configured using the following features:
- `1-waker` which is the default
- `2-wakers`
- `4-wakers`
- `8-wakers`
- `16-wakers`
- `32-wakers`

35
justfile Normal file
View File

@@ -0,0 +1,35 @@
all: check build embedded clippy check-fmt docs
clippy:
cargo clippy -- -D warnings
fmt:
cargo fmt --all
check-fmt:
cargo fmt --all -- --check
check:
cargo check
embedded:
cargo build --target armv7a-none-eabi
test:
cargo nextest r
cargo test --doc
build:
cargo build
docs:
RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --no-deps
docs-html:
RUSTDOCFLAGS="--cfg docsrs -Z unstable-options --generate-link-to-definition" cargo +nightly doc --open --no-deps
coverage:
cargo llvm-cov nextest
coverage-html:
cargo llvm-cov nextest --html --open

View File

@@ -1,8 +1,26 @@
//! # 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] #![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
use core::convert::Infallible; use core::convert::Infallible;
use registers::{Fcr, Ier, Lcr, RxFifoTrigger, StopBits, WordLen}; use registers::{FifoControl, InterruptEnable, LineControl, RxFifoTrigger, StopBits, WordLen};
pub mod registers; pub mod registers;
pub mod tx; pub mod tx;
@@ -14,14 +32,20 @@ pub use tx_async::*;
pub mod rx; pub mod rx;
pub use rx::*; pub use rx::*;
/// Maximum FIFO depth of the AXI UART16550.
pub const FIFO_DEPTH: usize = 16; pub const FIFO_DEPTH: usize = 16;
/// Default RX FIFO trigger level.
pub const DEFAULT_RX_TRIGGER_LEVEL: RxFifoTrigger = RxFifoTrigger::EightBytes; pub const DEFAULT_RX_TRIGGER_LEVEL: RxFifoTrigger = RxFifoTrigger::EightBytes;
/// Clock configuration structure.
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ClkConfig { pub struct ClockConfig {
/// Divisor value.
pub div: u16, pub div: u16,
} }
/// Divisor is zero error.
#[derive(Debug, thiserror::Error, PartialEq, Eq)] #[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("divisor is zero")] #[error("divisor is zero")]
pub struct DivisorZeroError; pub struct DivisorZeroError;
@@ -45,21 +69,26 @@ pub fn calculate_error_rate_from_div(
/// used clock is too large, or the baudrate is too slow for the used clock frequency. /// used clock is too large, or the baudrate is too slow for the used clock frequency.
#[derive(Debug, thiserror::Error, PartialEq, Eq)] #[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("divisor too large")] #[error("divisor too large")]
pub enum ClkConfigError { pub enum ClockConfigError {
/// Divisor too large error.
DivisorTooLargeError(u32), DivisorTooLargeError(u32),
/// Divisor is zero error.
DivisorZero(#[from] DivisorZeroError), DivisorZero(#[from] DivisorZeroError),
} }
impl ClkConfig { impl ClockConfig {
/// New clock config with the given divisor.
pub fn new(div: u16) -> Self { pub fn new(div: u16) -> Self {
Self { div } Self { div }
} }
/// MSB part of the divisor.
#[inline(always)] #[inline(always)]
pub fn div_msb(&self) -> u8 { pub fn div_msb(&self) -> u8 {
(self.div >> 8) as u8 (self.div >> 8) as u8
} }
/// LSB part of the divisor.
#[inline(always)] #[inline(always)]
pub fn div_lsb(&self) -> u8 { pub fn div_lsb(&self) -> u8 {
self.div as u8 self.div as u8
@@ -71,7 +100,7 @@ impl ClkConfig {
pub fn new_autocalc_with_error( pub fn new_autocalc_with_error(
clk_in: fugit::HertzU32, clk_in: fugit::HertzU32,
baudrate: u32, baudrate: u32,
) -> Result<(Self, f32), ClkConfigError> { ) -> Result<(Self, f32), ClockConfigError> {
let cfg = Self::new_autocalc(clk_in, baudrate)?; let cfg = Self::new_autocalc(clk_in, baudrate)?;
Ok((cfg, cfg.calculate_error_rate(clk_in, baudrate)?)) Ok((cfg, cfg.calculate_error_rate(clk_in, baudrate)?))
} }
@@ -82,10 +111,10 @@ impl ClkConfig {
/// to check the error rate, or use the [Self::new_autocalc_with_error] function to get both /// to check the error rate, or use the [Self::new_autocalc_with_error] function to get both
/// the clock config and its baud error. /// the clock config and its baud error.
#[inline] #[inline]
pub fn new_autocalc(clk_in: fugit::HertzU32, baudrate: u32) -> Result<Self, ClkConfigError> { pub fn new_autocalc(clk_in: fugit::HertzU32, baudrate: u32) -> Result<Self, ClockConfigError> {
let div = Self::calc_div_with_integer_div(clk_in, baudrate)?; let div = Self::calc_div_with_integer_div(clk_in, baudrate)?;
if div > u16::MAX as u32 { if div > u16::MAX as u32 {
return Err(ClkConfigError::DivisorTooLargeError(div)); return Err(ClockConfigError::DivisorTooLargeError(div));
} }
Ok(Self { div: div as u16 }) Ok(Self { div: div as u16 })
} }
@@ -101,6 +130,7 @@ impl ClkConfig {
calculate_error_rate_from_div(clk_in, baudrate, self.div) calculate_error_rate_from_div(clk_in, baudrate, self.div)
} }
/// Calculate the divisor from an input clock for a give target baudrate.
#[inline(always)] #[inline(always)]
pub const fn calc_div_with_integer_div( pub const fn calc_div_with_integer_div(
clk_in: fugit::HertzU32, clk_in: fugit::HertzU32,
@@ -114,30 +144,37 @@ impl ClkConfig {
} }
} }
/// Parity configuration.
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum Parity { pub enum Parity {
/// No parity (default).
#[default] #[default]
None, None,
/// Odd parity.
Odd, Odd,
/// Even parity.
Even, Even,
} }
/// AXI UART16550 peripheral driver.
pub struct AxiUart16550 { pub struct AxiUart16550 {
rx: Rx, rx: Rx,
tx: Tx, tx: Tx,
config: UartConfig, config: UartConfig,
} }
/// UART configuration structure.
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct UartConfig { pub struct UartConfig {
clk: ClkConfig, clk: ClockConfig,
word_len: WordLen, word_len: WordLen,
parity: Parity, parity: Parity,
stop_bits: StopBits, stop_bits: StopBits,
} }
impl UartConfig { impl UartConfig {
pub const fn new_with_clk_config(clk: ClkConfig) -> Self { /// New with the given clock configuration.
pub const fn new_with_clk_config(clk: ClockConfig) -> Self {
Self { Self {
clk, clk,
word_len: WordLen::Eight, word_len: WordLen::Eight,
@@ -146,8 +183,9 @@ impl UartConfig {
} }
} }
/// New with all parameters.
pub const fn new( pub const fn new(
clk: ClkConfig, clk: ClockConfig,
word_len: WordLen, word_len: WordLen,
parity: Parity, parity: Parity,
stop_bits: StopBits, stop_bits: StopBits,
@@ -175,15 +213,15 @@ impl AxiUart16550 {
/// with the same `base_addr` can lead to unintended behavior if not externally synchronized. /// 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. /// - The driver performs **volatile** reads and writes to the provided address.
pub unsafe fn new(base_addr: u32, config: UartConfig) -> Self { pub unsafe fn new(base_addr: u32, config: UartConfig) -> Self {
let mut regs = unsafe { registers::AxiUart16550::new_mmio_at(base_addr as usize) }; let mut regs = unsafe { registers::Registers::new_mmio_at(base_addr as usize) };
// This unlocks the divisor config registers. // This unlocks the divisor config registers.
regs.write_lcr(Lcr::new_for_divisor_access()); regs.write_lcr(LineControl::new_for_divisor_access());
regs.write_fifo_or_dll(config.clk.div_lsb() as u32); regs.write_fifo_or_dll(config.clk.div_lsb() as u32);
regs.write_ier_or_dlm(config.clk.div_msb() 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 // Configure all other settings and reset the div acess latch. This is important
// for accessing IER and the FIFO control register again. // for accessing IER and the FIFO control register again.
regs.write_lcr( regs.write_lcr(
Lcr::builder() LineControl::builder()
.with_div_access_latch(false) .with_div_access_latch(false)
.with_set_break(false) .with_set_break(false)
.with_stick_parity(false) .with_stick_parity(false)
@@ -194,10 +232,10 @@ impl AxiUart16550 {
.build(), .build(),
); );
// Disable all interrupts. // Disable all interrupts.
regs.write_ier_or_dlm(Ier::new_with_raw_value(0x0).raw_value()); regs.write_ier_or_dlm(InterruptEnable::new_with_raw_value(0x0).raw_value());
// Enable FIFO, configure 8 bytes FIFO trigger by default. // Enable FIFO, configure 8 bytes FIFO trigger by default.
regs.write_iir_or_fcr( regs.write_iir_or_fcr(
Fcr::builder() FifoControl::builder()
.with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL)
.with_dma_mode_sel(false) .with_dma_mode_sel(false)
.with_reset_tx_fifo(true) .with_reset_tx_fifo(true)
@@ -213,11 +251,13 @@ impl AxiUart16550 {
} }
} }
/// Raw register access.
#[inline(always)] #[inline(always)]
pub const fn regs(&mut self) -> &mut registers::MmioAxiUart16550<'static> { pub const fn regs(&mut self) -> &mut registers::MmioRegisters<'static> {
&mut self.rx.regs &mut self.rx.regs
} }
/// UART configuration.
#[inline(always)] #[inline(always)]
pub const fn config(&mut self) -> &UartConfig { pub const fn config(&mut self) -> &UartConfig {
&self.config &self.config
@@ -231,19 +271,21 @@ impl AxiUart16550 {
self.tx.write_fifo(data) self.tx.write_fifo(data)
} }
// TODO: Make this non-mut as soon as pure reads are available. /// Transmitter Holding Register empty status.
#[inline(always)] #[inline(always)]
pub fn thr_empty(&mut self) -> bool { pub fn thr_empty(&self) -> bool {
self.tx.thr_empty() self.tx.thr_empty()
} }
/// Transmitter empty status.
#[inline(always)] #[inline(always)]
pub fn tx_empty(&mut self) -> bool { pub fn tx_empty(&self) -> bool {
self.tx.tx_empty() self.tx.tx_empty()
} }
/// Receiver has data.
#[inline(always)] #[inline(always)]
pub fn rx_has_data(&mut self) -> bool { pub fn rx_has_data(&self) -> bool {
self.rx.has_data() self.rx.has_data()
} }
@@ -255,21 +297,28 @@ impl AxiUart16550 {
self.tx.write_fifo_unchecked(data); 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] #[inline]
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> { pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
self.rx.read_fifo() self.rx.read_fifo()
} }
/// Read from the FIFO without checking the FIFO fill status.
#[inline(always)] #[inline(always)]
pub fn read_fifo_unchecked(&mut self) -> u8 { pub fn read_fifo_unchecked(&mut self) -> u8 {
self.rx.read_fifo_unchecked() self.rx.read_fifo_unchecked()
} }
/// Enable interrupts according to the given interrupt enable configuration.
#[inline(always)] #[inline(always)]
pub fn enable_interrupts(&mut self, ier: Ier) { pub fn enable_interrupts(&mut self, ier: InterruptEnable) {
self.regs().write_ier_or_dlm(ier.raw_value()); self.regs().write_ier_or_dlm(ier.raw_value());
} }
/// Split into TX and RX halves.
pub fn split(self) -> (Tx, Rx) { pub fn split(self) -> (Tx, Rx) {
(self.tx, self.rx) (self.tx, self.rx)
} }
@@ -320,18 +369,18 @@ impl embedded_io::Write for AxiUart16550 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::ClkConfigError; use crate::ClockConfigError;
//extern crate std; //extern crate std;
use super::{DivisorZeroError, calculate_error_rate_from_div}; use super::{DivisorZeroError, calculate_error_rate_from_div};
use super::ClkConfig; use super::ClockConfig;
use approx::abs_diff_eq; use approx::abs_diff_eq;
use fugit::RateExtU32; use fugit::RateExtU32;
#[test] #[test]
fn test_clk_calc_example_0() { fn test_clk_calc_example_0() {
let clk_cfg = ClkConfig::new_autocalc(100.MHz(), 56000).unwrap(); let clk_cfg = ClockConfig::new_autocalc(100.MHz(), 56000).unwrap();
// For some reason, the Xilinx example rounds up here.. // For some reason, the Xilinx example rounds up here..
assert_eq!(clk_cfg.div, 0x0070); assert_eq!(clk_cfg.div, 0x0070);
assert_eq!(clk_cfg.div_msb(), 0x00); assert_eq!(clk_cfg.div_msb(), 0x00);
@@ -339,7 +388,7 @@ mod tests {
let error = clk_cfg.calculate_error_rate(100.MHz(), 56000).unwrap(); let error = clk_cfg.calculate_error_rate(100.MHz(), 56000).unwrap();
assert!(abs_diff_eq!(error, 0.0035, epsilon = 0.001)); assert!(abs_diff_eq!(error, 0.0035, epsilon = 0.001));
let (clk_cfg_checked, error_checked) = let (clk_cfg_checked, error_checked) =
ClkConfig::new_autocalc_with_error(100.MHz(), 56000).unwrap(); ClockConfig::new_autocalc_with_error(100.MHz(), 56000).unwrap();
assert_eq!(clk_cfg, clk_cfg_checked); assert_eq!(clk_cfg, clk_cfg_checked);
assert!(abs_diff_eq!(error, error_checked, epsilon = 0.001)); 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(); let error_calc = calculate_error_rate_from_div(100.MHz(), 56000, clk_cfg.div).unwrap();
@@ -348,7 +397,7 @@ mod tests {
#[test] #[test]
fn test_clk_calc_example_1() { fn test_clk_calc_example_1() {
let clk_cfg = ClkConfig::new_autocalc(1843200.Hz(), 56000).unwrap(); let clk_cfg = ClockConfig::new_autocalc(1843200.Hz(), 56000).unwrap();
assert_eq!(clk_cfg.div, 0x0002); assert_eq!(clk_cfg.div, 0x0002);
assert_eq!(clk_cfg.div_msb(), 0x00); assert_eq!(clk_cfg.div_msb(), 0x00);
assert_eq!(clk_cfg.div_lsb(), 0x02); assert_eq!(clk_cfg.div_lsb(), 0x02);
@@ -356,8 +405,11 @@ mod tests {
#[test] #[test]
fn test_invalid_baud() { fn test_invalid_baud() {
let clk_cfg = ClkConfig::new_autocalc_with_error(100.MHz(), 0); let clk_cfg = ClockConfig::new_autocalc_with_error(100.MHz(), 0);
assert_eq!(clk_cfg, Err(ClkConfigError::DivisorZero(DivisorZeroError))); assert_eq!(
clk_cfg,
Err(ClockConfigError::DivisorZero(DivisorZeroError))
);
} }
#[test] #[test]

View File

@@ -1,14 +1,17 @@
//! # Raw register module
use arbitrary_int::u2; use arbitrary_int::u2;
/// Transmitter Holding Register. /// Transmitter Holding Register.
#[bitbybit::bitfield(u32)] #[bitbybit::bitfield(u32)]
pub struct Fifo { pub struct Fifo {
/// Bytes to transmit or receive.
#[bits(0..=7, rw)] #[bits(0..=7, rw)]
data: u8, data: u8,
} }
/// Interrupt Enable Register.
#[bitbybit::bitfield(u32)] #[bitbybit::bitfield(u32)]
pub struct Ier { pub struct InterruptEnable {
/// Enable Modem Status Interrupt /// Enable Modem Status Interrupt
#[bit(3, rw)] #[bit(3, rw)]
modem_status: bool, modem_status: bool,
@@ -26,36 +29,48 @@ pub struct Ier {
/// Interrupt identification ID /// Interrupt identification ID
#[bitbybit::bitenum(u3, exhaustive = false)] #[bitbybit::bitenum(u3, exhaustive = false)]
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum IntId2 { pub enum InterruptId2 {
/// Receiver Line Status.
ReceiverLineStatus = 0b011, ReceiverLineStatus = 0b011,
/// RX data available.
RxDataAvailable = 0b010, RxDataAvailable = 0b010,
/// Character timeout.
CharTimeout = 0b110, CharTimeout = 0b110,
/// THR empty.
ThrEmpty = 0b001, ThrEmpty = 0b001,
/// Modem status.
ModemStatus = 0b000, ModemStatus = 0b000,
} }
/// Interrupt Identification Register /// Interrupt Identification Register
#[bitbybit::bitfield(u32)] #[bitbybit::bitfield(u32)]
pub struct Iir { pub struct InterruptIdentification {
/// 16550 mode enabled? /// 16550 mode enabled?
#[bits(6..=7, r)] #[bits(6..=7, r)]
fifo_enabled: u2, fifo_enabled: u2,
/// Interrupt ID2.
#[bits(1..=3, r)] #[bits(1..=3, r)]
int_id: Option<IntId2>, int_id: Option<InterruptId2>,
/// Interrupt Pending, active low. /// Interrupt Pending, active low.
#[bit(0, r)] #[bit(0, r)]
int_pend_n: bool, int_pend_n: bool,
} }
/// RX FIFO trigger level.
#[bitbybit::bitenum(u2, exhaustive = true)] #[bitbybit::bitenum(u2, exhaustive = true)]
pub enum RxFifoTrigger { pub enum RxFifoTrigger {
/// One byte.
OneByte = 0b00, OneByte = 0b00,
/// 4 bytes.
FourBytes = 0b01, FourBytes = 0b01,
/// 8 bytes.
EightBytes = 0b10, EightBytes = 0b10,
/// 14 bytes.
FourteenBytes = 0b11, FourteenBytes = 0b11,
} }
impl RxFifoTrigger { impl RxFifoTrigger {
/// Raw number instead of register value.
pub const fn as_num(self) -> u32 { pub const fn as_num(self) -> u32 {
match self { match self {
RxFifoTrigger::OneByte => 1, RxFifoTrigger::OneByte => 1,
@@ -68,59 +83,78 @@ impl RxFifoTrigger {
/// FIFO Control Register /// FIFO Control Register
#[bitbybit::bitfield(u32, default = 0x0)] #[bitbybit::bitfield(u32, default = 0x0)]
pub struct Fcr { pub struct FifoControl {
/// RX FIFO trigger level.
#[bits(4..=5, rw)] #[bits(4..=5, rw)]
rx_fifo_trigger: RxFifoTrigger, rx_fifo_trigger: RxFifoTrigger,
/// DMA mode select.
#[bit(3, rw)] #[bit(3, rw)]
dma_mode_sel: bool, dma_mode_sel: bool,
/// Reset TX FIFO.
#[bit(2, rw)] #[bit(2, rw)]
reset_tx_fifo: bool, reset_tx_fifo: bool,
/// Reset RX FIFO.
#[bit(1, rw)] #[bit(1, rw)]
reset_rx_fifo: bool, reset_rx_fifo: bool,
/// FIFO enable.
#[bit(0, rw)] #[bit(0, rw)]
fifo_enable: bool, fifo_enable: bool,
} }
/// Word length in bits.
#[bitbybit::bitenum(u2, exhaustive = true)] #[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
pub enum WordLen { pub enum WordLen {
/// 5 bits.
Five = 0b00, Five = 0b00,
/// 6 bits.
Six = 0b01, Six = 0b01,
/// 7 bits.
Seven = 0b10, Seven = 0b10,
/// 8 bits (default).
#[default] #[default]
Eight = 0b11, Eight = 0b11,
} }
/// Stop bits.
#[bitbybit::bitenum(u1, exhaustive = true)] #[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Default, Debug, PartialEq, Eq)] #[derive(Default, Debug, PartialEq, Eq)]
pub enum StopBits { pub enum StopBits {
/// One stop bit (default).
#[default] #[default]
One = 0b0, One = 0b0,
/// 1.5 for 5 bits/char, 2 otherwise. /// 1.5 for 5 bits/char, 2 otherwise.
OnePointFiveOrTwo = 0b1, OnePointFiveOrTwo = 0b1,
} }
/// Line control register /// Line control register.
#[bitbybit::bitfield(u32, default = 0x00)] #[bitbybit::bitfield(u32, default = 0x00)]
pub struct Lcr { pub struct LineControl {
/// Divisor Latch Access Bit.
#[bit(7, rw)] #[bit(7, rw)]
div_access_latch: bool, div_access_latch: bool,
/// Set break bit.
#[bit(6, rw)] #[bit(6, rw)]
set_break: bool, set_break: bool,
/// Parity stick bit.
#[bit(5, rw)] #[bit(5, rw)]
stick_parity: bool, stick_parity: bool,
/// Even parity.
#[bit(4, rw)] #[bit(4, rw)]
even_parity: bool, even_parity: bool,
/// Parity enable.
#[bit(3, rw)] #[bit(3, rw)]
parity_enable: bool, parity_enable: bool,
/// 0: 1 stop bit, 1: 2 stop bits or 1.5 if 5 bits/char selected /// 0: 1 stop bit, 1: 2 stop bits or 1.5 if 5 bits/char selected
#[bit(2, rw)] #[bit(2, rw)]
stop_bits: StopBits, stop_bits: StopBits,
/// Word length.
#[bits(0..=1, rw)] #[bits(0..=1, rw)]
word_len: WordLen, word_len: WordLen,
} }
impl Lcr { impl LineControl {
/// New line control value for accessing divisor latches.
pub fn new_for_divisor_access() -> Self { pub fn new_for_divisor_access() -> Self {
Self::new_with_raw_value(0x80) Self::new_with_raw_value(0x80)
} }
@@ -129,7 +163,8 @@ impl Lcr {
/// Line Status Register /// Line Status Register
#[bitbybit::bitfield(u32)] #[bitbybit::bitfield(u32)]
#[derive(Debug)] #[derive(Debug)]
pub struct Lsr { pub struct LineStatus {
/// Error in RX FIFO.
#[bit(7, rw)] #[bit(7, rw)]
error_in_rx_fifo: bool, error_in_rx_fifo: bool,
/// In the FIFO mode, this is set to 1 when the TX FIFO and shift register are both empty. /// In the FIFO mode, this is set to 1 when the TX FIFO and shift register are both empty.
@@ -139,21 +174,27 @@ pub struct Lsr {
/// in the TX shift register. /// in the TX shift register.
#[bit(5, rw)] #[bit(5, rw)]
thr_empty: bool, thr_empty: bool,
/// Break interrupt.
#[bit(4, rw)] #[bit(4, rw)]
break_interrupt: bool, break_interrupt: bool,
/// Framing error.
#[bit(3, rw)] #[bit(3, rw)]
framing_error: bool, framing_error: bool,
/// Parity error.
#[bit(2, rw)] #[bit(2, rw)]
parity_error: bool, parity_error: bool,
/// Overrun error.
#[bit(1, rw)] #[bit(1, rw)]
overrun_error: bool, overrun_error: bool,
/// Data ready.
#[bit(0, rw)] #[bit(0, rw)]
data_ready: bool, data_ready: bool,
} }
/// Raw register block.
#[derive(derive_mmio::Mmio)] #[derive(derive_mmio::Mmio)]
#[repr(C)] #[repr(C)]
pub struct AxiUart16550 { pub struct Registers {
_reserved: [u32; 0x400], _reserved: [u32; 0x400],
/// FIFO register for LCR[7] == 0 or Divisor Latch (LSB) register for LCR[7] == 1 /// FIFO register for LCR[7] == 0 or Divisor Latch (LSB) register for LCR[7] == 1
fifo_or_dll: u32, fifo_or_dll: u32,
@@ -165,11 +206,11 @@ pub struct AxiUart16550 {
/// write-only FIFO control register. /// write-only FIFO control register.
iir_or_fcr: u32, iir_or_fcr: u32,
/// Line Control Register /// Line Control Register
lcr: Lcr, lcr: LineControl,
/// Modem Control Register /// Modem Control Register
mcr: u32, mcr: u32,
/// Line Status Register /// Line Status Register
lsr: Lsr, lsr: LineStatus,
/// Modem Status Register /// Modem Status Register
msr: u32, msr: u32,
/// Scratch Register /// Scratch Register

View File

@@ -1,10 +1,14 @@
//! # Receiver (RX) support module
use core::convert::Infallible; use core::convert::Infallible;
use crate::{ use crate::{
DEFAULT_RX_TRIGGER_LEVEL, DEFAULT_RX_TRIGGER_LEVEL,
registers::{self, Fcr, Ier, Iir, IntId2, Lsr}, registers::{
self, FifoControl, InterruptEnable, InterruptId2, InterruptIdentification, LineStatus,
},
}; };
/// RX errors structure.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct RxErrors { pub struct RxErrors {
parity: bool, parity: bool,
@@ -13,6 +17,8 @@ pub struct RxErrors {
} }
impl RxErrors { impl RxErrors {
/// Construct a new empty [RxErrors] structure.
#[inline]
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
parity: false, parity: false,
@@ -21,26 +27,38 @@ impl RxErrors {
} }
} }
/// Parity error.
#[inline]
pub const fn parity(&self) -> bool { pub const fn parity(&self) -> bool {
self.parity self.parity
} }
/// Framing error.
#[inline]
pub const fn frame(&self) -> bool { pub const fn frame(&self) -> bool {
self.frame self.frame
} }
/// Overrun error.
#[inline]
pub const fn overrun(&self) -> bool { pub const fn overrun(&self) -> bool {
self.overrun self.overrun
} }
/// Error has occurred.
#[inline]
pub const fn has_errors(&self) -> bool { pub const fn has_errors(&self) -> bool {
self.parity || self.frame || self.overrun self.parity || self.frame || self.overrun
} }
} }
/// AXI UARTLITE RX driver.
///
/// Can be created by [super::AxiUart16550::split]ting a regular AXI UART16550 structure or
/// by [Self::steal]ing it unsafely.
pub struct Rx { pub struct Rx {
/// Internal MMIO register structure. /// Internal MMIO register structure.
pub(crate) regs: registers::MmioAxiUart16550<'static>, pub(crate) regs: registers::MmioRegisters<'static>,
pub(crate) errors: Option<RxErrors>, pub(crate) errors: Option<RxErrors>,
} }
@@ -60,15 +78,19 @@ impl Rx {
/// The same safey rules specified in [super::AxiUart16550::new] apply. /// The same safey rules specified in [super::AxiUart16550::new] apply.
pub const unsafe fn steal(base_addr: usize) -> Self { pub const unsafe fn steal(base_addr: usize) -> Self {
Self { Self {
regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, regs: unsafe { registers::Registers::new_mmio_at(base_addr) },
errors: None, errors: None,
} }
} }
pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { pub(crate) fn new(regs: registers::MmioRegisters<'static>) -> Self {
Self { regs, errors: None } Self { regs, errors: None }
} }
/// Read the RX FIFO.
///
/// This functions offers a [nb::Result] based API and returns [nb::Error::WouldBlock] if there
/// is nothing to read.
#[inline] #[inline]
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> { pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
let status_reg = self.regs.read_lsr(); let status_reg = self.regs.read_lsr();
@@ -76,11 +98,12 @@ impl Rx {
return Err(nb::Error::WouldBlock); return Err(nb::Error::WouldBlock);
} }
if status_reg.error_in_rx_fifo() { if status_reg.error_in_rx_fifo() {
self.errors = Some(Self::lsr_to_errors(status_reg)); self.errors = Some(Self::lsr_to_errors(&status_reg));
} }
Ok(self.read_fifo_unchecked()) Ok(self.read_fifo_unchecked())
} }
/// Read from the FIFO without checking the FIFO fill status.
#[inline(always)] #[inline(always)]
pub fn read_fifo_unchecked(&mut self) -> u8 { pub fn read_fifo_unchecked(&mut self) -> u8 {
self.regs.read_fifo_or_dll() as u8 self.regs.read_fifo_or_dll() as u8
@@ -99,30 +122,33 @@ impl Rx {
self.enable_interrupt(); self.enable_interrupt();
} }
/// Enable RX interrupts.
#[inline] #[inline]
pub fn enable_interrupt(&mut self) { pub fn enable_interrupt(&mut self) {
self.regs.modify_ier_or_dlm(|val| { self.regs.modify_ier_or_dlm(|val| {
let mut ier = Ier::new_with_raw_value(val); let mut ier = InterruptEnable::new_with_raw_value(val);
ier.set_rx_avl(true); ier.set_rx_avl(true);
ier.set_line_status(true); ier.set_line_status(true);
ier.raw_value() ier.raw_value()
}); });
} }
/// Disable RX interrupts.
#[inline] #[inline]
pub fn disable_interrupt(&mut self) { pub fn disable_interrupt(&mut self) {
self.regs.modify_ier_or_dlm(|val| { self.regs.modify_ier_or_dlm(|val| {
let mut ier = Ier::new_with_raw_value(val); let mut ier = InterruptEnable::new_with_raw_value(val);
ier.set_rx_avl(false); ier.set_rx_avl(false);
ier.set_line_status(false); ier.set_line_status(false);
ier.raw_value() ier.raw_value()
}); });
} }
/// Reset the RX FIFO.
#[inline] #[inline]
pub fn reset_fifo(&mut self) { pub fn reset_fifo(&mut self) {
self.regs.write_iir_or_fcr( self.regs.write_iir_or_fcr(
Fcr::builder() FifoControl::builder()
.with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL)
.with_dma_mode_sel(false) .with_dma_mode_sel(false)
.with_reset_tx_fifo(false) .with_reset_tx_fifo(false)
@@ -133,32 +159,38 @@ impl Rx {
); );
} }
/// Data is available.
#[inline(always)] #[inline(always)]
pub fn has_data(&mut self) -> bool { pub fn has_data(&self) -> bool {
self.regs.read_lsr().data_ready() self.regs.read_lsr().data_ready()
} }
/// Read the IIR register.
#[inline] #[inline]
pub fn read_iir(&mut self) -> Iir { pub fn read_iir(&mut self) -> InterruptIdentification {
Iir::new_with_raw_value(self.regs.read_iir_or_fcr()) InterruptIdentification::new_with_raw_value(self.regs.read_iir_or_fcr())
} }
/// Should be called when a Line Status interrupt occurs.
#[inline] #[inline]
pub fn on_interrupt_receiver_line_status(&mut self, _iir: Iir) -> RxErrors { pub fn on_interrupt_receiver_line_status(&mut self, _iir: InterruptIdentification) -> RxErrors {
let lsr = self.regs.read_lsr(); let lsr = self.regs.read_lsr();
Self::lsr_to_errors(lsr) Self::lsr_to_errors(&lsr)
} }
/// Should be called when a Data Available or Character Timeout interrupt occurs.
///
/// Reads all available data into the provided buffer and returns the number of bytes read.
#[inline] #[inline]
pub fn on_interrupt_data_available_or_char_timeout( pub fn on_interrupt_data_available_or_char_timeout(
&mut self, &mut self,
int_id2: IntId2, int_id2: InterruptId2,
buf: &mut [u8; 16], buf: &mut [u8; 16],
) -> usize { ) -> usize {
let mut read = 0; let mut read = 0;
// It is guaranteed that we can read the FIFO trigger level. // It is guaranteed that we can read the FIFO trigger level.
if int_id2 == IntId2::RxDataAvailable { if int_id2 == InterruptId2::RxDataAvailable {
let trigger_level = Fcr::new_with_raw_value(self.regs.read_iir_or_fcr()); let trigger_level = FifoControl::new_with_raw_value(self.regs.read_iir_or_fcr());
(0..trigger_level.rx_fifo_trigger().as_num() as usize).for_each(|i| { (0..trigger_level.rx_fifo_trigger().as_num() as usize).for_each(|i| {
buf[i] = self.read_fifo_unchecked(); buf[i] = self.read_fifo_unchecked();
read += 1; read += 1;
@@ -172,7 +204,8 @@ impl Rx {
read read
} }
pub fn lsr_to_errors(status_reg: Lsr) -> RxErrors { /// Extract RX errors from the LSR register.
pub fn lsr_to_errors(status_reg: &LineStatus) -> RxErrors {
let mut errors = RxErrors::new(); let mut errors = RxErrors::new();
if status_reg.framing_error() { if status_reg.framing_error() {
errors.frame = true; errors.frame = true;

View File

@@ -1,13 +1,18 @@
//! # Transmitter (TX) support module
use core::convert::Infallible; use core::convert::Infallible;
use crate::{ use crate::{
DEFAULT_RX_TRIGGER_LEVEL, DEFAULT_RX_TRIGGER_LEVEL,
registers::{self, Fcr, Ier}, registers::{self, FifoControl, InterruptEnable},
}; };
/// AXI UART16550 TX driver.
///
/// Can be created by [super::AxiUart16550::split]ting a regular AXI UARTLITE structure or
/// by [Self::steal]ing it unsafely.
pub struct Tx { pub struct Tx {
/// Internal MMIO register structure. /// Internal MMIO register structure.
pub(crate) regs: registers::MmioAxiUart16550<'static>, pub(crate) regs: registers::MmioRegisters<'static>,
} }
impl Tx { impl Tx {
@@ -26,14 +31,15 @@ impl Tx {
/// The same safey rules specified in [super::AxiUart16550::new] apply. /// The same safey rules specified in [super::AxiUart16550::new] apply.
pub const unsafe fn steal(base_addr: usize) -> Self { pub const unsafe fn steal(base_addr: usize) -> Self {
Self { Self {
regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, regs: unsafe { registers::Registers::new_mmio_at(base_addr) },
} }
} }
pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { pub(crate) fn new(regs: registers::MmioRegisters<'static>) -> Self {
Self { regs } Self { regs }
} }
/// Write a byte into the FIFO if there is space available.
#[inline] #[inline]
pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> { pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> {
if !self.thr_empty() { if !self.thr_empty() {
@@ -43,19 +49,21 @@ impl Tx {
Ok(()) Ok(())
} }
/// Enable TX interrupts.
#[inline] #[inline]
pub fn enable_interrupt(&mut self) { pub fn enable_interrupt(&mut self) {
self.regs.modify_ier_or_dlm(|val| { self.regs.modify_ier_or_dlm(|val| {
let mut ier = Ier::new_with_raw_value(val); let mut ier = InterruptEnable::new_with_raw_value(val);
ier.set_thr_empty(true); ier.set_thr_empty(true);
ier.raw_value() ier.raw_value()
}); });
} }
/// Disable TX interrupts.
#[inline] #[inline]
pub fn disable_interrupt(&mut self) { pub fn disable_interrupt(&mut self) {
self.regs.modify_ier_or_dlm(|val| { self.regs.modify_ier_or_dlm(|val| {
let mut ier = Ier::new_with_raw_value(val); let mut ier = InterruptEnable::new_with_raw_value(val);
ier.set_thr_empty(false); ier.set_thr_empty(false);
ier.raw_value() ier.raw_value()
}); });
@@ -69,21 +77,23 @@ impl Tx {
self.regs.write_fifo_or_dll(data as u32); self.regs.write_fifo_or_dll(data as u32);
} }
// TODO: Make this non-mut as soon as pure reads are available. /// Transmitter Holding Register empty status.
#[inline(always)] #[inline(always)]
pub fn thr_empty(&mut self) -> bool { pub fn thr_empty(&self) -> bool {
self.regs.read_lsr().thr_empty() self.regs.read_lsr().thr_empty()
} }
/// Transmitter empty status.
#[inline(always)] #[inline(always)]
pub fn tx_empty(&mut self) -> bool { pub fn tx_empty(&self) -> bool {
self.regs.read_lsr().tx_empty() self.regs.read_lsr().tx_empty()
} }
/// Reset the FIFOs.
#[inline] #[inline]
pub fn reset_fifo(&mut self) { pub fn reset_fifo(&mut self) {
self.regs.write_iir_or_fcr( self.regs.write_iir_or_fcr(
Fcr::builder() FifoControl::builder()
.with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL)
.with_dma_mode_sel(false) .with_dma_mode_sel(false)
.with_reset_tx_fifo(true) .with_reset_tx_fifo(true)
@@ -94,6 +104,7 @@ impl Tx {
); );
} }
/// Should be called from the interrupt handler when a THR empty interrupt occurs.
#[inline] #[inline]
pub fn on_interrupt_thr_empty(&mut self, next_write_chunk: &[u8]) -> usize { pub fn on_interrupt_thr_empty(&mut self, next_write_chunk: &[u8]) -> usize {
if next_write_chunk.is_empty() { if next_write_chunk.is_empty() {

View File

@@ -24,19 +24,25 @@ use raw_slice::RawBufSlice;
use crate::{ use crate::{
FIFO_DEPTH, Tx, FIFO_DEPTH, Tx,
registers::{self, Ier}, registers::{self, InterruptEnable},
}; };
/// 1 waker (default).
#[cfg(feature = "1-waker")] #[cfg(feature = "1-waker")]
pub const NUM_WAKERS: usize = 1; pub const NUM_WAKERS: usize = 1;
/// 2 wakers.
#[cfg(feature = "2-wakers")] #[cfg(feature = "2-wakers")]
pub const NUM_WAKERS: usize = 2; pub const NUM_WAKERS: usize = 2;
/// 4 wakers.
#[cfg(feature = "4-wakers")] #[cfg(feature = "4-wakers")]
pub const NUM_WAKERS: usize = 4; pub const NUM_WAKERS: usize = 4;
/// 8 wakers.
#[cfg(feature = "8-wakers")] #[cfg(feature = "8-wakers")]
pub const NUM_WAKERS: usize = 8; pub const NUM_WAKERS: usize = 8;
/// 16 wakers.
#[cfg(feature = "16-wakers")] #[cfg(feature = "16-wakers")]
pub const NUM_WAKERS: usize = 16; pub const NUM_WAKERS: usize = 16;
/// 32 wakers.
#[cfg(feature = "32-wakers")] #[cfg(feature = "32-wakers")]
pub const NUM_WAKERS: usize = 32; pub const NUM_WAKERS: usize = 32;
static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS]; static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
@@ -46,6 +52,7 @@ static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; NUM_WAKERS] =
// critical section. // critical section.
static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS]; static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS];
/// Invalid waker index error.
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("invalid waker slot index: {0}")] #[error("invalid waker slot index: {0}")]
pub struct InvalidWakerIndex(pub usize); pub struct InvalidWakerIndex(pub usize);
@@ -61,7 +68,7 @@ pub fn on_interrupt_tx(tx: &mut Tx, waker_slot: usize) {
return; return;
} }
let status = tx.regs.read_lsr(); let status = tx.regs.read_lsr();
let ier = Ier::new_with_raw_value(tx.regs.read_ier_or_dlm()); let ier = InterruptEnable::new_with_raw_value(tx.regs.read_ier_or_dlm());
// Interrupt are not even enabled. // Interrupt are not even enabled.
if !ier.thr_empty() { if !ier.thr_empty() {
return; return;
@@ -107,7 +114,7 @@ pub fn on_interrupt_tx(tx: &mut Tx, waker_slot: usize) {
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct TxContext { struct TxContext {
progress: usize, progress: usize,
slice: RawBufSlice, slice: RawBufSlice,
} }
@@ -122,9 +129,10 @@ impl TxContext {
} }
} }
/// TX future structure.
pub struct TxFuture { pub struct TxFuture {
waker_idx: usize, waker_idx: usize,
reg_block: registers::MmioAxiUart16550<'static>, reg_block: registers::MmioRegisters<'static>,
} }
impl TxFuture { impl TxFuture {
@@ -191,6 +199,7 @@ impl Drop for TxFuture {
} }
} }
/// Asynchronous TX driver.
pub struct TxAsync<D: DelayNs> { pub struct TxAsync<D: DelayNs> {
tx: Tx, tx: Tx,
waker_idx: usize, waker_idx: usize,
@@ -233,6 +242,7 @@ impl<D: DelayNs> TxAsync<D> {
} }
} }
/// Release the underlying TX handle.
pub fn release(self) -> Tx { pub fn release(self) -> Tx {
self.tx self.tx
} }