Skip to main content

osiris/drivers/
led.rs

1//! gpio-leds kernel driver.
2
3use 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    /// `Led::open_*` rejects handles whose init failed.
15    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}