pub use embedded_hal::digital::PinState; use crate::ioconfig::FilterClockSelect; use crate::ioconfig::FilterType; #[cfg(feature = "vor1x")] use crate::{PeripheralSelect, sysconfig::enable_peripheral_clock}; pub use crate::InvalidOffsetError; pub use crate::Port; pub use crate::ioconfig::regs::Pull; use crate::ioconfig::regs::{FunctionSelect, IoConfig, MmioIoConfig}; use crate::pins::PinId; use super::Pin; #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InterruptEdge { HighToLow, LowToHigh, BothEdges, } #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InterruptLevel { Low = 0, High = 1, } /// Pin identifier for all physical pins exposed by Vorago MCUs. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DynPinId { port: Port, /// Offset within the port. offset: u8, } #[derive(Debug, thiserror::Error)] #[cfg(feature = "vor4x")] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[error("port G does not support interrupts")] pub struct PortDoesNotSupportInterrupts; impl DynPinId { /// Unchecked constructor which panics on invalid offsets. pub const fn new_unchecked(port: Port, offset: usize) -> Self { if offset >= port.max_offset() { panic!("Pin ID construction: offset is out of range"); } DynPinId { port, offset: offset as u8, } } pub const fn new(port: Port, offset: usize) -> Result { if offset >= port.max_offset() { return Err(InvalidOffsetError { offset, port }); } Ok(DynPinId { port, offset: offset as u8, }) } pub const fn port(&self) -> Port { self.port } pub const fn offset(&self) -> usize { self.offset as usize } /// This function panics if the port is [Port::G]. #[cfg(feature = "vor4x")] pub fn irq(&self) -> Result { if self.port() == Port::G { return Err(PortDoesNotSupportInterrupts); } Ok(self.irq_unchecked()) } /// This function panics if the port is [Port::G]. #[cfg(feature = "vor4x")] pub const fn irq_unchecked(&self) -> va416xx::Interrupt { match self.port() { Port::A => match self.offset() { 0 => va416xx::Interrupt::PORTA0, 1 => va416xx::Interrupt::PORTA1, 2 => va416xx::Interrupt::PORTA2, 3 => va416xx::Interrupt::PORTA3, 4 => va416xx::Interrupt::PORTA4, 5 => va416xx::Interrupt::PORTA5, 6 => va416xx::Interrupt::PORTA6, 7 => va416xx::Interrupt::PORTA7, 8 => va416xx::Interrupt::PORTA8, 9 => va416xx::Interrupt::PORTA9, 10 => va416xx::Interrupt::PORTA10, 11 => va416xx::Interrupt::PORTA11, 12 => va416xx::Interrupt::PORTA12, 13 => va416xx::Interrupt::PORTA13, 14 => va416xx::Interrupt::PORTA14, 15 => va416xx::Interrupt::PORTA15, _ => unreachable!(), }, Port::B => match self.offset() { 0 => va416xx::Interrupt::PORTB0, 1 => va416xx::Interrupt::PORTB1, 2 => va416xx::Interrupt::PORTB2, 3 => va416xx::Interrupt::PORTB3, 4 => va416xx::Interrupt::PORTB4, 5 => va416xx::Interrupt::PORTB5, 6 => va416xx::Interrupt::PORTB6, 7 => va416xx::Interrupt::PORTB7, 8 => va416xx::Interrupt::PORTB8, 9 => va416xx::Interrupt::PORTB9, 10 => va416xx::Interrupt::PORTB10, 11 => va416xx::Interrupt::PORTB11, 12 => va416xx::Interrupt::PORTB12, 13 => va416xx::Interrupt::PORTB13, 14 => va416xx::Interrupt::PORTB14, 15 => va416xx::Interrupt::PORTB15, _ => unreachable!(), }, Port::C => match self.offset() { 0 => va416xx::Interrupt::PORTC0, 1 => va416xx::Interrupt::PORTC1, 2 => va416xx::Interrupt::PORTC2, 3 => va416xx::Interrupt::PORTC3, 4 => va416xx::Interrupt::PORTC4, 5 => va416xx::Interrupt::PORTC5, 6 => va416xx::Interrupt::PORTC6, 7 => va416xx::Interrupt::PORTC7, 8 => va416xx::Interrupt::PORTC8, 9 => va416xx::Interrupt::PORTC9, 10 => va416xx::Interrupt::PORTC10, 11 => va416xx::Interrupt::PORTC11, 12 => va416xx::Interrupt::PORTC12, 13 => va416xx::Interrupt::PORTC13, 14 => va416xx::Interrupt::PORTC14, 15 => va416xx::Interrupt::PORTC15, _ => unreachable!(), }, Port::D => match self.offset() { 0 => va416xx::Interrupt::PORTD0, 1 => va416xx::Interrupt::PORTD1, 2 => va416xx::Interrupt::PORTD2, 3 => va416xx::Interrupt::PORTD3, 4 => va416xx::Interrupt::PORTD4, 5 => va416xx::Interrupt::PORTD5, 6 => va416xx::Interrupt::PORTD6, 7 => va416xx::Interrupt::PORTD7, 8 => va416xx::Interrupt::PORTD8, 9 => va416xx::Interrupt::PORTD9, 10 => va416xx::Interrupt::PORTD10, 11 => va416xx::Interrupt::PORTD11, 12 => va416xx::Interrupt::PORTD12, 13 => va416xx::Interrupt::PORTD13, 14 => va416xx::Interrupt::PORTD14, 15 => va416xx::Interrupt::PORTD15, _ => unreachable!(), }, Port::E => match self.offset() { 0 => va416xx::Interrupt::PORTE0, 1 => va416xx::Interrupt::PORTE1, 2 => va416xx::Interrupt::PORTE2, 3 => va416xx::Interrupt::PORTE3, 4 => va416xx::Interrupt::PORTE4, 5 => va416xx::Interrupt::PORTE5, 6 => va416xx::Interrupt::PORTE6, 7 => va416xx::Interrupt::PORTE7, 8 => va416xx::Interrupt::PORTE8, 9 => va416xx::Interrupt::PORTE9, 10 => va416xx::Interrupt::PORTE10, 11 => va416xx::Interrupt::PORTE11, 12 => va416xx::Interrupt::PORTE12, 13 => va416xx::Interrupt::PORTE13, 14 => va416xx::Interrupt::PORTE14, 15 => va416xx::Interrupt::PORTE15, _ => unreachable!(), }, Port::F => match self.offset() { 0 => va416xx::Interrupt::PORTF0, 1 => va416xx::Interrupt::PORTF1, 2 => va416xx::Interrupt::PORTF2, 3 => va416xx::Interrupt::PORTF3, 4 => va416xx::Interrupt::PORTF4, 5 => va416xx::Interrupt::PORTF5, 6 => va416xx::Interrupt::PORTF6, 7 => va416xx::Interrupt::PORTF7, 8 => va416xx::Interrupt::PORTF8, 9 => va416xx::Interrupt::PORTF9, 10 => va416xx::Interrupt::PORTF10, 11 => va416xx::Interrupt::PORTF11, 12 => va416xx::Interrupt::PORTF12, 13 => va416xx::Interrupt::PORTF13, 14 => va416xx::Interrupt::PORTF14, 15 => va416xx::Interrupt::PORTF15, _ => unreachable!(), }, Port::G => panic!("port G does not have interrupts"), } } } /// Low-level driver structure for GPIO pins. pub struct LowLevelGpio { gpio: super::regs::MmioGpio<'static>, ioconfig: MmioIoConfig<'static>, id: DynPinId, } impl core::fmt::Debug for LowLevelGpio { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("LowLevelGpio") .field("gpio", &self.gpio.port()) .field("id", &self.id) .finish() } } impl LowLevelGpio { /// Create a new low-level GPIO pin instance from a given [Pin]. /// /// Can be used for performing resource management of the [Pin]s. pub fn new_with_pin(_pin: Pin) -> Self { Self::new(I::ID) } /// Create a new low-level GPIO pin instance using only the [PinId]. pub fn new(id: DynPinId) -> Self { LowLevelGpio { gpio: super::regs::Gpio::new_mmio(id.port), ioconfig: IoConfig::new_mmio(), id, } } #[inline] pub fn id(&self) -> DynPinId { self.id } #[inline] pub fn port(&self) -> Port { self.id.port() } #[inline] pub fn offset(&self) -> usize { self.id.offset() } pub fn configure_as_input_floating(&mut self) { self.ioconfig.modify_pin_config(self.id, |mut config| { config.set_funsel(FunctionSelect::Sel0); config.set_io_disable(false); config.set_invert_input(false); config.set_open_drain(false); config.set_pull_enable(false); config.set_pull_when_output_active(false); config.set_invert_output(false); config.set_input_enable_when_output(false); config }); self.gpio.modify_dir(|mut dir| { dir &= !(1 << self.id.offset()); dir }); } pub fn configure_as_input_with_pull(&mut self, pull: Pull) { self.ioconfig.modify_pin_config(self.id, |mut config| { config.set_funsel(FunctionSelect::Sel0); config.set_io_disable(false); config.set_invert_input(false); config.set_open_drain(false); config.set_pull_enable(true); config.set_pull_dir(pull); config.set_pull_when_output_active(false); config.set_invert_output(false); config.set_input_enable_when_output(false); config }); self.gpio.modify_dir(|mut dir| { dir &= !(1 << self.id.offset()); dir }); } pub fn configure_as_output_push_pull(&mut self, init_level: PinState) { self.ioconfig.modify_pin_config(self.id, |mut config| { config.set_funsel(FunctionSelect::Sel0); config.set_io_disable(false); config.set_invert_input(false); config.set_open_drain(false); config.set_pull_enable(false); config.set_pull_when_output_active(false); config.set_invert_output(false); config.set_input_enable_when_output(true); config }); match init_level { PinState::Low => self.gpio.write_clr_out(self.mask_32()), PinState::High => self.gpio.write_set_out(self.mask_32()), } self.gpio.modify_dir(|mut dir| { dir |= 1 << self.id.offset(); dir }); } pub fn configure_as_output_open_drain(&mut self, init_level: PinState) { self.ioconfig.modify_pin_config(self.id, |mut config| { config.set_funsel(FunctionSelect::Sel0); config.set_io_disable(false); config.set_invert_input(false); config.set_open_drain(true); config.set_pull_enable(true); config.set_pull_dir(Pull::Up); config.set_pull_when_output_active(false); config.set_invert_output(false); config.set_input_enable_when_output(true); config }); let mask32 = self.mask_32(); match init_level { PinState::Low => self.gpio.write_clr_out(mask32), PinState::High => self.gpio.write_set_out(mask32), } self.gpio.modify_dir(|mut dir| { dir |= mask32; dir }); } pub fn configure_as_peripheral_pin(&mut self, fun_sel: FunctionSelect, pull: Option) { self.ioconfig.modify_pin_config(self.id, |mut config| { config.set_funsel(fun_sel); config.set_io_disable(false); config.set_invert_input(false); config.set_open_drain(false); config.set_pull_enable(pull.is_some()); config.set_pull_dir(pull.unwrap_or(Pull::Up)); config.set_invert_output(false); config }); } #[inline] pub fn is_high(&self) -> bool { (self.gpio.read_data_in() >> self.offset()) & 1 == 1 } #[inline] pub fn is_low(&self) -> bool { !self.is_high() } #[inline] pub fn set_high(&mut self) { self.gpio.write_set_out(self.mask_32()); } #[inline] pub fn set_low(&mut self) { self.gpio.write_clr_out(self.mask_32()); } #[inline] pub fn is_set_high(&self) -> bool { (self.gpio.read_data_out() >> self.offset()) & 1 == 1 } #[inline] pub fn is_set_low(&self) -> bool { !self.is_set_high() } #[inline] pub fn toggle(&mut self) { self.gpio.write_tog_out(self.mask_32()); } #[cfg(feature = "vor1x")] pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) { if irq_cfg.route { self.configure_irqsel(irq_cfg.id); } if irq_cfg.enable_in_nvic { unsafe { crate::enable_nvic_interrupt(irq_cfg.id) }; } self.gpio.modify_irq_enable(|mut value| { value |= 1 << self.id.offset; value }); } #[cfg(feature = "vor4x")] pub fn enable_interrupt( &mut self, enable_in_nvic: bool, ) -> Result<(), PortDoesNotSupportInterrupts> { if enable_in_nvic { unsafe { crate::enable_nvic_interrupt(self.id().irq_unchecked()) }; } self.gpio.modify_irq_enable(|mut value| { value |= 1 << self.id.offset; value }); Ok(()) } #[cfg(feature = "vor1x")] pub fn disable_interrupt(&mut self, reset_irqsel: bool) { if reset_irqsel { self.reset_irqsel(); } // We only manipulate our own bit. self.gpio.modify_irq_enable(|mut value| { value &= !(1 << self.id.offset); value }); } #[cfg(feature = "vor4x")] pub fn disable_interrupt(&mut self) { self.gpio.modify_irq_enable(|mut value| { value &= !(1 << self.id.offset); value }); } /// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure /// When using edge mode, it is possible to generate interrupts on both edges as well #[inline] pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) { let mask32 = self.mask_32(); self.gpio.modify_irq_sen(|mut value| { value &= !mask32; value }); match edge_type { InterruptEdge::HighToLow => { self.gpio.modify_irq_evt(|mut value| { value &= !mask32; value }); } InterruptEdge::LowToHigh => { self.gpio.modify_irq_evt(|mut value| { value |= mask32; value }); } InterruptEdge::BothEdges => { self.gpio.modify_irq_edge(|mut value| { value |= mask32; value }); } } } /// Configure which edge or level type triggers an interrupt #[inline] pub fn configure_level_interrupt(&mut self, level: InterruptLevel) { let mask32 = self.mask_32(); self.gpio.modify_irq_sen(|mut value| { value |= mask32; value }); if level == InterruptLevel::Low { self.gpio.modify_irq_evt(|mut value| { value &= !mask32; value }); } else { self.gpio.modify_irq_evt(|mut value| { value |= mask32; value }); } } /// Only useful for input pins #[inline] pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClockSelect) { self.ioconfig.modify_pin_config(self.id, |mut config| { config.set_filter_type(filter); config.set_filter_clk_sel(clksel); config }); } /// Only useful for output pins. #[inline] pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) { self.gpio.modify_pulse(|mut value| { if enable { value |= 1 << self.id.offset; } else { value &= !(1 << self.id.offset); } value }); self.gpio.modify_pulsebase(|mut value| { if default_state == PinState::High { value |= 1 << self.id.offset; } else { value &= !(1 << self.id.offset); } value }); } /// Only useful for output pins #[inline] pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) { self.gpio.modify_delay1(|mut value| { if delay_1 { value |= 1 << self.id.offset; } else { value &= !(1 << self.id.offset); } value }); self.gpio.modify_delay2(|mut value| { if delay_2 { value |= 1 << self.id.offset; } else { value &= !(1 << self.id.offset); } value }); } #[cfg(feature = "vor1x")] /// Configure the IRQSEL peripheral for this particular pin with the given interrupt ID. pub fn configure_irqsel(&mut self, id: va108xx::Interrupt) { let irqsel = unsafe { va108xx::Irqsel::steal() }; enable_peripheral_clock(PeripheralSelect::Irqsel); match self.id().port() { // Set the correct interrupt number in the IRQSEL register super::Port::A => { irqsel .porta(self.id().offset()) .write(|w| unsafe { w.bits(id as u32) }); } super::Port::B => { irqsel .portb(self.id().offset()) .write(|w| unsafe { w.bits(id as u32) }); } } } #[cfg(feature = "vor1x")] /// Reset the IRQSEL peripheral value for this particular pin. pub fn reset_irqsel(&mut self) { let irqsel = unsafe { va108xx::Irqsel::steal() }; enable_peripheral_clock(PeripheralSelect::Irqsel); match self.id().port() { // Set the correct interrupt number in the IRQSEL register super::Port::A => { irqsel .porta(self.id().offset()) .write(|w| unsafe { w.bits(u32::MAX) }); } super::Port::B => { irqsel .portb(self.id().offset()) .write(|w| unsafe { w.bits(u32::MAX) }); } } } #[inline(always)] pub const fn mask_32(&self) -> u32 { 1 << self.id.offset() } }