Merge pull request #1880 from phire/rp_bootsel
rp2040: BOOTSEL button support
This commit is contained in:
commit
9c6a2d9cbd
83
embassy-rp/src/bootsel.rs
Normal file
83
embassy-rp/src/bootsel.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
//! 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;
|
||||||
|
|
||||||
|
impl crate::peripherals::BOOTSEL {
|
||||||
|
/// 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 is_pressed(&mut self) -> 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!()
|
||||||
|
}
|
||||||
|
}
|
@ -131,7 +131,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI
|
|||||||
|
|
||||||
let len = to - from;
|
let len = to - from;
|
||||||
|
|
||||||
unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len))? };
|
unsafe { in_ram(|| ram_helpers::flash_range_erase(from, len))? };
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI
|
|||||||
|
|
||||||
let unaligned_offset = offset as usize - start;
|
let unaligned_offset = offset as usize - start;
|
||||||
|
|
||||||
unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? }
|
unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? }
|
||||||
}
|
}
|
||||||
|
|
||||||
let remaining_len = bytes.len() - start_padding;
|
let remaining_len = bytes.len() - start_padding;
|
||||||
@ -174,12 +174,12 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI
|
|||||||
if bytes.as_ptr() as usize >= 0x2000_0000 {
|
if bytes.as_ptr() as usize >= 0x2000_0000 {
|
||||||
let aligned_data = &bytes[start_padding..end_padding];
|
let aligned_data = &bytes[start_padding..end_padding];
|
||||||
|
|
||||||
unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? }
|
unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? }
|
||||||
} else {
|
} else {
|
||||||
for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) {
|
for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) {
|
||||||
let mut ram_buf = [0xFF_u8; PAGE_SIZE];
|
let mut ram_buf = [0xFF_u8; PAGE_SIZE];
|
||||||
ram_buf.copy_from_slice(chunk);
|
ram_buf.copy_from_slice(chunk);
|
||||||
unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? }
|
unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? }
|
||||||
aligned_offset += PAGE_SIZE;
|
aligned_offset += PAGE_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,47 +194,15 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI
|
|||||||
|
|
||||||
let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset);
|
let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset);
|
||||||
|
|
||||||
unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? }
|
unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? }
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure to uphold the contract points with rp2040-flash.
|
|
||||||
/// - interrupts must be disabled
|
|
||||||
/// - DMA must not access flash memory
|
|
||||||
unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> {
|
|
||||||
// Make sure we're running on CORE0
|
|
||||||
let core_id: u32 = pac::SIO.cpuid().read();
|
|
||||||
if core_id != 0 {
|
|
||||||
return Err(Error::InvalidCore);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure CORE1 is paused during the entire duration of the RAM function
|
|
||||||
crate::multicore::pause_core1();
|
|
||||||
|
|
||||||
critical_section::with(|_| {
|
|
||||||
// Wait for all DMA channels in flash to finish before ram operation
|
|
||||||
const SRAM_LOWER: u32 = 0x2000_0000;
|
|
||||||
for n in 0..crate::dma::CHANNEL_COUNT {
|
|
||||||
let ch = crate::pac::DMA.ch(n);
|
|
||||||
while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {}
|
|
||||||
}
|
|
||||||
// Wait for completion of any background reads
|
|
||||||
while pac::XIP_CTRL.stream_ctr().read().0 > 0 {}
|
|
||||||
|
|
||||||
// Run our flash operation in RAM
|
|
||||||
operation();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Resume CORE1 execution
|
|
||||||
crate::multicore::resume_core1();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read SPI flash unique ID
|
/// Read SPI flash unique ID
|
||||||
pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> {
|
pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> {
|
||||||
unsafe { self.in_ram(|| ram_helpers::flash_unique_id(uid))? };
|
unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? };
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +210,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI
|
|||||||
pub fn blocking_jedec_id(&mut self) -> Result<u32, Error> {
|
pub fn blocking_jedec_id(&mut self) -> Result<u32, Error> {
|
||||||
let mut jedec = None;
|
let mut jedec = None;
|
||||||
unsafe {
|
unsafe {
|
||||||
self.in_ram(|| {
|
in_ram(|| {
|
||||||
jedec.replace(ram_helpers::flash_jedec_id());
|
jedec.replace(ram_helpers::flash_jedec_id());
|
||||||
})?;
|
})?;
|
||||||
};
|
};
|
||||||
@ -871,6 +839,38 @@ mod ram_helpers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make sure to uphold the contract points with rp2040-flash.
|
||||||
|
/// - interrupts must be disabled
|
||||||
|
/// - DMA must not access flash memory
|
||||||
|
pub(crate) unsafe fn in_ram(operation: impl FnOnce()) -> Result<(), Error> {
|
||||||
|
// Make sure we're running on CORE0
|
||||||
|
let core_id: u32 = pac::SIO.cpuid().read();
|
||||||
|
if core_id != 0 {
|
||||||
|
return Err(Error::InvalidCore);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure CORE1 is paused during the entire duration of the RAM function
|
||||||
|
crate::multicore::pause_core1();
|
||||||
|
|
||||||
|
critical_section::with(|_| {
|
||||||
|
// Wait for all DMA channels in flash to finish before ram operation
|
||||||
|
const SRAM_LOWER: u32 = 0x2000_0000;
|
||||||
|
for n in 0..crate::dma::CHANNEL_COUNT {
|
||||||
|
let ch = crate::pac::DMA.ch(n);
|
||||||
|
while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {}
|
||||||
|
}
|
||||||
|
// Wait for completion of any background reads
|
||||||
|
while pac::XIP_CTRL.stream_ctr().read().0 > 0 {}
|
||||||
|
|
||||||
|
// Run our flash operation in RAM
|
||||||
|
operation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resume CORE1 execution
|
||||||
|
crate::multicore::resume_core1();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
mod sealed {
|
mod sealed {
|
||||||
pub trait Instance {}
|
pub trait Instance {}
|
||||||
pub trait Mode {}
|
pub trait Mode {}
|
||||||
|
@ -10,6 +10,7 @@ mod critical_section_impl;
|
|||||||
mod intrinsics;
|
mod intrinsics;
|
||||||
|
|
||||||
pub mod adc;
|
pub mod adc;
|
||||||
|
pub mod bootsel;
|
||||||
pub mod clocks;
|
pub mod clocks;
|
||||||
pub mod dma;
|
pub mod dma;
|
||||||
pub mod flash;
|
pub mod flash;
|
||||||
@ -193,6 +194,7 @@ embassy_hal_internal::peripherals! {
|
|||||||
PIO1,
|
PIO1,
|
||||||
|
|
||||||
WATCHDOG,
|
WATCHDOG,
|
||||||
|
BOOTSEL,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! select_bootloader {
|
macro_rules! select_bootloader {
|
||||||
|
26
tests/rp/src/bin/bootsel.rs
Normal file
26
tests/rp/src/bin/bootsel.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
teleprobe_meta::target!(b"rpi-pico");
|
||||||
|
|
||||||
|
use defmt::{assert_eq, *};
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(_spawner: Spawner) {
|
||||||
|
let mut p = embassy_rp::init(Default::default());
|
||||||
|
info!("Hello World!");
|
||||||
|
|
||||||
|
// add some delay to give an attached debug probe time to parse the
|
||||||
|
// defmt RTT header. Reading that header might touch flash memory, which
|
||||||
|
// interferes with flash write operations.
|
||||||
|
// https://github.com/knurling-rs/defmt/pull/683
|
||||||
|
Timer::after(Duration::from_millis(10)).await;
|
||||||
|
|
||||||
|
assert_eq!(p.BOOTSEL.is_pressed(), false);
|
||||||
|
|
||||||
|
info!("Test OK");
|
||||||
|
cortex_m::asm::bkpt();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user