From f510333454555de9d0d8792fe14445e59384233d Mon Sep 17 00:00:00 2001 From: Nils Fitinghoff Date: Wed, 30 Oct 2024 15:58:13 +0100 Subject: [PATCH] Add imxrt1180 with rgpio --- .github/workflows/rust.yml | 5 + Cargo.toml | 1 + build.rs | 2 +- src/chip.rs | 1 + src/chip/drivers/rgpio.rs | 380 +++++++++++++++++++++++++++++++++++++ src/chip/imxrt1180.rs | 31 +++ 6 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 src/chip/drivers/rgpio.rs create mode 100644 src/chip/imxrt1180.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c29d0f1c..2ebdcc61 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -30,6 +30,11 @@ jobs: - imxrt-ral/imxrt1176_cm7 - imxrt-ral/imxrt1189_cm7 - imxrt-ral/imxrt1189_cm33 + # We can't lint the 1180 through lint-log. The logging package + # requires DMA-capable LPUART, and there's no DMA support for + # the 1180. + - imxrt-ral/imxrt1189_cm33,imxrt1180 + - imxrt-ral/imxrt1189_cm7,imxrt1180 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index 3402af93..8bd2e375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ imxrt1020 = ["imxrt-iomuxc/imxrt1020"] imxrt1060 = ["imxrt-iomuxc/imxrt1060"] imxrt1064 = ["imxrt-iomuxc/imxrt1060"] imxrt1170 = ["imxrt-iomuxc/imxrt1170"] +imxrt1180 = ["imxrt-iomuxc/imxrt1180"] ################ # Extra features diff --git a/build.rs b/build.rs index 3495bb8b..2f799e6b 100644 --- a/build.rs +++ b/build.rs @@ -26,7 +26,7 @@ fn features_10xx() -> HashSet { } fn features_11xx() -> HashSet { - ["imxrt1160", "imxrt1170"] + ["imxrt1160", "imxrt1170", "imxrt1180"] .iter() .map(ToString::to_string) .collect() diff --git a/src/chip.rs b/src/chip.rs index 0a2d0989..6d3d8fe1 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -4,6 +4,7 @@ #[cfg_attr(chip = "imxrt1020", path = "chip/imxrt1020.rs")] #[cfg_attr(chip = "imxrt1060", path = "chip/imxrt1060.rs")] #[cfg_attr(chip = "imxrt1170", path = "chip/imxrt1170.rs")] +#[cfg_attr(chip = "imxrt1180", path = "chip/imxrt1180.rs")] #[cfg_attr(chip = "none", path = "chip/none.rs")] pub(crate) mod selection; diff --git a/src/chip/drivers/rgpio.rs b/src/chip/drivers/rgpio.rs new file mode 100644 index 00000000..051a792f --- /dev/null +++ b/src/chip/drivers/rgpio.rs @@ -0,0 +1,380 @@ +//! General purpose I/O. +//! +//! Create a [`Port`](Port) over a RAL GPIO instance. Then, use the `Port` to +//! allocate GPIO outputs and inputs. +//! +//! Use [`Output`](Output) to drive GPIO outputs. Use [`Input`](Input) to read +//! GPIO pin states, and trigger interrupts when GPIO states change. +//! +//! # Interior mutability +//! +//! Methods on `Output` and `Input` take immutable references, `&self`. The hardware +//! guarantees that these operations can occur without data races. Methods that +//! require multiple operations on a register are implemented on the `Port`, and +//! take the GPIO by reference. +//! +//! # Example +//! +//! ```no_run +//! use imxrt_hal::gpio::Port; +//! use imxrt_ral::rgpio::GPIO2; +//! +//! let mut gpio2 = Port::new(unsafe { GPIO2::instance() }); +//! let gpio_b0_04 = // Handle to GPIO_B0_04 IOMUXC pin, provided by BSP or higher-level HAL... +//! # unsafe { imxrt_iomuxc::imxrt1060::gpio_b0::GPIO_B0_04::new() }; +//! +//! let output = gpio2.output(gpio_b0_04); +//! output.set(); +//! output.clear(); +//! output.toggle(); +//! +//! let input = gpio2.input(output.release()); +//! assert!(input.is_set()); +//! ``` + +use crate::{iomuxc, ral}; + +/// GPIO ports. +pub struct Port { + gpio: ral::rgpio::Instance, +} + +impl Port { + /// Create a GPIO port that can allocate and convert GPIOs. + pub fn new(gpio: ral::rgpio::Instance) -> Self { + Self { gpio } + } + + fn register_block(&self) -> &'static ral::rgpio::RegisterBlock { + let register_block: &ral::rgpio::RegisterBlock = &self.gpio; + // Safety: points to peripheral memory, which is static. + // Gpio implementation guarantees that memory which needs + // mutable access to shared GPIO registers passes through + // the Port type. + let register_block: &'static ral::rgpio::RegisterBlock = + unsafe { core::mem::transmute(register_block) }; + register_block + } + + /// Allocate an output GPIO. + pub fn output

(&mut self, mut pin: P) -> Output

+ where + P: iomuxc::gpio::Pin, + { + iomuxc::gpio::prepare(&mut pin); + Output::new(pin, self.register_block(), P::OFFSET) + } + + /// Allocate an input GPIO. + pub fn input

(&mut self, mut pin: P) -> Input

+ where + P: iomuxc::gpio::Pin, + { + iomuxc::gpio::prepare(&mut pin); + Input::new(pin, self.register_block(), P::OFFSET) + } +} + +/// An output GPIO. +pub struct Output

{ + pin: P, + // Logical ownership: + // - PDOR: read only + // - PDIR: read only + // - PSOR, PCOR, PTOR: write 1 to set value in PDOR + gpio: &'static ral::rgpio::RegisterBlock, + offset: u32, +} + +// Safety: an output pin is safe to send across execution contexts, +// because it points to static memory. +unsafe impl Send for Output

{} + +impl

Output

{ + fn new(pin: P, gpio: &'static ral::rgpio::RegisterBlock, offset: u32) -> Self { + let output = Self { pin, gpio, offset }; + ral::modify_reg!(ral::rgpio, gpio, PDDR, |gdir| gdir | output.mask()); + output + } + + const fn mask(&self) -> u32 { + 1 << self.offset + } + + /// Set the GPIO high. + pub fn set(&self) { + // Atomic write, OK to take immutable reference. + ral::write_reg!(ral::rgpio, self.gpio, PSOR, self.mask()); + } + + /// Set the GPIO low. + pub fn clear(&self) { + // Atomic write, OK to take immutable reference. + ral::write_reg!(ral::rgpio, self.gpio, PCOR, self.mask()); + } + + /// Alternate the GPIO pin output. + /// + /// `toggle` is implemented in hardware, so it will be more efficient + /// than implementing in software. + pub fn toggle(&self) { + // Atomic write, OK to take immutable reference. + ral::write_reg!(ral::rgpio, self.gpio, PTOR, self.mask()); + } + + /// Returns `true` if the GPIO is set. + pub fn is_set(&self) -> bool { + ral::read_reg!(ral::rgpio, self.gpio, PDOR) & self.mask() != 0 + } + + /// Returns `true` if the value of the pad is high. + /// + /// Can differ from [`is_set()`](Self::is_set), especially in an open drain config. + pub fn is_pad_high(&self) -> bool { + ral::read_reg!(ral::rgpio, self.gpio, PDIR) & self.mask() != 0 + } + + /// Release the underlying pin object. + pub fn release(self) -> P { + self.pin + } + + /// Access the underlying pin. + pub fn pin(&self) -> &P { + &self.pin + } + + /// Mutably access the underling pin. + pub fn pin_mut(&mut self) -> &mut P { + &mut self.pin + } +} + +impl Output<()> { + /// Allocate an output GPIO without a pin. + /// + /// Prefer using [`Port::output`](Port::output) to create a GPIO ouptut with a + /// pin resource. That method ensures that pin resources are managed throughout + /// your program, and that the pin is configured to operate as a GPIO output. + /// + /// You may use this method to allocate duplicate `Output` object for the same + /// physical GPIO output. This is considered safe, since the `Output` API is + /// reentrant. + /// + /// If you use this constructor, you're responsible for configuring the IOMUX + /// multiplexer register. + pub fn without_pin(port: &mut Port, offset: u32) -> Self { + Self::new((), port.register_block(), offset) + } +} + +/// An input GPIO. +pub struct Input

{ + pin: P, + // Logical ownership: + // - PDIR: read only + // - ICR[offset]: read/write + // - ISFRx: read, W1C + gpio: &'static ral::rgpio::RegisterBlock, + offset: u32, +} + +// Safety: see impl Send for Output. +unsafe impl Send for Input

{} + +/// Input interrupt triggers. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum Trigger { + /// Interrupt when GPIO is low + Low = 0, + /// Interrupt when GPIO is high + High = 1, + /// Interrupt after GPIO rising edge + RisingEdge = 2, + /// Interrupt after GPIO falling edge + FallingEdge = 3, + /// Interrupt after either a rising or falling edge + EitherEdge = 4, +} + +impl

Input

{ + fn new(pin: P, gpio: &'static ral::rgpio::RegisterBlock, offset: u32) -> Self { + let input = Self { pin, gpio, offset }; + ral::modify_reg!(ral::rgpio, gpio, PDDR, |gdir| gdir & !input.mask()); + input + } + + const fn mask(&self) -> u32 { + 1 << self.offset + } + + /// Returns `true` if the GPIO is set high. + pub fn is_set(&self) -> bool { + ral::read_reg!(ral::rgpio, self.gpio, PDIR) & self.mask() != 0 + } + + /// Returns `true` if the GPIO interrupt has triggered. + pub fn is_triggered(&self) -> bool { + ral::read_reg!(ral::rgpio, self.gpio, ICR[self.offset as usize], ISF) != 0 + } + + /// Clear the interrupt triggered flag. + pub fn clear_triggered(&mut self) { + // We could remove the &mut if we used ISFR instead of ICR, but then we need to choose + // between ISFR0 and ISFR1. + ral::write_reg!(ral::rgpio, self.gpio, ICR[self.offset as usize], ISF: ISF1); + } + + /// Release the underlying pin object. + pub fn release(self) -> P { + self.pin + } + + /// Access the underlying pin. + pub fn pin(&self) -> &P { + &self.pin + } + + /// Mutably access the underling pin. + pub fn pin_mut(&mut self) -> &mut P { + &mut self.pin + } + + /// Enable or disable GPIO input interrupts. + /// + /// Specify `None` to disable interrupts. Or, provide a trigger + /// to configure the interrupt. + pub fn set_interrupt(&mut self, trigger: Option) { + ral::modify_reg!(ral::rgpio, self.gpio, ICR[self.offset as usize], IRQC: + match trigger { + None => IRQC0, + Some(Trigger::Low) => IRQC8, + Some(Trigger::High) => IRQC12, + Some(Trigger::RisingEdge) => IRQC9, + Some(Trigger::FallingEdge) => IRQC10, + Some(Trigger::EitherEdge) => IRQC11, + } + ) + } +} + +impl Input<()> { + /// Allocate an input GPIO without a pin. + /// + /// Prefer using [`Port::input`](Port::input) to create a GPIO ouptut with a + /// pin resource. That method ensures that pin resources are managed throughout + /// your program, and that the pin is configured to operate as a GPIO input. + /// + /// You may use this method to allocate duplicate `Input` object for the same + /// physical GPIO input. This is considered safe, since the `Input` API is + /// reentrant. Any non-reentrant methods are attached to [`Port`], which cannot + /// be constructed without an `unsafe` constructor of the register block. + /// + /// If you use this constructor, you're responsible for configuring the IOMUX + /// multiplexer register. + pub fn without_pin(port: &mut Port, offset: u32) -> Self { + Self::new((), port.register_block(), offset) + } +} + +impl

eh02::digital::v2::OutputPin for Output

{ + type Error = core::convert::Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set(); + Ok(()) + } + fn set_low(&mut self) -> Result<(), Self::Error> { + self.clear(); + Ok(()) + } +} + +#[cfg(feature = "eh02-unproven")] +impl

eh02::digital::v2::StatefulOutputPin for Output

{ + fn is_set_high(&self) -> Result { + Ok(self.is_set()) + } + fn is_set_low(&self) -> Result { + Ok(!self.is_set()) + } +} + +#[cfg(feature = "eh02-unproven")] +impl

eh02::digital::v2::ToggleableOutputPin for Output

{ + type Error = core::convert::Infallible; + + fn toggle(&mut self) -> Result<(), Self::Error> { + Output::

::toggle(self); + Ok(()) + } +} + +#[cfg(feature = "eh02-unproven")] +impl

eh02::digital::v2::InputPin for Input

{ + type Error = core::convert::Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_set()) + } + fn is_low(&self) -> Result { + Ok(!self.is_set()) + } +} + +impl

eh1::digital::ErrorType for Output

{ + type Error = core::convert::Infallible; +} + +impl

eh1::digital::OutputPin for Output

{ + fn set_high(&mut self) -> Result<(), Self::Error> { + Output::set(self); + Ok(()) + } + fn set_low(&mut self) -> Result<(), Self::Error> { + Output::clear(self); + Ok(()) + } +} + +impl

eh1::digital::StatefulOutputPin for Output

{ + fn is_set_high(&mut self) -> Result { + Ok(Output::is_set(self)) + } + + fn is_set_low(&mut self) -> Result { + Ok(!Output::is_set(self)) + } + + fn toggle(&mut self) -> Result<(), Self::Error> { + Output::toggle(self); + Ok(()) + } +} + +// For open drain or simply reading back the actual state +// of the pin. +impl

eh1::digital::InputPin for Output

{ + fn is_high(&mut self) -> Result { + Ok(Output::is_pad_high(self)) + } + + fn is_low(&mut self) -> Result { + Ok(!Output::is_pad_high(self)) + } +} + +impl

eh1::digital::ErrorType for Input

{ + type Error = core::convert::Infallible; +} + +impl

eh1::digital::InputPin for Input

{ + fn is_high(&mut self) -> Result { + Ok(Input::is_set(self)) + } + + fn is_low(&mut self) -> Result { + Ok(!Input::is_set(self)) + } +} diff --git a/src/chip/imxrt1180.rs b/src/chip/imxrt1180.rs new file mode 100644 index 00000000..074eb1c2 --- /dev/null +++ b/src/chip/imxrt1180.rs @@ -0,0 +1,31 @@ +pub use drivers::rgpio; + +pub(crate) mod iomuxc { + pub use super::config::pads; + use crate::ral; + + /// Transform the `imxrt-ral` IOMUXC instances into pad objects. + pub fn into_pads(_: ral::iomuxc::IOMUXC, _: ral::iomuxc_aon::IOMUXC_AON) -> pads::Pads { + // Safety: acquiring pads has the same safety implications + // as acquiring the IOMUXC instances. The user has already + // assumed the unsafety. + unsafe { pads::Pads::new() } + } +} + +pub mod ccm { + pub use crate::common::ccm::*; +} + +pub mod dma { + #[doc(hidden)] + pub struct __PretendUsed(()); +} + +mod drivers { + pub mod rgpio; +} + +mod config { + pub use imxrt_iomuxc::imxrt1180 as pads; +}