diff --git a/ci.sh b/ci.sh
index fa24d5268adfc96ac50af05d8a228f5f81e6c9c0..cd1c0786cc87ecaee28d1076aa372821dc80b336 100755
--- a/ci.sh
+++ b/ci.sh
@@ -58,6 +58,7 @@ cargo batch  \
     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,log \
     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits \
     --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \
+    --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \
     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \
     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \
     --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits \
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 4a2b112a9554bd8aa41c41d13179f790d3b2785c..8286601ecb708e76bb0a302b4302341f9c6220c9 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -1,5 +1,4 @@
 #![feature(type_alias_impl_trait)]
-#![feature(generic_associated_types)]
 #![no_std]
 #![warn(missing_docs)]
 #![doc = include_str!("../../README.md")]
@@ -222,10 +221,7 @@ impl BootLoader {
         page: &mut [u8],
     ) -> Result<State, BootError> {
         // Ensure we have enough progress pages to store copy progress
-        assert_eq!(self.active.len() % page.len(), 0);
-        assert_eq!(self.dfu.len() % page.len(), 0);
-        assert!(self.dfu.len() - self.active.len() >= page.len());
-        assert!(self.active.len() / page.len() <= (self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE);
+        assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE);
         assert_eq!(magic.len(), P::STATE::WRITE_SIZE);
 
         // Copy contents from partition N to active
@@ -409,6 +405,13 @@ impl BootLoader {
     }
 }
 
+fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: usize, write_size: usize) {
+    assert_eq!(active.len() % page_size, 0);
+    assert_eq!(dfu.len() % page_size, 0);
+    assert!(dfu.len() - active.len() >= page_size);
+    assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size);
+}
+
 /// Convenience provider that uses a single flash for all partitions.
 pub struct SingleFlashConfig<'a, F>
 where
@@ -447,24 +450,24 @@ where
 }
 
 /// A flash wrapper implementing the Flash and embedded_storage traits.
-pub struct BootFlash<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF>
+pub struct BootFlash<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF>
 where
     F: NorFlash + ReadNorFlash,
 {
-    flash: &'a mut F,
+    flash: F,
 }
 
-impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
+impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
 where
     F: NorFlash + ReadNorFlash,
 {
     /// Create a new instance of a bootable flash
-    pub fn new(flash: &'a mut F) -> Self {
+    pub fn new(flash: F) -> Self {
         Self { flash }
     }
 }
 
-impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
+impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
 where
     F: NorFlash + ReadNorFlash,
 {
@@ -472,14 +475,14 @@ where
     const ERASE_VALUE: u8 = ERASE_VALUE;
 }
 
-impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
+impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
 where
     F: ReadNorFlash + NorFlash,
 {
     type Error = F::Error;
 }
 
-impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
+impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
 where
     F: ReadNorFlash + NorFlash,
 {
@@ -487,26 +490,26 @@ where
     const ERASE_SIZE: usize = F::ERASE_SIZE;
 
     fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
-        F::erase(self.flash, from, to)
+        F::erase(&mut self.flash, from, to)
     }
 
     fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
-        F::write(self.flash, offset, bytes)
+        F::write(&mut self.flash, offset, bytes)
     }
 }
 
-impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
+impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
 where
     F: ReadNorFlash + NorFlash,
 {
     const READ_SIZE: usize = F::READ_SIZE;
 
     fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
-        F::read(self.flash, offset, bytes)
+        F::read(&mut self.flash, offset, bytes)
     }
 
     fn capacity(&self) -> usize {
-        F::capacity(self.flash)
+        F::capacity(&self.flash)
     }
 }
 
@@ -601,6 +604,21 @@ impl FirmwareUpdater {
         self.dfu.len()
     }
 
+    /// Obtain the current state.
+    ///
+    /// This is useful to check if the bootloader has just done a swap, in order
+    /// to do verifications and self-tests of the new image before calling
+    /// `mark_booted`.
+    pub async fn get_state<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<State, F::Error> {
+        flash.read(self.state.from as u32, aligned).await?;
+
+        if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
+            Ok(State::Swap)
+        } else {
+            Ok(State::Boot)
+        }
+    }
+
     /// Mark to trigger firmware swap on next boot.
     ///
     /// # Safety
@@ -657,12 +675,6 @@ impl FirmwareUpdater {
     ) -> Result<(), F::Error> {
         assert!(data.len() >= F::ERASE_SIZE);
 
-        trace!(
-            "Writing firmware at offset 0x{:x} len {}",
-            self.dfu.from + offset,
-            data.len()
-        );
-
         flash
             .erase(
                 (self.dfu.from + offset) as u32,
@@ -676,7 +688,156 @@ impl FirmwareUpdater {
             self.dfu.from + offset + data.len()
         );
 
-        let mut write_offset = self.dfu.from + offset;
+        FirmwareWriter(self.dfu)
+            .write_block(offset, data, flash, block_size)
+            .await?;
+
+        Ok(())
+    }
+
+    /// Prepare for an incoming DFU update by erasing the entire DFU area and
+    /// returning a `FirmwareWriter`.
+    ///
+    /// Using this instead of `write_firmware` allows for an optimized API in
+    /// exchange for added complexity.
+    pub async fn prepare_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<FirmwareWriter, F::Error> {
+        flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32).await?;
+
+        trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
+
+        Ok(FirmwareWriter(self.dfu))
+    }
+
+    //
+    // Blocking API
+    //
+
+    /// Obtain the current state.
+    ///
+    /// This is useful to check if the bootloader has just done a swap, in order
+    /// to do verifications and self-tests of the new image before calling
+    /// `mark_booted`.
+    pub fn get_state_blocking<F: NorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<State, F::Error> {
+        flash.read(self.state.from as u32, aligned)?;
+
+        if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
+            Ok(State::Swap)
+        } else {
+            Ok(State::Boot)
+        }
+    }
+
+    /// Mark to trigger firmware swap on next boot.
+    ///
+    /// # Safety
+    ///
+    /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
+    pub fn mark_updated_blocking<F: NorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
+        assert_eq!(aligned.len(), F::WRITE_SIZE);
+        self.set_magic_blocking(aligned, SWAP_MAGIC, flash)
+    }
+
+    /// Mark firmware boot successful and stop rollback on reset.
+    ///
+    /// # Safety
+    ///
+    /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
+    pub fn mark_booted_blocking<F: NorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
+        assert_eq!(aligned.len(), F::WRITE_SIZE);
+        self.set_magic_blocking(aligned, BOOT_MAGIC, flash)
+    }
+
+    fn set_magic_blocking<F: NorFlash>(
+        &mut self,
+        aligned: &mut [u8],
+        magic: u8,
+        flash: &mut F,
+    ) -> Result<(), F::Error> {
+        flash.read(self.state.from as u32, aligned)?;
+
+        if aligned.iter().any(|&b| b != magic) {
+            aligned.fill(0);
+
+            flash.write(self.state.from as u32, aligned)?;
+            flash.erase(self.state.from as u32, self.state.to as u32)?;
+
+            aligned.fill(magic);
+            flash.write(self.state.from as u32, aligned)?;
+        }
+        Ok(())
+    }
+
+    /// Write data to a flash page.
+    ///
+    /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
+    ///
+    /// # Safety
+    ///
+    /// Failing to meet alignment and size requirements may result in a panic.
+    pub fn write_firmware_blocking<F: NorFlash>(
+        &mut self,
+        offset: usize,
+        data: &[u8],
+        flash: &mut F,
+        block_size: usize,
+    ) -> Result<(), F::Error> {
+        assert!(data.len() >= F::ERASE_SIZE);
+
+        flash.erase(
+            (self.dfu.from + offset) as u32,
+            (self.dfu.from + offset + data.len()) as u32,
+        )?;
+
+        trace!(
+            "Erased from {} to {}",
+            self.dfu.from + offset,
+            self.dfu.from + offset + data.len()
+        );
+
+        FirmwareWriter(self.dfu).write_block_blocking(offset, data, flash, block_size)?;
+
+        Ok(())
+    }
+
+    /// Prepare for an incoming DFU update by erasing the entire DFU area and
+    /// returning a `FirmwareWriter`.
+    ///
+    /// Using this instead of `write_firmware_blocking` allows for an optimized
+    /// API in exchange for added complexity.
+    pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<FirmwareWriter, F::Error> {
+        flash.erase((self.dfu.from) as u32, (self.dfu.to) as u32)?;
+
+        trace!("Erased from {} to {}", self.dfu.from, self.dfu.to);
+
+        Ok(FirmwareWriter(self.dfu))
+    }
+}
+
+/// FirmwareWriter allows writing blocks to an already erased flash.
+pub struct FirmwareWriter(Partition);
+
+impl FirmwareWriter {
+    /// Write data to a flash page.
+    ///
+    /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
+    ///
+    /// # Safety
+    ///
+    /// Failing to meet alignment and size requirements may result in a panic.
+    pub async fn write_block<F: AsyncNorFlash>(
+        &mut self,
+        offset: usize,
+        data: &[u8],
+        flash: &mut F,
+        block_size: usize,
+    ) -> Result<(), F::Error> {
+        trace!(
+            "Writing firmware at offset 0x{:x} len {}",
+            self.0.from + offset,
+            data.len()
+        );
+
+        let mut write_offset = self.0.from + offset;
         for chunk in data.chunks(block_size) {
             trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
             flash.write(write_offset as u32, chunk).await?;
@@ -699,6 +860,50 @@ impl FirmwareUpdater {
 
         Ok(())
     }
+
+    /// Write data to a flash page.
+    ///
+    /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
+    ///
+    /// # Safety
+    ///
+    /// Failing to meet alignment and size requirements may result in a panic.
+    pub fn write_block_blocking<F: NorFlash>(
+        &mut self,
+        offset: usize,
+        data: &[u8],
+        flash: &mut F,
+        block_size: usize,
+    ) -> Result<(), F::Error> {
+        trace!(
+            "Writing firmware at offset 0x{:x} len {}",
+            self.0.from + offset,
+            data.len()
+        );
+
+        let mut write_offset = self.0.from + offset;
+        for chunk in data.chunks(block_size) {
+            trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
+            flash.write(write_offset as u32, chunk)?;
+            write_offset += chunk.len();
+        }
+        /*
+        trace!("Wrote data, reading back for verification");
+
+        let mut buf: [u8; 4096] = [0; 4096];
+        let mut data_offset = 0;
+        let mut read_offset = self.dfu.from + offset;
+        for chunk in buf.chunks_mut(block_size) {
+            flash.read(read_offset as u32, chunk).await?;
+            trace!("Read chunk at {}: {:?}", read_offset, chunk);
+            assert_eq!(&data[data_offset..data_offset + block_size], chunk);
+            read_offset += chunk.len();
+            data_offset += chunk.len();
+        }
+        */
+
+        Ok(())
+    }
 }
 
 #[cfg(test)]
@@ -919,6 +1124,15 @@ mod tests {
         }
     }
 
+    #[test]
+    #[should_panic]
+    fn test_range_asserts() {
+        const ACTIVE: Partition = Partition::new(4096, 4194304);
+        const DFU: Partition = Partition::new(4194304, 2 * 4194304);
+        const STATE: Partition = Partition::new(0, 4096);
+        assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
+    }
+
     struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize>([u8; SIZE]);
 
     impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs
index 0c14781a265353416f70626dd23df3563cad0207..385e089fe0fae1649aee1eec512562686a536a9f 100644
--- a/embassy-boot/nrf/src/lib.rs
+++ b/embassy-boot/nrf/src/lib.rs
@@ -1,5 +1,4 @@
 #![no_std]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 #![warn(missing_docs)]
 #![doc = include_str!("../../README.md")]
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs
index 39f0805172179b13d6b3159b1fdce5bbae786d88..edba39ccace18096ce3a83f21728e886f0a5c0da 100644
--- a/embassy-boot/stm32/src/lib.rs
+++ b/embassy-boot/stm32/src/lib.rs
@@ -1,5 +1,4 @@
 #![no_std]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 #![warn(missing_docs)]
 #![doc = include_str!("../../README.md")]
diff --git a/embassy-embedded-hal/src/lib.rs b/embassy-embedded-hal/src/lib.rs
index 0c6f2786ac8d0d05687977d09d7181fa6578e3c3..a12a3a3a09b0f6a0b886079b4f213634a78dccb5 100644
--- a/embassy-embedded-hal/src/lib.rs
+++ b/embassy-embedded-hal/src/lib.rs
@@ -1,5 +1,5 @@
 #![cfg_attr(not(feature = "std"), no_std)]
-#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))]
+#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 #![warn(missing_docs)]
 
 //! Utilities to use `embedded-hal` traits with Embassy.
diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs
index 25a0d7dbb70017d501f4b6cfee5daaf7cc4ef5a1..400d973ff6d9e57f42a3746700aa59fb76bb28d7 100644
--- a/embassy-executor/src/spawner.rs
+++ b/embassy-executor/src/spawner.rs
@@ -1,10 +1,9 @@
+use core::future::poll_fn;
 use core::marker::PhantomData;
 use core::mem;
 use core::ptr::NonNull;
 use core::task::Poll;
 
-use futures_util::future::poll_fn;
-
 use super::raw;
 
 /// Token to spawn a newly-created task in an executor.
diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs
index 2483dcb2ee0acbe34cabd1c27c5ef603e3a4d7dc..90ba0d1d453dfd764c7d86f9e109f9e3ed2e231c 100644
--- a/embassy-lora/src/lib.rs
+++ b/embassy-lora/src/lib.rs
@@ -1,6 +1,5 @@
 #![no_std]
 #![feature(type_alias_impl_trait)]
-#![feature(generic_associated_types)]
 //! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
 //! crate's async LoRaWAN MAC implementation.
 
diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs
index 4d11244b632874147b9e6207eaaf972c39aad92c..e28fa2c1ab37414dfb4cdf410a8b4cef6b72bc93 100644
--- a/embassy-lora/src/stm32wl/mod.rs
+++ b/embassy-lora/src/stm32wl/mod.rs
@@ -1,5 +1,5 @@
 //! A radio driver integration for the radio found on STM32WL family devices.
-use core::future::Future;
+use core::future::{poll_fn, Future};
 use core::task::Poll;
 
 use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
@@ -11,7 +11,6 @@ use embassy_stm32::subghz::{
     Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
 };
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
 use lorawan_device::async_device::Timings;
 
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs
index 8eebc798ebf8ac2e1c0334ab172abeb05f21c2af..4d30550d3d60cc5e08ce5b147eb861860f7c5610 100644
--- a/embassy-net/src/lib.rs
+++ b/embassy-net/src/lib.rs
@@ -1,5 +1,5 @@
 #![cfg_attr(not(feature = "std"), no_std)]
-#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))]
+#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 
 // This mod MUST go first, so that the others see its macros.
 pub(crate) mod fmt;
diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs
index 8d2dd4bca32fa8479d82f23ce34c94f9c2a76f9f..3a761075810d590b055bb1adcce274de3581267f 100644
--- a/embassy-net/src/stack.rs
+++ b/embassy-net/src/stack.rs
@@ -1,10 +1,9 @@
 use core::cell::UnsafeCell;
-use core::future::Future;
+use core::future::{poll_fn, Future};
 use core::task::{Context, Poll};
 
 use embassy_sync::waitqueue::WakerRegistration;
 use embassy_time::{Instant, Timer};
-use futures::future::poll_fn;
 use futures::pin_mut;
 use heapless::Vec;
 #[cfg(feature = "dhcpv4")]
diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs
index 0fa87360259bf0c2d4ccdbf3c81d08bd0e90759f..f8fff3e2dc268494688e5c9a588a912a8155dc83 100644
--- a/embassy-net/src/tcp.rs
+++ b/embassy-net/src/tcp.rs
@@ -1,8 +1,8 @@
 use core::cell::UnsafeCell;
+use core::future::poll_fn;
 use core::mem;
 use core::task::Poll;
 
-use futures::future::poll_fn;
 use smoltcp::iface::{Interface, SocketHandle};
 use smoltcp::socket::tcp;
 use smoltcp::time::Duration;
@@ -103,7 +103,7 @@ impl<'a> TcpSocket<'a> {
             Err(tcp::ConnectError::Unaddressable) => return Err(ConnectError::NoRoute),
         }
 
-        futures::future::poll_fn(|cx| unsafe {
+        poll_fn(|cx| unsafe {
             self.io.with_mut(|s, _| match s.state() {
                 tcp::State::Closed | tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)),
                 tcp::State::Listen => unreachable!(),
@@ -128,7 +128,7 @@ impl<'a> TcpSocket<'a> {
             Err(tcp::ListenError::Unaddressable) => return Err(AcceptError::InvalidPort),
         }
 
-        futures::future::poll_fn(|cx| unsafe {
+        poll_fn(|cx| unsafe {
             self.io.with_mut(|s, _| match s.state() {
                 tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => {
                     s.register_send_waker(cx.waker());
diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs
index 78b09a492093177d081caccbb6b9cd92e86615e9..f2e33493cda53bbd6cfb1432f88d9495eaa0bb53 100644
--- a/embassy-net/src/udp.rs
+++ b/embassy-net/src/udp.rs
@@ -1,8 +1,8 @@
 use core::cell::UnsafeCell;
+use core::future::poll_fn;
 use core::mem;
 use core::task::Poll;
 
-use futures::future::poll_fn;
 use smoltcp::iface::{Interface, SocketHandle};
 use smoltcp::socket::udp::{self, PacketMetadata};
 use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs
index c3cba247059993cd0420968366de69df8b66b4b8..6e85a159fa9313b0c5cade36de76cde2e3b0307b 100644
--- a/embassy-nrf/src/buffered_uarte.rs
+++ b/embassy-nrf/src/buffered_uarte.rs
@@ -15,7 +15,7 @@
 
 use core::cell::RefCell;
 use core::cmp::min;
-use core::future::Future;
+use core::future::{poll_fn, Future};
 use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::Poll;
 
@@ -23,11 +23,10 @@ use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorag
 use embassy_hal_common::ring_buffer::RingBuffer;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::WakerRegistration;
-use futures::future::poll_fn;
 // Re-export SVD variants to allow user to directly set values
 pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
 
-use crate::gpio::Pin as GpioPin;
+use crate::gpio::{self, Pin as GpioPin};
 use crate::interrupt::InterruptExt;
 use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task};
 use crate::timer::{Frequency, Instance as TimerInstance, Timer};
@@ -429,21 +428,23 @@ impl<'a, U: UarteInstance, T: TimerInstance> Drop for StateInner<'a, U, T> {
     fn drop(&mut self) {
         let r = U::regs();
 
-        // TODO this probably deadlocks. do like Uarte instead.
-
         self.timer.stop();
-        if let RxState::Receiving = self.rx_state {
-            r.tasks_stoprx.write(|w| unsafe { w.bits(1) });
-        }
-        if let TxState::Transmitting(_) = self.tx_state {
-            r.tasks_stoptx.write(|w| unsafe { w.bits(1) });
-        }
-        if let RxState::Receiving = self.rx_state {
-            low_power_wait_until(|| r.events_endrx.read().bits() == 1);
-        }
-        if let TxState::Transmitting(_) = self.tx_state {
-            low_power_wait_until(|| r.events_endtx.read().bits() == 1);
-        }
+
+        r.inten.reset();
+        r.events_rxto.reset();
+        r.tasks_stoprx.write(|w| unsafe { w.bits(1) });
+        r.events_txstopped.reset();
+        r.tasks_stoptx.write(|w| unsafe { w.bits(1) });
+
+        while r.events_txstopped.read().bits() == 0 {}
+        while r.events_rxto.read().bits() == 0 {}
+
+        r.enable.write(|w| w.enable().disabled());
+
+        gpio::deconfigure_pin(r.psel.rxd.read().bits());
+        gpio::deconfigure_pin(r.psel.txd.read().bits());
+        gpio::deconfigure_pin(r.psel.rts.read().bits());
+        gpio::deconfigure_pin(r.psel.cts.read().bits());
     }
 }
 
@@ -549,13 +550,3 @@ impl<'a, U: UarteInstance, T: TimerInstance> PeripheralState for StateInner<'a,
         trace!("irq: end");
     }
 }
-
-/// Low power blocking wait loop using WFE/SEV.
-fn low_power_wait_until(mut condition: impl FnMut() -> bool) {
-    while !condition() {
-        // WFE might "eat" an event that would have otherwise woken the executor.
-        cortex_m::asm::wfe();
-    }
-    // Retrigger an event to be transparent to the executor.
-    cortex_m::asm::sev();
-}
diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs
index d99f592b0ef9a9973cbfd00a6dc69dd47a0913ef..b418be9d5a491d0fe85438ce847c0bfd8ec98b76 100644
--- a/embassy-nrf/src/gpiote.rs
+++ b/embassy-nrf/src/gpiote.rs
@@ -1,10 +1,9 @@
 use core::convert::Infallible;
-use core::future::Future;
+use core::future::{poll_fn, Future};
 use core::task::{Context, Poll};
 
 use embassy_hal_common::{impl_peripheral, Peripheral, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 
 use crate::gpio::sealed::Pin as _;
 use crate::gpio::{AnyPin, Flex, Input, Output, Pin as GpioPin};
diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs
index f3b3ca0ca9b7a5f04246d0b5691f2f7f9baebcc9..d7bd21702fd7e7df979bc43d22d104651a3bb526 100644
--- a/embassy-nrf/src/lib.rs
+++ b/embassy-nrf/src/lib.rs
@@ -43,7 +43,7 @@
 //! mutable slices always reside in RAM.
 
 #![no_std]
-#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))]
+#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 
 #[cfg(not(any(
     feature = "nrf51",
diff --git a/embassy-nrf/src/qdec.rs b/embassy-nrf/src/qdec.rs
index 762e0971544eb92020da13021ce117d07769c576..253c85c32454578dfecfd7a1e05014608e58bc4c 100644
--- a/embassy-nrf/src/qdec.rs
+++ b/embassy-nrf/src/qdec.rs
@@ -1,10 +1,10 @@
 //! Quadrature decoder interface
 
+use core::future::poll_fn;
 use core::task::Poll;
 
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 
 use crate::gpio::sealed::Pin as _;
 use crate::gpio::{AnyPin, Pin as GpioPin};
diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs
index c97cb1656b1219a9e1f10742205221b4dd2e37ca..ea0a17031d03190e06438bf859bac54e7f7aee66 100644
--- a/embassy-nrf/src/qspi.rs
+++ b/embassy-nrf/src/qspi.rs
@@ -1,11 +1,11 @@
 #![macro_use]
 
+use core::future::poll_fn;
 use core::ptr;
 use core::task::Poll;
 
 use embassy_hal_common::drop::DropBomb;
 use embassy_hal_common::{into_ref, PeripheralRef};
-use futures::future::poll_fn;
 
 use crate::gpio::{self, Pin as GpioPin};
 use crate::interrupt::{Interrupt, InterruptExt};
diff --git a/embassy-nrf/src/rng.rs b/embassy-nrf/src/rng.rs
index 42da51d0f14059d45dfa603893049f25e3b483a1..e0caeaaee65addca2bbf77f7cc02914f6d036576 100644
--- a/embassy-nrf/src/rng.rs
+++ b/embassy-nrf/src/rng.rs
@@ -1,3 +1,4 @@
+use core::future::poll_fn;
 use core::ptr;
 use core::sync::atomic::{AtomicPtr, Ordering};
 use core::task::Poll;
@@ -5,7 +6,6 @@ use core::task::Poll;
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 
 use crate::interrupt::InterruptExt;
 use crate::peripherals::RNG;
diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs
index 9bc89eb38e6084eaa0ba37cb9e92cd0964a9d443..d1c82423e2555a471533216711869a5ebe718482 100644
--- a/embassy-nrf/src/saadc.rs
+++ b/embassy-nrf/src/saadc.rs
@@ -1,12 +1,12 @@
 #![macro_use]
 
+use core::future::poll_fn;
 use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::Poll;
 
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 use pac::{saadc, SAADC};
 use saadc::ch::config::{GAIN_A, REFSEL_A, RESP_A, TACQ_A};
 // We treat the positive and negative channels with the same enum values to keep our type tidy and given they are the same
diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs
index 2955182e49028c84f654b80fe81895da6063c4a8..51cd73a47e023794c119bdf689a034e0ae6332c7 100644
--- a/embassy-nrf/src/spim.rs
+++ b/embassy-nrf/src/spim.rs
@@ -1,12 +1,12 @@
 #![macro_use]
 
+use core::future::poll_fn;
 use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::Poll;
 
 use embassy_embedded_hal::SetConfig;
 use embassy_hal_common::{into_ref, PeripheralRef};
 pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
-use futures::future::poll_fn;
 pub use pac::spim0::frequency::FREQUENCY_A as Frequency;
 
 use crate::chip::FORCE_COPY_BUFFER_SIZE;
diff --git a/embassy-nrf/src/temp.rs b/embassy-nrf/src/temp.rs
index d520fd686f83e1e85b621b5eb698701bdc765e49..7a7f61b51f0ee037f60aa8885b2d54d88e3d4097 100644
--- a/embassy-nrf/src/temp.rs
+++ b/embassy-nrf/src/temp.rs
@@ -1,12 +1,12 @@
 //! Temperature sensor interface.
 
+use core::future::poll_fn;
 use core::task::Poll;
 
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
 use fixed::types::I30F2;
-use futures::future::poll_fn;
 
 use crate::interrupt::InterruptExt;
 use crate::peripherals::TEMP;
diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs
index 3de5a89622d7682fcbec866b44341a1cf374f272..bc87106406778e84936cf7a81922aecb0dd3a533 100644
--- a/embassy-nrf/src/timer.rs
+++ b/embassy-nrf/src/timer.rs
@@ -1,12 +1,12 @@
 #![macro_use]
 
+use core::future::poll_fn;
 use core::marker::PhantomData;
 use core::task::Poll;
 
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 
 use crate::interrupt::{Interrupt, InterruptExt};
 use crate::ppi::{Event, Task};
diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs
index 3d4af753a0b0a9ab5b87faa1e1910bcda8fc95ac..7c6ca1d30efec85d8d70ae8c2386725ea3a3fbbe 100644
--- a/embassy-nrf/src/twim.rs
+++ b/embassy-nrf/src/twim.rs
@@ -6,7 +6,7 @@
 //!
 //! - nRF52832: Section 33
 //! - nRF52840: Section 6.31
-use core::future::Future;
+use core::future::{poll_fn, Future};
 use core::sync::atomic::compiler_fence;
 use core::sync::atomic::Ordering::SeqCst;
 use core::task::Poll;
@@ -16,7 +16,6 @@ use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
 #[cfg(feature = "time")]
 use embassy_time::{Duration, Instant};
-use futures::future::poll_fn;
 
 use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
 use crate::gpio::Pin as GpioPin;
diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs
index c250e24ca1d8256c07329b60b87e18466e83df9f..5f9c4f17d32814f97e1b21459d7d07ae7a79b91e 100644
--- a/embassy-nrf/src/uarte.rs
+++ b/embassy-nrf/src/uarte.rs
@@ -13,12 +13,12 @@
 //! memory may be used given that buffers are passed in directly to its read and write
 //! methods.
 
+use core::future::poll_fn;
 use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::Poll;
 
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
-use futures::future::poll_fn;
 use pac::uarte0::RegisterBlock;
 // Re-export SVD variants to allow user to directly set values.
 pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs
index 688326e9c9e58b100c8ff7428cfe69b1567377ec..20510eb493c9cf4bc805a7e53a7a26b350ba89e9 100644
--- a/embassy-nrf/src/usb.rs
+++ b/embassy-nrf/src/usb.rs
@@ -1,5 +1,6 @@
 #![macro_use]
 
+use core::future::{poll_fn, Future};
 use core::marker::PhantomData;
 use core::mem::MaybeUninit;
 use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
@@ -9,10 +10,9 @@ use cortex_m::peripheral::NVIC;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
 pub use embassy_usb;
-use embassy_usb::driver::{self, EndpointError, Event, Unsupported};
-use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection};
-use futures::future::poll_fn;
-use futures::Future;
+use embassy_usb::driver::{
+    self, Direction, EndpointAddress, EndpointError, EndpointInfo, EndpointType, Event, Unsupported,
+};
 use pac::usbd::RegisterBlock;
 
 use crate::interrupt::{Interrupt, InterruptExt};
@@ -244,7 +244,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
         interval: u8,
     ) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
         let index = self.alloc_in.allocate(ep_type)?;
-        let ep_addr = EndpointAddress::from_parts(index, UsbDirection::In);
+        let ep_addr = EndpointAddress::from_parts(index, Direction::In);
         Ok(Endpoint::new(EndpointInfo {
             addr: ep_addr,
             ep_type,
@@ -260,7 +260,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
         interval: u8,
     ) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
         let index = self.alloc_out.allocate(ep_type)?;
-        let ep_addr = EndpointAddress::from_parts(index, UsbDirection::Out);
+        let ep_addr = EndpointAddress::from_parts(index, Direction::Out);
         Ok(Endpoint::new(EndpointInfo {
             addr: ep_addr,
             ep_type,
@@ -429,8 +429,8 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> {
         let regs = T::regs();
         let i = ep_addr.index();
         match ep_addr.direction() {
-            UsbDirection::Out => regs.halted.epout[i].read().getstatus().is_halted(),
-            UsbDirection::In => regs.halted.epin[i].read().getstatus().is_halted(),
+            Direction::Out => regs.halted.epout[i].read().getstatus().is_halted(),
+            Direction::In => regs.halted.epin[i].read().getstatus().is_halted(),
         }
     }
 
@@ -443,7 +443,7 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> {
         debug!("endpoint_set_enabled {:?} {}", ep_addr, enabled);
 
         match ep_addr.direction() {
-            UsbDirection::In => {
+            Direction::In => {
                 let mut was_enabled = false;
                 regs.epinen.modify(|r, w| {
                     let mut bits = r.bits();
@@ -467,7 +467,7 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> {
 
                 In::waker(i).wake();
             }
-            UsbDirection::Out => {
+            Direction::Out => {
                 regs.epouten.modify(|r, w| {
                     let mut bits = r.bits();
                     if enabled {
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml
index 3debca710cf2106f278e0749d9f227b78f525378..d0cf8025cafee97548597c0ce7ff607fd96a002c 100644
--- a/embassy-rp/Cargo.toml
+++ b/embassy-rp/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
 [package.metadata.embassy_docs]
 src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/embassy-rp/src/"
 src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/"
-features = ["nightly", "defmt", "unstable-pac", "unstable-traits"]
+features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver"]
 flavors = [
     { name = "rp2040", target = "thumbv6m-none-eabi" },
 ]
@@ -20,6 +20,12 @@ defmt = ["dep:defmt", "embassy-usb?/defmt"]
 # There are no plans to make this stable.
 unstable-pac = []
 
+time-driver = []
+
+rom-func-cache = []
+intrinsics = []
+rom-v2-intrinsics = []
+
 # Enable nightly-only features
 nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly", "dep:embassy-usb", "dep:embedded-io"]
 
@@ -31,6 +37,7 @@ unstable-traits = ["embedded-hal-1"]
 embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
 embassy-executor = { version = "0.1.0", path = "../embassy-executor" }
 embassy-time = { version = "0.1.0", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] }
+embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
 embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-2"]}
 embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" }
 embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
@@ -44,6 +51,7 @@ cortex-m-rt = ">=0.6.15,<0.8"
 cortex-m = "0.7.6"
 critical-section = "1.1"
 futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
+chrono = { version = "0.4", default-features = false, optional = true }
 embedded-io = { version = "0.3.0", features = ["async"], optional = true }
 
 rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] }
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index 3ad1e5d82922a724b96d4b494d29c720d77bab5c..1c446f3890c9c68ffe6fd17990a4a17dd07c4a8f 100644
--- a/embassy-rp/src/clocks.rs
+++ b/embassy-rp/src/clocks.rs
@@ -122,7 +122,7 @@ pub(crate) fn clk_peri_freq() -> u32 {
     125_000_000
 }
 
-pub(crate) fn _clk_rtc_freq() -> u32 {
+pub(crate) fn clk_rtc_freq() -> u32 {
     46875
 }
 
diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs
index acf33822584366cc1a365a2489e6e1b2fbf92123..410c4866616f3a853d08b25feb733ada62b4554d 100644
--- a/embassy-rp/src/dma.rs
+++ b/embassy-rp/src/dma.rs
@@ -1,3 +1,4 @@
+use core::future::Future;
 use core::pin::Pin;
 use core::sync::atomic::{compiler_fence, Ordering};
 use core::task::{Context, Poll};
@@ -5,7 +6,6 @@ use core::task::{Context, Poll};
 use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
 use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::Future;
 use pac::dma::vals::DataSize;
 
 use crate::pac::dma::vals;
@@ -75,6 +75,25 @@ pub unsafe fn write<'a, C: Channel, W: Word>(
     )
 }
 
+pub unsafe fn write_repeated<'a, C: Channel, W: Word>(
+    ch: impl Peripheral<P = C> + 'a,
+    to: *mut W,
+    len: usize,
+    dreq: u8,
+) -> Transfer<'a, C> {
+    let dummy: u32 = 0;
+    copy_inner(
+        ch,
+        &dummy as *const u32,
+        to as *mut u32,
+        len,
+        W::size(),
+        false,
+        false,
+        dreq,
+    )
+}
+
 pub unsafe fn copy<'a, C: Channel, W: Word>(
     ch: impl Peripheral<P = C> + 'a,
     from: &[W],
diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs
index a0328302a07df50299f1c2b8880165f537fe217c..9b9a08110687ae39855c303ac69ead73cd8e4949 100644
--- a/embassy-rp/src/gpio.rs
+++ b/embassy-rp/src/gpio.rs
@@ -159,7 +159,7 @@ unsafe fn IO_IRQ_BANK0() {
                         w.set_edge_low(pin_group, false);
                     }
                     InterruptTrigger::LevelHigh => {
-                        debug!("IO_IRQ_BANK0 pin {} LevelHigh triggered\n", pin);
+                        debug!("IO_IRQ_BANK0 pin {} LevelHigh triggered", pin);
                         w.set_level_high(pin_group, false);
                     }
                     InterruptTrigger::LevelLow => {
@@ -198,7 +198,7 @@ impl<'d, T: Pin> InputFuture<'d, T> {
             critical_section::with(|_| {
                 pin.int_proc().inte((pin.pin() / 8) as usize).modify(|w| match level {
                     InterruptTrigger::LevelHigh => {
-                        debug!("InputFuture::new enable LevelHigh for pin {} \n", pin.pin());
+                        debug!("InputFuture::new enable LevelHigh for pin {}", pin.pin());
                         w.set_level_high(pin_group, true);
                     }
                     InterruptTrigger::LevelLow => {
@@ -245,45 +245,45 @@ impl<'d, T: Pin> Future for InputFuture<'d, T> {
         // the pin and if it has been disabled that means it was done by the
         // interrupt service routine, so we then know that the event/trigger
         // happened and Poll::Ready will be returned.
-        debug!("{:?} for pin {}\n", self.level, self.pin.pin());
+        debug!("{:?} for pin {}", self.level, self.pin.pin());
         match self.level {
             InterruptTrigger::AnyEdge => {
                 if !inte.edge_high(pin_group) && !inte.edge_low(pin_group) {
                     #[rustfmt::skip]
-                    debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin());
+                    debug!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
                     return Poll::Ready(());
                 }
             }
             InterruptTrigger::LevelHigh => {
                 if !inte.level_high(pin_group) {
                     #[rustfmt::skip]
-                    debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin());
+                    debug!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
                     return Poll::Ready(());
                 }
             }
             InterruptTrigger::LevelLow => {
                 if !inte.level_low(pin_group) {
                     #[rustfmt::skip]
-                    debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin());
+                    debug!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
                     return Poll::Ready(());
                 }
             }
             InterruptTrigger::EdgeHigh => {
                 if !inte.edge_high(pin_group) {
                     #[rustfmt::skip]
-                    debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin());
+                    debug!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
                     return Poll::Ready(());
                 }
             }
             InterruptTrigger::EdgeLow => {
                 if !inte.edge_low(pin_group) {
                     #[rustfmt::skip]
-                    debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin());
+                    debug!("{:?} for pin {} was cleared, return Poll::Ready", self.level, self.pin.pin());
                     return Poll::Ready(());
                 }
             }
         }
-        debug!("InputFuture::poll return Poll::Pending\n");
+        debug!("InputFuture::poll return Poll::Pending");
         Poll::Pending
     }
 }
diff --git a/embassy-rp/src/intrinsics.rs b/embassy-rp/src/intrinsics.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3e75fb7fc73931a40625a7259a22d3da15e1e975
--- /dev/null
+++ b/embassy-rp/src/intrinsics.rs
@@ -0,0 +1,276 @@
+#![macro_use]
+
+// Credit: taken from `rp-hal` (also licensed Apache+MIT)
+// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/intrinsics.rs
+
+/// Generate a series of aliases for an intrinsic function.
+macro_rules! intrinsics_aliases {
+    (
+        extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
+    ) => {};
+    (
+        unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
+    ) => {};
+
+    (
+        extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
+        $alias:ident
+        $($rest:ident)*
+    ) => {
+        #[cfg(all(target_arch = "arm", feature = "intrinsics"))]
+        intrinsics! {
+            extern $abi fn $alias( $($argname: $ty),* ) -> $ret {
+                $name($($argname),*)
+            }
+        }
+
+        intrinsics_aliases! {
+            extern $abi fn $name( $($argname: $ty),* ) -> $ret,
+            $($rest)*
+        }
+    };
+
+    (
+        unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty,
+        $alias:ident
+        $($rest:ident)*
+    ) => {
+        #[cfg(all(target_arch = "arm", feature = "intrinsics"))]
+        intrinsics! {
+            unsafe extern $abi fn $alias( $($argname: $ty),* ) -> $ret {
+                $name($($argname),*)
+            }
+        }
+
+        intrinsics_aliases! {
+            unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret,
+            $($rest)*
+        }
+    };
+}
+
+/// The macro used to define overridden intrinsics.
+///
+/// This is heavily inspired by the macro used by compiler-builtins.  The idea
+/// is to abstract anything special that needs to be done to override an
+/// intrinsic function.  Intrinsic generation is disabled for non-ARM targets
+/// so things like CI and docs generation do not have problems.  Additionally
+/// they can be disabled by disabling the crate feature `intrinsics` for
+/// testing or comparing performance.
+///
+/// Like the compiler-builtins macro, it accepts a series of functions that
+/// looks like normal Rust code:
+///
+///     intrinsics! {
+///         extern "C" fn foo(a: i32) -> u32 {
+///             // ...
+///         }
+///
+///         #[nonstandard_attribute]
+///         extern "C" fn bar(a: i32) -> u32 {
+///             // ...
+///         }
+///     }
+///
+/// Each function can also be decorated with nonstandard attributes to control
+/// additional behaviour:
+///
+/// * `slower_than_default` - indicates that the override is slower than the
+///   default implementation.  Currently this just disables the override
+///   entirely.
+/// * `bootrom_v2` - indicates that the override is only available
+///   on a V2 bootrom or higher.  Only enabled when the feature
+///   `rom-v2-intrinsics` is set.
+/// * `alias` - accepts a list of names to alias the intrinsic to.
+/// * `aeabi` - accepts a list of ARM EABI names to alias to.
+///
+macro_rules! intrinsics {
+    () => {};
+
+    (
+        #[slower_than_default]
+        $(#[$($attr:tt)*])*
+        extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
+            $($body:tt)*
+        }
+
+        $($rest:tt)*
+    ) => {
+        // Not exported, but defined so the actual implementation is
+        // considered used
+        #[allow(dead_code)]
+        fn $name( $($argname: $ty),* ) -> $ret {
+            $($body)*
+        }
+
+        intrinsics!($($rest)*);
+    };
+
+    (
+        #[bootrom_v2]
+        $(#[$($attr:tt)*])*
+        extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
+            $($body:tt)*
+        }
+
+        $($rest:tt)*
+    ) => {
+        // Not exported, but defined so the actual implementation is
+        // considered used
+        #[cfg(not(feature = "rom-v2-intrinsics"))]
+        #[allow(dead_code)]
+        fn $name( $($argname: $ty),* ) -> $ret {
+            $($body)*
+        }
+
+        #[cfg(feature = "rom-v2-intrinsics")]
+        intrinsics! {
+            $(#[$($attr)*])*
+            extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+                $($body)*
+            }
+        }
+
+        intrinsics!($($rest)*);
+    };
+
+    (
+        #[alias = $($alias:ident),*]
+        $(#[$($attr:tt)*])*
+        extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
+            $($body:tt)*
+        }
+
+        $($rest:tt)*
+    ) => {
+        intrinsics! {
+            $(#[$($attr)*])*
+            extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+                $($body)*
+            }
+        }
+
+        intrinsics_aliases! {
+            extern $abi fn $name( $($argname: $ty),* ) -> $ret,
+            $($alias) *
+        }
+
+        intrinsics!($($rest)*);
+    };
+
+    (
+        #[alias = $($alias:ident),*]
+        $(#[$($attr:tt)*])*
+        unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
+            $($body:tt)*
+        }
+
+        $($rest:tt)*
+    ) => {
+        intrinsics! {
+            $(#[$($attr)*])*
+            unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+                $($body)*
+            }
+        }
+
+        intrinsics_aliases! {
+            unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret,
+            $($alias) *
+        }
+
+        intrinsics!($($rest)*);
+    };
+
+    (
+        #[aeabi = $($alias:ident),*]
+        $(#[$($attr:tt)*])*
+        extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
+            $($body:tt)*
+        }
+
+        $($rest:tt)*
+    ) => {
+        intrinsics! {
+            $(#[$($attr)*])*
+            extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+                $($body)*
+            }
+        }
+
+        intrinsics_aliases! {
+            extern "aapcs" fn $name( $($argname: $ty),* ) -> $ret,
+            $($alias) *
+        }
+
+        intrinsics!($($rest)*);
+    };
+
+    (
+        $(#[$($attr:tt)*])*
+        extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
+            $($body:tt)*
+        }
+
+        $($rest:tt)*
+    ) => {
+        #[cfg(all(target_arch = "arm", feature = "intrinsics"))]
+        $(#[$($attr)*])*
+        extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+            $($body)*
+        }
+
+        #[cfg(all(target_arch = "arm", feature = "intrinsics"))]
+        mod $name {
+            #[no_mangle]
+            $(#[$($attr)*])*
+            pub extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+                super::$name($($argname),*)
+            }
+        }
+
+        // Not exported, but defined so the actual implementation is
+        // considered used
+        #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))]
+        #[allow(dead_code)]
+        fn $name( $($argname: $ty),* ) -> $ret {
+            $($body)*
+        }
+
+        intrinsics!($($rest)*);
+    };
+
+    (
+        $(#[$($attr:tt)*])*
+        unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty {
+            $($body:tt)*
+        }
+
+        $($rest:tt)*
+    ) => {
+        #[cfg(all(target_arch = "arm", feature = "intrinsics"))]
+        $(#[$($attr)*])*
+        unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+            $($body)*
+        }
+
+        #[cfg(all(target_arch = "arm", feature = "intrinsics"))]
+        mod $name {
+            #[no_mangle]
+            $(#[$($attr)*])*
+            pub unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret {
+                super::$name($($argname),*)
+            }
+        }
+
+        // Not exported, but defined so the actual implementation is
+        // considered used
+        #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))]
+        #[allow(dead_code)]
+        unsafe fn $name( $($argname: $ty),* ) -> $ret {
+            $($body)*
+        }
+
+        intrinsics!($($rest)*);
+    };
+}
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs
index aebbbf56763a75fb55db67615c4c0892a7e2da95..9ac98d2268c55eb069d0ccc19fcdb7f233dc75ad 100644
--- a/embassy-rp/src/lib.rs
+++ b/embassy-rp/src/lib.rs
@@ -1,13 +1,18 @@
 #![no_std]
-#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))]
+#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 
 // This mod MUST go first, so that the others see its macros.
 pub(crate) mod fmt;
 
+mod intrinsics;
+
 pub mod dma;
 pub mod gpio;
 pub mod interrupt;
+pub mod rom_data;
+pub mod rtc;
 pub mod spi;
+#[cfg(feature = "time-driver")]
 pub mod timer;
 pub mod uart;
 #[cfg(feature = "nightly")]
@@ -84,6 +89,8 @@ embassy_hal_common::peripherals! {
     DMA_CH11,
 
     USB,
+
+    RTC,
 }
 
 #[link_section = ".boot2"]
@@ -108,6 +115,7 @@ pub fn init(_config: config::Config) -> Peripherals {
 
     unsafe {
         clocks::init();
+        #[cfg(feature = "time-driver")]
         timer::init();
         dma::init();
     }
diff --git a/embassy-rp/src/rom_data.rs b/embassy-rp/src/rom_data.rs
new file mode 100644
index 0000000000000000000000000000000000000000..757a2711401b007ec7ec686115a93259302b897c
--- /dev/null
+++ b/embassy-rp/src/rom_data.rs
@@ -0,0 +1,733 @@
+//! Functions and data from the RPI Bootrom.
+//!
+//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1:
+//!
+//! > The Bootrom contains a number of public functions that provide useful
+//! > RP2040 functionality that might be needed in the absence of any other code
+//! > on the device, as well as highly optimized versions of certain key
+//! > functionality that would otherwise have to take up space in most user
+//! > binaries.
+
+// Credit: taken from `rp-hal` (also licensed Apache+MIT)
+// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/rom_data.rs
+
+/// A bootrom function table code.
+pub type RomFnTableCode = [u8; 2];
+
+/// This function searches for (table)
+type RomTableLookupFn<T> = unsafe extern "C" fn(*const u16, u32) -> T;
+
+/// The following addresses are described at `2.8.2. Bootrom Contents`
+/// Pointer to the lookup table function supplied by the rom.
+const ROM_TABLE_LOOKUP_PTR: *const u16 = 0x0000_0018 as _;
+
+/// Pointer to helper functions lookup table.
+const FUNC_TABLE: *const u16 = 0x0000_0014 as _;
+
+/// Pointer to the public data lookup table.
+const DATA_TABLE: *const u16 = 0x0000_0016 as _;
+
+/// Address of the version number of the ROM.
+const VERSION_NUMBER: *const u8 = 0x0000_0013 as _;
+
+/// Retrive rom content from a table using a code.
+fn rom_table_lookup<T>(table: *const u16, tag: RomFnTableCode) -> T {
+    unsafe {
+        let rom_table_lookup_ptr: *const u32 = rom_hword_as_ptr(ROM_TABLE_LOOKUP_PTR);
+        let rom_table_lookup: RomTableLookupFn<T> = core::mem::transmute(rom_table_lookup_ptr);
+        rom_table_lookup(rom_hword_as_ptr(table) as *const u16, u16::from_le_bytes(tag) as u32)
+    }
+}
+
+/// To save space, the ROM likes to store memory pointers (which are 32-bit on
+/// the Cortex-M0+) using only the bottom 16-bits. The assumption is that the
+/// values they point at live in the first 64 KiB of ROM, and the ROM is mapped
+/// to address `0x0000_0000` and so 16-bits are always sufficient.
+///
+/// This functions grabs a 16-bit value from ROM and expands it out to a full 32-bit pointer.
+unsafe fn rom_hword_as_ptr(rom_address: *const u16) -> *const u32 {
+    let ptr: u16 = *rom_address;
+    ptr as *const u32
+}
+
+macro_rules! declare_rom_function {
+    (
+        $(#[$outer:meta])*
+        fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
+        $lookup:block
+    ) => {
+        #[doc = r"Additional access for the `"]
+        #[doc = stringify!($name)]
+        #[doc = r"` ROM function."]
+        pub mod $name {
+            /// Retrieve a function pointer.
+            #[cfg(not(feature = "rom-func-cache"))]
+            pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
+                let p: *const u32 = $lookup;
+                unsafe {
+                    let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
+                    func
+                }
+            }
+
+            /// Retrieve a function pointer.
+            #[cfg(feature = "rom-func-cache")]
+            pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
+                use core::sync::atomic::{AtomicU16, Ordering};
+
+                // All pointers in the ROM fit in 16 bits, so we don't need a
+                // full width word to store the cached value.
+                static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
+                // This is safe because the lookup will always resolve
+                // to the same value.  So even if an interrupt or another
+                // core starts at the same time, it just repeats some
+                // work and eventually writes back the correct value.
+                let p: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) {
+                    0 => {
+                        let raw: *const u32 = $lookup;
+                        CACHED_PTR.store(raw as u16, Ordering::Relaxed);
+                        raw
+                    },
+                    val => val as *const u32,
+                };
+                unsafe {
+                    let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
+                    func
+                }
+            }
+        }
+
+        $(#[$outer])*
+        pub extern "C" fn $name( $($argname: $ty),* ) -> $ret {
+            $name::ptr()($($argname),*)
+        }
+    };
+
+    (
+        $(#[$outer:meta])*
+        unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
+        $lookup:block
+    ) => {
+        #[doc = r"Additional access for the `"]
+        #[doc = stringify!($name)]
+        #[doc = r"` ROM function."]
+        pub mod $name {
+            /// Retrieve a function pointer.
+            #[cfg(not(feature = "rom-func-cache"))]
+            pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
+                let p: *const u32 = $lookup;
+                unsafe {
+                    let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
+                    func
+                }
+            }
+
+            /// Retrieve a function pointer.
+            #[cfg(feature = "rom-func-cache")]
+            pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
+                use core::sync::atomic::{AtomicU16, Ordering};
+
+                // All pointers in the ROM fit in 16 bits, so we don't need a
+                // full width word to store the cached value.
+                static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
+                // This is safe because the lookup will always resolve
+                // to the same value.  So even if an interrupt or another
+                // core starts at the same time, it just repeats some
+                // work and eventually writes back the correct value.
+                let p: *const u32 = match CACHED_PTR.load(Ordering::Relaxed) {
+                    0 => {
+                        let raw: *const u32 = $lookup;
+                        CACHED_PTR.store(raw as u16, Ordering::Relaxed);
+                        raw
+                    },
+                    val => val as *const u32,
+                };
+                unsafe {
+                    let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
+                    func
+                }
+            }
+        }
+
+        $(#[$outer])*
+        pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret {
+            $name::ptr()($($argname),*)
+        }
+    };
+}
+
+macro_rules! rom_functions {
+    () => {};
+
+    (
+        $(#[$outer:meta])*
+        $c:literal fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty;
+
+        $($rest:tt)*
+    ) => {
+        declare_rom_function! {
+            $(#[$outer])*
+            fn $name( $($argname: $ty),* ) -> $ret {
+                $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c)
+            }
+        }
+
+        rom_functions!($($rest)*);
+    };
+
+    (
+        $(#[$outer:meta])*
+        $c:literal unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty;
+
+        $($rest:tt)*
+    ) => {
+        declare_rom_function! {
+            $(#[$outer])*
+            unsafe fn $name( $($argname: $ty),* ) -> $ret {
+                $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c)
+            }
+        }
+
+        rom_functions!($($rest)*);
+    };
+}
+
+rom_functions! {
+    /// Return a count of the number of 1 bits in value.
+    b"P3" fn popcount32(value: u32) -> u32;
+
+    /// Return the bits of value in the reverse order.
+    b"R3" fn reverse32(value: u32) -> u32;
+
+    /// Return the number of consecutive high order 0 bits of value. If value is zero, returns 32.
+    b"L3" fn clz32(value: u32) -> u32;
+
+    /// Return the number of consecutive low order 0 bits of value. If value is zero, returns 32.
+    b"T3" fn ctz32(value: u32) -> u32;
+
+    /// Resets the RP2040 and uses the watchdog facility to re-start in BOOTSEL mode:
+    ///   * gpio_activity_pin_mask is provided to enable an 'activity light' via GPIO attached LED
+    ///     for the USB Mass Storage Device:
+    ///     * 0 No pins are used as per cold boot.
+    ///     * Otherwise a single bit set indicating which GPIO pin should be set to output and
+    ///       raised whenever there is mass storage activity from the host.
+    ///  * disable_interface_mask may be used to control the exposed USB interfaces:
+    ///    * 0 To enable both interfaces (as per cold boot).
+    ///    * 1 To disable the USB Mass Storage Interface.
+    ///    * 2 to Disable the USB PICOBOOT Interface.
+    b"UB" fn reset_to_usb_boot(gpio_activity_pin_mask: u32, disable_interface_mask: u32) -> ();
+
+    /// Sets n bytes start at ptr to the value c and returns ptr
+    b"MS" unsafe fn memset(ptr: *mut u8, c: u8, n: u32) -> *mut u8;
+
+    /// Sets n bytes start at ptr to the value c and returns ptr.
+    ///
+    /// Note this is a slightly more efficient variant of _memset that may only
+    /// be used if ptr is word aligned.
+    // Note the datasheet does not match the actual ROM for the code here, see
+    // https://github.com/raspberrypi/pico-feedback/issues/217
+    b"S4" unsafe fn memset4(ptr: *mut u32, c: u8, n: u32) -> *mut u32;
+
+    /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the
+    /// regions overlap.
+    b"MC" unsafe fn memcpy(dest: *mut u8, src: *const u8, n: u32) -> *mut u8;
+
+    /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the
+    /// regions overlap.
+    ///
+    /// Note this is a slightly more efficient variant of _memcpy that may only be
+    /// used if dest and src are word aligned.
+    b"C4" unsafe fn memcpy44(dest: *mut u32, src: *const u32, n: u32) -> *mut u8;
+
+    /// Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads.
+    b"IF" unsafe fn connect_internal_flash() -> ();
+
+    /// First set up the SSI for serial-mode operations, then issue the fixed XIP exit sequence.
+    ///
+    /// Note that the bootrom code uses the IO forcing logic to drive the CS pin, which must be
+    /// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache). This
+    /// function configures the SSI with a fixed SCK clock divisor of /6.
+    b"EX" unsafe fn flash_exit_xip() -> ();
+
+    /// Erase a count bytes, starting at addr (offset from start of flash). Optionally, pass a
+    /// block erase command e.g. D8h block erase, and the size of the block erased by this
+    /// command — this function will use the larger block erase where possible, for much higher
+    /// erase speed. addr must be aligned to a 4096-byte sector, and count must be a multiple of
+    /// 4096 bytes.
+    b"RE" unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> ();
+
+    /// Program data to a range of flash addresses starting at `addr` (and
+    /// offset from the start of flash) and `count` bytes in size. The value
+    /// `addr` must be aligned to a 256-byte boundary, and `count` must be a
+    /// multiple of 256.
+    b"RP" unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> ();
+
+    /// Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that the SSI can
+    /// drive the flashchip select as normal.
+    b"FC" unsafe fn flash_flush_cache() -> ();
+
+    /// Configure the SSI to generate a standard 03h serial read command, with 24 address bits,
+    /// upon each XIP access. This is a very slow XIP configuration, but is very widely supported.
+    /// The debugger calls this function after performing a flash erase/programming operation, so
+    /// that the freshly-programmed code and data is visible to the debug host, without having to
+    /// know exactly what kind of flash device is connected.
+    b"CX" unsafe fn flash_enter_cmd_xip() -> ();
+
+    /// This is the method that is entered by core 1 on reset to wait to be launched by core 0.
+    /// There are few cases where you should call this method (resetting core 1 is much better).
+    /// This method does not return and should only ever be called on core 1.
+    b"WV" unsafe fn wait_for_vector() -> !;
+}
+
+// Various C intrinsics in the ROM
+intrinsics! {
+    #[alias = __popcountdi2]
+    extern "C" fn __popcountsi2(x: u32) -> u32 {
+        popcount32(x)
+    }
+
+    #[alias = __clzdi2]
+    extern "C" fn __clzsi2(x: u32) -> u32 {
+        clz32(x)
+    }
+
+    #[alias = __ctzdi2]
+    extern "C" fn __ctzsi2(x: u32) -> u32 {
+        ctz32(x)
+    }
+
+    // __rbit is only unofficial, but it show up in the ARM documentation,
+    // so may as well hook it up.
+    #[alias = __rbitl]
+    extern "C" fn __rbit(x: u32) -> u32 {
+        reverse32(x)
+    }
+
+    unsafe extern "aapcs" fn __aeabi_memset(dest: *mut u8, n: usize, c: i32) -> () {
+        // Different argument order
+        memset(dest, c as u8, n as u32);
+    }
+
+    #[alias = __aeabi_memset8]
+    unsafe extern "aapcs" fn __aeabi_memset4(dest: *mut u8, n: usize, c: i32) -> () {
+        // Different argument order
+        memset4(dest as *mut u32, c as u8, n as u32);
+    }
+
+    unsafe extern "aapcs" fn __aeabi_memclr(dest: *mut u8, n: usize) -> () {
+        memset(dest, 0, n as u32);
+    }
+
+    #[alias = __aeabi_memclr8]
+    unsafe extern "aapcs" fn __aeabi_memclr4(dest: *mut u8, n: usize) -> () {
+        memset4(dest as *mut u32, 0, n as u32);
+    }
+
+    unsafe extern "aapcs" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, n: usize) -> () {
+        memcpy(dest, src, n as u32);
+    }
+
+    #[alias = __aeabi_memcpy8]
+    unsafe extern "aapcs" fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, n: usize) -> () {
+        memcpy44(dest as *mut u32, src as *const u32, n as u32);
+    }
+}
+
+unsafe fn convert_str(s: *const u8) -> &'static str {
+    let mut end = s;
+    while *end != 0 {
+        end = end.add(1);
+    }
+    let s = core::slice::from_raw_parts(s, end.offset_from(s) as usize);
+    core::str::from_utf8_unchecked(s)
+}
+
+/// The version number of the rom.
+pub fn rom_version_number() -> u8 {
+    unsafe { *VERSION_NUMBER }
+}
+
+/// The Raspberry Pi Trading Ltd copyright string.
+pub fn copyright_string() -> &'static str {
+    let s: *const u8 = rom_table_lookup(DATA_TABLE, *b"CR");
+    unsafe { convert_str(s) }
+}
+
+/// The 8 most significant hex digits of the Bootrom git revision.
+pub fn git_revision() -> u32 {
+    let s: *const u32 = rom_table_lookup(DATA_TABLE, *b"GR");
+    unsafe { *s }
+}
+
+/// The start address of the floating point library code and data.
+///
+/// This and fplib_end along with the individual function pointers in
+/// soft_float_table can be used to copy the floating point implementation into
+/// RAM if desired.
+pub fn fplib_start() -> *const u8 {
+    rom_table_lookup(DATA_TABLE, *b"FS")
+}
+
+/// See Table 180 in the RP2040 datasheet for the contents of this table.
+pub fn soft_float_table() -> *const usize {
+    rom_table_lookup(DATA_TABLE, *b"SF")
+}
+
+/// The end address of the floating point library code and data.
+pub fn fplib_end() -> *const u8 {
+    rom_table_lookup(DATA_TABLE, *b"FE")
+}
+
+/// This entry is only present in the V2 bootrom. See Table 182 in the RP2040 datasheet for the contents of this table.
+pub fn soft_double_table() -> *const usize {
+    if rom_version_number() < 2 {
+        panic!(
+            "Double precision operations require V2 bootrom (found: V{})",
+            rom_version_number()
+        );
+    }
+    rom_table_lookup(DATA_TABLE, *b"SD")
+}
+
+/// ROM functions using single-precision arithmetic (i.e. 'f32' in Rust terms)
+pub mod float_funcs {
+
+    macro_rules! make_functions {
+        (
+            $(
+                $(#[$outer:meta])*
+                $offset:literal $name:ident (
+                    $( $aname:ident : $aty:ty ),*
+                ) -> $ret:ty;
+            )*
+        ) => {
+            $(
+                declare_rom_function! {
+                    $(#[$outer])*
+                    fn $name( $( $aname : $aty ),* ) -> $ret {
+                        let table: *const usize = $crate::rom_data::soft_float_table();
+                        unsafe {
+                            // This is the entry in the table. Our offset is given as a
+                            // byte offset, but we want the table index (each pointer in
+                            // the table is 4 bytes long)
+                            let entry: *const usize = table.offset($offset / 4);
+                            // Read the pointer from the table
+                            core::ptr::read(entry) as *const u32
+                        }
+                    }
+                }
+            )*
+        }
+    }
+
+    make_functions! {
+        /// Calculates `a + b`
+        0x00 fadd(a: f32, b: f32) -> f32;
+        /// Calculates `a - b`
+        0x04 fsub(a: f32, b: f32) -> f32;
+        /// Calculates `a * b`
+        0x08 fmul(a: f32, b: f32) -> f32;
+        /// Calculates `a / b`
+        0x0c fdiv(a: f32, b: f32) -> f32;
+
+        // 0x10 and 0x14 are deprecated
+
+        /// Calculates `sqrt(v)` (or return -Infinity if v is negative)
+        0x18 fsqrt(v: f32) -> f32;
+        /// Converts an f32 to a signed integer,
+        /// rounding towards -Infinity, and clamping the result to lie within the
+        /// range `-0x80000000` to `0x7FFFFFFF`
+        0x1c float_to_int(v: f32) -> i32;
+        /// Converts an f32 to an signed fixed point
+        /// integer representation where n specifies the position of the binary
+        /// point in the resulting fixed point representation, e.g.
+        /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
+        /// and clamps the resulting integer to lie within the range `0x00000000` to
+        /// `0xFFFFFFFF`
+        0x20 float_to_fix(v: f32, n: i32) -> i32;
+        /// Converts an f32 to an unsigned integer,
+        /// rounding towards -Infinity, and clamping the result to lie within the
+        /// range `0x00000000` to `0xFFFFFFFF`
+        0x24 float_to_uint(v: f32) -> u32;
+        /// Converts an f32 to an unsigned fixed point
+        /// integer representation where n specifies the position of the binary
+        /// point in the resulting fixed point representation, e.g.
+        /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
+        /// and clamps the resulting integer to lie within the range `0x00000000` to
+        /// `0xFFFFFFFF`
+        0x28 float_to_ufix(v: f32, n: i32) -> u32;
+        /// Converts a signed integer to the nearest
+        /// f32 value, rounding to even on tie
+        0x2c int_to_float(v: i32) -> f32;
+        /// Converts a signed fixed point integer
+        /// representation to the nearest f32 value, rounding to even on tie. `n`
+        /// specifies the position of the binary point in fixed point, so `f =
+        /// nearest(v/(2^n))`
+        0x30 fix_to_float(v: i32, n: i32) -> f32;
+        /// Converts an unsigned integer to the nearest
+        /// f32 value, rounding to even on tie
+        0x34 uint_to_float(v: u32) -> f32;
+        /// Converts an unsigned fixed point integer
+        /// representation to the nearest f32 value, rounding to even on tie. `n`
+        /// specifies the position of the binary point in fixed point, so `f =
+        /// nearest(v/(2^n))`
+        0x38 ufix_to_float(v: u32, n: i32) -> f32;
+        /// Calculates the cosine of `angle`. The value
+        /// of `angle` is in radians, and must be in the range `-1024` to `1024`
+        0x3c fcos(angle: f32) -> f32;
+        /// Calculates the sine of `angle`. The value of
+        /// `angle` is in radians, and must be in the range `-1024` to `1024`
+        0x40 fsin(angle: f32) -> f32;
+        /// Calculates the tangent of `angle`. The value
+        /// of `angle` is in radians, and must be in the range `-1024` to `1024`
+        0x44 ftan(angle: f32) -> f32;
+
+        // 0x48 is deprecated
+
+        /// Calculates the exponential value of `v`,
+        /// i.e. `e ** v`
+        0x4c fexp(v: f32) -> f32;
+        /// Calculates the natural logarithm of `v`. If `v <= 0` return -Infinity
+        0x50 fln(v: f32) -> f32;
+    }
+
+    macro_rules! make_functions_v2 {
+        (
+            $(
+                $(#[$outer:meta])*
+                $offset:literal $name:ident (
+                    $( $aname:ident : $aty:ty ),*
+                ) -> $ret:ty;
+            )*
+        ) => {
+            $(
+                declare_rom_function! {
+                    $(#[$outer])*
+                    fn $name( $( $aname : $aty ),* ) -> $ret {
+                        if $crate::rom_data::rom_version_number() < 2 {
+                            panic!(
+                                "Floating point function requires V2 bootrom (found: V{})",
+                                $crate::rom_data::rom_version_number()
+                            );
+                        }
+                        let table: *const usize = $crate::rom_data::soft_float_table();
+                        unsafe {
+                            // This is the entry in the table. Our offset is given as a
+                            // byte offset, but we want the table index (each pointer in
+                            // the table is 4 bytes long)
+                            let entry: *const usize = table.offset($offset / 4);
+                            // Read the pointer from the table
+                            core::ptr::read(entry) as *const u32
+                        }
+                    }
+                }
+            )*
+        }
+    }
+
+    // These are only on BootROM v2 or higher
+    make_functions_v2! {
+        /// Compares two floating point numbers, returning:
+        ///     • 0 if a == b
+        ///     • -1 if a < b
+        ///     • 1 if a > b
+        0x54 fcmp(a: f32, b: f32) -> i32;
+        /// Computes the arc tangent of `y/x` using the
+        /// signs of arguments to determine the correct quadrant
+        0x58 fatan2(y: f32, x: f32) -> f32;
+        /// Converts a signed 64-bit integer to the
+        /// nearest f32 value, rounding to even on tie
+        0x5c int64_to_float(v: i64) -> f32;
+        /// Converts a signed fixed point 64-bit integer
+        /// representation to the nearest f32 value, rounding to even on tie. `n`
+        /// specifies the position of the binary point in fixed point, so `f =
+        /// nearest(v/(2^n))`
+        0x60 fix64_to_float(v: i64, n: i32) -> f32;
+        /// Converts an unsigned 64-bit integer to the
+        /// nearest f32 value, rounding to even on tie
+        0x64 uint64_to_float(v: u64) -> f32;
+        /// Converts an unsigned fixed point 64-bit
+        /// integer representation to the nearest f32 value, rounding to even on
+        /// tie. `n` specifies the position of the binary point in fixed point, so
+        /// `f = nearest(v/(2^n))`
+        0x68 ufix64_to_float(v: u64, n: i32) -> f32;
+        /// Convert an f32 to a signed 64-bit integer, rounding towards -Infinity,
+        /// and clamping the result to lie within the range `-0x8000000000000000` to
+        /// `0x7FFFFFFFFFFFFFFF`
+        0x6c float_to_int64(v: f32) -> i64;
+        /// Converts an f32 to a signed fixed point
+        /// 64-bit integer representation where n specifies the position of the
+        /// binary point in the resulting fixed point representation - e.g. `f(0.5f,
+        /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
+        /// resulting integer to lie within the range `-0x8000000000000000` to
+        /// `0x7FFFFFFFFFFFFFFF`
+        0x70 float_to_fix64(v: f32, n: i32) -> f32;
+        /// Converts an f32 to an unsigned 64-bit
+        /// integer, rounding towards -Infinity, and clamping the result to lie
+        /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF`
+        0x74 float_to_uint64(v: f32) -> u64;
+        /// Converts an f32 to an unsigned fixed point
+        /// 64-bit integer representation where n specifies the position of the
+        /// binary point in the resulting fixed point representation, e.g. `f(0.5f,
+        /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
+        /// resulting integer to lie within the range `0x0000000000000000` to
+        /// `0xFFFFFFFFFFFFFFFF`
+        0x78 float_to_ufix64(v: f32, n: i32) -> u64;
+        /// Converts an f32 to an f64.
+        0x7c float_to_double(v: f32) -> f64;
+    }
+}
+
+/// Functions using double-precision arithmetic (i.e. 'f64' in Rust terms)
+pub mod double_funcs {
+
+    macro_rules! make_double_funcs {
+        (
+            $(
+                $(#[$outer:meta])*
+                $offset:literal $name:ident (
+                    $( $aname:ident : $aty:ty ),*
+                ) -> $ret:ty;
+            )*
+        ) => {
+            $(
+                declare_rom_function! {
+                    $(#[$outer])*
+                    fn $name( $( $aname : $aty ),* ) -> $ret {
+                        let table: *const usize = $crate::rom_data::soft_double_table();
+                        unsafe {
+                            // This is the entry in the table. Our offset is given as a
+                            // byte offset, but we want the table index (each pointer in
+                            // the table is 4 bytes long)
+                            let entry: *const usize = table.offset($offset / 4);
+                            // Read the pointer from the table
+                            core::ptr::read(entry) as *const u32
+                        }
+                    }
+                }
+            )*
+        }
+    }
+
+    make_double_funcs! {
+        /// Calculates `a + b`
+        0x00 dadd(a: f64, b: f64) -> f64;
+        /// Calculates `a - b`
+        0x04 dsub(a: f64, b: f64) -> f64;
+        /// Calculates `a * b`
+        0x08 dmul(a: f64, b: f64) -> f64;
+        /// Calculates `a / b`
+        0x0c ddiv(a: f64, b: f64) -> f64;
+
+        // 0x10 and 0x14 are deprecated
+
+        /// Calculates `sqrt(v)` (or return -Infinity if v is negative)
+        0x18 dsqrt(v: f64) -> f64;
+        /// Converts an f64 to a signed integer,
+        /// rounding towards -Infinity, and clamping the result to lie within the
+        /// range `-0x80000000` to `0x7FFFFFFF`
+        0x1c double_to_int(v: f64) -> i32;
+        /// Converts an f64 to an signed fixed point
+        /// integer representation where n specifies the position of the binary
+        /// point in the resulting fixed point representation, e.g.
+        /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
+        /// and clamps the resulting integer to lie within the range `0x00000000` to
+        /// `0xFFFFFFFF`
+        0x20 double_to_fix(v: f64, n: i32) -> i32;
+        /// Converts an f64 to an unsigned integer,
+        /// rounding towards -Infinity, and clamping the result to lie within the
+        /// range `0x00000000` to `0xFFFFFFFF`
+        0x24 double_to_uint(v: f64) -> u32;
+        /// Converts an f64 to an unsigned fixed point
+        /// integer representation where n specifies the position of the binary
+        /// point in the resulting fixed point representation, e.g.
+        /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity,
+        /// and clamps the resulting integer to lie within the range `0x00000000` to
+        /// `0xFFFFFFFF`
+        0x28 double_to_ufix(v: f64, n: i32) -> u32;
+        /// Converts a signed integer to the nearest
+        /// double value, rounding to even on tie
+        0x2c int_to_double(v: i32) -> f64;
+        /// Converts a signed fixed point integer
+        /// representation to the nearest double value, rounding to even on tie. `n`
+        /// specifies the position of the binary point in fixed point, so `f =
+        /// nearest(v/(2^n))`
+        0x30 fix_to_double(v: i32, n: i32) -> f64;
+        /// Converts an unsigned integer to the nearest
+        /// double value, rounding to even on tie
+        0x34 uint_to_double(v: u32) -> f64;
+        /// Converts an unsigned fixed point integer
+        /// representation to the nearest double value, rounding to even on tie. `n`
+        /// specifies the position of the binary point in fixed point, so f =
+        /// nearest(v/(2^n))
+        0x38 ufix_to_double(v: u32, n: i32) -> f64;
+        /// Calculates the cosine of `angle`. The value
+        /// of `angle` is in radians, and must be in the range `-1024` to `1024`
+        0x3c dcos(angle: f64) -> f64;
+        /// Calculates the sine of `angle`. The value of
+        /// `angle` is in radians, and must be in the range `-1024` to `1024`
+        0x40 dsin(angle: f64) -> f64;
+        /// Calculates the tangent of `angle`. The value
+        /// of `angle` is in radians, and must be in the range `-1024` to `1024`
+        0x44 dtan(angle: f64) -> f64;
+
+        // 0x48 is deprecated
+
+        /// Calculates the exponential value of `v`,
+        /// i.e. `e ** v`
+        0x4c dexp(v: f64) -> f64;
+        /// Calculates the natural logarithm of v. If v <= 0 return -Infinity
+        0x50 dln(v: f64) -> f64;
+
+        // These are only on BootROM v2 or higher
+
+        /// Compares two floating point numbers, returning:
+        ///     • 0 if a == b
+        ///     • -1 if a < b
+        ///     • 1 if a > b
+        0x54 dcmp(a: f64, b: f64) -> i32;
+        /// Computes the arc tangent of `y/x` using the
+        /// signs of arguments to determine the correct quadrant
+        0x58 datan2(y: f64, x: f64) -> f64;
+        /// Converts a signed 64-bit integer to the
+        /// nearest double value, rounding to even on tie
+        0x5c int64_to_double(v: i64) -> f64;
+        /// Converts a signed fixed point 64-bit integer
+        /// representation to the nearest double value, rounding to even on tie. `n`
+        /// specifies the position of the binary point in fixed point, so `f =
+        /// nearest(v/(2^n))`
+        0x60 fix64_to_doubl(v: i64, n: i32) -> f64;
+        /// Converts an unsigned 64-bit integer to the
+        /// nearest double value, rounding to even on tie
+        0x64 uint64_to_double(v: u64) -> f64;
+        /// Converts an unsigned fixed point 64-bit
+        /// integer representation to the nearest double value, rounding to even on
+        /// tie. `n` specifies the position of the binary point in fixed point, so
+        /// `f = nearest(v/(2^n))`
+        0x68 ufix64_to_double(v: u64, n: i32) -> f64;
+        /// Convert an f64 to a signed 64-bit integer, rounding towards -Infinity,
+        /// and clamping the result to lie within the range `-0x8000000000000000` to
+        /// `0x7FFFFFFFFFFFFFFF`
+        0x6c double_to_int64(v: f64) -> i64;
+        /// Converts an f64 to a signed fixed point
+        /// 64-bit integer representation where n specifies the position of the
+        /// binary point in the resulting fixed point representation - e.g. `f(0.5f,
+        /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
+        /// resulting integer to lie within the range `-0x8000000000000000` to
+        /// `0x7FFFFFFFFFFFFFFF`
+        0x70 double_to_fix64(v: f64, n: i32) -> i64;
+        /// Converts an f64 to an unsigned 64-bit
+        /// integer, rounding towards -Infinity, and clamping the result to lie
+        /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF`
+        0x74 double_to_uint64(v: f64) -> u64;
+        /// Converts an f64 to an unsigned fixed point
+        /// 64-bit integer representation where n specifies the position of the
+        /// binary point in the resulting fixed point representation, e.g. `f(0.5f,
+        /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the
+        /// resulting integer to lie within the range `0x0000000000000000` to
+        /// `0xFFFFFFFFFFFFFFFF`
+        0x78 double_to_ufix64(v: f64, n: i32) -> u64;
+        /// Converts an f64 to an f32
+        0x7c double_to_float(v: f64) -> f32;
+    }
+}
diff --git a/embassy-rp/src/rtc/datetime_chrono.rs b/embassy-rp/src/rtc/datetime_chrono.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b3c78dd47619ce2b36dfb96d64ccc8a682393627
--- /dev/null
+++ b/embassy-rp/src/rtc/datetime_chrono.rs
@@ -0,0 +1,62 @@
+use chrono::{Datelike, Timelike};
+
+use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
+
+/// Alias for [`chrono::NaiveDateTime`]
+pub type DateTime = chrono::NaiveDateTime;
+/// Alias for [`chrono::Weekday`]
+pub type DayOfWeek = chrono::Weekday;
+
+/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
+///
+/// [`DateTimeFilter`]: struct.DateTimeFilter.html
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Error {
+    /// The [DateTime] has an invalid year. The year must be between 0 and 4095.
+    InvalidYear,
+    /// The [DateTime] contains an invalid date.
+    InvalidDate,
+    /// The [DateTime] contains an invalid time.
+    InvalidTime,
+}
+
+pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
+    dotw.num_days_from_sunday() as u8
+}
+
+pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
+    if dt.year() < 0 || dt.year() > 4095 {
+        // rp2040 can't hold these years
+        Err(Error::InvalidYear)
+    } else {
+        // The rest of the chrono date is assumed to be valid
+        Ok(())
+    }
+}
+
+pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
+    w.set_year(dt.year() as u16);
+    w.set_month(dt.month() as u8);
+    w.set_day(dt.day() as u8);
+}
+
+pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
+    w.set_dotw(dt.weekday().num_days_from_sunday() as u8);
+    w.set_hour(dt.hour() as u8);
+    w.set_min(dt.minute() as u8);
+    w.set_sec(dt.second() as u8);
+}
+
+pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
+    let year = rtc_1.year() as i32;
+    let month = rtc_1.month() as u32;
+    let day = rtc_1.day() as u32;
+
+    let hour = rtc_0.hour() as u32;
+    let minute = rtc_0.min() as u32;
+    let second = rtc_0.sec() as u32;
+
+    let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?;
+    let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?;
+    Ok(DateTime::new(date, time))
+}
diff --git a/embassy-rp/src/rtc/datetime_no_deps.rs b/embassy-rp/src/rtc/datetime_no_deps.rs
new file mode 100644
index 0000000000000000000000000000000000000000..92770e98428a3a4ea2705cef82833dc229c18731
--- /dev/null
+++ b/embassy-rp/src/rtc/datetime_no_deps.rs
@@ -0,0 +1,127 @@
+use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1};
+
+/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs.
+///
+/// [`DateTimeFilter`]: struct.DateTimeFilter.html
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Error {
+    /// The [DateTime] contains an invalid year value. Must be between `0..=4095`.
+    InvalidYear,
+    /// The [DateTime] contains an invalid month value. Must be between `1..=12`.
+    InvalidMonth,
+    /// The [DateTime] contains an invalid day value. Must be between `1..=31`.
+    InvalidDay,
+    /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday.
+    InvalidDayOfWeek(
+        /// The value of the DayOfWeek that was given.
+        u8,
+    ),
+    /// The [DateTime] contains an invalid hour value. Must be between `0..=23`.
+    InvalidHour,
+    /// The [DateTime] contains an invalid minute value. Must be between `0..=59`.
+    InvalidMinute,
+    /// The [DateTime] contains an invalid second value. Must be between `0..=59`.
+    InvalidSecond,
+}
+
+/// Structure containing date and time information
+pub struct DateTime {
+    /// 0..4095
+    pub year: u16,
+    /// 1..12, 1 is January
+    pub month: u8,
+    /// 1..28,29,30,31 depending on month
+    pub day: u8,
+    ///
+    pub day_of_week: DayOfWeek,
+    /// 0..23
+    pub hour: u8,
+    /// 0..59
+    pub minute: u8,
+    /// 0..59
+    pub second: u8,
+}
+
+/// A day of the week
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
+#[allow(missing_docs)]
+pub enum DayOfWeek {
+    Sunday = 0,
+    Monday = 1,
+    Tuesday = 2,
+    Wednesday = 3,
+    Thursday = 4,
+    Friday = 5,
+    Saturday = 6,
+}
+
+fn day_of_week_from_u8(v: u8) -> Result<DayOfWeek, Error> {
+    Ok(match v {
+        0 => DayOfWeek::Sunday,
+        1 => DayOfWeek::Monday,
+        2 => DayOfWeek::Tuesday,
+        3 => DayOfWeek::Wednesday,
+        4 => DayOfWeek::Thursday,
+        5 => DayOfWeek::Friday,
+        6 => DayOfWeek::Saturday,
+        x => return Err(Error::InvalidDayOfWeek(x)),
+    })
+}
+
+pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 {
+    dotw as u8
+}
+
+pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> {
+    if dt.year > 4095 {
+        Err(Error::InvalidYear)
+    } else if dt.month < 1 || dt.month > 12 {
+        Err(Error::InvalidMonth)
+    } else if dt.day < 1 || dt.day > 31 {
+        Err(Error::InvalidDay)
+    } else if dt.hour > 23 {
+        Err(Error::InvalidHour)
+    } else if dt.minute > 59 {
+        Err(Error::InvalidMinute)
+    } else if dt.second > 59 {
+        Err(Error::InvalidSecond)
+    } else {
+        Ok(())
+    }
+}
+
+pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) {
+    w.set_year(dt.year);
+    w.set_month(dt.month);
+    w.set_day(dt.day);
+}
+
+pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) {
+    w.set_dotw(dt.day_of_week as u8);
+    w.set_hour(dt.hour);
+    w.set_min(dt.minute);
+    w.set_sec(dt.second);
+}
+
+pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result<DateTime, Error> {
+    let year = rtc_1.year();
+    let month = rtc_1.month();
+    let day = rtc_1.day();
+
+    let day_of_week = rtc_0.dotw();
+    let hour = rtc_0.hour();
+    let minute = rtc_0.min();
+    let second = rtc_0.sec();
+
+    let day_of_week = day_of_week_from_u8(day_of_week)?;
+    Ok(DateTime {
+        year,
+        month,
+        day,
+        day_of_week,
+        hour,
+        minute,
+        second,
+    })
+}
diff --git a/embassy-rp/src/rtc/filter.rs b/embassy-rp/src/rtc/filter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d4a3bab2f97b8976ebb2864f05f112029ad5f414
--- /dev/null
+++ b/embassy-rp/src/rtc/filter.rs
@@ -0,0 +1,100 @@
+use super::DayOfWeek;
+use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1};
+
+/// A filter used for [`RealTimeClock::schedule_alarm`].
+///
+/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm
+#[derive(Default)]
+pub struct DateTimeFilter {
+    /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value.
+    pub year: Option<u16>,
+    /// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value.
+    pub month: Option<u8>,
+    /// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value.
+    pub day: Option<u8>,
+    /// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value.
+    pub day_of_week: Option<DayOfWeek>,
+    /// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value.
+    pub hour: Option<u8>,
+    /// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value.
+    pub minute: Option<u8>,
+    /// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value.
+    pub second: Option<u8>,
+}
+
+impl DateTimeFilter {
+    /// Set a filter on the given year
+    pub fn year(mut self, year: u16) -> Self {
+        self.year = Some(year);
+        self
+    }
+    /// Set a filter on the given month
+    pub fn month(mut self, month: u8) -> Self {
+        self.month = Some(month);
+        self
+    }
+    /// Set a filter on the given day
+    pub fn day(mut self, day: u8) -> Self {
+        self.day = Some(day);
+        self
+    }
+    /// Set a filter on the given day of the week
+    pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self {
+        self.day_of_week = Some(day_of_week);
+        self
+    }
+    /// Set a filter on the given hour
+    pub fn hour(mut self, hour: u8) -> Self {
+        self.hour = Some(hour);
+        self
+    }
+    /// Set a filter on the given minute
+    pub fn minute(mut self, minute: u8) -> Self {
+        self.minute = Some(minute);
+        self
+    }
+    /// Set a filter on the given second
+    pub fn second(mut self, second: u8) -> Self {
+        self.second = Some(second);
+        self
+    }
+}
+
+// register helper functions
+impl DateTimeFilter {
+    pub(super) fn write_setup_0(&self, w: &mut IrqSetup0) {
+        if let Some(year) = self.year {
+            w.set_year_ena(true);
+
+            w.set_year(year);
+        }
+        if let Some(month) = self.month {
+            w.set_month_ena(true);
+            w.set_month(month);
+        }
+        if let Some(day) = self.day {
+            w.set_day_ena(true);
+            w.set_day(day);
+        }
+    }
+    pub(super) fn write_setup_1(&self, w: &mut IrqSetup1) {
+        if let Some(day_of_week) = self.day_of_week {
+            w.set_dotw_ena(true);
+            let bits = super::datetime::day_of_week_to_u8(day_of_week);
+
+            w.set_dotw(bits);
+        }
+        if let Some(hour) = self.hour {
+            w.set_hour_ena(true);
+            w.set_hour(hour);
+        }
+        if let Some(minute) = self.minute {
+            w.set_min_ena(true);
+            w.set_min(minute);
+        }
+        if let Some(second) = self.second {
+            w.set_sec_ena(true);
+            w.set_sec(second);
+        }
+    }
+}
diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7f3bbbe735f2033c0e94856e01a7dd833bad9f5b
--- /dev/null
+++ b/embassy-rp/src/rtc/mod.rs
@@ -0,0 +1,188 @@
+mod filter;
+
+use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
+
+pub use self::filter::DateTimeFilter;
+
+#[cfg_attr(feature = "chrono", path = "datetime_chrono.rs")]
+#[cfg_attr(not(feature = "chrono"), path = "datetime_no_deps.rs")]
+mod datetime;
+
+pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError};
+use crate::clocks::clk_rtc_freq;
+
+/// A reference to the real time clock of the system
+pub struct RealTimeClock<'d, T: Instance> {
+    inner: PeripheralRef<'d, T>,
+}
+
+impl<'d, T: Instance> RealTimeClock<'d, T> {
+    /// Create a new instance of the real time clock, with the given date as an initial value.
+    ///
+    /// # Errors
+    ///
+    /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
+    pub fn new(inner: impl Peripheral<P = T> + 'd, initial_date: DateTime) -> Result<Self, RtcError> {
+        into_ref!(inner);
+
+        // Set the RTC divider
+        unsafe {
+            inner
+                .regs()
+                .clkdiv_m1()
+                .write(|w| w.set_clkdiv_m1(clk_rtc_freq() as u16 - 1))
+        };
+
+        let mut result = Self { inner };
+        result.set_leap_year_check(true); // should be on by default, make sure this is the case.
+        result.set_datetime(initial_date)?;
+        Ok(result)
+    }
+
+    /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check.
+    ///
+    /// Leap year checking is enabled by default.
+    pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) {
+        unsafe {
+            self.inner
+                .regs()
+                .ctrl()
+                .modify(|w| w.set_force_notleapyear(!leap_year_check_enabled))
+        };
+    }
+
+    /// Checks to see if this RealTimeClock is running
+    pub fn is_running(&self) -> bool {
+        unsafe { self.inner.regs().ctrl().read().rtc_active() }
+    }
+
+    /// Set the datetime to a new value.
+    ///
+    /// # Errors
+    ///
+    /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range.
+    pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> {
+        self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?;
+
+        // disable RTC while we configure it
+        unsafe {
+            self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false));
+            while self.inner.regs().ctrl().read().rtc_active() {
+                core::hint::spin_loop();
+            }
+
+            self.inner.regs().setup_0().write(|w| {
+                self::datetime::write_setup_0(&t, w);
+            });
+            self.inner.regs().setup_1().write(|w| {
+                self::datetime::write_setup_1(&t, w);
+            });
+
+            // Load the new datetime and re-enable RTC
+            self.inner.regs().ctrl().write(|w| w.set_load(true));
+            self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true));
+            while !self.inner.regs().ctrl().read().rtc_active() {
+                core::hint::spin_loop();
+            }
+        }
+        Ok(())
+    }
+
+    /// Return the current datetime.
+    ///
+    /// # Errors
+    ///
+    /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`].
+    pub fn now(&self) -> Result<DateTime, RtcError> {
+        if !self.is_running() {
+            return Err(RtcError::NotRunning);
+        }
+
+        let rtc_0 = unsafe { self.inner.regs().rtc_0().read() };
+        let rtc_1 = unsafe { self.inner.regs().rtc_1().read() };
+
+        self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime)
+    }
+
+    /// Disable the alarm that was scheduled with [`schedule_alarm`].
+    ///
+    /// [`schedule_alarm`]: #method.schedule_alarm
+    pub fn disable_alarm(&mut self) {
+        unsafe {
+            self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false));
+
+            while self.inner.regs().irq_setup_0().read().match_active() {
+                core::hint::spin_loop();
+            }
+        }
+    }
+
+    /// Schedule an alarm. The `filter` determines at which point in time this alarm is set.
+    ///
+    /// Keep in mind that the filter only triggers on the specified time. If you want to schedule this alarm every minute, you have to call:
+    /// ```no_run
+    /// # #[cfg(feature = "chrono")]
+    /// # fn main() { }
+    /// # #[cfg(not(feature = "chrono"))]
+    /// # fn main() {
+    /// # use embassy_rp::rtc::{RealTimeClock, DateTimeFilter};
+    /// # let mut real_time_clock: RealTimeClock = unsafe { core::mem::zeroed() };
+    /// let now = real_time_clock.now().unwrap();
+    /// real_time_clock.schedule_alarm(
+    ///     DateTimeFilter::default()
+    ///         .minute(if now.minute == 59 { 0 } else { now.minute + 1 })
+    /// );
+    /// # }
+    /// ```
+    pub fn schedule_alarm(&mut self, filter: DateTimeFilter) {
+        self.disable_alarm();
+
+        unsafe {
+            self.inner.regs().irq_setup_0().write(|w| {
+                filter.write_setup_0(w);
+            });
+            self.inner.regs().irq_setup_1().write(|w| {
+                filter.write_setup_1(w);
+            });
+
+            // Set the enable bit and check if it is set
+            self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true));
+            while !self.inner.regs().irq_setup_0().read().match_active() {
+                core::hint::spin_loop();
+            }
+        }
+    }
+
+    /// Clear the interrupt. This should be called every time the `RTC_IRQ` interrupt is triggered,
+    /// or the next [`schedule_alarm`] will never fire.
+    ///
+    /// [`schedule_alarm`]: #method.schedule_alarm
+    pub fn clear_interrupt(&mut self) {
+        self.disable_alarm();
+    }
+}
+
+/// Errors that can occur on methods on [RtcClock]
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum RtcError {
+    /// An invalid DateTime was given or stored on the hardware.
+    InvalidDateTime(DateTimeError),
+
+    /// The RTC clock is not running
+    NotRunning,
+}
+
+mod sealed {
+    pub trait Instance {
+        fn regs(&self) -> crate::pac::rtc::Rtc;
+    }
+}
+
+pub trait Instance: sealed::Instance {}
+
+impl sealed::Instance for crate::peripherals::RTC {
+    fn regs(&self) -> crate::pac::rtc::Rtc {
+        crate::pac::RTC
+    }
+}
+impl Instance for crate::peripherals::RTC {}
diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs
index 74f0b04de3185ae68bddf97a9d7b1c229a35fea8..03293e0640d38d0c5c36d7180cbf92f61ab0b3e2 100644
--- a/embassy-rp/src/spi.rs
+++ b/embassy-rp/src/spi.rs
@@ -1,9 +1,9 @@
 use core::marker::PhantomData;
 
 use embassy_embedded_hal::SetConfig;
+use embassy_futures::join::join;
 use embassy_hal_common::{into_ref, PeripheralRef};
 pub use embedded_hal_02::spi::{Phase, Polarity};
-use futures::future::join;
 
 use crate::dma::{AnyChannel, Channel};
 use crate::gpio::sealed::Pin as _;
@@ -325,30 +325,52 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
     }
 
     pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
-        let ch = self.tx_dma.as_mut().unwrap();
-        let transfer = unsafe {
+        let tx_ch = self.tx_dma.as_mut().unwrap();
+        let tx_transfer = unsafe {
             self.inner.regs().dmacr().modify(|reg| {
                 reg.set_txdmae(true);
             });
             // If we don't assign future to a variable, the data register pointer
             // is held across an await and makes the future non-Send.
-            crate::dma::write(ch, buffer, self.inner.regs().dr().ptr() as *mut _, T::TX_DREQ)
+            crate::dma::write(tx_ch, buffer, self.inner.regs().dr().ptr() as *mut _, T::TX_DREQ)
         };
-        transfer.await;
+        tx_transfer.await;
+
+        let p = self.inner.regs();
+        unsafe {
+            while p.sr().read().bsy() {}
+
+            // clear RX FIFO contents to prevent stale reads
+            while p.sr().read().rne() {
+                let _: u16 = p.dr().read().data();
+            }
+            // clear RX overrun interrupt
+            p.icr().write(|w| w.set_roric(true));
+        }
+
         Ok(())
     }
 
     pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
-        let ch = self.rx_dma.as_mut().unwrap();
-        let transfer = unsafe {
-            self.inner.regs().dmacr().modify(|reg| {
+        unsafe {
+            self.inner.regs().dmacr().write(|reg| {
                 reg.set_rxdmae(true);
-            });
+                reg.set_txdmae(true);
+            })
+        };
+        let tx_ch = self.tx_dma.as_mut().unwrap();
+        let tx_transfer = unsafe {
             // If we don't assign future to a variable, the data register pointer
             // is held across an await and makes the future non-Send.
-            crate::dma::read(ch, self.inner.regs().dr().ptr() as *const _, buffer, T::RX_DREQ)
+            crate::dma::write_repeated(tx_ch, self.inner.regs().dr().ptr() as *mut u8, buffer.len(), T::TX_DREQ)
         };
-        transfer.await;
+        let rx_ch = self.rx_dma.as_mut().unwrap();
+        let rx_transfer = unsafe {
+            // If we don't assign future to a variable, the data register pointer
+            // is held across an await and makes the future non-Send.
+            crate::dma::read(rx_ch, self.inner.regs().dr().ptr() as *const _, buffer, T::RX_DREQ)
+        };
+        join(tx_transfer, rx_transfer).await;
         Ok(())
     }
 
@@ -364,20 +386,20 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         let (_, from_len) = crate::dma::slice_ptr_parts(tx_ptr);
         let (_, to_len) = crate::dma::slice_ptr_parts_mut(rx_ptr);
         assert_eq!(from_len, to_len);
+        unsafe {
+            self.inner.regs().dmacr().write(|reg| {
+                reg.set_rxdmae(true);
+                reg.set_txdmae(true);
+            })
+        };
         let tx_ch = self.tx_dma.as_mut().unwrap();
         let tx_transfer = unsafe {
-            self.inner.regs().dmacr().modify(|reg| {
-                reg.set_txdmae(true);
-            });
             // If we don't assign future to a variable, the data register pointer
             // is held across an await and makes the future non-Send.
             crate::dma::write(tx_ch, tx_ptr, self.inner.regs().dr().ptr() as *mut _, T::TX_DREQ)
         };
         let rx_ch = self.rx_dma.as_mut().unwrap();
         let rx_transfer = unsafe {
-            self.inner.regs().dmacr().modify(|reg| {
-                reg.set_rxdmae(true);
-            });
             // If we don't assign future to a variable, the data register pointer
             // is held across an await and makes the future non-Send.
             crate::dma::read(rx_ch, self.inner.regs().dr().ptr() as *const _, rx_ptr, T::RX_DREQ)
diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs
index 82eafdefdaf4dabb5c15cb01b18c668a73c4fc89..ce473b21d4f9560e2d50b3cd9c3f1c092ce8626f 100644
--- a/embassy-rp/src/usb.rs
+++ b/embassy-rp/src/usb.rs
@@ -1,3 +1,4 @@
+use core::future::{poll_fn, Future};
 use core::marker::PhantomData;
 use core::slice;
 use core::sync::atomic::Ordering;
@@ -6,10 +7,9 @@ use core::task::Poll;
 use atomic_polyfill::compiler_fence;
 use embassy_hal_common::into_ref;
 use embassy_sync::waitqueue::AtomicWaker;
-use embassy_usb::driver::{self, EndpointAllocError, EndpointError, Event, Unsupported};
-use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection};
-use futures::future::poll_fn;
-use futures::Future;
+use embassy_usb::driver::{
+    self, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported,
+};
 
 use crate::interrupt::{Interrupt, InterruptExt};
 use crate::{pac, peripherals, Peripheral, RegExt};
@@ -205,8 +205,8 @@ impl<'d, T: Instance> Driver<'d, T> {
         );
 
         let alloc = match D::dir() {
-            UsbDirection::Out => &mut self.ep_out,
-            UsbDirection::In => &mut self.ep_in,
+            Direction::Out => &mut self.ep_out,
+            Direction::In => &mut self.ep_in,
         };
 
         let index = alloc.iter_mut().enumerate().find(|(i, ep)| {
@@ -255,7 +255,7 @@ impl<'d, T: Instance> Driver<'d, T> {
         };
 
         match D::dir() {
-            UsbDirection::Out => unsafe {
+            Direction::Out => unsafe {
                 T::dpram().ep_out_control(index - 1).write(|w| {
                     w.set_enable(false);
                     w.set_buffer_address(addr);
@@ -263,7 +263,7 @@ impl<'d, T: Instance> Driver<'d, T> {
                     w.set_endpoint_type(ep_type_reg);
                 })
             },
-            UsbDirection::In => unsafe {
+            Direction::In => unsafe {
                 T::dpram().ep_in_control(index - 1).write(|w| {
                     w.set_enable(false);
                     w.set_buffer_address(addr);
@@ -430,14 +430,14 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
 
         let n = ep_addr.index();
         match ep_addr.direction() {
-            UsbDirection::In => unsafe {
+            Direction::In => unsafe {
                 T::dpram().ep_in_control(n - 1).modify(|w| w.set_enable(enabled));
                 T::dpram().ep_in_buffer_control(ep_addr.index()).write(|w| {
                     w.set_pid(0, true); // first packet is DATA0, but PID is flipped before
                 });
                 EP_IN_WAKERS[n].wake();
             },
-            UsbDirection::Out => unsafe {
+            Direction::Out => unsafe {
                 T::dpram().ep_out_control(n - 1).modify(|w| w.set_enable(enabled));
 
                 T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| {
@@ -475,14 +475,14 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
 }
 
 trait Dir {
-    fn dir() -> UsbDirection;
+    fn dir() -> Direction;
     fn waker(i: usize) -> &'static AtomicWaker;
 }
 
 pub enum In {}
 impl Dir for In {
-    fn dir() -> UsbDirection {
-        UsbDirection::In
+    fn dir() -> Direction {
+        Direction::In
     }
 
     #[inline]
@@ -493,8 +493,8 @@ impl Dir for In {
 
 pub enum Out {}
 impl Dir for Out {
-    fn dir() -> UsbDirection {
-        UsbDirection::Out
+    fn dir() -> Direction {
+        Direction::Out
     }
 
     #[inline]
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index b4c19f32eafc98bff1d910adb4008f3dfca49054..484496f246eddeb8d6fe79a6a2671a8b33511ff1 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -34,6 +34,7 @@ flavors = [
 embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
 embassy-executor = { version = "0.1.0", path = "../embassy-executor" }
 embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true }
+embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
 embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-4"]}
 embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" }
 embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" }
diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs
index c0bd44e0f2348ef919f8339faaa86393ad731547..bd92b35a0f13a7d2a80e422450582fc0eaa60cf2 100644
--- a/embassy-stm32/src/can/bxcan.rs
+++ b/embassy-stm32/src/can/bxcan.rs
@@ -12,6 +12,7 @@ pub struct Can<'d, T: Instance> {
 }
 
 impl<'d, T: Instance> Can<'d, T> {
+    /// Creates a new Bxcan instance, blocking for 11 recessive bits to sync with the CAN bus.
     pub fn new(
         peri: impl Peripheral<P = T> + 'd,
         rx: impl Peripheral<P = impl RxPin<T>> + 'd,
@@ -31,6 +32,28 @@ impl<'d, T: Instance> Can<'d, T> {
             can: bxcan::Can::builder(BxcanInstance(peri)).enable(),
         }
     }
+
+    /// Creates a new Bxcan instance, keeping the peripheral in sleep mode.
+    /// You must call [Can::enable_non_blocking] to use the peripheral.
+    pub fn new_disabled(
+        peri: impl Peripheral<P = T> + 'd,
+        rx: impl Peripheral<P = impl RxPin<T>> + 'd,
+        tx: impl Peripheral<P = impl TxPin<T>> + 'd,
+    ) -> Self {
+        into_ref!(peri, rx, tx);
+
+        unsafe {
+            rx.set_as_af(rx.af_num(), AFType::Input);
+            tx.set_as_af(tx.af_num(), AFType::OutputPushPull);
+        }
+
+        T::enable();
+        T::reset();
+
+        Self {
+            can: bxcan::Can::builder(BxcanInstance(peri)).leave_disabled(),
+        }
+    }
 }
 
 impl<'d, T: Instance> Drop for Can<'d, T> {
diff --git a/embassy-stm32/src/dcmi.rs b/embassy-stm32/src/dcmi.rs
index fb9dc9d082d678099c90a5b07315129cf384b0d1..20e1a4070e726fc66e141fd2467a9f7acd271c74 100644
--- a/embassy-stm32/src/dcmi.rs
+++ b/embassy-stm32/src/dcmi.rs
@@ -1,8 +1,8 @@
+use core::future::poll_fn;
 use core::task::Poll;
 
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 
 use crate::gpio::sealed::AFType;
 use crate::gpio::Speed;
@@ -429,7 +429,7 @@ where
             }
         });
 
-        let (_, result) = futures::future::join(dma_read, result).await;
+        let (_, result) = embassy_futures::join::join(dma_read, result).await;
 
         unsafe { Self::toggle(false) };
 
@@ -537,7 +537,7 @@ where
 
         unsafe { Self::toggle(true) };
 
-        let (_, result) = futures::future::join(dma_result, result).await;
+        let (_, result) = embassy_futures::join::join(dma_result, result).await;
 
         unsafe { Self::toggle(false) };
 
diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs
index 1ab0438ad6db2a96c68f03cc287697e675b0801c..38629a9326d8d3b202292cbe27b4c309eb201df0 100644
--- a/embassy-stm32/src/eth/v1/mod.rs
+++ b/embassy-stm32/src/eth/v1/mod.rs
@@ -29,7 +29,7 @@ use super::*;
 
 pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage<Inner<'d, T, TX, RX>>);
 impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> {
-    pub fn new() -> Self {
+    pub const fn new() -> Self {
         Self(StateStorage::new())
     }
 }
diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs
index d67c3c5e4eb0ddd8504ec46a7b471f321694d8c0..a81ee1183cb54ac6813e1078aa7e8b5032463a08 100644
--- a/embassy-stm32/src/eth/v2/mod.rs
+++ b/embassy-stm32/src/eth/v2/mod.rs
@@ -19,7 +19,7 @@ use super::*;
 
 pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage<Inner<'d, T, TX, RX>>);
 impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> {
-    pub fn new() -> Self {
+    pub const fn new() -> Self {
         Self(StateStorage::new())
     }
 }
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index db4924461ec52e54165890be35593a0cf3fb8643..b7c89931ce315189f9efa17e425df6c202266242 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -1,4 +1,5 @@
 use core::cmp;
+use core::future::poll_fn;
 use core::task::Poll;
 
 use atomic_polyfill::{AtomicUsize, Ordering};
@@ -6,7 +7,6 @@ use embassy_embedded_hal::SetConfig;
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 
 use crate::dma::NoDma;
 use crate::gpio::sealed::AFType;
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 30ff02d56cf8c439123725376ae48adc6fe66346..0392e8086d1ad8f8b2c1cb48e8e797924b4fa5a9 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -1,5 +1,5 @@
 #![no_std]
-#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))]
+#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 
 // This must go FIRST so that all the other modules see its macros.
 pub mod fmt;
diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs
index 520f2ab9a7374f4f18bf6510d00f324beea77732..10fc4a75effcf3845219e2538d4598949105209a 100644
--- a/embassy-stm32/src/rng.rs
+++ b/embassy-stm32/src/rng.rs
@@ -1,10 +1,10 @@
 #![macro_use]
 
+use core::future::poll_fn;
 use core::task::Poll;
 
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 use rand_core::{CryptoRng, RngCore};
 
 use crate::{pac, peripherals, Peripheral};
diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs
index 67758c49237166393ffd8e550a06e1714b4a4311..a8bc6385f6e989e9829f344b6cf34afcaa875500 100644
--- a/embassy-stm32/src/sdmmc/mod.rs
+++ b/embassy-stm32/src/sdmmc/mod.rs
@@ -1,12 +1,12 @@
 #![macro_use]
 
 use core::default::Default;
+use core::future::poll_fn;
 use core::task::Poll;
 
 use embassy_hal_common::drop::OnDrop;
 use embassy_hal_common::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
-use futures::future::poll_fn;
 use sdio_host::{BusWidth, CardCapacity, CardStatus, CurrentState, SDStatus, CID, CSD, OCR, SCR};
 
 use crate::dma::NoDma;
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs
index 02e6020b07c666888b6eddb9c52bbbca0c37958d..556d123051d5e62be8f92241c982fb4b4fa5b66b 100644
--- a/embassy-stm32/src/spi/mod.rs
+++ b/embassy-stm32/src/spi/mod.rs
@@ -3,9 +3,9 @@
 use core::ptr;
 
 use embassy_embedded_hal::SetConfig;
+use embassy_futures::join::join;
 use embassy_hal_common::{into_ref, PeripheralRef};
 pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
-use futures::future::join;
 
 use self::sealed::WordSize;
 use crate::dma::{slice_ptr_parts, NoDma, Transfer};
diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs
index a7fa438945e62edecafdf1fbc621474f38ae549d..46c49a997c24db35c289d210fafa0edd6723f986 100644
--- a/embassy-stm32/src/usart/buffered.rs
+++ b/embassy-stm32/src/usart/buffered.rs
@@ -1,17 +1,16 @@
-use core::future::Future;
+use core::future::{poll_fn, Future};
 use core::task::Poll;
 
 use atomic_polyfill::{compiler_fence, Ordering};
 use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage};
 use embassy_hal_common::ring_buffer::RingBuffer;
 use embassy_sync::waitqueue::WakerRegistration;
-use futures::future::poll_fn;
 
 use super::*;
 
 pub struct State<'d, T: BasicInstance>(StateStorage<StateInner<'d, T>>);
 impl<'d, T: BasicInstance> State<'d, T> {
-    pub fn new() -> Self {
+    pub const fn new() -> Self {
         Self(StateStorage::new())
     }
 }
diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs
index db965824a1d65be016bb37cfb7e59f426254fe48..39809a3e140065c6ea9eecdbb57fa54a22eaecc7 100644
--- a/embassy-stm32/src/usb/usb.rs
+++ b/embassy-stm32/src/usb/usb.rs
@@ -1,5 +1,6 @@
 #![macro_use]
 
+use core::future::{poll_fn, Future};
 use core::marker::PhantomData;
 use core::sync::atomic::Ordering;
 use core::task::Poll;
@@ -8,10 +9,9 @@ use atomic_polyfill::{AtomicBool, AtomicU8};
 use embassy_hal_common::into_ref;
 use embassy_sync::waitqueue::AtomicWaker;
 use embassy_time::{block_for, Duration};
-use embassy_usb::driver::{self, EndpointAllocError, EndpointError, Event, Unsupported};
-use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection};
-use futures::future::poll_fn;
-use futures::Future;
+use embassy_usb::driver::{
+    self, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported,
+};
 use pac::common::{Reg, RW};
 use pac::usb::vals::{EpType, Stat};
 
@@ -280,8 +280,8 @@ impl<'d, T: Instance> Driver<'d, T> {
             }
             let used = ep.used_out || ep.used_in;
             let used_dir = match D::dir() {
-                UsbDirection::Out => ep.used_out,
-                UsbDirection::In => ep.used_in,
+                Direction::Out => ep.used_out,
+                Direction::In => ep.used_in,
             };
             !used || (ep.ep_type == ep_type && !used_dir)
         });
@@ -294,7 +294,7 @@ impl<'d, T: Instance> Driver<'d, T> {
         ep.ep_type = ep_type;
 
         let buf = match D::dir() {
-            UsbDirection::Out => {
+            Direction::Out => {
                 assert!(!ep.used_out);
                 ep.used_out = true;
 
@@ -313,7 +313,7 @@ impl<'d, T: Instance> Driver<'d, T> {
                     _phantom: PhantomData,
                 }
             }
-            UsbDirection::In => {
+            Direction::In => {
                 assert!(!ep.used_in);
                 ep.used_in = true;
 
@@ -505,7 +505,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
         // This can race, so do a retry loop.
         let reg = T::regs().epr(ep_addr.index() as _);
         match ep_addr.direction() {
-            UsbDirection::In => {
+            Direction::In => {
                 loop {
                     let r = unsafe { reg.read() };
                     match r.stat_tx() {
@@ -524,7 +524,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
                 }
                 EP_IN_WAKERS[ep_addr.index()].wake();
             }
-            UsbDirection::Out => {
+            Direction::Out => {
                 loop {
                     let r = unsafe { reg.read() };
                     match r.stat_rx() {
@@ -550,8 +550,8 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
         let regs = T::regs();
         let epr = unsafe { regs.epr(ep_addr.index() as _).read() };
         match ep_addr.direction() {
-            UsbDirection::In => epr.stat_tx() == Stat::STALL,
-            UsbDirection::Out => epr.stat_rx() == Stat::STALL,
+            Direction::In => epr.stat_tx() == Stat::STALL,
+            Direction::Out => epr.stat_rx() == Stat::STALL,
         }
     }
 
@@ -561,7 +561,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
         let reg = T::regs().epr(ep_addr.index() as _);
         trace!("EPR before: {:04x}", unsafe { reg.read() }.0);
         match ep_addr.direction() {
-            UsbDirection::In => {
+            Direction::In => {
                 loop {
                     let want_stat = match enabled {
                         false => Stat::DISABLED,
@@ -577,7 +577,7 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
                 }
                 EP_IN_WAKERS[ep_addr.index()].wake();
             }
-            UsbDirection::Out => {
+            Direction::Out => {
                 loop {
                     let want_stat = match enabled {
                         false => Stat::DISABLED,
@@ -617,14 +617,14 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
 }
 
 trait Dir {
-    fn dir() -> UsbDirection;
+    fn dir() -> Direction;
     fn waker(i: usize) -> &'static AtomicWaker;
 }
 
 pub enum In {}
 impl Dir for In {
-    fn dir() -> UsbDirection {
-        UsbDirection::In
+    fn dir() -> Direction {
+        Direction::In
     }
 
     #[inline]
@@ -635,8 +635,8 @@ impl Dir for In {
 
 pub enum Out {}
 impl Dir for Out {
-    fn dir() -> UsbDirection {
-        UsbDirection::Out
+    fn dir() -> Direction {
+        Direction::Out
     }
 
     #[inline]
diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs
index 25150e8aa523978d9276c5425cbde699c03736fc..80bb907a3e09f697596f4b0d85c07a7c39e0460f 100644
--- a/embassy-sync/src/lib.rs
+++ b/embassy-sync/src/lib.rs
@@ -1,5 +1,5 @@
 #![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)]
-#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))]
+#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 #![allow(clippy::new_without_default)]
 #![doc = include_str!("../README.md")]
 #![warn(missing_docs)]
diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs
index 75a6e8dd33afba2bc9fdd74db32fe98a28c9fcf4..fcf056d36c641fd05c000ac059b35fbb9901392b 100644
--- a/embassy-sync/src/mutex.rs
+++ b/embassy-sync/src/mutex.rs
@@ -2,11 +2,10 @@
 //!
 //! This module provides a mutex that can be used to synchronize data between asynchronous tasks.
 use core::cell::{RefCell, UnsafeCell};
+use core::future::poll_fn;
 use core::ops::{Deref, DerefMut};
 use core::task::Poll;
 
-use futures_util::future::poll_fn;
-
 use crate::blocking_mutex::raw::RawMutex;
 use crate::blocking_mutex::Mutex as BlockingMutex;
 use crate::waitqueue::WakerRegistration;
@@ -111,6 +110,22 @@ where
 
         Ok(MutexGuard { mutex: self })
     }
+
+    /// Consumes this mutex, returning the underlying data.
+    pub fn into_inner(self) -> T
+    where
+        T: Sized,
+    {
+        self.inner.into_inner()
+    }
+
+    /// Returns a mutable reference to the underlying data.
+    ///
+    /// Since this call borrows the Mutex mutably, no actual locking needs to
+    /// take place -- the mutable borrow statically guarantees no locks exist.
+    pub fn get_mut(&mut self) -> &mut T {
+        self.inner.get_mut()
+    }
 }
 
 /// Async mutex guard.
diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs
index f6ebeb9b9445d6c30b18b2e56b182637bc794579..c3c10a8afb1592bb085e3845613d011b2158a1da 100644
--- a/embassy-sync/src/signal.rs
+++ b/embassy-sync/src/signal.rs
@@ -1,9 +1,11 @@
 //! A synchronization primitive for passing the latest value to a task.
-use core::cell::UnsafeCell;
-use core::future::Future;
-use core::mem;
+use core::cell::Cell;
+use core::future::{poll_fn, Future};
 use core::task::{Context, Poll, Waker};
 
+use crate::blocking_mutex::raw::RawMutex;
+use crate::blocking_mutex::Mutex;
+
 /// Single-slot signaling primitive.
 ///
 /// This is similar to a [`Channel`](crate::channel::Channel) with a buffer size of 1, except
@@ -20,16 +22,20 @@ use core::task::{Context, Poll, Waker};
 ///
 /// ```
 /// use embassy_sync::signal::Signal;
+/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 ///
 /// enum SomeCommand {
 ///   On,
 ///   Off,
 /// }
 ///
-/// static SOME_SIGNAL: Signal<SomeCommand> = Signal::new();
+/// static SOME_SIGNAL: Signal<CriticalSectionRawMutex, SomeCommand> = Signal::new();
 /// ```
-pub struct Signal<T> {
-    state: UnsafeCell<State<T>>,
+pub struct Signal<M, T>
+where
+    M: RawMutex,
+{
+    state: Mutex<M, Cell<State<T>>>,
 }
 
 enum State<T> {
@@ -38,24 +44,27 @@ enum State<T> {
     Signaled(T),
 }
 
-unsafe impl<T: Send> Send for Signal<T> {}
-unsafe impl<T: Send> Sync for Signal<T> {}
-
-impl<T> Signal<T> {
+impl<M, T> Signal<M, T>
+where
+    M: RawMutex,
+{
     /// Create a new `Signal`.
     pub const fn new() -> Self {
         Self {
-            state: UnsafeCell::new(State::None),
+            state: Mutex::new(Cell::new(State::None)),
         }
     }
 }
 
-impl<T: Send> Signal<T> {
+impl<M, T: Send> Signal<M, T>
+where
+    M: RawMutex,
+{
     /// Mark this Signal as signaled.
     pub fn signal(&self, val: T) {
-        critical_section::with(|_| unsafe {
-            let state = &mut *self.state.get();
-            if let State::Waiting(waker) = mem::replace(state, State::Signaled(val)) {
+        self.state.lock(|cell| {
+            let state = cell.replace(State::Signaled(val));
+            if let State::Waiting(waker) = state {
                 waker.wake();
             }
         })
@@ -63,38 +72,46 @@ impl<T: Send> Signal<T> {
 
     /// Remove the queued value in this `Signal`, if any.
     pub fn reset(&self) {
-        critical_section::with(|_| unsafe {
-            let state = &mut *self.state.get();
-            *state = State::None
-        })
+        self.state.lock(|cell| cell.set(State::None));
     }
 
-    /// Manually poll the Signal future.
-    pub fn poll_wait(&self, cx: &mut Context<'_>) -> Poll<T> {
-        critical_section::with(|_| unsafe {
-            let state = &mut *self.state.get();
+    fn poll_wait(&self, cx: &mut Context<'_>) -> Poll<T> {
+        self.state.lock(|cell| {
+            let state = cell.replace(State::None);
             match state {
                 State::None => {
-                    *state = State::Waiting(cx.waker().clone());
+                    cell.set(State::Waiting(cx.waker().clone()));
                     Poll::Pending
                 }
-                State::Waiting(w) if w.will_wake(cx.waker()) => Poll::Pending,
-                State::Waiting(_) => panic!("waker overflow"),
-                State::Signaled(_) => match mem::replace(state, State::None) {
-                    State::Signaled(res) => Poll::Ready(res),
-                    _ => unreachable!(),
-                },
+                State::Waiting(w) if w.will_wake(cx.waker()) => {
+                    cell.set(State::Waiting(w));
+                    Poll::Pending
+                }
+                State::Waiting(w) => {
+                    cell.set(State::Waiting(cx.waker().clone()));
+                    w.wake();
+                    Poll::Pending
+                }
+                State::Signaled(res) => Poll::Ready(res),
             }
         })
     }
 
     /// Future that completes when this Signal has been signaled.
     pub fn wait(&self) -> impl Future<Output = T> + '_ {
-        futures_util::future::poll_fn(move |cx| self.poll_wait(cx))
+        poll_fn(move |cx| self.poll_wait(cx))
     }
 
     /// non-blocking method to check whether this signal has been signaled.
     pub fn signaled(&self) -> bool {
-        critical_section::with(|_| matches!(unsafe { &*self.state.get() }, State::Signaled(_)))
+        self.state.lock(|cell| {
+            let state = cell.replace(State::None);
+
+            let res = matches!(state, State::Signaled(_));
+
+            cell.set(state);
+
+            res
+        })
     }
 }
diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs
index 5b2620986959e2e178f14862f8f8cd450bef0392..4edc883fe1e8ed91d223813fe2daa99742826f75 100644
--- a/embassy-time/src/lib.rs
+++ b/embassy-time/src/lib.rs
@@ -1,5 +1,5 @@
 #![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)]
-#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))]
+#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
 #![doc = include_str!("../README.md")]
 #![allow(clippy::new_without_default)]
 #![warn(missing_docs)]
diff --git a/embassy-usb-ncm/Cargo.toml b/embassy-usb-driver/Cargo.toml
similarity index 55%
rename from embassy-usb-ncm/Cargo.toml
rename to embassy-usb-driver/Cargo.toml
index 15d3db96f6dd71594cc870cd5ae6dbfd0979afe6..b525df3373c529f29f6503c119f0720bc8c3563b 100644
--- a/embassy-usb-ncm/Cargo.toml
+++ b/embassy-usb-driver/Cargo.toml
@@ -1,17 +1,16 @@
 [package]
-name = "embassy-usb-ncm"
+name = "embassy-usb-driver"
 version = "0.1.0"
 edition = "2021"
 
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
 [package.metadata.embassy_docs]
-src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-ncm-v$VERSION/embassy-usb-ncm/src/"
-src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-ncm/src/"
+src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-driver-v$VERSION/embassy-usb/src/"
+src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-driver/src/"
 features = ["defmt"]
 target = "thumbv7em-none-eabi"
 
 [dependencies]
-embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
-embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
-
 defmt = { version = "0.3", optional = true }
-log = { version = "0.4.14", optional = true }
+log = { version = "0.4.14", optional = true }
\ No newline at end of file
diff --git a/embassy-usb/src/driver.rs b/embassy-usb-driver/src/lib.rs
similarity index 73%
rename from embassy-usb/src/driver.rs
rename to embassy-usb-driver/src/lib.rs
index 7888f1639f28fc64f898b98507ce19ddb6b8af38..fc29786fc4ff3d784a9790ef7037a5ce96b822ce 100644
--- a/embassy-usb/src/driver.rs
+++ b/embassy-usb-driver/src/lib.rs
@@ -1,6 +1,104 @@
+#![no_std]
+
 use core::future::Future;
 
-use super::types::*;
+/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from
+/// the perspective of the host, which is backward for devices, but the standard directions are used
+/// for consistency.
+///
+/// The values of the enum also match the direction bit used in endpoint addresses and control
+/// request types.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Direction {
+    /// Host to device (OUT)
+    Out,
+    /// Device to host (IN)
+    In,
+}
+
+/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the
+/// transfer bmAttributes transfer type bits.
+#[repr(u8)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum EndpointType {
+    /// Control endpoint. Used for device management. Only the host can initiate requests. Usually
+    /// used only endpoint 0.
+    Control = 0b00,
+    /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet.
+    Isochronous = 0b01,
+    /// Bulk endpoint. Used for large amounts of best-effort reliable data.
+    Bulk = 0b10,
+    /// Interrupt endpoint. Used for small amounts of time-critical reliable data.
+    Interrupt = 0b11,
+}
+
+/// Type-safe endpoint address.
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct EndpointAddress(u8);
+
+impl From<u8> for EndpointAddress {
+    #[inline]
+    fn from(addr: u8) -> EndpointAddress {
+        EndpointAddress(addr)
+    }
+}
+
+impl From<EndpointAddress> for u8 {
+    #[inline]
+    fn from(addr: EndpointAddress) -> u8 {
+        addr.0
+    }
+}
+
+impl EndpointAddress {
+    const INBITS: u8 = Direction::In as u8;
+
+    /// Constructs a new EndpointAddress with the given index and direction.
+    #[inline]
+    pub fn from_parts(index: usize, dir: Direction) -> Self {
+        EndpointAddress(index as u8 | dir as u8)
+    }
+
+    /// Gets the direction part of the address.
+    #[inline]
+    pub fn direction(&self) -> Direction {
+        if (self.0 & Self::INBITS) != 0 {
+            Direction::In
+        } else {
+            Direction::Out
+        }
+    }
+
+    /// Returns true if the direction is IN, otherwise false.
+    #[inline]
+    pub fn is_in(&self) -> bool {
+        (self.0 & Self::INBITS) != 0
+    }
+
+    /// Returns true if the direction is OUT, otherwise false.
+    #[inline]
+    pub fn is_out(&self) -> bool {
+        (self.0 & Self::INBITS) == 0
+    }
+
+    /// Gets the index part of the endpoint address.
+    #[inline]
+    pub fn index(&self) -> usize {
+        (self.0 & !Self::INBITS) as usize
+    }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct EndpointInfo {
+    pub addr: EndpointAddress,
+    pub ep_type: EndpointType,
+    pub max_packet_size: u16,
+    pub interval: u8,
+}
 
 /// Driver for a specific USB peripheral. Implement this to add support for a new hardware
 /// platform.
diff --git a/embassy-usb-hid/Cargo.toml b/embassy-usb-hid/Cargo.toml
deleted file mode 100644
index 2f7733dc6ca44aefb9408d24d26dde05e0281ed3..0000000000000000000000000000000000000000
--- a/embassy-usb-hid/Cargo.toml
+++ /dev/null
@@ -1,24 +0,0 @@
-[package]
-name = "embassy-usb-hid"
-version = "0.1.0"
-edition = "2021"
-
-[package.metadata.embassy_docs]
-src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-hid-v$VERSION/embassy-usb-hid/src/"
-src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-hid/src/"
-features = ["defmt"]
-target = "thumbv7em-none-eabi"
-
-[features]
-default = ["usbd-hid"]
-usbd-hid = ["dep:usbd-hid", "ssmarshal"]
-
-[dependencies]
-embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
-embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
-
-defmt = { version = "0.3", optional = true }
-log = { version = "0.4.14", optional = true }
-usbd-hid = { version = "0.6.0", optional = true }
-ssmarshal = { version = "1.0", default-features = false, optional = true }
-futures-util = { version = "0.3.21", default-features = false }
diff --git a/embassy-usb-hid/src/fmt.rs b/embassy-usb-hid/src/fmt.rs
deleted file mode 100644
index 06697081337781e593185e5757b944779e37ec1a..0000000000000000000000000000000000000000
--- a/embassy-usb-hid/src/fmt.rs
+++ /dev/null
@@ -1,225 +0,0 @@
-#![macro_use]
-#![allow(unused_macros)]
-
-#[cfg(all(feature = "defmt", feature = "log"))]
-compile_error!("You may not enable both `defmt` and `log` features.");
-
-macro_rules! assert {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert!($($x)*);
-        }
-    };
-}
-
-macro_rules! assert_eq {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert_eq!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert_eq!($($x)*);
-        }
-    };
-}
-
-macro_rules! assert_ne {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert_ne!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert_ne!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert_eq {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert_eq!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert_eq!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert_ne {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert_ne!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert_ne!($($x)*);
-        }
-    };
-}
-
-macro_rules! todo {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::todo!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::todo!($($x)*);
-        }
-    };
-}
-
-macro_rules! unreachable {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::unreachable!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::unreachable!($($x)*);
-        }
-    };
-}
-
-macro_rules! panic {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::panic!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::panic!($($x)*);
-        }
-    };
-}
-
-macro_rules! trace {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::trace!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::trace!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! debug {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::debug!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! info {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::info!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::info!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! warn {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::warn!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::warn!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! error {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::error!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::error!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-#[cfg(feature = "defmt")]
-macro_rules! unwrap {
-    ($($x:tt)*) => {
-        ::defmt::unwrap!($($x)*)
-    };
-}
-
-#[cfg(not(feature = "defmt"))]
-macro_rules! unwrap {
-    ($arg:expr) => {
-        match $crate::fmt::Try::into_result($arg) {
-            ::core::result::Result::Ok(t) => t,
-            ::core::result::Result::Err(e) => {
-                ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
-            }
-        }
-    };
-    ($arg:expr, $($msg:expr),+ $(,)? ) => {
-        match $crate::fmt::Try::into_result($arg) {
-            ::core::result::Result::Ok(t) => t,
-            ::core::result::Result::Err(e) => {
-                ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
-            }
-        }
-    }
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct NoneError;
-
-pub trait Try {
-    type Ok;
-    type Error;
-    fn into_result(self) -> Result<Self::Ok, Self::Error>;
-}
-
-impl<T> Try for Option<T> {
-    type Ok = T;
-    type Error = NoneError;
-
-    #[inline]
-    fn into_result(self) -> Result<T, NoneError> {
-        self.ok_or(NoneError)
-    }
-}
-
-impl<T, E> Try for Result<T, E> {
-    type Ok = T;
-    type Error = E;
-
-    #[inline]
-    fn into_result(self) -> Self {
-        self
-    }
-}
diff --git a/embassy-usb-ncm/src/fmt.rs b/embassy-usb-ncm/src/fmt.rs
deleted file mode 100644
index 06697081337781e593185e5757b944779e37ec1a..0000000000000000000000000000000000000000
--- a/embassy-usb-ncm/src/fmt.rs
+++ /dev/null
@@ -1,225 +0,0 @@
-#![macro_use]
-#![allow(unused_macros)]
-
-#[cfg(all(feature = "defmt", feature = "log"))]
-compile_error!("You may not enable both `defmt` and `log` features.");
-
-macro_rules! assert {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert!($($x)*);
-        }
-    };
-}
-
-macro_rules! assert_eq {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert_eq!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert_eq!($($x)*);
-        }
-    };
-}
-
-macro_rules! assert_ne {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert_ne!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert_ne!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert_eq {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert_eq!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert_eq!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert_ne {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert_ne!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert_ne!($($x)*);
-        }
-    };
-}
-
-macro_rules! todo {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::todo!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::todo!($($x)*);
-        }
-    };
-}
-
-macro_rules! unreachable {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::unreachable!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::unreachable!($($x)*);
-        }
-    };
-}
-
-macro_rules! panic {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::panic!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::panic!($($x)*);
-        }
-    };
-}
-
-macro_rules! trace {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::trace!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::trace!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! debug {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::debug!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! info {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::info!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::info!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! warn {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::warn!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::warn!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! error {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::error!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::error!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-#[cfg(feature = "defmt")]
-macro_rules! unwrap {
-    ($($x:tt)*) => {
-        ::defmt::unwrap!($($x)*)
-    };
-}
-
-#[cfg(not(feature = "defmt"))]
-macro_rules! unwrap {
-    ($arg:expr) => {
-        match $crate::fmt::Try::into_result($arg) {
-            ::core::result::Result::Ok(t) => t,
-            ::core::result::Result::Err(e) => {
-                ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
-            }
-        }
-    };
-    ($arg:expr, $($msg:expr),+ $(,)? ) => {
-        match $crate::fmt::Try::into_result($arg) {
-            ::core::result::Result::Ok(t) => t,
-            ::core::result::Result::Err(e) => {
-                ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
-            }
-        }
-    }
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct NoneError;
-
-pub trait Try {
-    type Ok;
-    type Error;
-    fn into_result(self) -> Result<Self::Ok, Self::Error>;
-}
-
-impl<T> Try for Option<T> {
-    type Ok = T;
-    type Error = NoneError;
-
-    #[inline]
-    fn into_result(self) -> Result<T, NoneError> {
-        self.ok_or(NoneError)
-    }
-}
-
-impl<T, E> Try for Result<T, E> {
-    type Ok = T;
-    type Error = E;
-
-    #[inline]
-    fn into_result(self) -> Self {
-        self
-    }
-}
diff --git a/embassy-usb-serial/Cargo.toml b/embassy-usb-serial/Cargo.toml
deleted file mode 100644
index 9788588e9cc097b9e14824facd2ad5caee01de5f..0000000000000000000000000000000000000000
--- a/embassy-usb-serial/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "embassy-usb-serial"
-version = "0.1.0"
-edition = "2021"
-
-[package.metadata.embassy_docs]
-src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-serial-v$VERSION/embassy-usb-serial/src/"
-src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-serial/src/"
-features = ["defmt"]
-target = "thumbv7em-none-eabi"
-
-[dependencies]
-embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
-embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
-
-defmt = { version = "0.3", optional = true }
-log = { version = "0.4.14", optional = true }
diff --git a/embassy-usb-serial/src/fmt.rs b/embassy-usb-serial/src/fmt.rs
deleted file mode 100644
index 06697081337781e593185e5757b944779e37ec1a..0000000000000000000000000000000000000000
--- a/embassy-usb-serial/src/fmt.rs
+++ /dev/null
@@ -1,225 +0,0 @@
-#![macro_use]
-#![allow(unused_macros)]
-
-#[cfg(all(feature = "defmt", feature = "log"))]
-compile_error!("You may not enable both `defmt` and `log` features.");
-
-macro_rules! assert {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert!($($x)*);
-        }
-    };
-}
-
-macro_rules! assert_eq {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert_eq!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert_eq!($($x)*);
-        }
-    };
-}
-
-macro_rules! assert_ne {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::assert_ne!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::assert_ne!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert_eq {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert_eq!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert_eq!($($x)*);
-        }
-    };
-}
-
-macro_rules! debug_assert_ne {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::debug_assert_ne!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug_assert_ne!($($x)*);
-        }
-    };
-}
-
-macro_rules! todo {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::todo!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::todo!($($x)*);
-        }
-    };
-}
-
-macro_rules! unreachable {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::unreachable!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::unreachable!($($x)*);
-        }
-    };
-}
-
-macro_rules! panic {
-    ($($x:tt)*) => {
-        {
-            #[cfg(not(feature = "defmt"))]
-            ::core::panic!($($x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::panic!($($x)*);
-        }
-    };
-}
-
-macro_rules! trace {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::trace!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::trace!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! debug {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::debug!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::debug!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! info {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::info!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::info!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! warn {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::warn!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::warn!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-macro_rules! error {
-    ($s:literal $(, $x:expr)* $(,)?) => {
-        {
-            #[cfg(feature = "log")]
-            ::log::error!($s $(, $x)*);
-            #[cfg(feature = "defmt")]
-            ::defmt::error!($s $(, $x)*);
-            #[cfg(not(any(feature = "log", feature="defmt")))]
-            let _ = ($( & $x ),*);
-        }
-    };
-}
-
-#[cfg(feature = "defmt")]
-macro_rules! unwrap {
-    ($($x:tt)*) => {
-        ::defmt::unwrap!($($x)*)
-    };
-}
-
-#[cfg(not(feature = "defmt"))]
-macro_rules! unwrap {
-    ($arg:expr) => {
-        match $crate::fmt::Try::into_result($arg) {
-            ::core::result::Result::Ok(t) => t,
-            ::core::result::Result::Err(e) => {
-                ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
-            }
-        }
-    };
-    ($arg:expr, $($msg:expr),+ $(,)? ) => {
-        match $crate::fmt::Try::into_result($arg) {
-            ::core::result::Result::Ok(t) => t,
-            ::core::result::Result::Err(e) => {
-                ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
-            }
-        }
-    }
-}
-
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct NoneError;
-
-pub trait Try {
-    type Ok;
-    type Error;
-    fn into_result(self) -> Result<Self::Ok, Self::Error>;
-}
-
-impl<T> Try for Option<T> {
-    type Ok = T;
-    type Error = NoneError;
-
-    #[inline]
-    fn into_result(self) -> Result<T, NoneError> {
-        self.ok_or(NoneError)
-    }
-}
-
-impl<T, E> Try for Result<T, E> {
-    type Ok = T;
-    type Error = E;
-
-    #[inline]
-    fn into_result(self) -> Self {
-        self
-    }
-}
diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml
index 8cad4d314803c4ff5109dd9241dff94ba2416089..aad54dbaf4f715816982ba4f8c7655666f510837 100644
--- a/embassy-usb/Cargo.toml
+++ b/embassy-usb/Cargo.toml
@@ -9,9 +9,20 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb/s
 features = ["defmt"]
 target = "thumbv7em-none-eabi"
 
+[features]
+defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
+usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
+default = ["usbd-hid"]
+
 [dependencies]
 embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
+embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" }
+embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
 
 defmt = { version = "0.3", optional = true }
 log = { version = "0.4.14", optional = true }
-heapless = "0.7.10"
\ No newline at end of file
+heapless = "0.7.10"
+
+# for HID
+usbd-hid = { version = "0.6.0", optional = true }
+ssmarshal = { version = "1.0", default-features = false, optional = true }
diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs
index 6be88bc769c0b6fb1046a282e359a14e33c997b4..87a8333bb0ddfd30afcda90673e40b0c6409ac79 100644
--- a/embassy-usb/src/builder.rs
+++ b/embassy-usb/src/builder.rs
@@ -1,11 +1,10 @@
 use heapless::Vec;
 
-use super::control::ControlHandler;
-use super::descriptor::{BosWriter, DescriptorWriter};
-use super::driver::{Driver, Endpoint};
-use super::types::*;
-use super::{DeviceStateHandler, UsbDevice, MAX_INTERFACE_COUNT};
-use crate::{Interface, STRING_INDEX_CUSTOM_START};
+use crate::control::ControlHandler;
+use crate::descriptor::{BosWriter, DescriptorWriter};
+use crate::driver::{Driver, Endpoint, EndpointType};
+use crate::types::*;
+use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START};
 
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb/src/class/cdc_acm.rs
similarity index 95%
rename from embassy-usb-serial/src/lib.rs
rename to embassy-usb/src/class/cdc_acm.rs
index f3de2ec1b8bd316616a4b2052f9edcda8a6479d5..09bb1cc8dcc4953c1efab14b2edbf45c90ebf1e6 100644
--- a/embassy-usb-serial/src/lib.rs
+++ b/embassy-usb/src/class/cdc_acm.rs
@@ -1,19 +1,13 @@
-#![no_std]
-#![feature(generic_associated_types)]
-#![feature(type_alias_impl_trait)]
-
-// This mod MUST go first, so that the others see its macros.
-pub(crate) mod fmt;
-
 use core::cell::Cell;
 use core::mem::{self, MaybeUninit};
 use core::sync::atomic::{AtomicBool, Ordering};
 
 use embassy_sync::blocking_mutex::CriticalSectionMutex;
-use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request};
-use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
-use embassy_usb::types::*;
-use embassy_usb::Builder;
+
+use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
+use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
+use crate::types::*;
+use crate::Builder;
 
 /// This should be used as `device_class` when building the `UsbDevice`.
 pub const USB_CLASS_CDC: u8 = 0x02;
@@ -269,7 +263,7 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
 }
 
 /// Number of stop bits for LineCoding
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub enum StopBits {
     /// 1 stop bit
@@ -293,7 +287,7 @@ impl From<u8> for StopBits {
 }
 
 /// Parity for LineCoding
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub enum ParityType {
     None = 0,
@@ -317,7 +311,7 @@ impl From<u8> for ParityType {
 ///
 /// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
 /// be ignored if you don't plan to interface with a physical UART.
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub struct LineCoding {
     stop_bits: StopBits,
diff --git a/embassy-usb-ncm/src/lib.rs b/embassy-usb/src/class/cdc_ncm.rs
similarity index 97%
rename from embassy-usb-ncm/src/lib.rs
rename to embassy-usb/src/class/cdc_ncm.rs
index e796af28f3667f6f1ab7e5c66f7a396932e1349a..a39b87e9b3984bcd68d0186f4779a81848ccd476 100644
--- a/embassy-usb-ncm/src/lib.rs
+++ b/embassy-usb/src/class/cdc_ncm.rs
@@ -1,15 +1,10 @@
-#![no_std]
-
-// This mod MUST go first, so that the others see its macros.
-pub(crate) mod fmt;
-
 use core::intrinsics::copy_nonoverlapping;
 use core::mem::{size_of, MaybeUninit};
 
-use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request};
-use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
-use embassy_usb::types::*;
-use embassy_usb::Builder;
+use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
+use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
+use crate::types::*;
+use crate::Builder;
 
 /// This should be used as `device_class` when building the `UsbDevice`.
 pub const USB_CLASS_CDC: u8 = 0x02;
diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb/src/class/hid.rs
similarity index 96%
rename from embassy-usb-hid/src/lib.rs
rename to embassy-usb/src/class/hid.rs
index 5fee60bbc4f8e380612b976c45f4d291ba888afa..4d1fa995f60ec95ccdbcfee75485437e0177b3a3 100644
--- a/embassy-usb-hid/src/lib.rs
+++ b/embassy-usb/src/class/hid.rs
@@ -1,24 +1,16 @@
-#![no_std]
-#![feature(generic_associated_types)]
-#![feature(type_alias_impl_trait)]
-
-//! Implements HID functionality for a usb-device device.
-
-// This mod MUST go first, so that the others see its macros.
-pub(crate) mod fmt;
-
 use core::mem::MaybeUninit;
 use core::ops::Range;
 use core::sync::atomic::{AtomicUsize, Ordering};
 
-use embassy_usb::control::{ControlHandler, InResponse, OutResponse, Request, RequestType};
-use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
-use embassy_usb::Builder;
 #[cfg(feature = "usbd-hid")]
 use ssmarshal::serialize;
 #[cfg(feature = "usbd-hid")]
 use usbd_hid::descriptor::AsInputReport;
 
+use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType};
+use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
+use crate::Builder;
+
 const USB_CLASS_HID: u8 = 0x03;
 const USB_SUBCLASS_NONE: u8 = 0x00;
 const USB_PROTOCOL_NONE: u8 = 0x00;
@@ -205,9 +197,9 @@ pub enum ReadError {
     Sync(Range<usize>),
 }
 
-impl From<embassy_usb::driver::EndpointError> for ReadError {
-    fn from(val: embassy_usb::driver::EndpointError) -> Self {
-        use embassy_usb::driver::EndpointError::*;
+impl From<EndpointError> for ReadError {
+    fn from(val: EndpointError) -> Self {
+        use EndpointError::*;
         match val {
             BufferOverflow => ReadError::BufferOverflow,
             Disabled => ReadError::Disabled,
@@ -438,7 +430,7 @@ impl<'d> ControlHandler for Control<'d> {
         }
     }
 
-    fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse {
+    fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse {
         trace!("HID control_out {:?} {=[u8]:x}", req, data);
         if let RequestType::Class = req.request_type {
             match req.request {
diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..af27577a61a1e56081aafc9d403fc9be4cc19558
--- /dev/null
+++ b/embassy-usb/src/class/mod.rs
@@ -0,0 +1,3 @@
+pub mod cdc_acm;
+pub mod cdc_ncm;
+pub mod hid;
diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs
index 3e5749a01ae8045dff96bc5da03d8ac76590f005..d6d0c6565fb294fa1ed29445de03aff396ea8663 100644
--- a/embassy-usb/src/control.rs
+++ b/embassy-usb/src/control.rs
@@ -1,7 +1,8 @@
 //! USB control data types.
 use core::mem;
 
-use super::types::*;
+use crate::driver::Direction;
+use crate::types::StringIndex;
 
 /// Control request type.
 #[repr(u8)]
@@ -42,7 +43,7 @@ pub enum Recipient {
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub struct Request {
     /// Direction of the request.
-    pub direction: UsbDirection,
+    pub direction: Direction,
     /// Type of the request.
     pub request_type: RequestType,
     /// Recipient of the request.
@@ -105,7 +106,7 @@ impl Request {
         let recipient = rt & 0b11111;
 
         Request {
-            direction: rt.into(),
+            direction: if rt & 0x80 == 0 { Direction::Out } else { Direction::In },
             request_type: unsafe { mem::transmute((rt >> 5) & 0b11) },
             recipient: if recipient <= 3 {
                 unsafe { mem::transmute(recipient) }
diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs
index b94a4b161d7caf1609de5fbda269ed9fbfd568e4..497f03196a8a082f40bd6895f46e98d3bf5cf122 100644
--- a/embassy-usb/src/descriptor.rs
+++ b/embassy-usb/src/descriptor.rs
@@ -1,6 +1,7 @@
-use super::builder::Config;
-use super::types::*;
-use super::CONFIGURATION_VALUE;
+use crate::builder::Config;
+use crate::driver::EndpointInfo;
+use crate::types::*;
+use crate::CONFIGURATION_VALUE;
 
 /// Standard descriptor types
 #[allow(missing_docs)]
diff --git a/embassy-usb/src/descriptor_reader.rs b/embassy-usb/src/descriptor_reader.rs
index 0a12b566c5b2d18154a96aa809687e2124b9b78e..d64bcb73b23d4c319e756bb998bac5aa12efeeef 100644
--- a/embassy-usb/src/descriptor_reader.rs
+++ b/embassy-usb/src/descriptor_reader.rs
@@ -1,5 +1,5 @@
 use crate::descriptor::descriptor_type;
-use crate::types::EndpointAddress;
+use crate::driver::EndpointAddress;
 
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs
index b165804ef2931b3fe4ee58da0129fb11e50f3001..661b84119da7f000c57ada7e1c714ba250eecb3d 100644
--- a/embassy-usb/src/lib.rs
+++ b/embassy-usb/src/lib.rs
@@ -1,27 +1,27 @@
 #![no_std]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 // This mod MUST go first, so that the others see its macros.
 pub(crate) mod fmt;
 
+pub use embassy_usb_driver as driver;
+
 mod builder;
+pub mod class;
 pub mod control;
 pub mod descriptor;
 mod descriptor_reader;
-pub mod driver;
 pub mod types;
 
 use embassy_futures::select::{select, Either};
 use heapless::Vec;
 
-pub use self::builder::{Builder, Config};
-use self::control::*;
-use self::descriptor::*;
-use self::driver::{Bus, Driver, Event};
-use self::types::*;
+pub use crate::builder::{Builder, Config};
+use crate::control::*;
+use crate::descriptor::*;
 use crate::descriptor_reader::foreach_endpoint;
-use crate::driver::ControlPipe;
+use crate::driver::{Bus, ControlPipe, Direction, Driver, EndpointAddress, Event};
+use crate::types::*;
 
 /// The global state of the USB device.
 ///
@@ -248,11 +248,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
     async fn handle_control(&mut self, req: [u8; 8]) {
         let req = Request::parse(&req);
 
-        trace!("control request: {:02x}", req);
+        trace!("control request: {:?}", req);
 
         match req.direction {
-            UsbDirection::In => self.handle_control_in(req).await,
-            UsbDirection::Out => self.handle_control_out(req).await,
+            Direction::In => self.handle_control_in(req).await,
+            Direction::Out => self.handle_control_out(req).await,
         }
 
         if self.inner.set_address_pending {
diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs
index b8717ffa9adf965e3ead4cd72a6e7f6680762f62..aeab063d199491a82c2afa46fd8c0f38fe541db6 100644
--- a/embassy-usb/src/types.rs
+++ b/embassy-usb/src/types.rs
@@ -1,108 +1,3 @@
-/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from
-/// the perspective of the host, which is backward for devices, but the standard directions are used
-/// for consistency.
-///
-/// The values of the enum also match the direction bit used in endpoint addresses and control
-/// request types.
-#[repr(u8)]
-#[derive(Copy, Clone, Eq, PartialEq, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum UsbDirection {
-    /// Host to device (OUT)
-    Out = 0x00,
-    /// Device to host (IN)
-    In = 0x80,
-}
-
-impl From<u8> for UsbDirection {
-    fn from(value: u8) -> Self {
-        unsafe { core::mem::transmute(value & 0x80) }
-    }
-}
-
-/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the
-/// transfer bmAttributes transfer type bits.
-#[repr(u8)]
-#[derive(Copy, Clone, Eq, PartialEq, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum EndpointType {
-    /// Control endpoint. Used for device management. Only the host can initiate requests. Usually
-    /// used only endpoint 0.
-    Control = 0b00,
-    /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet.
-    Isochronous = 0b01,
-    /// Bulk endpoint. Used for large amounts of best-effort reliable data.
-    Bulk = 0b10,
-    /// Interrupt endpoint. Used for small amounts of time-critical reliable data.
-    Interrupt = 0b11,
-}
-
-/// Type-safe endpoint address.
-#[derive(Debug, Clone, Copy, Eq, PartialEq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct EndpointAddress(u8);
-
-impl From<u8> for EndpointAddress {
-    #[inline]
-    fn from(addr: u8) -> EndpointAddress {
-        EndpointAddress(addr)
-    }
-}
-
-impl From<EndpointAddress> for u8 {
-    #[inline]
-    fn from(addr: EndpointAddress) -> u8 {
-        addr.0
-    }
-}
-
-impl EndpointAddress {
-    const INBITS: u8 = UsbDirection::In as u8;
-
-    /// Constructs a new EndpointAddress with the given index and direction.
-    #[inline]
-    pub fn from_parts(index: usize, dir: UsbDirection) -> Self {
-        EndpointAddress(index as u8 | dir as u8)
-    }
-
-    /// Gets the direction part of the address.
-    #[inline]
-    pub fn direction(&self) -> UsbDirection {
-        if (self.0 & Self::INBITS) != 0 {
-            UsbDirection::In
-        } else {
-            UsbDirection::Out
-        }
-    }
-
-    /// Returns true if the direction is IN, otherwise false.
-    #[inline]
-    pub fn is_in(&self) -> bool {
-        (self.0 & Self::INBITS) != 0
-    }
-
-    /// Returns true if the direction is OUT, otherwise false.
-    #[inline]
-    pub fn is_out(&self) -> bool {
-        (self.0 & Self::INBITS) == 0
-    }
-
-    /// Gets the index part of the endpoint address.
-    #[inline]
-    pub fn index(&self) -> usize {
-        (self.0 & !Self::INBITS) as usize
-    }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct EndpointInfo {
-    pub addr: EndpointAddress,
-    pub ep_type: EndpointType,
-    pub max_packet_size: u16,
-    pub interval: u8,
-}
-
 /// A handle for a USB interface that contains its number.
 #[derive(Copy, Clone, Eq, PartialEq)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs
index 133a3e678dc93d0d130106a6b5e8bcb2bbdfeeba..7a404a914d3cddda325a488f7ea0e3af3f8ec38a 100644
--- a/examples/boot/application/nrf/src/bin/a.rs
+++ b/examples/boot/application/nrf/src/bin/a.rs
@@ -1,7 +1,6 @@
 #![no_std]
 #![no_main]
 #![macro_use]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use embassy_boot_nrf::FirmwareUpdater;
diff --git a/examples/boot/application/nrf/src/bin/b.rs b/examples/boot/application/nrf/src/bin/b.rs
index 5394bf0c70a3a782928534cec9591e3eff4ca851..1373f277d684f0a31b24ec33cad138278b0975b1 100644
--- a/examples/boot/application/nrf/src/bin/b.rs
+++ b/examples/boot/application/nrf/src/bin/b.rs
@@ -1,7 +1,6 @@
 #![no_std]
 #![no_main]
 #![macro_use]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use embassy_executor::Spawner;
diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs
index 9031997c282fbd0e6dfc610b14ace3ef80405397..8266206b3ef32f0bdf77193a25ca9160e951a39b 100644
--- a/examples/boot/bootloader/nrf/src/main.rs
+++ b/examples/boot/bootloader/nrf/src/main.rs
@@ -21,7 +21,7 @@ fn main() -> ! {
 
     let mut bl = BootLoader::default();
     let start = bl.prepare(&mut SingleFlashConfig::new(&mut BootFlash::<_, 4096>::new(
-        &mut WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, 5),
+        WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, 5),
     )));
     unsafe { bl.load(start) }
 }
diff --git a/examples/boot/bootloader/stm32/src/main.rs b/examples/boot/bootloader/stm32/src/main.rs
index bb5d3e5319708f6a8bb4f8a5b128485466cc30d4..294464d1c22152f27b0404cf9ca675dd049e9805 100644
--- a/examples/boot/bootloader/stm32/src/main.rs
+++ b/examples/boot/bootloader/stm32/src/main.rs
@@ -20,10 +20,9 @@ fn main() -> ! {
     */
 
     let mut bl: BootLoader<ERASE_SIZE, WRITE_SIZE> = BootLoader::default();
-    let mut flash = Flash::unlock(p.FLASH);
-    let start = bl.prepare(&mut SingleFlashConfig::new(
-        &mut BootFlash::<_, ERASE_SIZE, ERASE_VALUE>::new(&mut flash),
-    ));
+    let flash = Flash::unlock(p.FLASH);
+    let mut flash = BootFlash::<_, ERASE_SIZE, ERASE_VALUE>::new(flash);
+    let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash));
     core::mem::drop(flash);
     unsafe { bl.load(start) }
 }
diff --git a/examples/nrf-rtos-trace/src/bin/rtos_trace.rs b/examples/nrf-rtos-trace/src/bin/rtos_trace.rs
index 7d1ad87c81d01f348c5b3d23aa2fc1e927abf3a0..cf8b2f8086ac043abd6cc5424f764e91de5f20b1 100644
--- a/examples/nrf-rtos-trace/src/bin/rtos_trace.rs
+++ b/examples/nrf-rtos-trace/src/bin/rtos_trace.rs
@@ -2,6 +2,7 @@
 #![no_main]
 #![feature(type_alias_impl_trait)]
 
+use core::future::poll_fn;
 use core::task::Poll;
 
 use embassy_executor::Spawner;
@@ -46,7 +47,7 @@ async fn run2() {
 
 #[embassy_executor::task]
 async fn run3() {
-    futures::future::poll_fn(|cx| {
+    poll_fn(|cx| {
         cx.waker().wake_by_ref();
         Poll::<()>::Pending
     })
diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml
index dbc659cda79564d1fa2f1892b2bc201c56bf7513..a5d340c69fcbe04ddee1adbdaabcad1eb6b1ca73 100644
--- a/examples/nrf/Cargo.toml
+++ b/examples/nrf/Cargo.toml
@@ -5,7 +5,7 @@ version = "0.1.0"
 
 [features]
 default = ["nightly"]
-nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid", "embassy-usb-ncm", "embedded-io/async", "embassy-net"]
+nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net"]
 
 [dependencies]
 embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
@@ -15,9 +15,6 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de
 embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true }
-embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true }
-embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true }
-embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"], optional = true }
 embedded-io = "0.3.0"
 
 defmt = "0.3"
diff --git a/examples/nrf/src/bin/executor_fairness_test.rs b/examples/nrf/src/bin/executor_fairness_test.rs
index 9ae030d07cdea1875c6fafcdc9fa0fac748ba24b..2a28f27630e19311a95888c96a34f99beb34912d 100644
--- a/examples/nrf/src/bin/executor_fairness_test.rs
+++ b/examples/nrf/src/bin/executor_fairness_test.rs
@@ -2,6 +2,7 @@
 #![no_main]
 #![feature(type_alias_impl_trait)]
 
+use core::future::poll_fn;
 use core::task::Poll;
 
 use defmt::{info, unwrap};
@@ -26,7 +27,7 @@ async fn run2() {
 
 #[embassy_executor::task]
 async fn run3() {
-    futures::future::poll_fn(|cx| {
+    poll_fn(|cx| {
         cx.waker().wake_by_ref();
         Poll::<()>::Pending
     })
diff --git a/examples/nrf/src/bin/usb_ethernet.rs b/examples/nrf/src/bin/usb_ethernet.rs
index 352660b598c8b5a0961848fac554cb23ecd7560b..de93a2b45068b1eec8e8457b061dcfa73f21cd61 100644
--- a/examples/nrf/src/bin/usb_ethernet.rs
+++ b/examples/nrf/src/bin/usb_ethernet.rs
@@ -1,6 +1,5 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use core::mem;
@@ -16,8 +15,8 @@ use embassy_nrf::usb::{Driver, PowerUsb};
 use embassy_nrf::{interrupt, pac, peripherals};
 use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
 use embassy_sync::channel::Channel;
+use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State};
 use embassy_usb::{Builder, Config, UsbDevice};
-use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State};
 use embedded_io::asynch::Write;
 use static_cell::StaticCell;
 use {defmt_rtt as _, panic_probe as _};
diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs
index 7fdb0b685dd2ec57cd60679ad04fe9562fa5d1c8..76e198719d3dc3bff2aeae8c15f01f41947aa59b 100644
--- a/examples/nrf/src/bin/usb_hid_keyboard.rs
+++ b/examples/nrf/src/bin/usb_hid_keyboard.rs
@@ -1,6 +1,5 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use core::mem;
@@ -8,15 +7,16 @@ use core::sync::atomic::{AtomicBool, Ordering};
 
 use defmt::*;
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_futures::select::{select, Either};
 use embassy_nrf::gpio::{Input, Pin, Pull};
 use embassy_nrf::usb::{Driver, PowerUsb};
 use embassy_nrf::{interrupt, pac};
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 use embassy_sync::signal::Signal;
+use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State};
 use embassy_usb::control::OutResponse;
 use embassy_usb::{Builder, Config, DeviceStateHandler};
-use embassy_usb_hid::{HidReaderWriter, ReportId, RequestHandler, State};
-use futures::future::join;
 use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
 use {defmt_rtt as _, panic_probe as _};
 
@@ -67,7 +67,7 @@ async fn main(_spawner: Spawner) {
     );
 
     // Create classes on the builder.
-    let config = embassy_usb_hid::Config {
+    let config = embassy_usb::class::hid::Config {
         report_descriptor: KeyboardReport::desc(),
         request_handler: Some(&request_handler),
         poll_ms: 60,
@@ -78,7 +78,7 @@ async fn main(_spawner: Spawner) {
     // Build the builder.
     let mut usb = builder.build();
 
-    let remote_wakeup = Signal::new();
+    let remote_wakeup: Signal<CriticalSectionRawMutex, _> = Signal::new();
 
     // Run the USB device.
     let usb_fut = async {
diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs
index 7cd2ece1715c682662b71d35e4617a0f04fa1be7..4916a38d4fbf6d5edc2b9fa5289d8fa2a125a35c 100644
--- a/examples/nrf/src/bin/usb_hid_mouse.rs
+++ b/examples/nrf/src/bin/usb_hid_mouse.rs
@@ -1,19 +1,18 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use core::mem;
 
 use defmt::*;
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_nrf::usb::{Driver, PowerUsb};
 use embassy_nrf::{interrupt, pac};
 use embassy_time::{Duration, Timer};
+use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State};
 use embassy_usb::control::OutResponse;
 use embassy_usb::{Builder, Config};
-use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State};
-use futures::future::join;
 use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
 use {defmt_rtt as _, panic_probe as _};
 
@@ -60,7 +59,7 @@ async fn main(_spawner: Spawner) {
     );
 
     // Create classes on the builder.
-    let config = embassy_usb_hid::Config {
+    let config = embassy_usb::class::hid::Config {
         report_descriptor: MouseReport::desc(),
         request_handler: Some(&request_handler),
         poll_ms: 60,
diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs
index a68edb329331148880254f1d13e348ac36c1ba5f..7c9c4184b33ad339e706100a6346d8a9c3b4a578 100644
--- a/examples/nrf/src/bin/usb_serial.rs
+++ b/examples/nrf/src/bin/usb_serial.rs
@@ -1,18 +1,17 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use core::mem;
 
 use defmt::{info, panic};
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply};
 use embassy_nrf::{interrupt, pac};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::{Builder, Config};
-use embassy_usb_serial::{CdcAcmClass, State};
-use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
 #[embassy_executor::main]
diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs
index d62d7e520f25f8887d82f783b95aa9328ee73f43..93efc2fe67bc00d2a4359ab2ddafef9e2a7b3d85 100644
--- a/examples/nrf/src/bin/usb_serial_multitask.rs
+++ b/examples/nrf/src/bin/usb_serial_multitask.rs
@@ -1,6 +1,5 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use core::mem;
@@ -9,9 +8,9 @@ use defmt::{info, panic, unwrap};
 use embassy_executor::Spawner;
 use embassy_nrf::usb::{Driver, PowerUsb};
 use embassy_nrf::{interrupt, pac, peripherals};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::{Builder, Config, UsbDevice};
-use embassy_usb_serial::{CdcAcmClass, State};
 use static_cell::StaticCell;
 use {defmt_rtt as _, panic_probe as _};
 
diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml
index 18a92b0947725fe1e27b49ec73619e270d1f42aa..3c8f923e77d8f987c5da6264fde85e1ecda6aede 100644
--- a/examples/rp/Cargo.toml
+++ b/examples/rp/Cargo.toml
@@ -8,11 +8,10 @@ version = "0.1.0"
 embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] }
 embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
-embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] }
+embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
-embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] }
-embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"] }
+embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 
 defmt = "0.3"
 defmt-rtt = "0.3"
diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs
index 2df7f62f650278f321f82b4fb580cffb518589db..1057fe7fdfa6d456a4a017b543373f4e4e0c1a7e 100644
--- a/examples/rp/src/bin/usb_ethernet.rs
+++ b/examples/rp/src/bin/usb_ethernet.rs
@@ -1,6 +1,5 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use core::sync::atomic::{AtomicBool, Ordering};
@@ -14,8 +13,8 @@ use embassy_rp::usb::Driver;
 use embassy_rp::{interrupt, peripherals};
 use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
 use embassy_sync::channel::Channel;
+use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State};
 use embassy_usb::{Builder, Config, UsbDevice};
-use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State};
 use embedded_io::asynch::Write;
 use static_cell::StaticCell;
 use {defmt_rtt as _, panic_probe as _};
diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs
index 74be1f598719e14d9633c2e2ef3388d6e270bdce..b7d6493b4ec72ab78075ddeb58be68695b26f264 100644
--- a/examples/rp/src/bin/usb_serial.rs
+++ b/examples/rp/src/bin/usb_serial.rs
@@ -1,16 +1,15 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use defmt::{info, panic};
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_rp::interrupt;
 use embassy_rp::usb::{Driver, Instance};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::{Builder, Config};
-use embassy_usb_serial::{CdcAcmClass, State};
-use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
 #[embassy_executor::main]
diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml
index 33ac63db185bfb4588b8c52530d961812a166d52..e6553789a235fabeae44f399ecbceb423f1a67c8 100644
--- a/examples/stm32f1/Cargo.toml
+++ b/examples/stm32f1/Cargo.toml
@@ -9,7 +9,7 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature
 embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any"]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
-embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
+embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 
 defmt = "0.3"
 defmt-rtt = "0.3"
diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs
index a9c46068ff967d2e4ecb61199a9c7a0eaabd8f83..ad92cdeb26f499d590c76e3c8d21816110284e0d 100644
--- a/examples/stm32f1/src/bin/usb_serial.rs
+++ b/examples/stm32f1/src/bin/usb_serial.rs
@@ -4,15 +4,15 @@
 
 use defmt::{panic, *};
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_stm32::gpio::{Level, Output, Speed};
 use embassy_stm32::time::Hertz;
 use embassy_stm32::usb::{Driver, Instance};
 use embassy_stm32::{interrupt, Config};
 use embassy_time::{Duration, Timer};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
-use embassy_usb_serial::{CdcAcmClass, State};
-use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
 #[embassy_executor::main]
diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml
index 208f39080458ac7c4e86b704646f8389b92b22d7..f5b0b880c6dfa88b585fe70f9fa8f22b44ed25b7 100644
--- a/examples/stm32f3/Cargo.toml
+++ b/examples/stm32f3/Cargo.toml
@@ -9,8 +9,7 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature
 embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
-embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
-embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] }
+embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 
 defmt = "0.3"
 defmt-rtt = "0.3"
diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs
index d3702fc350d430632a6ea3b3c6fc0f2e452e7e0f..f6d27c8603a60395e2e73e4665b47fc4e09b788a 100644
--- a/examples/stm32f3/src/bin/usb_serial.rs
+++ b/examples/stm32f3/src/bin/usb_serial.rs
@@ -4,15 +4,15 @@
 
 use defmt::{panic, *};
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_stm32::gpio::{Level, Output, Speed};
 use embassy_stm32::time::mhz;
 use embassy_stm32::usb::{Driver, Instance};
 use embassy_stm32::{interrupt, Config};
 use embassy_time::{Duration, Timer};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
-use embassy_usb_serial::{CdcAcmClass, State};
-use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
 #[embassy_executor::main]
diff --git a/examples/stm32h7/src/bin/signal.rs b/examples/stm32h7/src/bin/signal.rs
index cc3e4e3caf0f85dc6b99807300d853dde590caa8..6d7c168d57133f526e7971a9a8e287f3c18e30f4 100644
--- a/examples/stm32h7/src/bin/signal.rs
+++ b/examples/stm32h7/src/bin/signal.rs
@@ -4,11 +4,12 @@
 
 use defmt::{info, unwrap};
 use embassy_executor::Spawner;
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 use embassy_sync::signal::Signal;
 use embassy_time::{Duration, Timer};
 use {defmt_rtt as _, panic_probe as _};
 
-static SIGNAL: Signal<u32> = Signal::new();
+static SIGNAL: Signal<CriticalSectionRawMutex, u32> = Signal::new();
 
 #[embassy_executor::task]
 async fn my_sending_task() {
diff --git a/examples/stm32l0/src/bin/lorawan.rs b/examples/stm32l0/src/bin/lorawan.rs
index 00ff67f3f3cbcbd7e1e5a4e5341495e9af74b350..27d7c29c2d94e1180d01e0e0ff456363cdd489d0 100644
--- a/examples/stm32l0/src/bin/lorawan.rs
+++ b/examples/stm32l0/src/bin/lorawan.rs
@@ -3,7 +3,6 @@
 #![no_main]
 #![macro_use]
 #![allow(dead_code)]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use embassy_executor::Spawner;
diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml
index c451bd22152edb1582fe713985efadf09cf14006..9ebab6476793c372117e6bb2819cc691c84290a1 100644
--- a/examples/stm32l5/Cargo.toml
+++ b/examples/stm32l5/Cargo.toml
@@ -11,10 +11,8 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature
 embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"]  }
 embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
-embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
-embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] }
-embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"] }
 embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] }
+embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 usbd-hid = "0.6.0"
 
 defmt = "0.3"
diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs
index 2c8706e418351439ed5a447e2e3a9ccc2dcdb6fe..4f36d3f5a44bdc9ab5670c6ba63d5cdf81f13fa5 100644
--- a/examples/stm32l5/src/bin/usb_ethernet.rs
+++ b/examples/stm32l5/src/bin/usb_ethernet.rs
@@ -1,6 +1,5 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use core::sync::atomic::{AtomicBool, Ordering};
@@ -16,8 +15,8 @@ use embassy_stm32::usb::Driver;
 use embassy_stm32::{interrupt, Config};
 use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
 use embassy_sync::channel::Channel;
+use embassy_usb::class::cdc_ncm::{CdcNcmClass, Receiver, Sender, State};
 use embassy_usb::{Builder, UsbDevice};
-use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State};
 use embedded_io::asynch::Write;
 use rand_core::RngCore;
 use static_cell::StaticCell;
diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs
index 7d763e7fd0580a67ebea1c5efb83c833016a5d92..d38ed74965280046ced3d7601b57c4d387c5f713 100644
--- a/examples/stm32l5/src/bin/usb_hid_mouse.rs
+++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs
@@ -1,18 +1,17 @@
 #![no_std]
 #![no_main]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use defmt::*;
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_stm32::rcc::*;
 use embassy_stm32::usb::Driver;
 use embassy_stm32::{interrupt, Config};
 use embassy_time::{Duration, Timer};
+use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State};
 use embassy_usb::control::OutResponse;
 use embassy_usb::Builder;
-use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State};
-use futures::future::join;
 use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
 use {defmt_rtt as _, panic_probe as _};
 
@@ -56,7 +55,7 @@ async fn main(_spawner: Spawner) {
     );
 
     // Create classes on the builder.
-    let config = embassy_usb_hid::Config {
+    let config = embassy_usb::class::hid::Config {
         report_descriptor: MouseReport::desc(),
         request_handler: Some(&request_handler),
         poll_ms: 60,
diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs
index b576a73534069e81dccf3957240a024e717d53de..7562a4e96bb58e2f07626662cd42ab086f780e48 100644
--- a/examples/stm32l5/src/bin/usb_serial.rs
+++ b/examples/stm32l5/src/bin/usb_serial.rs
@@ -4,13 +4,13 @@
 
 use defmt::{panic, *};
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_stm32::rcc::*;
 use embassy_stm32::usb::{Driver, Instance};
 use embassy_stm32::{interrupt, Config};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
 use embassy_usb::driver::EndpointError;
 use embassy_usb::Builder;
-use embassy_usb_serial::{CdcAcmClass, State};
-use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
 #[embassy_executor::main]
diff --git a/examples/stm32wl/src/bin/lorawan.rs b/examples/stm32wl/src/bin/lorawan.rs
index 9143e64dabd65ffa07fdc85f6a14eede34d5a971..7f34dd3060dff4b03d20b1a6179e5293acd08ff3 100644
--- a/examples/stm32wl/src/bin/lorawan.rs
+++ b/examples/stm32wl/src/bin/lorawan.rs
@@ -2,7 +2,6 @@
 #![no_main]
 #![macro_use]
 #![allow(dead_code)]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use embassy_executor::Spawner;
diff --git a/examples/stm32wl/src/bin/subghz.rs b/examples/stm32wl/src/bin/subghz.rs
index 8f674d796722e49c4341cd45483010c47bdd5aa2..32c8b55159d2b0156f59d2321e4cf1ea270b0295 100644
--- a/examples/stm32wl/src/bin/subghz.rs
+++ b/examples/stm32wl/src/bin/subghz.rs
@@ -2,7 +2,6 @@
 #![no_main]
 #![macro_use]
 #![allow(dead_code)]
-#![feature(generic_associated_types)]
 #![feature(type_alias_impl_trait)]
 
 use defmt::*;
@@ -13,6 +12,7 @@ use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
 use embassy_stm32::interrupt;
 use embassy_stm32::interrupt::{Interrupt, InterruptExt};
 use embassy_stm32::subghz::*;
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 use embassy_sync::signal::Signal;
 use {defmt_rtt as _, panic_probe as _};
 
@@ -65,7 +65,7 @@ async fn main(_spawner: Spawner) {
     let button = Input::new(p.PA0, Pull::Up);
     let mut pin = ExtiInput::new(button, p.EXTI0);
 
-    static IRQ_SIGNAL: Signal<()> = Signal::new();
+    static IRQ_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
     let radio_irq = interrupt::take!(SUBGHZ_RADIO);
     radio_irq.set_handler(|_| {
         IRQ_SIGNAL.signal(());
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index f5e342edc23a49859ade5a68cf73c794ee8d35e9..1ec19e58b1d87f01780b662a5862acae45cd193b 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,7 +1,7 @@
 # Before upgrading check that everything is available on all tier1 targets here:
 # https://rust-lang.github.io/rustup-components-history
 [toolchain]
-channel = "nightly-2022-08-16"
+channel = "nightly-2022-09-22"
 components = [ "rust-src", "rustfmt" ]
 targets = [
     "thumbv7em-none-eabi",
diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml
index 0330025e4b0814c80045f10ffbe7b6fc3ea2d1b6..9611db3a07de07c75947359b59fbf75a537a65d2 100644
--- a/tests/rp/.cargo/config.toml
+++ b/tests/rp/.cargo/config.toml
@@ -3,7 +3,7 @@ build-std = ["core"]
 build-std-features = ["panic_immediate_abort"]
 
 [target.'cfg(all(target_arch = "arm", target_os = "none"))']
-#runner = "teleprobe client run --target bluepill-stm32f103c8 --elf"
+#runner = "teleprobe client run --target rpi-pico --elf"
 runner = "teleprobe local run --chip RP2040 --elf"
 
 rustflags = [
diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml
index 70fd955572cfb3d2928deb1c78b064a28c712f26..5033737599203448af915d43540aa386cf9b405e 100644
--- a/tests/rp/Cargo.toml
+++ b/tests/rp/Cargo.toml
@@ -7,7 +7,8 @@ version = "0.1.0"
 embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] }
 embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] }
-embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits"]  }
+embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver"]  }
+embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
 
 defmt = "0.3.0"
 defmt-rtt = "0.3.0"
diff --git a/tests/rp/src/bin/gpio_async.rs b/tests/rp/src/bin/gpio_async.rs
index 1eeaac1f674dec7feea5c09733df64c0e9a29021..f20b8fcbda0e87b4d7db23ecd95311d13b6682c5 100644
--- a/tests/rp/src/bin/gpio_async.rs
+++ b/tests/rp/src/bin/gpio_async.rs
@@ -4,9 +4,9 @@
 
 use defmt::{assert, *};
 use embassy_executor::Spawner;
+use embassy_futures::join::join;
 use embassy_rp::gpio::{Input, Level, Output, Pull};
 use embassy_time::{Duration, Instant, Timer};
-use futures::future::join;
 use {defmt_rtt as _, panic_probe as _};
 
 #[embassy_executor::main]