8250: merge bcm aux uart into 8250 driver

This commit is contained in:
2026-01-19 11:06:05 +02:00
parent 195c19e225
commit d108494314
6 changed files with 58 additions and 227 deletions
+12 -2
View File
@@ -1,6 +1,6 @@
use alloc::sync::Arc;
use device_api::{
clock::{ClockController, ClockHandle},
clock::{ClockController, ClockHandle, Hertz, IntoHertz},
device::{Device, DeviceInitContext},
};
use device_tree::{
@@ -54,10 +54,10 @@ impl ClockController for Bcm2835Aux {
let regs = regs.lock();
match clock {
Some(0) => {
// TODO CPRMAN driver
regs.AUX_ENABLES.modify(AUX_ENABLES::MU_ENABLE::SET);
Ok(())
}
None => todo!(),
_ => Err(Error::InvalidArgument),
}
}
@@ -65,6 +65,16 @@ impl ClockController for Bcm2835Aux {
fn disable_clock(&self, _clock: Option<u32>) -> Result<(), Error> {
Err(Error::NotImplemented)
}
fn clock_rate(&self, clock: Option<u32>) -> Result<Hertz, Error> {
match clock {
Some(0) => {
// TODO CPRMAN driver
Ok(54u64.mhz())
}
_ => Err(Error::InvalidArgument),
}
}
}
impl DeviceTreeClockController for Bcm2835Aux {
-211
View File
@@ -1,211 +0,0 @@
use alloc::sync::Arc;
use device_api::{
clock::ClockHandle,
device::{Device, DeviceInitContext},
interrupt::{InterruptHandler, IrqHandle, IrqVector},
};
use device_tree::driver::{Node, ProbeContext, device_tree_driver};
use libk::{
debug::DebugSink,
device::manager::DEVICE_REGISTRY,
vfs::{Terminal, TerminalInput, TerminalOutput},
};
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::{OneTimeInit, sync::IrqSafeSpinlock};
use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite},
};
use yggdrasil_abi::{
error::Error,
io::{TerminalOptions, TerminalOutputOptions},
};
register_bitfields! {
u32,
AUX_MU_IER_REG [
RX_IRQ OFFSET(0) NUMBITS(1) [],
TX_IRQ OFFSET(1) NUMBITS(1) [],
],
AUX_MU_IIR_REG [
IID OFFSET(1) NUMBITS(2) [
None = 0,
TxEmpty = 1,
RxNotEmpty = 2,
],
PENDING OFFSET(0) NUMBITS(1) [],
],
AUX_MU_LSR_REG [
TX_EMPTY OFFSET(5) NUMBITS(1) [],
]
}
register_structs! {
#[allow(non_snake_case)]
Regs {
(0x00 => AUX_MU_IO_REG: ReadWrite<u32>),
(0x04 => AUX_MU_IER_REG: ReadWrite<u32, AUX_MU_IER_REG::Register>),
(0x08 => AUX_MU_IIR_REG: ReadWrite<u32, AUX_MU_IIR_REG::Register>),
(0x0C => _0),
(0x14 => AUX_MU_LSR_REG: ReadOnly<u32, AUX_MU_LSR_REG::Register>),
(0x18 => _1),
(0x30 => @END),
}
}
struct Inner {
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
}
/// Broadcom 283x mini-UART driver
pub struct Bcm2835AuxUart {
base: PhysicalAddress,
irq: IrqHandle,
clock: ClockHandle,
inner: OneTimeInit<Arc<Terminal<Inner>>>,
}
impl Regs {
fn write_byte(&self, byte: u8) -> Result<(), Error> {
while !self
.AUX_MU_LSR_REG
.matches_all(AUX_MU_LSR_REG::TX_EMPTY::SET)
{
core::hint::spin_loop();
}
self.AUX_MU_IO_REG.set(byte as u32);
Ok(())
}
}
impl TerminalOutput for Inner {
fn write(&self, byte: u8, options: &TerminalOutputOptions) -> Result<(), Error> {
let regs = self.regs.lock();
if byte == b'\n' && options.contains(TerminalOutputOptions::NL_TO_CRNL) {
regs.write_byte(b'\r')?;
}
regs.write_byte(byte)
}
fn write_multiple(
&self,
bytes: &[u8],
options: &TerminalOutputOptions,
) -> Result<usize, Error> {
let regs = self.regs.lock();
for &byte in bytes {
if byte == b'\n' && options.contains(TerminalOutputOptions::NL_TO_CRNL) {
regs.write_byte(b'\r')?;
}
regs.write_byte(byte)?;
}
Ok(bytes.len())
}
}
impl DebugSink for Bcm2835AuxUart {
fn putc(&self, c: u8) -> Result<(), Error> {
self.inner.get().putc_to_output(c)
}
fn puts(&self, s: &str) -> Result<(), Error> {
self.inner.get().write_to_output(s.as_bytes())?;
Ok(())
}
fn supports_control_sequences(&self) -> bool {
true
}
}
impl InterruptHandler for Bcm2835AuxUart {
fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
let inner = self.inner.get();
let (status, byte) = {
let regs = inner.output().regs.lock();
// Reset IRQ
regs.AUX_MU_IIR_REG.modify(AUX_MU_IIR_REG::IID::SET);
let byte = regs.AUX_MU_IO_REG.get() as u8;
let status = regs
.AUX_MU_IIR_REG
.matches_all(AUX_MU_IIR_REG::PENDING::SET);
(status, byte)
};
if status {
inner.write_to_input(byte);
}
status
}
}
impl Device for Bcm2835AuxUart {
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
// TODO initialize pinctrl
self.clock.enable()?;
let regs = unsafe { DeviceMemoryIo::map(self.base, Default::default()) }?;
let config = TerminalOptions {
output: TerminalOutputOptions::NL_TO_CRNL,
..Default::default()
};
let output = Inner {
regs: IrqSafeSpinlock::new(regs),
};
let input = TerminalInput::with_capacity(64)?;
let inner = self
.inner
.init(Arc::new(Terminal::from_parts(config, input, output)));
DEVICE_REGISTRY
.serial_terminal
.register(inner.clone(), Some(self.clone()))
.ok();
Ok(())
}
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
self.irq.register(self.clone())?;
self.irq.enable()?;
let inner = self.inner.get().output();
let regs = inner.regs.lock();
regs.AUX_MU_IER_REG
.modify(AUX_MU_IER_REG::RX_IRQ::SET + AUX_MU_IER_REG::TX_IRQ::CLEAR);
Ok(())
}
fn display_name(&self) -> &str {
"bcm2835 mini-UART"
}
}
// TODO handle pinctrl
device_tree_driver! {
compatible: ["brcm,bcm2835-aux-uart"],
driver: {
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
let base = node.map_base(context, 0)?;
let irq = node.interrupt(0)?;
let clock = node.clock(0)?;
Some(Arc::new(Bcm2835AuxUart {
base,
irq,
clock,
inner: OneTimeInit::new(),
}))
}
}
}
+1 -1
View File
@@ -3,6 +3,6 @@
extern crate alloc;
mod aux;
mod aux_uart;
// mod aux_uart;
mod gpio;
mod mbox;
+20 -4
View File
@@ -121,6 +121,18 @@ impl Config for Fdt8250Config {
}
}
// uart1: serial@7e215040 {
// compatible = "brcm,bcm2835-aux-uart";
// reg = <0x7e215040 0x40>;
// interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
// clocks = <&aux 0x00>;
// status = "okay";
// pinctrl-names = "default";
// pinctrl-0 = <&uart1_gpio14>;
// skip-init;
// bootph-all;
// };
impl Fdt8250Config {
fn from_device_tree(node: &Arc<Node>, context: &mut ProbeContext) -> Option<(Self, Vendor)> {
let vendor = if node.is_compatible("snps,dw-apb-uart") {
@@ -133,16 +145,19 @@ impl Fdt8250Config {
let base = node.map_base(context, 0)?;
let irq = node.interrupt(0)?;
let resets = node.resets().map(|r| r.collect()).unwrap_or_default();
let resets: Vec<_> = node.resets().map(|r| r.collect()).unwrap_or_default();
let clock_frequency = node
.prop_usize("clock-frequency")
.map(|clk| Hertz(clk as _));
let clk_baud = node.named_clock("baudclk");
let mut clk_baud = node.named_clock("baudclk");
let clk_apb = node.named_clock("apb_pclk");
if clk_baud.is_none() && clk_apb.is_none() {
clk_baud = node.clock(0);
}
let reg_io_width = node.prop_usize("reg-io-width").unwrap_or(1);
let reg_io_width = node.prop_usize("reg-io-width").unwrap_or(4);
let reg_offset = node.prop_usize("reg-offset").unwrap_or(0);
let reg_shift = node.prop_usize("reg-shift").unwrap_or(0);
let reg_shift = node.prop_usize("reg-shift").unwrap_or(2);
Some((
Self {
@@ -168,6 +183,7 @@ impl Fdt8250Config {
device_tree_driver! {
compatible: [
"snps,dw-apb-uart",
"brcm,bcm2835-aux-uart",
"ns16550a",
],
driver: {
+2
View File
@@ -46,6 +46,7 @@ pub trait Access: Sized + Send + 'static {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Vendor {
DwApbUart,
Bcm2835Aux,
Generic8250,
Generic16550,
}
@@ -102,6 +103,7 @@ impl<A: Access> Device for Uart8250<A> {
fn display_name(&self) -> &str {
match self.vendor {
Vendor::DwApbUart => "Synopsys DesignWare 16550 UART",
Vendor::Bcm2835Aux => "bcm2835 AUX UART",
Vendor::Generic16550 => "16550 UART",
Vendor::Generic8250 => "8250 UART",
}
+23 -9
View File
@@ -28,6 +28,7 @@ const REG_LSR: u8 = 5;
const DW_REG_USR: u8 = 31;
const DW_REG_SRR: u8 = 34;
const DW_REG_HTX: u8 = 41;
const BCM2835_REG_AUX_MU_CNTL: u8 = 24;
/// Received Data available interrupt
const IER_RXDE: u8 = 1 << 0;
@@ -43,6 +44,9 @@ const MCR_DTR: u8 = 1 << 0;
const MCR_RTS: u8 = 1 << 1;
const LSR_TEMT: u8 = 1 << 6;
const BCM2835_AUX_MU_CNTL_RE: u8 = 1 << 0;
const BCM2835_AUX_MU_CNTL_TE: u8 = 1 << 1;
pub struct Port8250<A: Access> {
pub(crate) vendor: Vendor,
pub(crate) inner: OneTimeInit<Arc<Terminal<Inner<A>>>>,
@@ -167,16 +171,26 @@ impl<A: Access> TerminalOutput for Inner<A> {
impl<A: Access> PortRegs<A> {
fn init(&mut self, vendor: Vendor, baud_rate: u32) -> Result<(), Error> {
if vendor == Vendor::DwApbUart {
self.write(DW_REG_SRR, 1);
delay();
self.write(DW_REG_HTX, 0);
delay();
// wait while DW USR.BUSY == 1
while self.read(DW_REG_USR) & (1 << 0) != 0 {
core::hint::spin_loop();
match vendor {
Vendor::Bcm2835Aux => {
self.write(
BCM2835_REG_AUX_MU_CNTL,
BCM2835_AUX_MU_CNTL_RE | BCM2835_AUX_MU_CNTL_TE,
);
delay();
}
Vendor::DwApbUart => {
self.write(DW_REG_SRR, 1);
delay();
self.write(DW_REG_HTX, 0);
delay();
// wait while DW USR.BUSY == 1
while self.read(DW_REG_USR) & (1 << 0) != 0 {
core::hint::spin_loop();
}
}
_ => (),
}
self.set_baud_rate(baud_rate)