When a button is pressed during power off, the buttons module disables the wakeup interrupt but the button scan never happens, leaving the module in an invalid state and preventing any wakeup from system off.
I modified the buttons.c CAF module to log when it changes the interrupt configuration:
static int setup_pin_wakeup(void) { uint32_t wakeup_cols = get_wakeup_mask(col, ARRAY_SIZE(col)); uint32_t wakeup_rows = get_wakeup_mask(row, ARRAY_SIZE(row)); LOG_DBG("Setting pin wakeup: 0x%08x 0x%08x", wakeup_cols, wakeup_rows); /* Disable callbacks (and cancel the work) to ensure it will not be scheduled by an * invalid button in the idle state. */ int err = callback_ctrl(0); if (!err) { /* Setup callbacks and columns for the idle state. */ err = set_cols(wakeup_cols); if (!err) { err = callback_ctrl(wakeup_rows); } } LOG_DBG("Completed setting pin wakeup: %d", err); return err; } ... static void button_pressed_isr(const struct device *gpio_dev, struct gpio_callback *cb, uint32_t pins) { int err = 0; /* Scanning will be scheduled, switch off pins */ if (set_cols(0)) { LOG_ERR("Cannot control pins"); err = -EFAULT; } /* This is a workaround. Zephyr will set any pin triggering interrupt * at the moment. Not only our pins. */ pins = pins & cb->pin_mask; /* Disable all interrupts synchronously requires holding a spinlock. * The problem is that GPIO callback disable code takes time. If lock * is kept during this operation BLE stack can fail in some cases. * Instead we disable callbacks associated with the pins. This is to * make sure CPU is available for threads. The remaining callbacks are * disabled in the workqueue thread context. Work code also cancels * itself to prevent double execution when interrupt for another * pin was triggered in-between. */ for (uint32_t pin = 0; (pins != 0) && !err; pins >>= 1, pin++) { if ((pins & 1) != 0) { err = gpio_pin_interrupt_configure(gpio_dev, pin, GPIO_INT_DISABLE); } } if (err) { LOG_ERR("Cannot disable callbacks"); module_set_state(MODULE_STATE_ERROR); } else { err = k_work_reschedule(&button_pressed, K_NO_WAIT); LOG_DBG("Disabled callbacks: %d", err); } }
Here are the logs when pressing the button during power off:
[00:00:00.309,814] <inf> app_event_manager: e: force_power_down_event [00:00:00.309,814] <inf> power_manager: Force power down processing [00:00:00.309,906] <inf> app_event_manager: e: power_down_event [00:00:00.310,058] <dbg> buttons: setup_pin_wakeup: Setting pin wakeup: 0xffffffff 0xffffffe4 [00:00:00.310,150] <dbg> buttons: setup_pin_wakeup: Completed setting pin wakeup: 0 [00:00:00.310,607] <inf> power_manager: Power down the board [00:00:00.310,913] <inf> app_event_manager: e:module_state_event module:buttons state:STANDBY [00:00:00.311,035] <inf> app_event_manager: e:module_state_event module:board state:OFF [00:00:00.317,413] <wrn> power_manager: System turned off [00:00:00.357,299] <dbg> buttons: button_pressed_isr: Disabled callbacks: 1
Although the k_work_reschedule call in button_pressed_isr succeeds, the work is never performed. It seems that the button module should somehow check if work can be scheduled, but I'm not sure what is preventing it from firing. Is the RTC stopped? Is the work cancelled on power down? Should k_work_reschedule even succeed in this situation?