yggdrasil/kernel/src/device/serial/sunxi_uart.rs
Mark Poliakov 18fa8b954a Add 'kernel/' from commit '7f1f6b73377367db17f98a740316b904c37ce3b1'
git-subtree-dir: kernel
git-subtree-mainline: 817f71f90f97270dd569fd44246bf74e57636552
git-subtree-split: 7f1f6b73377367db17f98a740316b904c37ce3b1
2024-03-12 15:52:48 +02:00

207 lines
5.4 KiB
Rust

//! Allwinner (H6) UART implementation
use abi::{error::Error, io::DeviceRequest};
use alloc::boxed::Box;
use device_api::{interrupt::InterruptHandler, serial::SerialDevice, Device};
use libk::util::OneTimeInit;
use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite},
};
use vfs::CharDevice;
use crate::{
arch::{aarch64::IrqNumber, Architecture, PLATFORM},
debug::{self, DebugSink, LogLevel},
device::{
devtree::{self, DevTreeIndexPropExt},
tty::{CharRing, TtyDevice},
},
device_tree_driver,
fs::devfs::{self, CharDeviceType},
mem::device::DeviceMemoryIo,
sync::IrqSafeSpinlock,
};
register_bitfields! {
u32,
USR [
TFE OFFSET(2) NUMBITS(1) [],
TFNF OFFSET(1) NUMBITS(1) []
],
IER [
ERBFI OFFSET(0) NUMBITS(1) [],
],
IIR [
IID OFFSET(0) NUMBITS(4) [
RecvDataAvailable = 0b0100,
]
]
}
register_structs! {
#[allow(non_snake_case)]
Regs {
(0x00 => DLL: ReadWrite<u32>),
(0x04 => IER: ReadWrite<u32, IER::Register>),
(0x08 => IIR: ReadWrite<u32, IIR::Register>),
(0x0C => _0),
(0x7C => USR: ReadOnly<u32, USR::Register>),
(0x80 => @END),
}
}
struct Inner {
regs: DeviceMemoryIo<Regs>,
}
struct SunxiUart {
inner: OneTimeInit<IrqSafeSpinlock<Inner>>,
base: usize,
irq: IrqNumber,
ring: CharRing<16>,
}
impl DebugSink for SunxiUart {
fn putc(&self, c: u8) -> Result<(), Error> {
self.send(c)
}
fn supports_control_sequences(&self) -> bool {
true
}
}
impl CharDevice for SunxiUart {
fn read(&'static self, _blocking: bool, data: &mut [u8]) -> Result<usize, Error> {
self.line_read(data)
}
fn write(&self, _blocking: bool, data: &[u8]) -> Result<usize, Error> {
self.line_write(data)
}
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
match req {
&mut DeviceRequest::SetTerminalGroup(id) => {
self.set_signal_group(id as _);
Ok(())
}
_ => Err(Error::InvalidArgument),
}
}
}
impl TtyDevice<16> for SunxiUart {
fn ring(&self) -> &CharRing<16> {
&self.ring
}
}
impl InterruptHandler for SunxiUart {
fn handle_irq(&self) -> bool {
let inner = self.inner.get().lock();
if inner.regs.IIR.matches_all(IIR::IID::RecvDataAvailable) {
let byte = inner.regs.DLL.get();
drop(inner);
if byte == b'\x1b' as u32 {
panic!("RESET TRIGGERED");
}
self.recv_byte(byte as u8);
}
// inner.regs.ICR.write(ICR::ALL::CLEAR);
// let byte = inner.regs.DR.get();
// drop(inner);
// if byte == b'\x1b' as u32 {
// use crate::task::sched::CpuQueue;
// for (i, queue) in CpuQueue::all().enumerate() {
// log_print_raw!(LogLevel::Fatal, "queue{}:\n", i);
// let lock = unsafe { queue.grab() };
// for item in lock.iter() {
// log_print_raw!(
// LogLevel::Fatal,
// "* {} {:?} {:?}\n",
// item.id(),
// item.name(),
// item.state()
// );
// }
// }
// } else {
// self.recv_byte(byte as u8);
// }
true
}
}
impl SerialDevice for SunxiUart {
fn send(&self, byte: u8) -> Result<(), Error> {
let inner = self.inner.get().lock();
if byte == b'\n' {
while inner.regs.USR.matches_all(USR::TFE::CLEAR) {
core::hint::spin_loop();
}
inner.regs.DLL.set(b'\r' as u32);
}
while inner.regs.USR.matches_all(USR::TFE::CLEAR) {
core::hint::spin_loop();
}
inner.regs.DLL.set(byte as u32);
Ok(())
}
}
impl Device for SunxiUart {
fn display_name(&self) -> &'static str {
"Allwinner UART"
}
unsafe fn init(&'static self) -> Result<(), Error> {
let regs = DeviceMemoryIo::<Regs>::map("sunxi-uart", self.base)?;
self.inner.init(IrqSafeSpinlock::new(Inner { regs }));
debug::add_sink(self, LogLevel::Debug);
devfs::add_char_device(self, CharDeviceType::TtySerial)?;
Ok(())
}
unsafe fn init_irq(&'static self) -> Result<(), Error> {
let intc = PLATFORM.external_interrupt_controller();
intc.register_irq(self.irq, Default::default(), self)?;
intc.enable_irq(self.irq)?;
let inner = self.inner.get().lock();
inner.regs.IER.modify(IER::ERBFI::SET);
Ok(())
}
}
device_tree_driver! {
compatible: ["snps,dw-apb-uart"],
probe(of) => {
let reg = devtree::find_prop(&of.node, "reg")?;
let (base, _) = reg.cell2_array_item(0, of.address_cells, of.size_cells)?;
if base == 0x05000000 {
Some(Box::new(SunxiUart {
inner: OneTimeInit::new(),
ring: CharRing::new(),
irq: IrqNumber::Shared(0),
base: base as usize
}))
} else {
// TODO don't just hardcode and ignore other UARTs
None
}
}
}