diff --git a/embassy-rp/src/bootsel.rs b/embassy-rp/src/bootsel.rs new file mode 100644 index 000000000..69d620e8d --- /dev/null +++ b/embassy-rp/src/bootsel.rs @@ -0,0 +1,81 @@ +//! Boot Select button +//! +//! The RP2040 rom supports a BOOTSEL button that is used to enter the USB bootloader +//! if held during reset. To avoid wasting GPIO pins, the button is multiplexed onto +//! the CS pin of the QSPI flash, but that makes it somewhat expensive and complicated +//! to utilize outside of the rom's bootloader. +//! +//! This module provides functionality to poll BOOTSEL from an embassy application. + +use crate::flash::in_ram; + +/// Polls the BOOTSEL button. Returns true if the button is pressed. +/// +/// Polling isn't cheap, as this function waits for core 1 to finish it's current +/// task and for any DMAs from flash to complete +pub fn poll_bootsel() -> bool { + let mut cs_status = Default::default(); + + unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); + + // bootsel is active low, so invert + !cs_status.infrompad() +} + +mod ram_helpers { + use rp_pac::io::regs::GpioStatus; + + /// Temporally reconfigures the CS gpio and returns the GpioStatus. + + /// This function runs from RAM so it can disable flash XIP. + /// + /// # Safety + /// + /// The caller must ensure flash is idle and will remain idle. + /// This function must live in ram. It uses inline asm to avoid any + /// potential calls to ABI functions that might be in flash. + #[inline(never)] + #[link_section = ".data.ram_func"] + #[cfg(target_arch = "arm")] + pub unsafe fn read_cs_status() -> GpioStatus { + let result: u32; + + // Magic value, used as both OEOVER::DISABLE and delay loop counter + let magic = 0x2000; + + core::arch::asm!( + ".equiv GPIO_STATUS, 0x0", + ".equiv GPIO_CTRL, 0x4", + + "ldr {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + // The BOOTSEL pulls the flash's CS line low though a 1K resistor. + // this is weak enough to avoid disrupting normal operation. + // But, if we disable CS's output drive and allow it to float... + "str {val}, [{cs_gpio}, $GPIO_CTRL]", + + // ...then wait for the state to settle... + "1:", // ~4000 cycle delay loop + "subs {val}, #8", + "bne 1b", + + // ...we can read the current state of bootsel + "ldr {val}, [{cs_gpio}, $GPIO_STATUS]", + + // Finally, restore CS to normal operation so XIP can continue + "str {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + cs_gpio = in(reg) rp_pac::IO_QSPI.gpio(1).as_ptr(), + orig_ctrl = out(reg) _, + val = inout(reg) magic => result, + options(nostack), + ); + + core::mem::transmute(result) + } + + #[cfg(not(target_arch = "arm"))] + pub unsafe fn read_cs_status() -> GpioStatus { + unimplemented!() + } +} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index e8f818bcf..fb9189203 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -10,6 +10,7 @@ mod critical_section_impl; mod intrinsics; pub mod adc; +pub mod bootsel; pub mod clocks; pub mod dma; pub mod flash;