#[cfg(feature = "chip-ws63")]
use crate::peripherals::IoConfig;
use crate::peripherals::{Gpio0, Gpio1, Gpio2};
use core::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Pull {
None,
Up,
Down,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[instability::unstable]
pub enum InterruptTrigger {
RisingEdge,
FallingEdge,
HighLevel,
LowLevel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpioBank {
Bank0,
Bank1,
Bank2,
}
impl GpioBank {
pub const fn from_index(index: u8) -> Option<Self> {
match index {
0 => Some(Self::Bank0),
1 => Some(Self::Bank1),
2 => Some(Self::Bank2),
_ => None,
}
}
pub const fn index(self) -> u8 {
match self {
Self::Bank0 => 0,
Self::Bank1 => 1,
Self::Bank2 => 2,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct InputConfig {
pub pull: Pull,
}
impl Default for InputConfig {
fn default() -> Self {
Self { pull: Pull::None }
}
}
impl InputConfig {
pub const fn new() -> Self {
Self { pull: Pull::None }
}
pub const fn with_pull(mut self, pull: Pull) -> Self {
self.pull = pull;
self
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OutputConfig {
pub initial_high: bool,
}
impl OutputConfig {
pub const fn new() -> Self {
Self { initial_high: false }
}
pub const fn with_initial(mut self, high: bool) -> Self {
self.initial_high = high;
self
}
}
pub struct AnyPin<'d> {
block: u8,
bit: u8,
_lifetime: PhantomData<&'d mut ()>,
}
impl<'d> AnyPin<'d> {
pub unsafe fn steal(pin: u8) -> Self {
Self { block: pin / 8, bit: pin % 8, _lifetime: PhantomData }
}
pub fn number(&self) -> u8 {
self.block * 8 + self.bit
}
fn set_oen(&self, input: bool) {
let r = regs(self.block);
let mask = 1 << self.bit;
if input {
r.gpio_sw_oen().modify(|r, w| unsafe { w.bits(r.bits() | mask) });
} else {
r.gpio_sw_oen().modify(|r, w| unsafe { w.bits(r.bits() & !mask) });
}
}
pub fn init_input(self, config: InputConfig) -> Input<'d> {
self.set_oen(true);
apply_pull(self.number(), config.pull);
Input { pin: self, config }
}
pub fn init_output(self, config: OutputConfig) -> Output<'d> {
let mut out = Output { pin: self, config };
if config.initial_high {
out.set_high();
} else {
out.set_low();
}
out.pin.set_oen(false);
out
}
pub fn init_flex(self, config: OutputConfig) -> Flex<'d> {
if config.initial_high {
unsafe { regs(self.block).gpio_data_set().write(|w| w.bits(1 << self.bit)) };
} else {
unsafe { regs(self.block).gpio_data_clr().write(|w| w.bits(1 << self.bit)) };
}
self.set_oen(false);
Flex { pin: self, config }
}
}
pub struct Input<'d> {
pin: AnyPin<'d>,
#[allow(dead_code)]
config: InputConfig,
}
impl<'d> Input<'d> {
pub fn is_high(&self) -> bool {
(regs(self.pin.block).gpio_sw_out().read().bits() >> self.pin.bit) & 1 != 0
}
pub fn is_low(&self) -> bool {
!self.is_high()
}
pub fn number(&self) -> u8 {
self.pin.number()
}
#[instability::unstable]
pub fn enable_interrupt(&self) {
let r = regs(self.pin.block);
r.gpio_int_en().modify(|r, w| unsafe { w.bits(r.bits() | (1 << self.pin.bit)) });
}
#[instability::unstable]
pub fn disable_interrupt(&self) {
let r = regs(self.pin.block);
r.gpio_int_en().modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.pin.bit)) });
}
#[instability::unstable]
pub fn clear_interrupt(&self) {
unsafe { regs(self.pin.block).gpio_int_eoi().write(|w| w.bits(1 << self.pin.bit)) };
}
#[instability::unstable]
pub fn set_interrupt_trigger(&self, trigger: InterruptTrigger) {
let r = regs(self.pin.block);
let mask = 1u32 << self.pin.bit;
let (edge, high) = match trigger {
InterruptTrigger::RisingEdge => (true, true),
InterruptTrigger::FallingEdge => (true, false),
InterruptTrigger::HighLevel => (false, true),
InterruptTrigger::LowLevel => (false, false),
};
r.gpio_int_type().modify(|r, w| unsafe { w.bits(if edge { r.bits() | mask } else { r.bits() & !mask }) });
r.gpio_int_polarity().modify(|r, w| unsafe { w.bits(if high { r.bits() | mask } else { r.bits() & !mask }) });
}
#[instability::unstable]
pub fn interrupt_pending(&self) -> bool {
(regs(self.pin.block).gpio_int_raw().read().bits() >> self.pin.bit) & 1 != 0
}
pub fn degrade(self) -> AnyPin<'d> {
self.pin
}
}
impl embedded_hal::digital::ErrorType for Input<'_> {
type Error = core::convert::Infallible;
}
impl embedded_hal::digital::InputPin for Input<'_> {
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(Input::is_high(self))
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(Input::is_low(self))
}
}
pub struct Output<'d> {
pin: AnyPin<'d>,
config: OutputConfig,
}
impl<'d> Output<'d> {
pub fn set_high(&mut self) {
unsafe { regs(self.pin.block).gpio_data_set().write(|w| w.bits(1 << self.pin.bit)) };
}
pub fn set_low(&mut self) {
unsafe { regs(self.pin.block).gpio_data_clr().write(|w| w.bits(1 << self.pin.bit)) };
}
pub fn toggle(&mut self) {
let r = regs(self.pin.block);
let val = r.gpio_sw_out().read().bits();
if val & (1 << self.pin.bit) != 0 {
unsafe { r.gpio_data_clr().write(|w| w.bits(1 << self.pin.bit)) };
} else {
unsafe { r.gpio_data_set().write(|w| w.bits(1 << self.pin.bit)) };
}
}
pub fn is_set_high(&self) -> bool {
(regs(self.pin.block).gpio_sw_out().read().bits() >> self.pin.bit) & 1 != 0
}
pub fn number(&self) -> u8 {
self.pin.number()
}
pub fn into_flex(self) -> Flex<'d> {
let this = core::mem::ManuallyDrop::new(self);
let pin = unsafe { core::ptr::read(&this.pin) };
let config = unsafe { core::ptr::read(&this.config) };
Flex { pin, config }
}
pub fn degrade(self) -> AnyPin<'d> {
let this = core::mem::ManuallyDrop::new(self);
unsafe { core::ptr::read(&this.pin) }
}
#[must_use = "the pin is now latched in its current state; bind the marker to make that explicit"]
pub fn into_latched(self) -> OutputLatched {
core::mem::forget(self); OutputLatched(())
}
}
#[derive(Debug)]
#[must_use]
pub struct OutputLatched(());
impl Drop for Output<'_> {
fn drop(&mut self) {
self.pin.set_oen(true);
}
}
impl embedded_hal::digital::ErrorType for Output<'_> {
type Error = core::convert::Infallible;
}
impl embedded_hal::digital::OutputPin for Output<'_> {
fn set_low(&mut self) -> Result<(), Self::Error> {
Output::set_low(self);
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
Output::set_high(self);
Ok(())
}
}
impl embedded_hal::digital::StatefulOutputPin for Output<'_> {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(Output::is_set_high(self))
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(!Output::is_set_high(self))
}
}
pub struct Flex<'d> {
pin: AnyPin<'d>,
#[allow(dead_code)]
config: OutputConfig,
}
impl<'d> Flex<'d> {
pub fn set_high(&mut self) {
self.pin.set_oen(false);
unsafe { regs(self.pin.block).gpio_data_set().write(|w| w.bits(1 << self.pin.bit)) };
}
pub fn set_low(&mut self) {
self.pin.set_oen(false);
unsafe { regs(self.pin.block).gpio_data_clr().write(|w| w.bits(1 << self.pin.bit)) };
}
pub fn toggle(&mut self) {
let r = regs(self.pin.block);
self.pin.set_oen(false);
let val = r.gpio_sw_out().read().bits();
if val & (1 << self.pin.bit) != 0 {
unsafe { r.gpio_data_clr().write(|w| w.bits(1 << self.pin.bit)) };
} else {
unsafe { r.gpio_data_set().write(|w| w.bits(1 << self.pin.bit)) };
}
}
pub fn is_set_high(&self) -> bool {
(regs(self.pin.block).gpio_sw_out().read().bits() >> self.pin.bit) & 1 != 0
}
pub fn is_high(&self) -> bool {
let r = regs(self.pin.block);
let oen = r.gpio_sw_oen().read().bits();
self.pin.set_oen(true);
let val = (r.gpio_sw_out().read().bits() >> self.pin.bit) & 1 != 0;
let mask = 1 << self.pin.bit;
if oen & mask != 0 {
r.gpio_sw_oen().modify(|reg, w| unsafe { w.bits(reg.bits() | mask) });
} else {
r.gpio_sw_oen().modify(|reg, w| unsafe { w.bits(reg.bits() & !mask) });
}
val
}
pub fn is_low(&self) -> bool {
!self.is_high()
}
pub fn set_as_output(&mut self) {
self.pin.set_oen(false);
}
pub fn set_as_input(&mut self) {
self.pin.set_oen(true);
}
pub fn number(&self) -> u8 {
self.pin.number()
}
pub fn degrade(self) -> AnyPin<'d> {
self.pin
}
}
impl embedded_hal::digital::ErrorType for Flex<'_> {
type Error = core::convert::Infallible;
}
impl embedded_hal::digital::OutputPin for Flex<'_> {
fn set_low(&mut self) -> Result<(), Self::Error> {
Flex::set_low(self);
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
Flex::set_high(self);
Ok(())
}
}
impl embedded_hal::digital::StatefulOutputPin for Flex<'_> {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(Flex::is_set_high(self))
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(!Flex::is_set_high(self))
}
}
impl embedded_hal::digital::InputPin for Flex<'_> {
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(Flex::is_high(self))
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(Flex::is_low(self))
}
}
fn regs(block: u8) -> &'static crate::soc::pac::gpio0::RegisterBlock {
unsafe {
match block {
0 => &*Gpio0::ptr(),
1 => &*Gpio1::ptr(),
2 => &*Gpio2::ptr(),
_ => unreachable!(),
}
}
}
#[cfg(feature = "chip-ws63")]
fn apply_pull(pin: u8, pull: Pull) {
if pin > 14 {
return;
}
let (pe, ps) = match pull {
Pull::None => (false, false),
Pull::Up => (true, true),
Pull::Down => (true, false),
};
let regs = unsafe { &*crate::peripherals::IoConfig::ptr() };
macro_rules! apply_pad {
($reg:expr, $pe:ident, $ps:ident, $ie:ident) => {{
let _ = $reg.modify(|_, w| {
let w = if pe { w.$pe().set_bit() } else { w.$pe().clear_bit() };
let w = if ps { w.$ps().set_bit() } else { w.$ps().clear_bit() };
w.$ie().set_bit()
});
}};
}
match pin {
0 => apply_pad!(regs.pad_gpio_00_ctrl(), pad_gpio_00_ctrl_pe, pad_gpio_00_ctrl_ps, pad_gpio_00_ctrl_ie),
1 => apply_pad!(regs.pad_gpio_01_ctrl(), pad_gpio_01_ctrl_pe, pad_gpio_01_ctrl_ps, pad_gpio_01_ctrl_ie),
2 => apply_pad!(regs.pad_gpio_02_ctrl(), pad_gpio_02_ctrl_pe, pad_gpio_02_ctrl_ps, pad_gpio_02_ctrl_ie),
3 => apply_pad!(regs.pad_gpio_03_ctrl(), pad_gpio_03_ctrl_pe, pad_gpio_03_ctrl_ps, pad_gpio_03_ctrl_ie),
4 => apply_pad!(regs.pad_gpio_04_ctrl(), pad_gpio_04_ctrl_pe, pad_gpio_04_ctrl_ps, pad_gpio_04_ctrl_ie),
5 => apply_pad!(regs.pad_gpio_05_ctrl(), pad_gpio_05_ctrl_pe, pad_gpio_05_ctrl_ps, pad_gpio_05_ctrl_ie),
6 => apply_pad!(regs.pad_gpio_06_ctrl(), pad_gpio_06_ctrl_pe, pad_gpio_06_ctrl_ps, pad_gpio_06_ctrl_ie),
7 => apply_pad!(regs.pad_gpio_07_ctrl(), pad_gpio_07_ctrl_pe, pad_gpio_07_ctrl_ps, pad_gpio_07_ctrl_ie),
8 => apply_pad!(regs.pad_gpio_08_ctrl(), pad_gpio_08_ctrl_pe, pad_gpio_08_ctrl_ps, pad_gpio_08_ctrl_ie),
9 => apply_pad!(regs.pad_gpio_09_ctrl(), pad_gpio_09_ctrl_pe, pad_gpio_09_ctrl_ps, pad_gpio_09_ctrl_ie),
10 => apply_pad!(regs.pad_gpio_10_ctrl(), pad_gpio_10_ctrl_pe, pad_gpio_10_ctrl_ps, pad_gpio_10_ctrl_ie),
11 => apply_pad!(regs.pad_gpio_11_ctrl(), pad_gpio_11_ctrl_pe, pad_gpio_11_ctrl_ps, pad_gpio_11_ctrl_ie),
12 => apply_pad!(regs.pad_gpio_12_ctrl(), pad_gpio_12_ctrl_pe, pad_gpio_12_ctrl_ps, pad_gpio_12_ctrl_ie),
13 => apply_pad!(regs.pad_gpio_13_ctrl(), pad_gpio_13_ctrl_pe, pad_gpio_13_ctrl_ps, pad_gpio_13_ctrl_ie),
14 => apply_pad!(regs.pad_gpio_14_ctrl(), pad_gpio_14_ctrl_pe, pad_gpio_14_ctrl_ps, pad_gpio_14_ctrl_ie),
_ => {
debug_assert!(false, "GPIO pad {pin} has no IO_CONFIG pad control register");
}
}
}
#[cfg(not(feature = "chip-ws63"))]
fn apply_pull(_pin: u8, _pull: Pull) {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutputSignal(pub(crate) u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InputSignal(pub(crate) u8);
pub trait PeripheralOutput: crate::private::Sealed {
fn output_signal(&self) -> OutputSignal;
}
pub trait PeripheralInput: crate::private::Sealed {
fn input_signal(&self) -> InputSignal;
}
impl crate::private::Sealed for Output<'_> {}
impl crate::private::Sealed for Input<'_> {}
impl crate::private::Sealed for Flex<'_> {}
#[cfg(feature = "chip-ws63")]
pub struct Io<'d> {
pub io_config: IoConfig<'d>,
}
#[cfg(feature = "chip-ws63")]
impl<'d> Io<'d> {
pub fn new(io_config: IoConfig<'d>) -> Self {
Self { io_config }
}
#[instability::unstable]
pub unsafe fn register_block(&self) -> &crate::soc::pac::io_config::RegisterBlock {
unsafe { self.io_config.register_block() }
}
}
#[cfg(all(feature = "chip-ws63", feature = "async", feature = "unstable"))]
mod asynch_impl {
use super::{GpioBank, Input, InterruptTrigger, regs};
use crate::asynch::IrqSignal;
use crate::interrupt::{self, Interrupt};
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use embedded_hal_async::digital::Wait;
static GPIO_SIGNAL: [IrqSignal; 3] = [IrqSignal::new(), IrqSignal::new(), IrqSignal::new()];
fn bank_irq(bank: GpioBank) -> Interrupt {
match bank {
GpioBank::Bank0 => Interrupt::GPIO_INT0,
GpioBank::Bank1 => Interrupt::GPIO_INT1,
GpioBank::Bank2 => Interrupt::GPIO_INT2,
}
}
pub fn on_interrupt(bank: GpioBank) {
let index = bank.index();
let r = regs(index);
let fired = r.gpio_int_raw().read().bits();
r.gpio_int_en().modify(|v, w| unsafe { w.bits(v.bits() & !fired) });
unsafe { r.gpio_int_eoi().write(|w| w.bits(fired)) };
GPIO_SIGNAL[index as usize].signal();
interrupt::clear_pending(bank_irq(bank));
}
async fn arm_and_wait(input: &mut Input<'_>, trig: InterruptTrigger) {
let bank = input.pin.block as usize;
input.set_interrupt_trigger(trig);
input.clear_interrupt();
GPIO_SIGNAL[bank].reset();
input.enable_interrupt();
let gpio_bank = GpioBank::from_index(bank as u8).expect("GPIO pin bank must be 0..=2");
unsafe { interrupt::enable(bank_irq(gpio_bank)) };
GpioWaitFuture { bank }.await;
}
struct GpioWaitFuture {
bank: usize,
}
impl Future for GpioWaitFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if GPIO_SIGNAL[self.bank].take_fired() {
Poll::Ready(())
} else {
GPIO_SIGNAL[self.bank].register(cx.waker());
Poll::Pending
}
}
}
impl Wait for Input<'_> {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
if self.is_high() {
return Ok(());
}
arm_and_wait(self, InterruptTrigger::HighLevel).await;
Ok(())
}
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
if self.is_low() {
return Ok(());
}
arm_and_wait(self, InterruptTrigger::LowLevel).await;
Ok(())
}
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
arm_and_wait(self, InterruptTrigger::RisingEdge).await;
Ok(())
}
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
arm_and_wait(self, InterruptTrigger::FallingEdge).await;
Ok(())
}
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
let trig = if self.is_high() { InterruptTrigger::FallingEdge } else { InterruptTrigger::RisingEdge };
arm_and_wait(self, trig).await;
Ok(())
}
}
}
#[cfg(all(feature = "chip-ws63", feature = "async", feature = "unstable"))]
pub use asynch_impl::on_interrupt;
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::*;
const PAD_PE_BIT: u32 = 1 << 9;
const PAD_PS_BIT: u32 = 1 << 10;
const PAD_IE_BIT: u32 = 1 << 11;
#[test]
fn output_latched_marker_is_zero_sized() {
assert_eq!(core::mem::size_of::<OutputLatched>(), 0);
}
fn split(pin: u8) -> (u8, u8) {
(pin / 8, pin % 8)
}
fn number(block: u8, bit: u8) -> u8 {
block * 8 + bit
}
#[test]
fn pin_number_round_trips() {
for pin in 0u8..=18 {
let (block, bit) = split(pin);
assert_eq!(number(block, bit), pin);
}
}
#[test]
fn pin_split_block_boundaries() {
assert_eq!(split(0), (0, 0));
assert_eq!(split(7), (0, 7));
assert_eq!(split(8), (1, 0));
assert_eq!(split(15), (1, 7));
assert_eq!(split(16), (2, 0));
assert_eq!(split(18), (2, 2));
}
#[test]
fn input_config_defaults_to_no_pull() {
assert_eq!(InputConfig::default().pull, Pull::None);
assert_eq!(InputConfig::new().pull, Pull::None);
}
#[test]
fn input_config_with_pull_sets_field() {
assert_eq!(InputConfig::new().with_pull(Pull::Up).pull, Pull::Up);
assert_eq!(InputConfig::new().with_pull(Pull::Down).pull, Pull::Down);
assert_eq!(InputConfig::new().with_pull(Pull::None).pull, Pull::None);
}
#[test]
fn output_config_defaults_are_false() {
let a = OutputConfig::new();
let b = OutputConfig::default();
assert!(!a.initial_high);
assert!(!b.initial_high);
}
#[test]
fn output_config_builder_sets_initial_level() {
let c = OutputConfig::new().with_initial(true);
assert!(c.initial_high);
}
fn trigger_bits(t: InterruptTrigger) -> (bool, bool) {
match t {
InterruptTrigger::RisingEdge => (true, true),
InterruptTrigger::FallingEdge => (true, false),
InterruptTrigger::HighLevel => (false, true),
InterruptTrigger::LowLevel => (false, false),
}
}
#[test]
fn interrupt_trigger_encoding() {
assert_eq!(trigger_bits(InterruptTrigger::RisingEdge), (true, true));
assert_eq!(trigger_bits(InterruptTrigger::FallingEdge), (true, false));
assert_eq!(trigger_bits(InterruptTrigger::HighLevel), (false, true));
assert_eq!(trigger_bits(InterruptTrigger::LowLevel), (false, false));
}
#[test]
fn interrupt_trigger_edge_vs_level() {
assert!(trigger_bits(InterruptTrigger::RisingEdge).0);
assert!(trigger_bits(InterruptTrigger::FallingEdge).0);
assert!(!trigger_bits(InterruptTrigger::HighLevel).0);
assert!(!trigger_bits(InterruptTrigger::LowLevel).0);
}
fn pull_bits(p: Pull) -> (bool, bool) {
match p {
Pull::None => (false, false),
Pull::Up => (true, true),
Pull::Down => (true, false),
}
}
#[test]
fn pull_encoding_matches_svd() {
assert_eq!(pull_bits(Pull::None), (false, false));
assert_eq!(pull_bits(Pull::Up), (true, true));
assert_eq!(pull_bits(Pull::Down), (true, false));
}
#[test]
fn pull_enable_implies_nonzero() {
assert!(!pull_bits(Pull::None).0);
assert!(pull_bits(Pull::Up).0);
assert!(pull_bits(Pull::Down).0);
}
fn has_pad_ctrl(pin: u8) -> bool {
pin <= 14
}
#[test]
fn pad_ctrl_pac_coverage() {
for pin in 0u8..=14 {
assert!(has_pad_ctrl(pin));
}
for pin in 15u8..=18 {
assert!(!has_pad_ctrl(pin));
}
}
#[test]
fn pad_pull_bits_are_distinct_and_placed() {
assert_eq!(PAD_PE_BIT, 1 << 9);
assert_eq!(PAD_PS_BIT, 1 << 10);
assert_ne!(PAD_PE_BIT, PAD_PS_BIT);
assert_eq!(PAD_PE_BIT | PAD_PS_BIT, 0b110_0000_0000);
}
#[test]
fn pad_clear_mask_preserves_other_bits() {
let other = 0xFFFF_FFFFu32 & !(PAD_PE_BIT | PAD_PS_BIT);
let v = other & !(PAD_PE_BIT | PAD_PS_BIT);
assert_eq!(v, other);
}
fn apply_pull_rmw(start: u32, pull: Pull) -> u32 {
let (pe, ps) = pull_bits(pull);
let mut v = start;
v &= !(PAD_PE_BIT | PAD_PS_BIT);
if pe {
v |= PAD_PE_BIT;
}
if ps {
v |= PAD_PS_BIT;
}
v |= PAD_IE_BIT;
v
}
#[test]
fn pad_ie_bit_is_bit11() {
assert_eq!(PAD_IE_BIT, 1 << 11);
assert_ne!(PAD_IE_BIT, PAD_PE_BIT);
assert_ne!(PAD_IE_BIT, PAD_PS_BIT);
}
#[test]
fn apply_pull_always_enables_input_buffer() {
for &start in &[0x0000_0000u32, 0x800, 0x600, 0xFFFF_FFFF] {
for pull in [Pull::None, Pull::Up, Pull::Down] {
let v = apply_pull_rmw(start, pull);
assert_ne!(v & PAD_IE_BIT, 0, "IE must be set (start={start:#x}, {pull:?})");
}
}
}
#[test]
fn apply_pull_sets_pull_and_keeps_unrelated_bits() {
let start = (0x7 << 4) | (1 << 3); let keep = (0x7 << 4) | (1 << 3);
let up = apply_pull_rmw(start, Pull::Up);
assert_eq!(up & keep, keep);
assert_eq!(up & (PAD_PE_BIT | PAD_PS_BIT), PAD_PE_BIT | PAD_PS_BIT);
assert_ne!(up & PAD_IE_BIT, 0);
let down = apply_pull_rmw(start, Pull::Down);
assert_eq!(down & keep, keep);
assert_eq!(down & (PAD_PE_BIT | PAD_PS_BIT), PAD_PE_BIT);
let none = apply_pull_rmw(start | PAD_PE_BIT | PAD_PS_BIT, Pull::None);
assert_eq!(none & (PAD_PE_BIT | PAD_PS_BIT), 0); assert_ne!(none & PAD_IE_BIT, 0);
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use proptest::prelude::*;
proptest! {
#[test]
fn pin_number_round_trips(pin in 0u8..=18) {
let block = pin / 8;
let bit = pin % 8;
prop_assert_eq!(block * 8 + bit, pin);
prop_assert!(bit < 8);
}
#[test]
fn pad_ctrl_coverage_matches_svd(pin in 0u8..=18) {
prop_assert_eq!(pin <= 14, (0..=14).contains(&pin));
}
}
}