Files
2026-08-28 13:48:53 +02:00

118 lines
5.4 KiB
Plaintext

/*
* SPDX-License-Identifier: MIT
* Devicetree half of boards/common/pm_esp32.conf — auto-paired by
* zephcore_auto_pair_overlay() in CMakeLists.txt, so passing the .conf is
* enough; this file does not need to be listed separately.
*/
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/gpio/espressif-esp32-gpio.h>
/*
* The RTC timer is the wake source for every timed sleep. Upstream leaves it
* status = "disabled" (zephyr/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi),
* and soc/espressif/common/power.c checks for it in rtc_wakeup_enable():
* without a ready rtc_timer device it logs "Sleep skipped. Make sure
* '&rtc_timer' is enabled as a wakeup source." and returns false — so
* CONFIG_PM=y on its own is a silent no-op. This is the single easiest thing
* to forget when enabling ESP32 PM.
*/
&rtc_timer {
status = "okay";
wakeup-source;
};
/*
* Arms the GPIO wake path. gpio_esp32_pm_action() calls
* esp_sleep_enable_gpio_wakeup() on PM_DEVICE_ACTION_RESUME, but only when the
* PORT is wakeup-capable — that is what pm_device_wakeup_is_capable() tests,
* and it is set by this property. Per-PIN arming is separate and comes from
* patches/zephyr/0003, which passes GPIO_INT_WAKEUP on the DIO1 configure.
* Both halves are needed: the port enables the wake source, the pin selects
* which line triggers it.
*
* gpio0 covers GPIO 0-31, which is where every RTC-capable pin lives on the
* S3 (0-21) — so this is also the only port that can carry a LoRa DIO1 wake.
*/
&gpio0 {
wakeup-source;
};
/*
* Hold the SX1262 chip-select across light sleep.
*
* Light sleep powers down RTC_PERIPH on this SoC, and every LoRa pin on the
* allowlisted boards is an RTC-capable pad (GPIO 0-21), so their levels are
* NOT retained unless explicitly held. The chain:
*
* 1. patches/zephyr/0003 passes GPIO_INT_WAKEUP on DIO1. On the S3 that
* lands on esp_sleep_enable_ext1_wakeup_io() (drivers/gpio/gpio_esp32.c,
* SOC_PM_SUPPORT_EXT1_WAKEUP), i.e. RTC_EXT1_TRIG_EN.
* 2. esp_sleep_start() only forces ESP_PD_DOMAIN_RTC_PERIPH on for
* RTC_EXT0_TRIG_EN | RTC_GPIO_TRIG_EN. EXT1 is not in that mask, so the
* domain is left AUTO and powered OFF for the whole sleep.
* 3. ext1_wakeup_prepare() then calls rtcio_hal_hold_enable() for the EXT1
* pin *only*, and only because RTC_PERIPH is off — IDF's own code
* treating "RTC_PERIPH down" as "this pad cannot hold its own state".
* DIO1 survives; nothing else does.
* 4. Zephyr's general mechanism, esp32_sleep_gpio_prepare(), holds only pins
* carrying ESP32_GPIO_SLEEP_HOLD_EN. Hence this override.
*
* NSS is the pin that matters. Undriven, it floats, and the SX1262 samples
* SPI commands whenever NSS is low — so a sleeping chip sitting in warm-start
* sleep with its entire configuration (PA config, OCP, SetTxParams) in
* retention RAM is exposed to whatever the floating bus does. Nothing in the
* driver ever re-issues SetTxParams: lora_config() is its only writer and
* LoRaRadioBase::configure() short-circuits on every transmit after the first,
* so a corrupted TX power persists until reboot or a `set tx <new value>`.
* Symptom is a node that still relays but transmits ~30 dB down.
*
* Holding NSS high for the duration deselects the chip, which also makes SCK
* and MOSI harmless — nothing can be clocked in while CS is deasserted, so
* those pins do not need the pinctrl-side `sleep-hold-en`.
*
* All five allowlisted boards (heltec_wifi_lora32_v3/v4/v43,
* heltec_wireless_tracker/_v2) wire the radio identically — &spi2, NSS GPIO8,
* RESET GPIO12 — which is what lets this live in one shared overlay. Re-check
* that before adding a board to the allowlist in CMakeLists.txt.
*/
&spi2 {
cs-gpios = <&gpio0 8 (GPIO_ACTIVE_LOW | ESP32_GPIO_SLEEP_HOLD_EN)>;
};
/*
* The user button must wake the SoC too.
*
* DIO1 is not the only thing that needs to interrupt a sleeping node — a person
* pressing the button expects the display to come on. Marking the PORT
* wakeup-source above only enables esp_sleep_enable_gpio_wakeup(); the PIN is
* armed separately, and until now only DIO1 got that (via patches/zephyr/0003).
* The result was a node that woke for packets and ignored its own button: press
* it, nothing happens, the display never lights. Reported from the field.
*
* GPIO_INT_WAKEUP is (1 << 6) in dt-bindings/gpio/gpio.h, so it fits the 16-bit
* gpio_dt_flags_t and can be set here in DT rather than needing a driver patch —
* gpio_pin_configure_dt() ORs spec->dt_flags into the flags it passes, so it
* reaches gpio_esp32_config() and arms the wake.
*
* All five allowlisted boards put the USER key on the same pad (&gpio0 0 =
* GPIO0, inside the S3's RTC-capable 0-21 range) under the same node label,
* which is what lets this be one shared override. Re-check when allowlisting
* a new board.
*/
&button0 {
gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW | GPIO_INT_WAKEUP)>;
};
/*
* RESET is open-drain active-low against the SX1262's internal pull-up, so an
* unheld pad going high-Z already reads as "not reset" and this is belt and
* braces rather than a known fault. It costs nothing and removes the question
* of what the pad does on the way into and out of power-down — a spurious low
* here would reset the radio outright.
*/
&lora0 {
reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW |
ESP32_GPIO_SLEEP_HOLD_EN)>;
};