prep v0.1.0 #1
65
.github/workflows/ci.yml
vendored
Normal file
65
.github/workflows/ci.yml
vendored
Normal 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
16
CHANGELOG.md
Normal 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
|
||||
@@ -1,12 +1,17 @@
|
||||
[package]
|
||||
name = "axi-uart16550"
|
||||
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"
|
||||
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"
|
||||
|
||||
[dependencies]
|
||||
derive-mmio = "0.6"
|
||||
bitbybit = "1.3"
|
||||
bitbybit = "1.4"
|
||||
arbitrary-int = "2"
|
||||
nb = "1"
|
||||
libm = "0.2"
|
||||
|
||||
28
README.md
Normal file
28
README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
[](https://crates.io/crates/axi-uart16550)
|
||||
[](https://docs.rs/axi-uart16550)
|
||||
[](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
35
justfile
Normal 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
|
||||
108
src/lib.rs
108
src/lib.rs
@@ -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]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
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 tx;
|
||||
@@ -14,14 +32,20 @@ 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 ClkConfig {
|
||||
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;
|
||||
@@ -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.
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
#[error("divisor too large")]
|
||||
pub enum ClkConfigError {
|
||||
pub enum ClockConfigError {
|
||||
/// Divisor too large error.
|
||||
DivisorTooLargeError(u32),
|
||||
/// Divisor is zero error.
|
||||
DivisorZero(#[from] DivisorZeroError),
|
||||
}
|
||||
|
||||
impl ClkConfig {
|
||||
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
|
||||
@@ -71,7 +100,7 @@ impl ClkConfig {
|
||||
pub fn new_autocalc_with_error(
|
||||
clk_in: fugit::HertzU32,
|
||||
baudrate: u32,
|
||||
) -> Result<(Self, f32), ClkConfigError> {
|
||||
) -> Result<(Self, f32), ClockConfigError> {
|
||||
let cfg = Self::new_autocalc(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
|
||||
/// the clock config and its baud error.
|
||||
#[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)?;
|
||||
if div > u16::MAX as u32 {
|
||||
return Err(ClkConfigError::DivisorTooLargeError(div));
|
||||
return Err(ClockConfigError::DivisorTooLargeError(div));
|
||||
}
|
||||
Ok(Self { div: div as u16 })
|
||||
}
|
||||
@@ -101,6 +130,7 @@ impl ClkConfig {
|
||||
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,
|
||||
@@ -114,30 +144,37 @@ impl ClkConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: ClkConfig,
|
||||
clk: ClockConfig,
|
||||
word_len: WordLen,
|
||||
parity: Parity,
|
||||
stop_bits: StopBits,
|
||||
}
|
||||
|
||||
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 {
|
||||
clk,
|
||||
word_len: WordLen::Eight,
|
||||
@@ -146,8 +183,9 @@ impl UartConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// New with all parameters.
|
||||
pub const fn new(
|
||||
clk: ClkConfig,
|
||||
clk: ClockConfig,
|
||||
word_len: WordLen,
|
||||
parity: Parity,
|
||||
stop_bits: StopBits,
|
||||
@@ -175,15 +213,15 @@ impl AxiUart16550 {
|
||||
/// 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::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.
|
||||
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_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(
|
||||
Lcr::builder()
|
||||
LineControl::builder()
|
||||
.with_div_access_latch(false)
|
||||
.with_set_break(false)
|
||||
.with_stick_parity(false)
|
||||
@@ -194,10 +232,10 @@ impl AxiUart16550 {
|
||||
.build(),
|
||||
);
|
||||
// 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.
|
||||
regs.write_iir_or_fcr(
|
||||
Fcr::builder()
|
||||
FifoControl::builder()
|
||||
.with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL)
|
||||
.with_dma_mode_sel(false)
|
||||
.with_reset_tx_fifo(true)
|
||||
@@ -213,11 +251,13 @@ impl AxiUart16550 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw register access.
|
||||
#[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
|
||||
}
|
||||
|
||||
/// UART configuration.
|
||||
#[inline(always)]
|
||||
pub const fn config(&mut self) -> &UartConfig {
|
||||
&self.config
|
||||
@@ -231,19 +271,21 @@ impl AxiUart16550 {
|
||||
self.tx.write_fifo(data)
|
||||
}
|
||||
|
||||
// TODO: Make this non-mut as soon as pure reads are available.
|
||||
/// Transmitter Holding Register empty status.
|
||||
#[inline(always)]
|
||||
pub fn thr_empty(&mut self) -> bool {
|
||||
pub fn thr_empty(&self) -> bool {
|
||||
self.tx.thr_empty()
|
||||
}
|
||||
|
||||
/// Transmitter empty status.
|
||||
#[inline(always)]
|
||||
pub fn tx_empty(&mut self) -> bool {
|
||||
pub fn tx_empty(&self) -> bool {
|
||||
self.tx.tx_empty()
|
||||
}
|
||||
|
||||
/// Receiver has data.
|
||||
#[inline(always)]
|
||||
pub fn rx_has_data(&mut self) -> bool {
|
||||
pub fn rx_has_data(&self) -> bool {
|
||||
self.rx.has_data()
|
||||
}
|
||||
|
||||
@@ -255,21 +297,28 @@ impl AxiUart16550 {
|
||||
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: Ier) {
|
||||
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)
|
||||
}
|
||||
@@ -320,18 +369,18 @@ impl embedded_io::Write for AxiUart16550 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ClkConfigError;
|
||||
use crate::ClockConfigError;
|
||||
|
||||
//extern crate std;
|
||||
use super::{DivisorZeroError, calculate_error_rate_from_div};
|
||||
|
||||
use super::ClkConfig;
|
||||
use super::ClockConfig;
|
||||
use approx::abs_diff_eq;
|
||||
use fugit::RateExtU32;
|
||||
|
||||
#[test]
|
||||
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..
|
||||
assert_eq!(clk_cfg.div, 0x0070);
|
||||
assert_eq!(clk_cfg.div_msb(), 0x00);
|
||||
@@ -339,7 +388,7 @@ mod tests {
|
||||
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) =
|
||||
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!(abs_diff_eq!(error, error_checked, epsilon = 0.001));
|
||||
let error_calc = calculate_error_rate_from_div(100.MHz(), 56000, clk_cfg.div).unwrap();
|
||||
@@ -348,7 +397,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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_msb(), 0x00);
|
||||
assert_eq!(clk_cfg.div_lsb(), 0x02);
|
||||
@@ -356,8 +405,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_invalid_baud() {
|
||||
let clk_cfg = ClkConfig::new_autocalc_with_error(100.MHz(), 0);
|
||||
assert_eq!(clk_cfg, Err(ClkConfigError::DivisorZero(DivisorZeroError)));
|
||||
let clk_cfg = ClockConfig::new_autocalc_with_error(100.MHz(), 0);
|
||||
assert_eq!(
|
||||
clk_cfg,
|
||||
Err(ClockConfigError::DivisorZero(DivisorZeroError))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
//! # Raw register module
|
||||
use arbitrary_int::u2;
|
||||
|
||||
/// Transmitter Holding Register.
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Fifo {
|
||||
/// Bytes to transmit or receive.
|
||||
#[bits(0..=7, rw)]
|
||||
data: u8,
|
||||
}
|
||||
|
||||
/// Interrupt Enable Register.
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Ier {
|
||||
pub struct InterruptEnable {
|
||||
/// Enable Modem Status Interrupt
|
||||
#[bit(3, rw)]
|
||||
modem_status: bool,
|
||||
@@ -26,36 +29,48 @@ pub struct Ier {
|
||||
/// Interrupt identification ID
|
||||
#[bitbybit::bitenum(u3, exhaustive = false)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum IntId2 {
|
||||
pub enum InterruptId2 {
|
||||
/// Receiver Line Status.
|
||||
ReceiverLineStatus = 0b011,
|
||||
/// RX data available.
|
||||
RxDataAvailable = 0b010,
|
||||
/// Character timeout.
|
||||
CharTimeout = 0b110,
|
||||
/// THR empty.
|
||||
ThrEmpty = 0b001,
|
||||
/// Modem status.
|
||||
ModemStatus = 0b000,
|
||||
}
|
||||
|
||||
/// Interrupt Identification Register
|
||||
#[bitbybit::bitfield(u32)]
|
||||
pub struct Iir {
|
||||
pub struct InterruptIdentification {
|
||||
/// 16550 mode enabled?
|
||||
#[bits(6..=7, r)]
|
||||
fifo_enabled: u2,
|
||||
/// Interrupt ID2.
|
||||
#[bits(1..=3, r)]
|
||||
int_id: Option<IntId2>,
|
||||
int_id: Option<InterruptId2>,
|
||||
/// Interrupt Pending, active low.
|
||||
#[bit(0, r)]
|
||||
int_pend_n: bool,
|
||||
}
|
||||
|
||||
/// RX FIFO trigger level.
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
pub enum RxFifoTrigger {
|
||||
/// One byte.
|
||||
OneByte = 0b00,
|
||||
/// 4 bytes.
|
||||
FourBytes = 0b01,
|
||||
/// 8 bytes.
|
||||
EightBytes = 0b10,
|
||||
/// 14 bytes.
|
||||
FourteenBytes = 0b11,
|
||||
}
|
||||
|
||||
impl RxFifoTrigger {
|
||||
/// Raw number instead of register value.
|
||||
pub const fn as_num(self) -> u32 {
|
||||
match self {
|
||||
RxFifoTrigger::OneByte => 1,
|
||||
@@ -68,59 +83,78 @@ impl RxFifoTrigger {
|
||||
|
||||
/// FIFO Control Register
|
||||
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||
pub struct Fcr {
|
||||
pub struct FifoControl {
|
||||
/// RX FIFO trigger level.
|
||||
#[bits(4..=5, rw)]
|
||||
rx_fifo_trigger: RxFifoTrigger,
|
||||
/// DMA mode select.
|
||||
#[bit(3, rw)]
|
||||
dma_mode_sel: bool,
|
||||
/// Reset TX FIFO.
|
||||
#[bit(2, rw)]
|
||||
reset_tx_fifo: bool,
|
||||
/// Reset RX FIFO.
|
||||
#[bit(1, rw)]
|
||||
reset_rx_fifo: bool,
|
||||
/// FIFO enable.
|
||||
#[bit(0, rw)]
|
||||
fifo_enable: bool,
|
||||
}
|
||||
|
||||
/// Word length in bits.
|
||||
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub enum WordLen {
|
||||
/// 5 bits.
|
||||
Five = 0b00,
|
||||
/// 6 bits.
|
||||
Six = 0b01,
|
||||
/// 7 bits.
|
||||
Seven = 0b10,
|
||||
/// 8 bits (default).
|
||||
#[default]
|
||||
Eight = 0b11,
|
||||
}
|
||||
|
||||
/// Stop bits.
|
||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub enum StopBits {
|
||||
/// One stop bit (default).
|
||||
#[default]
|
||||
One = 0b0,
|
||||
/// 1.5 for 5 bits/char, 2 otherwise.
|
||||
OnePointFiveOrTwo = 0b1,
|
||||
}
|
||||
|
||||
/// Line control register
|
||||
/// Line control register.
|
||||
#[bitbybit::bitfield(u32, default = 0x00)]
|
||||
pub struct Lcr {
|
||||
pub struct LineControl {
|
||||
/// Divisor Latch Access Bit.
|
||||
#[bit(7, rw)]
|
||||
div_access_latch: bool,
|
||||
/// Set break bit.
|
||||
#[bit(6, rw)]
|
||||
set_break: bool,
|
||||
/// Parity stick bit.
|
||||
#[bit(5, rw)]
|
||||
stick_parity: bool,
|
||||
/// Even parity.
|
||||
#[bit(4, rw)]
|
||||
even_parity: bool,
|
||||
/// Parity enable.
|
||||
#[bit(3, rw)]
|
||||
parity_enable: bool,
|
||||
/// 0: 1 stop bit, 1: 2 stop bits or 1.5 if 5 bits/char selected
|
||||
#[bit(2, rw)]
|
||||
stop_bits: StopBits,
|
||||
/// Word length.
|
||||
#[bits(0..=1, rw)]
|
||||
word_len: WordLen,
|
||||
}
|
||||
|
||||
impl Lcr {
|
||||
impl LineControl {
|
||||
/// New line control value for accessing divisor latches.
|
||||
pub fn new_for_divisor_access() -> Self {
|
||||
Self::new_with_raw_value(0x80)
|
||||
}
|
||||
@@ -129,7 +163,8 @@ impl Lcr {
|
||||
/// Line Status Register
|
||||
#[bitbybit::bitfield(u32)]
|
||||
#[derive(Debug)]
|
||||
pub struct Lsr {
|
||||
pub struct LineStatus {
|
||||
/// Error in RX FIFO.
|
||||
#[bit(7, rw)]
|
||||
error_in_rx_fifo: bool,
|
||||
/// 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.
|
||||
#[bit(5, rw)]
|
||||
thr_empty: bool,
|
||||
/// Break interrupt.
|
||||
#[bit(4, rw)]
|
||||
break_interrupt: bool,
|
||||
/// Framing error.
|
||||
#[bit(3, rw)]
|
||||
framing_error: bool,
|
||||
/// Parity error.
|
||||
#[bit(2, rw)]
|
||||
parity_error: bool,
|
||||
/// Overrun error.
|
||||
#[bit(1, rw)]
|
||||
overrun_error: bool,
|
||||
/// Data ready.
|
||||
#[bit(0, rw)]
|
||||
data_ready: bool,
|
||||
}
|
||||
|
||||
/// Raw register block.
|
||||
#[derive(derive_mmio::Mmio)]
|
||||
#[repr(C)]
|
||||
pub struct AxiUart16550 {
|
||||
pub struct Registers {
|
||||
_reserved: [u32; 0x400],
|
||||
/// FIFO register for LCR[7] == 0 or Divisor Latch (LSB) register for LCR[7] == 1
|
||||
fifo_or_dll: u32,
|
||||
@@ -165,11 +206,11 @@ pub struct AxiUart16550 {
|
||||
/// write-only FIFO control register.
|
||||
iir_or_fcr: u32,
|
||||
/// Line Control Register
|
||||
lcr: Lcr,
|
||||
lcr: LineControl,
|
||||
/// Modem Control Register
|
||||
mcr: u32,
|
||||
/// Line Status Register
|
||||
lsr: Lsr,
|
||||
lsr: LineStatus,
|
||||
/// Modem Status Register
|
||||
msr: u32,
|
||||
/// Scratch Register
|
||||
|
||||
67
src/rx.rs
67
src/rx.rs
@@ -1,10 +1,14 @@
|
||||
//! # Receiver (RX) support module
|
||||
use core::convert::Infallible;
|
||||
|
||||
use crate::{
|
||||
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)]
|
||||
pub struct RxErrors {
|
||||
parity: bool,
|
||||
@@ -13,6 +17,8 @@ pub struct RxErrors {
|
||||
}
|
||||
|
||||
impl RxErrors {
|
||||
/// Construct a new empty [RxErrors] structure.
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
parity: false,
|
||||
@@ -21,26 +27,38 @@ impl RxErrors {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parity error.
|
||||
#[inline]
|
||||
pub const fn parity(&self) -> bool {
|
||||
self.parity
|
||||
}
|
||||
|
||||
/// Framing error.
|
||||
#[inline]
|
||||
pub const fn frame(&self) -> bool {
|
||||
self.frame
|
||||
}
|
||||
|
||||
/// Overrun error.
|
||||
#[inline]
|
||||
pub const fn overrun(&self) -> bool {
|
||||
self.overrun
|
||||
}
|
||||
|
||||
/// Error has occurred.
|
||||
#[inline]
|
||||
pub const fn has_errors(&self) -> bool {
|
||||
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 {
|
||||
/// Internal MMIO register structure.
|
||||
pub(crate) regs: registers::MmioAxiUart16550<'static>,
|
||||
pub(crate) regs: registers::MmioRegisters<'static>,
|
||||
pub(crate) errors: Option<RxErrors>,
|
||||
}
|
||||
|
||||
@@ -60,15 +78,19 @@ impl Rx {
|
||||
/// The same safey rules specified in [super::AxiUart16550::new] apply.
|
||||
pub const unsafe fn steal(base_addr: usize) -> Self {
|
||||
Self {
|
||||
regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) },
|
||||
regs: unsafe { registers::Registers::new_mmio_at(base_addr) },
|
||||
errors: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self {
|
||||
pub(crate) fn new(regs: registers::MmioRegisters<'static>) -> Self {
|
||||
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]
|
||||
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
|
||||
let status_reg = self.regs.read_lsr();
|
||||
@@ -76,11 +98,12 @@ impl Rx {
|
||||
return Err(nb::Error::WouldBlock);
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
/// Read from the FIFO without checking the FIFO fill status.
|
||||
#[inline(always)]
|
||||
pub fn read_fifo_unchecked(&mut self) -> u8 {
|
||||
self.regs.read_fifo_or_dll() as u8
|
||||
@@ -99,30 +122,33 @@ impl Rx {
|
||||
self.enable_interrupt();
|
||||
}
|
||||
|
||||
/// Enable RX interrupts.
|
||||
#[inline]
|
||||
pub fn enable_interrupt(&mut self) {
|
||||
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_line_status(true);
|
||||
ier.raw_value()
|
||||
});
|
||||
}
|
||||
|
||||
/// Disable RX interrupts.
|
||||
#[inline]
|
||||
pub fn disable_interrupt(&mut self) {
|
||||
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_line_status(false);
|
||||
ier.raw_value()
|
||||
});
|
||||
}
|
||||
|
||||
/// Reset the RX FIFO.
|
||||
#[inline]
|
||||
pub fn reset_fifo(&mut self) {
|
||||
self.regs.write_iir_or_fcr(
|
||||
Fcr::builder()
|
||||
FifoControl::builder()
|
||||
.with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL)
|
||||
.with_dma_mode_sel(false)
|
||||
.with_reset_tx_fifo(false)
|
||||
@@ -133,32 +159,38 @@ impl Rx {
|
||||
);
|
||||
}
|
||||
|
||||
/// Data is available.
|
||||
#[inline(always)]
|
||||
pub fn has_data(&mut self) -> bool {
|
||||
pub fn has_data(&self) -> bool {
|
||||
self.regs.read_lsr().data_ready()
|
||||
}
|
||||
|
||||
/// Read the IIR register.
|
||||
#[inline]
|
||||
pub fn read_iir(&mut self) -> Iir {
|
||||
Iir::new_with_raw_value(self.regs.read_iir_or_fcr())
|
||||
pub fn read_iir(&mut self) -> InterruptIdentification {
|
||||
InterruptIdentification::new_with_raw_value(self.regs.read_iir_or_fcr())
|
||||
}
|
||||
|
||||
/// Should be called when a Line Status interrupt occurs.
|
||||
#[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();
|
||||
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]
|
||||
pub fn on_interrupt_data_available_or_char_timeout(
|
||||
&mut self,
|
||||
int_id2: IntId2,
|
||||
int_id2: InterruptId2,
|
||||
buf: &mut [u8; 16],
|
||||
) -> usize {
|
||||
let mut read = 0;
|
||||
// It is guaranteed that we can read the FIFO trigger level.
|
||||
if int_id2 == IntId2::RxDataAvailable {
|
||||
let trigger_level = Fcr::new_with_raw_value(self.regs.read_iir_or_fcr());
|
||||
if int_id2 == InterruptId2::RxDataAvailable {
|
||||
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| {
|
||||
buf[i] = self.read_fifo_unchecked();
|
||||
read += 1;
|
||||
@@ -172,7 +204,8 @@ impl Rx {
|
||||
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();
|
||||
if status_reg.framing_error() {
|
||||
errors.frame = true;
|
||||
|
||||
31
src/tx.rs
31
src/tx.rs
@@ -1,13 +1,18 @@
|
||||
//! # Transmitter (TX) support module
|
||||
use core::convert::Infallible;
|
||||
|
||||
use crate::{
|
||||
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 {
|
||||
/// Internal MMIO register structure.
|
||||
pub(crate) regs: registers::MmioAxiUart16550<'static>,
|
||||
pub(crate) regs: registers::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
impl Tx {
|
||||
@@ -26,14 +31,15 @@ impl Tx {
|
||||
/// The same safey rules specified in [super::AxiUart16550::new] apply.
|
||||
pub const unsafe fn steal(base_addr: usize) -> 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 }
|
||||
}
|
||||
|
||||
/// Write a byte into the FIFO if there is space available.
|
||||
#[inline]
|
||||
pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> {
|
||||
if !self.thr_empty() {
|
||||
@@ -43,19 +49,21 @@ impl Tx {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable TX interrupts.
|
||||
#[inline]
|
||||
pub fn enable_interrupt(&mut self) {
|
||||
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.raw_value()
|
||||
});
|
||||
}
|
||||
|
||||
/// Disable TX interrupts.
|
||||
#[inline]
|
||||
pub fn disable_interrupt(&mut self) {
|
||||
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.raw_value()
|
||||
});
|
||||
@@ -69,21 +77,23 @@ impl Tx {
|
||||
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)]
|
||||
pub fn thr_empty(&mut self) -> bool {
|
||||
pub fn thr_empty(&self) -> bool {
|
||||
self.regs.read_lsr().thr_empty()
|
||||
}
|
||||
|
||||
/// Transmitter empty status.
|
||||
#[inline(always)]
|
||||
pub fn tx_empty(&mut self) -> bool {
|
||||
pub fn tx_empty(&self) -> bool {
|
||||
self.regs.read_lsr().tx_empty()
|
||||
}
|
||||
|
||||
/// Reset the FIFOs.
|
||||
#[inline]
|
||||
pub fn reset_fifo(&mut self) {
|
||||
self.regs.write_iir_or_fcr(
|
||||
Fcr::builder()
|
||||
FifoControl::builder()
|
||||
.with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL)
|
||||
.with_dma_mode_sel(false)
|
||||
.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]
|
||||
pub fn on_interrupt_thr_empty(&mut self, next_write_chunk: &[u8]) -> usize {
|
||||
if next_write_chunk.is_empty() {
|
||||
|
||||
@@ -24,19 +24,25 @@ use raw_slice::RawBufSlice;
|
||||
|
||||
use crate::{
|
||||
FIFO_DEPTH, Tx,
|
||||
registers::{self, Ier},
|
||||
registers::{self, InterruptEnable},
|
||||
};
|
||||
|
||||
/// 1 waker (default).
|
||||
#[cfg(feature = "1-waker")]
|
||||
pub const NUM_WAKERS: usize = 1;
|
||||
/// 2 wakers.
|
||||
#[cfg(feature = "2-wakers")]
|
||||
pub const NUM_WAKERS: usize = 2;
|
||||
/// 4 wakers.
|
||||
#[cfg(feature = "4-wakers")]
|
||||
pub const NUM_WAKERS: usize = 4;
|
||||
/// 8 wakers.
|
||||
#[cfg(feature = "8-wakers")]
|
||||
pub const NUM_WAKERS: usize = 8;
|
||||
/// 16 wakers.
|
||||
#[cfg(feature = "16-wakers")]
|
||||
pub const NUM_WAKERS: usize = 16;
|
||||
/// 32 wakers.
|
||||
#[cfg(feature = "32-wakers")]
|
||||
pub const NUM_WAKERS: usize = 32;
|
||||
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.
|
||||
static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS];
|
||||
|
||||
/// Invalid waker index error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("invalid waker slot index: {0}")]
|
||||
pub struct InvalidWakerIndex(pub usize);
|
||||
@@ -61,7 +68,7 @@ pub fn on_interrupt_tx(tx: &mut Tx, waker_slot: usize) {
|
||||
return;
|
||||
}
|
||||
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.
|
||||
if !ier.thr_empty() {
|
||||
return;
|
||||
@@ -107,7 +114,7 @@ pub fn on_interrupt_tx(tx: &mut Tx, waker_slot: usize) {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TxContext {
|
||||
struct TxContext {
|
||||
progress: usize,
|
||||
slice: RawBufSlice,
|
||||
}
|
||||
@@ -122,9 +129,10 @@ impl TxContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// TX future structure.
|
||||
pub struct TxFuture {
|
||||
waker_idx: usize,
|
||||
reg_block: registers::MmioAxiUart16550<'static>,
|
||||
reg_block: registers::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
impl TxFuture {
|
||||
@@ -191,6 +199,7 @@ impl Drop for TxFuture {
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronous TX driver.
|
||||
pub struct TxAsync<D: DelayNs> {
|
||||
tx: Tx,
|
||||
waker_idx: usize,
|
||||
@@ -233,6 +242,7 @@ impl<D: DelayNs> TxAsync<D> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Release the underlying TX handle.
|
||||
pub fn release(self) -> Tx {
|
||||
self.tx
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user