mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-25 00:02:28 +00:00
Puts in the machinery to handle power detected/removed
This commit is contained in:
parent
c46e9b6cfc
commit
4a8f117f25
@ -2,7 +2,7 @@
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
|
||||
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
|
||||
use core::task::Poll;
|
||||
|
||||
use cortex_m::peripheral::NVIC;
|
||||
@ -25,6 +25,7 @@ static EP0_WAKER: AtomicWaker = NEW_AW;
|
||||
static EP_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8];
|
||||
static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8];
|
||||
static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0);
|
||||
static POWER_AVAILABLE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub struct Driver<'d, T: Instance> {
|
||||
phantom: PhantomData<&'d mut T>,
|
||||
@ -39,6 +40,11 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||
irq.unpend();
|
||||
irq.enable();
|
||||
|
||||
// Initialize the bus so that it signals that power is available.
|
||||
// Not required when using with_power_management as we then rely on the irq.
|
||||
POWER_AVAILABLE.store(true, Ordering::Relaxed);
|
||||
BUS_WAKER.wake();
|
||||
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
alloc_in: Allocator::new(),
|
||||
@ -46,6 +52,25 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Establish a new device that then uses the POWER peripheral to
|
||||
/// detect USB power detected/removed events are handled.
|
||||
#[cfg(not(feature = "_nrf5340-app"))]
|
||||
pub fn with_power_management(
|
||||
_usb: impl Unborrow<Target = T> + 'd,
|
||||
irq: impl Unborrow<Target = T::Interrupt> + 'd,
|
||||
power_irq: impl Interrupt,
|
||||
) -> Self {
|
||||
let regs = unsafe { &*pac::POWER::ptr() };
|
||||
|
||||
power_irq.set_handler(Self::on_power_interrupt);
|
||||
power_irq.unpend();
|
||||
power_irq.enable();
|
||||
|
||||
regs.intenset.write(|w| w.usbdetected().set().usbremoved().set());
|
||||
|
||||
Self::new(_usb, irq)
|
||||
}
|
||||
|
||||
fn on_interrupt(_: *mut ()) {
|
||||
let regs = T::regs();
|
||||
|
||||
@ -76,7 +101,6 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||
// doesn't cause an infinite irq loop.
|
||||
if regs.events_usbevent.read().bits() != 0 {
|
||||
regs.events_usbevent.reset();
|
||||
//regs.intenclr.write(|w| w.usbevent().clear());
|
||||
BUS_WAKER.wake();
|
||||
}
|
||||
|
||||
@ -96,6 +120,31 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "_nrf5340-app"))]
|
||||
fn on_power_interrupt(_: *mut ()) {
|
||||
let regs = unsafe { &*pac::POWER::ptr() };
|
||||
|
||||
let mut power_changed = false;
|
||||
let mut power_available = false;
|
||||
|
||||
if regs.events_usbdetected.read().bits() != 0 {
|
||||
regs.events_usbdetected.reset();
|
||||
power_changed = true;
|
||||
power_available = true;
|
||||
}
|
||||
|
||||
if regs.events_usbremoved.read().bits() != 0 {
|
||||
regs.events_usbremoved.reset();
|
||||
power_changed = true;
|
||||
power_available = false;
|
||||
}
|
||||
|
||||
if power_changed {
|
||||
POWER_AVAILABLE.store(power_available, Ordering::Relaxed);
|
||||
BUS_WAKER.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
|
||||
@ -138,7 +187,10 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
|
||||
|
||||
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
||||
(
|
||||
Bus { phantom: PhantomData },
|
||||
Bus {
|
||||
phantom: PhantomData,
|
||||
power_available: false,
|
||||
},
|
||||
ControlPipe {
|
||||
_phantom: PhantomData,
|
||||
max_packet_size: control_max_packet_size,
|
||||
@ -149,6 +201,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
|
||||
|
||||
pub struct Bus<'d, T: Instance> {
|
||||
phantom: PhantomData<&'d mut T>,
|
||||
power_available: bool,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
|
||||
@ -246,6 +299,17 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
|
||||
trace!("USB event: ready");
|
||||
}
|
||||
|
||||
if POWER_AVAILABLE.load(Ordering::Relaxed) != self.power_available {
|
||||
self.power_available = !self.power_available;
|
||||
if self.power_available {
|
||||
trace!("Power event: available");
|
||||
return Poll::Ready(Event::PowerDetected);
|
||||
} else {
|
||||
trace!("Power event: removed");
|
||||
return Poll::Ready(Event::PowerRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
})
|
||||
}
|
||||
|
@ -161,6 +161,9 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||
dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
|
||||
}
|
||||
|
||||
// Initialize the bus so that it signals that power is available
|
||||
BUS_WAKER.wake();
|
||||
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
alloc: [EndpointData {
|
||||
@ -406,6 +409,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
|
||||
Bus {
|
||||
phantom: PhantomData,
|
||||
ep_types,
|
||||
inited: false,
|
||||
},
|
||||
ControlPipe {
|
||||
_phantom: PhantomData,
|
||||
@ -420,6 +424,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
|
||||
pub struct Bus<'d, T: Instance> {
|
||||
phantom: PhantomData<&'d mut T>,
|
||||
ep_types: [EpType; EP_COUNT - 1],
|
||||
inited: bool,
|
||||
}
|
||||
|
||||
impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
|
||||
@ -428,53 +433,59 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
|
||||
fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> {
|
||||
poll_fn(move |cx| unsafe {
|
||||
BUS_WAKER.register(cx.waker());
|
||||
let regs = T::regs();
|
||||
|
||||
let flags = IRQ_FLAGS.load(Ordering::Acquire);
|
||||
if self.inited {
|
||||
let regs = T::regs();
|
||||
|
||||
if flags & IRQ_FLAG_RESUME != 0 {
|
||||
IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel);
|
||||
return Poll::Ready(Event::Resume);
|
||||
}
|
||||
let flags = IRQ_FLAGS.load(Ordering::Acquire);
|
||||
|
||||
if flags & IRQ_FLAG_RESET != 0 {
|
||||
IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel);
|
||||
|
||||
trace!("RESET REGS WRITINGINGING");
|
||||
regs.daddr().write(|w| {
|
||||
w.set_ef(true);
|
||||
w.set_add(0);
|
||||
});
|
||||
|
||||
regs.epr(0).write(|w| {
|
||||
w.set_ep_type(EpType::CONTROL);
|
||||
w.set_stat_rx(Stat::NAK);
|
||||
w.set_stat_tx(Stat::NAK);
|
||||
});
|
||||
|
||||
for i in 1..EP_COUNT {
|
||||
regs.epr(i).write(|w| {
|
||||
w.set_ea(i as _);
|
||||
w.set_ep_type(self.ep_types[i - 1]);
|
||||
})
|
||||
if flags & IRQ_FLAG_RESUME != 0 {
|
||||
IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel);
|
||||
return Poll::Ready(Event::Resume);
|
||||
}
|
||||
|
||||
for w in &EP_IN_WAKERS {
|
||||
w.wake()
|
||||
}
|
||||
for w in &EP_OUT_WAKERS {
|
||||
w.wake()
|
||||
if flags & IRQ_FLAG_RESET != 0 {
|
||||
IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel);
|
||||
|
||||
trace!("RESET REGS WRITINGINGING");
|
||||
regs.daddr().write(|w| {
|
||||
w.set_ef(true);
|
||||
w.set_add(0);
|
||||
});
|
||||
|
||||
regs.epr(0).write(|w| {
|
||||
w.set_ep_type(EpType::CONTROL);
|
||||
w.set_stat_rx(Stat::NAK);
|
||||
w.set_stat_tx(Stat::NAK);
|
||||
});
|
||||
|
||||
for i in 1..EP_COUNT {
|
||||
regs.epr(i).write(|w| {
|
||||
w.set_ea(i as _);
|
||||
w.set_ep_type(self.ep_types[i - 1]);
|
||||
})
|
||||
}
|
||||
|
||||
for w in &EP_IN_WAKERS {
|
||||
w.wake()
|
||||
}
|
||||
for w in &EP_OUT_WAKERS {
|
||||
w.wake()
|
||||
}
|
||||
|
||||
return Poll::Ready(Event::Reset);
|
||||
}
|
||||
|
||||
return Poll::Ready(Event::Reset);
|
||||
}
|
||||
if flags & IRQ_FLAG_SUSPEND != 0 {
|
||||
IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel);
|
||||
return Poll::Ready(Event::Suspend);
|
||||
}
|
||||
|
||||
if flags & IRQ_FLAG_SUSPEND != 0 {
|
||||
IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel);
|
||||
return Poll::Ready(Event::Suspend);
|
||||
Poll::Pending
|
||||
} else {
|
||||
self.inited = true;
|
||||
return Poll::Ready(Event::PowerDetected);
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -202,6 +202,12 @@ pub enum Event {
|
||||
/// A USB resume request has been detected after being suspended or, in the case of self-powered
|
||||
/// devices, the device has been connected to the USB bus.
|
||||
Resume,
|
||||
|
||||
/// The USB power has been detected.
|
||||
PowerDetected,
|
||||
|
||||
/// The USB power has been removed. Not supported by all devices.
|
||||
PowerRemoved,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
|
@ -11,7 +11,6 @@ pub mod descriptor;
|
||||
mod descriptor_reader;
|
||||
pub mod driver;
|
||||
pub mod types;
|
||||
pub mod util;
|
||||
|
||||
use embassy::util::{select, Either};
|
||||
use heapless::Vec;
|
||||
@ -115,6 +114,7 @@ struct Inner<'d, D: Driver<'d>> {
|
||||
|
||||
device_state: UsbDeviceState,
|
||||
suspended: bool,
|
||||
power_available: bool,
|
||||
remote_wakeup_enabled: bool,
|
||||
self_powered: bool,
|
||||
|
||||
@ -157,6 +157,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
|
||||
device_state: UsbDeviceState::Disabled,
|
||||
suspended: false,
|
||||
power_available: false,
|
||||
remote_wakeup_enabled: false,
|
||||
self_powered: false,
|
||||
address: 0,
|
||||
@ -186,6 +187,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
/// before calling any other `UsbDevice` methods to fully reset the
|
||||
/// peripheral.
|
||||
pub async fn run_until_suspend(&mut self) -> () {
|
||||
while !self.inner.power_available {
|
||||
let evt = self.inner.bus.poll().await;
|
||||
self.inner.handle_bus_event(evt);
|
||||
}
|
||||
|
||||
if self.inner.device_state == UsbDeviceState::Disabled {
|
||||
self.inner.bus.enable().await;
|
||||
self.inner.device_state = UsbDeviceState::Default;
|
||||
@ -195,7 +201,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
}
|
||||
}
|
||||
|
||||
while !self.inner.suspended {
|
||||
while !self.inner.suspended && self.inner.power_available {
|
||||
let control_fut = self.control.setup();
|
||||
let bus_fut = self.inner.bus.poll();
|
||||
match select(bus_fut, control_fut).await {
|
||||
@ -223,7 +229,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||
///
|
||||
/// This future is cancel-safe.
|
||||
pub async fn wait_resume(&mut self) {
|
||||
while self.inner.suspended {
|
||||
while self.inner.suspended || !self.inner.power_available {
|
||||
let evt = self.inner.bus.poll().await;
|
||||
self.inner.handle_bus_event(evt);
|
||||
}
|
||||
@ -377,6 +383,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||
h.suspended(true);
|
||||
}
|
||||
}
|
||||
Event::PowerDetected => {
|
||||
trace!("usb: power detected");
|
||||
self.power_available = true;
|
||||
}
|
||||
Event::PowerRemoved => {
|
||||
trace!("usb: power removed");
|
||||
self.power_available = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,68 +0,0 @@
|
||||
use embassy::channel::signal::Signal;
|
||||
use embassy::util::{select, Either};
|
||||
|
||||
use crate::driver::Driver;
|
||||
use crate::UsbDevice;
|
||||
|
||||
/// Am enabled usb device is a device that further receives external notifications
|
||||
/// regarding whether it is enabled or not. A common example of where this is
|
||||
/// required is when receiving notifications from the POWER peripheral that
|
||||
/// USB has been connected to or removed. The device here wraps an existing
|
||||
/// USB device, keeping it publically available so that device-oriented operations
|
||||
/// may still be performed. A signal is also provided that enables/disables the
|
||||
/// USB device, taking care of suspension and resumption. In the case of the POWER
|
||||
/// peripheral, this signal can be used from within a POWER_CLOCK interrupt
|
||||
/// handler. Alternatively, for softdevice usage where the POWER peripheral is not
|
||||
/// available, similar USB power events can be leveraged.
|
||||
pub struct EnabledUsbDevice<'d, D: Driver<'d>> {
|
||||
pub underlying: UsbDevice<'d, D>,
|
||||
enable_usb_signal: &'d Signal<bool>,
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> EnabledUsbDevice<'d, D> {
|
||||
/// Wrap an existing UsbDevice and take a signal that will be used
|
||||
/// to enable/disable it, perhaps from an external POWER_CLOCK
|
||||
/// interrupt, or the equivalent when dealing with softdevices.
|
||||
pub fn new(underlying: UsbDevice<'d, D>, enable_usb_signal: &'d Signal<bool>) -> Self {
|
||||
Self {
|
||||
underlying,
|
||||
enable_usb_signal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the underlying `UsbDevice` taking care of reacting to USB becoming
|
||||
/// enabled/disabled.
|
||||
///
|
||||
/// This future may leave the bus in an invalid state if it is dropped.
|
||||
/// After dropping the future, [`UsbDevice::disable()`] should be called
|
||||
/// before calling any other `UsbDevice` methods to fully reset the
|
||||
/// peripheral.
|
||||
pub async fn run(&mut self) -> ! {
|
||||
while !self.enable_usb_signal.wait().await {}
|
||||
loop {
|
||||
match select(
|
||||
self.underlying.run_until_suspend(),
|
||||
self.enable_usb_signal.wait(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Either::First(_) => {}
|
||||
Either::Second(enable) => {
|
||||
if !enable {
|
||||
self.underlying.disable().await;
|
||||
while !self.enable_usb_signal.wait().await {}
|
||||
}
|
||||
}
|
||||
}
|
||||
match select(self.underlying.wait_resume(), self.enable_usb_signal.wait()).await {
|
||||
Either::First(_) => {}
|
||||
Either::Second(enable) => {
|
||||
if !enable {
|
||||
self.underlying.disable().await;
|
||||
while !self.enable_usb_signal.wait().await {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,47 +6,18 @@
|
||||
use core::mem;
|
||||
|
||||
use defmt::{info, panic};
|
||||
use embassy::channel::signal::Signal;
|
||||
use embassy::executor::Spawner;
|
||||
use embassy::interrupt::InterruptExt;
|
||||
use embassy_nrf::usb::{Driver, Instance};
|
||||
use embassy_nrf::{interrupt, interrupt, pac, pac, Peripherals};
|
||||
use embassy_nrf::{interrupt, pac, Peripherals};
|
||||
use embassy_usb::driver::EndpointError;
|
||||
use embassy_usb::util::EnabledUsbDevice;
|
||||
use embassy_usb::{Builder, Config};
|
||||
use embassy_usb_serial::{CdcAcmClass, State};
|
||||
use futures::future::join;
|
||||
use {defmt_rtt as _, panic_probe as _}; // global logger
|
||||
|
||||
static ENABLE_USB: Signal<bool> = Signal::new();
|
||||
|
||||
fn on_power_interrupt(_: *mut ()) {
|
||||
let regs = unsafe { &*pac::POWER::ptr() };
|
||||
|
||||
if regs.events_usbdetected.read().bits() != 0 {
|
||||
regs.events_usbdetected.reset();
|
||||
info!("Vbus detected, enabling USB...");
|
||||
ENABLE_USB.signal(true);
|
||||
}
|
||||
|
||||
if regs.events_usbremoved.read().bits() != 0 {
|
||||
regs.events_usbremoved.reset();
|
||||
info!("Vbus removed, disabling USB...");
|
||||
ENABLE_USB.signal(false);
|
||||
}
|
||||
}
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[embassy::main]
|
||||
async fn main(_spawner: Spawner, p: Peripherals) {
|
||||
let clock: pac::CLOCK = unsafe { mem::transmute(()) };
|
||||
let power: pac::POWER = unsafe { mem::transmute(()) };
|
||||
|
||||
let power_irq = interrupt::take!(POWER_CLOCK);
|
||||
power_irq.set_handler(on_power_interrupt);
|
||||
power_irq.unpend();
|
||||
power_irq.enable();
|
||||
|
||||
power.intenset.write(|w| w.usbdetected().set().usbremoved().set());
|
||||
|
||||
info!("Enabling ext hfosc...");
|
||||
clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
|
||||
@ -54,7 +25,8 @@ async fn main(_spawner: Spawner, p: Peripherals) {
|
||||
|
||||
// Create the driver, from the HAL.
|
||||
let irq = interrupt::take!(USBD);
|
||||
let driver = Driver::new(p.USBD, irq);
|
||||
let power_irq = interrupt::take!(POWER_CLOCK);
|
||||
let driver = Driver::with_power_management(p.USBD, irq, power_irq);
|
||||
|
||||
// Create embassy-usb Config
|
||||
let mut config = Config::new(0xc0de, 0xcafe);
|
||||
@ -94,7 +66,7 @@ async fn main(_spawner: Spawner, p: Peripherals) {
|
||||
let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
|
||||
|
||||
// Build the builder.
|
||||
let mut usb = EnabledUsbDevice::new(builder.build(), &ENABLE_USB);
|
||||
let mut usb = builder.build();
|
||||
|
||||
// Run the USB device.
|
||||
let usb_fut = usb.run();
|
||||
|
Loading…
Reference in New Issue
Block a user