253 lines
7.3 KiB
Rust
253 lines
7.3 KiB
Rust
use alloc::sync::Arc;
|
|
use device_api::{
|
|
device::{Device, DeviceInitContext},
|
|
gpio::{GpioController, GpioInterruptEvent, PinHandle},
|
|
interrupt::{FullIrq, InterruptHandler, IrqVector},
|
|
};
|
|
use device_tree::{
|
|
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::{
|
|
interfaces::{Readable, Writeable},
|
|
register_structs,
|
|
registers::{ReadOnly, ReadWrite, WriteOnly},
|
|
};
|
|
use yggdrasil_abi::error::Error;
|
|
|
|
const PUPD_NONE: u32 = 0b00;
|
|
|
|
register_structs! {
|
|
#[allow(non_snake_case)]
|
|
Regs {
|
|
// Pin function select
|
|
(0x00 => GPFSEL: [ReadWrite<u32>; 6]),
|
|
(0x18 => _0),
|
|
// Set pin
|
|
(0x1C => GPSET: [WriteOnly<u32>; 2]),
|
|
(0x24 => _1),
|
|
// Clear pin
|
|
(0x28 => GPCLR: [WriteOnly<u32>; 2]),
|
|
(0x30 => _2),
|
|
// Current pin level
|
|
(0x34 => GPLEV: [ReadOnly<u32>; 2]),
|
|
(0x3C => _3),
|
|
// Pin event detect status
|
|
(0x40 => GPEDS: [ReadWrite<u32>; 2]),
|
|
(0x48 => _4),
|
|
// Pin rising edge event enable
|
|
(0x4C => GPREN: [ReadWrite<u32>; 2]),
|
|
(0x54 => _5),
|
|
// Pin falling edge event enable
|
|
(0x58 => GPFEN: [ReadWrite<u32>; 2]),
|
|
(0x60 => _6),
|
|
// Pin high event enable
|
|
(0x64 => GPHEN: [ReadWrite<u32>; 2]),
|
|
(0x6C => _7),
|
|
// Pin low event enable
|
|
(0x70 => GPLEN: [ReadWrite<u32>; 2]),
|
|
(0x78 => _8),
|
|
// Pin async rising edge event enable
|
|
(0x7C => GPAREN: [ReadWrite<u32>; 2]),
|
|
(0x84 => _9),
|
|
// Pin async falling edge event enable
|
|
(0x88 => GPAFEN: [ReadWrite<u32>; 2]),
|
|
(0x90 => _10),
|
|
// Pin pull up/down control
|
|
(0xE4 => GPIO_PUP_PDN_CNTRL_REG: [ReadWrite<u32>; 4]),
|
|
(0xF4 => _11),
|
|
(0x100 => @END),
|
|
}
|
|
}
|
|
|
|
struct Bcm2711Gpio {
|
|
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
|
|
#[allow(unused)]
|
|
irqs: [FullIrq; 2],
|
|
}
|
|
|
|
impl Regs {
|
|
fn write_fsel(&self, pin: usize, value: u32) {
|
|
let fsel_reg = pin / 10;
|
|
let fsel_shift = (pin % 10) * 3;
|
|
|
|
let mut fsel = self.GPFSEL[fsel_reg].get();
|
|
fsel &= !(0x7 << fsel_shift);
|
|
fsel |= (value & 0x7) << fsel_shift;
|
|
self.GPFSEL[fsel_reg].set(fsel);
|
|
}
|
|
|
|
fn write_pupd(&self, pin: usize, value: u32) {
|
|
let pupd_reg = pin / 16;
|
|
let pupd_shift = (pin % 16) * 2;
|
|
|
|
let mut pupd = self.GPIO_PUP_PDN_CNTRL_REG[pupd_reg].get();
|
|
pupd &= !(0x3 << pupd_shift);
|
|
pupd |= (value & 0x3) << pupd_shift;
|
|
self.GPIO_PUP_PDN_CNTRL_REG[pupd_reg].set(pupd);
|
|
}
|
|
|
|
fn configure_pin_interrupts(
|
|
&self,
|
|
pin: usize,
|
|
rising_edge: bool,
|
|
falling_edge: bool,
|
|
level_high: bool,
|
|
level_low: bool,
|
|
) {
|
|
#[inline]
|
|
fn modify_reg(reg: &ReadWrite<u32>, bit: u32, set: bool) {
|
|
if set {
|
|
reg.set(reg.get() | bit);
|
|
} else {
|
|
reg.set(reg.get() & !bit);
|
|
}
|
|
}
|
|
|
|
// TODO use async edge detection (likely have some limitations)?
|
|
let reg = pin / 32;
|
|
let bit = 1 << (pin % 32);
|
|
|
|
// Disable async edge events
|
|
modify_reg(&self.GPAREN[reg], bit, false);
|
|
modify_reg(&self.GPAFEN[reg], bit, false);
|
|
|
|
modify_reg(&self.GPREN[reg], bit, rising_edge);
|
|
modify_reg(&self.GPFEN[reg], bit, falling_edge);
|
|
modify_reg(&self.GPHEN[reg], bit, level_high);
|
|
modify_reg(&self.GPLEN[reg], bit, level_low);
|
|
|
|
// Clear interrupt status
|
|
self.GPEDS[reg].set(bit);
|
|
}
|
|
|
|
fn configure_pin_function(&self, pin: usize, function: u32, pull: u32) {
|
|
self.write_fsel(pin, function);
|
|
self.write_pupd(pin, pull);
|
|
}
|
|
}
|
|
|
|
impl Device for Bcm2711Gpio {
|
|
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
|
|
let regs = self.regs.lock();
|
|
|
|
// Disable all interrupts by default
|
|
for pin in 0..58 {
|
|
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(())
|
|
}
|
|
|
|
fn display_name(&self) -> &str {
|
|
"bcm2711-gpio"
|
|
}
|
|
}
|
|
|
|
impl InterruptHandler for Bcm2711Gpio {
|
|
fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
|
|
log::warn!("TODO: handle bcm2711-gpio interrupts");
|
|
false
|
|
}
|
|
}
|
|
|
|
impl GpioController for Bcm2711Gpio {
|
|
fn setup_gpio(&self, pin: &PinHandle, _event: Option<GpioInterruptEvent>) -> 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<bool, Error> {
|
|
Ok(false)
|
|
}
|
|
|
|
fn write_gpio(&self, _pin: &PinHandle, _value: bool) -> Result<(), Error> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl DeviceTreePinController for Bcm2711Gpio {
|
|
fn configure_pin_group(self: Arc<Self>, group: &Arc<Node>) -> Result<(), Error> {
|
|
let pins = group.property("brcm,pins").ok_or(Error::InvalidArgument)?;
|
|
let function = group.property("brcm,function").ok_or(Error::DoesNotExist)?;
|
|
let pull = group.property("brcm,pull");
|
|
|
|
if function.is_empty() || pins.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
let function = function.read_cell(0, 1).ok_or(Error::InvalidArgument)? as u32;
|
|
let regs = self.regs.lock();
|
|
|
|
for i in 0..pins.len() / 4 {
|
|
let pin = pins.read_cell(i, 1).ok_or(Error::InvalidArgument)? as u32;
|
|
let pull = if let Some(pull) = pull.as_ref().and_then(|p| p.read_cell(i, 1)) {
|
|
pull as u32
|
|
} else {
|
|
PUPD_NONE
|
|
};
|
|
log::info!("bcm2711-gpio: gpio{pin} function={function}");
|
|
regs.configure_pin_function(pin as usize, function, pull);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn map_gpio(
|
|
self: Arc<Self>,
|
|
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! {
|
|
compatible: ["brcm,bcm2711-gpio"],
|
|
driver: {
|
|
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
|
|
let irq0 = node.interrupt(0)?;
|
|
let irq1 = node.interrupt(1)?;
|
|
|
|
let base = node.map_base(context, 0)?;
|
|
let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?;
|
|
|
|
let gpio = Arc::new(Bcm2711Gpio {
|
|
regs: IrqSafeSpinlock::new(regs),
|
|
irqs: [irq0, irq1]
|
|
});
|
|
|
|
node.make_pin_controller(gpio.clone());
|
|
|
|
Some(gpio)
|
|
}
|
|
}
|
|
}
|