From 062db06473a90f339c68c61a0f2b9bb5c19cb3fd Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 31 Jul 2025 00:03:45 +0300 Subject: [PATCH] pinctrl: basic support for gpio --- doc/raspi4b.txt | 2 +- kernel/lib/device-api/src/gpio.rs | 69 ++++++++++ kernel/lib/device-api/src/lib.rs | 1 + .../lib/device-tree/src/driver/controller.rs | 25 +++- kernel/lib/device-tree/src/driver/mod.rs | 11 ++ kernel/lib/device-tree/src/driver/traits.rs | 11 +- kernel/lib/device-tree/src/driver/tree.rs | 23 +++- kernel/lib/device-tree/src/driver/util.rs | 44 +++++- kernel/libk/src/event.rs | 44 ++++++ kernel/libk/src/lib.rs | 1 + kernel/src/device/gpio.rs | 130 ++++++++++++++++++ kernel/src/device/mod.rs | 1 + kernel/src/device/pinctrl/bcm2711_gpio.rs | 62 ++++++++- kernel/src/device/pinctrl/jh7110_pinctrl.rs | 45 +++++- kernel/src/device/pinctrl/pl061.rs | 129 ++++++++++++++++- kernel/src/init.rs | 3 + 16 files changed, 588 insertions(+), 13 deletions(-) create mode 100644 kernel/lib/device-api/src/gpio.rs create mode 100644 kernel/libk/src/event.rs create mode 100644 kernel/src/device/gpio.rs diff --git a/doc/raspi4b.txt b/doc/raspi4b.txt index 5bd1fecf..c14e820d 100644 --- a/doc/raspi4b.txt +++ b/doc/raspi4b.txt @@ -45,7 +45,7 @@ $ booti ${loadaddr} ${initrd_addr_r}: ${fdt_ad env set ipaddr 13.0.0.2; env set fdt_addr_r 0x11000000; env set initrd_addr_r 0x04000000; tftpboot ${initrd_addr_r} 13.0.0.1:initrd.tar; tftpboot ${loadaddr} 13.0.0.1:kernel.bin; load mmc 0:1 ${fdt_addr_r} bcm2711-rpi-4-b.dtb; fdt addr ${fdt_addr_r}; fdt resize; fdt memory 0x0 0x3C000000; booti ${loadaddr} ${initrd_addr_r}:67108864 ${fdt_addr_r} dhcp; -env set fdt_addr_r 0x04000000; env set initrd_addr_r 0x20000000; tftpboot ${initrd_addr_r} 192.168.88.10:initrd.img; tftpboot ${loadaddr} 192.168.88.10:yggdrasil-kernel.bin; load mmc 1:1 ${fdt_addr_r} bcm2711-rpi-4-b.dtb; fdt addr ${fdt_addr_r}; fdt resize; fdt memory 0x0 0x3C000000; booti ${loadaddr} ${initrd_addr_r}:0x4000000 ${fdt_addr_r} +env set initrd_addr_r 0x20000000; tftpboot ${initrd_addr_r} 192.168.88.10:initrd.img; tftpboot ${loadaddr} 192.168.88.10:yggdrasil-kernel.bin; load mmc 1:1 ${fdt_addr_r} bcm2711-rpi-4-b.dtb; fdt addr ${fdt_addr_r}; fdt resize; fdt memory 0x0 0x3C000000; booti ${loadaddr} ${initrd_addr_r}:0x4000000 ${fdt_addr_r} Missing drivers: diff --git a/kernel/lib/device-api/src/gpio.rs b/kernel/lib/device-api/src/gpio.rs new file mode 100644 index 00000000..205ba974 --- /dev/null +++ b/kernel/lib/device-api/src/gpio.rs @@ -0,0 +1,69 @@ +use alloc::sync::Arc; +use yggdrasil_abi::error::Error; + +use crate::device::Device; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InputPinBias { + PullUp, + PullDown, + Floating, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputPinBias { + PushPull, + OpenDrain, + HighZ, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GpioPinLevel { + High, + Low, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SinglePinDirection { + Output, + Input, +} + +pub struct GpioPinConfig { + pub input: bool, + pub output: bool, + pub input_bias: Option, + pub output_bias: Option, + pub active_level: GpioPinLevel, + pub initial_level: GpioPinLevel, +} + +pub struct PinHandle { + pub index: u32, + pub config: GpioPinConfig, + pub parent: Arc, +} + +pub trait GpioController: Device { + fn write_gpio(&self, pin: &PinHandle, value: bool) -> Result<(), Error>; + fn read_gpio(&self, pin: &PinHandle) -> Result; + + fn setup_gpio(&self, pin: &PinHandle, event: Option) -> Result<(), Error>; +} + +impl PinHandle { + pub fn configure(&self) -> Result<(), Error> { + self.parent.setup_gpio(self, None) + } +} + +impl GpioPinConfig { + pub fn force_single_direction(&self) -> Option { + match (self.input, self.output) { + (true, false) => Some(SinglePinDirection::Input), + (false, true) => Some(SinglePinDirection::Output), + (false, false) => None, + (true, true) => None, + } + } +} diff --git a/kernel/lib/device-api/src/lib.rs b/kernel/lib/device-api/src/lib.rs index 0243116b..0d38f71a 100644 --- a/kernel/lib/device-api/src/lib.rs +++ b/kernel/lib/device-api/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; pub mod bus; pub mod clock; pub mod device; +pub mod gpio; pub mod interrupt; pub mod serial; pub mod timer; diff --git a/kernel/lib/device-tree/src/driver/controller.rs b/kernel/lib/device-tree/src/driver/controller.rs index ee3ab012..423abd31 100644 --- a/kernel/lib/device-tree/src/driver/controller.rs +++ b/kernel/lib/device-tree/src/driver/controller.rs @@ -2,11 +2,12 @@ use alloc::sync::Arc; use device_api::{ clock::{ClockHandle, ResetHandle}, + gpio::PinHandle, interrupt::FullIrq, }; use fdt_rs::spec::Phandle; -use crate::{DeviceTreePropertyRead, TProp}; +use crate::{driver::DeviceTreeGpioPins, DeviceTreePropertyRead, TProp}; use super::{lookup_phandle, Node}; @@ -20,6 +21,12 @@ pub(crate) struct ResetIter<'dt> { pub(crate) offset: usize, } +pub(crate) struct GpioIter<'o, 'dt> { + pub(crate) options: &'o DeviceTreeGpioPins, + pub(crate) gpios: TProp<'dt>, + pub(crate) offset: usize, +} + impl Iterator for ClockIter<'_> { type Item = ClockHandle; @@ -52,6 +59,22 @@ impl Iterator for ResetIter<'_> { } } +impl Iterator for GpioIter<'_, '_> { + type Item = PinHandle; + + fn next(&mut self) -> Option { + if self.offset >= self.gpios.len() { + return None; + } + let phandle = self.gpios.read_cell(self.offset, 1)? as Phandle; + let gpioc = lookup_phandle(phandle, true)?; + let gpioc = gpioc.as_pin_controller()?; + let (pin, len) = gpioc.map_gpio(&self.gpios, self.offset + 1, self.options)?; + self.offset += len + 1; + Some(pin) + } +} + // Interrupt controller handling /// Reads interrupt information, as interpreted by `interrupt_controller`, from `property` at a diff --git a/kernel/lib/device-tree/src/driver/mod.rs b/kernel/lib/device-tree/src/driver/mod.rs index bde975ce..75a8e442 100644 --- a/kernel/lib/device-tree/src/driver/mod.rs +++ b/kernel/lib/device-tree/src/driver/mod.rs @@ -1,6 +1,7 @@ //! Device tree-based driver definitions use alloc::sync::Arc; +use device_api::gpio::GpioPinLevel; use yggdrasil_abi::error::Error; mod controller; @@ -22,6 +23,16 @@ pub use traits::{ }; pub use tree::{find_node, unflatten_device_tree, walk_device_tree, Node}; +/// Contextual information about a GPIO pin being configured +pub struct DeviceTreeGpioPins { + /// Whether to enable input on the pin + pub input: bool, + /// Whether to enable output on the pin + pub output: bool, + /// Initial level to configure the GPIO with + pub initial_level: GpioPinLevel, +} + /// Specifies initialization sequence requirement for a driver #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum InitSequence { diff --git a/kernel/lib/device-tree/src/driver/traits.rs b/kernel/lib/device-tree/src/driver/traits.rs index f05c85f5..50bdc510 100644 --- a/kernel/lib/device-tree/src/driver/traits.rs +++ b/kernel/lib/device-tree/src/driver/traits.rs @@ -6,11 +6,12 @@ use device_api::{ bus::Bus, clock::{ClockHandle, ResetHandle}, device::Device, + gpio::PinHandle, interrupt::{ExternalInterruptController, FullIrq}, }; use libk::error::Error; -use crate::TProp; +use crate::{driver::DeviceTreeGpioPins, TProp}; use super::{InitSequence, Node}; @@ -56,6 +57,14 @@ pub struct ProbeContext { pub trait DeviceTreePinController { /// Configure a pin group fn configure_pin_group(self: Arc, pins: &Arc) -> Result<(), Error>; + /// Reads GPIO pin information from `property` at given `offset` and maps it to a + /// [PinHandle], returning the handle + the size of the GPIO entry. + fn map_gpio( + self: Arc, + property: &TProp, + offset: usize, + info: &DeviceTreeGpioPins, + ) -> Option<(PinHandle, usize)>; } impl ProbeContext { diff --git a/kernel/lib/device-tree/src/driver/tree.rs b/kernel/lib/device-tree/src/driver/tree.rs index d4dc912c..33eff3ec 100644 --- a/kernel/lib/device-tree/src/driver/tree.rs +++ b/kernel/lib/device-tree/src/driver/tree.rs @@ -9,6 +9,7 @@ use device_api::{ bus::Bus, clock::{ClockController, ClockHandle, ResetHandle}, device::{Device, DeviceInitContext}, + gpio::PinHandle, interrupt::{ExternalInterruptController, FullIrq, MessageInterruptController}, }; use fdt_rs::spec::Phandle; @@ -18,7 +19,9 @@ use libk_util::OneTimeInit; use yggdrasil_abi::error::Error; use crate::{ - driver::{traits::DeviceTreePinController, DeviceTreeSyscon}, + driver::{ + controller::GpioIter, traits::DeviceTreePinController, DeviceTreeGpioPins, DeviceTreeSyscon, + }, tree, DeviceTree, DeviceTreeNodeExt, DeviceTreePropertyRead, TNode, TProp, }; @@ -349,6 +352,24 @@ impl Node { .map(|range| PhysicalAddress::from_u64(range.start)) } + /// Returns an iterator over the node's defined `gpios` + pub fn gpios<'a>( + &self, + options: &'a DeviceTreeGpioPins, + ) -> Option + 'a> { + let gpios = self.property("gpios")?; + Some(GpioIter { + gpios, + options, + offset: 0, + }) + } + + /// Returns a `gpios` entry with given index + pub fn gpio(&self, options: &DeviceTreeGpioPins, index: usize) -> Option { + self.gpios(options)?.nth(index) + } + /// Returns an input `clock` handle for a given reset name pub fn named_clock(&self, name: &str) -> Option { self.property("clock-names")? diff --git a/kernel/lib/device-tree/src/driver/util.rs b/kernel/lib/device-tree/src/driver/util.rs index f257b8f3..83c50ec1 100644 --- a/kernel/lib/device-tree/src/driver/util.rs +++ b/kernel/lib/device-tree/src/driver/util.rs @@ -1,12 +1,15 @@ //! General helpers and utilities for device tree drivers use alloc::sync::Arc; -use device_api::interrupt::{FullIrq, MessageInterruptController}; +use device_api::{ + gpio::{GpioPinConfig, GpioPinLevel, InputPinBias, OutputPinBias}, + interrupt::{FullIrq, MessageInterruptController}, +}; use fdt_rs::prelude::PropReader; use yggdrasil_abi::net::MacAddress; use crate::{ - driver::{lookup_phandle, map_interrupt_at}, + driver::{lookup_phandle, map_interrupt_at, DeviceTreeGpioPins}, DeviceTreePropertyRead, TProp, }; @@ -272,3 +275,40 @@ pub fn read_mac_address(node: &Arc) -> Option { let mac_bytes: [u8; 6] = property.raw().try_into().ok()?; Some(MacAddress::from(mac_bytes)) } + +/// Parse a generic gpio configuration from the device tree 1-cell format +pub fn generic_gpio_config(cell: u32, gpio: &DeviceTreeGpioPins) -> GpioPinConfig { + const GPIO_ACTIVE_LOW: u32 = 1 << 0; + const GPIO_SINGLE_ENDED: u32 = 1 << 1; + const GPIO_LINE_OPEN_DRAIN: u32 = 1 << 2; + const GPIO_PULL_UP: u32 = 1 << 4; + const GPIO_PULL_DOWN: u32 = 1 << 5; + const GPIO_PULL_DISABLE: u32 = 1 << 6; + const GPIO_OPEN_DRAIN: u32 = GPIO_LINE_OPEN_DRAIN | GPIO_SINGLE_ENDED; + + let active_level = if cell & GPIO_ACTIVE_LOW != 0 { + GpioPinLevel::Low + } else { + GpioPinLevel::High + }; + let output_bias = match cell & (GPIO_SINGLE_ENDED | GPIO_LINE_OPEN_DRAIN) { + 0 => Some(OutputPinBias::PushPull), + GPIO_OPEN_DRAIN | GPIO_SINGLE_ENDED => Some(OutputPinBias::OpenDrain), + _ => None, + }; + let input_bias = match cell & (GPIO_PULL_UP | GPIO_PULL_DOWN | GPIO_PULL_DISABLE) { + GPIO_PULL_DISABLE => Some(InputPinBias::Floating), + GPIO_PULL_UP => Some(InputPinBias::PullUp), + GPIO_PULL_DOWN => Some(InputPinBias::PullDown), + _ => None, + }; + + GpioPinConfig { + input: gpio.input, + output: gpio.output, + initial_level: gpio.initial_level, + input_bias, + output_bias, + active_level, + } +} diff --git a/kernel/libk/src/event.rs b/kernel/libk/src/event.rs new file mode 100644 index 00000000..58190a7b --- /dev/null +++ b/kernel/libk/src/event.rs @@ -0,0 +1,44 @@ +use core::sync::atomic::{AtomicU64, Ordering}; + +use alloc::{boxed::Box, collections::BTreeMap}; + +use device_api::gpio::PinHandle; +use libk_util::{ring::LossyRingQueue, sync::spin_rwlock::IrqSafeRwLock}; +use yggdrasil_abi::error::Error; + +static GPIO_EVENT_QUEUE: LossyRingQueue = LossyRingQueue::with_capacity(64); +static GPIO_EVENT_MAP: IrqSafeRwLock>> = + IrqSafeRwLock::new(BTreeMap::new()); +static GPIO_EVENT_ID: AtomicU64 = AtomicU64::new(1); + +pub trait GpioEvent { + fn configure_with_interrupt(&self, handler: Box) -> Result<(), Error>; +} + +impl GpioEvent for PinHandle { + fn configure_with_interrupt(&self, handler: Box) -> Result<(), Error> { + let ev = bind_gpio_event(handler); + self.parent.setup_gpio(self, Some(ev)) + } +} + +pub async fn gpio_event_handler_task() { + loop { + let id = GPIO_EVENT_QUEUE.read().await; + + let lock = GPIO_EVENT_MAP.read(); + if let Some(handler) = lock.get(&id) { + handler(); + } + } +} + +pub fn bind_gpio_event(handler: Box) -> u64 { + let id = GPIO_EVENT_ID.fetch_add(1, Ordering::Acquire); + GPIO_EVENT_MAP.write().insert(id, handler); + id +} + +pub fn signal_gpio_event(ev: u64) { + GPIO_EVENT_QUEUE.write(ev); +} diff --git a/kernel/libk/src/lib.rs b/kernel/libk/src/lib.rs index 6edd90dc..06543138 100644 --- a/kernel/libk/src/lib.rs +++ b/kernel/libk/src/lib.rs @@ -36,6 +36,7 @@ pub mod arch; pub mod config; pub mod debug; pub mod device; +pub mod event; pub mod fs; pub mod module; pub mod random; diff --git a/kernel/src/device/gpio.rs b/kernel/src/device/gpio.rs new file mode 100644 index 00000000..6c6e003f --- /dev/null +++ b/kernel/src/device/gpio.rs @@ -0,0 +1,130 @@ +//! GPIO drivers + +use abi::error::Error; +use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; +use device_api::{ + device::{Device, DeviceInitContext}, + gpio::{GpioPinLevel, PinHandle}, +}; +use device_tree::driver::{device_tree_driver, DeviceTreeGpioPins, Node, ProbeContext}; +use libk::event::GpioEvent; + +struct LedPin { + name: String, + handle: PinHandle, +} + +struct KeyPin { + name: String, + handle: PinHandle, +} + +trait Pin: Sized + Send + Sync + 'static { + fn from_device_tree(node: &Arc) -> Option; + fn configure(&self) -> Result<(), Error>; + fn name(&self) -> &str; +} + +struct PinGroup { + name: String, + pins: Vec

, +} + +impl PinGroup

{ + fn from_device_tree(node: &Arc) -> Option> { + let pins = node + .children() + .filter_map(P::from_device_tree) + .collect::>(); + if pins.is_empty() { + return None; + } + let name = node.name().unwrap_or("pin-group"); + Some(Arc::new(Self { + pins, + name: name.into(), + })) + } +} + +impl Device for PinGroup

{ + unsafe fn init(self: Arc, _cx: DeviceInitContext) -> Result<(), Error> { + for pin in self.pins.iter() { + if let Err(error) = pin.configure() { + log::error!("gpio {} setup error: {:?}", pin.name(), error); + } + } + Ok(()) + } + + fn display_name(&self) -> &str { + &self.name + } +} + +impl Pin for LedPin { + fn from_device_tree(node: &Arc) -> Option { + let handle = node.gpio( + &DeviceTreeGpioPins { + input: false, + output: true, + initial_level: GpioPinLevel::Low, + }, + 0, + )?; + let name = node.name().unwrap_or("gpio-led").into(); + Some(Self { name, handle }) + } + + fn configure(&self) -> Result<(), Error> { + self.handle.configure() + } + + fn name(&self) -> &str { + &self.name + } +} + +impl Pin for KeyPin { + fn from_device_tree(node: &Arc) -> Option { + let handle = node.gpio( + &DeviceTreeGpioPins { + input: true, + output: false, + initial_level: GpioPinLevel::Low, + }, + 0, + )?; + let name = node.name().unwrap_or("gpio-key").into(); + Some(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 name(&self) -> &str { + &self.name + } +} + +device_tree_driver! { + compatible: ["gpio-leds"], + driver: { + fn probe(&self, node: &Arc, _context: &mut ProbeContext) -> Option> { + PinGroup::::from_device_tree(node) + } + } +} + +device_tree_driver! { + compatible: ["gpio-keys"], + driver: { + fn probe(&self, node: &Arc, _context: &mut ProbeContext) -> Option> { + PinGroup::::from_device_tree(node) + } + } +} diff --git a/kernel/src/device/mod.rs b/kernel/src/device/mod.rs index e85f4c23..befceac4 100644 --- a/kernel/src/device/mod.rs +++ b/kernel/src/device/mod.rs @@ -6,6 +6,7 @@ use libk_util::OneTimeInit; pub mod bus; pub mod clock; pub mod display; +pub mod gpio; pub mod interrupt; pub mod mbox; pub mod pinctrl; diff --git a/kernel/src/device/pinctrl/bcm2711_gpio.rs b/kernel/src/device/pinctrl/bcm2711_gpio.rs index 5fc6c381..1e6e807c 100644 --- a/kernel/src/device/pinctrl/bcm2711_gpio.rs +++ b/kernel/src/device/pinctrl/bcm2711_gpio.rs @@ -2,12 +2,17 @@ use abi::error::Error; use alloc::sync::Arc; use device_api::{ device::{Device, DeviceInitContext}, - interrupt::FullIrq, + gpio::{GpioController, PinHandle}, + interrupt::{FullIrq, InterruptHandler, IrqVector}, }; use device_tree::{ - driver::{device_tree_driver, DeviceTreePinController, Node, ProbeContext}, - DeviceTreePropertyRead, + driver::{ + device_tree_driver, util::generic_gpio_config, DeviceTreeGpioPins, DeviceTreePinController, + Node, ProbeContext, + }, + DeviceTreePropertyRead, TProp, }; +use libk::device::external_interrupt_controller; use libk_mm::device::DeviceMemoryIo; use libk_util::sync::IrqSafeSpinlock; use tock_registers::{ @@ -137,6 +142,12 @@ impl Device for Bcm2711Gpio { regs.configure_pin_interrupts(pin, false, false, false, false); } + let intc = external_interrupt_controller()?; + for irq in self.irqs.iter() { + intc.register_irq(irq.irq, irq.options, self.clone())?; + intc.enable_irq(irq.irq)?; + } + Ok(()) } @@ -145,6 +156,33 @@ impl Device for Bcm2711Gpio { } } +impl InterruptHandler for Bcm2711Gpio { + fn handle_irq(self: Arc, _vector: IrqVector) -> bool { + log::warn!("TODO: handle bcm2711-gpio interrupts"); + false + } +} + +impl GpioController for Bcm2711Gpio { + fn setup_gpio(&self, pin: &PinHandle, _event: Option) -> Result<(), Error> { + log::warn!( + "TOOD: bcm2711 gpio pin #{} setup: input={}, output={}", + pin.index, + pin.config.input, + pin.config.output + ); + Ok(()) + } + + fn read_gpio(&self, _pin: &PinHandle) -> Result { + todo!() + } + + fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> { + todo!() + } +} + impl DeviceTreePinController for Bcm2711Gpio { fn configure_pin_group(self: Arc, group: &Arc) -> Result<(), Error> { let pins = group.property("brcm,pins").ok_or(Error::InvalidArgument)?; @@ -171,6 +209,24 @@ impl DeviceTreePinController for Bcm2711Gpio { Ok(()) } + + fn map_gpio( + self: Arc, + property: &TProp, + offset: usize, + info: &DeviceTreeGpioPins, + ) -> Option<(PinHandle, usize)> { + let (pin, options) = property.read_cells_at(offset, (1, 1))?; + let config = generic_gpio_config(options as u32, info); + Some(( + PinHandle { + index: pin as u32, + config, + parent: self, + }, + 2, + )) + } } device_tree_driver! { diff --git a/kernel/src/device/pinctrl/jh7110_pinctrl.rs b/kernel/src/device/pinctrl/jh7110_pinctrl.rs index f3fce114..0e476f80 100644 --- a/kernel/src/device/pinctrl/jh7110_pinctrl.rs +++ b/kernel/src/device/pinctrl/jh7110_pinctrl.rs @@ -3,14 +3,15 @@ use alloc::{sync::Arc, vec::Vec}; use device_api::{ clock::{ClockHandle, ResetHandle}, device::{Device, DeviceInitContext}, + gpio::{GpioController, PinHandle}, }; use device_tree::{ driver::{ device_tree_driver, - util::{GenericPinctrlBiasConfig, GenericPinctrlConfig}, - DeviceTreePinController, Node, ProbeContext, + util::{generic_gpio_config, GenericPinctrlBiasConfig, GenericPinctrlConfig}, + DeviceTreeGpioPins, DeviceTreePinController, Node, ProbeContext, }, - DeviceTreePropertyRead, + DeviceTreePropertyRead, TProp, }; use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIoMut}; use libk_util::{sync::IrqSafeSpinlock, OneTimeInit}; @@ -267,6 +268,26 @@ impl Device for Gpio { } } +impl GpioController for Gpio { + fn setup_gpio(&self, pin: &PinHandle, _event: Option) -> Result<(), Error> { + log::warn!( + "TODO: setup gpio #{} input={}, output={}", + pin.index, + pin.config.input, + pin.config.output + ); + Err(Error::NotImplemented) + } + + fn read_gpio(&self, _pin: &PinHandle) -> Result { + Err(Error::NotImplemented) + } + + fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> { + Err(Error::NotImplemented) + } +} + impl DeviceTreePinController for Gpio { fn configure_pin_group(self: Arc, pins: &Arc) -> Result<(), Error> { self.ensure_init()?; @@ -283,6 +304,24 @@ impl DeviceTreePinController for Gpio { } Ok(()) } + + fn map_gpio( + self: Arc, + property: &TProp, + offset: usize, + info: &DeviceTreeGpioPins, + ) -> Option<(PinHandle, usize)> { + let (pin, options) = property.read_cells_at(offset, (1, 1))?; + let config = generic_gpio_config(options as u32, info); + Some(( + PinHandle { + index: pin as u32, + config, + parent: self, + }, + 2, + )) + } } impl GpioRegs for SysRegs { diff --git a/kernel/src/device/pinctrl/pl061.rs b/kernel/src/device/pinctrl/pl061.rs index e4308b1d..0137bb61 100644 --- a/kernel/src/device/pinctrl/pl061.rs +++ b/kernel/src/device/pinctrl/pl061.rs @@ -1,13 +1,25 @@ +use core::sync::atomic::{AtomicU64, Ordering}; + use abi::error::Error; use alloc::{sync::Arc, vec::Vec}; use device_api::{ clock::{ClockHandle, ResetHandle}, device::{Device, DeviceInitContext}, + gpio::{GpioController, GpioPinLevel, PinHandle, SinglePinDirection}, + interrupt::{FullIrq, InterruptHandler, IrqVector}, }; -use device_tree::driver::{device_tree_driver, DeviceTreePinController, Node, ProbeContext}; +use device_tree::{ + driver::{ + device_tree_driver, util::generic_gpio_config, DeviceTreeGpioPins, DeviceTreePinController, + Node, ProbeContext, + }, + DeviceTreePropertyRead, TProp, +}; +use libk::{device::external_interrupt_controller, event::signal_gpio_event}; use libk_mm::device::DeviceMemoryIo; use libk_util::sync::IrqSafeSpinlock; use tock_registers::{ + interfaces::{Readable, Writeable}, register_structs, registers::{ReadOnly, ReadWrite, WriteOnly}, }; @@ -35,8 +47,10 @@ register_structs! { struct Pl061 { #[allow(unused)] regs: IrqSafeSpinlock>, + irq: FullIrq, clocks: Vec, resets: Vec, + gpio_events: [AtomicU64; 8], } impl Device for Pl061 { @@ -47,6 +61,14 @@ impl Device for Pl061 { for reset in self.resets.iter() { reset.deassert()?; } + + Ok(()) + } + + unsafe fn init_irq(self: Arc) -> Result<(), Error> { + let intc = external_interrupt_controller()?; + intc.register_irq(self.irq.irq, self.irq.options, self.clone())?; + intc.enable_irq(self.irq.irq)?; Ok(()) } @@ -55,11 +77,113 @@ impl Device for Pl061 { } } +impl InterruptHandler for Pl061 { + fn handle_irq(self: Arc, _vector: IrqVector) -> bool { + let status = { + let lock = self.regs.lock(); + let val = lock.GPIOMIS.get(); + lock.GPIOIC.set(0xFF); + val + }; + + for bit in 0..8 { + let ev = self.gpio_events[bit].load(Ordering::Acquire); + if ev != 0 { + signal_gpio_event(ev); + } + } + + status != 0 + } +} + +impl GpioController for Pl061 { + fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> { + todo!() + } + + fn read_gpio(&self, _pin: &PinHandle) -> Result { + todo!() + } + + fn setup_gpio(&self, pin: &PinHandle, event: Option) -> Result<(), Error> { + let regs = self.regs.lock(); + + let direction = pin + .config + .force_single_direction() + .ok_or(Error::InvalidArgument) + .inspect_err(|_| { + log::warn!( + "pl061: gpio #{} has invalid direction input={}, output={}", + pin.index, + pin.config.input, + pin.config.output + ) + })?; + + // Enable software control + regs.GPIOAFSEL.set(regs.GPIOAFSEL.get() & !(1 << pin.index)); + + match direction { + SinglePinDirection::Output => { + log::info!("pl061: gpio #{} set as output", pin.index); + regs.GPIODIR.set(regs.GPIODIR.get() | (1 << pin.index)); + // Disable interrupt + regs.GPIOIE.set(regs.GPIOIE.get() & !(1 << pin.index)); + + let level = match pin.config.initial_level { + GpioPinLevel::Low => 0, + GpioPinLevel::High => 1, + }; + let mut val = regs.GPIODATA[0].get(); + val &= !(1 << pin.index); + val |= level << pin.index; + regs.GPIODATA[0].set(val); + } + SinglePinDirection::Input => { + log::info!("pl061: gpio #{} set as input", pin.index); + 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); + + 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)); + regs.GPIOIE.set(regs.GPIOIE.get() | (1 << pin.index)); + regs.GPIOIC.set(1 << pin.index); + } + } + } + + Ok(()) + } +} + impl DeviceTreePinController for Pl061 { fn configure_pin_group(self: Arc, _pins: &Arc) -> Result<(), Error> { // TODO implement this when I get some board with this pinctrl todo!() } + + fn map_gpio( + self: Arc, + property: &TProp, + offset: usize, + info: &DeviceTreeGpioPins, + ) -> Option<(PinHandle, usize)> { + let (pin, options) = property.read_cells_at(offset, (1, 1))?; + let config = generic_gpio_config(options as u32, info); + Some(( + PinHandle { + index: pin as u32, + config, + parent: self, + }, + 2, + )) + } } device_tree_driver! { @@ -76,14 +200,17 @@ device_tree_driver! { } else { Vec::new() }; + let irq = node.interrupt(0)?; let base = node.map_base(context, 0)?; let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?; let pinctrl = Arc::new(Pl061 { regs: IrqSafeSpinlock::new(regs), + irq, clocks, resets, + gpio_events: [const { AtomicU64::new(0) }; 8] }); node.make_pin_controller(pinctrl.clone()); diff --git a/kernel/src/init.rs b/kernel/src/init.rs index f49223d9..c1865db1 100644 --- a/kernel/src/init.rs +++ b/kernel/src/init.rs @@ -37,6 +37,7 @@ pub fn kinit() -> Result<(), Error> { #[cfg(any(rust_analyzer, target_arch = "riscv64", target_arch = "aarch64"))] { use device_tree::driver::InitSequence; + use libk::event; device_tree::driver::lazy_init( |_| (), @@ -55,6 +56,8 @@ pub fn kinit() -> Result<(), Error> { }, InitSequence::Late, ); + + runtime::spawn(event::gpio_event_handler_task()).ok(); } // Initialize PCI devices if let Err(error) = PciBusManager::setup_bus_devices(false) {