rp2/modmachine: Selectively leave the USB clocks enabled in lightsleep.

Without this change going to lightsleep stops the USB peripheral clock, and
can lead to either the device going into a weird state or the host deciding
to issue a bus reset.

This change only keeps the USB peripheral clocks enabled if the USB device
is currently active and a host has configured the device.  This means the
USB device continues to respond to host transfers and (presumably) will
even complete pending endpoint transfers.  All other requests are NAKed
while still asleep, but the interaction with the host seems to resume
correctly on wake

Otherwise, if USB is not active or configured by a host, USB clocks are
disabled, the same as before.

With the change, one can issue a `machine.lightsleep(...)` with USB CDC
connected and the USB CDC remains connected during the sleep and resumes
when the lightsleep finishes.

Tested on a RPi Pico, the power consumption is:
- During normal idle at the REPL, about 15.3mA.
- During lightsleep, prior to this change, about 1.35mA.
- During lightsleep, with this change and USB CDC connected, about 3.7mA.

If power consumption should be as low as possible when USB is connected,
one can use `machine.USBDevice` to disable the USB before entering
lightsleep.

As discussed at https://github.com/orgs/micropython/discussions/14401

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
This commit is contained in:
Angus Gratton 2024-05-24 15:57:01 +10:00 committed by Damien George
parent 93394da69c
commit a84c7a0ed9

View File

@ -28,6 +28,7 @@
// extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE.
#include "py/mphal.h"
#include "mp_usbd.h"
#include "modmachine.h"
#include "uart.h"
#include "hardware/clocks.h"
@ -134,8 +135,17 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
return;
}
#endif
// Disable USB and ADC clocks.
clock_stop(clk_usb);
#if MICROPY_HW_ENABLE_USBDEV
// Only disable the USB clock if a USB host has not configured the device
bool disable_usb = !tud_mounted();
#else
bool disable_usb = true;
#endif
if (disable_usb) {
clock_stop(clk_usb);
}
clock_stop(clk_adc);
// CLK_REF = XOSC
@ -152,7 +162,9 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
// Disable PLLs.
pll_deinit(pll_sys);
pll_deinit(pll_usb);
if (disable_usb) {
pll_deinit(pll_usb);
}
// Disable ROSC.
rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB;
@ -181,6 +193,12 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
// TODO: Use RTC alarm to wake.
clocks_hw->sleep_en1 = 0;
}
if (!disable_usb) {
clocks_hw->sleep_en0 |= CLOCKS_SLEEP_EN0_CLK_SYS_PLL_USB_BITS;
clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_USB_USBCTRL_BITS;
}
scb_hw->scr |= M0PLUS_SCR_SLEEPDEEP_BITS;
__wfi();
scb_hw->scr &= ~M0PLUS_SCR_SLEEPDEEP_BITS;