1use core::sync::atomic::{AtomicBool, Ordering};
4
5use crate::error::Result;
6use crate::hal;
7use crate::sync::once::OnceCell;
8
9use hal::device_tree::{LedDefaultState, LedOutputMode, LedPull, LedRegistryEntry};
10use hal::gpio::{Level, Pin, Pull};
11
12struct LedState {
13 entry: &'static LedRegistryEntry,
14 initialized: AtomicBool,
16}
17
18static SLOTS: [OnceCell<LedState>; hal::device_tree::LED_REGISTRY.len()] =
19 [const { OnceCell::new() }; hal::device_tree::LED_REGISTRY.len()];
20
21pub struct Led {
22 state: &'static LedState,
23}
24
25impl Led {
26 pub fn open_by_alias(name: &str) -> Result<Self> {
27 let entry = hal::device_tree::led_by_alias(name)
28 .ok_or_else(|| kerr!(ENODEV, "led alias not found: {name}"))?;
29 Self::open_for_node(entry.node)
30 }
31
32 pub fn open_by_label(label: &str) -> Result<Self> {
33 let entry = hal::device_tree::led_by_label(label)
34 .ok_or_else(|| kerr!(ENODEV, "led label not found: {label}"))?;
35 Self::open_for_node(entry.node)
36 }
37
38 fn open_for_node(node: usize) -> Result<Self> {
39 for cell in SLOTS.iter() {
40 if let Some(state) = cell.get() {
41 if state.entry.node == node {
42 if !state.initialized.load(Ordering::Acquire) {
43 return Err(kerr!(EIO, "LED node {node} init failed"));
44 }
45 return Ok(Self { state });
46 }
47 }
48 }
49 Err(kerr!(ENODEV, "LED node {node} not registered"))
50 }
51
52 pub fn label(&self) -> &'static str {
53 self.state.entry.label
54 }
55
56 pub fn on(&self) -> Result<()> {
57 self.set(true)
58 }
59
60 pub fn off(&self) -> Result<()> {
61 self.set(false)
62 }
63
64 pub fn set(&self, on: bool) -> Result<()> {
65 hal::gpio::write(pin_of(self.state.entry), level_for(self.state.entry, on))
66 .map_err(Into::into)
67 }
68
69 pub fn toggle(&self) -> Result<()> {
70 hal::gpio::toggle(pin_of(self.state.entry)).map_err(Into::into)
71 }
72}
73
74fn pin_of(entry: &LedRegistryEntry) -> Pin {
75 Pin {
76 port: entry.port,
77 line: entry.line,
78 }
79}
80
81fn level_for(entry: &LedRegistryEntry, on: bool) -> Level {
82 let active_low = entry.active_low != 0;
83 if on ^ active_low {
84 Level::High
85 } else {
86 Level::Low
87 }
88}
89
90fn keep_initial_level(entry: &LedRegistryEntry) -> Level {
91 let pin = pin_of(entry);
92 if let Err(e) = hal::gpio::enable_port_clock(pin) {
93 kprintln!(
94 " LED {}: keep: enable_port_clock failed ({:?}); falling back to Off",
95 entry.label,
96 e,
97 );
98 return level_for(entry, false);
99 }
100 match hal::gpio::read_odr(pin) {
101 Ok(Level::High) => Level::High,
102 Ok(Level::Low) => level_for(entry, false),
103 Err(e) => {
104 kprintln!(
105 " LED {}: keep: read_odr failed ({:?}); falling back to Off",
106 entry.label,
107 e,
108 );
109 level_for(entry, false)
110 }
111 }
112}
113
114pub fn init() {
115 let entries = hal::device_tree::LED_REGISTRY;
116 kprintln!("Found {} gpio-led entries", entries.len());
117
118 for (i, entry) in entries.iter().enumerate() {
119 let state = LedState {
120 entry,
121 initialized: AtomicBool::new(false),
122 };
123 let state_ref: &'static LedState = SLOTS[i].set_or_get(state);
124
125 let initial = match entry.default_state {
126 LedDefaultState::Off => level_for(entry, false),
127 LedDefaultState::On => level_for(entry, true),
128 LedDefaultState::Keep => keep_initial_level(entry),
129 };
130 let pull = match entry.pull {
131 LedPull::None => Pull::None,
132 LedPull::Up => Pull::Up,
133 LedPull::Down => Pull::Down,
134 };
135 let res = match entry.output_mode {
136 LedOutputMode::OpenDrain => {
137 hal::gpio::configure_output_od(pin_of(entry), initial, pull)
138 }
139 LedOutputMode::PushPull => hal::gpio::configure_output(pin_of(entry), initial, pull),
140 };
141 match res {
142 Ok(()) => {
143 state_ref.initialized.store(true, Ordering::Release);
144 kprintln!(
145 " Initialized LED {} on port 0x{:x} line {} ({:?}, {:?}, {:?})",
146 entry.label,
147 entry.port,
148 entry.line,
149 entry.default_state,
150 entry.output_mode,
151 entry.pull,
152 );
153 }
154 Err(e) => kprintln!(" LED {}: configure_output failed: {:?}", entry.label, e),
155 }
156 }
157}