/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "board/board.h" #include "drivers/button.h" #include "drivers/otp.h" #include "drivers/rtc.h" #include "drivers/periph_config.h" #include "drivers/dbgserial.h" #include "system/reset.h" static bool prv_is_wake_on_usb_supported(void) { // we accidentally left off a pull-up on early BB2s and v1_5 boards // with the upshot of not being able to support wake from standby on VUSB if (BOARD_CONFIG_POWER.wake_on_usb_power) { if (!otp_is_locked(OTP_HWVER)) { dbgserial_putstr("No HW Version in OTP"); // let's be optimistic return true; } const char* hw_ver = otp_get_slot(OTP_HWVER); #if defined(BOARD_BB2) // We fixed the issue for BB2.1 (900-0-22-02-R1) const char* no_support_hw_ver = "BB2.0"; return (memcmp(hw_ver, no_support_hw_ver, strlen(no_support_hw_ver)) != 0); #elif defined(BOARD_V1_5) // We fixed the issue for V3R2 (101-0-22-10-R3) const char* no_support_hw_ver = "V3R1"; return (memcmp(hw_ver, no_support_hw_ver, strlen(no_support_hw_ver)) != 0); #else return true; #endif } else { return false; } } static void prv_wait_until_buttons_are_released(void) { for (int bounce_count = 0; bounce_count < 10; ++bounce_count) { uint8_t button_state; // First, see if the buttons are all released for a period of time. for (int i = 0; i < 10000; ++i) { button_state = button_get_state_bits(); if (button_state != 0) { // Someone pushed a button! break; } } if (button_state == 0) { // We made it through with all the buttons released. We're good. return; } // Alright, so either the button is held down or we hit a bounce. Wait // for all the buttons to release again. // 100000 is about a second in practice for (int i = 0; i < 100000; ++i) { if (button_get_state_bits() == 0) { // All the buttons are released! break; } } } } static void prv_clear_wakeup_flags(void) { // This function follows the steps listed in Erratum 2.1.4 "Wakeup sequence from Standby mode..." // to avoid a situation where the watch cannot wake up // or immediately wakes up after going into standby. // The erratum says all used wakeup sources need to be disabled before // reenabling the required ones, so to be safe we disable all wakeup sources // to avoid dependence on knowing which wakeup sources the firmware left set // Possible wakeup sources taken from reference manual 4.3.5 "Exiting Standby Mode" // Disable the Wakeup pin PWR_WakeUpPinCmd(DISABLE); // Clear RTC interrupts, this ensures the flags won't be reset after we clear them RTC_ITConfig(RTC_IT_TAMP | RTC_IT_TS | RTC_IT_WUT | RTC_IT_ALRA | RTC_IT_ALRB, DISABLE); // Clear all RTC wakeup flags RTC_ClearFlag(RTC_FLAG_TAMP1F | RTC_FLAG_TSF | RTC_FLAG_WUTF | RTC_FLAG_ALRBF | RTC_FLAG_ALRAF); // At this point we know the wakeup flags are cleared so we can clear the PWR wakeup flag PWR->CR |= PWR_CR_CWUF; } static void prv_enable_wake_on_usb(void) { // Use the RTC timestamp alternate function to trigger a wakeup from the VUSB interrupt // We don't clear all the wakeup flags here as said in // 4.3.6 "Safe RTC alternate function wakeup flag clearing sequence", because // prv_clear_wakeup_flags already cleared them for use by multiple wakeup sources RTC_TimeStampPinSelection(RTC_TimeStampPin_PC13); RTC_TimeStampCmd(RTC_TimeStampEdge_Falling, ENABLE); RTC_ITConfig(RTC_IT_TS, ENABLE); } void enter_standby_mode(void) { rtc_slow_down(); // Set wakeup events for the board // If the WKUP pin is high when we enable wakeup, an additional // wakeup event is registered (4.4.2 "PWR power control/status register"), // which will cause the board to wake up immediately after entering standby. Therefore we // wait until the button is released (or too much time has passed). // It is possible to work around needing this by enabling the WKUP pin before // clearing the PWR WUF flag, but that risks running afoul of errata 2.1.4 prv_wait_until_buttons_are_released(); prv_clear_wakeup_flags(); PWR_WakeUpPinCmd(ENABLE); if (prv_is_wake_on_usb_supported()) { dbgserial_putstr("usb wakeup supported"); prv_enable_wake_on_usb(); } // Put the board into standby mode. The standard peripheral library provides PWR_EnterSTANDBYMode // to do this, but that function clears the WUF (wakeup) flag. According to errata 2.1.4 if the // wakeup flag is cleared when any wakeup source is high, further wakeup events may be masked. // This means if a button press or usb plugin was to occur in between enabling the wakeup events // and clearing the flag, the watch wouldn't wake up. dbgserial_putstr("Entering standby"); // Steps to enter standby follow 4.3.5 "Entering Standby mode" Table 11 // (except where they conflict with errata 2.1.4) // Select STANDBY mode PWR->CR |= PWR_CR_PDDS; // Set SLEEPDEEP bit on the cortex system control register SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // Wait for interrupt __WFI(); } bool should_leave_standby_mode(void) { if (RTC_GetFlagStatus(RTC_FLAG_TSF)) { // we were woken by the USB power being plugged in dbgserial_putstr("USB wakeup"); return true; } // Make sure a button is held down before waking up for (int i = 0; i < 100000; ++i) { if (button_get_state_bits() == 0) { // stop waiting if not held down any longer and go back to sleep return false; } } return true; } void leave_standby_mode(void) { // Speed up the RTC so the firmware doesn't need to deal with it rtc_speed_up(); }