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
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:
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]
|
[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
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]
|
#![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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
67
src/rx.rs
67
src/rx.rs
@@ -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;
|
||||||
|
|||||||
31
src/tx.rs
31
src/tx.rs
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user