From a52f89e96986e7a51c40388fb6dc2fed18ec8625 Mon Sep 17 00:00:00 2001 From: Steven Mak Date: Sun, 25 Feb 2024 02:14:40 -0800 Subject: [PATCH] Revert "Remove low power GPIO (#55)" This reverts commit 986e0109a5b436a49c9a5333ea115265cd3d2071. --- hal/src/peripherals/gpio.rs | 11 +- hal/src/peripherals/gpio/low_power.rs | 354 ++++++++++++++++++++++++++ tests/src/main.rs | 1 + tests/src/tests/gpio_tests.rs | 39 ++- 4 files changed, 402 insertions(+), 3 deletions(-) create mode 100644 hal/src/peripherals/gpio/low_power.rs diff --git a/hal/src/peripherals/gpio.rs b/hal/src/peripherals/gpio.rs index 01b22b95..6f4fd244 100644 --- a/hal/src/peripherals/gpio.rs +++ b/hal/src/peripherals/gpio.rs @@ -2,7 +2,7 @@ use core::{array, cell::Cell}; -use max78000::{GPIO0, GPIO1, GPIO2}; +use max78000::{GPIO0, GPIO1, GPIO2, MCR}; use sealed::sealed; use self::{ @@ -10,6 +10,7 @@ use self::{ port_num_types::{GpioOne, GpioTwo, GpioZero}, ActiveGpio, }, + low_power::LowPowerGpio, private::NonConstructible, }; @@ -17,6 +18,8 @@ pub mod pin_traits; pub mod active; +pub mod low_power; + /// Error type for GPIO operations #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] @@ -139,6 +142,12 @@ pub fn new_gpio2(gpio2: GPIO2) -> GpioPort<'static, ActiveGpio, 8> { GpioPort::, 8>::new(gpio2) } +/// Creates a new [`GpioPort`] representing GPIO3. +// TODO: Make this pub(crate) when peripheral manager is made +pub fn new_gpio3<'a>(gpio3: &'a MCR) -> GpioPort<'a, LowPowerGpio<'a>, 2> { + GpioPort::, 2>::new(gpio3) +} + /// Represents the I/O mode of a pin. pub enum PinIoMode { /// Input mode (The default after power-on-reset). diff --git a/hal/src/peripherals/gpio/low_power.rs b/hal/src/peripherals/gpio/low_power.rs new file mode 100644 index 00000000..e33da3df --- /dev/null +++ b/hal/src/peripherals/gpio/low_power.rs @@ -0,0 +1,354 @@ +//! GPIO3 pin manipulation. +//! This module contains trait implementations for the low power GPIO port. +//! +//! # Examples +//! +//! Basic usage: +//! ``` +//! let pin = gpio_port.get_pin_handle(0).unwrap().into_input_pin().unwrap(); +//! assert_ne!(pin.is_low(), pin.is_high()); +//! +//! let mut pin = pin.into_output_pin(PinState::High).unwrap(); +//! pin.set_low().unwrap(); +//! assert!(pin.is_set_low().unwrap()); +//! pin.set_high().unwrap(); +//! assert!(pin.is_set_high().unwrap()); +//! ``` + +use core::convert::Infallible; +use core::marker::PhantomData; + +use max78000::MCR; +use sealed::sealed; + +use super::pin_traits::{ + toggleable, GeneralIoPin, InputPin, IoPin, OutputPin, PinState, StatefulOutputPin, +}; + +use super::private::NonConstructible; +use super::{ + GpioError, GpioPort, GpioPortMetadata, PinHandle, PinIoMode, PinOperatingMode, + __seal_gpio_port_metadata, __seal_pin_handle, +}; + +// TODO for arelyx: +// - implement functions with todo!() in them (see LowPowerPinHandle::set_operating_mode for example) +// - add documentation +// - a module-level doc comment +// - public functions within this module that aren't trait impl functions +// - other public items like structs +// - improve existing comments in entire driver to add more detail +// - add unit tests for each public function in the low power pin API + +/// Marker struct implementing `GpioPortMetadata` for +/// low power GPIO ports. +pub struct LowPowerGpio<'mcr>(PhantomData<&'mcr ()>); + +#[sealed] +impl<'mcr> GpioPortMetadata<'mcr> for LowPowerGpio<'mcr> { + type PinHandleType<'a, const PIN_CT: usize> = LowPowerPinHandle<'a, 'mcr, PIN_CT> where 'mcr: 'a; + type GpioRegs = &'mcr MCR; +} + +/// `PinHandle` implementation for low power GPIO ports. +pub struct LowPowerPinHandle<'a, 'mcr, const PIN_CT: usize> { + port: &'a GpioPort<'mcr, LowPowerGpio<'mcr>, PIN_CT>, + pin_idx: usize, +} + +impl<'a, 'mcr, const PIN_CT: usize> + IoPin, LowPowerOutputPin<'a, 'mcr, PIN_CT>> + for LowPowerPinHandle<'a, 'mcr, PIN_CT> +{ + type Error = Infallible; + + fn into_input_pin(self) -> Result, Self::Error> { + if self.pin_idx == 0 { + self.port + .regs + .gpio3_ctrl() + .modify(|_, w| w.p30_oe().clear_bit()); + } else { + self.port + .regs + .gpio3_ctrl() + .modify(|_, w| w.p31_oe().clear_bit()); + } + + Ok(LowPowerInputPin(self)) + } + + fn into_output_pin( + self, + state: PinState, + ) -> Result, Self::Error> { + if self.pin_idx == 0 { + self.port + .regs + .gpio3_ctrl() + .modify(|_, w| w.p30_oe().set_bit()); + } else { + self.port + .regs + .gpio3_ctrl() + .modify(|_, w| w.p31_oe().set_bit()); + } + + let mut output_pin = LowPowerOutputPin(self); + output_pin.set_state(state)?; + + Ok(output_pin) + } +} + +impl<'a, 'mcr, const PIN_CT: usize> + GeneralIoPin, LowPowerOutputPin<'a, 'mcr, PIN_CT>> + for LowPowerPinHandle<'a, 'mcr, PIN_CT> +{ + fn set_operating_mode(&mut self, mode: PinOperatingMode) -> Result<(), GpioError> { + use GpioError::*; + use PinIoMode::*; + + // User guide is very confusing but + // - GPIO 3 pin 0's alt fn 1 is for PDOWN (it outputs data on the pin) + // - the MCR_OUTEN register at the pdown_out_en bit is how you switch the alternate function + // - GPIO 3 pin 1's alt fn 1 is for SQWOUT (it outputs data on the pin) + // - the MCR_OUTEN register at the sqwout_en bit is how you switch the alternate function + + let r = self.port.regs.outen(); + + match (mode, self.pin_idx, self.get_io_mode()) { + (PinOperatingMode::DigitalIo, 0, _) => r.modify(|_, w| w.pdown_out_en().clear_bit()), + (PinOperatingMode::DigitalIo, 1, _) => r.modify(|_, w| w.sqwout_en().clear_bit()), + (PinOperatingMode::AltFunction1, 0, Output) => { + r.modify(|_, w| w.pdown_out_en().set_bit()) + } + (PinOperatingMode::AltFunction1, 1, Output) => r.modify(|_, w| w.sqwout_en().set_bit()), + + // Pin is in input mode when AltFunction1 was requested + (PinOperatingMode::AltFunction1, _, _) => return Err(WrongIoMode), + + // AltFunction2 was given + _ => return Err(BadOperatingMode), + }; + + Ok(()) + } + + fn get_operating_mode(&self) -> PinOperatingMode { + let reg = self.port.regs.outen(); + + if self.pin_idx == 0 { + match reg.read().pdown_out_en().bit_is_set() { + true => PinOperatingMode::AltFunction1, + false => PinOperatingMode::DigitalIo, + } + } else { + // Pin 1 + match reg.read().sqwout_en().bit_is_set() { + true => PinOperatingMode::AltFunction1, + false => PinOperatingMode::DigitalIo, + } + } + } + + fn get_io_mode(&self) -> PinIoMode { + let reg = self.port.regs.gpio3_ctrl(); + + if self.pin_idx == 0 { + match reg.read().p30_oe().bit_is_set() { + true => PinIoMode::Output, + false => PinIoMode::Input, + } + } else { + match reg.read().p31_oe().bit_is_set() { + true => PinIoMode::Output, + false => PinIoMode::Input, + } + } + } +} + +impl<'a, 'mcr, const PIN_CT: usize> Drop for LowPowerPinHandle<'a, 'mcr, PIN_CT> { + fn drop(&mut self) { + // When handle is dropped, allow the pin to be taken again. + self.port.pin_taken[self.pin_idx].set(false); + } +} + +#[sealed] +impl<'a, 'mcr, const PIN_CT: usize> PinHandle<'a> for LowPowerPinHandle<'a, 'mcr, PIN_CT> { + type Port = GpioPort<'mcr, LowPowerGpio<'mcr>, PIN_CT>; + + fn new(_private: NonConstructible, port: &'a Self::Port, pin_idx: usize) -> Self { + // TODO: We can't get rid of the const generic here or otherwise prevent a bad pin count + // from being entered until more complex exprs can be evaluated in const generics stably. + // So there are asserts here to ensure they can't be constructed. The construction of these + // handles are done privately and not able to be done externally so this is fine. + assert!(PIN_CT == 2); + assert!(pin_idx < PIN_CT); + + Self { port, pin_idx } + } + + fn get_pin_idx(&self) -> usize { + self.pin_idx + } +} + +/// `InputPin` implementation for low power GPIO port. +pub struct LowPowerInputPin<'a, 'mcr, const PIN_CT: usize>(LowPowerPinHandle<'a, 'mcr, PIN_CT>); + +impl<'a, 'mcr, const PIN_CT: usize> LowPowerInputPin<'a, 'mcr, PIN_CT> { + /// Enables the pin's pull-up resistor. + pub fn enable_pullup_resistor(&self, enable: bool) { + let reg = self.0.port.regs.gpio3_ctrl(); + + match self.0.pin_idx == 0 { + true => reg.modify(|_, w| w.p30_pe().bit(enable)), + false => reg.modify(|_, w| w.p31_pe().bit(enable)), + } + } +} + +impl<'a, 'mcr, const PIN_CT: usize> InputPin for LowPowerInputPin<'a, 'mcr, PIN_CT> { + type Error = Infallible; + + fn is_high(&self) -> Result { + let reg = self.0.port.regs.gpio3_ctrl(); + + match self.0.pin_idx == 0 { + true => Ok(reg.read().p30_in().bit_is_set()), + false => Ok(reg.read().p31_in().bit_is_set()), + } + } + + fn is_low(&self) -> Result { + let reg = self.0.port.regs.gpio3_ctrl(); + + match self.0.pin_idx == 0 { + true => Ok(reg.read().p30_in().bit_is_clear()), + false => Ok(reg.read().p31_in().bit_is_clear()), + } + } +} + +impl<'a, 'mcr, const PIN_CT: usize> + IoPin, LowPowerOutputPin<'a, 'mcr, PIN_CT>> + for LowPowerInputPin<'a, 'mcr, PIN_CT> +{ + type Error = Infallible; + + fn into_input_pin(self) -> Result, Self::Error> { + self.0.into_input_pin() + } + + fn into_output_pin( + self, + state: PinState, + ) -> Result, Self::Error> { + self.0.into_output_pin(state) + } +} + +impl<'a, 'mcr, const PIN_CT: usize> + GeneralIoPin, LowPowerOutputPin<'a, 'mcr, PIN_CT>> + for LowPowerInputPin<'a, 'mcr, PIN_CT> +{ + fn set_operating_mode(&mut self, mode: PinOperatingMode) -> Result<(), GpioError> { + self.0.set_operating_mode(mode) + } + + fn get_operating_mode(&self) -> PinOperatingMode { + self.0.get_operating_mode() + } + + fn get_io_mode(&self) -> PinIoMode { + self.0.get_io_mode() + } +} + +/// `OutputPin` implementation for low power GPIO port. +pub struct LowPowerOutputPin<'a, 'mcr, const PIN_CT: usize>(LowPowerPinHandle<'a, 'mcr, PIN_CT>); + +impl<'a, 'mcr, const PIN_CT: usize> OutputPin for LowPowerOutputPin<'a, 'mcr, PIN_CT> { + type Error = Infallible; + + fn set_low(&mut self) -> Result<(), Self::Error> { + let reg = self.0.port.regs.gpio3_ctrl(); + match self.0.pin_idx == 0 { + true => reg.modify(|_, w| w.p30_do().clear_bit()), + false => reg.modify(|_, w| w.p31_do().clear_bit()), + }; + + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + let reg = self.0.port.regs.gpio3_ctrl(); + match self.0.pin_idx == 0 { + true => reg.modify(|_, w| w.p30_do().set_bit()), + false => reg.modify(|_, w| w.p31_do().set_bit()), + }; + + Ok(()) + } +} + +impl<'a, 'mcr, const PIN_CT: usize> StatefulOutputPin for LowPowerOutputPin<'a, 'mcr, PIN_CT> { + fn is_set_high(&self) -> Result { + let reg = self.0.port.regs.gpio3_ctrl(); + match self.0.pin_idx == 0 { + true => Ok(reg.read().p30_do().bit_is_set()), + false => Ok(reg.read().p31_do().bit_is_set()), + } + } + + fn is_set_low(&self) -> Result { + let reg = self.0.port.regs.gpio3_ctrl(); + match self.0.pin_idx == 0 { + true => Ok(reg.read().p30_do().bit_is_clear()), + false => Ok(reg.read().p31_do().bit_is_clear()), + } + } +} + +/// Provides [`ToggleableOutputPin`] as a blanket implementation from OutputPin + StatefulOutputPin. +/// +/// [`ToggleableOutputPin`]: #impl-ToggleableOutputPin-for-LowPowerOutputPin<'a,+PIN_CT> +impl<'a, 'mcr, const PIN_CT: usize> toggleable::Default for LowPowerOutputPin<'a, 'mcr, PIN_CT> {} + +impl<'a, 'mcr, const PIN_CT: usize> + IoPin, LowPowerOutputPin<'a, 'mcr, PIN_CT>> + for LowPowerOutputPin<'a, 'mcr, PIN_CT> +{ + type Error = Infallible; + + fn into_input_pin(self) -> Result, Self::Error> { + self.0.into_input_pin() + } + + fn into_output_pin( + self, + state: PinState, + ) -> Result, Self::Error> { + self.0.into_output_pin(state) + } +} + +impl<'a, 'mcr, const PIN_CT: usize> + GeneralIoPin, LowPowerOutputPin<'a, 'mcr, PIN_CT>> + for LowPowerOutputPin<'a, 'mcr, PIN_CT> +{ + fn set_operating_mode(&mut self, mode: PinOperatingMode) -> Result<(), GpioError> { + self.0.set_operating_mode(mode) + } + + fn get_operating_mode(&self) -> PinOperatingMode { + self.0.get_operating_mode() + } + + fn get_io_mode(&self) -> PinIoMode { + self.0.get_io_mode() + } +} diff --git a/tests/src/main.rs b/tests/src/main.rs index fd824c9e..a600247e 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -55,6 +55,7 @@ fn main() -> ! { peripherals.GPIO0, peripherals.GPIO1, peripherals.GPIO2, + &peripherals.MCR, &peripherals.GCR, &peripherals.LPGCR, &mut stdout, diff --git a/tests/src/tests/gpio_tests.rs b/tests/src/tests/gpio_tests.rs index 17afd696..03bcbc3a 100644 --- a/tests/src/tests/gpio_tests.rs +++ b/tests/src/tests/gpio_tests.rs @@ -3,10 +3,11 @@ use core::fmt::Write; use cortex_m_semihosting::hio; use max78000_hal::{ - max78000::{GCR, GPIO0, GPIO1, GPIO2, LPGCR}, + max78000::{GCR, GPIO0, GPIO1, GPIO2, LPGCR, MCR}, peripherals::gpio::{ active::{port_num_types::GpioPortNum, ActiveGpio}, - new_gpio0, new_gpio1, new_gpio2, + low_power::LowPowerGpio, + new_gpio0, new_gpio1, new_gpio2, new_gpio3, pin_traits::{GeneralIoPin, InputPin, IoPin, OutputPin, PinState, StatefulOutputPin}, GpioError, GpioPort, PinIoMode, }, @@ -17,6 +18,7 @@ pub fn run_gpio_tests( gpio0: GPIO0, gpio1: GPIO1, gpio2: GPIO2, + mcr: &MCR, gcr: &GCR, lpgcr: &LPGCR, stdout: &mut hio::HostStream, @@ -32,6 +34,7 @@ pub fn run_gpio_tests( let gpio0_port = new_gpio0(gpio0); let gpio1_port = new_gpio1(gpio1); let gpio2_port = new_gpio2(gpio2); + let gpio3_port = new_gpio3(mcr); // Note: Tests should be made generic over traits like GeneralIoPin, InputPin, and StatefulOutputPin // Write sanity checks for now (writing a value then reading it) -- physical tests will come later @@ -39,6 +42,7 @@ pub fn run_gpio_tests( test_active_port(gpio0_port); test_active_port(gpio1_port); test_active_port(gpio2_port); + test_low_power_port(gpio3_port); writeln!(stdout, "GPIO peripheral tests complete!\n").unwrap(); } @@ -75,3 +79,34 @@ fn test_active_port( Err(GpioError::HandleAlreadyTaken) )); } + +fn test_low_power_port<'a, const PIN_CT: usize>(port: GpioPort<'a, LowPowerGpio<'a>, PIN_CT>) { + let pin = port.get_pin_handle(PIN_CT - 1).unwrap(); + assert!(matches!( + port.get_pin_handle(PIN_CT - 1), + Err(GpioError::HandleAlreadyTaken) + )); + assert!(matches!( + port.get_pin_handle(PIN_CT), + Err(GpioError::InvalidPinIndex) + )); + + let pin = pin.into_input_pin().unwrap(); + assert!(matches!(pin.get_io_mode(), PinIoMode::Input)); + assert_ne!(pin.is_low(), pin.is_high()); + + let mut pin = pin.into_output_pin(PinState::High).unwrap(); + assert!(matches!(pin.get_io_mode(), PinIoMode::Output)); + assert!(pin.is_set_high().unwrap()); + pin.set_low().unwrap(); + assert!(pin.is_set_low().unwrap()); + pin.set_high().unwrap(); + assert!(pin.is_set_high().unwrap()); + drop(pin); + + let _pin = port.get_pin_handle(PIN_CT - 1).unwrap(); + assert!(matches!( + port.get_pin_handle(PIN_CT - 1), + Err(GpioError::HandleAlreadyTaken) + )); +}