/* * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #include "pico.h" #include "pico/stdlib.h" #include "pico/sleep.h" #include "hardware/rtc.h" #include "hardware/pll.h" #include "hardware/clocks.h" #include "hardware/xosc.h" #include "hardware/rosc.h" #include "hardware/regs/io_bank0.h" // For __wfi #include "hardware/sync.h" // For scb_hw so we can enable deep sleep #include "hardware/structs/scb.h" // The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, // until the source (either xosc or rosc) is started again by an external event. // In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks // block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) // can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. // TODO: Optionally, memories can also be powered down. static dormant_source_t _dormant_source; bool dormant_source_valid(dormant_source_t dormant_source) { return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC); } // In order to go into dormant mode we need to be running from a stoppable clock source: // either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks // and all PLLs void sleep_run_from_dormant_source(dormant_source_t dormant_source) { assert(dormant_source_valid(dormant_source)); _dormant_source = dormant_source; // FIXME: Just defining average rosc freq here. uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_MHZ * MHZ : 6.5 * MHZ; uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC : CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; // CLK_REF = XOSC or ROSC clock_configure(clk_ref, clk_ref_src, 0, // No aux mux src_hz, src_hz); // CLK SYS = CLK_REF clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, // Using glitchless mux src_hz, src_hz); // CLK USB = 0MHz clock_stop(clk_usb); // CLK ADC = 0MHz clock_stop(clk_adc); // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; clock_configure(clk_rtc, 0, // No GLMUX clk_rtc_src, src_hz, 46875); // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, src_hz, src_hz); pll_deinit(pll_sys); pll_deinit(pll_usb); // Assuming both xosc and rosc are running at the moment if (dormant_source == DORMANT_SOURCE_XOSC) { // Can disable rosc rosc_disable(); } else { // Can disable xosc xosc_disable(); } // Reconfigure uart with new clocks setup_default_uart(); } // Go to sleep until woken up by the RTC void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { // We should have already called the sleep_run_from_dormant_source function assert(dormant_source_valid(_dormant_source)); // Turn off all clocks when in sleep mode except for RTC clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; clocks_hw->sleep_en1 = 0x0; rtc_set_alarm(t, callback); uint save = scb_hw->scr; // Enable deep sleep at the proc scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; // Go to sleep __wfi(); } static void _go_dormant(void) { assert(dormant_source_valid(_dormant_source)); if (_dormant_source == DORMANT_SOURCE_XOSC) { xosc_dormant(); } else { rosc_set_dormant(); } } void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { bool low = !high; bool level = !edge; // Configure the appropriate IRQ at IO bank 0 assert(gpio_pin < NUM_BANK0_GPIOS); uint32_t event = 0; if (level && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS; if (level && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS; if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; gpio_set_dormant_irq_enabled(gpio_pin, event, true); _go_dormant(); // Execution stops here until woken up // Clear the irq so we can go back to dormant mode again if we want gpio_acknowledge_irq(gpio_pin, event); }