From a2c17b0c964df7e842fdc0322cbdca9541bd62a0 Mon Sep 17 00:00:00 2001 From: Philipp Erhardt Date: Wed, 13 May 2026 20:35:29 +0000 Subject: [PATCH 1/2] GPIO: don't turn off on boot --- machine/cortex-m/src/stub/device_tree.rs | 9 ++++ src/drivers/led.rs | 23 ++++++---- xtasks/crates/dtgen/src/codegen/led.rs | 57 ++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/machine/cortex-m/src/stub/device_tree.rs b/machine/cortex-m/src/stub/device_tree.rs index 7fbedfb..504c07d 100644 --- a/machine/cortex-m/src/stub/device_tree.rs +++ b/machine/cortex-m/src/stub/device_tree.rs @@ -86,6 +86,14 @@ pub fn can_by_compatible(_compatible: &str, _ord: usize) -> Option<&'static CanR None } +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LedDefaultState { + Off, + On, + Keep, +} + #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct LedRegistryEntry { @@ -94,6 +102,7 @@ pub struct LedRegistryEntry { pub line: u8, pub active_low: u8, pub label: &'static str, + pub default_state: LedDefaultState, } pub const LED_REGISTRY: &[LedRegistryEntry] = &[]; diff --git a/src/drivers/led.rs b/src/drivers/led.rs index c07508a..bbb64ce 100644 --- a/src/drivers/led.rs +++ b/src/drivers/led.rs @@ -6,7 +6,7 @@ use crate::error::Result; use crate::hal; use crate::sync::once::OnceCell; -use hal::device_tree::LedRegistryEntry; +use hal::device_tree::{LedDefaultState, LedRegistryEntry}; use hal::gpio::{Level, Pin}; struct LedState { @@ -71,14 +71,14 @@ impl Led { } } -fn pin_of(entry: &LedRegistryEntry) -> Pin { +const fn pin_of(entry: &LedRegistryEntry) -> Pin { Pin { port: entry.port, line: entry.line, } } -fn level_for(entry: &LedRegistryEntry, on: bool) -> Level { +const fn level_for(entry: &LedRegistryEntry, on: bool) -> Level { let active_low = entry.active_low != 0; if on ^ active_low { Level::High @@ -98,17 +98,22 @@ pub fn init() { }; let state_ref: &'static LedState = SLOTS[i].set_or_get(state); - // Drive the off level before switching to output so the line - // never glitches the active polarity on boot. - let off = level_for(entry, false); - match hal::gpio::configure_output(pin_of(entry), off) { + // Pre-MODER level from DT `default-state`; `Keep` is only + // meaningful on warm restart (cold reset reads 0 from analog). + let initial = match entry.default_state { + LedDefaultState::Off => level_for(entry, false), + LedDefaultState::On => level_for(entry, true), + LedDefaultState::Keep => hal::gpio::read(pin_of(entry)).unwrap_or(Level::Low), + }; + match hal::gpio::configure_output(pin_of(entry), initial) { Ok(()) => { state_ref.initialized.store(true, Ordering::Release); kprintln!( - " Initialized LED {} on port 0x{:x} line {}", + " Initialized LED {} on port 0x{:x} line {} ({:?})", entry.label, entry.port, - entry.line + entry.line, + entry.default_state, ); } Err(e) => kprintln!(" LED {}: configure_output failed: {:?}", entry.label, e), diff --git a/xtasks/crates/dtgen/src/codegen/led.rs b/xtasks/crates/dtgen/src/codegen/led.rs index b044720..0e54b77 100644 --- a/xtasks/crates/dtgen/src/codegen/led.rs +++ b/xtasks/crates/dtgen/src/codegen/led.rs @@ -1,9 +1,46 @@ -//! gpio-leds registry codegen. One entry per child of a `compatible = -//! "gpio-leds"` node; the (port, line, active_low, label) extraction is -//! shared with gpio-keys via [`super::collect_gpio_children`]. +//! gpio-leds registry codegen. Shared (port, line, active_low, label) +//! extraction via [`super::collect_gpio_children`]; `default-state` +//! (Linux/Zephyr binding) is parsed here into a `LedDefaultState` enum. use super::*; +#[derive(Clone, Copy)] +enum DefaultState { + Off, + On, + Keep, +} + +impl DefaultState { + fn from_node(node: &crate::ir::Node) -> Self { + match node.extra.get("default-state") { + Some(PropValue::Str(s)) => match s.as_str() { + "on" => DefaultState::On, + "keep" => DefaultState::Keep, + "off" => DefaultState::Off, + other => panic!( + "gpio-leds child {}: unknown `default-state` value {:?} \ + (expected \"on\", \"off\", or \"keep\")", + node.name, other + ), + }, + None => DefaultState::Off, + _ => panic!( + "gpio-leds child {}: `default-state` must be a string", + node.name + ), + } + } + + fn tokens(self) -> TokenStream { + match self { + DefaultState::Off => quote! { LedDefaultState::Off }, + DefaultState::On => quote! { LedDefaultState::On }, + DefaultState::Keep => quote! { LedDefaultState::Keep }, + } + } +} + #[derive(Clone)] struct Led { node: usize, @@ -11,6 +48,7 @@ struct Led { line: u8, active_low: u8, label: String, + default_state: DefaultState, } fn collect_leds(dt: &DeviceTree) -> Vec { @@ -22,6 +60,7 @@ fn collect_leds(dt: &DeviceTree) -> Vec { line: c.line, active_low: c.active_low, label: c.label, + default_state: DefaultState::from_node(c.child), }) .collect(); @@ -53,6 +92,7 @@ pub fn emit_registry(dt: &DeviceTree) -> TokenStream { let line = l.line; let active_low = l.active_low; let label = l.label.as_str(); + let default_state = l.default_state.tokens(); quote! { LedRegistryEntry { node: #node, @@ -60,11 +100,21 @@ pub fn emit_registry(dt: &DeviceTree) -> TokenStream { line: #line, active_low: #active_low, label: #label, + default_state: #default_state, }, } }); quote! { + #[repr(u8)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum LedDefaultState { + Off, + On, + /// Preserve current ODR (warm restart); cold boot reads analog → 0. + Keep, + } + #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct LedRegistryEntry { @@ -73,6 +123,7 @@ pub fn emit_registry(dt: &DeviceTree) -> TokenStream { pub line: u8, pub active_low: u8, pub label: &'static str, + pub default_state: LedDefaultState, } pub const LED_REGISTRY: &[LedRegistryEntry] = &[ From 869740480e75ab98a812c4d4acdecbf640045814 Mon Sep 17 00:00:00 2001 From: Philipp Erhardt Date: Wed, 13 May 2026 22:25:34 +0000 Subject: [PATCH 2/2] Ensure clock is enabled before reading --- machine/cortex-m/src/native/gpio.rs | 5 +++++ machine/cortex-m/src/stub/gpio.rs | 4 ++++ machine/cortex-m/st/stm32l4/interface/export.h | 1 + machine/cortex-m/st/stm32l4/interface/gpio.c | 9 +++++++++ machine/cortex-m/st/stm32l4/interface/gpio.h | 1 + src/drivers/led.rs | 10 +++++++--- 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/machine/cortex-m/src/native/gpio.rs b/machine/cortex-m/src/native/gpio.rs index 7f4659b..1fa86b6 100644 --- a/machine/cortex-m/src/native/gpio.rs +++ b/machine/cortex-m/src/native/gpio.rs @@ -88,6 +88,11 @@ pub fn write(pin: Pin, level: Level) -> Result<()> { ok_or_err(rc, ()) } +pub fn enable_port_clock(pin: Pin) -> Result<()> { + let rc = unsafe { bindings::gpio_clock_enable(port_ptr(pin)) }; + ok_or_err(rc, ()) +} + pub fn read(pin: Pin) -> Result { let mask = pin_mask(pin)?; let rc = unsafe { bindings::gpio_read(port_ptr(pin), mask) }; diff --git a/machine/cortex-m/src/stub/gpio.rs b/machine/cortex-m/src/stub/gpio.rs index 78809eb..ef9d9be 100644 --- a/machine/cortex-m/src/stub/gpio.rs +++ b/machine/cortex-m/src/stub/gpio.rs @@ -59,6 +59,10 @@ pub fn write(_pin: Pin, _level: Level) -> Result<()> { Err(PosixError::EOPNOTSUPP) } +pub fn enable_port_clock(_pin: Pin) -> Result<()> { + Err(PosixError::EOPNOTSUPP) +} + pub fn read(_pin: Pin) -> Result { Err(PosixError::EOPNOTSUPP) } diff --git a/machine/cortex-m/st/stm32l4/interface/export.h b/machine/cortex-m/st/stm32l4/interface/export.h index 0d97dd2..0a6c882 100644 --- a/machine/cortex-m/st/stm32l4/interface/export.h +++ b/machine/cortex-m/st/stm32l4/interface/export.h @@ -222,6 +222,7 @@ int gpio_configure_output_pp(void *port, uint16_t pin_mask, uint8_t initial); int gpio_write(void *port, uint16_t pin_mask, uint8_t level); int gpio_read(void *port, uint16_t pin_mask); int gpio_toggle(void *port, uint16_t pin_mask); +int gpio_clock_enable(void *port); // exti.c // edge_mask bitfield: 0x1 = rising, 0x2 = falling (see exti.h). diff --git a/machine/cortex-m/st/stm32l4/interface/gpio.c b/machine/cortex-m/st/stm32l4/interface/gpio.c index 81a42db..15ebefd 100644 --- a/machine/cortex-m/st/stm32l4/interface/gpio.c +++ b/machine/cortex-m/st/stm32l4/interface/gpio.c @@ -183,3 +183,12 @@ int gpio_toggle(void *port, uint16_t pin_mask) HAL_GPIO_TogglePin(p, pin_mask); return 0; } + +int gpio_clock_enable(void *port) +{ + GPIO_TypeDef *p = (GPIO_TypeDef *)port; + if (!port_is_known(p)) + return -PosixError_EINVAL; + gpio_enable_clock(p); + return 0; +} diff --git a/machine/cortex-m/st/stm32l4/interface/gpio.h b/machine/cortex-m/st/stm32l4/interface/gpio.h index 46ab9b0..455d1f5 100644 --- a/machine/cortex-m/st/stm32l4/interface/gpio.h +++ b/machine/cortex-m/st/stm32l4/interface/gpio.h @@ -24,3 +24,4 @@ int gpio_write(void *port, uint16_t pin_mask, uint8_t level); /* gpio_read returns 0 or 1 (level) on success, or negative PosixError. */ int gpio_read(void *port, uint16_t pin_mask); int gpio_toggle(void *port, uint16_t pin_mask); +int gpio_clock_enable(void *port); diff --git a/src/drivers/led.rs b/src/drivers/led.rs index bbb64ce..91aa613 100644 --- a/src/drivers/led.rs +++ b/src/drivers/led.rs @@ -98,12 +98,16 @@ pub fn init() { }; let state_ref: &'static LedState = SLOTS[i].set_or_get(state); - // Pre-MODER level from DT `default-state`; `Keep` is only - // meaningful on warm restart (cold reset reads 0 from analog). let initial = match entry.default_state { LedDefaultState::Off => level_for(entry, false), LedDefaultState::On => level_for(entry, true), - LedDefaultState::Keep => hal::gpio::read(pin_of(entry)).unwrap_or(Level::Low), + LedDefaultState::Keep => { + // enable clock, as we read before configure_output, which internally turns on clock + let _ = hal::gpio::enable_port_clock(pin_of(entry)).map_err(|e| { + kprintln!(" LED {}: enable_port_clock failed: {:?}", entry.label, e) + }); + hal::gpio::read(pin_of(entry)).unwrap_or(level_for(entry, false)) + } }; match hal::gpio::configure_output(pin_of(entry), initial) { Ok(()) => {