Merge pull request #92 from ptr727/gps-enable-logical-gpio

Drive every GPS control line with logical GPIO semantics
This commit is contained in:
liquidraver
2026-09-19 20:51:22 +02:00
committed by GitHub
3 changed files with 59 additions and 41 deletions
+49 -33
View File
@@ -100,7 +100,7 @@ static bool gps_diag_on = false;
* every wake is a warm start, not a cold one. (Backup mode, the deeper
* state, requires cutting VCC while V_BCKP holds the RTC domain; this
* board has no VCC switch, so Standby is the floor available to us.)
* - T1000-E (AG3335): GPS_EN LOW + VRTC HIGH = warm standby (ephemeris
* - T1000-E (AG3335): GPS_EN de-asserted + VRTC asserted = warm standby (ephemeris
* preserved via backup RAM, ~1-2µA VRTC current)
* - All boards: gps-enable alias → GPIO power control
*
@@ -228,7 +228,7 @@ static void gnss_data_cb(const struct device *dev, const struct gnss_data *data)
if (!gps_enabled || gps_current_state == GPS_STATE_STANDBY) {
/* GPS disabled or in standby — ignore NMEA data.
* The GNSS driver fires callbacks as long as the UART has data,
* even after we drive GPS_EN LOW (module drains its buffer).
* even after we de-assert GPS_EN (module drains its buffer).
* On boards without GPS power control (e.g. RAK3401 where 3V3_S
* rail is shared with LoRa FEM), the GPS module stays powered in
* standby and keeps streaming NMEA — suppress those callbacks to
@@ -1084,7 +1084,7 @@ static const struct gpio_dt_spec gps_sleep_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps_
#define HAS_GPS_SLEEP 0
#endif
/* GPS RTC interrupt pin — held LOW during normal operation */
/* GPS RTC interrupt pin — held de-asserted during normal operation */
#if DT_NODE_EXISTS(DT_ALIAS(gps_rtc_int))
static const struct gpio_dt_spec gps_rtcint_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps_rtc_int), gpios);
#define HAS_GPS_RTCINT 1
@@ -1143,42 +1143,51 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
if (on) {
#if HAS_T1000_GPS_CONTROL
/* T1000-E power-on sequence (from Arduino target.cpp start_gps())
* Must follow this exact order with delays:
* 1. GPS_EN HIGH, delay 10ms
* 2. GPS_VRTC_EN HIGH, delay 10ms (critical - RTC power)
* 3. GPS_RESET HIGH, delay 10ms, then LOW
* 4. GPS_SLEEP_INT HIGH
* Must follow this exact order with delays. Levels are ASSERTED /
* DE-ASSERTED, not physical: Arduino states them as HIGH/LOW because
* its pins are all active-high, while this block is also reached by
* boards whose lines are active-low (see the gate below).
* 1. GPS_EN asserted, delay 10ms
* 2. GPS_VRTC_EN asserted, delay 10ms (critical - RTC power)
* 3. GPS_RESET asserted, delay 10ms, then released
* 4. GPS_SLEEP_INT asserted
*
* Despite the name this is not a T1000-E-only path:
* HAS_T1000_GPS_CONTROL is (HAS_GPS_VRTC || HAS_GPS_RESET ||
* HAS_GPS_SLEEP), so a bare gps-reset alias is enough to route a
* board here. heltec_wifi_lora32_v4, _v43 and thinknode_m9 all arrive
* this way, and all three declare gps-enable active-low.
*/
if (gpio_is_ready_dt(&gps_enable_gpio)) {
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_HIGH);
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_ACTIVE);
}
k_msleep(10);
#if HAS_GPS_VRTC
if (gpio_is_ready_dt(&gps_vrtc_gpio)) {
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_HIGH);
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_ACTIVE);
}
k_msleep(10);
#endif
#if HAS_GPS_RESET
if (gpio_is_ready_dt(&gps_reset_gpio)) {
gpio_pin_configure_dt(&gps_reset_gpio, GPIO_OUTPUT_HIGH);
gpio_pin_configure_dt(&gps_reset_gpio, GPIO_OUTPUT_ACTIVE);
k_msleep(10);
gpio_pin_set_dt(&gps_reset_gpio, 0); /* Release reset */
gpio_pin_set_dt(&gps_reset_gpio, 0); /* Release reset (logical) */
}
#endif
#if HAS_GPS_SLEEP
if (gpio_is_ready_dt(&gps_sleep_gpio)) {
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_HIGH);
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_ACTIVE);
}
#endif
#if HAS_GPS_RTCINT
/* GPS_RTC_INT (P0.15) — held LOW during normal operation */
/* GPS_RTC_INT (P0.15) — held de-asserted during normal operation */
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
@@ -1196,9 +1205,12 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
/* Simple boards - just GPS_EN */
if (!gps_gpio_configured) {
if (gpio_is_ready_dt(&gps_enable_gpio)) {
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_HIGH);
/* ACTIVE, not HIGH: gpio_pin_set_dt() below is
* logical, so a physical init flag here would assert
* the opposite level on an active-low gps-enable. */
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_ACTIVE);
gps_gpio_configured = true;
LOG_INF("GPS power GPIO configured, set HIGH");
LOG_INF("GPS power GPIO configured, set ACTIVE");
} else {
LOG_WRN("GPS power GPIO not ready");
return;
@@ -1212,7 +1224,7 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
/* Power off sequence */
#if HAS_GPS_RESET
/* Hold GPS in reset during power-off — matches Arduino sleep_gps()/stop_gps().
* Ensures chip sees RESET asserted when GPS_EN goes HIGH on next
* Ensures chip sees RESET asserted when GPS_EN is asserted on next
* power-on, preventing uncontrolled startup before the reset pulse.
* Configure-on-first-use (mirrors the GPS_EN pin below): on a
* boot-with-GPS-off the power-on path never ran, so the pin isn't an
@@ -1233,18 +1245,18 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
/* Full power-off: VRTC off too (cold start on next wake) */
if (gpio_is_ready_dt(&gps_vrtc_gpio)) {
if (!gps_gpio_configured) {
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_INACTIVE);
} else {
gpio_pin_set_dt(&gps_vrtc_gpio, 0);
}
}
}
/* else: warm standby — VRTC stays HIGH, preserving
/* else: warm standby — VRTC stays asserted, preserving
* ephemeris/almanac/RTC for fast re-acquisition (~1-2 µA) */
#endif
if (gpio_is_ready_dt(&gps_enable_gpio)) {
if (!gps_gpio_configured) {
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_INACTIVE);
gps_gpio_configured = true;
} else {
gpio_pin_set_dt(&gps_enable_gpio, 0);
@@ -1252,16 +1264,16 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
}
#if HAS_GPS_RESETB
/* Drive RESETB LOW when GPS is off (Arduino sleep_gps/stop_gps) */
/* Assert RESETB when GPS is off (Arduino sleep_gps/stop_gps) */
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
#if HAS_GPS_RTCINT
/* GPS_RTC_INT stays LOW during sleep/off (same as normal operation) */
/* GPS_RTC_INT stays de-asserted during sleep/off (same as normal operation) */
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
@@ -1277,7 +1289,9 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
#endif
}
/* Drive all GPS power-enable GPIOs LOW for System OFF.
/* Put every GPS control line this board declares into its de-asserted state
* for System OFF -- the power enable, and where present VRTC, reset, sleep,
* rtcint and resetb -- not the power enable alone.
* Uses gpio_pin_configure_dt() so pins are properly set even if
* gps_power_control() was never called (GPIO not yet configured). */
void gps_power_off_for_shutdown(void)
@@ -1289,33 +1303,35 @@ void gps_power_off_for_shutdown(void)
}
#endif
#if HAS_GPS_POWER_CONTROL
/* INACTIVE: physical LOW leaves an active-low enable asserted, i.e. the
* module still powered across System OFF. */
if (gpio_is_ready_dt(&gps_enable_gpio)) {
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_enable_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
#if HAS_GPS_VRTC
if (gpio_is_ready_dt(&gps_vrtc_gpio)) {
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_vrtc_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
#if HAS_GPS_RESET
if (gpio_is_ready_dt(&gps_reset_gpio)) {
gpio_pin_configure_dt(&gps_reset_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_reset_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
#if HAS_GPS_SLEEP
if (gpio_is_ready_dt(&gps_sleep_gpio)) {
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
#if HAS_GPS_RTCINT
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
#if HAS_GPS_RESETB
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_LOW);
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_INACTIVE);
}
#endif
}
@@ -1527,7 +1543,7 @@ static uint32_t gps_acquire_window_ms(void)
/* Go to standby and schedule next wake.
* GPIO power control only — keep VRTC for warm start on T1000-E,
* FORCE_ON pin LOW for L76K hardware standby. */
* FORCE_ON de-asserted for L76K hardware standby. */
static void gps_go_to_standby(void)
{
/* Unified standby interval for both roles — set from prefs.gps_interval
@@ -71,10 +71,11 @@
* Declaring gps-reset moves this board onto the manager's
* HAS_T1000_GPS_CONTROL power sequence (EN -> reset -> release). That is
* safe without a VRTC pin — every VRTC/SLEEP step in that path is behind
* its own #if. Note the power-ON pulse uses the raw GPIO_OUTPUT_HIGH flag,
* so on an active-LOW line it de-asserts rather than pulsing; the useful
* half is power-OFF, which asserts reset (logical 1) and holds the module
* down while its supply is gated. */
* its own #if. That sequence now drives every line logically, so the
* power-ON pulse asserts on this active-LOW reset instead of de-asserting
* it; before that fix the pulse was a no-op here and only the power-OFF
* half, which asserts reset and holds the module down while its supply is
* gated, did anything. */
gps_rst: gps-reset {
compatible = "gpio-leds";
gps_reset_pin: gps_reset {
@@ -71,10 +71,11 @@
* Declaring gps-reset moves this board onto the manager's
* HAS_T1000_GPS_CONTROL power sequence (EN -> reset -> release). That is
* safe without a VRTC pin — every VRTC/SLEEP step in that path is behind
* its own #if. Note the power-ON pulse uses the raw GPIO_OUTPUT_HIGH flag,
* so on an active-LOW line it de-asserts rather than pulsing; the useful
* half is power-OFF, which asserts reset (logical 1) and holds the module
* down while its supply is gated. */
* its own #if. That sequence now drives every line logically, so the
* power-ON pulse asserts on this active-LOW reset instead of de-asserting
* it; before that fix the pulse was a no-op here and only the power-OFF
* half, which asserts reset and holds the module down while its supply is
* gated, did anything. */
gps_rst: gps-reset {
compatible = "gpio-leds";
gps_reset_pin: gps_reset {