embassy_nrf/usb/
vbus_detect.rs

1//! Trait and implementations for performing VBUS detection.
2
3use core::future::{poll_fn, Future};
4use core::sync::atomic::{AtomicBool, Ordering};
5use core::task::Poll;
6
7use embassy_sync::waitqueue::AtomicWaker;
8
9use super::BUS_WAKER;
10use crate::interrupt::typelevel::Interrupt;
11use crate::{interrupt, pac};
12
13/// Trait for detecting USB VBUS power.
14///
15/// There are multiple ways to detect USB power. The behavior
16/// here provides a hook into determining whether it is.
17pub trait VbusDetect {
18    /// Report whether power is detected.
19    ///
20    /// This is indicated by the `USBREGSTATUS.VBUSDETECT` register, or the
21    /// `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral.
22    fn is_usb_detected(&self) -> bool;
23
24    /// Wait until USB power is ready.
25    ///
26    /// USB power ready is indicated by the `USBREGSTATUS.OUTPUTRDY` register, or the
27    /// `USBPWRRDY` event from the `POWER` peripheral.
28    async fn wait_power_ready(&mut self) -> Result<(), ()>;
29}
30
31#[cfg(not(feature = "_nrf5340"))]
32type UsbRegIrq = interrupt::typelevel::CLOCK_POWER;
33#[cfg(feature = "_nrf5340")]
34type UsbRegIrq = interrupt::typelevel::USBREGULATOR;
35
36#[cfg(not(feature = "_nrf5340"))]
37const USB_REG_PERI: pac::power::Power = pac::POWER;
38#[cfg(feature = "_nrf5340")]
39const USB_REG_PERI: pac::usbreg::Usbreg = pac::USBREGULATOR;
40
41/// Interrupt handler.
42pub struct InterruptHandler {
43    _private: (),
44}
45
46impl interrupt::typelevel::Handler<UsbRegIrq> for InterruptHandler {
47    unsafe fn on_interrupt() {
48        let regs = USB_REG_PERI;
49
50        if regs.events_usbdetected().read() != 0 {
51            regs.events_usbdetected().write_value(0);
52            BUS_WAKER.wake();
53        }
54
55        if regs.events_usbremoved().read() != 0 {
56            regs.events_usbremoved().write_value(0);
57            BUS_WAKER.wake();
58            POWER_WAKER.wake();
59        }
60
61        if regs.events_usbpwrrdy().read() != 0 {
62            regs.events_usbpwrrdy().write_value(0);
63            POWER_WAKER.wake();
64        }
65    }
66}
67
68/// [`VbusDetect`] implementation using the native hardware POWER peripheral.
69///
70/// Unsuitable for usage with the nRF softdevice, since it reserves exclusive acces
71/// to POWER. In that case, use [`VbusDetectSignal`].
72pub struct HardwareVbusDetect {
73    _private: (),
74}
75
76static POWER_WAKER: AtomicWaker = AtomicWaker::new();
77
78impl HardwareVbusDetect {
79    /// Create a new `VbusDetectNative`.
80    pub fn new(_irq: impl interrupt::typelevel::Binding<UsbRegIrq, InterruptHandler> + 'static) -> Self {
81        let regs = USB_REG_PERI;
82
83        UsbRegIrq::unpend();
84        unsafe { UsbRegIrq::enable() };
85
86        regs.intenset().write(|w| {
87            w.set_usbdetected(true);
88            w.set_usbremoved(true);
89            w.set_usbpwrrdy(true);
90        });
91
92        Self { _private: () }
93    }
94}
95
96impl VbusDetect for HardwareVbusDetect {
97    fn is_usb_detected(&self) -> bool {
98        let regs = USB_REG_PERI;
99        regs.usbregstatus().read().vbusdetect()
100    }
101
102    fn wait_power_ready(&mut self) -> impl Future<Output = Result<(), ()>> {
103        poll_fn(|cx| {
104            POWER_WAKER.register(cx.waker());
105            let regs = USB_REG_PERI;
106
107            if regs.usbregstatus().read().outputrdy() {
108                Poll::Ready(Ok(()))
109            } else if !self.is_usb_detected() {
110                Poll::Ready(Err(()))
111            } else {
112                Poll::Pending
113            }
114        })
115    }
116}
117
118/// Software-backed [`VbusDetect`] implementation.
119///
120/// This implementation does not interact with the hardware, it allows user code
121/// to notify the power events by calling functions instead.
122///
123/// This is suitable for use with the nRF softdevice, by calling the functions
124/// when the softdevice reports power-related events.
125pub struct SoftwareVbusDetect {
126    usb_detected: AtomicBool,
127    power_ready: AtomicBool,
128}
129
130impl SoftwareVbusDetect {
131    /// Create a new `SoftwareVbusDetect`.
132    pub fn new(usb_detected: bool, power_ready: bool) -> Self {
133        BUS_WAKER.wake();
134
135        Self {
136            usb_detected: AtomicBool::new(usb_detected),
137            power_ready: AtomicBool::new(power_ready),
138        }
139    }
140
141    /// Report whether power was detected.
142    ///
143    /// Equivalent to the `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral.
144    pub fn detected(&self, detected: bool) {
145        self.usb_detected.store(detected, Ordering::Relaxed);
146        self.power_ready.store(false, Ordering::Relaxed);
147        BUS_WAKER.wake();
148        POWER_WAKER.wake();
149    }
150
151    /// Report when USB power is ready.
152    ///
153    /// Equivalent to the `USBPWRRDY` event from the `POWER` peripheral.
154    pub fn ready(&self) {
155        self.power_ready.store(true, Ordering::Relaxed);
156        POWER_WAKER.wake();
157    }
158}
159
160impl VbusDetect for &SoftwareVbusDetect {
161    fn is_usb_detected(&self) -> bool {
162        self.usb_detected.load(Ordering::Relaxed)
163    }
164
165    fn wait_power_ready(&mut self) -> impl Future<Output = Result<(), ()>> {
166        poll_fn(move |cx| {
167            POWER_WAKER.register(cx.waker());
168
169            if self.power_ready.load(Ordering::Relaxed) {
170                Poll::Ready(Ok(()))
171            } else if !self.usb_detected.load(Ordering::Relaxed) {
172                Poll::Ready(Err(()))
173            } else {
174                Poll::Pending
175            }
176        })
177    }
178}