pinctrl: basic gpio led support

This commit is contained in:
2025-08-01 08:59:24 +03:00
parent 062db06473
commit 919d6d62ba
9 changed files with 232 additions and 42 deletions
+29 -2
View File
@@ -1,4 +1,4 @@
use alloc::sync::Arc;
use alloc::{boxed::Box, sync::Arc};
use yggdrasil_abi::error::Error;
use crate::device::Device;
@@ -29,6 +29,25 @@ pub enum SinglePinDirection {
Input,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpioInterruptMode {
RisingEdge,
FallingEdge,
BothEdges,
HighLevel,
LowLevel,
}
pub struct GpioInterruptEvent {
pub mode: GpioInterruptMode,
pub event: u64,
}
pub struct GpioInterrupt {
pub mode: GpioInterruptMode,
pub handler: Box<dyn Fn() + Send>,
}
pub struct GpioPinConfig {
pub input: bool,
pub output: bool,
@@ -48,13 +67,21 @@ pub trait GpioController: Device {
fn write_gpio(&self, pin: &PinHandle, value: bool) -> Result<(), Error>;
fn read_gpio(&self, pin: &PinHandle) -> Result<bool, Error>;
fn setup_gpio(&self, pin: &PinHandle, event: Option<u64>) -> Result<(), Error>;
fn setup_gpio(&self, pin: &PinHandle, event: Option<GpioInterruptEvent>) -> Result<(), Error>;
}
pub trait GpioOutput: Sync + Send {
fn write(&self, value: bool) -> Result<(), Error>;
}
impl PinHandle {
pub fn configure(&self) -> Result<(), Error> {
self.parent.setup_gpio(self, None)
}
pub fn write(&self, value: bool) -> Result<(), Error> {
self.parent.write_gpio(self, value)
}
}
impl GpioPinConfig {
+35
View File
@@ -0,0 +1,35 @@
pub trait BitField: Sized {
fn modify_bit(&self, bit: usize, value: bool) -> Self;
fn set_bit(&mut self, bit: usize, value: bool);
fn get_bit(&self, bit: usize) -> bool;
}
macro_rules! impl_bit_field {
($($ty:ty),+ $(,)?) => {
$(
impl BitField for $ty {
#[inline]
fn modify_bit(&self, bit: usize, value: bool) -> Self {
if value {
*self | (1 << bit)
} else {
*self & !(1 << bit)
}
}
#[inline]
fn set_bit(&mut self, bit: usize, value: bool) {
*self = self.modify_bit(bit, value);
}
#[inline]
fn get_bit(&self, bit: usize) -> bool {
*self & (1 << bit) != 0
}
}
)+
};
}
impl_bit_field!(u8, u16, u32, u64, usize);
impl_bit_field!(i8, i16, i32, i64, isize);
+1
View File
@@ -18,6 +18,7 @@ use core::{
panic,
};
pub mod bit;
pub mod event;
pub mod ext;
pub mod hash_table;
+44 -7
View File
@@ -1,24 +1,37 @@
use core::sync::atomic::{AtomicU64, Ordering};
use core::{
sync::atomic::{AtomicU64, Ordering},
time::Duration,
};
use alloc::{boxed::Box, collections::BTreeMap};
use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
use device_api::gpio::PinHandle;
use device_api::gpio::{GpioInterrupt, GpioInterruptEvent, GpioOutput, PinHandle};
use libk_util::{ring::LossyRingQueue, sync::spin_rwlock::IrqSafeRwLock};
use yggdrasil_abi::error::Error;
use crate::task::runtime;
static GPIO_EVENT_QUEUE: LossyRingQueue<u64> = LossyRingQueue::with_capacity(64);
static GPIO_EVENT_MAP: IrqSafeRwLock<BTreeMap<u64, Box<dyn Fn() + Send>>> =
IrqSafeRwLock::new(BTreeMap::new());
static GPIO_EVENT_ID: AtomicU64 = AtomicU64::new(1);
static GPIO_HEARTBEATS: IrqSafeRwLock<Vec<Arc<dyn GpioOutput>>> = IrqSafeRwLock::new(Vec::new());
pub trait GpioEvent {
fn configure_with_interrupt(&self, handler: Box<dyn Fn() + Send>) -> Result<(), Error>;
fn configure_with_interrupt(&self, int: GpioInterrupt) -> Result<(), Error>;
}
impl GpioEvent for PinHandle {
fn configure_with_interrupt(&self, handler: Box<dyn Fn() + Send>) -> Result<(), Error> {
let ev = bind_gpio_event(handler);
self.parent.setup_gpio(self, Some(ev))
fn configure_with_interrupt(&self, int: GpioInterrupt) -> Result<(), Error> {
let event = bind_gpio_event(int.handler);
self.parent.setup_gpio(
self,
Some(GpioInterruptEvent {
mode: int.mode,
event,
}),
)
}
}
@@ -33,6 +46,30 @@ pub async fn gpio_event_handler_task() {
}
}
pub async fn heartbeat_handler_task() {
const LEVELS: &[(Duration, bool)] = &[
(Duration::from_millis(50), true),
(Duration::from_millis(200), false),
(Duration::from_millis(50), true),
(Duration::from_millis(700), false),
];
let mut index = 0;
loop {
let (timeout, level) = LEVELS[index];
for pin in GPIO_HEARTBEATS.read().iter() {
pin.write(level).ok();
}
runtime::sleep(timeout).await;
index = (index + 1) % LEVELS.len();
}
}
pub fn register_heartbeat_gpio(gpio: Arc<dyn GpioOutput>) {
GPIO_HEARTBEATS.write().push(gpio);
}
pub fn bind_gpio_event(handler: Box<dyn Fn() + Send>) -> u64 {
let id = GPIO_EVENT_ID.fetch_add(1, Ordering::Acquire);
GPIO_EVENT_MAP.write().insert(id, handler);
+51 -16
View File
@@ -4,13 +4,19 @@ use abi::error::Error;
use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
use device_api::{
device::{Device, DeviceInitContext},
gpio::{GpioPinLevel, PinHandle},
gpio::{GpioInterrupt, GpioInterruptMode, GpioOutput, GpioPinLevel, PinHandle},
};
use device_tree::driver::{device_tree_driver, DeviceTreeGpioPins, Node, ProbeContext};
use libk::event::GpioEvent;
use libk::event::{self, GpioEvent};
enum LedFunction {
Heartbeat,
Other,
}
struct LedPin {
name: String,
function: LedFunction,
handle: PinHandle,
}
@@ -20,14 +26,14 @@ struct KeyPin {
}
trait Pin: Sized + Send + Sync + 'static {
fn from_device_tree(node: &Arc<Node>) -> Option<Self>;
fn configure(&self) -> Result<(), Error>;
fn from_device_tree(node: &Arc<Node>) -> Option<Arc<Self>>;
fn configure(self: &Arc<Self>) -> Result<(), Error>;
fn name(&self) -> &str;
}
struct PinGroup<P: Pin> {
name: String,
pins: Vec<P>,
pins: Vec<Arc<P>>,
}
impl<P: Pin> PinGroup<P> {
@@ -63,7 +69,7 @@ impl<P: Pin> Device for PinGroup<P> {
}
impl Pin for LedPin {
fn from_device_tree(node: &Arc<Node>) -> Option<Self> {
fn from_device_tree(node: &Arc<Node>) -> Option<Arc<Self>> {
let handle = node.gpio(
&DeviceTreeGpioPins {
input: false,
@@ -72,12 +78,32 @@ impl Pin for LedPin {
},
0,
)?;
let function = match node.prop_str("function") {
Some("heartbeat") => LedFunction::Heartbeat,
_ => LedFunction::Other,
};
let name = node.name().unwrap_or("gpio-led").into();
Some(Self { name, handle })
Some(Arc::new(Self {
name,
function,
handle,
}))
}
fn configure(&self) -> Result<(), Error> {
self.handle.configure()
fn configure(self: &Arc<Self>) -> Result<(), Error> {
self.handle.configure()?;
match self.function {
LedFunction::Heartbeat => {
log::info!("Register led {:?} as heartbeat indicator", self.name);
event::register_heartbeat_gpio(self.clone())
}
_ => (),
}
Ok(())
}
fn name(&self) -> &str {
@@ -85,8 +111,14 @@ impl Pin for LedPin {
}
}
impl GpioOutput for LedPin {
fn write(&self, value: bool) -> Result<(), Error> {
self.handle.write(value)
}
}
impl Pin for KeyPin {
fn from_device_tree(node: &Arc<Node>) -> Option<Self> {
fn from_device_tree(node: &Arc<Node>) -> Option<Arc<Self>> {
let handle = node.gpio(
&DeviceTreeGpioPins {
input: true,
@@ -96,14 +128,17 @@ impl Pin for KeyPin {
0,
)?;
let name = node.name().unwrap_or("gpio-key").into();
Some(Self { name, handle })
Some(Arc::new(Self { name, handle }))
}
fn configure(&self) -> Result<(), Error> {
let name = self.name.clone();
self.handle.configure_with_interrupt(Box::new(move || {
log::info!("GPIO key {name:?} triggered");
}))
fn configure(self: &Arc<Self>) -> Result<(), Error> {
let key = self.clone();
self.handle.configure_with_interrupt(GpioInterrupt {
mode: GpioInterruptMode::FallingEdge,
handler: Box::new(move || {
log::info!("GPIO key triggered: {:?}", key.name);
}),
})
}
fn name(&self) -> &str {
+4 -4
View File
@@ -2,7 +2,7 @@ use abi::error::Error;
use alloc::sync::Arc;
use device_api::{
device::{Device, DeviceInitContext},
gpio::{GpioController, PinHandle},
gpio::{GpioController, GpioInterruptEvent, PinHandle},
interrupt::{FullIrq, InterruptHandler, IrqVector},
};
use device_tree::{
@@ -164,7 +164,7 @@ impl InterruptHandler for Bcm2711Gpio {
}
impl GpioController for Bcm2711Gpio {
fn setup_gpio(&self, pin: &PinHandle, _event: Option<u64>) -> Result<(), Error> {
fn setup_gpio(&self, pin: &PinHandle, _event: Option<GpioInterruptEvent>) -> Result<(), Error> {
log::warn!(
"TOOD: bcm2711 gpio pin #{} setup: input={}, output={}",
pin.index,
@@ -175,11 +175,11 @@ impl GpioController for Bcm2711Gpio {
}
fn read_gpio(&self, _pin: &PinHandle) -> Result<bool, Error> {
todo!()
Ok(false)
}
fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> {
todo!()
Ok(())
}
}
+48 -6
View File
@@ -3,7 +3,10 @@ use alloc::{sync::Arc, vec::Vec};
use device_api::{
clock::{ClockHandle, ResetHandle},
device::{Device, DeviceInitContext},
gpio::{GpioController, PinHandle},
gpio::{
GpioController, GpioInterruptEvent, GpioPinLevel, OutputPinBias, PinHandle,
SinglePinDirection,
},
};
use device_tree::{
driver::{
@@ -14,7 +17,7 @@ use device_tree::{
DeviceTreePropertyRead, TProp,
};
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIoMut};
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
use libk_util::{bit::BitField, sync::IrqSafeSpinlock, OneTimeInit};
#[derive(Debug)]
struct PinMuxConfig {
@@ -55,6 +58,17 @@ trait GpioRegs: Sized + Send + 'static {
fn reg_mut(&mut self, index: usize) -> *mut u32;
fn pin_functions(&self, pin: usize) -> Option<(usize, usize, u32)>;
fn write_output_pin(&mut self, pin: usize, value: bool) {
let reg = pin / 4;
let shift = (pin % 4) * 8;
let reg_dout = self.reg_mut(Self::DOUT_OFFSET + reg);
unsafe {
let mut val = reg_dout.read_volatile();
val.set_bit(shift, value);
reg_dout.write_volatile(val);
}
}
fn configure_pin(&mut self, pin: usize, doen: u32, dout: u32, din: u32) {
let reg = pin / 4;
let shift = (pin % 4) * 8;
@@ -240,6 +254,21 @@ impl<R: GpioRegs> Gpio<R> {
}
}
fn configure_pin_output(
&self,
pin: usize,
_active_level: GpioPinLevel,
initial_level: GpioPinLevel,
_output_bias: Option<OutputPinBias>,
) -> Result<(), Error> {
// TODO configure pull up/pull down?
let mut regs = self.regs.lock();
let dout_val = initial_level == GpioPinLevel::High;
regs.configure_pin_function(pin, 0);
regs.configure_pin(pin, 0, dout_val as u32, 0);
Ok(())
}
fn ensure_init(&self) -> Result<(), Error> {
let res = self.init.or_init_with(|| {
for clock in self.clocks.iter() {
@@ -269,22 +298,35 @@ impl<R: GpioRegs> Device for Gpio<R> {
}
impl<R: GpioRegs> GpioController for Gpio<R> {
fn setup_gpio(&self, pin: &PinHandle, _event: Option<u64>) -> Result<(), Error> {
fn setup_gpio(&self, pin: &PinHandle, _event: Option<GpioInterruptEvent>) -> Result<(), Error> {
log::warn!(
"TODO: setup gpio #{} input={}, output={}",
pin.index,
pin.config.input,
pin.config.output
);
Err(Error::NotImplemented)
// TODO bidi pins?
match pin.config.force_single_direction() {
Some(SinglePinDirection::Input) => todo!(),
Some(SinglePinDirection::Output) => self.configure_pin_output(
pin.index as usize,
pin.config.active_level,
pin.config.initial_level,
pin.config.output_bias,
),
None => {
todo!("{}: support for bidi gpio", R::NAME);
}
}
}
fn read_gpio(&self, _pin: &PinHandle) -> Result<bool, Error> {
Err(Error::NotImplemented)
}
fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> {
Err(Error::NotImplemented)
fn write_gpio(&self, pin: &PinHandle, value: bool) -> Result<(), Error> {
self.regs.lock().write_output_pin(pin.index as _, value);
Ok(())
}
}
+19 -7
View File
@@ -5,7 +5,10 @@ use alloc::{sync::Arc, vec::Vec};
use device_api::{
clock::{ClockHandle, ResetHandle},
device::{Device, DeviceInitContext},
gpio::{GpioController, GpioPinLevel, PinHandle, SinglePinDirection},
gpio::{
GpioController, GpioInterruptEvent, GpioInterruptMode, GpioPinLevel, PinHandle,
SinglePinDirection,
},
interrupt::{FullIrq, InterruptHandler, IrqVector},
};
use device_tree::{
@@ -17,7 +20,7 @@ use device_tree::{
};
use libk::{device::external_interrupt_controller, event::signal_gpio_event};
use libk_mm::device::DeviceMemoryIo;
use libk_util::sync::IrqSafeSpinlock;
use libk_util::{bit::BitField, sync::IrqSafeSpinlock};
use tock_registers::{
interfaces::{Readable, Writeable},
register_structs,
@@ -106,7 +109,7 @@ impl GpioController for Pl061 {
todo!()
}
fn setup_gpio(&self, pin: &PinHandle, event: Option<u64>) -> Result<(), Error> {
fn setup_gpio(&self, pin: &PinHandle, event: Option<GpioInterruptEvent>) -> Result<(), Error> {
let regs = self.regs.lock();
let direction = pin
@@ -146,11 +149,20 @@ impl GpioController for Pl061 {
regs.GPIODIR.set(regs.GPIODIR.get() & !(1 << pin.index));
if let Some(event) = event {
self.gpio_events[pin.index as usize].store(event, Ordering::Release);
let ibe = event.mode == GpioInterruptMode::BothEdges;
let is = event.mode == GpioInterruptMode::HighLevel
|| event.mode == GpioInterruptMode::LowLevel;
let iev = event.mode == GpioInterruptMode::HighLevel
|| event.mode == GpioInterruptMode::RisingEdge;
regs.GPIOIS.set(regs.GPIOIS.get() & !(1 << pin.index));
regs.GPIOIBE.set(regs.GPIOIBE.get() & !(1 << pin.index));
regs.GPIOIEV.set(regs.GPIOIEV.get() | (1 << pin.index));
self.gpio_events[pin.index as usize].store(event.event, Ordering::Release);
regs.GPIOIS
.set(regs.GPIOIS.get().modify_bit(pin.index as usize, is));
regs.GPIOIBE
.set(regs.GPIOIBE.get().modify_bit(pin.index as usize, ibe));
regs.GPIOIEV
.set(regs.GPIOIEV.get().modify_bit(pin.index as usize, iev));
regs.GPIOIE.set(regs.GPIOIE.get() | (1 << pin.index));
regs.GPIOIC.set(1 << pin.index);
}
+1
View File
@@ -58,6 +58,7 @@ pub fn kinit() -> Result<(), Error> {
);
runtime::spawn(event::gpio_event_handler_task()).ok();
runtime::spawn(event::heartbeat_handler_task()).ok();
}
// Initialize PCI devices
if let Err(error) = PciBusManager::setup_bus_devices(false) {