Skip to content

Implement BOOTSEL button entry to safe mode for RP2. #10231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ports/raspberrypi/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@

#define CIRCUITPY_PROCESSOR_COUNT (2)

// For many RP2 boards BOOTSEL is not connected to a GPIO pin.
#ifndef CIRCUITPY_BOOT_BUTTON
#define CIRCUITPY_BOOT_BUTTON_NO_GPIO (1)
#endif

#if CIRCUITPY_USB_HOST
#define CIRCUITPY_USB_HOST_INSTANCE 1
#endif
Expand Down
57 changes: 57 additions & 0 deletions ports/raspberrypi/supervisor/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@
#include "RP2350.h" // CMSIS
#endif

#if CIRCUITPY_BOOT_BUTTON_NO_GPIO
#include "hardware/gpio.h"
#include "hardware/sync.h"
#include "hardware/structs/ioqspi.h"
#include "hardware/structs/sio.h"
#endif

#include "supervisor/shared/serial.h"

#include "tusb.h"
Expand Down Expand Up @@ -576,3 +583,53 @@ void port_boot_info(void) {
mp_printf(&mp_plat_print, "\n");
#endif
}

#if CIRCUITPY_BOOT_BUTTON_NO_GPIO
bool __no_inline_not_in_flash_func(port_boot_button_pressed)(void) {
// Sense the state of the boot button. Because this function
// disables flash, it cannot be safely called once the second
// core has been started. When the BOOTSEL button is sensed as
// pressed, return is delayed until the button is released and
// a delay has passed in order to debounce the button.
const uint32_t CS_PIN_INDEX = 1;
#if defined(PICO_RP2040)
const uint32_t CS_BIT = 1u << 1;
#else
const uint32_t CS_BIT = SIO_GPIO_HI_IN_QSPI_CSN_BITS;
#endif
uint32_t int_state = save_and_disable_interrupts();
// Wait for any outstanding XIP activity to finish. Flash
// must be quiescent before disabling the chip select. Since
// there's no XIP busy indication we can test, we delay a
// generous 5 ms to allow any XIP activity to finish.
busy_wait_us(5000);
// Float the flash chip select pin. The line will HI-Z due to
// the external 10K pull-up resistor.
hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl,
GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);
// Delay 100 us to allow the CS line to stabilize. If BOOTSEL is
// pressed, the line will be pulled low by the button and its
// 1K external resistor to ground.
busy_wait_us(100);
bool button_pressed = !(sio_hw->gpio_hi_in & CS_BIT);
// Wait for the button to be released.
if (button_pressed) {
while (!(sio_hw->gpio_hi_in & CS_BIT)) {
tight_loop_contents();
}
// Wait for 50 ms to debounce the button.
busy_wait_us(50000);
}
// Restore the flash chip select pin to its original state.
hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl,
GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);
// Delay 5 ms to allow the flash chip to re-enable and for the
// flash CS pin to stabilize.
busy_wait_us(5000);
// Restore the interrupt state.
restore_interrupts(int_state);
return button_pressed;
}
#endif
9 changes: 9 additions & 0 deletions py/circuitpy_mpconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,15 @@ void background_callback_run_all(void);
#define CIRCUITPY_SAVES_PARTITION_SIZE 0
#endif

// Boards that have a boot button connected to a GPIO pin should set
// CIRCUITPY_BOOT_BUTTON_NO_GPIO to 1.
#ifndef CIRCUITPY_BOOT_BUTTON_NO_GPIO
#define CIRCUITPY_BOOT_BUTTON_NO_GPIO (0)
#endif
#if defined(CIRCUITPY_BOOT_BUTTON) && CIRCUITPY_BOOT_BUTTON_NO_GPIO
#error "CIRCUITPY_BOOT_BUTTON and CIRCUITPY_BOOT_BUTTON_NO_GPIO are mutually exclusive"
#endif

#if defined(__GNUC__) && !defined(__ZEPHYR__)
#if __GNUC__ < CIRCUITPY_MIN_GCC_VERSION
// (the 3 level scheme here is required to get expansion & stringization
Expand Down
5 changes: 5 additions & 0 deletions supervisor/port.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@ void port_boot_info(void);
// Some ports want to mark additional pointers as gc roots.
// A default weak implementation is provided that does nothing.
void port_gc_collect(void);

// Most ports that implement CIRCUITPY_BOOT_BUTTON use a generic version of
// this function to sense the button. Ports that need to can override this
// function to provide their own implementation.
bool port_boot_button_pressed(void);
17 changes: 3 additions & 14 deletions supervisor/shared/bluetooth/bluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@

#include "shared-bindings/_bleio/__init__.h"
#include "shared-bindings/_bleio/Adapter.h"
#if defined(CIRCUITPY_BOOT_BUTTON)
#include "shared-bindings/digitalio/DigitalInOut.h"
#include "shared-bindings/time/__init__.h"
#endif
#include "shared-bindings/microcontroller/Processor.h"
#include "shared-bindings/microcontroller/ResetReason.h"
#include "shared-module/storage/__init__.h"

#include "common-hal/_bleio/__init__.h"

#include "supervisor/port.h"
#include "supervisor/shared/serial.h"
#include "supervisor/shared/status_leds.h"
#include "supervisor/shared/tick.h"
Expand Down Expand Up @@ -238,18 +235,10 @@ void supervisor_bluetooth_init(void) {
new_status_color(BLACK);
}
#endif
// Init the boot button every time in case it is used for LEDs.
#ifdef CIRCUITPY_BOOT_BUTTON
digitalio_digitalinout_obj_t boot_button;
common_hal_digitalio_digitalinout_construct(&boot_button, CIRCUITPY_BOOT_BUTTON);
common_hal_digitalio_digitalinout_switch_to_input(&boot_button, PULL_UP);
common_hal_time_delay_ms(1);
bool button_pressed = !common_hal_digitalio_digitalinout_get_value(&boot_button);
common_hal_digitalio_digitalinout_deinit(&boot_button);
if (button_pressed) {
if (port_boot_button_pressed()) {
boot_in_discovery_mode = true;
break;
}
#endif
diff = supervisor_ticks_ms64() - start_ticks;
}
if (boot_in_discovery_mode) {
Expand Down
20 changes: 20 additions & 0 deletions supervisor/shared/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

#include "lib/tlsf/tlsf.h"

#ifdef CIRCUITPY_BOOT_BUTTON
#include "shared-bindings/digitalio/DigitalInOut.h"
#include "shared-bindings/time/__init__.h"
#endif

static tlsf_t heap;

MP_WEAK void port_wake_main_task(void) {
Expand Down Expand Up @@ -60,3 +65,18 @@ MP_WEAK size_t port_heap_get_largest_free_size(void) {
// IDF does this. Not sure why.
return tlsf_fit_size(heap, max_size);
}

MP_WEAK bool port_boot_button_pressed(void) {
#if defined(CIRCUITPY_BOOT_BUTTON)
// Init/deinit the boot button every time in case it is used for LEDs.
digitalio_digitalinout_obj_t boot_button;
common_hal_digitalio_digitalinout_construct(&boot_button, CIRCUITPY_BOOT_BUTTON);
common_hal_digitalio_digitalinout_switch_to_input(&boot_button, PULL_UP);
common_hal_time_delay_ms(1);
bool button_pressed = !common_hal_digitalio_digitalinout_get_value(&boot_button);
common_hal_digitalio_digitalinout_deinit(&boot_button);
return button_pressed;
#else
return false;
#endif
}
17 changes: 2 additions & 15 deletions supervisor/shared/safe_mode.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@

#include "mphalport.h"

#if defined(CIRCUITPY_BOOT_BUTTON)
#include "shared-bindings/digitalio/DigitalInOut.h"
#include "shared-bindings/time/__init__.h"
#endif
#include "shared-bindings/microcontroller/Processor.h"
#include "shared-bindings/microcontroller/ResetReason.h"

Expand Down Expand Up @@ -78,19 +74,10 @@ safe_mode_t wait_for_safe_mode_reset(void) {
new_status_color(BLACK);
}
#endif
// Init the boot button every time in case it is used for LEDs.
#ifdef CIRCUITPY_BOOT_BUTTON
digitalio_digitalinout_obj_t boot_button;
common_hal_digitalio_digitalinout_construct(&boot_button, CIRCUITPY_BOOT_BUTTON);
common_hal_digitalio_digitalinout_switch_to_input(&boot_button, PULL_UP);
common_hal_time_delay_ms(1);
bool button_pressed = !common_hal_digitalio_digitalinout_get_value(&boot_button);
common_hal_digitalio_digitalinout_deinit(&boot_button);
if (button_pressed) {
if (port_boot_button_pressed()) {
boot_in_safe_mode = true;
break;
}
#endif
diff = supervisor_ticks_ms64() - start_ticks;
}
#if CIRCUITPY_STATUS_LED
Expand Down Expand Up @@ -142,7 +129,7 @@ void print_safe_mode_message(safe_mode_t reason) {
case SAFE_MODE_USER:
#if defined(BOARD_USER_SAFE_MODE_ACTION)
message = BOARD_USER_SAFE_MODE_ACTION;
#elif defined(CIRCUITPY_BOOT_BUTTON)
#elif defined(CIRCUITPY_BOOT_BUTTON) || CIRCUITPY_BOOT_BUTTON_NO_GPIO
message = MP_ERROR_TEXT("You pressed the BOOT button at start up");
#else
message = MP_ERROR_TEXT("You pressed the reset button during boot.");
Expand Down
Loading