Puts in the machinery to handle power detected/removed

This commit is contained in:
huntc 2022-06-16 16:08:58 +10:00
parent c46e9b6cfc
commit 4a8f117f25
6 changed files with 144 additions and 145 deletions

View File

@ -2,7 +2,7 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use core::mem::MaybeUninit; 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 core::task::Poll;
use cortex_m::peripheral::NVIC; 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_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8];
static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8];
static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0);
static POWER_AVAILABLE: AtomicBool = AtomicBool::new(false);
pub struct Driver<'d, T: Instance> { pub struct Driver<'d, T: Instance> {
phantom: PhantomData<&'d mut T>, phantom: PhantomData<&'d mut T>,
@ -39,6 +40,11 @@ impl<'d, T: Instance> Driver<'d, T> {
irq.unpend(); irq.unpend();
irq.enable(); 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 { Self {
phantom: PhantomData, phantom: PhantomData,
alloc_in: Allocator::new(), 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 ()) { fn on_interrupt(_: *mut ()) {
let regs = T::regs(); let regs = T::regs();
@ -76,7 +101,6 @@ impl<'d, T: Instance> Driver<'d, T> {
// doesn't cause an infinite irq loop. // doesn't cause an infinite irq loop.
if regs.events_usbevent.read().bits() != 0 { if regs.events_usbevent.read().bits() != 0 {
regs.events_usbevent.reset(); regs.events_usbevent.reset();
//regs.intenclr.write(|w| w.usbevent().clear());
BUS_WAKER.wake(); 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> { 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) { fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
( (
Bus { phantom: PhantomData }, Bus {
phantom: PhantomData,
power_available: false,
},
ControlPipe { ControlPipe {
_phantom: PhantomData, _phantom: PhantomData,
max_packet_size: control_max_packet_size, 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> { pub struct Bus<'d, T: Instance> {
phantom: PhantomData<&'d mut T>, phantom: PhantomData<&'d mut T>,
power_available: bool,
} }
impl<'d, T: Instance> driver::Bus for Bus<'d, T> { 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"); 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 Poll::Pending
}) })
} }

View File

@ -161,6 +161,9 @@ impl<'d, T: Instance> Driver<'d, T> {
dm.set_as_af(dm.af_num(), AFType::OutputPushPull); dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
} }
// Initialize the bus so that it signals that power is available
BUS_WAKER.wake();
Self { Self {
phantom: PhantomData, phantom: PhantomData,
alloc: [EndpointData { alloc: [EndpointData {
@ -406,6 +409,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
Bus { Bus {
phantom: PhantomData, phantom: PhantomData,
ep_types, ep_types,
inited: false,
}, },
ControlPipe { ControlPipe {
_phantom: PhantomData, _phantom: PhantomData,
@ -420,6 +424,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
pub struct Bus<'d, T: Instance> { pub struct Bus<'d, T: Instance> {
phantom: PhantomData<&'d mut T>, phantom: PhantomData<&'d mut T>,
ep_types: [EpType; EP_COUNT - 1], ep_types: [EpType; EP_COUNT - 1],
inited: bool,
} }
impl<'d, T: Instance> driver::Bus for Bus<'d, T> { 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> { fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> {
poll_fn(move |cx| unsafe { poll_fn(move |cx| unsafe {
BUS_WAKER.register(cx.waker()); 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 { let flags = IRQ_FLAGS.load(Ordering::Acquire);
IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel);
return Poll::Ready(Event::Resume);
}
if flags & IRQ_FLAG_RESET != 0 { if flags & IRQ_FLAG_RESUME != 0 {
IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel); IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel);
return Poll::Ready(Event::Resume);
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 { if flags & IRQ_FLAG_RESET != 0 {
w.wake() IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel);
}
for w in &EP_OUT_WAKERS { trace!("RESET REGS WRITINGINGING");
w.wake() 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 { Poll::Pending
IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel); } else {
return Poll::Ready(Event::Suspend); self.inited = true;
return Poll::Ready(Event::PowerDetected);
} }
Poll::Pending
}) })
} }

View File

@ -202,6 +202,12 @@ pub enum Event {
/// A USB resume request has been detected after being suspended or, in the case of self-powered /// 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. /// devices, the device has been connected to the USB bus.
Resume, 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)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]

View File

@ -11,7 +11,6 @@ pub mod descriptor;
mod descriptor_reader; mod descriptor_reader;
pub mod driver; pub mod driver;
pub mod types; pub mod types;
pub mod util;
use embassy::util::{select, Either}; use embassy::util::{select, Either};
use heapless::Vec; use heapless::Vec;
@ -115,6 +114,7 @@ struct Inner<'d, D: Driver<'d>> {
device_state: UsbDeviceState, device_state: UsbDeviceState,
suspended: bool, suspended: bool,
power_available: bool,
remote_wakeup_enabled: bool, remote_wakeup_enabled: bool,
self_powered: bool, self_powered: bool,
@ -157,6 +157,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
device_state: UsbDeviceState::Disabled, device_state: UsbDeviceState::Disabled,
suspended: false, suspended: false,
power_available: false,
remote_wakeup_enabled: false, remote_wakeup_enabled: false,
self_powered: false, self_powered: false,
address: 0, address: 0,
@ -186,6 +187,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
/// before calling any other `UsbDevice` methods to fully reset the /// before calling any other `UsbDevice` methods to fully reset the
/// peripheral. /// peripheral.
pub async fn run_until_suspend(&mut self) -> () { 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 { if self.inner.device_state == UsbDeviceState::Disabled {
self.inner.bus.enable().await; self.inner.bus.enable().await;
self.inner.device_state = UsbDeviceState::Default; 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 control_fut = self.control.setup();
let bus_fut = self.inner.bus.poll(); let bus_fut = self.inner.bus.poll();
match select(bus_fut, control_fut).await { match select(bus_fut, control_fut).await {
@ -223,7 +229,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
/// ///
/// This future is cancel-safe. /// This future is cancel-safe.
pub async fn wait_resume(&mut self) { 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; let evt = self.inner.bus.poll().await;
self.inner.handle_bus_event(evt); self.inner.handle_bus_event(evt);
} }
@ -377,6 +383,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
h.suspended(true); h.suspended(true);
} }
} }
Event::PowerDetected => {
trace!("usb: power detected");
self.power_available = true;
}
Event::PowerRemoved => {
trace!("usb: power removed");
self.power_available = false;
}
} }
} }

View File

@ -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 {}
}
}
}
}
}
}

View File

@ -6,47 +6,18 @@
use core::mem; use core::mem;
use defmt::{info, panic}; use defmt::{info, panic};
use embassy::channel::signal::Signal;
use embassy::executor::Spawner; use embassy::executor::Spawner;
use embassy::interrupt::InterruptExt;
use embassy_nrf::usb::{Driver, Instance}; 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::driver::EndpointError;
use embassy_usb::util::EnabledUsbDevice;
use embassy_usb::{Builder, Config}; use embassy_usb::{Builder, Config};
use embassy_usb_serial::{CdcAcmClass, State}; use embassy_usb_serial::{CdcAcmClass, State};
use futures::future::join; use futures::future::join;
use {defmt_rtt as _, panic_probe as _}; // global logger use {defmt_rtt as _, panic_probe as _};
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);
}
}
#[embassy::main] #[embassy::main]
async fn main(_spawner: Spawner, p: Peripherals) { async fn main(_spawner: Spawner, p: Peripherals) {
let clock: pac::CLOCK = unsafe { mem::transmute(()) }; 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..."); info!("Enabling ext hfosc...");
clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); 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. // Create the driver, from the HAL.
let irq = interrupt::take!(USBD); 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 // Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe); 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); let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
// Build the builder. // Build the builder.
let mut usb = EnabledUsbDevice::new(builder.build(), &ENABLE_USB); let mut usb = builder.build();
// Run the USB device. // Run the USB device.
let usb_fut = usb.run(); let usb_fut = usb.run();