diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml
index 3e984af..5e5d114 100644
--- a/bootloader/Cargo.toml
+++ b/bootloader/Cargo.toml
@@ -16,3 +16,4 @@ static_assertions = "1"
 [dependencies.va416xx-hal]
 version = "0.5"
 features = ["va41630", "defmt"]
+path = "../va416xx-hal"
diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs
index 1551e05..19eaa3b 100644
--- a/bootloader/src/main.rs
+++ b/bootloader/src/main.rs
@@ -10,11 +10,10 @@ use crc::{Crc, CRC_32_ISO_HDLC};
 use defmt_rtt as _;
 use panic_probe as _;
 use va416xx_hal::{
-    clock::{pll_setup_delay, ClkDivSel, ClkselSys},
+    clock::{pll_setup_delay, ClkDivSel, ClkselSys, ClockConfigurator},
     edac,
     nvm::Nvm,
     pac::{self, interrupt},
-    prelude::*,
     time::Hertz,
     wdt::Wdt,
 };
@@ -104,23 +103,20 @@ fn main() -> ! {
     dp.sysconfig.rom_prot().write(|w| unsafe { w.bits(1) });
     setup_edac(&mut dp.sysconfig);
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     let mut opt_wdt = OptWdt(None);
     if WITH_WDT {
         opt_wdt.0 = Some(Wdt::start(
-            &mut dp.sysconfig,
             dp.watch_dog,
             &clocks,
             WDT_FREQ_MS,
         ));
     }
 
-    let nvm = Nvm::new(&mut dp.sysconfig, dp.spi3, &clocks);
+    let nvm = Nvm::new(dp.spi3, &clocks);
 
     if FLASH_SELF {
         let mut first_four_bytes: [u8; 4] = [0; 4];
diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml
index 4c41c7f..ee96fb5 100644
--- a/examples/embassy/Cargo.toml
+++ b/examples/embassy/Cargo.toml
@@ -29,8 +29,8 @@ embassy-executor = { version = "0.7", features = [
   "executor-interrupt"
 ]}
 
-va416xx-hal = { version = "0.5", features = ["defmt"] }
-va416xx-embassy = { version = "0.1", default-features = false }
+va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["defmt"] }
+va416xx-embassy = { version = "0.1", path = "../../va416xx-embassy", default-features = false }
 
 [features]
 default = ["ticks-hz-1_000", "va416xx-embassy/irq-tim14-tim15"]
diff --git a/examples/embassy/src/bin/async-gpio.rs b/examples/embassy/src/bin/async-gpio.rs
index 56b3729..e62e536 100644
--- a/examples/embassy/src/bin/async-gpio.rs
+++ b/examples/embassy/src/bin/async-gpio.rs
@@ -5,6 +5,7 @@
 //! [CHECK_XXX_TO_XXX] constants to true.
 #![no_std]
 #![no_main]
+
 // Import panic provider.
 use panic_probe as _;
 // Import logger.
@@ -16,23 +17,19 @@ use embassy_sync::channel::{Receiver, Sender};
 use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
 use embassy_time::{Duration, Instant, Timer};
 use embedded_hal_async::digital::Wait;
-use va416xx_hal::clock::ClkgenExt;
-use va416xx_hal::gpio::{
-    on_interrupt_for_async_gpio_for_port, InputDynPinAsync, InputPinAsync, PinsB, PinsC, PinsD,
-    PinsE, PinsF, PinsG, Port,
-};
+use va416xx_hal::clock::ClockConfigurator;
+use va416xx_hal::gpio::asynch::{on_interrupt_for_async_gpio_for_port, InputPinAsync};
+use va416xx_hal::gpio::{Input, Output, PinState, Port};
+use va416xx_hal::pac::{self, interrupt};
+use va416xx_hal::pins::{PinsA, PinsB, PinsC, PinsD, PinsE, PinsF, PinsG};
 use va416xx_hal::time::Hertz;
-use va416xx_hal::{
-    gpio::{DynPin, PinsA},
-    pac::{self, interrupt},
-};
 
 const CHECK_PA0_TO_PA1: bool = true;
-const CHECK_PB0_TO_PB1: bool = true;
-const CHECK_PC14_TO_PC15: bool = true;
-const CHECK_PD2_TO_PD3: bool = true;
-const CHECK_PE0_TO_PE1: bool = true;
-const CHECK_PF0_TO_PF1: bool = true;
+const CHECK_PB0_TO_PB1: bool = false;
+const CHECK_PC14_TO_PC15: bool = false;
+const CHECK_PD2_TO_PD3: bool = false;
+const CHECK_PE0_TO_PE1: bool = false;
+const CHECK_PF0_TO_PF1: bool = false;
 
 #[derive(Clone, Copy)]
 pub struct GpioCmd {
@@ -70,41 +67,30 @@ static CHANNEL_PF0_TO_PF1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::ne
 async fn main(spawner: Spawner) {
     defmt::println!("-- VA416xx Async GPIO Demo --");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Initialize the systick interrupt & obtain the token to prove that we did
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     // Safety: Only called once here.
-    unsafe {
-        va416xx_embassy::init(
-            &mut dp.sysconfig,
-            &dp.irq_router,
-            dp.tim15,
-            dp.tim14,
-            &clocks,
-        )
-    };
+    va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
 
-    let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
-    let portb = PinsB::new(&mut dp.sysconfig, dp.portb);
-    let portc = PinsC::new(&mut dp.sysconfig, dp.portc);
-    let portd = PinsD::new(&mut dp.sysconfig, dp.portd);
-    let porte = PinsE::new(&mut dp.sysconfig, dp.porte);
-    let portf = PinsF::new(&mut dp.sysconfig, dp.portf);
+    let porta = PinsA::new(dp.porta);
+    let portb = PinsB::new(dp.portb);
+    let portc = PinsC::new(dp.portc);
+    let portd = PinsD::new(dp.portd);
+    let porte = PinsE::new(dp.porte);
+    let portf = PinsF::new(dp.portf);
 
-    let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
-    let mut led = portg.pg5.into_readable_push_pull_output();
+    let portg = PinsG::new(dp.portg);
+    let mut led = Output::new(portg.pg5, PinState::Low);
 
     if CHECK_PA0_TO_PA1 {
-        let out_pin = porta.pa0.into_readable_push_pull_output();
-        let in_pin = porta.pa1.into_floating_input();
-        let out_pin = out_pin.downgrade();
+        let out_pin = Output::new(porta.pa0, PinState::Low);
+        let in_pin = Input::new_floating(porta.pa1);
         let in_pin = InputPinAsync::new(in_pin).unwrap();
 
         spawner
@@ -119,10 +105,9 @@ async fn main(spawner: Spawner) {
     }
 
     if CHECK_PB0_TO_PB1 {
-        let out_pin = portb.pb0.into_readable_push_pull_output();
-        let in_pin = portb.pb1.into_floating_input();
-        let out_pin = out_pin.downgrade();
-        let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
+        let out_pin = Output::new(portb.pb0, PinState::Low);
+        let in_pin = Input::new_floating(portb.pb1);
+        let in_pin = InputPinAsync::new(in_pin).unwrap();
 
         spawner
             .spawn(output_task(
@@ -136,10 +121,9 @@ async fn main(spawner: Spawner) {
     }
 
     if CHECK_PC14_TO_PC15 {
-        let out_pin = portc.pc14.into_readable_push_pull_output();
-        let in_pin = portc.pc15.into_floating_input();
-        let out_pin = out_pin.downgrade();
-        let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
+        let out_pin = Output::new(portc.pc14, PinState::Low);
+        let in_pin = Input::new_floating(portc.pc15);
+        let in_pin = InputPinAsync::new(in_pin).unwrap();
         spawner
             .spawn(output_task(
                 "PC14 to PC15",
@@ -152,10 +136,9 @@ async fn main(spawner: Spawner) {
     }
 
     if CHECK_PD2_TO_PD3 {
-        let out_pin = portd.pd2.into_readable_push_pull_output();
-        let in_pin = portd.pd3.into_floating_input();
-        let out_pin = out_pin.downgrade();
-        let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
+        let out_pin = Output::new(portd.pd2, PinState::Low);
+        let in_pin = Input::new_floating(portd.pd3);
+        let in_pin = InputPinAsync::new(in_pin).unwrap();
         spawner
             .spawn(output_task(
                 "PD2 to PD3",
@@ -168,10 +151,9 @@ async fn main(spawner: Spawner) {
     }
 
     if CHECK_PE0_TO_PE1 {
-        let out_pin = porte.pe0.into_readable_push_pull_output();
-        let in_pin = porte.pe1.into_floating_input();
-        let out_pin = out_pin.downgrade();
-        let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
+        let out_pin = Output::new(porte.pe0, PinState::Low);
+        let in_pin = Input::new_floating(porte.pe1);
+        let in_pin = InputPinAsync::new(in_pin).unwrap();
         spawner
             .spawn(output_task(
                 "PE0 to PE1",
@@ -184,10 +166,9 @@ async fn main(spawner: Spawner) {
     }
 
     if CHECK_PF0_TO_PF1 {
-        let out_pin = portf.pf0.into_readable_push_pull_output();
-        let in_pin = portf.pf1.into_floating_input();
-        let out_pin = out_pin.downgrade();
-        let in_pin = InputDynPinAsync::new(in_pin.downgrade()).unwrap();
+        let out_pin = Output::new(portf.pf0, PinState::Low);
+        let in_pin = Input::new_floating(portf.pf1);
+        let in_pin = InputPinAsync::new(in_pin).unwrap();
         spawner
             .spawn(output_task(
                 "PF0 to PF1",
@@ -298,7 +279,7 @@ async fn check_pin_to_pin_async_ops(
 #[embassy_executor::task(pool_size = 8)]
 async fn output_task(
     ctx: &'static str,
-    mut out: DynPin,
+    mut out: Output,
     receiver: Receiver<'static, ThreadModeRawMutex, GpioCmd, 3>,
 ) {
     loop {
@@ -307,25 +288,25 @@ async fn output_task(
         match next_cmd.cmd_type {
             GpioCmdType::SetHigh => {
                 defmt::info!("{}: Set output high", ctx);
-                out.set_high().unwrap();
+                out.set_high();
             }
             GpioCmdType::SetLow => {
                 defmt::info!("{}: Set output low", ctx);
-                out.set_low().unwrap();
+                out.set_low();
             }
             GpioCmdType::RisingEdge => {
                 defmt::info!("{}: Rising edge", ctx);
-                if !out.is_low().unwrap() {
-                    out.set_low().unwrap();
+                if !out.is_set_low() {
+                    out.set_low();
                 }
-                out.set_high().unwrap();
+                out.set_high();
             }
             GpioCmdType::FallingEdge => {
                 defmt::info!("{}: Falling edge", ctx);
-                if !out.is_high().unwrap() {
-                    out.set_high().unwrap();
+                if !out.is_set_high() {
+                    out.set_high();
                 }
-                out.set_low().unwrap();
+                out.set_low();
             }
             GpioCmdType::CloseTask => {
                 defmt::info!("{}: Closing task", ctx);
diff --git a/examples/embassy/src/bin/async-uart-rx.rs b/examples/embassy/src/bin/async-uart-rx.rs
index 4f3447d..53b6fc4 100644
--- a/examples/embassy/src/bin/async-uart-rx.rs
+++ b/examples/embassy/src/bin/async-uart-rx.rs
@@ -27,8 +27,10 @@ use embedded_io::Write;
 use embedded_io_async::Read;
 use heapless::spsc::{Producer, Queue};
 use va416xx_hal::{
-    gpio::PinsG,
+    clock::ClockConfigurator,
+    gpio::{Output, PinState},
     pac::{self, interrupt},
+    pins::PinsG,
     prelude::*,
     time::Hertz,
     uart::{
@@ -46,34 +48,22 @@ static PRODUCER_UART_A: Mutex<RefCell<Option<Producer<u8, 256>>>> = Mutex::new(R
 async fn main(_spawner: Spawner) {
     defmt::println!("-- VA108xx Async UART RX Demo --");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Initialize the systick interrupt & obtain the token to prove that we did
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     // Safety: Only called once here.
-    unsafe {
-        va416xx_embassy::init(
-            &mut dp.sysconfig,
-            &dp.irq_router,
-            dp.tim15,
-            dp.tim14,
-            &clocks,
-        );
-    }
+    va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
 
-    let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
-    let mut led = portg.pg5.into_readable_push_pull_output();
+    let portg = PinsG::new(dp.portg);
+    let mut led = Output::new(portg.pg5, PinState::Low);
 
-    let tx = portg.pg0.into_funsel_1();
-    let rx = portg.pg1.into_funsel_1();
-
-    let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
+    let uarta =
+        uart::Uart::new(dp.uart0, portg.pg0, portg.pg1, &clocks, 115200.Hz().into()).unwrap();
 
     let (mut tx_uart_a, rx_uart_a) = uarta.split();
     let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split();
diff --git a/examples/embassy/src/bin/async-uart-tx.rs b/examples/embassy/src/bin/async-uart-tx.rs
index c110182..ee4bae9 100644
--- a/examples/embassy/src/bin/async-uart-tx.rs
+++ b/examples/embassy/src/bin/async-uart-tx.rs
@@ -21,8 +21,10 @@ use embassy_executor::Spawner;
 use embassy_time::{Duration, Instant, Ticker};
 use embedded_io_async::Write;
 use va416xx_hal::{
-    gpio::PinsG,
+    clock::ClockConfigurator,
+    gpio::{Output, PinState},
     pac::{self, interrupt},
+    pins::PinsG,
     prelude::*,
     time::Hertz,
     uart::{
@@ -44,34 +46,22 @@ const STR_LIST: &[&str] = &[
 async fn main(_spawner: Spawner) {
     defmt::println!("-- VA108xx Async UART TX Demo --");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Initialize the systick interrupt & obtain the token to prove that we did
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     // Safety: Only called once here.
-    unsafe {
-        va416xx_embassy::init(
-            &mut dp.sysconfig,
-            &dp.irq_router,
-            dp.tim15,
-            dp.tim14,
-            &clocks,
-        );
-    }
+    va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
 
-    let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
-    let mut led = portg.pg5.into_readable_push_pull_output();
+    let pinsg = PinsG::new(dp.portg);
+    let mut led = Output::new(pinsg.pg5, PinState::Low);
 
-    let tx = portg.pg0.into_funsel_1();
-    let rx = portg.pg1.into_funsel_1();
-
-    let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
+    let uarta =
+        uart::Uart::new(dp.uart0, pinsg.pg0, pinsg.pg1, &clocks, 115200.Hz().into()).unwrap();
     let (tx, _rx) = uarta.split();
     let mut async_tx = TxAsync::new(tx);
     let mut ticker = Ticker::every(Duration::from_secs(1));
diff --git a/examples/embassy/src/bin/uart-echo-with-irq.rs b/examples/embassy/src/bin/uart-echo-with-irq.rs
index e8c6f22..769a471 100644
--- a/examples/embassy/src/bin/uart-echo-with-irq.rs
+++ b/examples/embassy/src/bin/uart-echo-with-irq.rs
@@ -27,15 +27,15 @@ use ringbuf::{
     StaticRb,
 };
 use va416xx_hal::{
-    gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
+    clock::ClockConfigurator,
+    gpio::{Output, PinState},
     pac::{self, interrupt},
-    prelude::*,
+    pins::PinsG,
     time::Hertz,
     uart,
 };
 
-pub type SharedUart =
-    Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithInterrupt<pac::Uart0>>>>;
+pub type SharedUart = Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithInterrupt>>>;
 static RX: SharedUart = Mutex::new(RefCell::new(None));
 
 const BAUDRATE: u32 = 115200;
@@ -54,39 +54,27 @@ static RINGBUF: SharedRingBuf = Mutex::new(RefCell::new(None));
 async fn main(spawner: Spawner) {
     defmt::println!("VA416xx UART-Embassy Example");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Initialize the systick interrupt & obtain the token to prove that we did
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     // Safety: Only called once here.
-    unsafe {
-        va416xx_embassy::init(
-            &mut dp.sysconfig,
-            &dp.irq_router,
-            dp.tim15,
-            dp.tim14,
-            &clocks,
-        )
-    };
+    va416xx_embassy::init(dp.tim15, dp.tim14, &clocks);
 
-    let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
-
-    let tx = portg.pg0.into_funsel_1();
-    let rx = portg.pg1.into_funsel_1();
+    let portg = PinsG::new(dp.portg);
 
     let uart0 = uart::Uart::new(
-        &mut dp.sysconfig,
         dp.uart0,
-        (tx, rx),
-        Hertz::from_raw(BAUDRATE),
+        portg.pg0,
+        portg.pg1,
         &clocks,
-    );
+        Hertz::from_raw(BAUDRATE).into(),
+    )
+    .unwrap();
     let (mut tx, rx) = uart0.split();
     let mut rx = rx.into_rx_with_irq();
     rx.start();
@@ -97,7 +85,7 @@ async fn main(spawner: Spawner) {
         static_rb.borrow_mut().replace(StaticRb::default());
     });
 
-    let led = portg.pg5.into_readable_push_pull_output();
+    let led = Output::new(portg.pg5, PinState::Low);
     let mut ticker = Ticker::every(Duration::from_millis(50));
     let mut processing_buf: [u8; RING_BUF_SIZE] = [0; RING_BUF_SIZE];
     let mut read_bytes = 0;
@@ -117,7 +105,7 @@ async fn main(spawner: Spawner) {
 }
 
 #[embassy_executor::task]
-async fn blinky(mut led: Pin<PG5, OutputReadablePushPull>) {
+async fn blinky(mut led: Output) {
     let mut ticker = Ticker::every(Duration::from_millis(500));
     loop {
         led.toggle();
diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs
index f89d348..c6d3da0 100644
--- a/examples/embassy/src/main.rs
+++ b/examples/embassy/src/main.rs
@@ -8,7 +8,13 @@ use defmt_rtt as _;
 use embassy_example::EXTCLK_FREQ;
 use embassy_executor::Spawner;
 use embassy_time::{Duration, Instant, Ticker};
-use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
+use va416xx_hal::{
+    clock::ClockConfigurator,
+    gpio::{Output, PinState},
+    pac,
+    pins::PinsG,
+    time::Hertz,
+};
 
 cfg_if::cfg_if! {
     if #[cfg(feature = "custom-irqs")] {
@@ -22,40 +28,32 @@ cfg_if::cfg_if! {
 async fn main(_spawner: Spawner) {
     defmt::println!("VA416xx Embassy Demo");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Initialize the systick interrupt & obtain the token to prove that we did
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     // Safety: Only called once here.
-    unsafe {
-        cfg_if::cfg_if! {
-            if #[cfg(not(feature = "custom-irqs"))] {
-                va416xx_embassy::init(
-                    &mut dp.sysconfig,
-                    &dp.irq_router,
-                    dp.tim15,
-                    dp.tim14,
-                    &clocks
-                );
-            } else {
-                va416xx_embassy::init(
-                    &mut dp.sysconfig,
-                    &dp.irq_router,
-                    dp.tim12,
-                    dp.tim11,
-                    &clocks
-                );
-            }
+    cfg_if::cfg_if! {
+        if #[cfg(not(feature = "custom-irqs"))] {
+            va416xx_embassy::init(
+                dp.tim15,
+                dp.tim14,
+                &clocks
+            );
+        } else {
+            va416xx_embassy::init(
+                dp.tim12,
+                dp.tim11,
+                &clocks
+            );
         }
     }
-    let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
-    let mut led = portg.pg5.into_readable_push_pull_output();
+    let pinsg = PinsG::new(dp.portg);
+    let mut led = Output::new(pinsg.pg5, PinState::Low);
     let mut ticker = Ticker::every(Duration::from_secs(1));
     loop {
         ticker.next().await;
diff --git a/examples/rtic/Cargo.toml b/examples/rtic/Cargo.toml
index f04fd4b..e299403 100644
--- a/examples/rtic/Cargo.toml
+++ b/examples/rtic/Cargo.toml
@@ -12,7 +12,7 @@ defmt = "1"
 panic-probe = { version = "1", features = ["defmt"] }
 rtic-sync = { version = "1.3", features = ["defmt-03"] }
 
-va416xx-hal = { version = "0.5", features = ["va41630"] }
+va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["va41630"] }
 
 [dependencies.rtic]
 version = "2"
diff --git a/examples/rtic/src/main.rs b/examples/rtic/src/main.rs
index d2bbc6c..5e2e9d7 100644
--- a/examples/rtic/src/main.rs
+++ b/examples/rtic/src/main.rs
@@ -17,14 +17,15 @@ mod app {
     use rtic_monotonics::systick::prelude::*;
     use rtic_monotonics::Monotonic;
     use va416xx_hal::{
-        gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
+        clock::ClockConfigurator,
+        gpio::{Output, PinState},
         pac,
-        prelude::*,
+        pins::PinsG,
     };
 
     #[local]
     struct Local {
-        led: Pin<PG5, OutputReadablePushPull>,
+        led: Output,
     }
 
     #[shared]
@@ -33,19 +34,16 @@ mod app {
     rtic_monotonics::systick_monotonic!(Mono, 1_000);
 
     #[init]
-    fn init(mut cx: init::Context) -> (Shared, Local) {
+    fn init(cx: init::Context) -> (Shared, Local) {
         defmt::println!("-- Vorago RTIC example application --");
         // Use the external clock connected to XTAL_N.
-        let clocks = cx
-            .device
-            .clkgen
-            .constrain()
+        let clocks = ClockConfigurator::new(cx.device.clkgen)
             .xtal_n_clk_with_src_freq(EXTCLK_FREQ)
-            .freeze(&mut cx.device.sysconfig)
+            .freeze()
             .unwrap();
         Mono::start(cx.core.SYST, clocks.sysclk().raw());
-        let portg = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
-        let led = portg.pg5.into_readable_push_pull_output();
+        let pinsg = PinsG::new(cx.device.portg);
+        let led = Output::new(pinsg.pg5, PinState::Low);
         blinky::spawn().ok();
         (Shared {}, Local { led })
     }
diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml
index c2dbf7a..c3cc4c1 100644
--- a/examples/simple/Cargo.toml
+++ b/examples/simple/Cargo.toml
@@ -17,7 +17,7 @@ embedded-io = "0.6"
 panic-halt = "1"
 accelerometer = "0.12"
 
-va416xx-hal = { version = "0.5", features = ["va41630", "defmt"] }
+va416xx-hal = { version = "0.5", path = "../../va416xx-hal", features = ["va41630", "defmt"] }
 
 [dependencies.vorago-peb1]
 path = "../../vorago-peb1"
diff --git a/examples/simple/examples/adc.rs b/examples/simple/examples/adc.rs
index 3321a1f..fe11eff 100644
--- a/examples/simple/examples/adc.rs
+++ b/examples/simple/examples/adc.rs
@@ -12,8 +12,8 @@ use embedded_hal::delay::DelayNs;
 use simple_examples::peb1;
 use va416xx_hal::{
     adc::{Adc, ChannelSelect, ChannelValue, MultiChannelSelect},
+    clock::ClockConfigurator,
     pac,
-    prelude::*,
     timer::CountdownTimer,
 };
 
@@ -24,17 +24,15 @@ const ENABLE_BUF_PRINTOUT: bool = false;
 fn main() -> ! {
     defmt::println!("VA416xx ADC example");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
 
-    let adc = Adc::new_with_channel_tag(&mut dp.sysconfig, dp.adc, &clocks);
-    let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
+    let adc = Adc::new_with_channel_tag(dp.adc, &clocks);
+    let mut delay = CountdownTimer::new(dp.tim0, &clocks);
     let mut read_buf: [ChannelValue; 8] = [ChannelValue::default(); 8];
     loop {
         let single_value = adc
@@ -70,6 +68,6 @@ fn main() -> ! {
         assert_eq!(read_buf[0].channel(), ChannelSelect::AnIn0);
         assert_eq!(read_buf[1].channel(), ChannelSelect::AnIn2);
         assert_eq!(read_buf[2].channel(), ChannelSelect::TempSensor);
-        delay_provider.delay_ms(500);
+        delay.delay_ms(500);
     }
 }
diff --git a/examples/simple/examples/blinky.rs b/examples/simple/examples/blinky.rs
index 57bb4cc..2c29855 100644
--- a/examples/simple/examples/blinky.rs
+++ b/examples/simple/examples/blinky.rs
@@ -8,15 +8,19 @@ use panic_probe as _;
 use defmt_rtt as _;
 
 use cortex_m_rt::entry;
-use va416xx_hal::{gpio::PinsG, pac};
+use va416xx_hal::{
+    gpio::{Output, PinState},
+    pac,
+    pins::PinsG,
+};
 
 #[entry]
 fn main() -> ! {
     defmt::println!("VA416xx HAL blinky example");
 
-    let mut dp = pac::Peripherals::take().unwrap();
-    let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
-    let mut led = portg.pg5.into_readable_push_pull_output();
+    let dp = pac::Peripherals::take().unwrap();
+    let portg = PinsG::new(dp.portg);
+    let mut led = Output::new(portg.pg5, PinState::Low);
     loop {
         cortex_m::asm::delay(2_000_000);
         led.toggle();
diff --git a/examples/simple/examples/dac-adc.rs b/examples/simple/examples/dac-adc.rs
index 411fd80..3d326fe 100644
--- a/examples/simple/examples/dac-adc.rs
+++ b/examples/simple/examples/dac-adc.rs
@@ -10,7 +10,7 @@ use defmt_rtt as _;
 use cortex_m_rt::entry;
 use embedded_hal::delay::DelayNs;
 use simple_examples::peb1;
-use va416xx_hal::{adc::Adc, dac::Dac, pac, prelude::*, timer::CountdownTimer};
+use va416xx_hal::{adc::Adc, clock::ClockConfigurator, dac::Dac, pac, timer::CountdownTimer};
 
 const DAC_INCREMENT: u16 = 256;
 
@@ -30,18 +30,15 @@ const APP_MODE: AppMode = AppMode::DacAndAdc;
 fn main() -> ! {
     defmt::println!("VA416xx DAC/ADC example");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     let mut dac = None;
     if APP_MODE == AppMode::DacOnly || APP_MODE == AppMode::DacAndAdc {
         dac = Some(Dac::new(
-            &mut dp.sysconfig,
             dp.dac0,
             va416xx_hal::dac::DacSettling::Apb2Times100,
             &clocks,
@@ -49,12 +46,12 @@ fn main() -> ! {
     }
     let mut adc = None;
     if APP_MODE == AppMode::AdcOnly || APP_MODE == AppMode::DacAndAdc {
-        adc = Some(Adc::new(&mut dp.sysconfig, dp.adc, &clocks));
+        adc = Some(Adc::new(dp.adc, &clocks));
     }
-    let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
+    let mut delay_provider = CountdownTimer::new(dp.tim0, &clocks);
     let mut current_val = 0;
     loop {
-        if let Some(dac) = &dac {
+        if let Some(dac) = &mut dac {
             defmt::info!("loading DAC with value {}", current_val);
             dac.load_and_trigger_manually(current_val)
                 .expect("loading DAC value failed");
diff --git a/examples/simple/examples/dma.rs b/examples/simple/examples/dma.rs
index e49df7a..bdd36aa 100644
--- a/examples/simple/examples/dma.rs
+++ b/examples/simple/examples/dma.rs
@@ -6,6 +6,7 @@
 use panic_probe as _;
 // Import logger.
 use defmt_rtt as _;
+use va416xx_hal::clock::ClockConfigurator;
 
 use core::cell::Cell;
 
@@ -15,11 +16,8 @@ use embedded_hal::delay::DelayNs;
 use simple_examples::peb1;
 use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
 use va416xx_hal::irq_router::enable_and_init_irq_router;
+use va416xx_hal::pac::{self, interrupt};
 use va416xx_hal::timer::CountdownTimer;
-use va416xx_hal::{
-    pac::{self, interrupt},
-    prelude::*,
-};
 
 static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
 static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
@@ -40,26 +38,23 @@ static mut DMA_DEST_BUF: [u16; 36] = [0; 36];
 fn main() -> ! {
     defmt::println!("VA416xx DMA example");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
-    enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
+    enable_and_init_irq_router();
     // Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
     // statically.
     let dma = Dma::new(
-        &mut dp.sysconfig,
         dp.dma,
         DmaCfg::default(),
         core::ptr::addr_of_mut!(DMA_CTRL_BLOCK),
     )
     .expect("error creating DMA");
     let (mut dma0, _, _, _) = dma.split();
-    let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
+    let mut delay_ms = CountdownTimer::new(dp.tim0, &clocks);
     let mut src_buf_8_bit: [u8; 65] = [0; 65];
     let mut dest_buf_8_bit: [u8; 65] = [0; 65];
     let mut src_buf_32_bit: [u32; 17] = [0; 17];
@@ -90,7 +85,7 @@ fn transfer_example_8_bit(
     src_buf: &mut [u8; 65],
     dest_buf: &mut [u8; 65],
     dma0: &mut DmaChannel,
-    delay_ms: &mut CountdownTimer<pac::Tim0>,
+    delay: &mut CountdownTimer,
 ) {
     (0..64).for_each(|i| {
         src_buf[i] = i as u8;
@@ -132,7 +127,7 @@ fn transfer_example_8_bit(
             defmt::info!("8-bit transfer done");
             break;
         }
-        delay_ms.delay_ms(1);
+        delay.delay_ms(1);
     }
     (0..64).for_each(|i| {
         assert_eq!(dest_buf[i], i as u8);
@@ -142,7 +137,7 @@ fn transfer_example_8_bit(
     dest_buf.fill(0);
 }
 
-fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<pac::Tim0>) {
+fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer) {
     let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..33]) };
     unsafe {
         // Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
@@ -207,7 +202,7 @@ fn transfer_example_32_bit(
     src_buf: &mut [u32; 17],
     dest_buf: &mut [u32; 17],
     dma0: &mut DmaChannel,
-    delay_ms: &mut CountdownTimer<pac::Tim0>,
+    delay_ms: &mut CountdownTimer,
 ) {
     // Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
     (0..16).for_each(|i| {
diff --git a/examples/simple/examples/peb1-accelerometer.rs b/examples/simple/examples/peb1-accelerometer.rs
index 533c8de..3e19cf2 100644
--- a/examples/simple/examples/peb1-accelerometer.rs
+++ b/examples/simple/examples/peb1-accelerometer.rs
@@ -12,10 +12,11 @@ use cortex_m_rt::entry;
 use embedded_hal::delay::DelayNs;
 use simple_examples::peb1;
 use va416xx_hal::{
+    clock::ClockConfigurator,
     i2c,
     pac::{self},
     prelude::*,
-    pwm::CountdownTimer,
+    timer::CountdownTimer,
 };
 use vorago_peb1::lis2dh12::{self, detect_i2c_addr, FullScale, Odr};
 
@@ -31,21 +32,18 @@ fn main() -> ! {
     let mut dp = pac::Peripherals::take().unwrap();
     defmt::println!("-- Vorago PEB1 accelerometer example --");
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
     let mut i2c_master = i2c::I2cMaster::new(
         dp.i2c0,
-        &mut dp.sysconfig,
-        i2c::MasterConfig::default(),
         &clocks,
+        i2c::MasterConfig::default(),
         i2c::I2cSpeed::Regular100khz,
     )
     .expect("creating I2C master failed");
-    let mut delay_provider = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
+    let mut delay_provider = CountdownTimer::new(dp.tim1, &clocks);
     // Detect the I2C address of the accelerometer by scanning all possible values.
     let slave_addr = detect_i2c_addr(&mut i2c_master).expect("detecting I2C address failed");
     // Create the accelerometer driver using the PEB1 BSP.
@@ -53,7 +51,7 @@ fn main() -> ! {
         .expect("creating accelerometer driver failed");
     let device_id = accelerometer.get_device_id().unwrap();
     accelerometer
-        .set_mode(lis2dh12::reg::Mode::Normal)
+        .set_mode(lis2dh12::Mode::Normal)
         .expect("setting mode failed");
     accelerometer
         .set_odr(Odr::Hz100)
@@ -65,7 +63,7 @@ fn main() -> ! {
     accelerometer
         .enable_temp(true)
         .expect("enabling temperature sensor failed");
-    rprintln!("Device ID: 0x{:02X}", device_id);
+    defmt::info!("Device ID: 0x{:02X}", device_id);
     // Start reading the accelerometer periodically.
     loop {
         let temperature = accelerometer
@@ -76,13 +74,25 @@ fn main() -> ! {
                 let value = accelerometer
                     .accel_norm()
                     .expect("reading normalized accelerometer data failed");
-                rprintln!("Accel Norm F32x3: {:.06?} | Temp {} °C", value, temperature);
+                defmt::info!(
+                    "Accel Norm F32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
+                    value.x,
+                    value.y,
+                    value.z,
+                    temperature
+                );
             }
             DisplayMode::Raw => {
                 let value_raw = accelerometer
                     .accel_raw()
                     .expect("reading raw accelerometer data failed");
-                rprintln!("Accel Raw F32x3: {:?} | Temp {} °C", value_raw, temperature);
+                defmt::info!(
+                    "Accel Raw I32x3 {{ x: {:05}, y: {:05}, z:{:05}}} | Temp {} °C",
+                    value_raw.x,
+                    value_raw.y,
+                    value_raw.z,
+                    temperature
+                );
             }
         }
         delay_provider.delay_ms(100);
diff --git a/examples/simple/examples/pwm.rs b/examples/simple/examples/pwm.rs
index e439735..48d3e39 100644
--- a/examples/simple/examples/pwm.rs
+++ b/examples/simple/examples/pwm.rs
@@ -1,4 +1,6 @@
 //! Simple PWM example
+//!
+//! Outputs a PWM waveform on pin PG2.
 #![no_main]
 #![no_std]
 
@@ -10,41 +12,34 @@ use panic_probe as _;
 use defmt_rtt as _;
 use simple_examples::peb1;
 use va416xx_hal::{
-    gpio::PinsA,
+    clock::ClockConfigurator,
     pac,
+    pins::{PinsA, PinsG},
     prelude::*,
-    pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin},
+    pwm::{get_duty_from_percent, PwmA, PwmB, PwmPin},
     timer::CountdownTimer,
 };
 
 #[entry]
 fn main() -> ! {
     defmt::println!("-- VA108xx PWM example application--");
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
 
-    let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
-    let mut pwm = pwm::PwmPin::new(
-        (pinsa.pa3.into_funsel_1(), dp.tim3),
-        &mut dp.sysconfig,
-        &clocks,
-        10.Hz(),
-    );
-    let mut delay_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
+    let pinsg = PinsG::new(dp.portg);
+    let mut pwm = PwmPin::new(pinsg.pg2, dp.tim9, &clocks, 10.Hz()).unwrap();
+    let mut delay_timer = CountdownTimer::new(dp.tim0, &clocks);
     let mut current_duty_cycle = 0.0;
     pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
         .unwrap();
     pwm.enable();
 
     // Delete type information, increased code readibility for the rest of the code
-    let mut reduced_pin = ReducedPwmPin::from(pwm);
     loop {
         let mut counter = 0;
         // Increase duty cycle continuously
@@ -56,8 +51,7 @@ fn main() -> ! {
                 defmt::info!("current duty cycle: {}", current_duty_cycle);
             }
 
-            reduced_pin
-                .set_duty_cycle(get_duty_from_percent(current_duty_cycle))
+            pwm.set_duty_cycle(get_duty_from_percent(current_duty_cycle))
                 .unwrap();
         }
 
@@ -66,7 +60,7 @@ fn main() -> ! {
         current_duty_cycle = 0.0;
         let mut upper_limit = 1.0;
         let mut lower_limit = 0.0;
-        let mut pwmb: ReducedPwmPin<PwmB> = ReducedPwmPin::from(reduced_pin);
+        let mut pwmb: PwmPin<PwmB> = PwmPin::from(pwm);
         pwmb.set_pwmb_lower_limit(get_duty_from_percent(lower_limit));
         pwmb.set_pwmb_upper_limit(get_duty_from_percent(upper_limit));
         while lower_limit < 0.5 {
@@ -78,6 +72,6 @@ fn main() -> ! {
             defmt::info!("Lower limit: {}", pwmb.pwmb_lower_limit());
             defmt::info!("Upper limit: {}", pwmb.pwmb_upper_limit());
         }
-        reduced_pin = ReducedPwmPin::<PwmA>::from(pwmb);
+        pwm = PwmPin::<PwmA>::from(pwmb);
     }
 }
diff --git a/examples/simple/examples/rtt-log.rs b/examples/simple/examples/rtt-log.rs
deleted file mode 100644
index bbf8ac7..0000000
--- a/examples/simple/examples/rtt-log.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-// Code to test RTT logger functionality.
-#![no_main]
-#![no_std]
-
-// Import panic provider.
-use panic_probe as _;
-// Import logger.
-use defmt_rtt as _;
-
-use cortex_m_rt::entry;
-use va416xx_hal::pac;
-
-// Mask for the LED
-const LED_PG5: u32 = 1 << 5;
-
-#[entry]
-fn main() -> ! {
-    defmt::println!("VA416xx RTT Demo");
-    let dp = pac::Peripherals::take().unwrap();
-    // Enable all peripheral clocks
-    dp.sysconfig
-        .peripheral_clk_enable()
-        .modify(|_, w| unsafe { w.bits(0xffffffff) });
-    dp.portg.dir().modify(|_, w| unsafe { w.bits(LED_PG5) });
-    dp.portg
-        .datamask()
-        .modify(|_, w| unsafe { w.bits(LED_PG5) });
-
-    let mut counter = 0;
-    loop {
-        defmt::info!("{}: Hello, world!", counter);
-        // Still toggle LED. If there are issues with the RTT log, the LED
-        // blinking ensures that the application is actually running.
-        dp.portg.togout().write(|w| unsafe { w.bits(LED_PG5) });
-        counter += 1;
-        cortex_m::asm::delay(10_000_000);
-    }
-}
diff --git a/examples/simple/examples/spi.rs b/examples/simple/examples/spi.rs
index 26038d0..7196999 100644
--- a/examples/simple/examples/spi.rs
+++ b/examples/simple/examples/spi.rs
@@ -3,6 +3,7 @@
 //! If you do not use the loopback mode, MOSI and MISO need to be tied together on the board.
 #![no_main]
 #![no_std]
+use embedded_hal::delay::DelayNs;
 // Import panic provider.
 use panic_probe as _;
 // Import logger.
@@ -11,11 +12,12 @@ use defmt_rtt as _;
 use cortex_m_rt::entry;
 use embedded_hal::spi::{Mode, SpiBus, MODE_0};
 use simple_examples::peb1;
+use va416xx_hal::clock::ClockConfigurator;
 use va416xx_hal::spi::{Spi, SpiClkConfig};
+use va416xx_hal::timer::CountdownTimer;
 use va416xx_hal::{
-    gpio::{PinsB, PinsC},
     pac,
-    prelude::*,
+    pins::{PinsB, PinsC},
     spi::SpiConfig,
     time::Hertz,
 };
@@ -37,29 +39,20 @@ const FILL_WORD: u8 = 0x0f;
 #[entry]
 fn main() -> ! {
     defmt::println!("-- VA108xx SPI example application--");
-    let cp = cortex_m::Peripherals::take().unwrap();
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
-    let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
+    let mut delay = CountdownTimer::new(dp.tim1, &clocks);
 
-    let pins_b = PinsB::new(&mut dp.sysconfig, dp.portb);
-    let pins_c = PinsC::new(&mut dp.sysconfig, dp.portc);
-    // Configure SPI0 pins.
-    let (sck, miso, mosi) = (
-        pins_b.pb15.into_funsel_1(),
-        pins_c.pc0.into_funsel_1(),
-        pins_c.pc1.into_funsel_1(),
-    );
+    let pins_b = PinsB::new(dp.portb);
+    let pins_c = PinsC::new(dp.portc);
 
     let mut spi_cfg = SpiConfig::default()
         .clk_cfg(
-            SpiClkConfig::from_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
+            SpiClkConfig::from_clks(&clocks, Hertz::from_raw(SPI_SPEED_KHZ))
                 .expect("invalid target clock"),
         )
         .mode(SPI_MODE)
@@ -68,13 +61,7 @@ fn main() -> ! {
         spi_cfg = spi_cfg.loopback(true)
     }
     // Create SPI peripheral.
-    let mut spi0 = Spi::new(
-        &mut dp.sysconfig,
-        &clocks,
-        dp.spi0,
-        (sck, miso, mosi),
-        spi_cfg,
-    );
+    let mut spi0 = Spi::new(dp.spi0, (pins_b.pb15, pins_c.pc0, pins_c.pc1), spi_cfg).unwrap();
     spi0.set_fill_word(FILL_WORD);
     loop {
         let tx_buf: [u8; 4] = [1, 2, 3, 0];
@@ -95,6 +82,6 @@ fn main() -> ! {
         spi0.transfer(&mut rx_buf, &tx_buf)
             .expect("SPI transfer failed");
         assert_eq!(rx_buf, [1, 2, 3, 0]);
-        delay_sysclk.delay_ms(500);
+        delay.delay_ms(500);
     }
 }
diff --git a/examples/simple/examples/timer-ticks.rs b/examples/simple/examples/timer-ticks.rs
index 353bf34..a9cda46 100644
--- a/examples/simple/examples/timer-ticks.rs
+++ b/examples/simple/examples/timer-ticks.rs
@@ -6,16 +6,16 @@ use panic_probe as _;
 // Import logger.
 use defmt_rtt as _;
 
-use core::cell::Cell;
+use core::sync::atomic::{AtomicU32, Ordering};
 use cortex_m::asm;
 use cortex_m_rt::entry;
-use critical_section::Mutex;
 use simple_examples::peb1;
 use va416xx_hal::{
+    clock::ClockConfigurator,
     irq_router::enable_and_init_irq_router,
     pac::{self, interrupt},
     prelude::*,
-    timer::{default_ms_irq_handler, set_up_ms_tick, CountdownTimer, MS_COUNTER},
+    timer::CountdownTimer,
 };
 
 #[allow(dead_code)]
@@ -24,32 +24,33 @@ enum LibType {
     Hal,
 }
 
-static SEC_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
+static MS_COUNTER: AtomicU32 = AtomicU32::new(0);
+static SEC_COUNTER: AtomicU32 = AtomicU32::new(0);
 
 #[entry]
 fn main() -> ! {
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
     let mut last_ms = 0;
     defmt::println!("-- Vorago system ticks using timers --");
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
-    enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
-    let _ = set_up_ms_tick(&mut dp.sysconfig, dp.tim0, &clocks);
-    let mut second_timer = CountdownTimer::new(&mut dp.sysconfig, dp.tim1, &clocks);
-    second_timer.listen();
+    enable_and_init_irq_router();
+    let mut ms_timer = CountdownTimer::new(dp.tim0, &clocks);
+    ms_timer.enable_interrupt(true);
+    ms_timer.start(1.Hz());
+    let mut second_timer = CountdownTimer::new(dp.tim1, &clocks);
+    second_timer.enable_interrupt(true);
     second_timer.start(1.Hz());
     loop {
-        let current_ms = critical_section::with(|cs| MS_COUNTER.borrow(cs).get());
+        let current_ms = MS_COUNTER.load(Ordering::Relaxed);
         if current_ms >= last_ms + 1000 {
             // To prevent drift.
             last_ms += 1000;
             defmt::info!("MS counter: {}", current_ms);
-            let second = critical_section::with(|cs| SEC_COUNTER.borrow(cs).get());
+            let second = SEC_COUNTER.load(Ordering::Relaxed);
             defmt::info!("Second counter: {}", second);
         }
         asm::delay(1000);
@@ -59,15 +60,11 @@ fn main() -> ! {
 #[interrupt]
 #[allow(non_snake_case)]
 fn TIM0() {
-    default_ms_irq_handler()
+    MS_COUNTER.fetch_add(1, Ordering::Relaxed);
 }
 
 #[interrupt]
 #[allow(non_snake_case)]
 fn TIM1() {
-    critical_section::with(|cs| {
-        let mut sec = SEC_COUNTER.borrow(cs).get();
-        sec += 1;
-        SEC_COUNTER.borrow(cs).set(sec);
-    });
+    SEC_COUNTER.fetch_add(1, Ordering::Relaxed);
 }
diff --git a/examples/simple/examples/uart.rs b/examples/simple/examples/uart.rs
index f7c722f..e2308e9 100644
--- a/examples/simple/examples/uart.rs
+++ b/examples/simple/examples/uart.rs
@@ -11,35 +11,33 @@ use cortex_m_rt::entry;
 use embedded_hal_nb::serial::Read;
 use embedded_io::Write;
 use simple_examples::peb1;
-use va416xx_hal::clock::ClkgenExt;
+use va416xx_hal::clock::ClockConfigurator;
+use va416xx_hal::pins::PinsG;
 use va416xx_hal::time::Hertz;
-use va416xx_hal::{gpio::PinsG, pac, uart};
+use va416xx_hal::{pac, uart};
 
 #[entry]
 fn main() -> ! {
     defmt::println!("-- VA416xx UART example application--");
 
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
 
-    let gpiob = PinsG::new(&mut dp.sysconfig, dp.portg);
-    let tx = gpiob.pg0.into_funsel_1();
-    let rx = gpiob.pg1.into_funsel_1();
+    let gpiog = PinsG::new(dp.portg);
 
     let uart0 = uart::Uart::new(
-        &mut dp.sysconfig,
         dp.uart0,
-        (tx, rx),
-        Hertz::from_raw(115200),
+        gpiog.pg0,
+        gpiog.pg1,
         &clocks,
-    );
+        Hertz::from_raw(115200).into(),
+    )
+    .unwrap();
     let (mut tx, mut rx) = uart0.split();
     writeln!(tx, "Hello World\n\r").unwrap();
     loop {
diff --git a/examples/simple/examples/wdt.rs b/examples/simple/examples/wdt.rs
index f9d749a..3fa3119 100644
--- a/examples/simple/examples/wdt.rs
+++ b/examples/simple/examples/wdt.rs
@@ -5,17 +5,16 @@
 use panic_probe as _;
 // Import logger.
 use defmt_rtt as _;
+use va416xx_hal::clock::ClockConfigurator;
 
-use core::cell::Cell;
+use core::sync::atomic::{AtomicU32, Ordering};
 use cortex_m_rt::entry;
-use critical_section::Mutex;
 use simple_examples::peb1;
 use va416xx_hal::irq_router::enable_and_init_irq_router;
 use va416xx_hal::pac::{self, interrupt};
-use va416xx_hal::prelude::*;
 use va416xx_hal::wdt::Wdt;
 
-static WDT_INTRPT_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
+static WDT_INTRPT_COUNT: AtomicU32 = AtomicU32::new(0);
 
 #[derive(Debug, PartialEq, Eq)]
 #[allow(dead_code)]
@@ -33,33 +32,37 @@ const WDT_ROLLOVER_MS: u32 = 100;
 fn main() -> ! {
     defmt::println!("-- VA416xx WDT example application--");
     let cp = cortex_m::Peripherals::take().unwrap();
-    let mut dp = pac::Peripherals::take().unwrap();
+    let dp = pac::Peripherals::take().unwrap();
 
     // Use the external clock connected to XTAL_N.
-    let clocks = dp
-        .clkgen
-        .constrain()
+    let clocks = ClockConfigurator::new(dp.clkgen)
         .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
-        .freeze(&mut dp.sysconfig)
+        .freeze()
         .unwrap();
-    enable_and_init_irq_router(&mut dp.sysconfig, &dp.irq_router);
-    let mut delay_sysclk = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
+    enable_and_init_irq_router();
+    let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.apb0().raw());
 
     let mut last_interrupt_counter = 0;
-    let mut wdt_ctrl = Wdt::start(&mut dp.sysconfig, dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
+    let mut wdt_ctrl = Wdt::start(dp.watch_dog, &clocks, WDT_ROLLOVER_MS);
     wdt_ctrl.enable_reset();
+    let log_divisor = 25;
+    let mut counter: u32 = 0;
     loop {
+        counter = counter.wrapping_add(1);
+        if counter % log_divisor == 0 {
+            defmt::info!("wdt example main loop alive");
+        }
         if TEST_MODE != TestMode::AllowReset {
             wdt_ctrl.feed();
         }
-        let interrupt_counter = critical_section::with(|cs| WDT_INTRPT_COUNT.borrow(cs).get());
+        let interrupt_counter = WDT_INTRPT_COUNT.load(Ordering::Relaxed);
         if interrupt_counter > last_interrupt_counter {
             defmt::info!("interrupt counter has increased to {}", interrupt_counter);
             last_interrupt_counter = interrupt_counter;
         }
         match TEST_MODE {
-            TestMode::FedByMain => delay_sysclk.delay_ms(WDT_ROLLOVER_MS / 5),
-            TestMode::FedByIrq => delay_sysclk.delay_ms(WDT_ROLLOVER_MS),
+            TestMode::FedByMain => delay.delay_ms(WDT_ROLLOVER_MS / 5),
+            TestMode::FedByIrq => delay.delay_ms(WDT_ROLLOVER_MS),
             _ => (),
         }
     }
@@ -68,11 +71,7 @@ fn main() -> ! {
 #[interrupt]
 #[allow(non_snake_case)]
 fn WATCHDOG() {
-    critical_section::with(|cs| {
-        WDT_INTRPT_COUNT
-            .borrow(cs)
-            .set(WDT_INTRPT_COUNT.borrow(cs).get() + 1);
-    });
+    WDT_INTRPT_COUNT.fetch_add(1, Ordering::Relaxed);
     let wdt = unsafe { pac::WatchDog::steal() };
     // Clear interrupt.
     if TEST_MODE != TestMode::AllowReset {
diff --git a/flashloader/Cargo.toml b/flashloader/Cargo.toml
index b497c68..84832e2 100644
--- a/flashloader/Cargo.toml
+++ b/flashloader/Cargo.toml
@@ -12,7 +12,6 @@ embedded-io = "0.6"
 defmt-rtt = "0.4"
 defmt = "1"
 panic-probe = { version = "1", features = ["defmt"] }
-log = "0.4"
 crc = "3"
 rtic-sync = "1"
 static_cell = "2"
@@ -22,7 +21,7 @@ once_cell = { version = "1", default-features = false, features = ["critical-sec
 spacepackets = { version = "0.13", default-features = false, features = ["defmt"] }
 cobs = { version = "0.3", default-features = false }
 
-va416xx-hal = { version = "0.5", features = ["va41630", "defmt"] }
+va416xx-hal = { version = "0.5", features = ["va41630", "defmt"], path = "../va416xx-hal" }
 
 rtic = { version = "2", features = ["thumbv7-backend"] }
 rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
diff --git a/flashloader/image-loader.py b/flashloader/image-loader.py
index 2310042..336d342 100755
--- a/flashloader/image-loader.py
+++ b/flashloader/image-loader.py
@@ -99,7 +99,7 @@ class ImageLoader:
         )
         self.verificator.add_tc(action_tc)
         self.com_if.send(bytes(action_tc.pack()))
-        self.await_for_command_copletion("boot image selection command")
+        self.await_for_command_completion("boot image selection command")
 
     def handle_ping_cmd(self):
         _LOGGER.info("Sending ping command")
@@ -112,7 +112,7 @@ class ImageLoader:
         )
         self.verificator.add_tc(ping_tc)
         self.com_if.send(bytes(ping_tc.pack()))
-        self.await_for_command_copletion("ping command")
+        self.await_for_command_completion("ping command")
 
     def handle_corruption_cmd(self, target: Target):
         if target == Target.BOOTLOADER:
@@ -134,11 +134,11 @@ class ImageLoader:
                 ),
             )
 
-    def await_for_command_copletion(self, context: str):
+    def await_for_command_completion(self, context: str):
         done = False
         now = time.time()
         while time.time() - now < 2.0:
-            if not self.com_if.data_available():
+            if self.com_if.data_available() == 0:
                 time.sleep(0.2)
                 continue
             for reply in self.com_if.receive():
diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs
index f3dbf5c..189dac0 100644
--- a/flashloader/src/main.rs
+++ b/flashloader/src/main.rs
@@ -32,6 +32,7 @@ const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
 const UART_BAUDRATE: u32 = 115200;
 const BOOT_NVM_MEMORY_ID: u8 = 1;
 const RX_DEBUGGING: bool = false;
+const TX_DEBUGGING: bool = false;
 
 pub enum ActionId {
     CorruptImageA = 128,
@@ -105,14 +106,14 @@ mod app {
     use spacepackets::ecss::{
         tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
     };
+    use va416xx_hal::clock::ClockConfigurator;
     use va416xx_hal::irq_router::enable_and_init_irq_router;
     use va416xx_hal::uart::IrqContextTimeoutOrMaxSize;
     use va416xx_hal::{
-        clock::ClkgenExt,
         edac,
-        gpio::PinsG,
         nvm::Nvm,
         pac,
+        pins::PinsG,
         uart::{self, Uart},
     };
 
@@ -128,8 +129,8 @@ mod app {
 
     #[local]
     struct Local {
-        uart_rx: uart::RxWithInterrupt<pac::Uart0>,
-        uart_tx: uart::Tx<pac::Uart0>,
+        uart_rx: uart::RxWithInterrupt,
+        uart_tx: uart::Tx,
         rx_context: IrqContextTimeoutOrMaxSize,
         rom_spi: Option<pac::Spi3>,
         // We handle all TM in one task.
@@ -154,28 +155,24 @@ mod app {
         defmt::println!("-- Vorago flashloader --");
         // Initialize the systick interrupt & obtain the token to prove that we did
         // Use the external clock connected to XTAL_N.
-        let clocks = cx
-            .device
-            .clkgen
-            .constrain()
+        let clocks = ClockConfigurator::new(cx.device.clkgen)
             .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
-            .freeze(&mut cx.device.sysconfig)
+            .freeze()
             .unwrap();
 
-        enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
+        enable_and_init_irq_router();
         setup_edac(&mut cx.device.sysconfig);
 
-        let gpiog = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
-        let tx = gpiog.pg0.into_funsel_1();
-        let rx = gpiog.pg1.into_funsel_1();
+        let gpiog = PinsG::new(cx.device.portg);
 
         let uart0 = Uart::new(
-            &mut cx.device.sysconfig,
             cx.device.uart0,
-            (tx, rx),
-            Hertz::from_raw(UART_BAUDRATE),
+            gpiog.pg0,
+            gpiog.pg1,
             &clocks,
-        );
+            Hertz::from_raw(UART_BAUDRATE).into(),
+        )
+        .unwrap();
         let (tx, rx) = uart0.split();
 
         let verif_reporter = VerificationReportCreator::new(0).unwrap();
@@ -259,8 +256,8 @@ mod app {
         {
             Ok(result) => {
                 if RX_DEBUGGING {
-                    log::debug!("RX Info: {:?}", cx.local.rx_context);
-                    log::debug!("RX Result: {:?}", result);
+                    defmt::info!("RX Info: {:?}", cx.local.rx_context);
+                    defmt::info!("RX Result: {:?}", result);
                 }
                 if result.complete() {
                     // Check frame validity (must have COBS format) and decode the frame.
@@ -331,7 +328,7 @@ mod app {
                 continue;
             }
             let packet_len = packet_len.unwrap();
-            log::info!(target: "TC Handler", "received packet with length {}", packet_len);
+            defmt::info!("received packet with length {}", packet_len);
             assert_eq!(
                 cx.local
                     .tc_cons
@@ -378,9 +375,7 @@ mod app {
             let mut corrupt_image = |base_addr: u32| {
                 // Safety: We only use this for NVM handling and we only do NVM
                 // handling here.
-                let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
                 let nvm = Nvm::new(
-                    &mut sys_cfg,
                     cx.local.rom_spi.take().unwrap(),
                     CLOCKS.get().as_ref().unwrap(),
                 );
@@ -388,7 +383,7 @@ mod app {
                 nvm.read_data(base_addr + 32, &mut buf);
                 buf[0] += 1;
                 nvm.write_data(base_addr + 32, &buf);
-                *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
+                *cx.local.rom_spi = Some(nvm.release());
                 let tm = cx
                     .local
                     .verif_reporter
@@ -406,7 +401,7 @@ mod app {
             }
         }
         if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
-            log::info!(target: "TC Handler", "received ping TC");
+            defmt::info!("received ping TC");
             let tm = cx
                 .local
                 .verif_reporter
@@ -453,31 +448,22 @@ mod app {
                     return;
                 }
                 let data = &app_data[10..10 + data_len as usize];
-                log::info!(
-                    target: "TC Handler",
-                    "writing {} bytes at offset {} to NVM",
-                    data_len,
-                    offset
-                );
+                defmt::info!("writing {} bytes at offset {} to NVM", data_len, offset);
                 // Safety: We only use this for NVM handling and we only do NVM
                 // handling here.
-                let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
                 let nvm = Nvm::new(
-                    &mut sys_cfg,
                     cx.local.rom_spi.take().unwrap(),
                     CLOCKS.get().as_ref().unwrap(),
                 );
                 nvm.write_data(offset, data);
-                *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
+                *cx.local.rom_spi = Some(nvm.release());
                 let tm = cx
                     .local
                     .verif_reporter
                     .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
                     .expect("completion success failed");
                 write_and_send(&tm);
-                log::info!(
-                    target: "TC Handler",
-                    "NVM operation done");
+                defmt::info!("NVM operation done");
             }
         }
     }
@@ -506,9 +492,12 @@ mod app {
                     &mut cx.local.encoded_buf[1..],
                 );
                 cx.local.encoded_buf[send_size + 1] = 0;
+                if TX_DEBUGGING {
+                    defmt::debug!("UART TX: Sending data with size {}", send_size + 2);
+                }
                 cx.local
                     .uart_tx
-                    .write(&cx.local.encoded_buf[0..send_size + 2])
+                    .write_all(&cx.local.encoded_buf[0..send_size + 2])
                     .unwrap();
                 Mono::delay(2.millis()).await;
             }
diff --git a/va416xx-embassy/Cargo.toml b/va416xx-embassy/Cargo.toml
index bfc3053..e2536dd 100644
--- a/va416xx-embassy/Cargo.toml
+++ b/va416xx-embassy/Cargo.toml
@@ -11,17 +11,8 @@ keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"]
 categories = ["aerospace", "embedded", "no-std", "hardware-support"]
 
 [dependencies]
-critical-section = "1"
-
-embassy-sync = "0.6"
-embassy-executor = "0.7"
-embassy-time-driver = "0.2"
-embassy-time-queue-utils = "0.1"
-portable-atomic = "1"
-
-once_cell = { version = "1", default-features = false, features = ["critical-section"] }
-
-va416xx-hal = { version = ">=0.4, <=0.5" }
+vorago-shared-periphs = { path = "../../vorago-shared-periphs", features = ["vor4x"] }
+va416xx-hal = { path = "../va416xx-hal" }
 
 [features]
 default = ["irq-tim14-tim15"]
diff --git a/va416xx-embassy/src/lib.rs b/va416xx-embassy/src/lib.rs
index b1f6864..5f350f5 100644
--- a/va416xx-embassy/src/lib.rs
+++ b/va416xx-embassy/src/lib.rs
@@ -21,11 +21,11 @@
 //! itself by using the `irq-tim14-tim15` feature flag. This library exposes three combinations:
 //!
 //! - `irq-tim14-tim15`: Uses [pac::Interrupt::TIM14] for alarm and [pac::Interrupt::TIM15]
-//!    for timekeeper
+//!   for timekeeper
 //! - `irq-tim13-tim14`: Uses [pac::Interrupt::TIM13] for alarm and [pac::Interrupt::TIM14]
-//!    for timekeeper
+//!   for timekeeper
 //! - `irq-tim22-tim23`: Uses [pac::Interrupt::TIM22] for alarm and [pac::Interrupt::TIM23]
-//!    for timekeeper
+//!   for timekeeper
 //!
 //! You can disable the default features and then specify one of the features above to use the
 //! documented combination of IRQs. It is also possible to specify custom IRQs by importing and
@@ -38,34 +38,13 @@
 //! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy)
 #![no_std]
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
-use core::{
-    cell::{Cell, RefCell},
-    sync::atomic::{AtomicU32, Ordering},
-};
-
-use critical_section::{CriticalSection, Mutex};
-
-use embassy_time_driver::{time_driver_impl, Driver, TICK_HZ};
-use embassy_time_queue_utils::Queue;
-use once_cell::sync::OnceCell;
 use va416xx_hal::{
     clock::Clocks,
-    enable_nvic_interrupt,
     irq_router::enable_and_init_irq_router,
     pac::{self, interrupt},
-    pwm::ValidTim,
-    timer::{
-        assert_tim_reset_for_two_cycles, enable_tim_clk, get_tim_raw, TimRegInterface,
-        TIM_IRQ_OFFSET,
-    },
+    timer::{TimMarker, TIM_IRQ_OFFSET},
 };
-
-time_driver_impl!(
-    static TIME_DRIVER: TimerDriver = TimerDriver {
-        periods: AtomicU32::new(0),
-        alarms: Mutex::new(AlarmState::new()),
-        queue: Mutex::new(RefCell::new(Queue::new())),
-});
+use vorago_shared_periphs::embassy::time_driver;
 
 /// Macro to define the IRQ handlers for the time driver.
 ///
@@ -110,289 +89,29 @@ embassy_time_driver_irqs!(timekeeper_irq = TIM14, alarm_irq = TIM13);
 #[cfg(feature = "irq-tim22-tim23")]
 embassy_time_driver_irqs!(timekeeper_irq = TIM23, alarm_irq = TIM22);
 
-/// Expose the time driver so the user can specify the IRQ handlers themselves.
-pub fn time_driver() -> &'static TimerDriver {
-    &TIME_DRIVER
-}
-
 /// Initialization method for embassy
 ///
 /// If the interrupt handlers are provided by the library, the ID of the
 /// used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this
 /// can only be checked at run-time, and a run-time assertion will panic on the embassy
 /// initialization in case of a missmatch.
-///
-/// # Safety
-///
-/// This has to be called once at initialization time to initiate the time driver for
-/// embassy.
-pub unsafe fn init<
-    TimekeeperTim: TimRegInterface + ValidTim,
-    AlarmTim: TimRegInterface + ValidTim,
->(
-    syscfg: &mut pac::Sysconfig,
-    irq_router: &pac::IrqRouter,
+pub fn init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
     timekeeper: TimekeeperTim,
     alarm: AlarmTim,
     clocks: &Clocks,
 ) {
     #[cfg(feature = "_irqs-in-lib")]
     assert_eq!(
-        TimekeeperTim::ID,
+        TimekeeperTim::ID.value(),
         TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8,
         "Timekeeper TIM and IRQ missmatch"
     );
     #[cfg(feature = "_irqs-in-lib")]
     assert_eq!(
-        AlarmTim::ID,
+        AlarmTim::ID.value(),
         ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8,
         "Alarm TIM and IRQ missmatch"
     );
-    enable_and_init_irq_router(syscfg, irq_router);
-    TIME_DRIVER.init(syscfg, timekeeper, alarm, clocks)
-}
-
-struct AlarmState {
-    timestamp: Cell<u64>,
-}
-
-impl AlarmState {
-    const fn new() -> Self {
-        Self {
-            timestamp: Cell::new(u64::MAX),
-        }
-    }
-}
-
-unsafe impl Send for AlarmState {}
-
-static SCALE: OnceCell<u64> = OnceCell::new();
-static TIMEKEEPER_TIM: OnceCell<u8> = OnceCell::new();
-static ALARM_TIM: OnceCell<u8> = OnceCell::new();
-
-pub struct TimerDriver {
-    periods: AtomicU32,
-    /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
-    alarms: Mutex<AlarmState>,
-    queue: Mutex<RefCell<Queue>>,
-}
-
-impl TimerDriver {
-    fn init<TimekeeperTim: TimRegInterface + ValidTim, AlarmTim: TimRegInterface + ValidTim>(
-        &self,
-        syscfg: &mut pac::Sysconfig,
-        timekeeper_tim: TimekeeperTim,
-        alarm_tim: AlarmTim,
-        clocks: &Clocks,
-    ) {
-        if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
-            return;
-        }
-        ALARM_TIM.set(alarm_tim.tim_id()).ok();
-        TIMEKEEPER_TIM.set(timekeeper_tim.tim_id()).ok();
-        enable_tim_clk(syscfg, timekeeper_tim.tim_id());
-        assert_tim_reset_for_two_cycles(syscfg, alarm_tim.tim_id());
-
-        // Initiate scale value here. This is required to convert timer ticks back to a timestamp.
-        SCALE
-            .set((TimekeeperTim::clock(clocks).raw() / TICK_HZ as u32) as u64)
-            .unwrap();
-        let timekeeper_tim_regs = timekeeper_tim.reg_block();
-        timekeeper_tim_regs
-            .rst_value()
-            .write(|w| unsafe { w.bits(u32::MAX) });
-        // Decrementing counter.
-        timekeeper_tim_regs
-            .cnt_value()
-            .write(|w| unsafe { w.bits(u32::MAX) });
-        // Switch on. Timekeeping should always be done.
-        unsafe {
-            enable_nvic_interrupt(TimekeeperTim::IRQ);
-        }
-        timekeeper_tim_regs
-            .ctrl()
-            .modify(|_, w| w.irq_enb().set_bit());
-        timekeeper_tim_regs.enable().write(|w| unsafe { w.bits(1) });
-
-        enable_tim_clk(syscfg, AlarmTim::ID);
-        assert_tim_reset_for_two_cycles(syscfg, AlarmTim::ID);
-        let alarm_tim_regs = alarm_tim.reg_block();
-        // Explicitely disable alarm timer until needed.
-        alarm_tim_regs.ctrl().modify(|_, w| {
-            w.irq_enb().clear_bit();
-            w.enable().clear_bit()
-        });
-        // Enable general interrupts. The IRQ enable of the peripheral remains cleared.
-        unsafe {
-            enable_nvic_interrupt(AlarmTim::IRQ);
-        }
-    }
-
-    fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock {
-        TIMEKEEPER_TIM
-            .get()
-            .map(|idx| unsafe { get_tim_raw(*idx as usize) })
-            .unwrap()
-    }
-    fn alarm_tim() -> &'static pac::tim0::RegisterBlock {
-        ALARM_TIM
-            .get()
-            .map(|idx| unsafe { get_tim_raw(*idx as usize) })
-            .unwrap()
-    }
-
-    /// Should be called inside the IRQ of the timekeeper timer.
-    ///
-    /// # Safety
-    ///
-    /// This function has to be called once by the TIM IRQ used for the timekeeping.
-    pub unsafe fn on_interrupt_timekeeping(&self) {
-        self.next_period();
-    }
-
-    /// Should be called inside the IRQ of the alarm timer.
-    ///
-    /// # Safety
-    ///
-    ///This function has to be called once by the TIM IRQ used for the timekeeping.
-    pub unsafe fn on_interrupt_alarm(&self) {
-        critical_section::with(|cs| {
-            if self.alarms.borrow(cs).timestamp.get() <= self.now() {
-                self.trigger_alarm(cs)
-            }
-        })
-    }
-
-    fn next_period(&self) {
-        let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
-        let t = (period as u64) << 32;
-        critical_section::with(|cs| {
-            let alarm = &self.alarms.borrow(cs);
-            let at = alarm.timestamp.get();
-            if at < t {
-                self.trigger_alarm(cs);
-            } else {
-                let alarm_tim = Self::alarm_tim();
-
-                let remaining_ticks = (at - t) * *SCALE.get().unwrap();
-                if remaining_ticks <= u32::MAX as u64 {
-                    alarm_tim.enable().write(|w| unsafe { w.bits(0) });
-                    alarm_tim
-                        .cnt_value()
-                        .write(|w| unsafe { w.bits(remaining_ticks as u32) });
-                    alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
-                    alarm_tim.enable().write(|w| unsafe { w.bits(1) });
-                }
-            }
-        })
-    }
-
-    fn trigger_alarm(&self, cs: CriticalSection) {
-        Self::alarm_tim().ctrl().modify(|_, w| {
-            w.irq_enb().clear_bit();
-            w.enable().clear_bit()
-        });
-
-        let alarm = &self.alarms.borrow(cs);
-        // Setting the maximum value disables the alarm.
-        alarm.timestamp.set(u64::MAX);
-
-        // Call after clearing alarm, so the callback can set another alarm.
-        let mut next = self
-            .queue
-            .borrow(cs)
-            .borrow_mut()
-            .next_expiration(self.now());
-        while !self.set_alarm(cs, next) {
-            next = self
-                .queue
-                .borrow(cs)
-                .borrow_mut()
-                .next_expiration(self.now());
-        }
-    }
-
-    fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
-        if SCALE.get().is_none() {
-            return false;
-        }
-        let alarm_tim = Self::alarm_tim();
-        alarm_tim.ctrl().modify(|_, w| {
-            w.irq_enb().clear_bit();
-            w.enable().clear_bit()
-        });
-
-        let alarm = self.alarms.borrow(cs);
-        alarm.timestamp.set(timestamp);
-
-        let t = self.now();
-        if timestamp <= t {
-            alarm.timestamp.set(u64::MAX);
-            return false;
-        }
-
-        // If it hasn't triggered yet, setup the relevant reset value, regardless of whether
-        // the interrupts are enabled or not. When they are enabled at a later point, the
-        // right value is already set.
-
-        // If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
-        // is not missed.
-        //
-        // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
-        // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
-        // and we don't do that here.
-        let safe_timestamp = timestamp.max(t + 3);
-        let timer_ticks = (safe_timestamp - t).checked_mul(*SCALE.get().unwrap());
-        alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) });
-        if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) {
-            alarm_tim
-                .cnt_value()
-                .write(|w| unsafe { w.bits(timer_ticks.unwrap() as u32) });
-            alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit());
-            alarm_tim.enable().write(|w| unsafe { w.bits(1) });
-        }
-        // If it's too far in the future, don't enable timer yet.
-        // It will be enabled later by `next_period`.
-
-        true
-    }
-}
-
-impl Driver for TimerDriver {
-    fn now(&self) -> u64 {
-        if SCALE.get().is_none() {
-            return 0;
-        }
-        let mut period1: u32;
-        let mut period2: u32;
-        let mut counter_val: u32;
-
-        loop {
-            // Acquire ensures that we get the latest value of `periods` and
-            // no instructions can be reordered before the load.
-            period1 = self.periods.load(Ordering::Acquire);
-
-            counter_val = u32::MAX - Self::timekeeper_tim().cnt_value().read().bits();
-
-            // Double read to protect against race conditions when the counter is overflowing.
-            period2 = self.periods.load(Ordering::Relaxed);
-            if period1 == period2 {
-                let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap();
-                return now;
-            }
-        }
-    }
-
-    fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
-        critical_section::with(|cs| {
-            let mut queue = self.queue.borrow(cs).borrow_mut();
-
-            if queue.schedule_wake(at, waker) {
-                let mut next = queue.next_expiration(self.now());
-                while !self.set_alarm(cs, next) {
-                    next = queue.next_expiration(self.now());
-                }
-            }
-        })
-    }
+    enable_and_init_irq_router();
+    time_driver().__init(timekeeper, alarm, clocks)
 }
diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml
index 1a14d1e..82ccdb1 100644
--- a/va416xx-hal/Cargo.toml
+++ b/va416xx-hal/Cargo.toml
@@ -31,6 +31,7 @@ void = { version = "1", default-features = false }
 thiserror = { version = "2", default-features = false }
 portable-atomic = "1"
 embassy-sync = "0.6"
+vorago-shared-periphs = { path = "../../vorago-shared-periphs", features = ["vor4x"] }
 va416xx = { version = "0.4", features = ["critical-section"], default-features = false }
 
 defmt = { version = "0.3", optional = true }
@@ -38,13 +39,13 @@ defmt = { version = "0.3", optional = true }
 [features]
 default = ["rt", "revb"]
 rt = ["va416xx/rt"]
-defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03"]
+defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03", "vorago-shared-periphs/defmt"]
 
 va41630 = ["device-selected"]
 va41620 = ["device-selected"]
 
 va41629 = ["device-selected"]
-va41628 = ["device-selected"]
+va41628 = ["device-selected", "vorago-shared-periphs/va41628"]
 
 device-selected = []
 revb = []
diff --git a/va416xx-hal/src/adc.rs b/va416xx-hal/src/adc.rs
index 2eec515..7d80a29 100644
--- a/va416xx-hal/src/adc.rs
+++ b/va416xx-hal/src/adc.rs
@@ -8,9 +8,9 @@ use core::marker::PhantomData;
 
 use crate::clock::Clocks;
 use crate::pac;
-use crate::prelude::*;
 use crate::time::Hertz;
 use num_enum::{IntoPrimitive, TryFromPrimitive};
+use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
 
 pub const ADC_MIN_CLK: Hertz = Hertz::from_raw(2_000_000);
 pub const ADC_MAX_CLK: Hertz = Hertz::from_raw(12_500_000);
@@ -151,8 +151,8 @@ pub struct Adc<TagEnabled = ChannelTagDisabled> {
 impl Adc<ChannelTagEnabled> {}
 
 impl Adc<ChannelTagDisabled> {
-    pub fn new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, clocks: &Clocks) -> Self {
-        Self::generic_new(syscfg, adc, clocks)
+    pub fn new(adc: pac::Adc, clocks: &Clocks) -> Self {
+        Self::generic_new(adc, clocks)
     }
 
     pub fn trigger_and_read_single_channel(&self, ch: ChannelSelect) -> Result<u16, AdcEmptyError> {
@@ -209,12 +209,8 @@ impl Adc<ChannelTagDisabled> {
 }
 
 impl Adc<ChannelTagEnabled> {
-    pub fn new_with_channel_tag(
-        syscfg: &mut pac::Sysconfig,
-        adc: pac::Adc,
-        clocks: &Clocks,
-    ) -> Self {
-        let mut adc = Self::generic_new(syscfg, adc, clocks);
+    pub fn new_with_channel_tag(adc: pac::Adc, clocks: &Clocks) -> Self {
+        let mut adc = Self::generic_new(adc, clocks);
         adc.enable_channel_tag();
         adc
     }
@@ -286,8 +282,8 @@ impl Adc<ChannelTagEnabled> {
 }
 
 impl<TagEnabled> Adc<TagEnabled> {
-    fn generic_new(syscfg: &mut pac::Sysconfig, adc: pac::Adc, _clocks: &Clocks) -> Self {
-        syscfg.enable_peripheral_clock(crate::clock::PeripheralSelect::Adc);
+    fn generic_new(adc: pac::Adc, _clocks: &Clocks) -> Self {
+        enable_peripheral_clock(PeripheralSelect::Adc);
         adc.ctrl().write(|w| unsafe { w.bits(0) });
         let adc = Self {
             adc,
diff --git a/va416xx-hal/src/clock.rs b/va416xx-hal/src/clock.rs
index 6bdd7ee..b9e8c5f 100644
--- a/va416xx-hal/src/clock.rs
+++ b/va416xx-hal/src/clock.rs
@@ -15,48 +15,11 @@ use crate::adc::ADC_MAX_CLK;
 use crate::pac;
 
 use crate::time::Hertz;
+pub use vorago_shared_periphs::clock::{Clocks, HBO_FREQ};
+use vorago_shared_periphs::{enable_peripheral_clock, PeripheralSelect};
 
-pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000);
 pub const XTAL_OSC_TSTART_MS: u32 = 15;
 
-#[derive(Debug, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum PeripheralSelect {
-    Spi0 = 0,
-    Spi1 = 1,
-    Spi2 = 2,
-    Spi3 = 3,
-    Uart0 = 4,
-    Uart1 = 5,
-    Uart2 = 6,
-    I2c0 = 7,
-    I2c1 = 8,
-    I2c2 = 9,
-    Can0 = 10,
-    Can1 = 11,
-    Rng = 12,
-    Adc = 13,
-    Dac = 14,
-    Dma = 15,
-    Ebi = 16,
-    Eth = 17,
-    Spw = 18,
-    Clkgen = 19,
-    IrqRouter = 20,
-    IoConfig = 21,
-    Utility = 22,
-    Watchdog = 23,
-    PortA = 24,
-    PortB = 25,
-    PortC = 26,
-    PortD = 27,
-    PortE = 28,
-    PortF = 29,
-    PortG = 30,
-}
-
-pub type PeripheralClock = PeripheralSelect;
-
 #[derive(Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub enum FilterClkSel {
@@ -70,81 +33,6 @@ pub enum FilterClkSel {
     Clk7 = 7,
 }
 
-#[inline(always)]
-pub fn enable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) {
-    syscfg
-        .peripheral_clk_enable()
-        .modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
-}
-
-#[inline(always)]
-pub fn disable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) {
-    syscfg
-        .peripheral_clk_enable()
-        .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
-}
-
-#[inline(always)]
-pub fn assert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
-    syscfg
-        .peripheral_reset()
-        .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph as u8)) });
-}
-
-#[inline(always)]
-pub fn deassert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
-    syscfg
-        .peripheral_reset()
-        .modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) });
-}
-
-#[inline(always)]
-fn assert_periph_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) {
-    assert_periph_reset(syscfg, periph);
-    cortex_m::asm::nop();
-    cortex_m::asm::nop();
-    deassert_periph_reset(syscfg, periph);
-}
-
-pub trait SyscfgExt {
-    fn enable_peripheral_clock(&mut self, clock: PeripheralClock);
-
-    fn disable_peripheral_clock(&mut self, clock: PeripheralClock);
-
-    fn assert_periph_reset(&mut self, periph: PeripheralSelect);
-
-    fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
-
-    fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect);
-}
-
-impl SyscfgExt for pac::Sysconfig {
-    #[inline(always)]
-    fn enable_peripheral_clock(&mut self, clock: PeripheralClock) {
-        enable_peripheral_clock(self, clock)
-    }
-
-    #[inline(always)]
-    fn disable_peripheral_clock(&mut self, clock: PeripheralClock) {
-        disable_peripheral_clock(self, clock)
-    }
-
-    #[inline(always)]
-    fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
-        assert_periph_reset(self, clock)
-    }
-
-    #[inline(always)]
-    fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
-        deassert_periph_reset(self, clock)
-    }
-
-    #[inline(always)]
-    fn assert_periph_reset_for_two_cycles(&mut self, periph: PeripheralSelect) {
-        assert_periph_reset_for_two_cycles(self, periph)
-    }
-}
-
 /// Refer to chapter 8 (p.57) of the programmers guide for detailed information.
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -223,12 +111,12 @@ pub fn pll_setup_delay() {
 }
 
 pub trait ClkgenExt {
-    fn constrain(self) -> ClkgenCfgr;
+    fn constrain(self) -> ClockConfigurator;
 }
 
 impl ClkgenExt for pac::Clkgen {
-    fn constrain(self) -> ClkgenCfgr {
-        ClkgenCfgr {
+    fn constrain(self) -> ClockConfigurator {
+        ClockConfigurator {
             source_clk: None,
             ref_clk_sel: RefClkSel::None,
             clksel_sys: ClkselSys::Hbo,
@@ -241,21 +129,6 @@ impl ClkgenExt for pac::Clkgen {
     }
 }
 
-pub struct ClkgenCfgr {
-    ref_clk_sel: RefClkSel,
-    clksel_sys: ClkselSys,
-    clk_div_sel: ClkDivSel,
-    /// The source clock frequency which is either an external clock connected to XTAL_N, or a
-    /// crystal connected to the XTAL_OSC input.
-    source_clk: Option<Hertz>,
-    pll_cfg: Option<PllCfg>,
-    clk_lost_detection: bool,
-    /// Feature only works on revision B of the board.
-    #[cfg(feature = "revb")]
-    pll_lock_lost_detection: bool,
-    clkgen: pac::Clkgen,
-}
-
 #[derive(Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub struct ClkSourceFreqNotSet;
@@ -269,6 +142,21 @@ pub enum ClkCfgError {
     InconsistentCfg,
 }
 
+pub struct ClockConfigurator {
+    ref_clk_sel: RefClkSel,
+    clksel_sys: ClkselSys,
+    clk_div_sel: ClkDivSel,
+    /// The source clock frequency which is either an external clock connected to XTAL_N, or a
+    /// crystal connected to the XTAL_OSC input.
+    source_clk: Option<Hertz>,
+    pll_cfg: Option<PllCfg>,
+    clk_lost_detection: bool,
+    /// Feature only works on revision B of the board.
+    #[cfg(feature = "revb")]
+    pll_lock_lost_detection: bool,
+    clkgen: pac::Clkgen,
+}
+
 /// Delays a given amount of milliseconds.
 ///
 /// Taken from the HAL implementation. This implementation is probably not precise and it
@@ -283,7 +171,30 @@ pub fn hbo_clock_delay_ms(ms: u32) {
     }
 }
 
-impl ClkgenCfgr {
+impl ClockConfigurator {
+    /// Create a new clock configuration instance.
+    pub fn new(clkgen: pac::Clkgen) -> Self {
+        ClockConfigurator {
+            source_clk: None,
+            ref_clk_sel: RefClkSel::None,
+            clksel_sys: ClkselSys::Hbo,
+            clk_div_sel: ClkDivSel::Div1,
+            clk_lost_detection: false,
+            pll_lock_lost_detection: false,
+            pll_cfg: None,
+            clkgen,
+        }
+    }
+
+    /// Steals a new [ClockConfigurator] instance.
+    ///
+    /// # Safety
+    ///
+    /// Circumvents HAL ownership rules.
+    pub unsafe fn steal() -> Self {
+        Self::new(unsafe { pac::Clkgen::steal() })
+    }
+
     #[inline]
     pub fn source_clk(mut self, src_clk: Hertz) -> Self {
         self.source_clk = Some(src_clk);
@@ -334,7 +245,7 @@ impl ClkgenCfgr {
     /// might have had a reason for those, so I am going to keep them. Chances are, this
     /// process only has to be performed once, and it does not matter if it takes a few
     /// microseconds or milliseconds longer.
-    pub fn freeze(self, syscfg: &mut pac::Sysconfig) -> Result<Clocks, ClkCfgError> {
+    pub fn freeze(self) -> Result<Clocks, ClkCfgError> {
         // Sanitize configuration.
         if self.source_clk.is_none() {
             return Err(ClkCfgError::ClkSourceFreqNotSet);
@@ -349,7 +260,7 @@ impl ClkgenCfgr {
             return Err(ClkCfgError::PllConfigNotSet);
         }
 
-        syscfg.enable_peripheral_clock(PeripheralSelect::Clkgen);
+        enable_peripheral_clock(PeripheralSelect::Clkgen);
         let mut final_sysclk = self.source_clk.unwrap();
         // The HAL forces back the HBO clock here with a delay.. Even though this is
         // not stricly necessary when coming from a fresh start, it could be still become relevant
@@ -458,13 +369,11 @@ impl ClkgenCfgr {
             .ctrl0()
             .modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
 
-        Ok(Clocks {
-            sysclk: final_sysclk,
-            apb1: final_sysclk / 2,
-            apb2: final_sysclk / 4,
+        Ok(Clocks::__new(
+            final_sysclk,
             #[cfg(not(feature = "va41628"))]
-            adc_clk: self.cfg_adc_clk_div(final_sysclk),
-        })
+            self.cfg_adc_clk_div(final_sysclk),
+        ))
     }
 
     #[cfg(not(feature = "va41628"))]
@@ -487,54 +396,6 @@ impl ClkgenCfgr {
     }
 }
 
-/// Frozen clock frequencies
-///
-/// The existence of this value indicates that the clock configuration can no longer be changed.
-/// The [self] module documentation gives some more information on how to retrieve an instance
-/// of this structure.
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct Clocks {
-    sysclk: Hertz,
-    apb1: Hertz,
-    apb2: Hertz,
-    #[cfg(not(feature = "va41628"))]
-    adc_clk: Hertz,
-}
-
-impl Clocks {
-    /// Returns the frequency of the HBO clock
-    pub const fn hbo(&self) -> Hertz {
-        HBO_FREQ
-    }
-
-    /// Returns the frequency of the APB0 which is equal to the system clock.
-    pub const fn apb0(&self) -> Hertz {
-        self.sysclk()
-    }
-
-    /// Returns system clock divied by 2.
-    pub const fn apb1(&self) -> Hertz {
-        self.apb1
-    }
-
-    /// Returns system clock divied by 4.
-    pub const fn apb2(&self) -> Hertz {
-        self.apb2
-    }
-
-    /// Returns the system (core) frequency
-    pub const fn sysclk(&self) -> Hertz {
-        self.sysclk
-    }
-
-    /// Returns the ADC clock frequency which has a separate divider.
-    #[cfg(not(feature = "va41628"))]
-    pub const fn adc_clk(&self) -> Hertz {
-        self.adc_clk
-    }
-}
-
 pub fn rearm_sysclk_lost() {
     rearm_sysclk_lost_with_periph(&unsafe { pac::Clkgen::steal() })
 }
diff --git a/va416xx-hal/src/dac.rs b/va416xx-hal/src/dac.rs
index a009093..1d77c59 100644
--- a/va416xx-hal/src/dac.rs
+++ b/va416xx-hal/src/dac.rs
@@ -5,22 +5,24 @@
 //! - [ADC and DAC example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/dac-adc.rs)
 use core::ops::Deref;
 
-use crate::{
-    clock::{Clocks, PeripheralSelect, SyscfgExt},
-    pac,
+use vorago_shared_periphs::{
+    disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
+    PeripheralSelect,
 };
 
+use crate::{clock::Clocks, pac};
+
 pub type DacRegisterBlock = pac::dac0::RegisterBlock;
 
 /// Common trait implemented by all PAC peripheral access structures. The register block
 /// format is the same for all DAC blocks.
-pub trait Instance: Deref<Target = DacRegisterBlock> {
+pub trait DacMarker: Deref<Target = DacRegisterBlock> {
     const IDX: u8;
 
     fn ptr() -> *const DacRegisterBlock;
 }
 
-impl Instance for pac::Dac0 {
+impl DacMarker for pac::Dac0 {
     const IDX: u8 = 0;
 
     #[inline(always)]
@@ -29,7 +31,7 @@ impl Instance for pac::Dac0 {
     }
 }
 
-impl Instance for pac::Dac1 {
+impl DacMarker for pac::Dac1 {
     const IDX: u8 = 1;
 
     #[inline(always)]
@@ -50,40 +52,37 @@ pub enum DacSettling {
     Apb2Times150 = 6,
 }
 
-pub struct Dac<DacInstance> {
-    dac: DacInstance,
-}
+pub struct Dac(*const DacRegisterBlock);
 
 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub struct ValueTooLarge;
 
-impl<DacInstance: Instance> Dac<DacInstance> {
+impl Dac {
     /// Create a new [Dac] driver instance.
     ///
     /// The [Clocks] structure is expected here as well to ensure the clock was set up properly.
-    pub fn new(
-        syscfg: &mut pac::Sysconfig,
-        dac: DacInstance,
-        dac_settling: DacSettling,
-        _clocks: &Clocks,
-    ) -> Self {
-        syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
+    pub fn new<Dac: DacMarker>(dac: Dac, dac_settling: DacSettling, _clocks: &Clocks) -> Self {
+        enable_peripheral_clock(PeripheralSelect::Dac);
 
         dac.ctrl1().write(|w| {
             w.dac_en().set_bit();
             // SAFETY: Enum values are valid values only.
             unsafe { w.dac_settling().bits(dac_settling as u8) }
         });
-        let dac = Self { dac };
+        let mut dac = Self(Dac::ptr());
         dac.clear_fifo();
         dac.clear_irqs();
         dac
     }
 
+    pub const fn regs(&self) -> &DacRegisterBlock {
+        unsafe { &*self.0 }
+    }
+
     #[inline(always)]
-    pub fn clear_irqs(&self) {
-        self.dac.irq_clr().write(|w| {
+    pub fn clear_irqs(&mut self) {
+        self.regs().irq_clr().write(|w| {
             w.fifo_oflow().set_bit();
             w.fifo_uflow().set_bit();
             w.dac_done().set_bit();
@@ -92,31 +91,30 @@ impl<DacInstance: Instance> Dac<DacInstance> {
     }
 
     #[inline(always)]
-    pub fn clear_fifo(&self) {
-        self.dac.fifo_clr().write(|w| unsafe { w.bits(1) });
+    pub fn clear_fifo(&mut self) {
+        self.regs().fifo_clr().write(|w| unsafe { w.bits(1) });
     }
 
     /// Load next value into the FIFO.
     ///
     /// Uses the [nb] API to allow blocking and non-blocking usage.
     #[inline(always)]
-    pub fn load_value(&self, val: u16) -> nb::Result<(), ValueTooLarge> {
+    pub fn load_value(&mut self, val: u16) -> nb::Result<(), ValueTooLarge> {
         if val > 2_u16.pow(12) - 1 {
             return Err(nb::Error::Other(ValueTooLarge));
         }
-        if self.dac.status().read().fifo_entry_cnt().bits() >= 32_u8 {
+        let regs = self.regs();
+        if regs.status().read().fifo_entry_cnt().bits() >= 32_u8 {
             return Err(nb::Error::WouldBlock);
         }
-        self.dac
-            .fifo_data()
-            .write(|w| unsafe { w.bits(val.into()) });
+        regs.fifo_data().write(|w| unsafe { w.bits(val.into()) });
         Ok(())
     }
 
     /// This loads and triggers the next value immediately. It also clears the FIFO before
     /// loading the passed value.
     #[inline(always)]
-    pub fn load_and_trigger_manually(&self, val: u16) -> Result<(), ValueTooLarge> {
+    pub fn load_and_trigger_manually(&mut self, val: u16) -> Result<(), ValueTooLarge> {
         if val > 2_u16.pow(12) - 1 {
             return Err(ValueTooLarge);
         }
@@ -132,31 +130,30 @@ impl<DacInstance: Instance> Dac<DacInstance> {
     /// to be processed by the DAC.
     #[inline(always)]
     pub fn trigger_manually(&self) {
-        self.dac.ctrl0().write(|w| w.man_trig_en().set_bit());
+        self.regs().ctrl0().write(|w| w.man_trig_en().set_bit());
     }
 
     #[inline(always)]
     pub fn enable_external_trigger(&self) {
-        self.dac.ctrl0().write(|w| w.ext_trig_en().set_bit());
+        self.regs().ctrl0().write(|w| w.ext_trig_en().set_bit());
     }
 
     pub fn is_settled(&self) -> nb::Result<(), ()> {
-        if self.dac.status().read().dac_busy().bit_is_set() {
+        if self.regs().status().read().dac_busy().bit_is_set() {
             return Err(nb::Error::WouldBlock);
         }
         Ok(())
     }
 
     #[inline(always)]
-    pub fn reset(&mut self, syscfg: &mut pac::Sysconfig) {
-        syscfg.enable_peripheral_clock(PeripheralSelect::Dac);
-        syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dac);
+    pub fn reset(&mut self) {
+        enable_peripheral_clock(PeripheralSelect::Dac);
+        reset_peripheral_for_cycles(PeripheralSelect::Dac, 2);
     }
 
-    /// Relases the DAC, which also disables its peripheral clock.
+    /// Stops the DAC, which disables its peripheral clock.
     #[inline(always)]
-    pub fn release(self, syscfg: &mut pac::Sysconfig) -> DacInstance {
-        syscfg.disable_peripheral_clock(PeripheralSelect::Dac);
-        self.dac
+    pub fn stop(self) {
+        disable_peripheral_clock(PeripheralSelect::Dac);
     }
 }
diff --git a/va416xx-hal/src/dma.rs b/va416xx-hal/src/dma.rs
index 9590a1a..363dd20 100644
--- a/va416xx-hal/src/dma.rs
+++ b/va416xx-hal/src/dma.rs
@@ -3,12 +3,12 @@
 //! ## Examples
 //!
 //! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
-use crate::{
-    clock::{PeripheralClock, PeripheralSelect},
-    enable_nvic_interrupt, pac,
-    prelude::*,
+use vorago_shared_periphs::{
+    enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
 };
 
+use crate::{enable_nvic_interrupt, pac};
+
 const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
 const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
 
@@ -495,7 +495,6 @@ impl Dma {
     /// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA
     /// control block at a specific address.
     pub fn new(
-        syscfg: &mut pac::Sysconfig,
         dma: pac::Dma,
         cfg: DmaCfg,
         ctrl_block: *mut DmaCtrlBlock,
@@ -505,8 +504,8 @@ impl Dma {
         if raw_addr & BASE_PTR_ADDR_MASK > 0 {
             return Err(InvalidCtrlBlockAddrError);
         }
-        syscfg.enable_peripheral_clock(PeripheralClock::Dma);
-        syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma);
+        enable_peripheral_clock(PeripheralSelect::Dma);
+        reset_peripheral_for_cycles(PeripheralSelect::Dma, 2);
         let dma = Dma { dma, ctrl_block };
         dma.dma
             .ctrl_base_ptr()
diff --git a/va416xx-hal/src/gpio/asynch.rs b/va416xx-hal/src/gpio/asynch.rs
deleted file mode 100644
index 7d18c82..0000000
--- a/va416xx-hal/src/gpio/asynch.rs
+++ /dev/null
@@ -1,448 +0,0 @@
-//! # Async GPIO functionality for the VA416xx family.
-//!
-//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement
-//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting
-//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers
-//! which must be provided for async support to work. However, it provides the
-//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all
-//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument.
-//!
-//! # Example
-//!
-//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
-use core::future::Future;
-
-use embassy_sync::waitqueue::AtomicWaker;
-use embedded_hal_async::digital::Wait;
-use portable_atomic::AtomicBool;
-use va416xx::{self as pac};
-
-use crate::enable_nvic_interrupt;
-
-use super::{
-    pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port,
-    NUM_PINS_PORT_A_TO_F,
-};
-
-static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
-static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
-static WAKERS_FOR_PORT_C: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
-static WAKERS_FOR_PORT_D: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
-static WAKERS_FOR_PORT_E: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
-static WAKERS_FOR_PORT_F: [AtomicWaker; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicWaker::new() }; NUM_PINS_PORT_A_TO_F];
-
-static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
-static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
-static EDGE_DETECTION_PORT_C: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
-static EDGE_DETECTION_PORT_D: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
-static EDGE_DETECTION_PORT_E: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
-static EDGE_DETECTION_PORT_F: [AtomicBool; NUM_PINS_PORT_A_TO_F] =
-    [const { AtomicBool::new(false) }; NUM_PINS_PORT_A_TO_F];
-
-#[derive(Debug, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[error("port G does not support async functionality")]
-pub struct PortGDoesNotSupportAsyncError;
-
-#[derive(Debug, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum AsyncDynPinError {
-    #[error("invalid pin type: {0}")]
-    InvalidPinType(#[from] InvalidPinTypeError),
-    #[error("port g does not support async functionality: {0}")]
-    PortGDoesNotSupportAsync(#[from] PortGDoesNotSupportAsyncError),
-}
-
-/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities
-///
-/// This function should be called in all interrupt handlers which handle any GPIO interrupts
-/// matching the [Port] argument.
-/// The handler will wake the corresponding wakers for the pins that triggered an interrupts
-/// as well as update the static edge detection structures. This allows the pin future tocomplete
-/// complete async operations.
-pub fn on_interrupt_for_async_gpio_for_port(
-    port: Port,
-) -> Result<(), PortGDoesNotSupportAsyncError> {
-    let periphs = unsafe { pac::Peripherals::steal() };
-
-    let (irq_enb, edge_status, wakers, edge_detection) = match port {
-        Port::A => (
-            periphs.porta.irq_enb().read().bits(),
-            periphs.porta.edge_status().read().bits(),
-            &WAKERS_FOR_PORT_A,
-            &EDGE_DETECTION_PORT_A,
-        ),
-        Port::B => (
-            periphs.portb.irq_enb().read().bits(),
-            periphs.portb.edge_status().read().bits(),
-            &WAKERS_FOR_PORT_B,
-            &EDGE_DETECTION_PORT_B,
-        ),
-        Port::C => (
-            periphs.portc.irq_enb().read().bits(),
-            periphs.portc.edge_status().read().bits(),
-            &WAKERS_FOR_PORT_C,
-            &EDGE_DETECTION_PORT_C,
-        ),
-        Port::D => (
-            periphs.portd.irq_enb().read().bits(),
-            periphs.portd.edge_status().read().bits(),
-            &WAKERS_FOR_PORT_D,
-            &EDGE_DETECTION_PORT_D,
-        ),
-        Port::E => (
-            periphs.porte.irq_enb().read().bits(),
-            periphs.porte.edge_status().read().bits(),
-            &WAKERS_FOR_PORT_E,
-            &EDGE_DETECTION_PORT_E,
-        ),
-        Port::F => (
-            periphs.portf.irq_enb().read().bits(),
-            periphs.portf.edge_status().read().bits(),
-            &WAKERS_FOR_PORT_F,
-            &EDGE_DETECTION_PORT_F,
-        ),
-        Port::G => return Err(PortGDoesNotSupportAsyncError),
-    };
-
-    on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection);
-    Ok(())
-}
-
-#[inline]
-fn on_interrupt_for_port(
-    mut irq_enb: u32,
-    edge_status: u32,
-    wakers: &'static [AtomicWaker],
-    edge_detection: &'static [AtomicBool],
-) {
-    while irq_enb != 0 {
-        let bit_pos = irq_enb.trailing_zeros() as usize;
-        let bit_mask = 1 << bit_pos;
-
-        wakers[bit_pos].wake();
-
-        if edge_status & bit_mask != 0 {
-            edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed);
-
-            // Clear the processed bit
-            irq_enb &= !bit_mask;
-        }
-    }
-}
-
-/// Input pin future which implements the [Future] trait.
-///
-/// Generally, you want to use the [InputPinAsync] or [InputDynPinAsync] types instead of this
-/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this
-/// struture is granted  to allow writing custom async structures.
-pub struct InputPinFuture {
-    pin_id: DynPinId,
-    waker_group: &'static [AtomicWaker],
-    edge_detection_group: &'static [AtomicBool],
-}
-
-impl InputPinFuture {
-    pub fn new_with_dyn_pin(
-        pin: &mut DynPin,
-        edge: InterruptEdge,
-    ) -> Result<Self, AsyncDynPinError> {
-        if !pin.is_input_pin() {
-            return Err(InvalidPinTypeError(pin.mode()).into());
-        }
-        if pin.id().port() == Port::G {
-            return Err(PortGDoesNotSupportAsyncError.into());
-        }
-
-        let (waker_group, edge_detection_group) =
-            Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
-        edge_detection_group[pin.id().num() as usize]
-            .store(false, core::sync::atomic::Ordering::Relaxed);
-        // Unwraps okay, checked for PORT G previously
-        pin.configure_edge_interrupt(edge).unwrap();
-        unsafe { enable_nvic_interrupt(pin.irq_id().unwrap()) };
-        pin.enable_interrupt();
-        Ok(Self {
-            pin_id: pin.id(),
-            waker_group,
-            edge_detection_group,
-        })
-    }
-
-    pub fn new_with_pin<I: PinId, C: InputConfig>(
-        pin: &mut Pin<I, pin::Input<C>>,
-        edge: InterruptEdge,
-    ) -> Result<Self, PortGDoesNotSupportAsyncError> {
-        if pin.id().port() == Port::G {
-            return Err(PortGDoesNotSupportAsyncError);
-        }
-        let (waker_group, edge_detection_group) =
-            Self::pin_group_to_waker_and_edge_detection_group(pin.id().port());
-        edge_detection_group[pin.id().num() as usize]
-            .store(false, core::sync::atomic::Ordering::Relaxed);
-        // Unwraps okay, checked for PORT G previously
-        pin.configure_edge_interrupt(edge);
-        unsafe { enable_nvic_interrupt(I::IRQ.unwrap()) };
-        pin.enable_interrupt();
-        Ok(Self {
-            pin_id: pin.id(),
-            waker_group,
-            edge_detection_group,
-        })
-    }
-
-    #[inline]
-    pub fn pin_group_to_waker_and_edge_detection_group(
-        group: Port,
-    ) -> (&'static [AtomicWaker], &'static [AtomicBool]) {
-        match group {
-            Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()),
-            Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()),
-            Port::C => (WAKERS_FOR_PORT_C.as_ref(), EDGE_DETECTION_PORT_C.as_ref()),
-            Port::D => (WAKERS_FOR_PORT_D.as_ref(), EDGE_DETECTION_PORT_D.as_ref()),
-            Port::E => (WAKERS_FOR_PORT_E.as_ref(), EDGE_DETECTION_PORT_E.as_ref()),
-            Port::F => (WAKERS_FOR_PORT_F.as_ref(), EDGE_DETECTION_PORT_F.as_ref()),
-            _ => panic!("unexpected pin group G"),
-        }
-    }
-}
-
-impl Drop for InputPinFuture {
-    fn drop(&mut self) {
-        // The API ensures that we actually own the pin, so stealing it here is okay.
-        unsafe { DynPin::steal(self.pin_id) }.disable_interrupt();
-    }
-}
-
-impl Future for InputPinFuture {
-    type Output = ();
-    fn poll(
-        self: core::pin::Pin<&mut Self>,
-        cx: &mut core::task::Context<'_>,
-    ) -> core::task::Poll<Self::Output> {
-        let idx = self.pin_id.num() as usize;
-        self.waker_group[idx].register(cx.waker());
-        if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
-            return core::task::Poll::Ready(());
-        }
-        core::task::Poll::Pending
-    }
-}
-
-pub struct InputDynPinAsync {
-    pin: DynPin,
-}
-
-impl InputDynPinAsync {
-    /// Create a new asynchronous input pin from a [DynPin]. The interrupt ID to be used must be
-    /// passed as well and is used to route and enable the interrupt.
-    ///
-    /// Please note that the interrupt handler itself must be provided by the user and the
-    /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
-    /// for the asynchronous functionality to work.
-    pub fn new(pin: DynPin) -> Result<Self, AsyncDynPinError> {
-        if !pin.is_input_pin() {
-            return Err(InvalidPinTypeError(pin.mode()).into());
-        }
-        if pin.id().port() == Port::G {
-            return Err(PortGDoesNotSupportAsyncError.into());
-        }
-        Ok(Self { pin })
-    }
-
-    /// Asynchronously wait until the pin is high.
-    ///
-    /// This returns immediately if the pin is already high.
-    pub async fn wait_for_high(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        let fut =
-            InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::LowToHigh).unwrap();
-        if self.pin.is_high().unwrap() {
-            return;
-        }
-        fut.await;
-    }
-
-    /// Asynchronously wait until the pin is low.
-    ///
-    /// This returns immediately if the pin is already high.
-    pub async fn wait_for_low(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        let fut =
-            InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::HighToLow).unwrap();
-        if self.pin.is_low().unwrap() {
-            return;
-        }
-        fut.await;
-    }
-
-    /// Asynchronously wait until the pin sees a falling edge.
-    pub async fn wait_for_falling_edge(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::HighToLow)
-            .unwrap()
-            .await;
-    }
-
-    /// Asynchronously wait until the pin sees a rising edge.
-    pub async fn wait_for_rising_edge(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::LowToHigh)
-            .unwrap()
-            .await;
-    }
-
-    /// Asynchronously wait until the pin sees any edge (either rising or falling).
-    pub async fn wait_for_any_edge(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        InputPinFuture::new_with_dyn_pin(&mut self.pin, InterruptEdge::BothEdges)
-            .unwrap()
-            .await;
-    }
-
-    pub fn release(self) -> DynPin {
-        self.pin
-    }
-}
-
-impl embedded_hal::digital::ErrorType for InputDynPinAsync {
-    type Error = core::convert::Infallible;
-}
-
-impl Wait for InputDynPinAsync {
-    async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_high().await;
-        Ok(())
-    }
-
-    async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_low().await;
-        Ok(())
-    }
-
-    async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_rising_edge().await;
-        Ok(())
-    }
-
-    async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_falling_edge().await;
-        Ok(())
-    }
-
-    async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_any_edge().await;
-        Ok(())
-    }
-}
-
-pub struct InputPinAsync<I: PinId, C: InputConfig> {
-    pin: Pin<I, pin::Input<C>>,
-}
-
-impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
-    /// Create a new asynchronous input pin from a typed [Pin]. The interrupt ID to be used must be
-    /// passed as well and is used to route and enable the interrupt.
-    ///
-    /// Please note that the interrupt handler itself must be provided by the user and the
-    /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
-    /// for the asynchronous functionality to work.
-    pub fn new(pin: Pin<I, pin::Input<C>>) -> Result<Self, PortGDoesNotSupportAsyncError> {
-        if pin.id().port() == Port::G {
-            return Err(PortGDoesNotSupportAsyncError);
-        }
-        Ok(Self { pin })
-    }
-
-    /// Asynchronously wait until the pin is high.
-    ///
-    /// This returns immediately if the pin is already high.
-    pub async fn wait_for_high(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        let fut = InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::LowToHigh).unwrap();
-        if self.pin.is_high() {
-            return;
-        }
-        fut.await;
-    }
-
-    /// Asynchronously wait until the pin is low.
-    ///
-    /// This returns immediately if the pin is already high.
-    pub async fn wait_for_low(&mut self) {
-        let fut = InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::HighToLow).unwrap();
-        if self.pin.is_low() {
-            return;
-        }
-        fut.await;
-    }
-
-    /// Asynchronously wait until the pin sees falling edge.
-    pub async fn wait_for_falling_edge(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::HighToLow)
-            .unwrap()
-            .await;
-    }
-
-    /// Asynchronously wait until the pin sees rising edge.
-    pub async fn wait_for_rising_edge(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::LowToHigh)
-            .unwrap()
-            .await;
-    }
-
-    /// Asynchronously wait until the pin sees any edge (either rising or falling).
-    pub async fn wait_for_any_edge(&mut self) {
-        // Unwrap okay, checked pin in constructor.
-        InputPinFuture::new_with_pin(&mut self.pin, InterruptEdge::BothEdges)
-            .unwrap()
-            .await;
-    }
-
-    pub fn release(self) -> Pin<I, pin::Input<C>> {
-        self.pin
-    }
-}
-impl<I: PinId, C: InputConfig> embedded_hal::digital::ErrorType for InputPinAsync<I, C> {
-    type Error = core::convert::Infallible;
-}
-
-impl<I: PinId, C: InputConfig> Wait for InputPinAsync<I, C> {
-    async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_high().await;
-        Ok(())
-    }
-
-    async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_low().await;
-        Ok(())
-    }
-
-    async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_rising_edge().await;
-        Ok(())
-    }
-
-    async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_falling_edge().await;
-        Ok(())
-    }
-
-    async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
-        self.wait_for_any_edge().await;
-        Ok(())
-    }
-}
diff --git a/va416xx-hal/src/gpio/dynpin.rs b/va416xx-hal/src/gpio/dynpin.rs
deleted file mode 100644
index a944ca6..0000000
--- a/va416xx-hal/src/gpio/dynpin.rs
+++ /dev/null
@@ -1,1041 +0,0 @@
-//! # Type-erased, value-level module for GPIO pins
-//!
-//! Although the type-level API is generally preferred, it is not suitable in
-//! all cases. Because each pin is represented by a distinct type, it is not
-//! possible to store multiple pins in a homogeneous data structure. The
-//! value-level API solves this problem by erasing the type information and
-//! tracking the pin at run-time.
-//!
-//! Value-level pins are represented by the [`DynPin`] type. [`DynPin`] has two
-//! fields, `id` and `mode` with types [`DynPinId`] and [`DynPinMode`]
-//! respectively. The implementation of these types closely mirrors the
-//! type-level API.
-//!
-//! Instances of [`DynPin`] cannot be created directly. Rather, they must be
-//! created from their type-level equivalents using [`From`]/[`Into`].
-//!
-//! ```
-//! // Move a pin out of the Pins struct and convert to a DynPin
-//! let pa0: DynPin = pins.pa0.into();
-//! ```
-//!
-//! Conversions between pin modes use a value-level version of the type-level
-//! API.
-//!
-//! ```
-//! // Use one of the literal function names
-//! pa0.into_floating_input();
-//! // Use a method and a DynPinMode variant
-//! pa0.into_mode(DYN_FLOATING_INPUT);
-//! ```
-//!
-//! Because the pin state cannot be tracked at compile-time, many [`DynPin`]
-//! operations become fallible. Run-time checks are inserted to ensure that
-//! users don't try to, for example, set the output level of an input pin.
-//!
-//! Users may try to convert value-level pins back to their type-level
-//! equivalents. However, this option is fallible, because the compiler cannot
-//! guarantee the pin has the correct ID or is in the correct mode at
-//! compile-time. Use [TryFrom]/[TryInto] for this conversion.
-//!
-//! ```
-//! // Convert to a `DynPin`
-//! let pa0: DynPin = pins.pa0.into();
-//! // Change pin mode
-//! pa0.into_floating_input();
-//! // Convert back to a `Pin`
-//! let pa0: Pin<PA0, FloatingInput> = pa0.try_into().unwrap();
-//! ```
-//!
-//! # Embedded HAL traits
-//!
-//! This module implements all of the embedded HAL GPIO traits for [`DynPin`].
-//! However, whereas the type-level API uses
-//! `Error = core::convert::Infallible`, the value-level API can return a real
-//! error. If the [`DynPin`] is not in the correct [`DynPinMode`] for the
-//! operation, the trait functions will return
-//! [InvalidPinTypeError].
-
-use crate::FunSel;
-
-use va416xx as pac;
-
-use super::{
-    AsyncDynPinError, FilterClkSel, FilterType, InputDynPinAsync, InterruptEdge, InterruptLevel,
-    IsMaskedError, Pin, PinId, PinMode, PinState, Port,
-};
-
-//==================================================================================================
-//  DynPinMode configurations
-//==================================================================================================
-
-/// Value-level `enum` for disabled configurations
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum DynDisabled {
-    Floating,
-    PullDown,
-    PullUp,
-}
-
-/// Value-level `enum` for input configurations
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum DynInput {
-    Floating,
-    PullDown,
-    PullUp,
-}
-
-/// Value-level `enum` for output configurations
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum DynOutput {
-    PushPull,
-    OpenDrain,
-    ReadablePushPull,
-    ReadableOpenDrain,
-}
-
-pub type DynAlternate = crate::FunSel;
-
-//==============================================================================
-//  Error
-//==============================================================================
-
-/// GPIO error type
-///
-/// [`DynPin`]s are not tracked and verified at compile-time, so run-time
-/// operations are fallible. This `enum` represents the corresponding errors.
-#[derive(Debug, PartialEq, Eq, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[error("Invalid pin type for operation: {0:?}")]
-pub struct InvalidPinTypeError(pub DynPinMode);
-
-impl embedded_hal::digital::Error for InvalidPinTypeError {
-    fn kind(&self) -> embedded_hal::digital::ErrorKind {
-        embedded_hal::digital::ErrorKind::Other
-    }
-}
-
-//==================================================================================================
-//  DynPinMode
-//==================================================================================================
-
-/// Value-level `enum` representing pin modes
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum DynPinMode {
-    Input(DynInput),
-    Output(DynOutput),
-    Alternate(DynAlternate),
-}
-
-/// Value-level variant of [`DynPinMode`] for floating input mode
-pub const DYN_FLOATING_INPUT: DynPinMode = DynPinMode::Input(DynInput::Floating);
-/// Value-level variant of [`DynPinMode`] for pull-down input mode
-pub const DYN_PULL_DOWN_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullDown);
-/// Value-level variant of [`DynPinMode`] for pull-up input mode
-pub const DYN_PULL_UP_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullUp);
-
-/// Value-level variant of [`DynPinMode`] for push-pull output mode
-pub const DYN_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::PushPull);
-/// Value-level variant of [`DynPinMode`] for open-drain output mode
-pub const DYN_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::OpenDrain);
-/// Value-level variant of [`DynPinMode`] for readable push-pull output mode
-pub const DYN_RD_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadablePushPull);
-/// Value-level variant of [`DynPinMode`] for readable opendrain output mode
-pub const DYN_RD_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadableOpenDrain);
-
-/// Value-level variant of [`DynPinMode`] for function select 1
-pub const DYN_ALT_FUNC_1: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel1);
-/// Value-level variant of [`DynPinMode`] for function select 2
-pub const DYN_ALT_FUNC_2: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel2);
-/// Value-level variant of [`DynPinMode`] for function select 3
-pub const DYN_ALT_FUNC_3: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel3);
-
-//==================================================================================================
-//  DynGroup & DynPinId
-//==================================================================================================
-
-pub type DynGroup = Port;
-
-#[inline]
-pub const fn irq_id(port: Port, num: u8) -> Option<va416xx::Interrupt> {
-    match port {
-        Port::A => match num {
-            0 => Some(va416xx::Interrupt::PORTA0),
-            1 => Some(va416xx::Interrupt::PORTA1),
-            2 => Some(va416xx::Interrupt::PORTA2),
-            3 => Some(va416xx::Interrupt::PORTA3),
-            4 => Some(va416xx::Interrupt::PORTA4),
-            5 => Some(va416xx::Interrupt::PORTA5),
-            6 => Some(va416xx::Interrupt::PORTA6),
-            7 => Some(va416xx::Interrupt::PORTA7),
-            8 => Some(va416xx::Interrupt::PORTA8),
-            9 => Some(va416xx::Interrupt::PORTA9),
-            10 => Some(va416xx::Interrupt::PORTA10),
-            11 => Some(va416xx::Interrupt::PORTA11),
-            12 => Some(va416xx::Interrupt::PORTA12),
-            13 => Some(va416xx::Interrupt::PORTA13),
-            14 => Some(va416xx::Interrupt::PORTA14),
-            15 => Some(va416xx::Interrupt::PORTA15),
-            _ => None,
-        },
-        Port::B => match num {
-            0 => Some(va416xx::Interrupt::PORTB0),
-            1 => Some(va416xx::Interrupt::PORTB1),
-            2 => Some(va416xx::Interrupt::PORTB2),
-            3 => Some(va416xx::Interrupt::PORTB3),
-            4 => Some(va416xx::Interrupt::PORTB4),
-            5 => Some(va416xx::Interrupt::PORTB5),
-            6 => Some(va416xx::Interrupt::PORTB6),
-            7 => Some(va416xx::Interrupt::PORTB7),
-            8 => Some(va416xx::Interrupt::PORTB8),
-            9 => Some(va416xx::Interrupt::PORTB9),
-            10 => Some(va416xx::Interrupt::PORTB10),
-            11 => Some(va416xx::Interrupt::PORTB11),
-            12 => Some(va416xx::Interrupt::PORTB12),
-            13 => Some(va416xx::Interrupt::PORTB13),
-            14 => Some(va416xx::Interrupt::PORTB14),
-            15 => Some(va416xx::Interrupt::PORTB15),
-            _ => None,
-        },
-        Port::C => match num {
-            0 => Some(va416xx::Interrupt::PORTC0),
-            1 => Some(va416xx::Interrupt::PORTC1),
-            2 => Some(va416xx::Interrupt::PORTC2),
-            3 => Some(va416xx::Interrupt::PORTC3),
-            4 => Some(va416xx::Interrupt::PORTC4),
-            5 => Some(va416xx::Interrupt::PORTC5),
-            6 => Some(va416xx::Interrupt::PORTC6),
-            7 => Some(va416xx::Interrupt::PORTC7),
-            8 => Some(va416xx::Interrupt::PORTC8),
-            9 => Some(va416xx::Interrupt::PORTC9),
-            10 => Some(va416xx::Interrupt::PORTC10),
-            11 => Some(va416xx::Interrupt::PORTC11),
-            12 => Some(va416xx::Interrupt::PORTC12),
-            13 => Some(va416xx::Interrupt::PORTC13),
-            14 => Some(va416xx::Interrupt::PORTC14),
-            15 => Some(va416xx::Interrupt::PORTC15),
-            _ => None,
-        },
-        Port::D => match num {
-            0 => Some(va416xx::Interrupt::PORTD0),
-            1 => Some(va416xx::Interrupt::PORTD1),
-            2 => Some(va416xx::Interrupt::PORTD2),
-            3 => Some(va416xx::Interrupt::PORTD3),
-            4 => Some(va416xx::Interrupt::PORTD4),
-            5 => Some(va416xx::Interrupt::PORTD5),
-            6 => Some(va416xx::Interrupt::PORTD6),
-            7 => Some(va416xx::Interrupt::PORTD7),
-            8 => Some(va416xx::Interrupt::PORTD8),
-            9 => Some(va416xx::Interrupt::PORTD9),
-            10 => Some(va416xx::Interrupt::PORTD10),
-            11 => Some(va416xx::Interrupt::PORTD11),
-            12 => Some(va416xx::Interrupt::PORTD12),
-            13 => Some(va416xx::Interrupt::PORTD13),
-            14 => Some(va416xx::Interrupt::PORTD14),
-            15 => Some(va416xx::Interrupt::PORTD15),
-            _ => None,
-        },
-        Port::E => match num {
-            0 => Some(va416xx::Interrupt::PORTE0),
-            1 => Some(va416xx::Interrupt::PORTE1),
-            2 => Some(va416xx::Interrupt::PORTE2),
-            3 => Some(va416xx::Interrupt::PORTE3),
-            4 => Some(va416xx::Interrupt::PORTE4),
-            5 => Some(va416xx::Interrupt::PORTE5),
-            6 => Some(va416xx::Interrupt::PORTE6),
-            7 => Some(va416xx::Interrupt::PORTE7),
-            8 => Some(va416xx::Interrupt::PORTE8),
-            9 => Some(va416xx::Interrupt::PORTE9),
-            10 => Some(va416xx::Interrupt::PORTE10),
-            11 => Some(va416xx::Interrupt::PORTE11),
-            12 => Some(va416xx::Interrupt::PORTE12),
-            13 => Some(va416xx::Interrupt::PORTE13),
-            14 => Some(va416xx::Interrupt::PORTE14),
-            15 => Some(va416xx::Interrupt::PORTE15),
-            _ => None,
-        },
-        Port::F => match num {
-            0 => Some(va416xx::Interrupt::PORTF0),
-            1 => Some(va416xx::Interrupt::PORTF1),
-            2 => Some(va416xx::Interrupt::PORTF2),
-            3 => Some(va416xx::Interrupt::PORTF3),
-            4 => Some(va416xx::Interrupt::PORTF4),
-            5 => Some(va416xx::Interrupt::PORTF5),
-            6 => Some(va416xx::Interrupt::PORTF6),
-            7 => Some(va416xx::Interrupt::PORTF7),
-            8 => Some(va416xx::Interrupt::PORTF8),
-            9 => Some(va416xx::Interrupt::PORTF9),
-            10 => Some(va416xx::Interrupt::PORTF10),
-            11 => Some(va416xx::Interrupt::PORTF11),
-            12 => Some(va416xx::Interrupt::PORTF12),
-            13 => Some(va416xx::Interrupt::PORTF13),
-            14 => Some(va416xx::Interrupt::PORTF14),
-            15 => Some(va416xx::Interrupt::PORTF15),
-            _ => None,
-        },
-        Port::G => None,
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct DynPinId {
-    port: Port,
-    num: u8,
-}
-
-impl DynPinId {
-    pub const fn new(port: Port, num: u8) -> Self {
-        DynPinId { port, num }
-    }
-
-    pub const fn port(&self) -> Port {
-        self.port
-    }
-    pub const fn num(&self) -> u8 {
-        self.num
-    }
-}
-
-//==================================================================================================
-//  ModeFields
-//==================================================================================================
-
-/// Collect all fields needed to set the [`PinMode`](super::PinMode)
-#[derive(Default)]
-struct ModeFields {
-    dir: bool,
-    opendrn: bool,
-    pull_en: bool,
-    /// true for pullup, false for pulldown
-    pull_dir: bool,
-    funsel: u8,
-    enb_input: bool,
-}
-
-impl From<DynPinMode> for ModeFields {
-    #[inline]
-    fn from(mode: DynPinMode) -> Self {
-        let mut fields = Self::default();
-        match mode {
-            DynPinMode::Input(config) => {
-                fields.dir = false;
-                fields.funsel = FunSel::Sel0 as u8;
-                match config {
-                    DynInput::Floating => (),
-                    DynInput::PullUp => {
-                        fields.pull_en = true;
-                        fields.pull_dir = true;
-                    }
-                    DynInput::PullDown => {
-                        fields.pull_en = true;
-                    }
-                }
-            }
-            DynPinMode::Output(config) => {
-                fields.dir = true;
-                fields.funsel = FunSel::Sel0 as u8;
-                match config {
-                    DynOutput::PushPull => (),
-                    DynOutput::OpenDrain => {
-                        fields.opendrn = true;
-                    }
-                    DynOutput::ReadableOpenDrain => {
-                        fields.enb_input = true;
-                        fields.opendrn = true;
-                    }
-                    DynOutput::ReadablePushPull => {
-                        fields.enb_input = true;
-                    }
-                }
-            }
-            DynPinMode::Alternate(config) => {
-                fields.funsel = config as u8;
-            }
-        }
-        fields
-    }
-}
-
-/// Type definition to avoid confusion: These register blocks are identical
-type PortRegisterBlock = pac::porta::RegisterBlock;
-pub type PortReg = pac::ioconfig::Porta;
-
-//==================================================================================================
-//  DynPin
-//==================================================================================================
-
-/// A value-level pin, parameterized by [`DynPinId`] and [`DynPinMode`]
-///
-/// This type acts as a type-erased version of [`Pin`]. Every pin is represented
-/// by the same type, and pins are tracked and distinguished at run-time.
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct DynPin {
-    id: DynPinId,
-    mode: DynPinMode,
-}
-
-impl DynPin {
-    /// Create a new [DynPin]
-    ///
-    /// # Safety
-    ///
-    /// Each [DynPin] must be a singleton. For a given [DynPinId], there
-    /// must be at most one corresponding [`DynPin`] in existence at any given
-    /// time.  Violating this requirement is `unsafe`.
-    #[inline]
-    pub(crate) const unsafe fn new(id: DynPinId, mode: DynPinMode) -> Self {
-        DynPin { id, mode }
-    }
-
-    /// Steals a new [DynPin].
-    ///
-    /// This function will simply set the internal mode to [DYN_FLOATING_INPUT] pin without
-    /// modifying any registers related to the behaviour of the pin. The user should call
-    /// [Self::into_mode] to ensure the correct mode of the pin.
-    ///
-    /// # Safety
-    ///
-    /// Circumvents the HAL's safety guarantees. The caller must ensure that the pin is not
-    /// used cocurrently somewhere else. The caller might also want to call [Self::into_mode]
-    /// to ensure the correct desired state of the pin. It is recommended to create the pin using
-    /// [Pin::downgrade] instead.
-    pub const unsafe fn steal(id: DynPinId) -> Self {
-        DynPin {
-            id,
-            mode: DYN_FLOATING_INPUT,
-        }
-    }
-
-    /// Return a copy of the pin ID
-    #[inline]
-    pub const fn id(&self) -> DynPinId {
-        self.id
-    }
-
-    #[inline]
-    pub const fn irq_id(&self) -> Option<va416xx::Interrupt> {
-        irq_id(self.id.port(), self.id.num())
-    }
-
-    /// Return a copy of the pin mode
-    #[inline]
-    pub const fn mode(&self) -> DynPinMode {
-        self.mode
-    }
-
-    /// Convert the pin to the requested [`DynPinMode`]
-    #[inline]
-    pub fn into_mode(&mut self, mode: DynPinMode) {
-        self.change_mode(mode);
-        self.mode = mode;
-    }
-
-    #[inline]
-    pub fn is_input_pin(&self) -> bool {
-        matches!(self.mode, DynPinMode::Input(_))
-    }
-
-    #[inline]
-    pub fn is_output_pin(&self) -> bool {
-        matches!(self.mode, DynPinMode::Output(_))
-    }
-
-    /// Configure the pin for function select 1. See Programmer Guide p.286 for the function table
-    #[inline]
-    pub fn into_funsel_1(&mut self) {
-        self.into_mode(DYN_ALT_FUNC_1);
-    }
-
-    /// Configure the pin for function select 2. See Programmer Guide p.286 for the function table
-    #[inline]
-    pub fn into_funsel_2(&mut self) {
-        self.into_mode(DYN_ALT_FUNC_2);
-    }
-
-    /// Configure the pin for function select 3. See Programmer Guide p.286 for the function table
-    #[inline]
-    pub fn into_funsel_3(&mut self) {
-        self.into_mode(DYN_ALT_FUNC_3);
-    }
-
-    /// Configure the pin to operate as a floating input
-    #[inline]
-    pub fn into_floating_input(&mut self) {
-        self.into_mode(DYN_FLOATING_INPUT);
-    }
-
-    /// Configure the pin to operate as a pulled down input
-    #[inline]
-    pub fn into_pull_down_input(&mut self) {
-        self.into_mode(DYN_PULL_DOWN_INPUT);
-    }
-
-    /// Configure the pin to operate as a pulled up input
-    #[inline]
-    pub fn into_pull_up_input(&mut self) {
-        self.into_mode(DYN_PULL_UP_INPUT);
-    }
-
-    /// Configure the pin to operate as a push-pull output
-    #[inline]
-    pub fn into_push_pull_output(&mut self) {
-        self.into_mode(DYN_PUSH_PULL_OUTPUT);
-    }
-
-    /// Configure the pin to operate as a push-pull output
-    #[inline]
-    pub fn into_open_drain_output(&mut self) {
-        self.into_mode(DYN_OPEN_DRAIN_OUTPUT);
-    }
-
-    /// Configure the pin to operate as a push-pull output
-    #[inline]
-    pub fn into_readable_push_pull_output(&mut self) {
-        self.into_mode(DYN_RD_PUSH_PULL_OUTPUT);
-    }
-
-    /// Configure the pin to operate as a push-pull output
-    #[inline]
-    pub fn into_readable_open_drain_output(&mut self) {
-        self.into_mode(DYN_RD_OPEN_DRAIN_OUTPUT);
-    }
-
-    #[inline(always)]
-    pub fn is_low(&self) -> Result<bool, InvalidPinTypeError> {
-        self.read_internal().map(|v| !v)
-    }
-
-    #[inline(always)]
-    pub fn is_high(&self) -> Result<bool, InvalidPinTypeError> {
-        self.read_internal()
-    }
-
-    #[inline(always)]
-    pub fn set_low(&mut self) -> Result<(), InvalidPinTypeError> {
-        self.write_internal(false)
-    }
-
-    #[inline(always)]
-    pub fn set_high(&mut self) -> Result<(), InvalidPinTypeError> {
-        self.write_internal(true)
-    }
-
-    /// Toggle the logic level of an output pin
-    #[inline(always)]
-    pub fn toggle(&mut self) -> Result<(), InvalidPinTypeError> {
-        if !self.is_output_pin() {
-            return Err(InvalidPinTypeError(self.mode));
-        }
-        // Safety: TOGOUT is a "mask" register, and we only write the bit for
-        // this pin ID
-        unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) };
-        Ok(())
-    }
-
-    #[inline(always)]
-    pub fn enable_interrupt(&self) {
-        self.port_reg()
-            .irq_enb()
-            .modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
-    }
-
-    #[inline(always)]
-    pub fn disable_interrupt(&self) {
-        self.port_reg()
-            .irq_enb()
-            .modify(|r, w| unsafe { w.bits(r.bits() & !self.mask_32()) });
-    }
-
-    /// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
-    ///
-    /// There is no way for the compiler to know if the conversion will be
-    /// successful at compile-time. We must verify the conversion at run-time
-    /// or refuse to perform it.
-    #[inline]
-    pub fn upgrade<I: PinId, M: PinMode>(self) -> Result<Pin<I, M>, InvalidPinTypeError> {
-        if self.id == I::DYN && self.mode == M::DYN {
-            // The `DynPin` is consumed, so it is safe to replace it with the
-            // corresponding `Pin`
-            return Ok(unsafe { Pin::new() });
-        }
-        Err(InvalidPinTypeError(self.mode))
-    }
-
-    /// Convert the pin into an async pin. The pin can be converted back by calling
-    /// [InputDynPinAsync::release]
-    pub fn into_async_input(self) -> Result<InputDynPinAsync, AsyncDynPinError> {
-        InputDynPinAsync::new(self)
-    }
-
-    // Get DATAMASK bit for this particular pin
-    #[inline(always)]
-    pub fn datamask(&self) -> bool {
-        (self.port_reg().datamask().read().bits() >> self.id().num) == 1
-    }
-
-    /// Clear DATAMASK bit for this particular pin. This prevents access
-    /// of the corresponding bit for output and input operations
-    #[inline(always)]
-    pub fn clear_datamask(&self) {
-        self.port_reg()
-            .datamask()
-            .modify(|r, w| unsafe { w.bits(r.bits() & !self.mask_32()) });
-    }
-
-    /// Set DATAMASK bit for this particular pin. 1 is the default
-    /// state of the bit and allows access of the corresponding bit
-    #[inline(always)]
-    pub fn set_datamask(&self) {
-        self.port_reg()
-            .datamask()
-            .modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) });
-    }
-
-    #[inline]
-    pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
-        self.read_pin_masked()
-    }
-
-    #[inline]
-    pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
-        self.read_pin_masked().map(|v| !v)
-    }
-
-    #[inline]
-    pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
-        self.write_pin_masked(true)
-    }
-
-    #[inline]
-    pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
-        self.write_pin_masked(false)
-    }
-
-    /// See p.293 of the programmers guide for more information.
-    /// Possible delays in clock cycles:
-    ///  - Delay 1: 1
-    ///  - Delay 2: 2
-    ///  - Delay 1 + Delay 2: 3
-    #[inline]
-    pub fn configure_delay(
-        &mut self,
-        delay_1: bool,
-        delay_2: bool,
-    ) -> Result<(), InvalidPinTypeError> {
-        match self.mode {
-            DynPinMode::Output(_) => {
-                self.configure_delay_internal(delay_1, delay_2);
-                Ok(())
-            }
-            _ => Err(InvalidPinTypeError(self.mode)),
-        }
-    }
-
-    /// See p.293 of the programmers guide for more information.
-    /// When configured for pulse mode, a given pin will set the non-default state for exactly
-    /// one clock cycle before returning to the configured default state
-    pub fn configure_pulse_mode(
-        &mut self,
-        enable: bool,
-        default_state: PinState,
-    ) -> Result<(), InvalidPinTypeError> {
-        match self.mode {
-            DynPinMode::Output(_) => {
-                self.configure_pulse_mode_internal(enable, default_state);
-                Ok(())
-            }
-            _ => Err(InvalidPinTypeError(self.mode)),
-        }
-    }
-
-    /// See p.284 of the programmers guide for more information.
-    #[inline]
-    pub fn configure_filter_type(
-        &mut self,
-        filter: FilterType,
-        clksel: FilterClkSel,
-    ) -> Result<(), InvalidPinTypeError> {
-        match self.mode {
-            DynPinMode::Input(_) => {
-                self.configure_filter_type_internal(filter, clksel);
-                Ok(())
-            }
-            _ => Err(InvalidPinTypeError(self.mode)),
-        }
-    }
-
-    pub fn configure_edge_interrupt(
-        &mut self,
-        edge_type: InterruptEdge,
-    ) -> Result<(), InvalidPinTypeError> {
-        match self.mode {
-            DynPinMode::Input(_) | DynPinMode::Output(_) => {
-                self.configure_edge_interrupt_internal(edge_type);
-                Ok(())
-            }
-            _ => Err(InvalidPinTypeError(self.mode)),
-        }
-    }
-
-    pub fn configure_level_interrupt(
-        &mut self,
-        level_type: InterruptLevel,
-    ) -> Result<(), InvalidPinTypeError> {
-        match self.mode {
-            DynPinMode::Input(_) | DynPinMode::Output(_) => {
-                self.configure_level_interrupt_internal(level_type);
-                Ok(())
-            }
-            _ => Err(InvalidPinTypeError(self.mode)),
-        }
-    }
-
-    #[inline(always)]
-    fn read_internal(&self) -> Result<bool, InvalidPinTypeError> {
-        match self.mode {
-            DynPinMode::Input(_) | DYN_RD_OPEN_DRAIN_OUTPUT | DYN_RD_PUSH_PULL_OUTPUT => {
-                Ok(self.read_pin())
-            }
-            _ => Err(InvalidPinTypeError(self.mode)),
-        }
-    }
-
-    #[inline(always)]
-    fn write_internal(&mut self, bit: bool) -> Result<(), InvalidPinTypeError> {
-        match self.mode {
-            DynPinMode::Output(_) => {
-                self.write_pin(bit);
-                Ok(())
-            }
-            _ => Err(InvalidPinTypeError(self.mode)),
-        }
-    }
-
-    /// Change the pin mode
-    #[inline]
-    pub(crate) fn change_mode(&mut self, mode: DynPinMode) {
-        let ModeFields {
-            dir,
-            funsel,
-            opendrn,
-            pull_dir,
-            pull_en,
-            enb_input,
-        } = mode.into();
-        let (portreg, iocfg) = (self.port_reg(), self.iocfg_port());
-        iocfg.write(|w| {
-            w.opendrn().bit(opendrn);
-            w.pen().bit(pull_en);
-            w.plevel().bit(pull_dir);
-            w.iewo().bit(enb_input);
-            unsafe { w.funsel().bits(funsel) }
-        });
-        let mask = self.mask_32();
-        unsafe {
-            if dir {
-                portreg.dir().modify(|r, w| w.bits(r.bits() | mask));
-                // Clear output
-                portreg.clrout().write(|w| w.bits(mask));
-            } else {
-                portreg.dir().modify(|r, w| w.bits(r.bits() & !mask));
-            }
-        }
-    }
-
-    #[inline(always)]
-    const fn port_reg(&self) -> &PortRegisterBlock {
-        match self.id().port() {
-            Port::A => unsafe { &(*pac::Porta::ptr()) },
-            Port::B => unsafe { &(*pac::Portb::ptr()) },
-            Port::C => unsafe { &(*pac::Portc::ptr()) },
-            Port::D => unsafe { &(*pac::Portd::ptr()) },
-            Port::E => unsafe { &(*pac::Porte::ptr()) },
-            Port::F => unsafe { &(*pac::Portf::ptr()) },
-            Port::G => unsafe { &(*pac::Portg::ptr()) },
-        }
-    }
-
-    #[inline(always)]
-    const fn iocfg_port(&self) -> &PortReg {
-        let ioconfig = unsafe { pac::Ioconfig::ptr().as_ref().unwrap() };
-        match self.id().port() {
-            Port::A => ioconfig.porta(self.id().num() as usize),
-            Port::B => ioconfig.portb0(self.id().num() as usize),
-            Port::C => ioconfig.portc0(self.id().num() as usize),
-            Port::D => ioconfig.portd0(self.id().num() as usize),
-            Port::E => ioconfig.porte0(self.id().num() as usize),
-            Port::F => ioconfig.portf0(self.id().num() as usize),
-            Port::G => ioconfig.portg0(self.id().num() as usize),
-        }
-    }
-
-    #[inline(always)]
-    const fn mask_32(&self) -> u32 {
-        1 << self.id().num()
-    }
-
-    #[inline]
-    /// Read the logic level of an output pin
-    pub(crate) fn read_pin(&self) -> bool {
-        let portreg = self.port_reg();
-        ((portreg.datainraw().read().bits() >> self.id().num) & 0x01) == 1
-    }
-
-    /// Read a pin but use the masked version but check whether the datamask for the pin is
-    /// cleared as well
-    #[inline(always)]
-    fn read_pin_masked(&self) -> Result<bool, IsMaskedError> {
-        if !self.datamask() {
-            Err(IsMaskedError)
-        } else {
-            Ok(((self.port_reg().datain().read().bits() >> self.id().num) & 0x01) == 1)
-        }
-    }
-
-    /// Write the logic level of an output pin
-    #[inline(always)]
-    pub(crate) fn write_pin(&mut self, bit: bool) {
-        // Safety: SETOUT is a "mask" register, and we only write the bit for
-        // this pin ID
-        unsafe {
-            if bit {
-                self.port_reg().setout().write(|w| w.bits(self.mask_32()));
-            } else {
-                self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
-            }
-        }
-    }
-
-    /// Write the logic level of an output pin but check whether the datamask for the pin is
-    /// cleared as well
-    #[inline]
-    fn write_pin_masked(&mut self, bit: bool) -> Result<(), IsMaskedError> {
-        if !self.datamask() {
-            Err(IsMaskedError)
-        } else {
-            // Safety: SETOUT is a "mask" register, and we only write the bit for
-            // this pin ID
-            unsafe {
-                if bit {
-                    self.port_reg().setout().write(|w| w.bits(self.mask_32()));
-                } else {
-                    self.port_reg().clrout().write(|w| w.bits(self.mask_32()));
-                }
-                Ok(())
-            }
-        }
-    }
-
-    /// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure
-    /// When using edge mode, it is possible to generate interrupts on both edges as well
-    #[inline]
-    fn configure_edge_interrupt_internal(&mut self, edge_type: InterruptEdge) {
-        unsafe {
-            self.port_reg()
-                .irq_sen()
-                .modify(|r, w| w.bits(r.bits() & !self.mask_32()));
-            match edge_type {
-                InterruptEdge::HighToLow => {
-                    self.port_reg()
-                        .irq_evt()
-                        .modify(|r, w| w.bits(r.bits() & !self.mask_32()));
-                }
-                InterruptEdge::LowToHigh => {
-                    self.port_reg()
-                        .irq_evt()
-                        .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-                }
-                InterruptEdge::BothEdges => {
-                    self.port_reg()
-                        .irq_edge()
-                        .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-                }
-            }
-        }
-    }
-
-    /// Configure which edge or level type triggers an interrupt
-    #[inline]
-    fn configure_level_interrupt_internal(&mut self, level: InterruptLevel) {
-        unsafe {
-            self.port_reg()
-                .irq_sen()
-                .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-            if level == InterruptLevel::Low {
-                self.port_reg()
-                    .irq_evt()
-                    .modify(|r, w| w.bits(r.bits() & !self.mask_32()));
-            } else {
-                self.port_reg()
-                    .irq_evt()
-                    .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-            }
-        }
-    }
-
-    /// Only useful for input pins
-    #[inline]
-    fn configure_filter_type_internal(&mut self, filter: FilterType, clksel: FilterClkSel) {
-        self.iocfg_port().modify(|_, w| {
-            // Safety: Only write to register for this Pin ID
-            unsafe {
-                w.flttype().bits(filter as u8);
-                w.fltclk().bits(clksel as u8)
-            }
-        });
-    }
-
-    /// Only useful for output pins
-    /// See p.293 of the programmers guide for more information.
-    /// When configured for pulse mode, a given pin will set the non-default state for exactly
-    /// one clock cycle before returning to the configured default state
-    #[inline]
-    fn configure_pulse_mode_internal(&mut self, enable: bool, default_state: PinState) {
-        let portreg = self.port_reg();
-        unsafe {
-            if enable {
-                portreg
-                    .pulse()
-                    .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-            } else {
-                portreg
-                    .pulse()
-                    .modify(|r, w| w.bits(r.bits() & !self.mask_32()));
-            }
-            if default_state == PinState::Low {
-                portreg
-                    .pulsebase()
-                    .modify(|r, w| w.bits(r.bits() & !self.mask_32()));
-            } else {
-                portreg
-                    .pulsebase()
-                    .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-            }
-        }
-    }
-
-    /// Only useful for output pins
-    #[inline]
-    fn configure_delay_internal(&mut self, delay_1: bool, delay_2: bool) {
-        let portreg = self.port_reg();
-        unsafe {
-            if delay_1 {
-                portreg
-                    .delay1()
-                    .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-            } else {
-                portreg
-                    .delay1()
-                    .modify(|r, w| w.bits(r.bits() & !self.mask_32()));
-            }
-            if delay_2 {
-                portreg
-                    .delay2()
-                    .modify(|r, w| w.bits(r.bits() | self.mask_32()));
-            } else {
-                portreg
-                    .delay2()
-                    .modify(|r, w| w.bits(r.bits() & !self.mask_32()));
-            }
-        }
-    }
-
-    // Only serves disambiguation purposes for the Embedded HAL impl
-    #[inline(always)]
-    fn is_low_mut(&mut self) -> Result<bool, InvalidPinTypeError> {
-        self.is_low()
-    }
-
-    // Only serves disambiguation purposes for the Embedded HAL impl
-    #[inline(always)]
-    fn is_high_mut(&mut self) -> Result<bool, InvalidPinTypeError> {
-        self.is_high()
-    }
-}
-
-//==============================================================================
-//  Convert between Pin and DynPin
-//==============================================================================
-
-impl<I, M> From<Pin<I, M>> for DynPin
-where
-    I: PinId,
-    M: PinMode,
-{
-    /// Erase the type-level information in a [`Pin`] and return a value-level
-    /// [`DynPin`]
-    #[inline]
-    fn from(pin: Pin<I, M>) -> Self {
-        pin.downgrade()
-    }
-}
-
-impl<I, M> TryFrom<DynPin> for Pin<I, M>
-where
-    I: PinId,
-    M: PinMode,
-{
-    type Error = InvalidPinTypeError;
-
-    /// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`]
-    ///
-    /// There is no way for the compiler to know if the conversion will be
-    /// successful at compile-time. We must verify the conversion at run-time
-    /// or refuse to perform it.
-    #[inline]
-    fn try_from(pin: DynPin) -> Result<Self, Self::Error> {
-        pin.upgrade()
-    }
-}
-
-//==============================================================================
-// Embedded HAL v1 traits
-//==============================================================================
-
-impl embedded_hal::digital::ErrorType for DynPin {
-    type Error = InvalidPinTypeError;
-}
-
-impl embedded_hal::digital::OutputPin for DynPin {
-    #[inline]
-    fn set_high(&mut self) -> Result<(), Self::Error> {
-        self.set_high()
-    }
-    #[inline]
-    fn set_low(&mut self) -> Result<(), Self::Error> {
-        self.set_low()
-    }
-}
-
-impl embedded_hal::digital::InputPin for DynPin {
-    #[inline]
-    fn is_high(&mut self) -> Result<bool, Self::Error> {
-        self.is_high_mut()
-    }
-    #[inline]
-    fn is_low(&mut self) -> Result<bool, Self::Error> {
-        self.is_low_mut()
-    }
-}
-
-impl embedded_hal::digital::StatefulOutputPin for DynPin {
-    #[inline]
-    fn is_set_high(&mut self) -> Result<bool, Self::Error> {
-        self.is_high_mut()
-    }
-    #[inline]
-    fn is_set_low(&mut self) -> Result<bool, Self::Error> {
-        self.is_low_mut()
-    }
-
-    #[inline]
-    fn toggle(&mut self) -> Result<(), Self::Error> {
-        self.toggle()
-    }
-}
diff --git a/va416xx-hal/src/gpio/mod.rs b/va416xx-hal/src/gpio/mod.rs
index a01f8c7..761ac39 100644
--- a/va416xx-hal/src/gpio/mod.rs
+++ b/va416xx-hal/src/gpio/mod.rs
@@ -1,80 +1,14 @@
-//! # API for the GPIO peripheral
+//! # API for the UART peripheral
 //!
-//! The implementation of this GPIO module is heavily based on the
-//! [ATSAMD HAL implementation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/index.html).
+//! The core of this API are the [Uart], [Rx] and [Tx] structures.
+//! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver
+//! using interrupts.
 //!
-//! This API provides two different submodules, [pin] and [dynpin],
-//! representing two different ways to handle GPIO pins. The default, [pin],
-//! is a type-level API that tracks the state of each pin at compile-time. The
-//! alternative, [dynpin] is a type-erased, value-level API that tracks the
-//! state of each pin at run-time.
-//!
-//! The type-level API is strongly preferred. By representing the state of each
-//! pin within the type system, the compiler can detect logic errors at
-//! compile-time. Furthermore, the type-level API has absolutely zero run-time
-//! cost.
-//!
-//! If needed, [dynpin] can be used to erase the type-level differences
-//! between pins. However, by doing so, pins must now be tracked at run-time,
-//! and each pin has a non-zero memory footprint.
+//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART
+//! peripheral.
 //!
 //! ## Examples
 //!
 //! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
-
-//==================================================================================================
-//  Errors, Definitions and Constants
-//==================================================================================================
-
-pub const NUM_PINS_PORT_A_TO_F: usize = 16;
-pub const NUM_PINS_PORT_G: usize = 8;
-pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A_TO_F * 6 + NUM_PINS_PORT_G;
-pub const NUM_GPIO_PINS_WITH_IRQ: usize = NUM_GPIO_PINS - NUM_PINS_PORT_G;
-
-#[derive(Debug, PartialEq, Eq, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[error("pin is masked")]
-pub struct IsMaskedError;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum Port {
-    A,
-    B,
-    C,
-    D,
-    E,
-    F,
-    G,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum InterruptEdge {
-    HighToLow,
-    LowToHigh,
-    BothEdges,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum InterruptLevel {
-    Low = 0,
-    High = 1,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum PinState {
-    Low = 0,
-    High = 1,
-}
-
-pub mod pin;
-pub use pin::*;
-
-pub mod dynpin;
-pub use dynpin::*;
-
-pub mod asynch;
-pub use asynch::*;
+//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs)
+pub use vorago_shared_periphs::gpio::*;
diff --git a/va416xx-hal/src/gpio/pin.rs b/va416xx-hal/src/gpio/pin.rs
deleted file mode 100644
index 07927df..0000000
--- a/va416xx-hal/src/gpio/pin.rs
+++ /dev/null
@@ -1,935 +0,0 @@
-//! # Type-level module for GPIO pins
-//!
-//! This documentation is strongly based on the
-//! [atsamd documentation](https://docs.rs/atsamd-hal/latest/atsamd_hal/gpio/pin/index.html).
-//!
-//! This module provides a type-level API for GPIO pins. It uses the type system
-//! to track the state of pins at compile-time. Representing GPIO pins in this
-//! manner incurs no run-time overhead. Each [`Pin`] struct is zero-sized, so
-//! there is no data to copy around. Instead, real code is generated as a side
-//! effect of type transformations, and the resulting assembly is nearly
-//! identical to the equivalent, hand-written C.
-//!
-//! To track the state of pins at compile-time, this module uses traits to
-//! represent [type classes] and types as instances of those type classes. For
-//! example, the trait [`InputConfig`] acts as a [type-level enum] of the
-//! available input configurations, and the types [`Floating`], [`PullDown`] and
-//! [`PullUp`] are its type-level variants.
-//!
-//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
-//! [`PinMode`].
-//!
-//! ```
-//! pub struct Pin<I, M>
-//! where
-//!     I: PinId,
-//!     M: PinMode,
-//! {
-//!     // ...
-//! }
-//! ```
-//!
-//! A `PinId` identifies a pin by it's group (A to G) and pin number. Each
-//! `PinId` instance is named according to its datasheet identifier, e.g.
-//! [PA2].
-//!
-//! A `PinMode` represents the various pin modes. The available `PinMode`
-//! variants are [`Input`], [`Output`] and [`Alternate`], each with its own
-//! corresponding configurations.
-//!
-//! It is not possible for users to create new instances of a [`Pin`]. Singleton
-//! instances of each pin are made available to users through the PinsX
-//! struct.
-//!
-//! Example for the pins of PORT A:
-//!
-//! To create the [PinsA] struct, users must supply the PAC
-//! [Port](crate::pac::Porta) peripheral. The [PinsA] struct takes
-//! ownership of the [Porta] and provides the corresponding pins. Each [`Pin`]
-//! within the [PinsA] struct can be moved out and used individually.
-//!
-//!
-//! ```no_run
-//! let mut peripherals = Peripherals::take().unwrap();
-//! let pinsa = PinsA::new(peripherals.porta);
-//! ```
-//!
-//! Pins can be converted between modes using several different methods.
-//!
-//! ```no_run
-//! // Use one of the literal function names
-//! let pa0 = pinsa.pa0.into_floating_input();
-//! // Use a generic method and one of the `PinMode` variant types
-//! let pa0 = pinsa.pa0.into_mode::<FloatingInput>();
-//! // Specify the target type and use `From`/`Into`
-//! let pa0: Pin<PA0, FloatingInput> = pinsa.pa27.into();
-//! ```
-//!
-//! # Embedded HAL traits
-//!
-//! This module implements all of the embedded HAL GPIO traits for each [`Pin`]
-//! in the corresponding [`PinMode`]s, namely: [embedded_hal::digital::InputPin],
-//! [embedded_hal::digital::OutputPin] and [embedded_hal::digital::StatefulOutputPin].
-use core::{convert::Infallible, marker::PhantomData, mem::transmute};
-
-pub use crate::clock::FilterClkSel;
-use crate::typelevel::Sealed;
-use va416xx::{self as pac, Porta, Portb, Portc, Portd, Porte, Portf, Portg};
-
-use super::{
-    DynAlternate, DynInput, DynOutput, DynPin, DynPinId, DynPinMode, InputPinAsync, InterruptEdge,
-    InterruptLevel, PinState, Port, PortGDoesNotSupportAsyncError,
-};
-
-//==================================================================================================
-// Input configuration
-//==================================================================================================
-
-/// Type-level enum for input configurations
-///
-/// The valid options are [Floating], [PullDown] and [PullUp].
-pub trait InputConfig: Sealed {
-    /// Corresponding [DynInput]
-    const DYN: DynInput;
-}
-
-pub enum Floating {}
-pub enum PullDown {}
-pub enum PullUp {}
-
-impl InputConfig for Floating {
-    const DYN: DynInput = DynInput::Floating;
-}
-impl InputConfig for PullDown {
-    const DYN: DynInput = DynInput::PullDown;
-}
-impl InputConfig for PullUp {
-    const DYN: DynInput = DynInput::PullUp;
-}
-
-impl Sealed for Floating {}
-impl Sealed for PullDown {}
-impl Sealed for PullUp {}
-
-/// Type-level variant of [PinMode] for floating input mode
-pub type InputFloating = Input<Floating>;
-/// Type-level variant of [PinMode] for pull-down input mode
-pub type InputPullDown = Input<PullDown>;
-/// Type-level variant of [PinMode] for pull-up input mode
-pub type InputPullUp = Input<PullUp>;
-
-/// Type-level variant of [PinMode] for input modes
-///
-/// Type `C` is one of three input configurations: [Floating], [PullDown] or
-/// [PullUp]
-pub struct Input<C: InputConfig> {
-    cfg: PhantomData<C>,
-}
-
-impl<C: InputConfig> Sealed for Input<C> {}
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum FilterType {
-    SystemClock = 0,
-    DirectInputWithSynchronization = 1,
-    FilterOneClockCycle = 2,
-    FilterTwoClockCycles = 3,
-    FilterThreeClockCycles = 4,
-    FilterFourClockCycles = 5,
-}
-
-//==================================================================================================
-// Output configuration
-//==================================================================================================
-
-pub trait OutputConfig: Sealed {
-    const DYN: DynOutput;
-}
-
-pub trait ReadableOutput: Sealed {}
-
-/// Type-level variant of [`OutputConfig`] for a push-pull configuration
-pub enum PushPull {}
-/// Type-level variant of [`OutputConfig`] for an open drain configuration
-pub enum OpenDrain {}
-
-/// Type-level variant of [`OutputConfig`] for a readable push-pull configuration
-pub enum ReadablePushPull {}
-/// Type-level variant of [`OutputConfig`] for a readable open-drain configuration
-pub enum ReadableOpenDrain {}
-
-impl Sealed for PushPull {}
-impl Sealed for OpenDrain {}
-impl Sealed for ReadableOpenDrain {}
-impl Sealed for ReadablePushPull {}
-impl ReadableOutput for ReadableOpenDrain {}
-impl ReadableOutput for ReadablePushPull {}
-
-impl OutputConfig for PushPull {
-    const DYN: DynOutput = DynOutput::PushPull;
-}
-impl OutputConfig for OpenDrain {
-    const DYN: DynOutput = DynOutput::OpenDrain;
-}
-impl OutputConfig for ReadablePushPull {
-    const DYN: DynOutput = DynOutput::ReadablePushPull;
-}
-impl OutputConfig for ReadableOpenDrain {
-    const DYN: DynOutput = DynOutput::ReadableOpenDrain;
-}
-
-/// Type-level variant of [`PinMode`] for output modes
-///
-/// Type `C` is one of four output configurations: [`PushPull`], [`OpenDrain`] or
-/// their respective readable versions
-pub struct Output<C: OutputConfig> {
-    cfg: PhantomData<C>,
-}
-
-impl<C: OutputConfig> Sealed for Output<C> {}
-
-/// Type-level variant of [`PinMode`] for push-pull output mode
-pub type PushPullOutput = Output<PushPull>;
-/// Type-level variant of [`PinMode`] for open drain output mode
-pub type OutputOpenDrain = Output<OpenDrain>;
-
-pub type OutputReadablePushPull = Output<ReadablePushPull>;
-pub type OutputReadableOpenDrain = Output<ReadableOpenDrain>;
-
-//==================================================================================================
-//  Alternate configurations
-//==================================================================================================
-
-/// Type-level enum for alternate peripheral function configurations
-pub trait AlternateConfig: Sealed {
-    const DYN: DynAlternate;
-}
-
-pub enum Funsel1 {}
-pub enum Funsel2 {}
-pub enum Funsel3 {}
-
-impl AlternateConfig for Funsel1 {
-    const DYN: DynAlternate = DynAlternate::Sel1;
-}
-impl AlternateConfig for Funsel2 {
-    const DYN: DynAlternate = DynAlternate::Sel2;
-}
-impl AlternateConfig for Funsel3 {
-    const DYN: DynAlternate = DynAlternate::Sel3;
-}
-
-impl Sealed for Funsel1 {}
-impl Sealed for Funsel2 {}
-impl Sealed for Funsel3 {}
-
-/// Type-level variant of [`PinMode`] for alternate peripheral functions
-///
-/// Type `C` is an [`AlternateConfig`]
-pub struct Alternate<C: AlternateConfig> {
-    cfg: PhantomData<C>,
-}
-
-impl<C: AlternateConfig> Sealed for Alternate<C> {}
-
-pub type AltFunc1 = Alternate<Funsel1>;
-pub type AltFunc2 = Alternate<Funsel2>;
-pub type AltFunc3 = Alternate<Funsel3>;
-
-/// Type alias for the [`PinMode`] at reset
-pub type Reset = InputFloating;
-
-//==================================================================================================
-//  Pin modes
-//==================================================================================================
-
-/// Type-level enum representing pin modes
-///
-/// The valid options are [Input], [Output] and [Alternate].
-pub trait PinMode: Sealed {
-    /// Corresponding [DynPinMode]
-    const DYN: DynPinMode;
-}
-
-impl<C: InputConfig> PinMode for Input<C> {
-    const DYN: DynPinMode = DynPinMode::Input(C::DYN);
-}
-impl<C: OutputConfig> PinMode for Output<C> {
-    const DYN: DynPinMode = DynPinMode::Output(C::DYN);
-}
-impl<C: AlternateConfig> PinMode for Alternate<C> {
-    const DYN: DynPinMode = DynPinMode::Alternate(C::DYN);
-}
-
-//==================================================================================================
-//  Pin IDs
-//==================================================================================================
-
-/// Type-level enum for pin IDs
-pub trait PinId: Sealed {
-    /// Corresponding [DynPinId]
-    const DYN: DynPinId;
-    const IRQ: Option<pac::Interrupt>;
-}
-
-macro_rules! pin_id {
-    ($Port:ident, $Id:ident, $NUM:literal, $Irq:expr, $(, $meta: meta)?) => {
-        // Need paste macro to use ident in doc attribute
-        paste::paste! {
-            $(#[$meta])?
-            #[doc = "Pin ID representing pin " $Id]
-            pub enum $Id {}
-
-            $(#[$meta])?
-            impl Sealed for $Id {}
-
-            $(#[$meta])?
-            impl PinId for $Id {
-                const DYN: DynPinId = DynPinId::new(Port::$Port, $NUM);
-                const IRQ: Option<pac::Interrupt> = $Irq;
-            }
-        }
-    };
-}
-
-//==================================================================================================
-//  Pin
-//==================================================================================================
-
-/// A type-level GPIO pin, parameterized by [`PinId`] and [`PinMode`] types
-#[derive(Debug)]
-pub struct Pin<I: PinId, M: PinMode> {
-    inner: DynPin,
-    mode: PhantomData<(I, M)>,
-}
-
-impl<I: PinId, M: PinMode> Pin<I, M> {
-    /// Create a new [`Pin`]
-    ///
-    /// # Safety
-    ///
-    /// Each [`Pin`] must be a singleton. For a given [`PinId`], there must be
-    /// at most one corresponding [`Pin`] in existence at any given time.
-    /// Violating this requirement is `unsafe`.
-    #[inline]
-    pub(crate) const unsafe fn new() -> Pin<I, M> {
-        Pin {
-            inner: DynPin::new(I::DYN, M::DYN),
-            mode: PhantomData,
-        }
-    }
-
-    #[inline]
-    pub const fn id(&self) -> DynPinId {
-        self.inner.id()
-    }
-
-    #[inline(always)]
-    pub const fn irq_id(&self) -> Option<pac::Interrupt> {
-        I::IRQ
-    }
-
-    /// Convert the pin to the requested [`PinMode`]
-    #[inline]
-    pub fn into_mode<N: PinMode>(mut self) -> Pin<I, N> {
-        // Only modify registers if we are actually changing pin mode
-        // This check should compile away
-        if N::DYN != M::DYN {
-            self.inner.change_mode(N::DYN);
-        }
-        // Safe because we drop the existing Pin
-        unsafe { Pin::new() }
-    }
-
-    /// Configure the pin for function select 1. See Programmer Guide p. 286 for the function table
-    #[inline]
-    pub fn into_funsel_1(self) -> Pin<I, AltFunc1> {
-        self.into_mode()
-    }
-
-    /// Configure the pin for function select 2. See Programmer Guide p. 286 for the function table
-    #[inline]
-    pub fn into_funsel_2(self) -> Pin<I, AltFunc2> {
-        self.into_mode()
-    }
-
-    /// Configure the pin for function select 3. See Programmer Guide p. 286 for the function table
-    #[inline]
-    pub fn into_funsel_3(self) -> Pin<I, AltFunc3> {
-        self.into_mode()
-    }
-
-    /// Configure the pin to operate as a floating input
-    #[inline]
-    pub fn into_floating_input(self) -> Pin<I, InputFloating> {
-        self.into_mode()
-    }
-
-    /// Configure the pin to operate as a pulled down input
-    #[inline]
-    pub fn into_pull_down_input(self) -> Pin<I, InputPullDown> {
-        self.into_mode()
-    }
-
-    /// Configure the pin to operate as a pulled up input
-    #[inline]
-    pub fn into_pull_up_input(self) -> Pin<I, InputPullUp> {
-        self.into_mode()
-    }
-
-    /// Configure the pin to operate as a push-pull output
-    #[inline]
-    pub fn into_push_pull_output(self) -> Pin<I, PushPullOutput> {
-        self.into_mode()
-    }
-
-    /// Configure the pin to operate as a readable push-pull output
-    #[inline]
-    pub fn into_readable_push_pull_output(self) -> Pin<I, OutputReadablePushPull> {
-        self.into_mode()
-    }
-
-    /// Configure the pin to operate as a readable open-drain output
-    #[inline]
-    pub fn into_readable_open_drain_output(self) -> Pin<I, OutputReadableOpenDrain> {
-        self.into_mode()
-    }
-
-    #[inline]
-    pub fn is_low(&self) -> bool {
-        !self.inner.read_pin()
-    }
-
-    #[inline]
-    pub fn is_high(&self) -> bool {
-        self.inner.read_pin()
-    }
-
-    #[inline]
-    pub fn datamask(&self) -> bool {
-        self.inner.datamask()
-    }
-
-    #[inline]
-    pub fn clear_datamask(&mut self) {
-        self.inner.clear_datamask()
-    }
-
-    #[inline]
-    pub fn set_datamask(&mut self) {
-        self.inner.set_datamask()
-    }
-
-    #[inline]
-    pub fn is_high_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
-        self.inner.is_high_masked()
-    }
-
-    #[inline]
-    pub fn is_low_masked(&self) -> Result<bool, crate::gpio::IsMaskedError> {
-        self.inner.is_low_masked()
-    }
-
-    #[inline]
-    pub fn downgrade(self) -> DynPin {
-        self.inner
-    }
-
-    // Those only serve for the embedded HAL implementations which have different mutability.
-
-    #[inline]
-    fn is_low_mut(&mut self) -> bool {
-        self.is_low()
-    }
-
-    #[inline]
-    fn is_high_mut(&mut self) -> bool {
-        self.is_high()
-    }
-
-    #[inline]
-    pub fn enable_interrupt(&mut self) {
-        self.inner.enable_interrupt();
-    }
-
-    #[inline]
-    pub fn disable_interrupt(&mut self) {
-        self.inner.disable_interrupt();
-    }
-
-    /// Configure the pin for an edge interrupt but does not enable the interrupt.
-    pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) {
-        self.inner.configure_edge_interrupt(edge_type).unwrap();
-    }
-
-    /// Configure the pin for a level interrupt but does not enable the interrupt.
-    pub fn configure_level_interrupt(&mut self, level_type: InterruptLevel) {
-        self.inner.configure_level_interrupt(level_type).unwrap();
-    }
-}
-
-//==============================================================================
-//  AnyPin
-//==============================================================================
-
-/// Type class for [`Pin`] types
-///
-/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for
-/// [`Pin`] types. See the `AnyKind` documentation for more details on the
-/// pattern.
-///
-/// ## `v1` Compatibility
-///
-/// Normally, this trait would use `Is<Type = SpecificPin<Self>>` as a super
-/// trait. But doing so would restrict implementations to only the `v2` `Pin`
-/// type in this module. To aid in backwards compatibility, we want to implement
-/// `AnyPin` for the `v1` `Pin` type as well. This is possible for a few
-/// reasons. First, both structs are zero-sized, so there is no meaningful
-/// memory layout to begin with. And even if there were, the `v1` `Pin` type is
-/// a newtype wrapper around a `v2` `Pin`, and single-field structs are
-/// guaranteed to have the same layout as the field, even for `repr(Rust)`.
-///
-/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
-/// [type class]: crate::typelevel#type-classes
-pub trait AnyPin
-where
-    Self: Sealed,
-    Self: From<SpecificPin<Self>>,
-    Self: Into<SpecificPin<Self>>,
-    Self: AsRef<SpecificPin<Self>>,
-    Self: AsMut<SpecificPin<Self>>,
-{
-    /// [`PinId`] of the corresponding [`Pin`]
-    type Id: PinId;
-    /// [`PinMode`] of the corresponding [`Pin`]
-    type Mode: PinMode;
-}
-
-impl<I, M> Sealed for Pin<I, M>
-where
-    I: PinId,
-    M: PinMode,
-{
-}
-
-impl<I, M> AnyPin for Pin<I, M>
-where
-    I: PinId,
-    M: PinMode,
-{
-    type Id = I;
-    type Mode = M;
-}
-
-/// Type alias to recover the specific [`Pin`] type from an implementation of
-/// [`AnyPin`]
-///
-/// See the [`AnyKind`] documentation for more details on the pattern.
-///
-/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern
-pub type SpecificPin<P> = Pin<<P as AnyPin>::Id, <P as AnyPin>::Mode>;
-
-impl<P: AnyPin> AsRef<P> for SpecificPin<P> {
-    #[inline]
-    fn as_ref(&self) -> &P {
-        // SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
-        // Transmuting between `v1` and `v2` `Pin` types is also safe, because
-        // both are zero-sized, and single-field, newtype structs are guaranteed
-        // to have the same layout as the field anyway, even for repr(Rust).
-        unsafe { transmute(self) }
-    }
-}
-
-impl<P: AnyPin> AsMut<P> for SpecificPin<P> {
-    #[inline]
-    fn as_mut(&mut self) -> &mut P {
-        // SAFETY: This is guaranteed to be safe, because P == SpecificPin<P>
-        // Transmuting between `v1` and `v2` `Pin` types is also safe, because
-        // both are zero-sized, and single-field, newtype structs are guaranteed
-        // to have the same layout as the field anyway, even for repr(Rust).
-        unsafe { transmute(self) }
-    }
-}
-
-//==================================================================================================
-//  Additional functionality
-//==================================================================================================
-
-impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
-    /// Convert the pin into an async pin. The pin can be converted back by calling
-    /// [InputPinAsync::release]
-    pub fn into_async_input(self) -> Result<InputPinAsync<I, C>, PortGDoesNotSupportAsyncError> {
-        InputPinAsync::new(self)
-    }
-}
-
-impl<I: PinId, C: OutputConfig> Pin<I, Output<C>> {
-    #[inline]
-    pub fn set_high(&mut self) {
-        self.inner.write_pin(true)
-    }
-
-    #[inline]
-    pub fn set_low(&mut self) {
-        self.inner.write_pin(false)
-    }
-
-    #[inline]
-    pub fn toggle(&mut self) {
-        self.inner.toggle().unwrap()
-    }
-
-    #[inline]
-    pub fn set_high_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
-        self.inner.set_high_masked()
-    }
-
-    #[inline]
-    pub fn set_low_masked(&mut self) -> Result<(), crate::gpio::IsMaskedError> {
-        self.inner.set_low_masked()
-    }
-
-    /// Possible delays in clock cycles:
-    ///  - Delay 1: 1
-    ///  - Delay 2: 2
-    ///  - Delay 1 + Delay 2: 3
-    #[inline]
-    pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
-        self.inner.configure_delay(delay_1, delay_2).unwrap();
-    }
-
-    /// When configured for pulse mode, a given pin will set the non-default state for exactly
-    /// one clock cycle before returning to the configured default state
-    pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) {
-        self.inner
-            .configure_pulse_mode(enable, default_state)
-            .unwrap();
-    }
-}
-
-impl<I: PinId, C: InputConfig> Pin<I, Input<C>> {
-    #[inline]
-    pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
-        self.inner.configure_filter_type(filter, clksel).unwrap();
-    }
-}
-
-//==================================================================================================
-//  Embedded HAL traits
-//==================================================================================================
-
-impl<I, M> embedded_hal::digital::ErrorType for Pin<I, M>
-where
-    I: PinId,
-    M: PinMode,
-{
-    type Error = Infallible;
-}
-
-impl<I: PinId, C: OutputConfig> embedded_hal::digital::OutputPin for Pin<I, Output<C>> {
-    #[inline]
-    fn set_high(&mut self) -> Result<(), Self::Error> {
-        self.set_high();
-        Ok(())
-    }
-
-    #[inline]
-    fn set_low(&mut self) -> Result<(), Self::Error> {
-        self.set_low();
-        Ok(())
-    }
-}
-
-impl<I, C> embedded_hal::digital::InputPin for Pin<I, Input<C>>
-where
-    I: PinId,
-    C: InputConfig,
-{
-    #[inline]
-    fn is_high(&mut self) -> Result<bool, Self::Error> {
-        Ok(self.is_high_mut())
-    }
-    #[inline]
-    fn is_low(&mut self) -> Result<bool, Self::Error> {
-        Ok(self.is_low_mut())
-    }
-}
-
-impl<I, C> embedded_hal::digital::StatefulOutputPin for Pin<I, Output<C>>
-where
-    I: PinId,
-    C: OutputConfig + ReadableOutput,
-{
-    #[inline]
-    fn is_set_high(&mut self) -> Result<bool, Self::Error> {
-        Ok(self.is_high())
-    }
-    #[inline]
-    fn is_set_low(&mut self) -> Result<bool, Self::Error> {
-        Ok(self.is_low())
-    }
-
-    #[inline]
-    fn toggle(&mut self) -> Result<(), Self::Error> {
-        self.toggle();
-        Ok(())
-    }
-}
-
-impl<I, C> embedded_hal::digital::InputPin for Pin<I, Output<C>>
-where
-    I: PinId,
-    C: OutputConfig + ReadableOutput,
-{
-    #[inline]
-    fn is_high(&mut self) -> Result<bool, Self::Error> {
-        Ok(self.is_high_mut())
-    }
-
-    #[inline]
-    fn is_low(&mut self) -> Result<bool, Self::Error> {
-        Ok(self.is_low_mut())
-    }
-}
-
-//==================================================================================================
-//  Pin definitions
-//==================================================================================================
-
-macro_rules! pins {
-    (
-        $Port:ident, $PinsName:ident, $($Id:ident $(, $meta:meta)?)+,
-    ) => {
-        paste::paste!(
-            /// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
-            pub struct $PinsName {
-                port: $Port,
-                $(
-                    $(#[$meta])?
-                    #[doc = "Pin " $Id]
-                    pub [<$Id:lower>]: Pin<$Id, Reset>,
-                )+
-            }
-
-            impl $PinsName {
-                /// Create a new struct containing all the Pins. Passing the IOCONFIG peripheral
-                /// is optional because it might be required to create pin definitions for both
-                /// ports.
-                #[inline]
-                pub fn new(
-                    syscfg: &mut va416xx::Sysconfig,
-                    port: $Port
-                ) -> $PinsName {
-                    syscfg.peripheral_clk_enable().modify(|_, w| {
-                        w.[<$Port:lower>]().set_bit();
-                        w.ioconfig().set_bit()
-                    });
-                    $PinsName {
-                        port,
-                        // Safe because we only create one `Pin` per `PinId`
-                        $(
-                            $(#[$meta])?
-                            [<$Id:lower>]: unsafe { Pin::new() },
-                        )+
-                    }
-                }
-
-                /// Get the peripheral ID
-                /// Safety: Read-only register
-                pub fn get_perid() -> u32 {
-                    let port = unsafe { &(*$Port::ptr()) };
-                    port.perid().read().bits()
-                }
-
-                /// Consumes the Pins struct and returns the port definitions
-                pub fn release(self) -> $Port {
-                    self.port
-                }
-            }
-        );
-    }
-}
-
-macro_rules! declare_pins_with_irq {
-    (
-        $Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
-    ) => {
-        pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
-        $(
-            paste::paste! {
-                pin_id!($Group, $Id, $NUM, Some(pac::Interrupt::[<$Port:upper $NUM>]), $(, $meta)?);
-            }
-        )+
-    }
-}
-
-macro_rules! declare_pins {
-    (
-        $Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal $(, $meta:meta)?)),+]
-    ) => {
-        pins!($Port, $PinsName, $($Id $(, $meta)?)+,);
-        $(
-            pin_id!($Group, $Id, $NUM, None, $(, $meta)?);
-        )+
-    }
-}
-
-declare_pins_with_irq!(
-    A,
-    PinsA,
-    Porta,
-    [
-        (PA0, 0),
-        (PA1, 1),
-        (PA2, 2),
-        (PA3, 3),
-        (PA4, 4),
-        (PA5, 5),
-        (PA6, 6),
-        (PA7, 7),
-        (PA8, 8),
-        (PA9, 9),
-        (PA10, 10),
-        (PA11, 11),
-        (PA12, 12),
-        (PA13, 13),
-        (PA14, 14),
-        (PA15, 15)
-    ]
-);
-
-declare_pins_with_irq!(
-    B,
-    PinsB,
-    Portb,
-    [
-        (PB0, 0),
-        (PB1, 1),
-        (PB2, 2),
-        (PB3, 3),
-        (PB4, 4),
-        (PB5, 5, cfg(not(feature = "va41628"))),
-        (PB6, 6, cfg(not(feature = "va41628"))),
-        (PB7, 7, cfg(not(feature = "va41628"))),
-        (PB8, 8, cfg(not(feature = "va41628"))),
-        (PB9, 9, cfg(not(feature = "va41628"))),
-        (PB10, 10, cfg(not(feature = "va41628"))),
-        (PB11, 11, cfg(not(feature = "va41628"))),
-        (PB12, 12),
-        (PB13, 13),
-        (PB14, 14),
-        (PB15, 15)
-    ]
-);
-
-declare_pins_with_irq!(
-    C,
-    PinsC,
-    Portc,
-    [
-        (PC0, 0),
-        (PC1, 1),
-        (PC2, 2),
-        (PC3, 3),
-        (PC4, 4),
-        (PC5, 5),
-        (PC6, 6),
-        (PC7, 7),
-        (PC8, 8),
-        (PC9, 9),
-        (PC10, 10),
-        (PC11, 11),
-        (PC12, 12),
-        (PC13, 13, cfg(not(feature = "va41628"))),
-        (PC14, 14),
-        (PC15, 15, cfg(not(feature = "va41628")))
-    ]
-);
-
-declare_pins_with_irq!(
-    D,
-    PinsD,
-    Portd,
-    [
-        (PD0, 0, cfg(not(feature = "va41628"))),
-        (PD1, 1, cfg(not(feature = "va41628"))),
-        (PD2, 2, cfg(not(feature = "va41628"))),
-        (PD3, 3, cfg(not(feature = "va41628"))),
-        (PD4, 4, cfg(not(feature = "va41628"))),
-        (PD5, 5, cfg(not(feature = "va41628"))),
-        (PD6, 6, cfg(not(feature = "va41628"))),
-        (PD7, 7, cfg(not(feature = "va41628"))),
-        (PD8, 8, cfg(not(feature = "va41628"))),
-        (PD9, 9, cfg(not(feature = "va41628"))),
-        (PD10, 10),
-        (PD11, 11),
-        (PD12, 12),
-        (PD13, 13),
-        (PD14, 14),
-        (PD15, 15)
-    ]
-);
-
-declare_pins_with_irq!(
-    E,
-    PinsE,
-    Porte,
-    [
-        (PE0, 0),
-        (PE1, 1),
-        (PE2, 2),
-        (PE3, 3),
-        (PE4, 4),
-        (PE5, 5),
-        (PE6, 6),
-        (PE7, 7),
-        (PE8, 8),
-        (PE9, 9),
-        (PE10, 10, cfg(not(feature = "va41628"))),
-        (PE11, 11, cfg(not(feature = "va41628"))),
-        (PE12, 12),
-        (PE13, 13),
-        (PE14, 14),
-        (PE15, 15)
-    ]
-);
-
-declare_pins_with_irq!(
-    F,
-    PinsF,
-    Portf,
-    [
-        (PF0, 0),
-        (PF1, 1),
-        (PF2, 2, cfg(not(feature = "va41628"))),
-        (PF3, 3, cfg(not(feature = "va41628"))),
-        (PF4, 4, cfg(not(feature = "va41628"))),
-        (PF5, 5, cfg(not(feature = "va41628"))),
-        (PF6, 6, cfg(not(feature = "va41628"))),
-        (PF7, 7, cfg(not(feature = "va41628"))),
-        (PF8, 8, cfg(not(feature = "va41628"))),
-        (PF9, 9),
-        (PF10, 10, cfg(not(feature = "va41628"))),
-        (PF11, 11),
-        (PF12, 12),
-        (PF13, 13),
-        (PF14, 14),
-        (PF15, 15)
-    ]
-);
-
-declare_pins!(
-    G,
-    PinsG,
-    Portg,
-    [
-        (PG0, 0),
-        (PG1, 1),
-        (PG2, 2),
-        (PG3, 3),
-        (PG4, 4),
-        (PG5, 5),
-        (PG6, 6),
-        (PG7, 7)
-    ]
-);
diff --git a/va416xx-hal/src/i2c.rs b/va416xx-hal/src/i2c.rs
index 23fdc93..0dafeaa 100644
--- a/va416xx-hal/src/i2c.rs
+++ b/va416xx-hal/src/i2c.rs
@@ -3,913 +3,4 @@
 //! ## Examples
 //!
 //! - [PEB1 accelerometer example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
-use crate::{
-    clock::{Clocks, PeripheralSelect},
-    pac,
-    prelude::SyscfgExt,
-    time::Hertz,
-    typelevel::Sealed,
-};
-use core::{marker::PhantomData, ops::Deref};
-use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
-
-//==================================================================================================
-// Defintions
-//==================================================================================================
-
-const CLK_100K: Hertz = Hertz::from_raw(100_000);
-const CLK_400K: Hertz = Hertz::from_raw(400_000);
-const MIN_CLK_400K: Hertz = Hertz::from_raw(10_000_000);
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum FifoEmptyMode {
-    Stall = 0,
-    EndTransaction = 1,
-}
-
-#[derive(Debug, PartialEq, Eq, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[error("clock too slow for fast I2C mode")]
-pub struct ClockTooSlowForFastI2cError;
-
-#[derive(Debug, PartialEq, Eq, thiserror::Error)]
-#[error("invalid timing parameters")]
-pub struct InvalidTimingParamsError;
-
-#[derive(Debug, PartialEq, Eq, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum Error {
-    #[error("arbitration lost")]
-    ArbitrationLost,
-    #[error("nack address")]
-    NackAddr,
-    /// Data not acknowledged in write operation
-    #[error("data not acknowledged in write operation")]
-    NackData,
-    /// Not enough data received in read operation
-    #[error("insufficient data received")]
-    InsufficientDataReceived,
-    /// Number of bytes in transfer too large (larger than 0x7fe)
-    #[error("data too large (larger than 0x7fe)")]
-    DataTooLarge,
-}
-
-#[derive(Debug, PartialEq, Eq, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum InitError {
-    /// Wrong address used in constructor
-    #[error("wrong address mode")]
-    WrongAddrMode,
-    /// APB1 clock is too slow for fast I2C mode.
-    #[error("clock too slow for fast I2C mode: {0}")]
-    ClkTooSlow(#[from] ClockTooSlowForFastI2cError),
-}
-
-impl embedded_hal::i2c::Error for Error {
-    fn kind(&self) -> embedded_hal::i2c::ErrorKind {
-        match self {
-            Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
-            Error::NackAddr => {
-                embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
-            }
-            Error::NackData => {
-                embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
-            }
-            Error::DataTooLarge | Error::InsufficientDataReceived => {
-                embedded_hal::i2c::ErrorKind::Other
-            }
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-enum I2cCmd {
-    Start = 0b00,
-    Stop = 0b10,
-    StartWithStop = 0b11,
-    Cancel = 0b100,
-}
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum I2cSpeed {
-    Regular100khz = 0,
-    Fast400khz = 1,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum I2cDirection {
-    Send = 0,
-    Read = 1,
-}
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum I2cAddress {
-    Regular(u8),
-    TenBit(u16),
-}
-
-pub type I2cRegBlock = pac::i2c0::RegisterBlock;
-
-/// Common trait implemented by all PAC peripheral access structures. The register block
-/// format is the same for all SPI blocks.
-pub trait Instance: Deref<Target = I2cRegBlock> {
-    const IDX: u8;
-    const PERIPH_SEL: PeripheralSelect;
-
-    fn ptr() -> *const I2cRegBlock;
-}
-
-impl Instance for pac::I2c0 {
-    const IDX: u8 = 0;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
-
-    #[inline(always)]
-    fn ptr() -> *const I2cRegBlock {
-        Self::ptr()
-    }
-}
-
-impl Instance for pac::I2c1 {
-    const IDX: u8 = 1;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
-
-    #[inline(always)]
-    fn ptr() -> *const I2cRegBlock {
-        Self::ptr()
-    }
-}
-
-impl Instance for pac::I2c2 {
-    const IDX: u8 = 2;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c2;
-
-    #[inline(always)]
-    fn ptr() -> *const I2cRegBlock {
-        Self::ptr()
-    }
-}
-
-//==================================================================================================
-// Config
-//==================================================================================================
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct TrTfThighTlow(u8, u8, u8, u8);
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct TsuStoTsuStaThdStaTBuf(u8, u8, u8, u8);
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct TimingCfg {
-    // 4 bit max width
-    tr: u8,
-    // 4 bit max width
-    tf: u8,
-    // 4 bit max width
-    thigh: u8,
-    // 4 bit max width
-    tlow: u8,
-    // 4 bit max width
-    tsu_sto: u8,
-    // 4 bit max width
-    tsu_sta: u8,
-    // 4 bit max width
-    thd_sta: u8,
-    // 4 bit max width
-    tbuf: u8,
-}
-
-impl TimingCfg {
-    pub fn new(
-        first_16_bits: TrTfThighTlow,
-        second_16_bits: TsuStoTsuStaThdStaTBuf,
-    ) -> Result<Self, InvalidTimingParamsError> {
-        if first_16_bits.0 > 0xf
-            || first_16_bits.1 > 0xf
-            || first_16_bits.2 > 0xf
-            || first_16_bits.3 > 0xf
-            || second_16_bits.0 > 0xf
-            || second_16_bits.1 > 0xf
-            || second_16_bits.2 > 0xf
-            || second_16_bits.3 > 0xf
-        {
-            return Err(InvalidTimingParamsError);
-        }
-        Ok(TimingCfg {
-            tr: first_16_bits.0,
-            tf: first_16_bits.1,
-            thigh: first_16_bits.2,
-            tlow: first_16_bits.3,
-            tsu_sto: second_16_bits.0,
-            tsu_sta: second_16_bits.1,
-            thd_sta: second_16_bits.2,
-            tbuf: second_16_bits.3,
-        })
-    }
-
-    pub fn reg(&self) -> u32 {
-        ((self.tbuf as u32) << 28)
-            | ((self.thd_sta as u32) << 24)
-            | ((self.tsu_sta as u32) << 20)
-            | ((self.tsu_sto as u32) << 16)
-            | ((self.tlow as u32) << 12)
-            | ((self.thigh as u32) << 8)
-            | ((self.tf as u32) << 4)
-            | (self.tr as u32)
-    }
-}
-
-impl Default for TimingCfg {
-    fn default() -> Self {
-        TimingCfg {
-            tr: 0x02,
-            tf: 0x01,
-            thigh: 0x08,
-            tlow: 0x09,
-            tsu_sto: 0x8,
-            tsu_sta: 0x0a,
-            thd_sta: 0x8,
-            tbuf: 0xa,
-        }
-    }
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct MasterConfig {
-    pub tx_fe_mode: FifoEmptyMode,
-    pub rx_fe_mode: FifoEmptyMode,
-    /// Enable the analog delay glitch filter
-    pub alg_filt: bool,
-    /// Enable the digital glitch filter
-    pub dlg_filt: bool,
-    pub tm_cfg: Option<TimingCfg>,
-    // Loopback mode
-    // lbm: bool,
-}
-
-impl Default for MasterConfig {
-    fn default() -> Self {
-        MasterConfig {
-            tx_fe_mode: FifoEmptyMode::Stall,
-            rx_fe_mode: FifoEmptyMode::Stall,
-            alg_filt: false,
-            dlg_filt: false,
-            tm_cfg: None,
-        }
-    }
-}
-
-impl Sealed for MasterConfig {}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct SlaveConfig {
-    pub tx_fe_mode: FifoEmptyMode,
-    pub rx_fe_mode: FifoEmptyMode,
-    /// Maximum number of words before issuing a negative acknowledge.
-    /// Range should be 0 to 0x7fe. Setting the value to 0x7ff has the same effect as not setting
-    /// the enable bit since RXCOUNT stops counting at 0x7fe.
-    pub max_words: Option<usize>,
-    /// A received address is compared to the ADDRESS register (addr) using the address mask
-    /// (addr_mask). Those bits with a 1 in the address mask must match for there to be an address
-    /// match
-    pub addr: I2cAddress,
-    /// The default address mask will be 0x3ff to only allow full matches
-    pub addr_mask: Option<u16>,
-    /// Optionally specify a second I2C address the slave interface responds to
-    pub addr_b: Option<I2cAddress>,
-    pub addr_b_mask: Option<u16>,
-}
-
-impl SlaveConfig {
-    /// Build a default slave config given a specified slave address to respond to
-    pub fn new(addr: I2cAddress) -> Self {
-        SlaveConfig {
-            tx_fe_mode: FifoEmptyMode::Stall,
-            rx_fe_mode: FifoEmptyMode::Stall,
-            max_words: None,
-            addr,
-            addr_mask: None,
-            addr_b: None,
-            addr_b_mask: None,
-        }
-    }
-}
-
-impl Sealed for SlaveConfig {}
-
-//==================================================================================================
-// I2C Base
-//==================================================================================================
-
-pub struct I2cBase<I2c> {
-    i2c: I2c,
-    clock: Hertz,
-}
-
-impl<I2C> I2cBase<I2C> {
-    #[inline]
-    fn unwrap_addr(addr: I2cAddress) -> (u16, u32) {
-        match addr {
-            I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
-            I2cAddress::TenBit(addr) => (addr, 1 << 15),
-        }
-    }
-}
-
-impl<I2c: Instance> I2cBase<I2c> {
-    pub fn new(
-        i2c: I2c,
-        syscfg: &mut pac::Sysconfig,
-        clocks: &Clocks,
-        speed_mode: I2cSpeed,
-        ms_cfg: Option<&MasterConfig>,
-        sl_cfg: Option<&SlaveConfig>,
-    ) -> Result<Self, ClockTooSlowForFastI2cError> {
-        syscfg.enable_peripheral_clock(I2c::PERIPH_SEL);
-
-        let mut i2c_base = I2cBase {
-            i2c,
-            clock: clocks.apb1(),
-        };
-        if let Some(ms_cfg) = ms_cfg {
-            i2c_base.cfg_master(ms_cfg);
-        }
-
-        if let Some(sl_cfg) = sl_cfg {
-            i2c_base.cfg_slave(sl_cfg);
-        }
-        i2c_base.cfg_clk_scale(speed_mode)?;
-        Ok(i2c_base)
-    }
-
-    fn cfg_master(&mut self, ms_cfg: &MasterConfig) {
-        let (txfemd, rxfemd) = match (ms_cfg.tx_fe_mode, ms_cfg.rx_fe_mode) {
-            (FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
-            (FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
-            (FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
-            (FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
-        };
-        self.i2c.ctrl().modify(|_, w| {
-            w.txfemd().bit(txfemd);
-            w.rxffmd().bit(rxfemd);
-            w.dlgfilter().bit(ms_cfg.dlg_filt);
-            w.algfilter().bit(ms_cfg.alg_filt)
-        });
-        if let Some(ref tm_cfg) = ms_cfg.tm_cfg {
-            self.i2c
-                .tmconfig()
-                .write(|w| unsafe { w.bits(tm_cfg.reg()) });
-        }
-        self.i2c.fifo_clr().write(|w| {
-            w.rxfifo().set_bit();
-            w.txfifo().set_bit()
-        });
-    }
-
-    fn cfg_slave(&mut self, sl_cfg: &SlaveConfig) {
-        let (txfemd, rxfemd) = match (sl_cfg.tx_fe_mode, sl_cfg.rx_fe_mode) {
-            (FifoEmptyMode::Stall, FifoEmptyMode::Stall) => (false, false),
-            (FifoEmptyMode::Stall, FifoEmptyMode::EndTransaction) => (false, true),
-            (FifoEmptyMode::EndTransaction, FifoEmptyMode::Stall) => (true, false),
-            (FifoEmptyMode::EndTransaction, FifoEmptyMode::EndTransaction) => (true, true),
-        };
-        self.i2c.s0_ctrl().modify(|_, w| {
-            w.txfemd().bit(txfemd);
-            w.rxffmd().bit(rxfemd)
-        });
-        self.i2c.s0_fifo_clr().write(|w| {
-            w.rxfifo().set_bit();
-            w.txfifo().set_bit()
-        });
-        let max_words = sl_cfg.max_words;
-        if let Some(max_words) = max_words {
-            self.i2c
-                .s0_maxwords()
-                .write(|w| unsafe { w.bits((1 << 31) | max_words as u32) });
-        }
-        let (addr, addr_mode_mask) = Self::unwrap_addr(sl_cfg.addr);
-        // The first bit is the read/write value. Normally, both read and write are matched
-        // using the RWMASK bit of the address mask register
-        self.i2c
-            .s0_address()
-            .write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
-        if let Some(addr_mask) = sl_cfg.addr_mask {
-            self.i2c
-                .s0_addressmask()
-                .write(|w| unsafe { w.bits((addr_mask << 1) as u32) });
-        }
-        if let Some(addr_b) = sl_cfg.addr_b {
-            let (addr, addr_mode_mask) = Self::unwrap_addr(addr_b);
-            self.i2c
-                .s0_addressb()
-                .write(|w| unsafe { w.bits((addr << 1) as u32 | addr_mode_mask) });
-        }
-        if let Some(addr_b_mask) = sl_cfg.addr_b_mask {
-            self.i2c
-                .s0_addressmaskb()
-                .write(|w| unsafe { w.bits((addr_b_mask << 1) as u32) });
-        }
-    }
-
-    #[inline]
-    pub fn filters(&mut self, digital_filt: bool, analog_filt: bool) {
-        self.i2c.ctrl().modify(|_, w| {
-            w.dlgfilter().bit(digital_filt);
-            w.algfilter().bit(analog_filt)
-        });
-    }
-
-    #[inline]
-    pub fn fifo_empty_mode(&mut self, rx: FifoEmptyMode, tx: FifoEmptyMode) {
-        self.i2c.ctrl().modify(|_, w| {
-            w.txfemd().bit(tx as u8 != 0);
-            w.rxffmd().bit(rx as u8 != 0)
-        });
-    }
-
-    fn calc_clk_div(&self, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2cError> {
-        if speed_mode == I2cSpeed::Regular100khz {
-            Ok(((self.clock.raw() / CLK_100K.raw() / 20) - 1) as u8)
-        } else {
-            if self.clock.raw() < MIN_CLK_400K.raw() {
-                return Err(ClockTooSlowForFastI2cError);
-            }
-            Ok(((self.clock.raw() / CLK_400K.raw() / 25) - 1) as u8)
-        }
-    }
-
-    /// Configures the clock scale for a given speed mode setting
-    pub fn cfg_clk_scale(
-        &mut self,
-        speed_mode: I2cSpeed,
-    ) -> Result<(), ClockTooSlowForFastI2cError> {
-        let clk_div = self.calc_clk_div(speed_mode)?;
-        self.i2c
-            .clkscale()
-            .write(|w| unsafe { w.bits(((speed_mode as u32) << 31) | clk_div as u32) });
-        Ok(())
-    }
-
-    pub fn load_address(&mut self, addr: u16) {
-        // Load address
-        self.i2c
-            .address()
-            .write(|w| unsafe { w.bits((addr << 1) as u32) });
-    }
-
-    #[inline]
-    fn stop_cmd(&mut self) {
-        self.i2c
-            .cmd()
-            .write(|w| unsafe { w.bits(I2cCmd::Stop as u32) });
-    }
-}
-
-//==================================================================================================
-// I2C Master
-//==================================================================================================
-
-pub struct I2cMaster<I2c, Addr = SevenBitAddress> {
-    i2c_base: I2cBase<I2c>,
-    addr: PhantomData<Addr>,
-}
-
-impl<I2c: Instance, Addr> I2cMaster<I2c, Addr> {
-    pub fn new(
-        i2c: I2c,
-        sys_cfg: &mut pac::Sysconfig,
-        cfg: MasterConfig,
-        clocks: &Clocks,
-        speed_mode: I2cSpeed,
-    ) -> Result<Self, ClockTooSlowForFastI2cError> {
-        Ok(I2cMaster {
-            i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, Some(&cfg), None)?,
-            addr: PhantomData,
-        }
-        .enable_master())
-    }
-
-    #[inline]
-    pub fn cancel_transfer(&self) {
-        self.i2c_base
-            .i2c
-            .cmd()
-            .write(|w| unsafe { w.bits(I2cCmd::Cancel as u32) });
-    }
-
-    #[inline]
-    pub fn clear_tx_fifo(&self) {
-        self.i2c_base.i2c.fifo_clr().write(|w| w.txfifo().set_bit());
-    }
-
-    #[inline]
-    pub fn clear_rx_fifo(&self) {
-        self.i2c_base.i2c.fifo_clr().write(|w| w.rxfifo().set_bit());
-    }
-
-    #[inline]
-    pub fn enable_master(self) -> Self {
-        self.i2c_base.i2c.ctrl().modify(|_, w| w.enable().set_bit());
-        self
-    }
-
-    #[inline]
-    pub fn disable_master(self) -> Self {
-        self.i2c_base
-            .i2c
-            .ctrl()
-            .modify(|_, w| w.enable().clear_bit());
-        self
-    }
-
-    #[inline(always)]
-    fn load_fifo(&self, word: u8) {
-        self.i2c_base
-            .i2c
-            .data()
-            .write(|w| unsafe { w.bits(word as u32) });
-    }
-
-    #[inline(always)]
-    fn read_fifo(&self) -> u8 {
-        self.i2c_base.i2c.data().read().bits() as u8
-    }
-
-    fn error_handler_write(&mut self, init_cmd: &I2cCmd) {
-        self.clear_tx_fifo();
-        if *init_cmd == I2cCmd::Start {
-            self.i2c_base.stop_cmd()
-        }
-    }
-
-    fn write_base(
-        &mut self,
-        addr: I2cAddress,
-        init_cmd: I2cCmd,
-        bytes: impl IntoIterator<Item = u8>,
-    ) -> Result<(), Error> {
-        let mut iter = bytes.into_iter();
-        // Load address
-        let (addr, addr_mode_bit) = I2cBase::<I2c>::unwrap_addr(addr);
-        self.i2c_base.i2c.address().write(|w| unsafe {
-            w.bits(I2cDirection::Send as u32 | (addr << 1) as u32 | addr_mode_bit)
-        });
-
-        self.i2c_base
-            .i2c
-            .cmd()
-            .write(|w| unsafe { w.bits(init_cmd as u32) });
-        let mut load_if_next_available = || {
-            if let Some(next_byte) = iter.next() {
-                self.load_fifo(next_byte);
-            }
-        };
-        loop {
-            let status_reader = self.i2c_base.i2c.status().read();
-            if status_reader.arblost().bit_is_set() {
-                self.error_handler_write(&init_cmd);
-                return Err(Error::ArbitrationLost);
-            } else if status_reader.nackaddr().bit_is_set() {
-                self.error_handler_write(&init_cmd);
-                return Err(Error::NackAddr);
-            } else if status_reader.nackdata().bit_is_set() {
-                self.error_handler_write(&init_cmd);
-                return Err(Error::NackData);
-            } else if status_reader.idle().bit_is_set() {
-                return Ok(());
-            } else {
-                while !status_reader.txnfull().bit_is_set() {
-                    load_if_next_available();
-                }
-            }
-        }
-    }
-
-    fn write_from_buffer(
-        &mut self,
-        init_cmd: I2cCmd,
-        addr: I2cAddress,
-        output: &[u8],
-    ) -> Result<(), Error> {
-        let len = output.len();
-        // It should theoretically possible to transfer larger data sizes by tracking
-        // the number of sent words and setting it to 0x7fe as soon as only that many
-        // bytes are remaining. However, large transfer like this are not common. This
-        // feature will therefore not be supported for now.
-        if len > 0x7fe {
-            return Err(Error::DataTooLarge);
-        }
-        // Load number of words
-        self.i2c_base
-            .i2c
-            .words()
-            .write(|w| unsafe { w.bits(len as u32) });
-        let mut bytes = output.iter();
-        // FIFO has a depth of 16. We load slightly above the trigger level
-        // but not all of it because the transaction might fail immediately
-        const FILL_DEPTH: usize = 12;
-
-        // load the FIFO
-        for _ in 0..core::cmp::min(FILL_DEPTH, len) {
-            self.load_fifo(*bytes.next().unwrap());
-        }
-
-        self.write_base(addr, init_cmd, output.iter().cloned())
-    }
-
-    fn read_internal(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
-        let len = buffer.len();
-        // It should theoretically possible to transfer larger data sizes by tracking
-        // the number of sent words and setting it to 0x7fe as soon as only that many
-        // bytes are remaining. However, large transfer like this are not common. This
-        // feature will therefore not be supported for now.
-        if len > 0x7fe {
-            return Err(Error::DataTooLarge);
-        }
-        // Clear the receive FIFO
-        self.clear_rx_fifo();
-
-        // Load number of words
-        self.i2c_base
-            .i2c
-            .words()
-            .write(|w| unsafe { w.bits(len as u32) });
-        let (addr, addr_mode_bit) = match addr {
-            I2cAddress::Regular(addr) => (addr as u16, 0 << 15),
-            I2cAddress::TenBit(addr) => (addr, 1 << 15),
-        };
-        // Load address
-        self.i2c_base.i2c.address().write(|w| unsafe {
-            w.bits(I2cDirection::Read as u32 | (addr << 1) as u32 | addr_mode_bit)
-        });
-
-        let mut buf_iter = buffer.iter_mut();
-        let mut read_bytes = 0;
-        // Start receive transfer
-        self.i2c_base
-            .i2c
-            .cmd()
-            .write(|w| unsafe { w.bits(I2cCmd::StartWithStop as u32) });
-        let mut read_if_next_available = || {
-            if let Some(next_byte) = buf_iter.next() {
-                *next_byte = self.read_fifo();
-            }
-        };
-        loop {
-            let status_reader = self.i2c_base.i2c.status().read();
-            if status_reader.arblost().bit_is_set() {
-                self.clear_rx_fifo();
-                return Err(Error::ArbitrationLost);
-            } else if status_reader.nackaddr().bit_is_set() {
-                self.clear_rx_fifo();
-                return Err(Error::NackAddr);
-            } else if status_reader.idle().bit_is_set() {
-                if read_bytes != len {
-                    return Err(Error::InsufficientDataReceived);
-                }
-                return Ok(());
-            } else if status_reader.rxnempty().bit_is_set() {
-                read_if_next_available();
-                read_bytes += 1;
-            }
-        }
-    }
-}
-
-//======================================================================================
-// Embedded HAL I2C implementations
-//======================================================================================
-
-impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, SevenBitAddress> {
-    type Error = Error;
-}
-
-impl<I2c: Instance> embedded_hal::i2c::I2c for I2cMaster<I2c, SevenBitAddress> {
-    fn transaction(
-        &mut self,
-        address: SevenBitAddress,
-        operations: &mut [Operation<'_>],
-    ) -> Result<(), Self::Error> {
-        for operation in operations {
-            match operation {
-                Operation::Read(buf) => self.read_internal(I2cAddress::Regular(address), buf)?,
-                Operation::Write(buf) => self.write_from_buffer(
-                    I2cCmd::StartWithStop,
-                    I2cAddress::Regular(address),
-                    buf,
-                )?,
-            }
-        }
-        Ok(())
-    }
-}
-
-impl<I2c> embedded_hal::i2c::ErrorType for I2cMaster<I2c, TenBitAddress> {
-    type Error = Error;
-}
-
-impl<I2c: Instance> embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<I2c, TenBitAddress> {
-    fn transaction(
-        &mut self,
-        address: TenBitAddress,
-        operations: &mut [Operation<'_>],
-    ) -> Result<(), Self::Error> {
-        for operation in operations {
-            match operation {
-                Operation::Read(buf) => self.read_internal(I2cAddress::TenBit(address), buf)?,
-                Operation::Write(buf) => {
-                    self.write_from_buffer(I2cCmd::StartWithStop, I2cAddress::TenBit(address), buf)?
-                }
-            }
-        }
-        Ok(())
-    }
-}
-
-//==================================================================================================
-// I2C Slave
-//==================================================================================================
-
-pub struct I2cSlave<I2c, Addr = SevenBitAddress> {
-    i2c_base: I2cBase<I2c>,
-    addr: PhantomData<Addr>,
-}
-
-impl<I2c: Instance, Addr> I2cSlave<I2c, Addr> {
-    fn new_generic(
-        i2c: I2c,
-        sys_cfg: &mut pac::Sysconfig,
-        cfg: SlaveConfig,
-        clocks: &Clocks,
-        speed_mode: I2cSpeed,
-    ) -> Result<Self, ClockTooSlowForFastI2cError> {
-        Ok(I2cSlave {
-            i2c_base: I2cBase::new(i2c, sys_cfg, clocks, speed_mode, None, Some(&cfg))?,
-            addr: PhantomData,
-        }
-        .enable_slave())
-    }
-
-    #[inline]
-    pub fn enable_slave(self) -> Self {
-        self.i2c_base
-            .i2c
-            .s0_ctrl()
-            .modify(|_, w| w.enable().set_bit());
-        self
-    }
-
-    #[inline]
-    pub fn disable_slave(self) -> Self {
-        self.i2c_base
-            .i2c
-            .s0_ctrl()
-            .modify(|_, w| w.enable().clear_bit());
-        self
-    }
-
-    #[inline(always)]
-    fn load_fifo(&self, word: u8) {
-        self.i2c_base
-            .i2c
-            .s0_data()
-            .write(|w| unsafe { w.bits(word as u32) });
-    }
-
-    #[inline(always)]
-    fn read_fifo(&self) -> u8 {
-        self.i2c_base.i2c.s0_data().read().bits() as u8
-    }
-
-    #[inline]
-    fn clear_tx_fifo(&self) {
-        self.i2c_base
-            .i2c
-            .s0_fifo_clr()
-            .write(|w| w.txfifo().set_bit());
-    }
-
-    #[inline]
-    fn clear_rx_fifo(&self) {
-        self.i2c_base
-            .i2c
-            .s0_fifo_clr()
-            .write(|w| w.rxfifo().set_bit());
-    }
-
-    /// Get the last address that was matched by the slave control and the corresponding
-    /// master direction
-    pub fn last_address(&self) -> (I2cDirection, u32) {
-        let bits = self.i2c_base.i2c.s0_lastaddress().read().bits();
-        match bits & 0x01 {
-            0 => (I2cDirection::Send, bits >> 1),
-            1 => (I2cDirection::Read, bits >> 1),
-            _ => (I2cDirection::Send, bits >> 1),
-        }
-    }
-
-    pub fn write(&mut self, output: &[u8]) -> Result<(), Error> {
-        let len = output.len();
-        // It should theoretically possible to transfer larger data sizes by tracking
-        // the number of sent words and setting it to 0x7fe as soon as only that many
-        // bytes are remaining. However, large transfer like this are not common. This
-        // feature will therefore not be supported for now.
-        if len > 0x7fe {
-            return Err(Error::DataTooLarge);
-        }
-        let mut bytes = output.iter();
-        // FIFO has a depth of 16. We load slightly above the trigger level
-        // but not all of it because the transaction might fail immediately
-        const FILL_DEPTH: usize = 12;
-
-        // load the FIFO
-        for _ in 0..core::cmp::min(FILL_DEPTH, len) {
-            self.load_fifo(*bytes.next().unwrap());
-        }
-
-        let status_reader = self.i2c_base.i2c.s0_status().read();
-        let mut load_if_next_available = || {
-            if let Some(next_byte) = bytes.next() {
-                self.load_fifo(*next_byte);
-            }
-        };
-        loop {
-            if status_reader.nackdata().bit_is_set() {
-                self.clear_tx_fifo();
-                return Err(Error::NackData);
-            } else if status_reader.idle().bit_is_set() {
-                return Ok(());
-            } else {
-                while !status_reader.txnfull().bit_is_set() {
-                    load_if_next_available();
-                }
-            }
-        }
-    }
-
-    pub fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
-        let len = buffer.len();
-        // It should theoretically possible to transfer larger data sizes by tracking
-        // the number of sent words and setting it to 0x7fe as soon as only that many
-        // bytes are remaining. However, large transfer like this are not common. This
-        // feature will therefore not be supported for now.
-        if len > 0x7fe {
-            return Err(Error::DataTooLarge);
-        }
-        // Clear the receive FIFO
-        self.clear_rx_fifo();
-
-        let mut buf_iter = buffer.iter_mut();
-        let mut read_bytes = 0;
-        let mut read_if_next_available = || {
-            if let Some(next_byte) = buf_iter.next() {
-                *next_byte = self.read_fifo();
-            }
-        };
-        loop {
-            let status_reader = self.i2c_base.i2c.s0_status().read();
-            if status_reader.idle().bit_is_set() {
-                if read_bytes != len {
-                    return Err(Error::InsufficientDataReceived);
-                }
-                return Ok(());
-            } else if status_reader.rxnempty().bit_is_set() {
-                read_bytes += 1;
-                read_if_next_available();
-            }
-        }
-    }
-}
-
-impl<I2c: Instance> I2cSlave<I2c, SevenBitAddress> {
-    /// Create a new I2C slave for seven bit addresses
-    pub fn new(
-        i2c: I2c,
-        sys_cfg: &mut pac::Sysconfig,
-        cfg: SlaveConfig,
-        clocks: &Clocks,
-        speed_mode: I2cSpeed,
-    ) -> Result<Self, InitError> {
-        if let I2cAddress::TenBit(_) = cfg.addr {
-            return Err(InitError::WrongAddrMode);
-        }
-        Ok(Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)?)
-    }
-}
-
-impl<I2c: Instance> I2cSlave<I2c, TenBitAddress> {
-    pub fn new_ten_bit_addr(
-        i2c: I2c,
-        sys_cfg: &mut pac::Sysconfig,
-        cfg: SlaveConfig,
-        clocks: &Clocks,
-        speed_mode: I2cSpeed,
-    ) -> Result<Self, ClockTooSlowForFastI2cError> {
-        Self::new_generic(i2c, sys_cfg, cfg, clocks, speed_mode)
-    }
-}
+pub use vorago_shared_periphs::i2c::*;
diff --git a/va416xx-hal/src/irq_router.rs b/va416xx-hal/src/irq_router.rs
index 7c44816..93d6f5e 100644
--- a/va416xx-hal/src/irq_router.rs
+++ b/va416xx-hal/src/irq_router.rs
@@ -1,9 +1,10 @@
 //! IRQ Router peripheral support.
-use crate::{
-    clock::{PeripheralSelect, SyscfgExt},
-    pac,
+use vorago_shared_periphs::{
+    enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
 };
 
+use crate::pac;
+
 /// This enables and initiates the peripheral.
 ///
 /// Please note that this method also writes 0 to the registers which do not have 0 as the default
@@ -11,9 +12,10 @@ use crate::{
 /// are inconsistent here, and the registers being non-zero can actually lead to weird bugs
 /// when working with interrupts. Registers DMASELx and ADCSEL/DMASELx will reset to 0x7f and 0x1f
 /// respectively instead of 0x00.
-pub fn enable_and_init_irq_router(sysconfig: &mut pac::Sysconfig, irq_router: &pac::IrqRouter) {
-    sysconfig.enable_peripheral_clock(PeripheralSelect::IrqRouter);
-    sysconfig.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter);
+pub fn enable_and_init_irq_router() {
+    let irq_router = unsafe { pac::IrqRouter::steal() };
+    enable_peripheral_clock(PeripheralSelect::IrqRouter);
+    reset_peripheral_for_cycles(PeripheralSelect::IrqRouter, 2);
     unsafe {
         irq_router.dmasel0().write_with_zero(|w| w);
         irq_router.dmasel1().write_with_zero(|w| w);
diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs
index 676a702..84b058d 100644
--- a/va416xx-hal/src/lib.rs
+++ b/va416xx-hal/src/lib.rs
@@ -41,11 +41,11 @@ pub mod edac;
 pub mod gpio;
 pub mod i2c;
 pub mod irq_router;
+pub mod pins;
 pub mod pwm;
 pub mod spi;
 pub mod time;
 pub mod timer;
-pub mod typelevel;
 pub mod uart;
 pub mod wdt;
 
@@ -57,14 +57,11 @@ pub mod adc;
 #[cfg(not(feature = "va41628"))]
 pub mod dac;
 
-#[derive(Debug, Eq, Copy, Clone, PartialEq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum FunSel {
-    Sel0 = 0b00,
-    Sel1 = 0b01,
-    Sel2 = 0b10,
-    Sel3 = 0b11,
-}
+pub use vorago_shared_periphs::{
+    assert_peripheral_reset, deassert_peripheral_reset, disable_nvic_interrupt,
+    disable_peripheral_clock, enable_nvic_interrupt, enable_peripheral_clock,
+    reset_peripheral_for_cycles, FunSel, PeripheralSelect,
+};
 
 #[derive(Debug, PartialEq, Eq, thiserror::Error)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -100,18 +97,41 @@ pub fn port_function_select(
     Ok(())
 }
 
-/// Enable a specific interrupt using the NVIC peripheral.
-///
-/// # Safety
-///
-/// This function is `unsafe` because it can break mask-based critical sections.
-#[inline]
-pub unsafe fn enable_nvic_interrupt(irq: pac::Interrupt) {
-    cortex_m::peripheral::NVIC::unmask(irq);
+pub trait SyscfgExt {
+    fn enable_peripheral_clock(&mut self, clock: PeripheralSelect);
+
+    fn disable_peripheral_clock(&mut self, clock: PeripheralSelect);
+
+    fn assert_periph_reset(&mut self, periph: PeripheralSelect);
+
+    fn deassert_periph_reset(&mut self, periph: PeripheralSelect);
+
+    fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize);
 }
 
-/// Disable a specific interrupt using the NVIC peripheral.
-#[inline]
-pub fn disable_nvic_interrupt(irq: pac::Interrupt) {
-    cortex_m::peripheral::NVIC::mask(irq);
+impl SyscfgExt for pac::Sysconfig {
+    #[inline(always)]
+    fn enable_peripheral_clock(&mut self, clock: PeripheralSelect) {
+        enable_peripheral_clock(clock)
+    }
+
+    #[inline(always)]
+    fn disable_peripheral_clock(&mut self, clock: PeripheralSelect) {
+        disable_peripheral_clock(clock)
+    }
+
+    #[inline(always)]
+    fn assert_periph_reset(&mut self, clock: PeripheralSelect) {
+        assert_peripheral_reset(clock)
+    }
+
+    #[inline(always)]
+    fn deassert_periph_reset(&mut self, clock: PeripheralSelect) {
+        deassert_peripheral_reset(clock)
+    }
+
+    #[inline(always)]
+    fn reset_peripheral_reset_for_cycles(&mut self, periph: PeripheralSelect, cycles: usize) {
+        reset_peripheral_for_cycles(periph, cycles)
+    }
 }
diff --git a/va416xx-hal/src/nvm.rs b/va416xx-hal/src/nvm.rs
index e9de33f..2c72db9 100644
--- a/va416xx-hal/src/nvm.rs
+++ b/va416xx-hal/src/nvm.rs
@@ -6,11 +6,14 @@
 //!
 //! - [Flashloader application](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
 use embedded_hal::spi::MODE_0;
+use vorago_shared_periphs::{
+    disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
+};
 
-use crate::clock::{Clocks, SyscfgExt};
+use crate::clock::Clocks;
 use crate::pac;
 use crate::spi::{
-    mode_to_cpo_cph_bit, spi_clk_config_from_div, Instance, WordProvider, BMSTART_BMSTOP_MASK,
+    mode_to_cpo_cph_bit, spi_clk_config_from_div, SpiMarker, WordProvider, BMSTART_BMSTOP_MASK,
 };
 
 const NVM_CLOCK_DIV: u16 = 2;
@@ -65,10 +68,10 @@ pub struct VerifyError {
 }
 
 impl Nvm {
-    pub fn new(syscfg: &mut pac::Sysconfig, spi: pac::Spi3, _clocks: &Clocks) -> Self {
-        crate::clock::enable_peripheral_clock(syscfg, pac::Spi3::PERIPH_SEL);
+    pub fn new(spi: pac::Spi3, _clocks: &Clocks) -> Self {
+        enable_peripheral_clock(pac::Spi3::PERIPH_SEL);
         // This is done in the C HAL.
-        syscfg.assert_periph_reset_for_two_cycles(pac::Spi3::PERIPH_SEL);
+        reset_peripheral_for_cycles(pac::Spi3::PERIPH_SEL, 2);
 
         let spi_clk_cfg = spi_clk_config_from_div(NVM_CLOCK_DIV).unwrap();
         let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(MODE_0);
@@ -234,17 +237,17 @@ impl Nvm {
     }
 
     /// Enable write-protection and disables the peripheral clock.
-    pub fn shutdown(&mut self, sys_cfg: &mut pac::Sysconfig) {
+    pub fn shutdown(&mut self) {
         self.wait_for_tx_idle();
         self.write_with_bmstop(FRAM_WREN);
         self.wait_for_tx_idle();
         self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK);
-        crate::clock::disable_peripheral_clock(sys_cfg, pac::Spi3::PERIPH_SEL);
+        disable_peripheral_clock(pac::Spi3::PERIPH_SEL);
     }
 
     /// This function calls [Self::shutdown] and gives back the peripheral structure.
-    pub fn release(mut self, sys_cfg: &mut pac::Sysconfig) -> pac::Spi3 {
-        self.shutdown(sys_cfg);
+    pub fn release(mut self) -> pac::Spi3 {
+        self.shutdown();
         self.spi.take().unwrap()
     }
 
@@ -268,7 +271,7 @@ impl Nvm {
 impl Drop for Nvm {
     fn drop(&mut self) {
         if self.spi.is_some() {
-            self.shutdown(unsafe { &mut pac::Sysconfig::steal() });
+            self.shutdown();
         }
     }
 }
diff --git a/va416xx-hal/src/pins.rs b/va416xx-hal/src/pins.rs
new file mode 100644
index 0000000..8500714
--- /dev/null
+++ b/va416xx-hal/src/pins.rs
@@ -0,0 +1,6 @@
+//! Pin resource management singletons.
+//!
+//! This module contains the pin singletons. It allows creating those singletons
+//! to access the [Pin] structures of individual ports in a safe way with checked ownership
+//! rules.
+pub use vorago_shared_periphs::pins::*;
diff --git a/va416xx-hal/src/prelude.rs b/va416xx-hal/src/prelude.rs
index e67a9ed..1b86abe 100644
--- a/va416xx-hal/src/prelude.rs
+++ b/va416xx-hal/src/prelude.rs
@@ -1,4 +1,4 @@
 //! Prelude
-pub use crate::clock::{ClkgenExt, SyscfgExt};
+pub use crate::clock::ClkgenExt;
 pub use fugit::ExtU32 as _;
 pub use fugit::RateExtU32 as _;
diff --git a/va416xx-hal/src/pwm.rs b/va416xx-hal/src/pwm.rs
index 9d92cc9..decede2 100644
--- a/va416xx-hal/src/pwm.rs
+++ b/va416xx-hal/src/pwm.rs
@@ -1,459 +1,8 @@
 //! API for Pulse-Width Modulation (PWM)
 //!
-//! The Vorago VA416xx devices use the TIM peripherals to perform PWM related tasks.
+//! The Vorago devices use the TIM peripherals to perform PWM related tasks
 //!
 //! ## Examples
 //!
 //! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/pwm.rs)
-use core::convert::Infallible;
-use core::marker::PhantomData;
-
-use crate::pac;
-use crate::time::Hertz;
-pub use crate::timer::ValidTim;
-use crate::timer::{TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTimAndPin};
-use crate::{clock::Clocks, gpio::DynPinId};
-
-const DUTY_MAX: u16 = u16::MAX;
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub(crate) struct PwmCommon {
-    clock: Hertz,
-    /// For PWMB, this is the upper limit
-    current_duty: u16,
-    /// For PWMA, this value will not be used
-    current_lower_limit: u16,
-    current_period: Hertz,
-    current_rst_val: u32,
-}
-
-enum StatusSelPwm {
-    PwmA = 3,
-    PwmB = 4,
-}
-
-pub struct PwmA {}
-pub struct PwmB {}
-
-//==================================================================================================
-// Strongly typed PWM pin
-//==================================================================================================
-
-pub struct PwmPin<Pin: TimPin, Tim: ValidTim, Mode = PwmA> {
-    reg: TimAndPinRegister<Pin, Tim>,
-    inner: ReducedPwmPin<Mode>,
-    mode: PhantomData<Mode>,
-}
-
-impl<Pin: TimPin, Tim: ValidTim, Mode> PwmPin<Pin, Tim, Mode>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    /// Create a new stronlgy typed PWM pin
-    pub fn new(
-        pin_and_tim: (Pin, Tim),
-        sys_cfg: &mut pac::Sysconfig,
-        clocks: &Clocks,
-        initial_period: impl Into<Hertz> + Copy,
-    ) -> Self {
-        let mut pin = PwmPin {
-            inner: ReducedPwmPin::<Mode>::new(
-                Tim::ID,
-                Pin::DYN,
-                PwmCommon {
-                    clock: Tim::clock(clocks),
-                    current_duty: 0,
-                    current_lower_limit: 0,
-                    current_period: initial_period.into(),
-                    current_rst_val: 0,
-                },
-            ),
-            reg: unsafe { TimAndPinRegister::new(pin_and_tim.0, pin_and_tim.1) },
-            mode: PhantomData,
-        };
-        sys_cfg
-            .tim_clk_enable()
-            .modify(|r, w| unsafe { w.bits(r.bits() | pin.reg.mask_32()) });
-        pin.enable_pwm_a();
-        pin.set_period(initial_period);
-        pin
-    }
-
-    pub fn downgrade(self) -> ReducedPwmPin<Mode> {
-        self.inner
-    }
-
-    pub fn release(self) -> (Pin, Tim) {
-        self.reg.release()
-    }
-
-    #[inline]
-    fn enable_pwm_a(&mut self) {
-        self.inner.enable_pwm_a();
-    }
-
-    #[inline]
-    fn enable_pwm_b(&mut self) {
-        self.inner.enable_pwm_b();
-    }
-
-    #[inline]
-    pub fn get_period(&self) -> Hertz {
-        self.inner.get_period()
-    }
-
-    #[inline]
-    pub fn set_period(&mut self, period: impl Into<Hertz>) {
-        self.inner.set_period(period);
-    }
-
-    #[inline]
-    pub fn disable(&mut self) {
-        self.inner.disable();
-    }
-
-    #[inline]
-    pub fn enable(&mut self) {
-        self.inner.enable();
-    }
-
-    #[inline]
-    pub fn period(&self) -> Hertz {
-        self.inner.period()
-    }
-
-    #[inline(always)]
-    pub fn duty(&self) -> u16 {
-        self.inner.duty()
-    }
-}
-
-impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for PwmPin<Pin, Tim, PwmB>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    fn from(other: PwmPin<Pin, Tim, PwmA>) -> Self {
-        let mut pwmb = Self {
-            reg: other.reg,
-            inner: other.inner.into(),
-            mode: PhantomData,
-        };
-        pwmb.enable_pwm_b();
-        pwmb
-    }
-}
-
-impl<PIN: TimPin, TIM: ValidTim> From<PwmPin<PIN, TIM, PwmB>> for PwmPin<PIN, TIM, PwmA>
-where
-    (PIN, TIM): ValidTimAndPin<PIN, TIM>,
-{
-    fn from(other: PwmPin<PIN, TIM, PwmB>) -> Self {
-        let mut pwmb = Self {
-            reg: other.reg,
-            inner: other.inner.into(),
-            mode: PhantomData,
-        };
-        pwmb.enable_pwm_a();
-        pwmb
-    }
-}
-
-impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmA>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    pub fn pwma(
-        tim_and_pin: (Pin, Tim),
-        sys_cfg: &mut pac::Sysconfig,
-        clocks: &Clocks,
-        initial_period: impl Into<Hertz> + Copy,
-    ) -> Self {
-        let mut pin: PwmPin<Pin, Tim, PwmA> =
-            Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
-        pin.enable_pwm_a();
-        pin
-    }
-}
-
-impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    pub fn pwmb(
-        tim_and_pin: (Pin, Tim),
-        sys_cfg: &mut pac::Sysconfig,
-        clocks: &Clocks,
-        initial_period: impl Into<Hertz> + Copy,
-    ) -> Self {
-        let mut pin: PwmPin<Pin, Tim, PwmB> =
-            Self::new(tim_and_pin, sys_cfg, clocks, initial_period);
-        pin.enable_pwm_b();
-        pin
-    }
-}
-
-//==================================================================================================
-// Reduced PWM pin
-//==================================================================================================
-
-/// Reduced version where type information is deleted
-pub struct ReducedPwmPin<Mode = PwmA> {
-    dyn_reg: TimDynRegister,
-    common: PwmCommon,
-    mode: PhantomData<Mode>,
-}
-
-impl<Mode> ReducedPwmPin<Mode> {
-    pub(crate) fn new(tim_id: u8, pin_id: DynPinId, common: PwmCommon) -> Self {
-        Self {
-            dyn_reg: TimDynRegister { tim_id, pin_id },
-            common,
-            mode: PhantomData,
-        }
-    }
-
-    #[inline]
-    fn enable_pwm_a(&mut self) {
-        self.dyn_reg
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) });
-    }
-
-    #[inline]
-    fn enable_pwm_b(&mut self) {
-        self.dyn_reg
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) });
-    }
-
-    #[inline]
-    pub fn get_period(&self) -> Hertz {
-        self.common.current_period
-    }
-
-    #[inline]
-    pub fn set_period(&mut self, period: impl Into<Hertz>) {
-        self.common.current_period = period.into();
-        // Avoid division by 0
-        if self.common.current_period.raw() == 0 {
-            return;
-        }
-        self.common.current_rst_val = self.common.clock.raw() / self.common.current_period.raw();
-        self.dyn_reg
-            .reg_block()
-            .rst_value()
-            .write(|w| unsafe { w.bits(self.common.current_rst_val) });
-    }
-
-    #[inline]
-    pub fn disable(&mut self) {
-        self.dyn_reg
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| w.enable().clear_bit());
-    }
-
-    #[inline]
-    pub fn enable(&mut self) {
-        self.dyn_reg
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| w.enable().set_bit());
-    }
-
-    #[inline]
-    pub fn period(&self) -> Hertz {
-        self.common.current_period
-    }
-
-    #[inline(always)]
-    pub fn duty(&self) -> u16 {
-        self.common.current_duty
-    }
-}
-
-impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmA>> for ReducedPwmPin<PwmA>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    fn from(value: PwmPin<Pin, Tim, PwmA>) -> Self {
-        value.downgrade()
-    }
-}
-
-impl<Pin: TimPin, Tim: ValidTim> From<PwmPin<Pin, Tim, PwmB>> for ReducedPwmPin<PwmB>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    fn from(value: PwmPin<Pin, Tim, PwmB>) -> Self {
-        value.downgrade()
-    }
-}
-
-impl From<ReducedPwmPin<PwmA>> for ReducedPwmPin<PwmB> {
-    fn from(other: ReducedPwmPin<PwmA>) -> Self {
-        let mut pwmb = Self {
-            dyn_reg: other.dyn_reg,
-            common: other.common,
-            mode: PhantomData,
-        };
-        pwmb.enable_pwm_b();
-        pwmb
-    }
-}
-
-impl From<ReducedPwmPin<PwmB>> for ReducedPwmPin<PwmA> {
-    fn from(other: ReducedPwmPin<PwmB>) -> Self {
-        let mut pwmb = Self {
-            dyn_reg: other.dyn_reg,
-            common: other.common,
-            mode: PhantomData,
-        };
-        pwmb.enable_pwm_a();
-        pwmb
-    }
-}
-
-//==================================================================================================
-// PWMB implementations
-//==================================================================================================
-
-impl<Pin: TimPin, Tim: ValidTim> PwmPin<Pin, Tim, PwmB>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    pub fn pwmb_lower_limit(&self) -> u16 {
-        self.inner.pwmb_lower_limit()
-    }
-
-    pub fn pwmb_upper_limit(&self) -> u16 {
-        self.inner.pwmb_upper_limit()
-    }
-
-    /// Set the lower limit for PWMB
-    ///
-    /// The PWM signal will be 1 as long as the current RST counter is larger than
-    /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
-    /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
-    /// state
-    pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
-        self.inner.set_pwmb_lower_limit(duty);
-    }
-
-    /// Set the higher limit for PWMB
-    ///
-    /// The PWM signal will be 1 as long as the current RST counter is smaller than
-    /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
-    /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
-    /// state
-    pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
-        self.inner.set_pwmb_upper_limit(duty);
-    }
-}
-
-impl ReducedPwmPin<PwmB> {
-    #[inline(always)]
-    pub fn pwmb_lower_limit(&self) -> u16 {
-        self.common.current_lower_limit
-    }
-
-    #[inline(always)]
-    pub fn pwmb_upper_limit(&self) -> u16 {
-        self.common.current_duty
-    }
-
-    /// Set the lower limit for PWMB
-    ///
-    /// The PWM signal will be 1 as long as the current RST counter is larger than
-    /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
-    /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
-    /// state
-    #[inline(always)]
-    pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
-        self.common.current_lower_limit = duty;
-        let pwmb_val: u64 = (self.common.current_rst_val as u64
-            * self.common.current_lower_limit as u64)
-            / DUTY_MAX as u64;
-        self.dyn_reg
-            .reg_block()
-            .pwmb_value()
-            .write(|w| unsafe { w.bits(pwmb_val as u32) });
-    }
-
-    /// Set the higher limit for PWMB
-    ///
-    /// The PWM signal will be 1 as long as the current RST counter is smaller than
-    /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
-    /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
-    /// state
-    pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
-        self.common.current_duty = duty;
-        let pwma_val: u64 = (self.common.current_rst_val as u64 * self.common.current_duty as u64)
-            / DUTY_MAX as u64;
-        self.dyn_reg
-            .reg_block()
-            .pwma_value()
-            .write(|w| unsafe { w.bits(pwma_val as u32) });
-    }
-}
-
-//==================================================================================================
-// Embedded HAL implementation: PWMA only
-//==================================================================================================
-
-impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::ErrorType for PwmPin<Pin, Tim> {
-    type Error = Infallible;
-}
-
-impl embedded_hal::pwm::ErrorType for ReducedPwmPin {
-    type Error = Infallible;
-}
-
-impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin {
-    #[inline]
-    fn max_duty_cycle(&self) -> u16 {
-        DUTY_MAX
-    }
-
-    #[inline]
-    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
-        self.common.current_duty = duty;
-        let pwma_val: u64 = (self.common.current_rst_val as u64
-            * (DUTY_MAX as u64 - self.common.current_duty as u64))
-            / DUTY_MAX as u64;
-        self.dyn_reg
-            .reg_block()
-            .pwma_value()
-            .write(|w| unsafe { w.bits(pwma_val as u32) });
-        Ok(())
-    }
-}
-
-impl<Pin: TimPin, Tim: ValidTim> embedded_hal::pwm::SetDutyCycle for PwmPin<Pin, Tim> {
-    #[inline]
-    fn max_duty_cycle(&self) -> u16 {
-        DUTY_MAX
-    }
-
-    #[inline]
-    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
-        self.inner.set_duty_cycle(duty)
-    }
-}
-
-/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
-///
-/// Please note that this might load a lot of floating point code because this processor does not
-/// have a FPU
-pub fn get_duty_from_percent(percent: f32) -> u16 {
-    if percent > 1.0 {
-        DUTY_MAX
-    } else if percent <= 0.0 {
-        0
-    } else {
-        (percent * DUTY_MAX as f32) as u16
-    }
-}
+pub use vorago_shared_periphs::pwm::*;
diff --git a/va416xx-hal/src/spi.rs b/va416xx-hal/src/spi.rs
index 2681b83..991347f 100644
--- a/va416xx-hal/src/spi.rs
+++ b/va416xx-hal/src/spi.rs
@@ -1,1251 +1,11 @@
-//! API for the SPI peripheral
+//! API for the SPI peripheral.
 //!
-//! The main abstraction provided by this module are the [Spi] and the [SpiBase] structure.
-//! These provide the [embedded_hal::spi] traits, but also offer a low level interface
+//! The main abstraction provided by this module is the [Spi] an structure.
+//! It provides the [embedded_hal::spi] traits, but also offer a low level interface
 //! via the [SpiLowLevel] trait.
 //!
 //! ## Examples
 //!
 //! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs)
-use core::{convert::Infallible, marker::PhantomData, ops::Deref};
-
-use embedded_hal::spi::{Mode, MODE_0};
-
-use crate::{
-    clock::{Clocks, PeripheralSelect, SyscfgExt},
-    gpio::{
-        AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
-        PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1, PC10, PC11, PC7, PC8, PC9, PE12,
-        PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9, PF0, PF1, PG2, PG3, PG4,
-    },
-    pac,
-    time::Hertz,
-    typelevel::{NoneT, Sealed},
-};
-
-#[cfg(not(feature = "va41628"))]
-use crate::gpio::{PB10, PB11, PB5, PB6, PB7, PB8, PB9, PE10, PE11, PF2, PF3, PF4, PF5, PF6, PF7};
-
-//==================================================================================================
-// Defintions
-//==================================================================================================
-
-// FIFO has a depth of 16.
-const FILL_DEPTH: usize = 12;
-
-pub const DEFAULT_CLK_DIV: u16 = 2;
-
-pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
-pub const BMSKIPDATA_MASK: u32 = 1 << 30;
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum HwChipSelectId {
-    Id0 = 0,
-    Id1 = 1,
-    Id2 = 2,
-    Id3 = 3,
-    Id4 = 4,
-    Id5 = 5,
-    Id6 = 6,
-    Id7 = 7,
-    Invalid = 0xff,
-}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum SpiId {
-    Spi0,
-    Spi1,
-    Spi2,
-    Spi3,
-    Invalid,
-}
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum WordSize {
-    OneBit = 0x00,
-    FourBits = 0x03,
-    EightBits = 0x07,
-    SixteenBits = 0x0f,
-}
-
-pub type SpiRegBlock = pac::spi0::RegisterBlock;
-
-/// Common trait implemented by all PAC peripheral access structures. The register block
-/// format is the same for all SPI blocks.
-pub trait Instance: Deref<Target = SpiRegBlock> {
-    const IDX: u8;
-    const PERIPH_SEL: PeripheralSelect;
-
-    fn ptr() -> *const SpiRegBlock;
-}
-
-impl Instance for pac::Spi0 {
-    const IDX: u8 = 0;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
-
-    #[inline(always)]
-    fn ptr() -> *const SpiRegBlock {
-        Self::ptr()
-    }
-}
-
-impl Instance for pac::Spi1 {
-    const IDX: u8 = 1;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
-
-    #[inline(always)]
-    fn ptr() -> *const SpiRegBlock {
-        Self::ptr()
-    }
-}
-
-impl Instance for pac::Spi2 {
-    const IDX: u8 = 2;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
-
-    #[inline(always)]
-    fn ptr() -> *const SpiRegBlock {
-        Self::ptr()
-    }
-}
-
-impl Instance for pac::Spi3 {
-    const IDX: u8 = 3;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi3;
-
-    #[inline(always)]
-    fn ptr() -> *const SpiRegBlock {
-        Self::ptr()
-    }
-}
-
-//==================================================================================================
-// Pin type definitions
-//==================================================================================================
-
-pub trait PinSck<SPI>: Sealed {}
-pub trait PinMosi<SPI>: Sealed {}
-pub trait PinMiso<SPI>: Sealed {}
-
-pub trait HwCsProvider: Sealed {
-    const CS_ID: HwChipSelectId;
-    const SPI_ID: SpiId;
-}
-
-pub trait OptionalHwCs<Spi>: HwCsProvider + Sealed {}
-
-macro_rules! hw_cs_pins {
-    ($SPIx:path, $portId: path:
-        $(
-            ($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident $(, $meta: meta)?),
-        )+
-    ) => {
-        $(
-            $(#[$meta])?
-            impl HwCsProvider for Pin<$PXx, $AFx> {
-                const CS_ID: HwChipSelectId = $HwCsIdent;
-                const SPI_ID: SpiId = $portId;
-            }
-
-            $(#[$meta])?
-            impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {}
-
-            $(#[$meta])?
-            pub type $typedef = Pin<$PXx, $AFx>;
-        )+
-    };
-}
-
-impl HwCsProvider for NoneT {
-    const CS_ID: HwChipSelectId = HwChipSelectId::Invalid;
-    const SPI_ID: SpiId = SpiId::Invalid;
-}
-
-impl OptionalHwCs<pac::Spi0> for NoneT {}
-impl OptionalHwCs<pac::Spi1> for NoneT {}
-impl OptionalHwCs<pac::Spi2> for NoneT {}
-impl OptionalHwCs<pac::Spi3> for NoneT {}
-
-pub struct RomSpiSck;
-pub struct RomSpiMiso;
-pub struct RomSpiMosi;
-
-impl Sealed for RomSpiSck {}
-impl Sealed for RomSpiMosi {}
-impl Sealed for RomSpiMiso {}
-
-// SPI 0
-
-impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {}
-impl PinMosi<pac::Spi0> for Pin<PC1, AltFunc1> {}
-impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {}
-
-// SPI 1
-#[cfg(not(feature = "va41628"))]
-impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {}
-#[cfg(not(feature = "va41628"))]
-impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {}
-#[cfg(not(feature = "va41628"))]
-impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {}
-
-impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {}
-impl PinMosi<pac::Spi1> for Pin<PC11, AltFunc2> {}
-impl PinMiso<pac::Spi1> for Pin<PC10, AltFunc2> {}
-
-impl PinSck<pac::Spi1> for Pin<PG3, AltFunc2> {}
-impl PinMiso<pac::Spi1> for Pin<PG4, AltFunc2> {}
-
-impl PinSck<pac::Spi1> for Pin<PE13, AltFunc2> {}
-impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {}
-impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {}
-
-#[cfg(not(feature = "va41628"))]
-impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {}
-#[cfg(not(feature = "va41628"))]
-impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {}
-#[cfg(not(feature = "va41628"))]
-impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {}
-
-// SPI 2
-
-impl PinSck<pac::Spi2> for Pin<PA5, AltFunc2> {}
-impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {}
-impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {}
-
-#[cfg(not(feature = "va41628"))]
-impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {}
-#[cfg(not(feature = "va41628"))]
-impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {}
-#[cfg(not(feature = "va41628"))]
-impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {}
-
-// SPI3 is shared with the ROM SPI pins and has its own dedicated pins.
-//
-impl PinSck<pac::Spi3> for RomSpiSck {}
-impl PinMosi<pac::Spi3> for RomSpiMosi {}
-impl PinMiso<pac::Spi3> for RomSpiMiso {}
-
-// SPI 0 HW CS pins
-
-hw_cs_pins!(
-    pac::Spi0, SpiId::Spi0:
-    (PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0),
-    (PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0),
-    (PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0),
-    (PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0, cfg(not(feature="va41628"))),
-);
-
-hw_cs_pins!(
-    pac::Spi1, SpiId::Spi1:
-    (PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb, cfg(not(feature="va41628"))),
-    (PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb, cfg(not(feature="va41628"))),
-    (PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb, cfg(not(feature="va41628"))),
-    (PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb),
-    (PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb),
-    (PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb),
-    (PB1, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pb),
-    (PB0, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pb),
-    (PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc),
-    (PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc),
-    (PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe),
-    (PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe, cfg(not(feature="va41628"))),
-    (PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe, cfg(not(feature="va41628"))),
-    (PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe),
-    (PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe),
-    (PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe),
-    (PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe),
-    (PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe),
-    (PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf, cfg(not(feature="va41628"))),
-    (PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
-);
-
-hw_cs_pins!(
-    pac::Spi2, SpiId::Spi2:
-    (PA4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pa),
-    (PA3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pa),
-    (PA2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pa),
-    (PA1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pa),
-    (PA0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pa),
-    (PA8, AltFunc2, HwChipSelectId::Id6, HwCs6Spi2Pa),
-    (PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa),
-    (PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf),
-    (PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf),
-    (PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf, cfg(not(feature="va41628"))),
-    (PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf, cfg(not(feature="va41628"))),
-    (PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf, cfg(not(feature="va41628"))),
-);
-
-//==================================================================================================
-// Config
-//==================================================================================================
-
-pub trait TransferConfigProvider {
-    fn sod(&mut self, sod: bool);
-    fn blockmode(&mut self, blockmode: bool);
-    fn mode(&mut self, mode: Mode);
-    fn clk_cfg(&mut self, clk_cfg: SpiClkConfig);
-    fn hw_cs_id(&self) -> u8;
-}
-
-/// This struct contains all configuration parameter which are transfer specific
-/// and might change for transfers to different SPI slaves
-#[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct TransferConfigWithHwcs<HwCs> {
-    pub hw_cs: Option<HwCs>,
-    pub cfg: TransferConfig,
-}
-
-/// Type erased variant of the transfer configuration. This is required to avoid generics in
-/// the SPI constructor.
-#[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct TransferConfig {
-    pub clk_cfg: Option<SpiClkConfig>,
-    pub mode: Option<Mode>,
-    pub sod: bool,
-    /// If this is enabled, all data in the FIFO is transmitted in a single frame unless
-    /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
-    /// duration of multiple data words
-    pub blockmode: bool,
-    /// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
-    /// is set on a written word.
-    pub bmstall: bool,
-    pub hw_cs: HwChipSelectId,
-}
-
-impl TransferConfigWithHwcs<NoneT> {
-    pub fn new_no_hw_cs(
-        clk_cfg: Option<SpiClkConfig>,
-        mode: Option<Mode>,
-        blockmode: bool,
-        bmstall: bool,
-        sod: bool,
-    ) -> Self {
-        TransferConfigWithHwcs {
-            hw_cs: None,
-            cfg: TransferConfig {
-                clk_cfg,
-                mode,
-                sod,
-                blockmode,
-                bmstall,
-                hw_cs: HwChipSelectId::Invalid,
-            },
-        }
-    }
-}
-
-impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> {
-    pub fn new(
-        clk_cfg: Option<SpiClkConfig>,
-        mode: Option<Mode>,
-        hw_cs: Option<HwCs>,
-        blockmode: bool,
-        bmstall: bool,
-        sod: bool,
-    ) -> Self {
-        TransferConfigWithHwcs {
-            hw_cs,
-            cfg: TransferConfig {
-                clk_cfg,
-                mode,
-                sod,
-                blockmode,
-                bmstall,
-                hw_cs: HwCs::CS_ID,
-            },
-        }
-    }
-
-    pub fn downgrade(self) -> TransferConfig {
-        self.cfg
-    }
-}
-
-impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfigWithHwcs<HwCs> {
-    /// Slave Output Disable
-    fn sod(&mut self, sod: bool) {
-        self.cfg.sod = sod;
-    }
-
-    fn blockmode(&mut self, blockmode: bool) {
-        self.cfg.blockmode = blockmode;
-    }
-
-    fn mode(&mut self, mode: Mode) {
-        self.cfg.mode = Some(mode);
-    }
-
-    fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) {
-        self.cfg.clk_cfg = Some(clk_cfg);
-    }
-
-    fn hw_cs_id(&self) -> u8 {
-        HwCs::CS_ID as u8
-    }
-}
-
-/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct SpiConfig {
-    clk: SpiClkConfig,
-    // SPI mode configuration
-    pub init_mode: Mode,
-    /// If this is enabled, all data in the FIFO is transmitted in a single frame unless
-    /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
-    /// duration of multiple data words. Defaults to true.
-    pub blockmode: bool,
-    /// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty.
-    /// Currently enabled by default.
-    pub bmstall: bool,
-    /// By default, configure SPI for master mode (ms == false)
-    ms: bool,
-    /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
-    pub slave_output_disable: bool,
-    /// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
-    pub loopback_mode: bool,
-    /// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
-    pub master_delayer_capture: bool,
-}
-
-impl Default for SpiConfig {
-    fn default() -> Self {
-        Self {
-            init_mode: MODE_0,
-            blockmode: true,
-            bmstall: true,
-            // Default value is definitely valid.
-            clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
-            ms: Default::default(),
-            slave_output_disable: Default::default(),
-            loopback_mode: Default::default(),
-            master_delayer_capture: Default::default(),
-        }
-    }
-}
-
-impl SpiConfig {
-    pub fn loopback(mut self, enable: bool) -> Self {
-        self.loopback_mode = enable;
-        self
-    }
-
-    pub fn blockmode(mut self, enable: bool) -> Self {
-        self.blockmode = enable;
-        self
-    }
-
-    pub fn bmstall(mut self, enable: bool) -> Self {
-        self.bmstall = enable;
-        self
-    }
-
-    pub fn mode(mut self, mode: Mode) -> Self {
-        self.init_mode = mode;
-        self
-    }
-
-    pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
-        self.clk = clk_cfg;
-        self
-    }
-
-    pub fn master_mode(mut self, master: bool) -> Self {
-        self.ms = !master;
-        self
-    }
-
-    pub fn slave_output_disable(mut self, sod: bool) -> Self {
-        self.slave_output_disable = sod;
-        self
-    }
-}
-
-//==================================================================================================
-// Word Size
-//==================================================================================================
-
-/// Configuration trait for the Word Size used by the SPI peripheral
-pub trait WordProvider: Copy + Default + Into<u32> + TryFrom<u32> + 'static {
-    const MASK: u32;
-    fn word_reg() -> u8;
-}
-
-impl WordProvider for u8 {
-    const MASK: u32 = 0xff;
-    fn word_reg() -> u8 {
-        0x07
-    }
-}
-
-impl WordProvider for u16 {
-    const MASK: u32 = 0xffff;
-    fn word_reg() -> u8 {
-        0x0f
-    }
-}
-
-//==================================================================================================
-// Spi
-//==================================================================================================
-
-/// Low level access trait for the SPI peripheral.
-pub trait SpiLowLevel {
-    /// Low level function to write a word to the SPI FIFO but also checks whether
-    /// there is actually data in the FIFO.
-    ///
-    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
-    fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>;
-
-    /// Low level function to write a word to the SPI FIFO without checking whether
-    /// there FIFO is full.
-    ///
-    /// This does not necesarily mean there is a space in the FIFO available.
-    /// Use [Self::write_fifo] function to write a word into the FIFO reliably.
-    fn write_fifo_unchecked(&self, data: u32);
-
-    /// Low level function to read a word from the SPI FIFO. Must be preceeded by a
-    /// [Self::write_fifo] call.
-    ///
-    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
-    fn read_fifo(&self) -> nb::Result<u32, Infallible>;
-
-    /// Low level function to read a word from from the SPI FIFO.
-    ///
-    /// This does not necesarily mean there is a word in the FIFO available.
-    /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
-    /// API.
-    /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
-    fn read_fifo_unchecked(&self) -> u32;
-}
-
-pub struct SpiBase<SpiInstance, Word = u8> {
-    spi: SpiInstance,
-    cfg: SpiConfig,
-    apb1_clk: Hertz,
-    /// Fill word for read-only SPI transactions.
-    pub fill_word: Word,
-    blockmode: bool,
-    bmstall: bool,
-    word: PhantomData<Word>,
-}
-
-pub struct Spi<SpiInstance, Pins, Word = u8> {
-    inner: SpiBase<SpiInstance, Word>,
-    pins: Pins,
-}
-
-#[inline(always)]
-pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
-    match mode {
-        embedded_hal::spi::MODE_0 => (false, false),
-        embedded_hal::spi::MODE_1 => (false, true),
-        embedded_hal::spi::MODE_2 => (true, false),
-        embedded_hal::spi::MODE_3 => (true, true),
-    }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct SpiClkConfig {
-    prescale_val: u16,
-    scrdv: u8,
-}
-
-impl SpiClkConfig {
-    pub fn prescale_val(&self) -> u16 {
-        self.prescale_val
-    }
-    pub fn scrdv(&self) -> u8 {
-        self.scrdv
-    }
-}
-
-impl SpiClkConfig {
-    pub fn new(prescale_val: u16, scrdv: u8) -> Self {
-        Self {
-            prescale_val,
-            scrdv,
-        }
-    }
-
-    pub fn from_div(div: u16) -> Result<Self, SpiClkConfigError> {
-        spi_clk_config_from_div(div)
-    }
-
-    pub fn from_clk(spi_clk: Hertz, clocks: &Clocks) -> Option<Self> {
-        clk_div_for_target_clock(spi_clk, clocks).map(|div| spi_clk_config_from_div(div).unwrap())
-    }
-}
-
-#[derive(Debug, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum SpiClkConfigError {
-    #[error("division by zero")]
-    DivIsZero,
-    #[error("divide value is not even")]
-    DivideValueNotEven,
-    #[error("scrdv value is too large")]
-    ScrdvValueTooLarge,
-}
-
-#[inline]
-pub fn spi_clk_config_from_div(mut div: u16) -> Result<SpiClkConfig, SpiClkConfigError> {
-    if div == 0 {
-        return Err(SpiClkConfigError::DivIsZero);
-    }
-    if div % 2 != 0 {
-        return Err(SpiClkConfigError::DivideValueNotEven);
-    }
-    let mut prescale_val = 0;
-
-    // find largest (even) prescale value that divides into div
-    for i in (2..=0xfe).rev().step_by(2) {
-        if div % i == 0 {
-            prescale_val = i;
-            break;
-        }
-    }
-
-    if prescale_val == 0 {
-        return Err(SpiClkConfigError::DivideValueNotEven);
-    }
-
-    div /= prescale_val;
-    if div > u8::MAX as u16 + 1 {
-        return Err(SpiClkConfigError::ScrdvValueTooLarge);
-    }
-    Ok(SpiClkConfig {
-        prescale_val,
-        scrdv: (div - 1) as u8,
-    })
-}
-
-#[inline]
-pub fn clk_div_for_target_clock(spi_clk: impl Into<Hertz>, clocks: &Clocks) -> Option<u16> {
-    let spi_clk = spi_clk.into();
-    if spi_clk > clocks.apb1() {
-        return None;
-    }
-
-    // Step 1: Calculate raw divider.
-    let raw_div = clocks.apb1().raw() / spi_clk.raw();
-    let remainder = clocks.apb1().raw() % spi_clk.raw();
-
-    // Step 2: Round up if necessary.
-    let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
-        raw_div + 1
-    } else {
-        raw_div
-    };
-
-    if rounded_div % 2 != 0 {
-        // Take slower clock conservatively.
-        rounded_div += 1;
-    }
-    if rounded_div > u16::MAX as u32 {
-        return None;
-    }
-    Some(rounded_div as u16)
-}
-
-impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word>
-where
-    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
-{
-    #[inline]
-    pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
-        self.spi
-            .ctrl0()
-            .modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) });
-        self.spi
-            .clkprescale()
-            .write(|w| unsafe { w.bits(cfg.prescale_val as u32) });
-    }
-
-    #[inline]
-    pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
-        let val = spi_clk_config_from_div(div)?;
-        self.cfg_clock(val);
-        Ok(())
-    }
-
-    #[inline]
-    pub fn cfg_mode(&mut self, mode: Mode) {
-        let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
-        self.spi.ctrl0().modify(|_, w| {
-            w.spo().bit(cpo_bit);
-            w.sph().bit(cph_bit)
-        });
-    }
-
-    #[inline]
-    pub fn spi(&self) -> &SpiInstance {
-        &self.spi
-    }
-
-    #[inline]
-    pub fn clear_tx_fifo(&self) {
-        self.spi.fifo_clr().write(|w| w.txfifo().set_bit());
-    }
-
-    #[inline]
-    pub fn clear_rx_fifo(&self) {
-        self.spi.fifo_clr().write(|w| w.rxfifo().set_bit());
-    }
-
-    #[inline]
-    pub fn perid(&self) -> u32 {
-        self.spi.perid().read().bits()
-    }
-
-    #[inline]
-    pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) {
-        if hw_cs == HwChipSelectId::Invalid {
-            return;
-        }
-        self.spi.ctrl1().modify(|_, w| {
-            w.sod().clear_bit();
-            unsafe {
-                w.ss().bits(hw_cs as u8);
-            }
-            w
-        });
-    }
-
-    #[inline]
-    pub fn cfg_hw_cs_with_pin<HwCs: OptionalHwCs<SpiInstance>>(&mut self, _: &HwCs) {
-        self.cfg_hw_cs(HwCs::CS_ID);
-    }
-
-    #[inline]
-    pub fn cfg_hw_cs_disable(&mut self) {
-        self.spi.ctrl1().modify(|_, w| {
-            w.sod().set_bit();
-            w
-        });
-    }
-
-    pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
-        &mut self,
-        transfer_cfg: &TransferConfigWithHwcs<HwCs>,
-    ) {
-        if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg {
-            self.cfg_clock(trans_clk_div);
-        }
-        if let Some(mode) = transfer_cfg.cfg.mode {
-            self.cfg_mode(mode);
-        }
-        self.blockmode = transfer_cfg.cfg.blockmode;
-        self.spi.ctrl1().modify(|_, w| {
-            if transfer_cfg.cfg.sod {
-                w.sod().set_bit();
-            } else if transfer_cfg.hw_cs.is_some() {
-                w.sod().clear_bit();
-                unsafe {
-                    w.ss().bits(HwCs::CS_ID as u8);
-                }
-            } else {
-                w.sod().clear_bit();
-            }
-            w.blockmode().bit(transfer_cfg.cfg.blockmode);
-            w.bmstall().bit(transfer_cfg.cfg.bmstall)
-        });
-    }
-
-    /// Low level function to write a word to the SPI FIFO but also checks whether
-    /// there is actually data in the FIFO.
-    ///
-    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
-    #[inline(always)]
-    pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
-        if self.spi.status().read().tnf().bit_is_clear() {
-            return Err(nb::Error::WouldBlock);
-        }
-        self.write_fifo_unchecked(data);
-        Ok(())
-    }
-
-    /// Low level function to write a word to the SPI FIFO without checking whether
-    /// there FIFO is full.
-    ///
-    /// This does not necesarily mean there is a space in the FIFO available.
-    /// Use [Self::write_fifo] function to write a word into the FIFO reliably.
-    #[inline(always)]
-    pub fn write_fifo_unchecked(&self, data: u32) {
-        self.spi.data().write(|w| unsafe { w.bits(data) });
-    }
-
-    /// Low level function to read a word from the SPI FIFO. Must be preceeded by a
-    /// [Self::write_fifo] call.
-    ///
-    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
-    #[inline(always)]
-    pub fn read_fifo(&self) -> nb::Result<u32, Infallible> {
-        if self.spi.status().read().rne().bit_is_clear() {
-            return Err(nb::Error::WouldBlock);
-        }
-        Ok(self.read_fifo_unchecked())
-    }
-
-    /// Low level function to read a word from from the SPI FIFO.
-    ///
-    /// This does not necesarily mean there is a word in the FIFO available.
-    /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
-    /// API.
-    /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
-    #[inline(always)]
-    pub fn read_fifo_unchecked(&self) -> u32 {
-        self.spi.data().read().bits()
-    }
-
-    fn flush_internal(&self) {
-        let mut status_reg = self.spi.status().read();
-        while status_reg.tfe().bit_is_clear()
-            || status_reg.rne().bit_is_set()
-            || status_reg.busy().bit_is_set()
-        {
-            if status_reg.rne().bit_is_set() {
-                self.read_fifo_unchecked();
-            }
-            status_reg = self.spi.status().read();
-        }
-    }
-
-    fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
-        if words.is_empty() {
-            return Ok(());
-        }
-        self.flush_internal();
-        Ok(())
-    }
-
-    // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
-    // initialization. Returns the amount of written bytes.
-    fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize {
-        if self.blockmode {
-            self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit());
-        }
-        // Fill the first half of the write FIFO
-        let mut current_write_idx = 0;
-        let smaller_idx = core::cmp::min(FILL_DEPTH, words.len());
-        for _ in 0..smaller_idx {
-            if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
-                self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK);
-            } else {
-                self.write_fifo_unchecked(words[current_write_idx].into());
-            }
-            current_write_idx += 1;
-        }
-        if self.blockmode {
-            self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit());
-        }
-        current_write_idx
-    }
-
-    // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
-    // initialization.
-    fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize {
-        if self.blockmode {
-            self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit());
-        }
-        // Fill the first half of the write FIFO
-        let mut current_write_idx = 0;
-        let smaller_idx = core::cmp::min(FILL_DEPTH, send_len);
-        for _ in 0..smaller_idx {
-            if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
-                self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK);
-            } else {
-                self.write_fifo_unchecked(self.fill_word.into());
-            }
-            current_write_idx += 1;
-        }
-        if self.blockmode {
-            self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit());
-        }
-        current_write_idx
-    }
-}
-
-impl<SpiInstance: Instance, Word: WordProvider> SpiLowLevel for SpiBase<SpiInstance, Word>
-where
-    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
-{
-    #[inline(always)]
-    fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
-        if self.spi.status().read().tnf().bit_is_clear() {
-            return Err(nb::Error::WouldBlock);
-        }
-        self.write_fifo_unchecked(data);
-        Ok(())
-    }
-
-    #[inline(always)]
-    fn write_fifo_unchecked(&self, data: u32) {
-        self.spi.data().write(|w| unsafe { w.bits(data) });
-    }
-
-    #[inline(always)]
-    fn read_fifo(&self) -> nb::Result<u32, Infallible> {
-        if self.spi.status().read().rne().bit_is_clear() {
-            return Err(nb::Error::WouldBlock);
-        }
-        Ok(self.read_fifo_unchecked())
-    }
-
-    #[inline(always)]
-    fn read_fifo_unchecked(&self) -> u32 {
-        self.spi.data().read().bits()
-    }
-}
-
-impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
-    type Error = Infallible;
-}
-
-impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
-where
-    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
-{
-    fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
-        self.transfer_preparation(words)?;
-        let mut current_read_idx = 0;
-        let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len());
-        loop {
-            if current_read_idx < words.len() {
-                words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
-                    .try_into()
-                    .unwrap();
-                current_read_idx += 1;
-            }
-            if current_write_idx < words.len() {
-                if current_write_idx == words.len() - 1 && self.bmstall {
-                    nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?;
-                } else {
-                    nb::block!(self.write_fifo(self.fill_word.into()))?;
-                }
-                current_write_idx += 1;
-            }
-            if current_read_idx >= words.len() && current_write_idx >= words.len() {
-                break;
-            }
-        }
-        Ok(())
-    }
-
-    fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
-        self.transfer_preparation(words)?;
-        let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
-        while current_write_idx < words.len() {
-            if current_write_idx == words.len() - 1 && self.bmstall {
-                nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?;
-            } else {
-                nb::block!(self.write_fifo(words[current_write_idx].into()))?;
-            }
-            current_write_idx += 1;
-            // Ignore received words.
-            if self.spi.status().read().rne().bit_is_set() {
-                self.clear_rx_fifo();
-            }
-        }
-        Ok(())
-    }
-
-    fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
-        self.transfer_preparation(write)?;
-        let mut current_read_idx = 0;
-        let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write);
-        while current_read_idx < read.len() || current_write_idx < write.len() {
-            if current_write_idx < write.len() {
-                if current_write_idx == write.len() - 1 && self.bmstall {
-                    nb::block!(
-                        self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK)
-                    )?;
-                } else {
-                    nb::block!(self.write_fifo(write[current_write_idx].into()))?;
-                }
-                current_write_idx += 1;
-            }
-            if current_read_idx < read.len() {
-                read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
-                    .try_into()
-                    .unwrap();
-                current_read_idx += 1;
-            }
-        }
-
-        Ok(())
-    }
-
-    fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
-        self.transfer_preparation(words)?;
-        let mut current_read_idx = 0;
-        let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
-
-        while current_read_idx < words.len() || current_write_idx < words.len() {
-            if current_write_idx < words.len() {
-                if current_write_idx == words.len() - 1 && self.bmstall {
-                    nb::block!(
-                        self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK)
-                    )?;
-                } else {
-                    nb::block!(self.write_fifo(words[current_write_idx].into()))?;
-                }
-                current_write_idx += 1;
-            }
-            if current_read_idx < words.len() && current_read_idx < current_write_idx {
-                words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
-                    .try_into()
-                    .unwrap();
-                current_read_idx += 1;
-            }
-        }
-        Ok(())
-    }
-
-    fn flush(&mut self) -> Result<(), Self::Error> {
-        self.flush_internal();
-        Ok(())
-    }
-}
-
-impl<
-        SpiI: Instance,
-        Sck: PinSck<SpiI>,
-        Miso: PinMiso<SpiI>,
-        Mosi: PinMosi<SpiI>,
-        Word: WordProvider,
-    > Spi<SpiI, (Sck, Miso, Mosi), Word>
-where
-    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
-{
-    /// Create a new SPI struct
-    ///
-    /// You can delete the pin type information by calling the
-    /// [`downgrade`](Self::downgrade) function
-    ///
-    /// ## Arguments
-    /// * `spi` - SPI bus to use
-    /// * `pins` - Pins to be used for SPI transactions. These pins are consumed
-    ///     to ensure the pins can not be used for other purposes anymore
-    /// * `spi_cfg` - Configuration specific to the SPI bus
-    /// * `transfer_cfg` - Optional initial transfer configuration which includes
-    ///     configuration which can change across individual SPI transfers like SPI mode
-    ///     or SPI clock. If only one device is connected, this configuration only needs
-    ///     to be done once.
-    /// * `syscfg` - Can be passed optionally to enable the peripheral clock
-    pub fn new(
-        syscfg: &mut pac::Sysconfig,
-        clocks: &crate::clock::Clocks,
-        spi: SpiI,
-        pins: (Sck, Miso, Mosi),
-        spi_cfg: SpiConfig,
-    ) -> Self {
-        crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
-        // This is done in the C HAL.
-        syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
-        let SpiConfig {
-            clk,
-            init_mode,
-            blockmode,
-            bmstall,
-            ms,
-            slave_output_disable,
-            loopback_mode,
-            master_delayer_capture,
-        } = spi_cfg;
-
-        let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
-        spi.ctrl0().write(|w| {
-            unsafe {
-                w.size().bits(Word::word_reg());
-                w.scrdv().bits(clk.scrdv);
-                // Clear clock phase and polarity. Will be set to correct value for each
-                // transfer
-                w.spo().bit(cpo_bit);
-                w.sph().bit(cph_bit)
-            }
-        });
-
-        spi.ctrl1().write(|w| {
-            w.lbm().bit(loopback_mode);
-            w.sod().bit(slave_output_disable);
-            w.ms().bit(ms);
-            w.mdlycap().bit(master_delayer_capture);
-            w.blockmode().bit(blockmode);
-            w.bmstall().bit(bmstall);
-            unsafe { w.ss().bits(0) }
-        });
-        spi.clkprescale()
-            .write(|w| unsafe { w.bits(clk.prescale_val as u32) });
-
-        spi.fifo_clr().write(|w| {
-            w.rxfifo().set_bit();
-            w.txfifo().set_bit()
-        });
-        // Enable the peripheral as the last step as recommended in the
-        // programmers guide
-        spi.ctrl1().modify(|_, w| w.enable().set_bit());
-        Spi {
-            inner: SpiBase {
-                spi,
-                cfg: spi_cfg,
-                apb1_clk: clocks.apb1(),
-                fill_word: Default::default(),
-                bmstall,
-                blockmode,
-                word: PhantomData,
-            },
-            pins,
-        }
-    }
-
-    delegate::delegate! {
-        to self.inner {
-            #[inline]
-            pub fn cfg_clock(&mut self, cfg: SpiClkConfig);
-
-            #[inline]
-            pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
-
-            #[inline]
-            pub fn spi(&self) -> &SpiI;
-
-            #[inline]
-            pub fn cfg_mode(&mut self, mode: Mode);
-
-            #[inline]
-            pub fn perid(&self) -> u32;
-
-            pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
-                &mut self, transfer_cfg: &TransferConfigWithHwcs<HwCs>
-            );
-        }
-    }
-
-    #[inline]
-    pub fn set_fill_word(&mut self, fill_word: Word) {
-        self.inner.fill_word = fill_word;
-    }
-
-    #[inline]
-    pub fn fill_word(&self) -> Word {
-        self.inner.fill_word
-    }
-
-    /// Releases the SPI peripheral and associated pins
-    pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) {
-        (self.inner.spi, self.pins, self.inner.cfg)
-    }
-
-    pub fn downgrade(self) -> SpiBase<SpiI, Word> {
-        self.inner
-    }
-}
-
-impl<
-        SpiI: Instance,
-        Sck: PinSck<SpiI>,
-        Miso: PinMiso<SpiI>,
-        Mosi: PinMosi<SpiI>,
-        Word: WordProvider,
-    > SpiLowLevel for Spi<SpiI, (Sck, Miso, Mosi), Word>
-where
-    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
-{
-    delegate::delegate! {
-        to self.inner {
-            fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>;
-            fn write_fifo_unchecked(&self, data: u32);
-            fn read_fifo(&self) -> nb::Result<u32, Infallible>;
-            fn read_fifo_unchecked(&self) -> u32;
-        }
-    }
-}
-
-impl<
-        SpiI: Instance,
-        Word: WordProvider,
-        Sck: PinSck<SpiI>,
-        Miso: PinMiso<SpiI>,
-        Mosi: PinMosi<SpiI>,
-    > embedded_hal::spi::ErrorType for Spi<SpiI, (Sck, Miso, Mosi), Word>
-{
-    type Error = Infallible;
-}
-
-impl<
-        SpiI: Instance,
-        Word: WordProvider,
-        Sck: PinSck<SpiI>,
-        Miso: PinMiso<SpiI>,
-        Mosi: PinMosi<SpiI>,
-    > embedded_hal::spi::SpiBus<Word> for Spi<SpiI, (Sck, Miso, Mosi), Word>
-where
-    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
-{
-    delegate::delegate! {
-        to self.inner {
-            fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
-            fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>;
-            fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>;
-            fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
-            fn flush(&mut self) -> Result<(), Self::Error>;
-        }
-    }
-}
-
-/// Changing the word size also requires a type conversion
-impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
-    From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16>
-{
-    fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> Self {
-        old_spi
-            .inner
-            .spi
-            .ctrl0()
-            .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) });
-        Spi {
-            inner: SpiBase {
-                spi: old_spi.inner.spi,
-                cfg: old_spi.inner.cfg,
-                blockmode: old_spi.inner.blockmode,
-                bmstall: old_spi.inner.bmstall,
-                fill_word: Default::default(),
-                apb1_clk: old_spi.inner.apb1_clk,
-                word: PhantomData,
-            },
-            pins: old_spi.pins,
-        }
-    }
-}
-
-/// Changing the word size also requires a type conversion
-impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
-    From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
-{
-    fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> Self {
-        old_spi
-            .inner
-            .spi
-            .ctrl0()
-            .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) });
-        Spi {
-            inner: SpiBase {
-                spi: old_spi.inner.spi,
-                cfg: old_spi.inner.cfg,
-                blockmode: old_spi.inner.blockmode,
-                bmstall: old_spi.inner.bmstall,
-                apb1_clk: old_spi.inner.apb1_clk,
-                fill_word: Default::default(),
-                word: PhantomData,
-            },
-            pins: old_spi.pins,
-        }
-    }
-}
+//! - [NVM library][crate::nvm]
+pub use vorago_shared_periphs::spi::*;
diff --git a/va416xx-hal/src/spi_tmp.rs b/va416xx-hal/src/spi_tmp.rs
new file mode 100644
index 0000000..2681b83
--- /dev/null
+++ b/va416xx-hal/src/spi_tmp.rs
@@ -0,0 +1,1251 @@
+//! API for the SPI peripheral
+//!
+//! The main abstraction provided by this module are the [Spi] and the [SpiBase] structure.
+//! These provide the [embedded_hal::spi] traits, but also offer a low level interface
+//! via the [SpiLowLevel] trait.
+//!
+//! ## Examples
+//!
+//! - [Blocking SPI example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/spi.rs)
+use core::{convert::Infallible, marker::PhantomData, ops::Deref};
+
+use embedded_hal::spi::{Mode, MODE_0};
+
+use crate::{
+    clock::{Clocks, PeripheralSelect, SyscfgExt},
+    gpio::{
+        AltFunc1, AltFunc2, AltFunc3, Pin, PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PB0,
+        PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1, PC10, PC11, PC7, PC8, PC9, PE12,
+        PE13, PE14, PE15, PE5, PE6, PE7, PE8, PE9, PF0, PF1, PG2, PG3, PG4,
+    },
+    pac,
+    time::Hertz,
+    typelevel::{NoneT, Sealed},
+};
+
+#[cfg(not(feature = "va41628"))]
+use crate::gpio::{PB10, PB11, PB5, PB6, PB7, PB8, PB9, PE10, PE11, PF2, PF3, PF4, PF5, PF6, PF7};
+
+//==================================================================================================
+// Defintions
+//==================================================================================================
+
+// FIFO has a depth of 16.
+const FILL_DEPTH: usize = 12;
+
+pub const DEFAULT_CLK_DIV: u16 = 2;
+
+pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
+pub const BMSKIPDATA_MASK: u32 = 1 << 30;
+
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum HwChipSelectId {
+    Id0 = 0,
+    Id1 = 1,
+    Id2 = 2,
+    Id3 = 3,
+    Id4 = 4,
+    Id5 = 5,
+    Id6 = 6,
+    Id7 = 7,
+    Invalid = 0xff,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum SpiId {
+    Spi0,
+    Spi1,
+    Spi2,
+    Spi3,
+    Invalid,
+}
+
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum WordSize {
+    OneBit = 0x00,
+    FourBits = 0x03,
+    EightBits = 0x07,
+    SixteenBits = 0x0f,
+}
+
+pub type SpiRegBlock = pac::spi0::RegisterBlock;
+
+/// Common trait implemented by all PAC peripheral access structures. The register block
+/// format is the same for all SPI blocks.
+pub trait Instance: Deref<Target = SpiRegBlock> {
+    const IDX: u8;
+    const PERIPH_SEL: PeripheralSelect;
+
+    fn ptr() -> *const SpiRegBlock;
+}
+
+impl Instance for pac::Spi0 {
+    const IDX: u8 = 0;
+    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
+
+    #[inline(always)]
+    fn ptr() -> *const SpiRegBlock {
+        Self::ptr()
+    }
+}
+
+impl Instance for pac::Spi1 {
+    const IDX: u8 = 1;
+    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
+
+    #[inline(always)]
+    fn ptr() -> *const SpiRegBlock {
+        Self::ptr()
+    }
+}
+
+impl Instance for pac::Spi2 {
+    const IDX: u8 = 2;
+    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
+
+    #[inline(always)]
+    fn ptr() -> *const SpiRegBlock {
+        Self::ptr()
+    }
+}
+
+impl Instance for pac::Spi3 {
+    const IDX: u8 = 3;
+    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi3;
+
+    #[inline(always)]
+    fn ptr() -> *const SpiRegBlock {
+        Self::ptr()
+    }
+}
+
+//==================================================================================================
+// Pin type definitions
+//==================================================================================================
+
+pub trait PinSck<SPI>: Sealed {}
+pub trait PinMosi<SPI>: Sealed {}
+pub trait PinMiso<SPI>: Sealed {}
+
+pub trait HwCsProvider: Sealed {
+    const CS_ID: HwChipSelectId;
+    const SPI_ID: SpiId;
+}
+
+pub trait OptionalHwCs<Spi>: HwCsProvider + Sealed {}
+
+macro_rules! hw_cs_pins {
+    ($SPIx:path, $portId: path:
+        $(
+            ($PXx:ident, $AFx:ident, $HwCsIdent:path, $typedef:ident $(, $meta: meta)?),
+        )+
+    ) => {
+        $(
+            $(#[$meta])?
+            impl HwCsProvider for Pin<$PXx, $AFx> {
+                const CS_ID: HwChipSelectId = $HwCsIdent;
+                const SPI_ID: SpiId = $portId;
+            }
+
+            $(#[$meta])?
+            impl OptionalHwCs<$SPIx> for Pin<$PXx, $AFx> {}
+
+            $(#[$meta])?
+            pub type $typedef = Pin<$PXx, $AFx>;
+        )+
+    };
+}
+
+impl HwCsProvider for NoneT {
+    const CS_ID: HwChipSelectId = HwChipSelectId::Invalid;
+    const SPI_ID: SpiId = SpiId::Invalid;
+}
+
+impl OptionalHwCs<pac::Spi0> for NoneT {}
+impl OptionalHwCs<pac::Spi1> for NoneT {}
+impl OptionalHwCs<pac::Spi2> for NoneT {}
+impl OptionalHwCs<pac::Spi3> for NoneT {}
+
+pub struct RomSpiSck;
+pub struct RomSpiMiso;
+pub struct RomSpiMosi;
+
+impl Sealed for RomSpiSck {}
+impl Sealed for RomSpiMosi {}
+impl Sealed for RomSpiMiso {}
+
+// SPI 0
+
+impl PinSck<pac::Spi0> for Pin<PB15, AltFunc1> {}
+impl PinMosi<pac::Spi0> for Pin<PC1, AltFunc1> {}
+impl PinMiso<pac::Spi0> for Pin<PC0, AltFunc1> {}
+
+// SPI 1
+#[cfg(not(feature = "va41628"))]
+impl PinSck<pac::Spi1> for Pin<PB8, AltFunc3> {}
+#[cfg(not(feature = "va41628"))]
+impl PinMosi<pac::Spi1> for Pin<PB10, AltFunc3> {}
+#[cfg(not(feature = "va41628"))]
+impl PinMiso<pac::Spi1> for Pin<PB9, AltFunc3> {}
+
+impl PinSck<pac::Spi1> for Pin<PC9, AltFunc2> {}
+impl PinMosi<pac::Spi1> for Pin<PC11, AltFunc2> {}
+impl PinMiso<pac::Spi1> for Pin<PC10, AltFunc2> {}
+
+impl PinSck<pac::Spi1> for Pin<PG3, AltFunc2> {}
+impl PinMiso<pac::Spi1> for Pin<PG4, AltFunc2> {}
+
+impl PinSck<pac::Spi1> for Pin<PE13, AltFunc2> {}
+impl PinMosi<pac::Spi1> for Pin<PE15, AltFunc2> {}
+impl PinMiso<pac::Spi1> for Pin<PE14, AltFunc2> {}
+
+#[cfg(not(feature = "va41628"))]
+impl PinSck<pac::Spi1> for Pin<PF3, AltFunc1> {}
+#[cfg(not(feature = "va41628"))]
+impl PinMosi<pac::Spi1> for Pin<PF5, AltFunc1> {}
+#[cfg(not(feature = "va41628"))]
+impl PinMiso<pac::Spi1> for Pin<PF4, AltFunc1> {}
+
+// SPI 2
+
+impl PinSck<pac::Spi2> for Pin<PA5, AltFunc2> {}
+impl PinMosi<pac::Spi2> for Pin<PA7, AltFunc2> {}
+impl PinMiso<pac::Spi2> for Pin<PA6, AltFunc2> {}
+
+#[cfg(not(feature = "va41628"))]
+impl PinSck<pac::Spi2> for Pin<PF5, AltFunc2> {}
+#[cfg(not(feature = "va41628"))]
+impl PinMosi<pac::Spi2> for Pin<PF7, AltFunc2> {}
+#[cfg(not(feature = "va41628"))]
+impl PinMiso<pac::Spi2> for Pin<PF6, AltFunc2> {}
+
+// SPI3 is shared with the ROM SPI pins and has its own dedicated pins.
+//
+impl PinSck<pac::Spi3> for RomSpiSck {}
+impl PinMosi<pac::Spi3> for RomSpiMosi {}
+impl PinMiso<pac::Spi3> for RomSpiMiso {}
+
+// SPI 0 HW CS pins
+
+hw_cs_pins!(
+    pac::Spi0, SpiId::Spi0:
+    (PB14, AltFunc1, HwChipSelectId::Id0, HwCs0Spi0),
+    (PB13, AltFunc1, HwChipSelectId::Id1, HwCs1Spi0),
+    (PB12, AltFunc1, HwChipSelectId::Id2, HwCs2Spi0),
+    (PB11, AltFunc1, HwChipSelectId::Id3, HwCs3Spi0, cfg(not(feature="va41628"))),
+);
+
+hw_cs_pins!(
+    pac::Spi1, SpiId::Spi1:
+    (PB7, AltFunc3, HwChipSelectId::Id0, HwCs0Spi1Pb, cfg(not(feature="va41628"))),
+    (PB6, AltFunc3, HwChipSelectId::Id1, HwCs1Spi1Pb, cfg(not(feature="va41628"))),
+    (PB5, AltFunc3, HwChipSelectId::Id2, HwCs2Spi1Pb, cfg(not(feature="va41628"))),
+    (PB4, AltFunc3, HwChipSelectId::Id3, HwCs3Spi1Pb),
+    (PB3, AltFunc3, HwChipSelectId::Id4, HwCs4Spi1Pb),
+    (PB2, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pb),
+    (PB1, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pb),
+    (PB0, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pb),
+    (PC8, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pc),
+    (PC7, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pc),
+    (PE12, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pe),
+    (PE11, AltFunc2, HwChipSelectId::Id1, HwCs1Spi1Pe, cfg(not(feature="va41628"))),
+    (PE10, AltFunc2, HwChipSelectId::Id2, HwCs2Spi1Pe, cfg(not(feature="va41628"))),
+    (PE9, AltFunc2, HwChipSelectId::Id3, HwCs3Spi1Pe),
+    (PE8, AltFunc2, HwChipSelectId::Id4, HwCs4Spi1Pe),
+    (PE7, AltFunc3, HwChipSelectId::Id5, HwCs5Spi1Pe),
+    (PE6, AltFunc3, HwChipSelectId::Id6, HwCs6Spi1Pe),
+    (PE5, AltFunc3, HwChipSelectId::Id7, HwCs7Spi1Pe),
+    (PF2, AltFunc1, HwChipSelectId::Id0, HwCs0Spi1Pf, cfg(not(feature="va41628"))),
+    (PG2, AltFunc2, HwChipSelectId::Id0, HwCs0Spi1Pg),
+);
+
+hw_cs_pins!(
+    pac::Spi2, SpiId::Spi2:
+    (PA4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pa),
+    (PA3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pa),
+    (PA2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pa),
+    (PA1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pa),
+    (PA0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pa),
+    (PA8, AltFunc2, HwChipSelectId::Id6, HwCs6Spi2Pa),
+    (PA9, AltFunc2, HwChipSelectId::Id5, HwCs5Spi2Pa),
+    (PF0, AltFunc2, HwChipSelectId::Id4, HwCs4Spi2Pf),
+    (PF1, AltFunc2, HwChipSelectId::Id3, HwCs3Spi2Pf),
+    (PF2, AltFunc2, HwChipSelectId::Id2, HwCs2Spi2Pf, cfg(not(feature="va41628"))),
+    (PF3, AltFunc2, HwChipSelectId::Id1, HwCs1Spi2Pf, cfg(not(feature="va41628"))),
+    (PF4, AltFunc2, HwChipSelectId::Id0, HwCs0Spi2Pf, cfg(not(feature="va41628"))),
+);
+
+//==================================================================================================
+// Config
+//==================================================================================================
+
+pub trait TransferConfigProvider {
+    fn sod(&mut self, sod: bool);
+    fn blockmode(&mut self, blockmode: bool);
+    fn mode(&mut self, mode: Mode);
+    fn clk_cfg(&mut self, clk_cfg: SpiClkConfig);
+    fn hw_cs_id(&self) -> u8;
+}
+
+/// This struct contains all configuration parameter which are transfer specific
+/// and might change for transfers to different SPI slaves
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct TransferConfigWithHwcs<HwCs> {
+    pub hw_cs: Option<HwCs>,
+    pub cfg: TransferConfig,
+}
+
+/// Type erased variant of the transfer configuration. This is required to avoid generics in
+/// the SPI constructor.
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct TransferConfig {
+    pub clk_cfg: Option<SpiClkConfig>,
+    pub mode: Option<Mode>,
+    pub sod: bool,
+    /// If this is enabled, all data in the FIFO is transmitted in a single frame unless
+    /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
+    /// duration of multiple data words
+    pub blockmode: bool,
+    /// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
+    /// is set on a written word.
+    pub bmstall: bool,
+    pub hw_cs: HwChipSelectId,
+}
+
+impl TransferConfigWithHwcs<NoneT> {
+    pub fn new_no_hw_cs(
+        clk_cfg: Option<SpiClkConfig>,
+        mode: Option<Mode>,
+        blockmode: bool,
+        bmstall: bool,
+        sod: bool,
+    ) -> Self {
+        TransferConfigWithHwcs {
+            hw_cs: None,
+            cfg: TransferConfig {
+                clk_cfg,
+                mode,
+                sod,
+                blockmode,
+                bmstall,
+                hw_cs: HwChipSelectId::Invalid,
+            },
+        }
+    }
+}
+
+impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> {
+    pub fn new(
+        clk_cfg: Option<SpiClkConfig>,
+        mode: Option<Mode>,
+        hw_cs: Option<HwCs>,
+        blockmode: bool,
+        bmstall: bool,
+        sod: bool,
+    ) -> Self {
+        TransferConfigWithHwcs {
+            hw_cs,
+            cfg: TransferConfig {
+                clk_cfg,
+                mode,
+                sod,
+                blockmode,
+                bmstall,
+                hw_cs: HwCs::CS_ID,
+            },
+        }
+    }
+
+    pub fn downgrade(self) -> TransferConfig {
+        self.cfg
+    }
+}
+
+impl<HwCs: HwCsProvider> TransferConfigProvider for TransferConfigWithHwcs<HwCs> {
+    /// Slave Output Disable
+    fn sod(&mut self, sod: bool) {
+        self.cfg.sod = sod;
+    }
+
+    fn blockmode(&mut self, blockmode: bool) {
+        self.cfg.blockmode = blockmode;
+    }
+
+    fn mode(&mut self, mode: Mode) {
+        self.cfg.mode = Some(mode);
+    }
+
+    fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) {
+        self.cfg.clk_cfg = Some(clk_cfg);
+    }
+
+    fn hw_cs_id(&self) -> u8 {
+        HwCs::CS_ID as u8
+    }
+}
+
+/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SpiConfig {
+    clk: SpiClkConfig,
+    // SPI mode configuration
+    pub init_mode: Mode,
+    /// If this is enabled, all data in the FIFO is transmitted in a single frame unless
+    /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
+    /// duration of multiple data words. Defaults to true.
+    pub blockmode: bool,
+    /// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty.
+    /// Currently enabled by default.
+    pub bmstall: bool,
+    /// By default, configure SPI for master mode (ms == false)
+    ms: bool,
+    /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
+    pub slave_output_disable: bool,
+    /// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
+    pub loopback_mode: bool,
+    /// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
+    pub master_delayer_capture: bool,
+}
+
+impl Default for SpiConfig {
+    fn default() -> Self {
+        Self {
+            init_mode: MODE_0,
+            blockmode: true,
+            bmstall: true,
+            // Default value is definitely valid.
+            clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
+            ms: Default::default(),
+            slave_output_disable: Default::default(),
+            loopback_mode: Default::default(),
+            master_delayer_capture: Default::default(),
+        }
+    }
+}
+
+impl SpiConfig {
+    pub fn loopback(mut self, enable: bool) -> Self {
+        self.loopback_mode = enable;
+        self
+    }
+
+    pub fn blockmode(mut self, enable: bool) -> Self {
+        self.blockmode = enable;
+        self
+    }
+
+    pub fn bmstall(mut self, enable: bool) -> Self {
+        self.bmstall = enable;
+        self
+    }
+
+    pub fn mode(mut self, mode: Mode) -> Self {
+        self.init_mode = mode;
+        self
+    }
+
+    pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
+        self.clk = clk_cfg;
+        self
+    }
+
+    pub fn master_mode(mut self, master: bool) -> Self {
+        self.ms = !master;
+        self
+    }
+
+    pub fn slave_output_disable(mut self, sod: bool) -> Self {
+        self.slave_output_disable = sod;
+        self
+    }
+}
+
+//==================================================================================================
+// Word Size
+//==================================================================================================
+
+/// Configuration trait for the Word Size used by the SPI peripheral
+pub trait WordProvider: Copy + Default + Into<u32> + TryFrom<u32> + 'static {
+    const MASK: u32;
+    fn word_reg() -> u8;
+}
+
+impl WordProvider for u8 {
+    const MASK: u32 = 0xff;
+    fn word_reg() -> u8 {
+        0x07
+    }
+}
+
+impl WordProvider for u16 {
+    const MASK: u32 = 0xffff;
+    fn word_reg() -> u8 {
+        0x0f
+    }
+}
+
+//==================================================================================================
+// Spi
+//==================================================================================================
+
+/// Low level access trait for the SPI peripheral.
+pub trait SpiLowLevel {
+    /// Low level function to write a word to the SPI FIFO but also checks whether
+    /// there is actually data in the FIFO.
+    ///
+    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
+    fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>;
+
+    /// Low level function to write a word to the SPI FIFO without checking whether
+    /// there FIFO is full.
+    ///
+    /// This does not necesarily mean there is a space in the FIFO available.
+    /// Use [Self::write_fifo] function to write a word into the FIFO reliably.
+    fn write_fifo_unchecked(&self, data: u32);
+
+    /// Low level function to read a word from the SPI FIFO. Must be preceeded by a
+    /// [Self::write_fifo] call.
+    ///
+    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
+    fn read_fifo(&self) -> nb::Result<u32, Infallible>;
+
+    /// Low level function to read a word from from the SPI FIFO.
+    ///
+    /// This does not necesarily mean there is a word in the FIFO available.
+    /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
+    /// API.
+    /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
+    fn read_fifo_unchecked(&self) -> u32;
+}
+
+pub struct SpiBase<SpiInstance, Word = u8> {
+    spi: SpiInstance,
+    cfg: SpiConfig,
+    apb1_clk: Hertz,
+    /// Fill word for read-only SPI transactions.
+    pub fill_word: Word,
+    blockmode: bool,
+    bmstall: bool,
+    word: PhantomData<Word>,
+}
+
+pub struct Spi<SpiInstance, Pins, Word = u8> {
+    inner: SpiBase<SpiInstance, Word>,
+    pins: Pins,
+}
+
+#[inline(always)]
+pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
+    match mode {
+        embedded_hal::spi::MODE_0 => (false, false),
+        embedded_hal::spi::MODE_1 => (false, true),
+        embedded_hal::spi::MODE_2 => (true, false),
+        embedded_hal::spi::MODE_3 => (true, true),
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct SpiClkConfig {
+    prescale_val: u16,
+    scrdv: u8,
+}
+
+impl SpiClkConfig {
+    pub fn prescale_val(&self) -> u16 {
+        self.prescale_val
+    }
+    pub fn scrdv(&self) -> u8 {
+        self.scrdv
+    }
+}
+
+impl SpiClkConfig {
+    pub fn new(prescale_val: u16, scrdv: u8) -> Self {
+        Self {
+            prescale_val,
+            scrdv,
+        }
+    }
+
+    pub fn from_div(div: u16) -> Result<Self, SpiClkConfigError> {
+        spi_clk_config_from_div(div)
+    }
+
+    pub fn from_clk(spi_clk: Hertz, clocks: &Clocks) -> Option<Self> {
+        clk_div_for_target_clock(spi_clk, clocks).map(|div| spi_clk_config_from_div(div).unwrap())
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum SpiClkConfigError {
+    #[error("division by zero")]
+    DivIsZero,
+    #[error("divide value is not even")]
+    DivideValueNotEven,
+    #[error("scrdv value is too large")]
+    ScrdvValueTooLarge,
+}
+
+#[inline]
+pub fn spi_clk_config_from_div(mut div: u16) -> Result<SpiClkConfig, SpiClkConfigError> {
+    if div == 0 {
+        return Err(SpiClkConfigError::DivIsZero);
+    }
+    if div % 2 != 0 {
+        return Err(SpiClkConfigError::DivideValueNotEven);
+    }
+    let mut prescale_val = 0;
+
+    // find largest (even) prescale value that divides into div
+    for i in (2..=0xfe).rev().step_by(2) {
+        if div % i == 0 {
+            prescale_val = i;
+            break;
+        }
+    }
+
+    if prescale_val == 0 {
+        return Err(SpiClkConfigError::DivideValueNotEven);
+    }
+
+    div /= prescale_val;
+    if div > u8::MAX as u16 + 1 {
+        return Err(SpiClkConfigError::ScrdvValueTooLarge);
+    }
+    Ok(SpiClkConfig {
+        prescale_val,
+        scrdv: (div - 1) as u8,
+    })
+}
+
+#[inline]
+pub fn clk_div_for_target_clock(spi_clk: impl Into<Hertz>, clocks: &Clocks) -> Option<u16> {
+    let spi_clk = spi_clk.into();
+    if spi_clk > clocks.apb1() {
+        return None;
+    }
+
+    // Step 1: Calculate raw divider.
+    let raw_div = clocks.apb1().raw() / spi_clk.raw();
+    let remainder = clocks.apb1().raw() % spi_clk.raw();
+
+    // Step 2: Round up if necessary.
+    let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
+        raw_div + 1
+    } else {
+        raw_div
+    };
+
+    if rounded_div % 2 != 0 {
+        // Take slower clock conservatively.
+        rounded_div += 1;
+    }
+    if rounded_div > u16::MAX as u32 {
+        return None;
+    }
+    Some(rounded_div as u16)
+}
+
+impl<SpiInstance: Instance, Word: WordProvider> SpiBase<SpiInstance, Word>
+where
+    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
+{
+    #[inline]
+    pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
+        self.spi
+            .ctrl0()
+            .modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) });
+        self.spi
+            .clkprescale()
+            .write(|w| unsafe { w.bits(cfg.prescale_val as u32) });
+    }
+
+    #[inline]
+    pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
+        let val = spi_clk_config_from_div(div)?;
+        self.cfg_clock(val);
+        Ok(())
+    }
+
+    #[inline]
+    pub fn cfg_mode(&mut self, mode: Mode) {
+        let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
+        self.spi.ctrl0().modify(|_, w| {
+            w.spo().bit(cpo_bit);
+            w.sph().bit(cph_bit)
+        });
+    }
+
+    #[inline]
+    pub fn spi(&self) -> &SpiInstance {
+        &self.spi
+    }
+
+    #[inline]
+    pub fn clear_tx_fifo(&self) {
+        self.spi.fifo_clr().write(|w| w.txfifo().set_bit());
+    }
+
+    #[inline]
+    pub fn clear_rx_fifo(&self) {
+        self.spi.fifo_clr().write(|w| w.rxfifo().set_bit());
+    }
+
+    #[inline]
+    pub fn perid(&self) -> u32 {
+        self.spi.perid().read().bits()
+    }
+
+    #[inline]
+    pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) {
+        if hw_cs == HwChipSelectId::Invalid {
+            return;
+        }
+        self.spi.ctrl1().modify(|_, w| {
+            w.sod().clear_bit();
+            unsafe {
+                w.ss().bits(hw_cs as u8);
+            }
+            w
+        });
+    }
+
+    #[inline]
+    pub fn cfg_hw_cs_with_pin<HwCs: OptionalHwCs<SpiInstance>>(&mut self, _: &HwCs) {
+        self.cfg_hw_cs(HwCs::CS_ID);
+    }
+
+    #[inline]
+    pub fn cfg_hw_cs_disable(&mut self) {
+        self.spi.ctrl1().modify(|_, w| {
+            w.sod().set_bit();
+            w
+        });
+    }
+
+    pub fn cfg_transfer<HwCs: OptionalHwCs<SpiInstance>>(
+        &mut self,
+        transfer_cfg: &TransferConfigWithHwcs<HwCs>,
+    ) {
+        if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg {
+            self.cfg_clock(trans_clk_div);
+        }
+        if let Some(mode) = transfer_cfg.cfg.mode {
+            self.cfg_mode(mode);
+        }
+        self.blockmode = transfer_cfg.cfg.blockmode;
+        self.spi.ctrl1().modify(|_, w| {
+            if transfer_cfg.cfg.sod {
+                w.sod().set_bit();
+            } else if transfer_cfg.hw_cs.is_some() {
+                w.sod().clear_bit();
+                unsafe {
+                    w.ss().bits(HwCs::CS_ID as u8);
+                }
+            } else {
+                w.sod().clear_bit();
+            }
+            w.blockmode().bit(transfer_cfg.cfg.blockmode);
+            w.bmstall().bit(transfer_cfg.cfg.bmstall)
+        });
+    }
+
+    /// Low level function to write a word to the SPI FIFO but also checks whether
+    /// there is actually data in the FIFO.
+    ///
+    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
+    #[inline(always)]
+    pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
+        if self.spi.status().read().tnf().bit_is_clear() {
+            return Err(nb::Error::WouldBlock);
+        }
+        self.write_fifo_unchecked(data);
+        Ok(())
+    }
+
+    /// Low level function to write a word to the SPI FIFO without checking whether
+    /// there FIFO is full.
+    ///
+    /// This does not necesarily mean there is a space in the FIFO available.
+    /// Use [Self::write_fifo] function to write a word into the FIFO reliably.
+    #[inline(always)]
+    pub fn write_fifo_unchecked(&self, data: u32) {
+        self.spi.data().write(|w| unsafe { w.bits(data) });
+    }
+
+    /// Low level function to read a word from the SPI FIFO. Must be preceeded by a
+    /// [Self::write_fifo] call.
+    ///
+    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
+    #[inline(always)]
+    pub fn read_fifo(&self) -> nb::Result<u32, Infallible> {
+        if self.spi.status().read().rne().bit_is_clear() {
+            return Err(nb::Error::WouldBlock);
+        }
+        Ok(self.read_fifo_unchecked())
+    }
+
+    /// Low level function to read a word from from the SPI FIFO.
+    ///
+    /// This does not necesarily mean there is a word in the FIFO available.
+    /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
+    /// API.
+    /// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
+    #[inline(always)]
+    pub fn read_fifo_unchecked(&self) -> u32 {
+        self.spi.data().read().bits()
+    }
+
+    fn flush_internal(&self) {
+        let mut status_reg = self.spi.status().read();
+        while status_reg.tfe().bit_is_clear()
+            || status_reg.rne().bit_is_set()
+            || status_reg.busy().bit_is_set()
+        {
+            if status_reg.rne().bit_is_set() {
+                self.read_fifo_unchecked();
+            }
+            status_reg = self.spi.status().read();
+        }
+    }
+
+    fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> {
+        if words.is_empty() {
+            return Ok(());
+        }
+        self.flush_internal();
+        Ok(())
+    }
+
+    // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
+    // initialization. Returns the amount of written bytes.
+    fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize {
+        if self.blockmode {
+            self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit());
+        }
+        // Fill the first half of the write FIFO
+        let mut current_write_idx = 0;
+        let smaller_idx = core::cmp::min(FILL_DEPTH, words.len());
+        for _ in 0..smaller_idx {
+            if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
+                self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK);
+            } else {
+                self.write_fifo_unchecked(words[current_write_idx].into());
+            }
+            current_write_idx += 1;
+        }
+        if self.blockmode {
+            self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit());
+        }
+        current_write_idx
+    }
+
+    // The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
+    // initialization.
+    fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize {
+        if self.blockmode {
+            self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit());
+        }
+        // Fill the first half of the write FIFO
+        let mut current_write_idx = 0;
+        let smaller_idx = core::cmp::min(FILL_DEPTH, send_len);
+        for _ in 0..smaller_idx {
+            if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
+                self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK);
+            } else {
+                self.write_fifo_unchecked(self.fill_word.into());
+            }
+            current_write_idx += 1;
+        }
+        if self.blockmode {
+            self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit());
+        }
+        current_write_idx
+    }
+}
+
+impl<SpiInstance: Instance, Word: WordProvider> SpiLowLevel for SpiBase<SpiInstance, Word>
+where
+    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
+{
+    #[inline(always)]
+    fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
+        if self.spi.status().read().tnf().bit_is_clear() {
+            return Err(nb::Error::WouldBlock);
+        }
+        self.write_fifo_unchecked(data);
+        Ok(())
+    }
+
+    #[inline(always)]
+    fn write_fifo_unchecked(&self, data: u32) {
+        self.spi.data().write(|w| unsafe { w.bits(data) });
+    }
+
+    #[inline(always)]
+    fn read_fifo(&self) -> nb::Result<u32, Infallible> {
+        if self.spi.status().read().rne().bit_is_clear() {
+            return Err(nb::Error::WouldBlock);
+        }
+        Ok(self.read_fifo_unchecked())
+    }
+
+    #[inline(always)]
+    fn read_fifo_unchecked(&self) -> u32 {
+        self.spi.data().read().bits()
+    }
+}
+
+impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::ErrorType for SpiBase<SpiI, Word> {
+    type Error = Infallible;
+}
+
+impl<SpiI: Instance, Word: WordProvider> embedded_hal::spi::SpiBus<Word> for SpiBase<SpiI, Word>
+where
+    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
+{
+    fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
+        self.transfer_preparation(words)?;
+        let mut current_read_idx = 0;
+        let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len());
+        loop {
+            if current_read_idx < words.len() {
+                words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
+                    .try_into()
+                    .unwrap();
+                current_read_idx += 1;
+            }
+            if current_write_idx < words.len() {
+                if current_write_idx == words.len() - 1 && self.bmstall {
+                    nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?;
+                } else {
+                    nb::block!(self.write_fifo(self.fill_word.into()))?;
+                }
+                current_write_idx += 1;
+            }
+            if current_read_idx >= words.len() && current_write_idx >= words.len() {
+                break;
+            }
+        }
+        Ok(())
+    }
+
+    fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
+        self.transfer_preparation(words)?;
+        let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
+        while current_write_idx < words.len() {
+            if current_write_idx == words.len() - 1 && self.bmstall {
+                nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?;
+            } else {
+                nb::block!(self.write_fifo(words[current_write_idx].into()))?;
+            }
+            current_write_idx += 1;
+            // Ignore received words.
+            if self.spi.status().read().rne().bit_is_set() {
+                self.clear_rx_fifo();
+            }
+        }
+        Ok(())
+    }
+
+    fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
+        self.transfer_preparation(write)?;
+        let mut current_read_idx = 0;
+        let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write);
+        while current_read_idx < read.len() || current_write_idx < write.len() {
+            if current_write_idx < write.len() {
+                if current_write_idx == write.len() - 1 && self.bmstall {
+                    nb::block!(
+                        self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK)
+                    )?;
+                } else {
+                    nb::block!(self.write_fifo(write[current_write_idx].into()))?;
+                }
+                current_write_idx += 1;
+            }
+            if current_read_idx < read.len() {
+                read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
+                    .try_into()
+                    .unwrap();
+                current_read_idx += 1;
+            }
+        }
+
+        Ok(())
+    }
+
+    fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
+        self.transfer_preparation(words)?;
+        let mut current_read_idx = 0;
+        let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
+
+        while current_read_idx < words.len() || current_write_idx < words.len() {
+            if current_write_idx < words.len() {
+                if current_write_idx == words.len() - 1 && self.bmstall {
+                    nb::block!(
+                        self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK)
+                    )?;
+                } else {
+                    nb::block!(self.write_fifo(words[current_write_idx].into()))?;
+                }
+                current_write_idx += 1;
+            }
+            if current_read_idx < words.len() && current_read_idx < current_write_idx {
+                words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
+                    .try_into()
+                    .unwrap();
+                current_read_idx += 1;
+            }
+        }
+        Ok(())
+    }
+
+    fn flush(&mut self) -> Result<(), Self::Error> {
+        self.flush_internal();
+        Ok(())
+    }
+}
+
+impl<
+        SpiI: Instance,
+        Sck: PinSck<SpiI>,
+        Miso: PinMiso<SpiI>,
+        Mosi: PinMosi<SpiI>,
+        Word: WordProvider,
+    > Spi<SpiI, (Sck, Miso, Mosi), Word>
+where
+    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
+{
+    /// Create a new SPI struct
+    ///
+    /// You can delete the pin type information by calling the
+    /// [`downgrade`](Self::downgrade) function
+    ///
+    /// ## Arguments
+    /// * `spi` - SPI bus to use
+    /// * `pins` - Pins to be used for SPI transactions. These pins are consumed
+    ///     to ensure the pins can not be used for other purposes anymore
+    /// * `spi_cfg` - Configuration specific to the SPI bus
+    /// * `transfer_cfg` - Optional initial transfer configuration which includes
+    ///     configuration which can change across individual SPI transfers like SPI mode
+    ///     or SPI clock. If only one device is connected, this configuration only needs
+    ///     to be done once.
+    /// * `syscfg` - Can be passed optionally to enable the peripheral clock
+    pub fn new(
+        syscfg: &mut pac::Sysconfig,
+        clocks: &crate::clock::Clocks,
+        spi: SpiI,
+        pins: (Sck, Miso, Mosi),
+        spi_cfg: SpiConfig,
+    ) -> Self {
+        crate::clock::enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL);
+        // This is done in the C HAL.
+        syscfg.assert_periph_reset_for_two_cycles(SpiI::PERIPH_SEL);
+        let SpiConfig {
+            clk,
+            init_mode,
+            blockmode,
+            bmstall,
+            ms,
+            slave_output_disable,
+            loopback_mode,
+            master_delayer_capture,
+        } = spi_cfg;
+
+        let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode);
+        spi.ctrl0().write(|w| {
+            unsafe {
+                w.size().bits(Word::word_reg());
+                w.scrdv().bits(clk.scrdv);
+                // Clear clock phase and polarity. Will be set to correct value for each
+                // transfer
+                w.spo().bit(cpo_bit);
+                w.sph().bit(cph_bit)
+            }
+        });
+
+        spi.ctrl1().write(|w| {
+            w.lbm().bit(loopback_mode);
+            w.sod().bit(slave_output_disable);
+            w.ms().bit(ms);
+            w.mdlycap().bit(master_delayer_capture);
+            w.blockmode().bit(blockmode);
+            w.bmstall().bit(bmstall);
+            unsafe { w.ss().bits(0) }
+        });
+        spi.clkprescale()
+            .write(|w| unsafe { w.bits(clk.prescale_val as u32) });
+
+        spi.fifo_clr().write(|w| {
+            w.rxfifo().set_bit();
+            w.txfifo().set_bit()
+        });
+        // Enable the peripheral as the last step as recommended in the
+        // programmers guide
+        spi.ctrl1().modify(|_, w| w.enable().set_bit());
+        Spi {
+            inner: SpiBase {
+                spi,
+                cfg: spi_cfg,
+                apb1_clk: clocks.apb1(),
+                fill_word: Default::default(),
+                bmstall,
+                blockmode,
+                word: PhantomData,
+            },
+            pins,
+        }
+    }
+
+    delegate::delegate! {
+        to self.inner {
+            #[inline]
+            pub fn cfg_clock(&mut self, cfg: SpiClkConfig);
+
+            #[inline]
+            pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>;
+
+            #[inline]
+            pub fn spi(&self) -> &SpiI;
+
+            #[inline]
+            pub fn cfg_mode(&mut self, mode: Mode);
+
+            #[inline]
+            pub fn perid(&self) -> u32;
+
+            pub fn cfg_transfer<HwCs: OptionalHwCs<SpiI>>(
+                &mut self, transfer_cfg: &TransferConfigWithHwcs<HwCs>
+            );
+        }
+    }
+
+    #[inline]
+    pub fn set_fill_word(&mut self, fill_word: Word) {
+        self.inner.fill_word = fill_word;
+    }
+
+    #[inline]
+    pub fn fill_word(&self) -> Word {
+        self.inner.fill_word
+    }
+
+    /// Releases the SPI peripheral and associated pins
+    pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) {
+        (self.inner.spi, self.pins, self.inner.cfg)
+    }
+
+    pub fn downgrade(self) -> SpiBase<SpiI, Word> {
+        self.inner
+    }
+}
+
+impl<
+        SpiI: Instance,
+        Sck: PinSck<SpiI>,
+        Miso: PinMiso<SpiI>,
+        Mosi: PinMosi<SpiI>,
+        Word: WordProvider,
+    > SpiLowLevel for Spi<SpiI, (Sck, Miso, Mosi), Word>
+where
+    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
+{
+    delegate::delegate! {
+        to self.inner {
+            fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible>;
+            fn write_fifo_unchecked(&self, data: u32);
+            fn read_fifo(&self) -> nb::Result<u32, Infallible>;
+            fn read_fifo_unchecked(&self) -> u32;
+        }
+    }
+}
+
+impl<
+        SpiI: Instance,
+        Word: WordProvider,
+        Sck: PinSck<SpiI>,
+        Miso: PinMiso<SpiI>,
+        Mosi: PinMosi<SpiI>,
+    > embedded_hal::spi::ErrorType for Spi<SpiI, (Sck, Miso, Mosi), Word>
+{
+    type Error = Infallible;
+}
+
+impl<
+        SpiI: Instance,
+        Word: WordProvider,
+        Sck: PinSck<SpiI>,
+        Miso: PinMiso<SpiI>,
+        Mosi: PinMosi<SpiI>,
+    > embedded_hal::spi::SpiBus<Word> for Spi<SpiI, (Sck, Miso, Mosi), Word>
+where
+    <Word as TryFrom<u32>>::Error: core::fmt::Debug,
+{
+    delegate::delegate! {
+        to self.inner {
+            fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
+            fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>;
+            fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>;
+            fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
+            fn flush(&mut self) -> Result<(), Self::Error>;
+        }
+    }
+}
+
+/// Changing the word size also requires a type conversion
+impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
+    From<Spi<SpiI, (Sck, Miso, Mosi), u8>> for Spi<SpiI, (Sck, Miso, Mosi), u16>
+{
+    fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u8>) -> Self {
+        old_spi
+            .inner
+            .spi
+            .ctrl0()
+            .modify(|_, w| unsafe { w.size().bits(WordSize::SixteenBits as u8) });
+        Spi {
+            inner: SpiBase {
+                spi: old_spi.inner.spi,
+                cfg: old_spi.inner.cfg,
+                blockmode: old_spi.inner.blockmode,
+                bmstall: old_spi.inner.bmstall,
+                fill_word: Default::default(),
+                apb1_clk: old_spi.inner.apb1_clk,
+                word: PhantomData,
+            },
+            pins: old_spi.pins,
+        }
+    }
+}
+
+/// Changing the word size also requires a type conversion
+impl<SpiI: Instance, Sck: PinSck<SpiI>, Miso: PinMiso<SpiI>, Mosi: PinMosi<SpiI>>
+    From<Spi<SpiI, (Sck, Miso, Mosi), u16>> for Spi<SpiI, (Sck, Miso, Mosi), u8>
+{
+    fn from(old_spi: Spi<SpiI, (Sck, Miso, Mosi), u16>) -> Self {
+        old_spi
+            .inner
+            .spi
+            .ctrl0()
+            .modify(|_, w| unsafe { w.size().bits(WordSize::EightBits as u8) });
+        Spi {
+            inner: SpiBase {
+                spi: old_spi.inner.spi,
+                cfg: old_spi.inner.cfg,
+                blockmode: old_spi.inner.blockmode,
+                bmstall: old_spi.inner.bmstall,
+                apb1_clk: old_spi.inner.apb1_clk,
+                fill_word: Default::default(),
+                word: PhantomData,
+            },
+            pins: old_spi.pins,
+        }
+    }
+}
diff --git a/va416xx-hal/src/timer.rs b/va416xx-hal/src/timer.rs
index 07de4fe..6e2b083 100644
--- a/va416xx-hal/src/timer.rs
+++ b/va416xx-hal/src/timer.rs
@@ -2,904 +2,8 @@
 //!
 //! ## Examples
 //!
-//! - [Timer MS and Second Tick Example](https://github.com/us-irs/va416xx-rs/blob/main/examples/simple/examples/timer-ticks.rs)
-use core::cell::Cell;
-
-use cortex_m::asm;
-use critical_section::Mutex;
-
-use crate::clock::Clocks;
-use crate::gpio::{
-    AltFunc1, AltFunc2, AltFunc3, DynPinId, Pin, PinId, PA0, PA1, PA10, PA11, PA12, PA13, PA14,
-    PA15, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1, PB12, PB13, PB14, PB15, PB2, PB3, PB4, PC0, PC1,
-    PD10, PD11, PD12, PD13, PD14, PD15, PE0, PE1, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE6,
-    PE7, PE8, PE9, PF0, PF1, PF11, PF12, PF13, PF14, PF15, PF9, PG0, PG1, PG2, PG3, PG6,
-};
-
-#[cfg(not(feature = "va41628"))]
-use crate::gpio::{
-    PB10, PB11, PB5, PB6, PB7, PB8, PB9, PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PE10,
-    PE11, PF10, PF2, PF3, PF4, PF5, PF6, PF7, PF8,
-};
-
-use crate::time::Hertz;
-use crate::typelevel::Sealed;
-use crate::{disable_nvic_interrupt, enable_nvic_interrupt, pac, prelude::*};
-
-pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
+//! - [MS and second tick implementation](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/timer-ticks.rs)
+//! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/cascade.rs)
+pub use vorago_shared_periphs::timer::*;
 
 pub const TIM_IRQ_OFFSET: usize = 48;
-
-/// Get the peripheral block of a TIM peripheral given the index.
-///
-/// This function panics if the given index is greater than 23.
-///
-/// # Safety
-///
-/// This returns a direct handle to the peripheral block, which allows to circumvent ownership
-/// rules for the peripheral block. You have to ensure that the retrieved peripheral block is not
-/// used by any other software component.
-#[inline(always)]
-pub const unsafe fn get_tim_raw(tim_idx: usize) -> &'static pac::tim0::RegisterBlock {
-    match tim_idx {
-        0 => unsafe { &*pac::Tim0::ptr() },
-        1 => unsafe { &*pac::Tim1::ptr() },
-        2 => unsafe { &*pac::Tim2::ptr() },
-        3 => unsafe { &*pac::Tim3::ptr() },
-        4 => unsafe { &*pac::Tim4::ptr() },
-        5 => unsafe { &*pac::Tim5::ptr() },
-        6 => unsafe { &*pac::Tim6::ptr() },
-        7 => unsafe { &*pac::Tim7::ptr() },
-        8 => unsafe { &*pac::Tim8::ptr() },
-        9 => unsafe { &*pac::Tim9::ptr() },
-        10 => unsafe { &*pac::Tim10::ptr() },
-        11 => unsafe { &*pac::Tim11::ptr() },
-        12 => unsafe { &*pac::Tim12::ptr() },
-        13 => unsafe { &*pac::Tim13::ptr() },
-        14 => unsafe { &*pac::Tim14::ptr() },
-        15 => unsafe { &*pac::Tim15::ptr() },
-        16 => unsafe { &*pac::Tim16::ptr() },
-        17 => unsafe { &*pac::Tim17::ptr() },
-        18 => unsafe { &*pac::Tim18::ptr() },
-        19 => unsafe { &*pac::Tim19::ptr() },
-        20 => unsafe { &*pac::Tim20::ptr() },
-        21 => unsafe { &*pac::Tim21::ptr() },
-        22 => unsafe { &*pac::Tim22::ptr() },
-        23 => unsafe { &*pac::Tim23::ptr() },
-        _ => {
-            panic!("invalid alarm timer index")
-        }
-    }
-}
-
-//==================================================================================================
-// Defintions
-//==================================================================================================
-
-#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct CascadeCtrl {
-    /// Enable Cascade 0 signal active as a requirement for counting
-    pub enb_start_src_csd0: bool,
-    /// Invert Cascade 0, making it active low
-    pub inv_csd0: bool,
-    /// Enable Cascade 1 signal active as a requirement for counting
-    pub enb_start_src_csd1: bool,
-    /// Invert Cascade 1, making it active low
-    pub inv_csd1: bool,
-    /// Specify required operation if both Cascade 0 and Cascade 1 are active.
-    /// 0 is a logical AND of both cascade signals, 1 is a logical OR
-    pub dual_csd_op: bool,
-    /// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
-    /// cascade signal active, but once the counter is active, cascade control will be ignored
-    pub trg_csd0: bool,
-    /// Trigger mode, identical to [`trg_csd0`](CascadeCtrl) but for Cascade 1
-    pub trg_csd1: bool,
-    /// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
-    /// to the REQ_STOP control bit, but signalled by a Cascade source
-    pub enb_stop_src_csd2: bool,
-    /// Invert Cascade 2, making it active low
-    pub inv_csd2: bool,
-    /// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
-    /// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
-    /// ignored
-    pub trg_csd2: bool,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum CascadeSel {
-    Sel0 = 0,
-    Sel1 = 1,
-    Sel2 = 2,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct InvalidCascadeSourceId;
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum CascadeSource {
-    PortA(u8),
-    PortB(u8),
-    PortC(u8),
-    PortD(u8),
-    PortE(u8),
-    Tim(u8),
-    TxEv,
-    AdcIrq,
-    RomSbe,
-    RomMbe,
-    Ram0Sbe,
-    Ram0Mbe,
-    Ram1Sbe,
-    Ram2Mbe,
-    WdogIrq,
-}
-
-impl CascadeSource {
-    fn id(&self) -> Result<u8, InvalidCascadeSourceId> {
-        let port_check = |base: u8, id: u8| {
-            if id > 15 {
-                return Err(InvalidCascadeSourceId);
-            }
-            Ok(base + id)
-        };
-        match self {
-            CascadeSource::PortA(id) => port_check(0, *id),
-            CascadeSource::PortB(id) => port_check(16, *id),
-            CascadeSource::PortC(id) => port_check(32, *id),
-            CascadeSource::PortD(id) => port_check(48, *id),
-            CascadeSource::PortE(id) => port_check(65, *id),
-            CascadeSource::Tim(id) => {
-                if *id > 23 {
-                    return Err(InvalidCascadeSourceId);
-                }
-                Ok(80 + id)
-            }
-            CascadeSource::TxEv => Ok(104),
-            CascadeSource::AdcIrq => Ok(105),
-            CascadeSource::RomSbe => Ok(106),
-            CascadeSource::RomMbe => Ok(106),
-            CascadeSource::Ram0Sbe => Ok(108),
-            CascadeSource::Ram0Mbe => Ok(109),
-            CascadeSource::Ram1Sbe => Ok(110),
-            CascadeSource::Ram2Mbe => Ok(111),
-            CascadeSource::WdogIrq => Ok(112),
-        }
-    }
-}
-
-//==================================================================================================
-// Valid TIM and PIN combinations
-//==================================================================================================
-
-pub trait TimPin {
-    const DYN: DynPinId;
-}
-
-pub trait ValidTim {
-    // TIM ID ranging from 0 to 23 for 24 TIM peripherals
-    const ID: u8;
-    const IRQ: pac::Interrupt;
-
-    fn clock(clocks: &Clocks) -> Hertz {
-        if Self::ID <= 15 {
-            clocks.apb1()
-        } else {
-            clocks.apb2()
-        }
-    }
-}
-
-macro_rules! tim_markers {
-    (
-        $(
-            ($TimX:path, $id:expr, $Irq:path),
-        )+
-    ) => {
-        $(
-            impl ValidTim for $TimX {
-                const ID: u8 = $id;
-                const IRQ: pac::Interrupt = $Irq;
-            }
-        )+
-    };
-}
-
-pub const fn const_clock<Tim: ValidTim + ?Sized>(_: &Tim, clocks: &Clocks) -> Hertz {
-    if Tim::ID <= 15 {
-        clocks.apb1()
-    } else {
-        clocks.apb2()
-    }
-}
-
-tim_markers!(
-    (pac::Tim0, 0, pac::Interrupt::TIM0),
-    (pac::Tim1, 1, pac::Interrupt::TIM1),
-    (pac::Tim2, 2, pac::Interrupt::TIM2),
-    (pac::Tim3, 3, pac::Interrupt::TIM3),
-    (pac::Tim4, 4, pac::Interrupt::TIM4),
-    (pac::Tim5, 5, pac::Interrupt::TIM5),
-    (pac::Tim6, 6, pac::Interrupt::TIM6),
-    (pac::Tim7, 7, pac::Interrupt::TIM7),
-    (pac::Tim8, 8, pac::Interrupt::TIM8),
-    (pac::Tim9, 9, pac::Interrupt::TIM9),
-    (pac::Tim10, 10, pac::Interrupt::TIM10),
-    (pac::Tim11, 11, pac::Interrupt::TIM11),
-    (pac::Tim12, 12, pac::Interrupt::TIM12),
-    (pac::Tim13, 13, pac::Interrupt::TIM13),
-    (pac::Tim14, 14, pac::Interrupt::TIM14),
-    (pac::Tim15, 15, pac::Interrupt::TIM15),
-    (pac::Tim16, 16, pac::Interrupt::TIM16),
-    (pac::Tim17, 17, pac::Interrupt::TIM17),
-    (pac::Tim18, 18, pac::Interrupt::TIM18),
-    (pac::Tim19, 19, pac::Interrupt::TIM19),
-    (pac::Tim20, 20, pac::Interrupt::TIM20),
-    (pac::Tim21, 21, pac::Interrupt::TIM21),
-    (pac::Tim22, 22, pac::Interrupt::TIM22),
-    (pac::Tim23, 23, pac::Interrupt::TIM23),
-);
-
-pub trait ValidTimAndPin<Pin: TimPin, Tim: ValidTim>: Sealed {}
-
-macro_rules! valid_pin_and_tims {
-    (
-        $(
-            ($PinX:ident, $AltFunc:ident, $TimX:path $(, $meta: meta)?),
-        )+
-    ) => {
-        $(
-            $(#[$meta])?
-            impl TimPin for Pin<$PinX, $AltFunc>
-            where
-                $PinX: PinId,
-            {
-                const DYN: DynPinId = $PinX::DYN;
-            }
-
-            $(#[$meta])?
-            impl<
-                PinInstance: TimPin,
-                Tim: ValidTim
-            > ValidTimAndPin<PinInstance, Tim> for (Pin<$PinX, $AltFunc>, $TimX)
-            where
-                Pin<$PinX, $AltFunc>: TimPin,
-                $PinX: PinId,
-            {
-            }
-
-            $(#[$meta])?
-            impl Sealed for (Pin<$PinX, $AltFunc>, $TimX) {}
-        )+
-    };
-}
-
-valid_pin_and_tims!(
-    (PA0, AltFunc1, pac::Tim0),
-    (PA1, AltFunc1, pac::Tim1),
-    (PA2, AltFunc1, pac::Tim2),
-    (PA3, AltFunc1, pac::Tim3),
-    (PA4, AltFunc1, pac::Tim4),
-    (PA5, AltFunc1, pac::Tim5),
-    (PA6, AltFunc1, pac::Tim6),
-    (PA7, AltFunc1, pac::Tim7),
-    (PA10, AltFunc2, pac::Tim23),
-    (PA11, AltFunc2, pac::Tim22),
-    (PA12, AltFunc2, pac::Tim21),
-    (PA13, AltFunc2, pac::Tim20),
-    (PA14, AltFunc2, pac::Tim19),
-    (PA15, AltFunc2, pac::Tim18),
-    (PB0, AltFunc2, pac::Tim17),
-    (PB1, AltFunc2, pac::Tim16),
-    (PB2, AltFunc2, pac::Tim15),
-    (PB3, AltFunc2, pac::Tim14),
-    (PB4, AltFunc2, pac::Tim13),
-    (PB5, AltFunc2, pac::Tim12, cfg(not(feature = "va41628"))),
-    (PB6, AltFunc2, pac::Tim11, cfg(not(feature = "va41628"))),
-    (PB7, AltFunc2, pac::Tim10, cfg(not(feature = "va41628"))),
-    (PB8, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
-    (PB9, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
-    (PB10, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
-    (PB11, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
-    (PB12, AltFunc2, pac::Tim5),
-    (PB13, AltFunc2, pac::Tim4),
-    (PB14, AltFunc2, pac::Tim3),
-    (PB15, AltFunc2, pac::Tim2),
-    (PC0, AltFunc2, pac::Tim1),
-    (PC1, AltFunc2, pac::Tim0),
-    (PD0, AltFunc2, pac::Tim0, cfg(not(feature = "va41628"))),
-    (PD1, AltFunc2, pac::Tim1, cfg(not(feature = "va41628"))),
-    (PD2, AltFunc2, pac::Tim2, cfg(not(feature = "va41628"))),
-    (PD3, AltFunc2, pac::Tim3, cfg(not(feature = "va41628"))),
-    (PD4, AltFunc2, pac::Tim4, cfg(not(feature = "va41628"))),
-    (PD5, AltFunc2, pac::Tim5, cfg(not(feature = "va41628"))),
-    (PD6, AltFunc2, pac::Tim6, cfg(not(feature = "va41628"))),
-    (PD7, AltFunc2, pac::Tim7, cfg(not(feature = "va41628"))),
-    (PD8, AltFunc2, pac::Tim8, cfg(not(feature = "va41628"))),
-    (PD9, AltFunc2, pac::Tim9, cfg(not(feature = "va41628"))),
-    (PD10, AltFunc2, pac::Tim10),
-    (PD11, AltFunc2, pac::Tim11),
-    (PD12, AltFunc2, pac::Tim12),
-    (PD13, AltFunc2, pac::Tim13),
-    (PD14, AltFunc2, pac::Tim14),
-    (PD15, AltFunc2, pac::Tim15),
-    (PE0, AltFunc2, pac::Tim16),
-    (PE1, AltFunc2, pac::Tim17),
-    (PE2, AltFunc2, pac::Tim18),
-    (PE3, AltFunc2, pac::Tim19),
-    (PE4, AltFunc2, pac::Tim20),
-    (PE5, AltFunc2, pac::Tim21),
-    (PE6, AltFunc2, pac::Tim22),
-    (PE7, AltFunc2, pac::Tim23),
-    (PE8, AltFunc3, pac::Tim16),
-    (PE9, AltFunc3, pac::Tim17),
-    (PE10, AltFunc3, pac::Tim18, cfg(not(feature = "va41628"))),
-    (PE11, AltFunc3, pac::Tim19, cfg(not(feature = "va41628"))),
-    (PE12, AltFunc3, pac::Tim20),
-    (PE13, AltFunc3, pac::Tim21),
-    (PE14, AltFunc3, pac::Tim22),
-    (PE15, AltFunc3, pac::Tim23),
-    (PF0, AltFunc3, pac::Tim0),
-    (PF1, AltFunc3, pac::Tim1),
-    (PF2, AltFunc3, pac::Tim2, cfg(not(feature = "va41628"))),
-    (PF3, AltFunc3, pac::Tim3, cfg(not(feature = "va41628"))),
-    (PF4, AltFunc3, pac::Tim4, cfg(not(feature = "va41628"))),
-    (PF5, AltFunc3, pac::Tim5, cfg(not(feature = "va41628"))),
-    (PF6, AltFunc3, pac::Tim6, cfg(not(feature = "va41628"))),
-    (PF7, AltFunc3, pac::Tim7, cfg(not(feature = "va41628"))),
-    (PF8, AltFunc3, pac::Tim8, cfg(not(feature = "va41628"))),
-    (PF9, AltFunc3, pac::Tim9),
-    (PF10, AltFunc3, pac::Tim10, cfg(not(feature = "va41628"))),
-    (PF11, AltFunc3, pac::Tim11),
-    (PF12, AltFunc3, pac::Tim12),
-    (PF13, AltFunc2, pac::Tim19),
-    (PF14, AltFunc2, pac::Tim20),
-    (PF15, AltFunc2, pac::Tim21),
-    (PG0, AltFunc2, pac::Tim22),
-    (PG1, AltFunc2, pac::Tim23),
-    (PG2, AltFunc1, pac::Tim9),
-    (PG3, AltFunc1, pac::Tim10),
-    (PG6, AltFunc1, pac::Tim12),
-);
-
-//==================================================================================================
-// Register Interface for TIM registers and TIM pins
-//==================================================================================================
-
-/// Clear the reset bit of the TIM, holding it in reset
-///
-/// # Safety
-///
-/// Only the bit related to the corresponding TIM peripheral is modified
-#[inline]
-pub fn assert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
-    syscfg
-        .tim_reset()
-        .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << tim_id as u32)) });
-}
-
-#[inline]
-pub fn deassert_tim_reset(syscfg: &mut pac::Sysconfig, tim_id: u8) {
-    syscfg
-        .tim_reset()
-        .modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim_id as u32)) });
-}
-
-#[inline]
-pub fn assert_tim_reset_for_two_cycles(syscfg: &mut pac::Sysconfig, tim_id: u8) {
-    assert_tim_reset(syscfg, tim_id);
-    asm::nop();
-    asm::nop();
-    deassert_tim_reset(syscfg, tim_id);
-}
-
-pub type TimRegBlock = pac::tim0::RegisterBlock;
-
-/// Register interface.
-///
-/// This interface provides valid TIM pins a way to access their corresponding TIM
-/// registers
-///
-/// # Safety
-///
-/// Users should only implement the [Self::tim_id] function. No default function
-/// implementations should be overridden. The implementing type must also have
-/// "control" over the corresponding pin ID, i.e. it must guarantee that a each
-/// pin ID is a singleton.
-pub unsafe trait TimRegInterface {
-    fn tim_id(&self) -> u8;
-
-    const PORT_BASE: *const pac::tim0::RegisterBlock = pac::Tim0::ptr() as *const _;
-
-    /// All 24 TIM blocks are identical. This helper functions returns the correct
-    /// memory mapped peripheral depending on the TIM ID.
-    #[inline(always)]
-    fn reg_block(&self) -> &TimRegBlock {
-        unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) }
-    }
-
-    #[inline(always)]
-    fn mask_32(&self) -> u32 {
-        1 << self.tim_id()
-    }
-
-    /// Clear the reset bit of the TIM, holding it in reset
-    ///
-    /// # Safety
-    ///
-    /// Only the bit related to the corresponding TIM peripheral is modified
-    #[inline]
-    #[allow(dead_code)]
-    fn assert_tim_reset(&self, syscfg: &mut pac::Sysconfig) {
-        assert_tim_reset(syscfg, self.tim_id());
-    }
-
-    #[inline]
-    #[allow(dead_code)]
-    fn deassert_time_reset(&self, syscfg: &mut pac::Sysconfig) {
-        deassert_tim_reset(syscfg, self.tim_id());
-    }
-}
-
-unsafe impl<Tim: ValidTim> TimRegInterface for Tim {
-    fn tim_id(&self) -> u8 {
-        Tim::ID
-    }
-}
-
-/// Provide a safe register interface for [`ValidTimAndPin`]s
-///
-/// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to
-/// access the corresponding registers.
-pub(super) struct TimAndPinRegister<Pin: TimPin, Tim: ValidTim> {
-    pin: Pin,
-    tim: Tim,
-}
-
-pub(super) struct TimRegister<TIM: ValidTim> {
-    tim: TIM,
-}
-
-impl<TIM: ValidTim> TimRegister<TIM> {
-    #[inline]
-    pub(super) unsafe fn new(tim: TIM) -> Self {
-        TimRegister { tim }
-    }
-
-    pub(super) fn release(self) -> TIM {
-        self.tim
-    }
-}
-
-unsafe impl<Tim: ValidTim> TimRegInterface for TimRegister<Tim> {
-    #[inline(always)]
-    fn tim_id(&self) -> u8 {
-        Tim::ID
-    }
-}
-
-impl<Pin: TimPin, Tim: ValidTim> TimAndPinRegister<Pin, Tim>
-where
-    (Pin, Tim): ValidTimAndPin<Pin, Tim>,
-{
-    #[inline]
-    pub(super) unsafe fn new(pin: Pin, tim: Tim) -> Self {
-        TimAndPinRegister { pin, tim }
-    }
-
-    pub(super) fn release(self) -> (Pin, Tim) {
-        (self.pin, self.tim)
-    }
-}
-
-unsafe impl<Pin: TimPin, Tim: ValidTim> TimRegInterface for TimAndPinRegister<Pin, Tim> {
-    #[inline(always)]
-    fn tim_id(&self) -> u8 {
-        Tim::ID
-    }
-}
-
-pub(crate) struct TimDynRegister {
-    pub(crate) tim_id: u8,
-    #[allow(dead_code)]
-    pub(crate) pin_id: DynPinId,
-}
-
-impl<Pin: TimPin, Tim: ValidTim> From<TimAndPinRegister<Pin, Tim>> for TimDynRegister {
-    fn from(_reg: TimAndPinRegister<Pin, Tim>) -> Self {
-        Self {
-            tim_id: Tim::ID,
-            pin_id: Pin::DYN,
-        }
-    }
-}
-
-unsafe impl TimRegInterface for TimDynRegister {
-    #[inline(always)]
-    fn tim_id(&self) -> u8 {
-        self.tim_id
-    }
-}
-
-//==================================================================================================
-// Timers
-//==================================================================================================
-
-/// Hardware timers.
-///
-/// These timers also implement the [embedded_hal::delay::DelayNs] trait and can be used to delay
-/// with a higher resolution compared to the Cortex-M systick delays.
-pub struct CountdownTimer<TIM: ValidTim> {
-    tim: TimRegister<TIM>,
-    curr_freq: Hertz,
-    clock: Hertz,
-    rst_val: u32,
-    last_cnt: u32,
-    listening: bool,
-}
-
-#[inline]
-pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) {
-    syscfg
-        .tim_clk_enable()
-        .modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) });
-}
-
-unsafe impl<Tim: ValidTim> TimRegInterface for CountdownTimer<Tim> {
-    #[inline]
-    fn tim_id(&self) -> u8 {
-        Tim::ID
-    }
-}
-
-impl<Tim: ValidTim> CountdownTimer<Tim> {
-    /// Create a new countdown timer, but does not start it.
-    ///
-    /// You can use [Self::start] to start the countdown timer, and you may optionally call
-    /// [Self::listen] to enable interrupts for the TIM peripheral as well.
-    pub fn new(syscfg: &mut pac::Sysconfig, tim: Tim, clocks: &Clocks) -> Self {
-        enable_tim_clk(syscfg, Tim::ID);
-        assert_tim_reset(syscfg, Tim::ID);
-        cortex_m::asm::nop();
-        cortex_m::asm::nop();
-        deassert_tim_reset(syscfg, Tim::ID);
-
-        CountdownTimer {
-            tim: unsafe { TimRegister::new(tim) },
-            clock: Tim::clock(clocks),
-            rst_val: 0,
-            curr_freq: 0_u32.Hz(),
-            listening: false,
-            last_cnt: 0,
-        }
-    }
-
-    #[inline]
-    pub fn start(&mut self, timeout: impl Into<Hertz>) {
-        self.load(timeout);
-        self.enable();
-    }
-
-    /// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the
-    /// IRQSEL peripheral for the provided interrupt and unmasks the interrupt
-    #[inline]
-    pub fn listen(&mut self) {
-        self.listening = true;
-        self.enable_interrupt();
-        unsafe { enable_nvic_interrupt(Tim::IRQ) }
-    }
-
-    /// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
-    /// flag and restart the time if configured correctly
-    pub fn wait(&mut self) -> nb::Result<(), void::Void> {
-        let cnt = self.tim.reg_block().cnt_value().read().bits();
-        if (cnt > self.last_cnt) || cnt == 0 {
-            self.last_cnt = self.rst_val;
-            Ok(())
-        } else {
-            self.last_cnt = cnt;
-            Err(nb::Error::WouldBlock)
-        }
-    }
-
-    #[inline]
-    pub fn stop(&mut self) {
-        self.tim
-            .reg_block()
-            .ctrl()
-            .write(|w| w.enable().clear_bit());
-    }
-
-    #[inline]
-    pub fn unlisten(&mut self) {
-        self.listening = true;
-        self.disable_interrupt();
-        disable_nvic_interrupt(Tim::IRQ);
-    }
-
-    #[inline(always)]
-    pub fn enable_interrupt(&mut self) {
-        self.tim
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| w.irq_enb().set_bit());
-    }
-
-    #[inline(always)]
-    pub fn disable_interrupt(&mut self) {
-        self.tim
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| w.irq_enb().clear_bit());
-    }
-
-    #[inline]
-    pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim {
-        self.tim
-            .reg_block()
-            .ctrl()
-            .write(|w| w.enable().clear_bit());
-        syscfg
-            .tim_clk_enable()
-            .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::ID)) });
-        self.tim.release()
-    }
-
-    /// Load the count down timer with a timeout but do not start it.
-    pub fn load(&mut self, timeout: impl Into<Hertz>) {
-        self.tim
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| w.enable().clear_bit());
-        self.curr_freq = timeout.into();
-        self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1;
-        self.set_reload(self.rst_val);
-        // Decrementing counter, to set the reset value.
-        self.set_count(self.rst_val);
-    }
-
-    #[inline(always)]
-    pub fn set_reload(&mut self, val: u32) {
-        self.tim
-            .reg_block()
-            .rst_value()
-            .write(|w| unsafe { w.bits(val) });
-    }
-
-    #[inline(always)]
-    pub fn set_count(&mut self, val: u32) {
-        self.tim
-            .reg_block()
-            .cnt_value()
-            .write(|w| unsafe { w.bits(val) });
-    }
-
-    #[inline(always)]
-    pub fn count(&self) -> u32 {
-        self.tim.reg_block().cnt_value().read().bits()
-    }
-
-    #[inline(always)]
-    pub fn enable(&mut self) {
-        self.tim
-            .reg_block()
-            .enable()
-            .write(|w| unsafe { w.bits(1) });
-    }
-
-    #[inline(always)]
-    pub fn disable(&mut self) {
-        self.tim
-            .reg_block()
-            .ctrl()
-            .modify(|_, w| w.enable().clear_bit());
-    }
-
-    /// Disable the counter, setting both enable and active bit to 0
-    #[inline]
-    pub fn auto_disable(self, enable: bool) -> Self {
-        if enable {
-            self.tim
-                .reg_block()
-                .ctrl()
-                .modify(|_, w| w.auto_disable().set_bit());
-        } else {
-            self.tim
-                .reg_block()
-                .ctrl()
-                .modify(|_, w| w.auto_disable().clear_bit());
-        }
-        self
-    }
-
-    /// This option only applies when the Auto-Disable functionality is 0.
-    ///
-    /// The active bit is changed to 0 when count reaches 0, but the counter stays
-    /// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
-    #[inline]
-    pub fn auto_deactivate(self, enable: bool) -> Self {
-        if enable {
-            self.tim
-                .reg_block()
-                .ctrl()
-                .modify(|_, w| w.auto_deactivate().set_bit());
-        } else {
-            self.tim
-                .reg_block()
-                .ctrl()
-                .modify(|_, w| w.auto_deactivate().clear_bit());
-        }
-        self
-    }
-
-    /// Configure the cascade parameters
-    #[inline]
-    pub fn cascade_control(&mut self, ctrl: CascadeCtrl) {
-        self.tim.reg_block().csd_ctrl().write(|w| {
-            w.csden0().bit(ctrl.enb_start_src_csd0);
-            w.csdinv0().bit(ctrl.inv_csd0);
-            w.csden1().bit(ctrl.enb_start_src_csd1);
-            w.csdinv1().bit(ctrl.inv_csd1);
-            w.dcasop().bit(ctrl.dual_csd_op);
-            w.csdtrg0().bit(ctrl.trg_csd0);
-            w.csdtrg1().bit(ctrl.trg_csd1);
-            w.csden2().bit(ctrl.enb_stop_src_csd2);
-            w.csdinv2().bit(ctrl.inv_csd2);
-            w.csdtrg2().bit(ctrl.trg_csd2)
-        });
-    }
-
-    #[inline]
-    pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
-        let id = src.id()?;
-        self.tim
-            .reg_block()
-            .cascade0()
-            .write(|w| unsafe { w.cassel().bits(id) });
-        Ok(())
-    }
-
-    #[inline]
-    pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
-        let id = src.id()?;
-        self.tim
-            .reg_block()
-            .cascade1()
-            .write(|w| unsafe { w.cassel().bits(id) });
-        Ok(())
-    }
-
-    #[inline]
-    pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> {
-        let id = src.id()?;
-        self.tim
-            .reg_block()
-            .cascade2()
-            .write(|w| unsafe { w.cassel().bits(id) });
-        Ok(())
-    }
-
-    #[inline]
-    pub fn curr_freq(&self) -> Hertz {
-        self.curr_freq
-    }
-
-    #[inline]
-    pub fn listening(&self) -> bool {
-        self.listening
-    }
-}
-
-impl<Tim: ValidTim> embedded_hal::delay::DelayNs for CountdownTimer<Tim> {
-    fn delay_ns(&mut self, ns: u32) {
-        let ticks = (u64::from(ns)) * (u64::from(self.clock.raw())) / 1_000_000_000;
-
-        let full_cycles = ticks >> 32;
-        let mut last_count;
-        let mut new_count;
-        if full_cycles > 0 {
-            self.set_reload(u32::MAX);
-            self.set_count(u32::MAX);
-            self.enable();
-
-            for _ in 0..full_cycles {
-                // Always ensure that both values are the same at the start.
-                new_count = self.count();
-                last_count = new_count;
-                loop {
-                    new_count = self.count();
-                    if new_count == 0 {
-                        // Wait till timer has wrapped.
-                        while self.count() == 0 {
-                            cortex_m::asm::nop()
-                        }
-                        break;
-                    }
-                    // Timer has definitely wrapped.
-                    if new_count > last_count {
-                        break;
-                    }
-                    last_count = new_count;
-                }
-            }
-        }
-        let ticks = (ticks & u32::MAX as u64) as u32;
-        self.disable();
-        if ticks > 1 {
-            self.set_reload(ticks);
-            self.set_count(ticks);
-            self.enable();
-            last_count = ticks;
-
-            loop {
-                new_count = self.count();
-                if new_count == 0 || (new_count > last_count) {
-                    break;
-                }
-                last_count = new_count;
-            }
-        }
-
-        self.disable();
-    }
-}
-
-//==================================================================================================
-// MS tick implementations
-//==================================================================================================
-
-// Set up a millisecond timer on TIM0. Please note that the user still has to provide an IRQ handler
-// which should call [default_ms_irq_handler].
-pub fn set_up_ms_tick<Tim: ValidTim>(
-    sys_cfg: &mut pac::Sysconfig,
-    tim: Tim,
-    clocks: &Clocks,
-) -> CountdownTimer<Tim> {
-    let mut ms_timer = CountdownTimer::new(sys_cfg, tim, clocks);
-    ms_timer.listen();
-    ms_timer.start(1000.Hz());
-    ms_timer
-}
-
-/// This function can be called in a specified interrupt handler to increment
-/// the MS counter
-pub fn default_ms_irq_handler() {
-    critical_section::with(|cs| {
-        let mut ms = MS_COUNTER.borrow(cs).get();
-        ms += 1;
-        MS_COUNTER.borrow(cs).set(ms);
-    });
-}
-
-/// Get the current MS tick count
-pub fn get_ms_ticks() -> u32 {
-    critical_section::with(|cs| MS_COUNTER.borrow(cs).get())
-}
-
-pub struct DelayMs<Tim: ValidTim = pac::Tim0>(CountdownTimer<Tim>);
-
-impl<Tim: ValidTim> DelayMs<Tim> {
-    pub fn new(timer: CountdownTimer<Tim>) -> Option<Self> {
-        if timer.curr_freq() != Hertz::from_raw(1000) || !timer.listening() {
-            return None;
-        }
-        Some(Self(timer))
-    }
-}
-
-/// This assumes that the user has already set up a MS tick timer with [set_up_ms_tick]
-impl embedded_hal::delay::DelayNs for DelayMs {
-    fn delay_ns(&mut self, ns: u32) {
-        let ns_as_ms = ns / 1_000_000;
-        if self.0.curr_freq() != Hertz::from_raw(1000) || !self.0.listening() {
-            return;
-        }
-        let start_time = get_ms_ticks();
-        while get_ms_ticks() - start_time < ns_as_ms {
-            cortex_m::asm::nop();
-        }
-    }
-}
diff --git a/va416xx-hal/src/typelevel.rs b/va416xx-hal/src/typelevel.rs
deleted file mode 100644
index 7803c20..0000000
--- a/va416xx-hal/src/typelevel.rs
+++ /dev/null
@@ -1,155 +0,0 @@
-//! Module supporting type-level programming
-//!
-//! This module is identical to the
-//! [atsamd typelevel](https://docs.rs/atsamd-hal/latest/atsamd_hal/typelevel/index.html).
-
-use core::ops::{Add, Sub};
-
-use typenum::{Add1, Bit, Sub1, UInt, Unsigned, B1, U0};
-
-mod private {
-    /// Super trait used to mark traits with an exhaustive set of
-    /// implementations
-    pub trait Sealed {}
-
-    impl Sealed for u8 {}
-    impl Sealed for i8 {}
-    impl Sealed for u16 {}
-    impl Sealed for i16 {}
-    impl Sealed for u32 {}
-    impl Sealed for i32 {}
-    impl Sealed for f32 {}
-
-    /// Mapping from an instance of a countable type to its successor
-    pub trait Increment {
-        /// Successor type of `Self`
-        type Inc;
-        /// Consume an instance of `Self` and return its successor
-        fn inc(self) -> Self::Inc;
-    }
-
-    /// Mapping from an instance of a countable type to its predecessor
-    pub trait Decrement {
-        /// Predecessor type of `Self`
-        type Dec;
-        /// Consume an instance of `Self` and return its predecessor
-        fn dec(self) -> Self::Dec;
-    }
-}
-
-pub(crate) use private::Decrement as PrivateDecrement;
-pub(crate) use private::Increment as PrivateIncrement;
-pub(crate) use private::Sealed;
-
-/// Type-level version of the [`None`] variant
-#[derive(Default)]
-pub struct NoneT;
-
-impl Sealed for NoneT {}
-
-//==============================================================================
-// Is
-//==============================================================================
-
-/// Marker trait for type identity
-///
-/// This trait is used as part of the [`AnyKind`] trait pattern. It represents
-/// the concept of type identity, because all implementors have
-/// `<Self as Is>::Type == Self`. When used as a trait bound with a specific
-/// type, it guarantees that the corresponding type parameter is exactly the
-/// specific type. Stated differently, it guarantees that `T == Specific` in
-/// the following example.
-///
-/// ```ignore
-/// where T: Is<Type = Specific>
-/// ```
-///
-/// Moreover, the super traits guarantee that any instance of or reference to a
-/// type `T` can be converted into the `Specific` type.
-///
-/// ```ignore
-/// fn example<T>(mut any: T)
-/// where
-///     T: Is<Type = Specific>,
-/// {
-///     let specific_mut: &mut Specific = any.as_mut();
-///     let specific_ref: &Specific = any.as_ref();
-///     let specific: Specific = any.into();
-/// }
-/// ```
-///
-/// [`AnyKind`]: #anykind-trait-pattern
-pub trait Is
-where
-    Self: Sealed,
-    Self: From<IsType<Self>>,
-    Self: Into<IsType<Self>>,
-    Self: AsRef<IsType<Self>>,
-    Self: AsMut<IsType<Self>>,
-{
-    type Type;
-}
-
-/// Type alias for [`Is::Type`]
-pub type IsType<T> = <T as Is>::Type;
-
-impl<T> Is for T
-where
-    T: Sealed + AsRef<T> + AsMut<T>,
-{
-    type Type = T;
-}
-
-//==============================================================================
-// Counting
-//==============================================================================
-
-/// Implement `Sealed` for [`U0`]
-impl Sealed for U0 {}
-
-/// Implement `Sealed` for all type-level, [`Unsigned`] integers *except* [`U0`]
-impl<U: Unsigned, B: Bit> Sealed for UInt<U, B> {}
-
-/// Trait mapping each countable type to its successor
-///
-/// This trait maps each countable type to its corresponding successor type. The
-/// actual implementation of this trait is contained within `PrivateIncrement`.
-/// Access to `PrivateIncrement` is restricted, so that safe HAL APIs can be
-/// built with it.
-pub trait Increment: PrivateIncrement {}
-
-impl<T: PrivateIncrement> Increment for T {}
-
-/// Trait mapping each countable type to its predecessor
-///
-/// This trait maps each countable type to its corresponding predecessor type.
-/// The actual implementation of this trait is contained within
-/// `PrivateDecrement`. Access to `PrivateDecrement` is restricted, so that safe
-/// HAL APIs can be built with it.
-pub trait Decrement: PrivateDecrement {}
-
-impl<T: PrivateDecrement> Decrement for T {}
-
-impl<N> PrivateIncrement for N
-where
-    N: Unsigned + Add<B1>,
-    Add1<N>: Unsigned,
-{
-    type Inc = Add1<N>;
-    #[inline]
-    fn inc(self) -> Self::Inc {
-        Self::Inc::default()
-    }
-}
-
-impl<N> PrivateDecrement for N
-where
-    N: Unsigned + Sub<B1>,
-    Sub1<N>: Unsigned,
-{
-    type Dec = Sub1<N>;
-    #[inline]
-    fn dec(self) -> Self::Dec {
-        Self::Dec::default()
-    }
-}
diff --git a/va416xx-hal/src/uart/mod.rs b/va416xx-hal/src/uart/mod.rs
index e3d1ee6..e460508 100644
--- a/va416xx-hal/src/uart/mod.rs
+++ b/va416xx-hal/src/uart/mod.rs
@@ -1,1329 +1,17 @@
 //! # API for the UART peripheral
 //!
-//! The core of this API are the [Uart], [UartBase], [Rx] and [Tx] structures.
+//! The core of this API are the [Uart], [Rx] and [Tx] structures.
 //! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver
 //! using interrupts.
 //!
+//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART
+//! peripheral.
+//!
 //! ## Examples
 //!
 //! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/uart.rs)
-//! - [UART echo with IRQ and Embassy](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/uart-echo-with-irq.rs)
-//! - [Flashloader app using UART with IRQs](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
-use core::convert::Infallible;
-use core::ops::Deref;
-
-use embedded_hal_nb::serial::Read;
-use fugit::RateExtU32;
-
-use crate::clock::{Clocks, PeripheralSelect, SyscfgExt};
-use crate::gpio::PF13;
-use crate::time::Hertz;
-use crate::{disable_nvic_interrupt, enable_nvic_interrupt};
-use crate::{
-    gpio::{
-        AltFunc1, AltFunc2, AltFunc3, Pin, PA2, PA3, PB14, PB15, PC14, PC4, PC5, PD11, PD12, PE2,
-        PE3, PF12, PF9, PG0, PG1,
-    },
-    pac::{self, uart0 as uart_base, Uart0, Uart1, Uart2},
-};
-
-#[cfg(not(feature = "va41628"))]
-use crate::gpio::{PC15, PF8};
-
-#[derive(Debug, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum Bank {
-    Uart0 = 0,
-    Uart1 = 1,
-    Uart2 = 2,
-}
-
-//==================================================================================================
-// Type-Level support
-//==================================================================================================
-
-pub trait RxPin<Uart> {}
-pub trait TxPin<Uart> {}
-
-impl TxPin<Uart0> for Pin<PA2, AltFunc3> {}
-impl RxPin<Uart0> for Pin<PA3, AltFunc3> {}
-
-impl TxPin<Uart0> for Pin<PC4, AltFunc2> {}
-impl RxPin<Uart0> for Pin<PC5, AltFunc2> {}
-
-impl TxPin<Uart0> for Pin<PE2, AltFunc3> {}
-impl RxPin<Uart0> for Pin<PE3, AltFunc3> {}
-
-impl TxPin<Uart0> for Pin<PG0, AltFunc1> {}
-impl RxPin<Uart0> for Pin<PG1, AltFunc1> {}
-
-impl TxPin<Uart1> for Pin<PB14, AltFunc3> {}
-impl RxPin<Uart1> for Pin<PB15, AltFunc3> {}
-
-impl TxPin<Uart1> for Pin<PD11, AltFunc3> {}
-impl RxPin<Uart1> for Pin<PD12, AltFunc3> {}
-
-impl TxPin<Uart1> for Pin<PF12, AltFunc1> {}
-impl RxPin<Uart1> for Pin<PF13, AltFunc1> {}
-
-impl TxPin<Uart2> for Pin<PC14, AltFunc2> {}
-#[cfg(not(feature = "va41628"))]
-impl RxPin<Uart2> for Pin<PC15, AltFunc2> {}
-
-#[cfg(not(feature = "va41628"))]
-impl TxPin<Uart2> for Pin<PF8, AltFunc1> {}
-impl RxPin<Uart2> for Pin<PF9, AltFunc1> {}
-
-//==================================================================================================
-// Regular Definitions
-//==================================================================================================
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct TransferPendingError;
-
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum Event {
-    // Receiver FIFO interrupt enable. Generates interrupt
-    // when FIFO is at least half full. Half full is defined as FIFO
-    // count >= RXFIFOIRQTRG
-    RxFifoHalfFull,
-    // Framing error, Overrun error, Parity Error and Break error
-    RxError,
-    // Event for timeout condition: Data in the FIFO and no receiver
-    // FIFO activity for 4 character times
-    RxTimeout,
-
-    // Transmitter FIFO interrupt enable. Generates interrupt
-    // when FIFO is at least half full. Half full is defined as FIFO
-    // count >= TXFIFOIRQTRG
-    TxFifoHalfFull,
-    // FIFO overflow error
-    TxError,
-    // Generate interrupt when transmit FIFO is empty and TXBUSY is 0
-    TxEmpty,
-    // Interrupt when CTSn changes value
-    TxCts,
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum Parity {
-    None,
-    Odd,
-    Even,
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum StopBits {
-    One = 0,
-    Two = 1,
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum WordSize {
-    Five = 0,
-    Six = 1,
-    Seven = 2,
-    Eight = 3,
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct Config {
-    pub baudrate: Hertz,
-    pub parity: Parity,
-    pub stopbits: StopBits,
-    // When false, use standard 16x baud clock, other 8x baud clock
-    pub baud8: bool,
-    pub wordsize: WordSize,
-    pub enable_tx: bool,
-    pub enable_rx: bool,
-}
-
-impl Config {
-    pub fn baudrate(mut self, baudrate: Hertz) -> Self {
-        self.baudrate = baudrate;
-        self
-    }
-
-    pub fn parity_none(mut self) -> Self {
-        self.parity = Parity::None;
-        self
-    }
-
-    pub fn parity_even(mut self) -> Self {
-        self.parity = Parity::Even;
-        self
-    }
-
-    pub fn parity_odd(mut self) -> Self {
-        self.parity = Parity::Odd;
-        self
-    }
-
-    pub fn stopbits(mut self, stopbits: StopBits) -> Self {
-        self.stopbits = stopbits;
-        self
-    }
-
-    pub fn wordsize(mut self, wordsize: WordSize) -> Self {
-        self.wordsize = wordsize;
-        self
-    }
-
-    pub fn baud8(mut self, baud: bool) -> Self {
-        self.baud8 = baud;
-        self
-    }
-}
-
-impl Default for Config {
-    fn default() -> Config {
-        Config {
-            baudrate: 115200_u32.Hz(),
-            parity: Parity::None,
-            stopbits: StopBits::One,
-            baud8: false,
-            wordsize: WordSize::Eight,
-            enable_tx: true,
-            enable_rx: true,
-        }
-    }
-}
-
-impl From<Hertz> for Config {
-    fn from(value: Hertz) -> Self {
-        Config::default().baudrate(value)
-    }
-}
-
-//==================================================================================================
-// IRQ Definitions
-//==================================================================================================
-
-#[derive(Debug, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct IrqContextTimeoutOrMaxSize {
-    rx_idx: usize,
-    mode: IrqReceptionMode,
-    pub max_len: usize,
-}
-
-impl IrqContextTimeoutOrMaxSize {
-    pub fn new(max_len: usize) -> Self {
-        IrqContextTimeoutOrMaxSize {
-            rx_idx: 0,
-            max_len,
-            mode: IrqReceptionMode::Idle,
-        }
-    }
-}
-
-impl IrqContextTimeoutOrMaxSize {
-    pub fn reset(&mut self) {
-        self.rx_idx = 0;
-        self.mode = IrqReceptionMode::Idle;
-    }
-}
-
-/// This struct is used to return the default IRQ handler result to the user
-#[derive(Debug, Default)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct IrqResult {
-    pub bytes_read: usize,
-    pub errors: Option<UartErrors>,
-}
-
-/// This struct is used to return the default IRQ handler result to the user
-#[derive(Debug, Default)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct IrqResultMaxSizeOrTimeout {
-    complete: bool,
-    timeout: bool,
-    pub errors: Option<UartErrors>,
-    pub bytes_read: usize,
-}
-
-impl IrqResultMaxSizeOrTimeout {
-    pub fn new() -> Self {
-        IrqResultMaxSizeOrTimeout {
-            complete: false,
-            timeout: false,
-            errors: None,
-            bytes_read: 0,
-        }
-    }
-}
-impl IrqResultMaxSizeOrTimeout {
-    #[inline]
-    pub fn has_errors(&self) -> bool {
-        self.errors.is_some()
-    }
-
-    #[inline]
-    pub fn overflow_error(&self) -> bool {
-        self.errors.is_some_and(|e| e.overflow)
-    }
-
-    #[inline]
-    pub fn framing_error(&self) -> bool {
-        self.errors.is_some_and(|e| e.framing)
-    }
-
-    #[inline]
-    pub fn parity_error(&self) -> bool {
-        self.errors.is_some_and(|e| e.parity)
-    }
-
-    #[inline]
-    pub fn timeout(&self) -> bool {
-        self.timeout
-    }
-
-    #[inline]
-    pub fn complete(&self) -> bool {
-        self.complete
-    }
-}
-
-#[derive(Debug, PartialEq, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-enum IrqReceptionMode {
-    Idle,
-    Pending,
-}
-
-#[derive(Default, Debug, Copy, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct UartErrors {
-    overflow: bool,
-    framing: bool,
-    parity: bool,
-    other: bool,
-}
-
-impl UartErrors {
-    #[inline(always)]
-    pub fn overflow(&self) -> bool {
-        self.overflow
-    }
-
-    #[inline(always)]
-    pub fn framing(&self) -> bool {
-        self.framing
-    }
-
-    #[inline(always)]
-    pub fn parity(&self) -> bool {
-        self.parity
-    }
-
-    #[inline(always)]
-    pub fn other(&self) -> bool {
-        self.other
-    }
-}
-
-impl UartErrors {
-    #[inline(always)]
-    pub fn error(&self) -> bool {
-        self.overflow || self.framing || self.parity
-    }
-}
-
-#[derive(Debug, PartialEq, Eq)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct BufferTooShortError {
-    found: usize,
-    expected: usize,
-}
-
-//==================================================================================================
-// UART peripheral wrapper
-//==================================================================================================
-
-pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
-    const IDX: u8;
-    const PERIPH_SEL: PeripheralSelect;
-    const PTR: *const uart_base::RegisterBlock;
-    const IRQ_RX: pac::Interrupt;
-    const IRQ_TX: pac::Interrupt;
-
-    /// Retrieve the peripheral structure.
-    ///
-    /// # Safety
-    ///
-    /// This circumvents the safety guarantees of the HAL.
-    unsafe fn steal() -> Self;
-
-    #[inline(always)]
-    fn ptr() -> *const uart_base::RegisterBlock {
-        Self::PTR
-    }
-
-    /// Retrieve the type erased peripheral register block.
-    ///
-    /// # Safety
-    ///
-    /// This circumvents the safety guarantees of the HAL.
-    #[inline(always)]
-    unsafe fn reg_block() -> &'static uart_base::RegisterBlock {
-        unsafe { &(*Self::ptr()) }
-    }
-}
-
-impl Instance for Uart0 {
-    const IDX: u8 = 0;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0;
-    const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
-    const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
-    const PTR: *const uart_base::RegisterBlock = Self::PTR;
-
-    unsafe fn steal() -> Self {
-        Self::steal()
-    }
-    fn ptr() -> *const uart_base::RegisterBlock {
-        Self::ptr() as *const _
-    }
-}
-
-impl Instance for Uart1 {
-    const IDX: u8 = 1;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1;
-    const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
-    const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
-    const PTR: *const uart_base::RegisterBlock = Self::PTR;
-
-    unsafe fn steal() -> Self {
-        Self::steal()
-    }
-    fn ptr() -> *const uart_base::RegisterBlock {
-        Self::ptr() as *const _
-    }
-}
-
-impl Instance for Uart2 {
-    const IDX: u8 = 2;
-    const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart2;
-    const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
-    const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
-    const PTR: *const uart_base::RegisterBlock = Self::PTR;
-
-    unsafe fn steal() -> Self {
-        Self::steal()
-    }
-    fn ptr() -> *const uart_base::RegisterBlock {
-        Self::ptr() as *const _
-    }
-}
-
-impl Bank {
-    /// Retrieve the peripheral register block.
-    ///
-    /// # Safety
-    ///
-    /// Circumvents the HAL safety guarantees.
-    pub unsafe fn reg_block(&self) -> &'static uart_base::RegisterBlock {
-        match self {
-            Bank::Uart0 => unsafe { pac::Uart0::reg_block() },
-            Bank::Uart1 => unsafe { pac::Uart1::reg_block() },
-            Bank::Uart2 => unsafe { pac::Uart2::reg_block() },
-        }
-    }
-}
-
-//==================================================================================================
-// UART implementation
-//==================================================================================================
-
-/// Type erased variant of a UART. Can be created with the [Uart::downgrade] function.
-pub struct UartBase<Uart> {
-    uart: Uart,
-    tx: Tx<Uart>,
-    rx: Rx<Uart>,
-}
-
-impl<Uart: Instance> UartBase<Uart> {
-    fn init(self, config: Config, clocks: &Clocks) -> Self {
-        if Uart::IDX == 2 {
-            self.init_with_clock_freq(config, clocks.apb1())
-        } else {
-            self.init_with_clock_freq(config, clocks.apb2())
-        }
-    }
-
-    /// This function assumes that the peripheral clock was alredy enabled
-    /// in the SYSCONFIG register
-    fn init_with_clock_freq(self, config: Config, apb_clk: Hertz) -> Self {
-        let baud_multiplier = match config.baud8 {
-            false => 16,
-            true => 8,
-        };
-        // This is the calculation: (64.0 * (x - integer_part as f32) + 0.5) as u32 without floating
-        // point calculations.
-        let frac = ((apb_clk.raw() % (config.baudrate.raw() * 16)) * 64
-            + (config.baudrate.raw() * 8))
-            / (config.baudrate.raw() * 16);
-        // Calculations here are derived from chapter 10.4.4 (p.74) of the datasheet.
-        let x = apb_clk.raw() as f32 / (config.baudrate.raw() * baud_multiplier) as f32;
-        let integer_part = x as u32;
-        self.uart.clkscale().write(|w| unsafe {
-            w.frac().bits(frac as u8);
-            w.int().bits(integer_part)
-        });
-
-        let (paren, pareven) = match config.parity {
-            Parity::None => (false, false),
-            Parity::Odd => (true, false),
-            Parity::Even => (true, true),
-        };
-        let stopbits = match config.stopbits {
-            StopBits::One => false,
-            StopBits::Two => true,
-        };
-        let wordsize = config.wordsize as u8;
-        let baud8 = config.baud8;
-        self.uart.ctrl().write(|w| {
-            w.paren().bit(paren);
-            w.pareven().bit(pareven);
-            w.stopbits().bit(stopbits);
-            w.baud8().bit(baud8);
-            unsafe { w.wordsize().bits(wordsize) }
-        });
-        let (txenb, rxenb) = (config.enable_tx, config.enable_rx);
-        // Clear the FIFO
-        self.uart.fifo_clr().write(|w| {
-            w.rxfifo().set_bit();
-            w.txfifo().set_bit()
-        });
-        self.uart.enable().write(|w| {
-            w.rxenable().bit(rxenb);
-            w.txenable().bit(txenb)
-        });
-        self
-    }
-
-    #[inline]
-    pub fn enable_rx(&mut self) {
-        self.uart.enable().modify(|_, w| w.rxenable().set_bit());
-    }
-
-    #[inline]
-    pub fn disable_rx(&mut self) {
-        self.uart.enable().modify(|_, w| w.rxenable().clear_bit());
-    }
-
-    #[inline]
-    pub fn enable_tx(&mut self) {
-        self.uart.enable().modify(|_, w| w.txenable().set_bit());
-    }
-
-    #[inline]
-    pub fn disable_tx(&mut self) {
-        self.uart.enable().modify(|_, w| w.txenable().clear_bit());
-    }
-
-    #[inline]
-    pub fn clear_rx_fifo(&mut self) {
-        self.uart.fifo_clr().write(|w| w.rxfifo().set_bit());
-    }
-
-    #[inline]
-    pub fn clear_tx_fifo(&mut self) {
-        self.uart.fifo_clr().write(|w| w.txfifo().set_bit());
-    }
-
-    pub fn listen(&self, event: Event) {
-        self.uart.irq_enb().modify(|_, w| match event {
-            Event::RxError => w.irq_rx_status().set_bit(),
-            Event::RxFifoHalfFull => w.irq_rx().set_bit(),
-            Event::RxTimeout => w.irq_rx_to().set_bit(),
-            Event::TxEmpty => w.irq_tx_empty().set_bit(),
-            Event::TxError => w.irq_tx_status().set_bit(),
-            Event::TxFifoHalfFull => w.irq_tx().set_bit(),
-            Event::TxCts => w.irq_tx_cts().set_bit(),
-        });
-    }
-
-    pub fn unlisten(&self, event: Event) {
-        self.uart.irq_enb().modify(|_, w| match event {
-            Event::RxError => w.irq_rx_status().clear_bit(),
-            Event::RxFifoHalfFull => w.irq_rx().clear_bit(),
-            Event::RxTimeout => w.irq_rx_to().clear_bit(),
-            Event::TxEmpty => w.irq_tx_empty().clear_bit(),
-            Event::TxError => w.irq_tx_status().clear_bit(),
-            Event::TxFifoHalfFull => w.irq_tx().clear_bit(),
-            Event::TxCts => w.irq_tx_cts().clear_bit(),
-        });
-    }
-
-    pub fn release(self) -> Uart {
-        // Clear the FIFO
-        self.uart.fifo_clr().write(|w| {
-            w.rxfifo().set_bit();
-            w.txfifo().set_bit()
-        });
-        self.uart.enable().write(|w| {
-            w.rxenable().clear_bit();
-            w.txenable().clear_bit()
-        });
-        disable_nvic_interrupt(Uart::IRQ_RX);
-        disable_nvic_interrupt(Uart::IRQ_TX);
-        self.uart
-    }
-
-    /// Poll receiver errors.
-    pub fn poll_rx_errors(&self) -> Option<UartErrors> {
-        self.rx.poll_errors()
-    }
-
-    pub fn split(self) -> (Tx<Uart>, Rx<Uart>) {
-        (self.tx, self.rx)
-    }
-}
-
-impl<UartInstance> embedded_io::ErrorType for UartBase<UartInstance> {
-    type Error = Infallible;
-}
-
-impl<UartInstance> embedded_hal_nb::serial::ErrorType for UartBase<UartInstance> {
-    type Error = Infallible;
-}
-
-impl<Uart: Instance> embedded_hal_nb::serial::Read<u8> for UartBase<Uart> {
-    fn read(&mut self) -> nb::Result<u8, Self::Error> {
-        self.rx.read()
-    }
-}
-
-impl<Uart: Instance> embedded_hal_nb::serial::Write<u8> for UartBase<Uart> {
-    fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
-        self.tx.write(word).map_err(|e| {
-            if let nb::Error::Other(_) = e {
-                unreachable!()
-            }
-            nb::Error::WouldBlock
-        })
-    }
-
-    fn flush(&mut self) -> nb::Result<(), Self::Error> {
-        self.tx.flush().map_err(|e| {
-            if let nb::Error::Other(_) = e {
-                unreachable!()
-            }
-            nb::Error::WouldBlock
-        })
-    }
-}
-
-/// Serial abstraction. Entry point to create a new UART
-pub struct Uart<UartInstance, Pins> {
-    inner: UartBase<UartInstance>,
-    pins: Pins,
-}
-
-impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstance: Instance>
-    Uart<UartInstance, (TxPinInst, RxPinInst)>
-{
-    pub fn new(
-        syscfg: &mut va416xx::Sysconfig,
-        uart: UartInstance,
-        pins: (TxPinInst, RxPinInst),
-        config: impl Into<Config>,
-        clocks: &Clocks,
-    ) -> Self {
-        crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
-        // This is done in the C HAL.
-        syscfg.assert_periph_reset_for_two_cycles(UartInstance::PERIPH_SEL);
-        Uart {
-            inner: UartBase {
-                uart,
-                tx: Tx::new(unsafe { UartInstance::steal() }),
-                rx: Rx::new(unsafe { UartInstance::steal() }),
-            },
-            pins,
-        }
-        .init(config.into(), clocks)
-    }
-
-    pub fn new_with_clock_freq(
-        syscfg: &mut va416xx::Sysconfig,
-        uart: UartInstance,
-        pins: (TxPinInst, RxPinInst),
-        config: impl Into<Config>,
-        clock: impl Into<Hertz>,
-    ) -> Self {
-        crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
-        Uart {
-            inner: UartBase {
-                uart,
-                tx: Tx::new(unsafe { UartInstance::steal() }),
-                rx: Rx::new(unsafe { UartInstance::steal() }),
-            },
-            pins,
-        }
-        .init_with_clock_freq(config.into(), clock.into())
-    }
-
-    fn init(mut self, config: Config, clocks: &Clocks) -> Self {
-        self.inner = self.inner.init(config, clocks);
-        self
-    }
-
-    /// This function assumes that the peripheral clock was already enabled
-    /// in the SYSCONFIG register
-    #[allow(dead_code)]
-    fn init_with_clock_freq(mut self, config: Config, sys_clk: Hertz) -> Self {
-        self.inner = self.inner.init_with_clock_freq(config, sys_clk);
-        self
-    }
-
-    delegate::delegate! {
-        to self.inner {
-            /// Poll receiver errors.
-            pub fn poll_rx_errors(&self) -> Option<UartErrors>;
-            #[inline]
-            pub fn enable_rx(&mut self);
-            #[inline]
-            pub fn disable_rx(&mut self);
-
-            #[inline]
-            pub fn enable_tx(&mut self);
-            #[inline]
-            pub fn disable_tx(&mut self);
-
-            #[inline]
-            pub fn clear_rx_fifo(&mut self);
-            #[inline]
-            pub fn clear_tx_fifo(&mut self);
-
-            #[inline]
-            pub fn listen(&self, event: Event);
-            #[inline]
-            pub fn unlisten(&self, event: Event);
-            #[inline]
-            pub fn split(self) -> (Tx<UartInstance>, Rx<UartInstance>);
-        }
-    }
-
-    pub fn downgrade(self) -> UartBase<UartInstance> {
-        UartBase {
-            uart: self.inner.uart,
-            tx: self.inner.tx,
-            rx: self.inner.rx,
-        }
-    }
-
-    pub fn release(self) -> (UartInstance, (TxPinInst, RxPinInst)) {
-        (self.inner.release(), self.pins)
-    }
-}
-
-#[inline(always)]
-pub fn enable_rx(uart: &uart_base::RegisterBlock) {
-    uart.enable().modify(|_, w| w.rxenable().set_bit());
-}
-
-#[inline(always)]
-pub fn disable_rx(uart: &uart_base::RegisterBlock) {
-    uart.enable().modify(|_, w| w.rxenable().clear_bit());
-}
-
-#[inline(always)]
-pub fn enable_rx_interrupts(uart: &uart_base::RegisterBlock) {
-    uart.irq_enb().modify(|_, w| {
-        w.irq_rx().set_bit();
-        w.irq_rx_to().set_bit();
-        w.irq_rx_status().set_bit()
-    });
-}
-
-#[inline(always)]
-pub fn disable_rx_interrupts(uart: &uart_base::RegisterBlock) {
-    uart.irq_enb().modify(|_, w| {
-        w.irq_rx().clear_bit();
-        w.irq_rx_to().clear_bit();
-        w.irq_rx_status().clear_bit()
-    });
-}
-
-/// Serial receiver.
-///
-/// Can be created by using the [Uart::split] or [UartBase::split] API.
-pub struct Rx<Uart>(Uart);
-
-impl<Uart: Instance> Rx<Uart> {
-    fn new(uart: Uart) -> Self {
-        Self(uart)
-    }
-
-    /// Direct access to the peripheral structure.
-    ///
-    /// # Safety
-    ///
-    /// You must ensure that only registers related to the operation of the RX side are used.
-    pub unsafe fn uart(&self) -> &Uart {
-        &self.0
-    }
-
-    pub fn poll_errors(&self) -> Option<UartErrors> {
-        let mut errors = UartErrors::default();
-
-        let uart = unsafe { &(*Uart::ptr()) };
-        let status_reader = uart.rxstatus().read();
-        if status_reader.rxovr().bit_is_set() {
-            errors.overflow = true;
-        } else if status_reader.rxfrm().bit_is_set() {
-            errors.framing = true;
-        } else if status_reader.rxpar().bit_is_set() {
-            errors.parity = true;
-        } else {
-            return None;
-        };
-        Some(errors)
-    }
-
-    #[inline]
-    pub fn clear_fifo(&self) {
-        self.0.fifo_clr().write(|w| w.rxfifo().set_bit());
-    }
-
-    #[inline]
-    pub fn enable(&mut self) {
-        self.0.enable().modify(|_, w| w.rxenable().set_bit());
-    }
-
-    #[inline]
-    pub fn disable(&mut self) {
-        self.0.enable().modify(|_, w| w.rxenable().clear_bit());
-    }
-
-    #[inline]
-    pub fn disable_interrupts(&mut self) {
-        disable_rx_interrupts(unsafe { Uart::reg_block() });
-    }
-    #[inline]
-    pub fn enable_interrupts(&mut self) {
-        enable_rx_interrupts(unsafe { Uart::reg_block() });
-    }
-
-    /// Low level function to read a word from the UART FIFO.
-    ///
-    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
-    ///
-    /// Please note that you might have to mask the returned value with 0xff to retrieve the actual
-    /// value if you use the manual parity mode. See chapter 11.4.1 for more information.
-    #[inline(always)]
-    pub fn read_fifo(&self) -> nb::Result<u32, Infallible> {
-        if self.0.rxstatus().read().rdavl().bit_is_clear() {
-            return Err(nb::Error::WouldBlock);
-        }
-        Ok(self.read_fifo_unchecked())
-    }
-
-    /// Low level function to read a word from from the UART FIFO.
-    ///
-    /// This does not necesarily mean there is a word in the FIFO available.
-    /// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
-    /// API.
-    ///
-    /// Please note that you might have to mask the returned value with 0xff to retrieve the actual
-    /// value if you use the manual parity mode. See chapter 11.4.1 for more information.
-    #[inline(always)]
-    pub fn read_fifo_unchecked(&self) -> u32 {
-        self.0.data().read().bits()
-    }
-
-    pub fn into_rx_with_irq(self) -> RxWithInterrupt<Uart> {
-        RxWithInterrupt(self)
-    }
-
-    pub fn release(self) -> Uart {
-        self.0
-    }
-}
-
-impl<Uart> embedded_io::ErrorType for Rx<Uart> {
-    type Error = Infallible;
-}
-
-impl<Uart> embedded_hal_nb::serial::ErrorType for Rx<Uart> {
-    type Error = Infallible;
-}
-
-impl<Uart: Instance> embedded_hal_nb::serial::Read<u8> for Rx<Uart> {
-    fn read(&mut self) -> nb::Result<u8, Self::Error> {
-        self.read_fifo().map(|val| (val & 0xff) as u8).map_err(|e| {
-            if let nb::Error::Other(_) = e {
-                unreachable!()
-            }
-            nb::Error::WouldBlock
-        })
-    }
-}
-
-impl<Uart: Instance> embedded_io::Read for Rx<Uart> {
-    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
-        if buf.is_empty() {
-            return Ok(0);
-        }
-        let mut read = 0;
-        loop {
-            if self.0.rxstatus().read().rdavl().bit_is_set() {
-                break;
-            }
-        }
-        for byte in buf.iter_mut() {
-            match <Self as embedded_hal_nb::serial::Read<u8>>::read(self) {
-                Ok(w) => {
-                    *byte = w;
-                    read += 1;
-                }
-                Err(nb::Error::WouldBlock) => break,
-            }
-        }
-
-        Ok(read)
-    }
-}
-
-#[inline(always)]
-pub fn enable_tx(uart: &uart_base::RegisterBlock) {
-    uart.enable().modify(|_, w| w.txenable().set_bit());
-}
-
-#[inline(always)]
-pub fn disable_tx(uart: &uart_base::RegisterBlock) {
-    uart.enable().modify(|_, w| w.txenable().clear_bit());
-}
-
-#[inline(always)]
-pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) {
-    uart.irq_enb().modify(|_, w| {
-        w.irq_tx().set_bit();
-        w.irq_tx_status().set_bit();
-        w.irq_tx_empty().set_bit()
-    });
-}
-
-#[inline(always)]
-pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) {
-    uart.irq_enb().modify(|_, w| {
-        w.irq_tx().clear_bit();
-        w.irq_tx_status().clear_bit();
-        w.irq_tx_empty().clear_bit()
-    });
-}
-
-/// Serial transmitter
-///
-/// Can be created by using the [Uart::split] or [UartBase::split] API.
-pub struct Tx<Uart>(Uart);
-
-impl<Uart: Instance> Tx<Uart> {
-    /// Retrieve a TX pin without expecting an explicit UART structure
-    ///
-    /// # Safety
-    ///
-    /// Circumvents the HAL safety guarantees.
-    #[inline(always)]
-    pub unsafe fn steal() -> Self {
-        Self(Uart::steal())
-    }
-
-    #[inline(always)]
-    fn new(uart: Uart) -> Self {
-        Self(uart)
-    }
-
-    /// Direct access to the peripheral structure.
-    ///
-    /// # Safety
-    ///
-    /// You must ensure that only registers related to the operation of the TX side are used.
-    #[inline(always)]
-    pub const unsafe fn uart(&self) -> &Uart {
-        &self.0
-    }
-
-    #[inline]
-    pub fn clear_fifo(&self) {
-        self.0.fifo_clr().write(|w| w.txfifo().set_bit());
-    }
-
-    #[inline]
-    pub fn enable(&mut self) {
-        self.0.enable().modify(|_, w| w.txenable().set_bit());
-    }
-
-    #[inline]
-    pub fn disable(&mut self) {
-        self.0.enable().modify(|_, w| w.txenable().clear_bit());
-    }
-
-    /// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
-    ///
-    /// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty.
-    /// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow
-    /// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal
-    ///   is 0
-    #[inline]
-    pub fn enable_interrupts(&self) {
-        // Safety: We own the UART structure
-        enable_tx_interrupts(unsafe { Uart::reg_block() });
-    }
-
-    /// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
-    ///
-    /// [Self::enable_interrupts] documents the interrupts.
-    #[inline]
-    pub fn disable_interrupts(&self) {
-        // Safety: We own the UART structure
-        disable_tx_interrupts(unsafe { Uart::reg_block() });
-    }
-
-    /// Low level function to write a word to the UART FIFO.
-    ///
-    /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
-    ///
-    /// Please note that you might have to mask the returned value with 0xff to retrieve the actual
-    /// value if you use the manual parity mode. See chapter 11.4.1 for more information.
-    #[inline(always)]
-    pub fn write_fifo(&self, data: u32) -> nb::Result<(), Infallible> {
-        if self.0.txstatus().read().wrrdy().bit_is_clear() {
-            return Err(nb::Error::WouldBlock);
-        }
-        self.write_fifo_unchecked(data);
-        Ok(())
-    }
-
-    /// Low level function to write a word to the UART FIFO.
-    ///
-    /// This does not necesarily mean that the FIFO can process another word because it might be
-    /// full.
-    /// Use the [Self::write_fifo] function to write a word to the FIFO reliably using the [nb]
-    /// API.
-    #[inline(always)]
-    pub fn write_fifo_unchecked(&self, data: u32) {
-        self.0.data().write(|w| unsafe { w.bits(data) });
-    }
-
-    #[inline]
-    pub fn into_async(self) -> TxAsync<Uart> {
-        TxAsync::new(self)
-    }
-}
-
-impl<Uart> embedded_io::ErrorType for Tx<Uart> {
-    type Error = Infallible;
-}
-
-impl<Uart> embedded_hal_nb::serial::ErrorType for Tx<Uart> {
-    type Error = Infallible;
-}
-
-impl<Uart: Instance> embedded_hal_nb::serial::Write<u8> for Tx<Uart> {
-    fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
-        self.write_fifo(word as u32)
-    }
-
-    fn flush(&mut self) -> nb::Result<(), Self::Error> {
-        // SAFETY: Only TX related registers are used.
-        let reader = unsafe { &(*Uart::ptr()) }.txstatus().read();
-        if reader.wrbusy().bit_is_set() {
-            return Err(nb::Error::WouldBlock);
-        }
-        Ok(())
-    }
-}
-
-impl<Uart: Instance> embedded_io::Write for Tx<Uart> {
-    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
-        if buf.is_empty() {
-            return Ok(0);
-        }
-        loop {
-            if self.0.txstatus().read().wrrdy().bit_is_set() {
-                break;
-            }
-        }
-        let mut written = 0;
-        for byte in buf.iter() {
-            match <Self as embedded_hal_nb::serial::Write<u8>>::write(self, *byte) {
-                Ok(_) => written += 1,
-                Err(nb::Error::WouldBlock) => return Ok(written),
-            }
-        }
-        Ok(written)
-    }
-
-    fn flush(&mut self) -> Result<(), Self::Error> {
-        nb::block!(<Self as embedded_hal_nb::serial::Write<u8>>::flush(self))
-    }
-}
-
-/// Serial receiver, using interrupts to offload reading to the hardware.
-///
-/// You can use [Rx::into_rx_with_irq] to convert a normal [Rx] structure into this structure.
-/// This structure provides two distinct ways to read the UART RX using interrupts. It should
-/// be noted that the interrupt service routine (ISR) still has to be provided by the user. However,
-/// this structure provides API calls which can be used inside the ISRs to simplify the reading
-/// of the UART.
-///
-///  1. The first way simply empties the FIFO on an interrupt into a user provided buffer. You
-///     can simply use [Self::start] to prepare the peripheral and then call the
-///     [Self::irq_handler] in the interrupt service routine.
-///  2. The second way reads packets bounded by a maximum size or a baudtick based timeout. You
-///     can use [Self::read_fixed_len_or_timeout_based_using_irq] to prepare the peripheral and
-///     then call the [Self::irq_handler_max_size_or_timeout_based] in the interrupt service
-///     routine. You have to call [Self::read_fixed_len_or_timeout_based_using_irq] in the ISR to
-///     start reading the next packet.
-pub struct RxWithInterrupt<Uart>(Rx<Uart>);
-
-impl<Uart: Instance> RxWithInterrupt<Uart> {
-    /// This function should be called once at initialization time if the regular
-    /// [Self::irq_handler] is used to read the UART receiver to enable and start the receiver.
-    pub fn start(&mut self) {
-        self.0.enable();
-        self.enable_rx_irq_sources(true);
-        unsafe { enable_nvic_interrupt(Uart::IRQ_RX) };
-    }
-
-    #[inline(always)]
-    pub fn uart(&self) -> &Uart {
-        &self.0 .0
-    }
-
-    /// This function is used together with the [Self::irq_handler_max_size_or_timeout_based]
-    /// function to read packets with a maximum size or variable sized packets by using the
-    /// receive timeout of the hardware.
-    ///
-    /// This function should be called once at initialization to initiate the context state
-    /// and to [Self::start] the receiver. After that, it should be called after each
-    /// completed [Self::irq_handler_max_size_or_timeout_based] call to restart the reception
-    /// of a packet.
-    pub fn read_fixed_len_or_timeout_based_using_irq(
-        &mut self,
-        context: &mut IrqContextTimeoutOrMaxSize,
-    ) -> Result<(), TransferPendingError> {
-        if context.mode != IrqReceptionMode::Idle {
-            return Err(TransferPendingError);
-        }
-        context.mode = IrqReceptionMode::Pending;
-        context.rx_idx = 0;
-        self.start();
-        Ok(())
-    }
-
-    #[inline]
-    fn enable_rx_irq_sources(&mut self, timeout: bool) {
-        self.uart().irq_enb().modify(|_, w| {
-            if timeout {
-                w.irq_rx_to().set_bit();
-            }
-            w.irq_rx_status().set_bit();
-            w.irq_rx().set_bit()
-        });
-    }
-
-    #[inline]
-    fn disable_rx_irq_sources(&mut self) {
-        self.uart().irq_enb().modify(|_, w| {
-            w.irq_rx_to().clear_bit();
-            w.irq_rx_status().clear_bit();
-            w.irq_rx().clear_bit()
-        });
-    }
-
-    pub fn cancel_transfer(&mut self) {
-        self.disable_rx_irq_sources();
-        self.0.clear_fifo();
-    }
-
-    /// This function should be called in the user provided UART interrupt handler.
-    ///
-    /// It simply empties any bytes in the FIFO into the user provided buffer and returns the
-    /// result of the operation.
-    ///
-    /// This function will not disable the RX interrupts, so you don't need to call any other
-    /// API after calling this function to continue emptying the FIFO. RX errors are handled
-    /// as partial errors and are returned as part of the [IrqResult].
-    pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> IrqResult {
-        let mut result = IrqResult::default();
-
-        let irq_end = self.uart().irq_end().read();
-        let enb_status = self.uart().enable().read();
-        let rx_enabled = enb_status.rxenable().bit_is_set();
-
-        // Half-Full interrupt. We have a guaranteed amount of data we can read.
-        if irq_end.irq_rx().bit_is_set() {
-            let available_bytes = self.uart().rxfifoirqtrg().read().bits() as usize;
-
-            // If this interrupt bit is set, the trigger level is available at the very least.
-            // Read everything as fast as possible
-            for _ in 0..available_bytes {
-                buf[result.bytes_read] = (self.uart().data().read().bits() & 0xff) as u8;
-                result.bytes_read += 1;
-            }
-        }
-
-        // Timeout, empty the FIFO completely.
-        if irq_end.irq_rx_to().bit_is_set() {
-            // While there is data in the FIFO, write it into the reception buffer
-            while let Ok(byte) = self.0.read_fifo() {
-                buf[result.bytes_read] = byte as u8;
-                result.bytes_read += 1;
-            }
-        }
-
-        // RX transfer not complete, check for RX errors
-        if rx_enabled {
-            self.check_for_errors(&mut result.errors);
-        }
-
-        // Clear the interrupt status bits
-        self.uart()
-            .irq_clr()
-            .write(|w| unsafe { w.bits(irq_end.bits()) });
-        result
-    }
-
-    /// This function should be called in the user provided UART interrupt handler.
-    ///
-    /// This function is used to read packets which either have a maximum size or variable sized
-    /// packet which are bounded by sufficient delays between them, triggering a hardware timeout.
-    ///
-    /// If either the maximum number of packets have been read or a timeout occured, the transfer
-    /// will be deemed completed. The state information of the transfer is tracked in the
-    /// [IrqContextTimeoutOrMaxSize] structure.
-    ///
-    /// If passed buffer is equal to or larger than the specified maximum length, an
-    /// [BufferTooShortError] will be returned. Other RX errors are treated as partial errors
-    /// and returned inside the [IrqResultMaxSizeOrTimeout] structure.
-    pub fn on_interrupt_max_size_or_timeout_based(
-        &mut self,
-        context: &mut IrqContextTimeoutOrMaxSize,
-        buf: &mut [u8],
-    ) -> Result<IrqResultMaxSizeOrTimeout, BufferTooShortError> {
-        if buf.len() < context.max_len {
-            return Err(BufferTooShortError {
-                found: buf.len(),
-                expected: context.max_len,
-            });
-        }
-        let mut result = IrqResultMaxSizeOrTimeout::default();
-
-        let irq_end = self.uart().irq_end().read();
-        let enb_status = self.uart().enable().read();
-        let rx_enabled = enb_status.rxenable().bit_is_set();
-
-        // Half-Full interrupt. We have a guaranteed amount of data we can read.
-        if irq_end.irq_rx().bit_is_set() {
-            // Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO.
-            // We use this trick/hack because the timeout feature of the peripheral relies on data
-            // being in the RX FIFO. If data continues arriving, another half-full IRQ will fire.
-            // If not, the last byte(s) is/are emptied by the timeout interrupt.
-            let available_bytes = self.uart().rxfifoirqtrg().read().bits() as usize;
-
-            let bytes_to_read = core::cmp::min(
-                available_bytes.saturating_sub(1),
-                context.max_len - context.rx_idx,
-            );
-
-            // If this interrupt bit is set, the trigger level is available at the very least.
-            // Read everything as fast as possible
-            for _ in 0..bytes_to_read {
-                buf[context.rx_idx] = (self.uart().data().read().bits() & 0xff) as u8;
-                context.rx_idx += 1;
-            }
-
-            // On high-baudrates, data might be available immediately, and we possible have to
-            // read continuosly? Then again, the CPU should always be faster than that. I'd rather
-            // rely on the hardware firing another IRQ. I have not tried baudrates higher than
-            // 115200 so far.
-        }
-        // Timeout, empty the FIFO completely.
-        if irq_end.irq_rx_to().bit_is_set() {
-            // While there is data in the FIFO, write it into the reception buffer
-            loop {
-                if context.rx_idx == context.max_len {
-                    break;
-                }
-                // While there is data in the FIFO, write it into the reception buffer
-                match self.0.read() {
-                    Ok(byte) => {
-                        buf[result.bytes_read] = byte;
-                        result.bytes_read += 1;
-                    }
-                    Err(_) => break,
-                }
-            }
-            self.irq_completion_handler_max_size_timeout(&mut result, context);
-            return Ok(result);
-        }
-
-        // RX transfer not complete, check for RX errors
-        if (context.rx_idx < context.max_len) && rx_enabled {
-            self.check_for_errors(&mut result.errors);
-        }
-
-        // Clear the interrupt status bits
-        self.uart()
-            .irq_clr()
-            .write(|w| unsafe { w.bits(irq_end.bits()) });
-        Ok(result)
-    }
-
-    fn check_for_errors(&self, errors: &mut Option<UartErrors>) {
-        let rx_status = self.uart().rxstatus().read();
-
-        if rx_status.rxovr().bit_is_set()
-            || rx_status.rxfrm().bit_is_set()
-            || rx_status.rxpar().bit_is_set()
-        {
-            let err = errors.get_or_insert(UartErrors::default());
-
-            if rx_status.rxovr().bit_is_set() {
-                err.overflow = true;
-            }
-            if rx_status.rxfrm().bit_is_set() {
-                err.framing = true;
-            }
-            if rx_status.rxpar().bit_is_set() {
-                err.parity = true;
-            }
-        }
-    }
-
-    fn irq_completion_handler_max_size_timeout(
-        &mut self,
-        res: &mut IrqResultMaxSizeOrTimeout,
-        context: &mut IrqContextTimeoutOrMaxSize,
-    ) {
-        self.disable_rx_irq_sources();
-        self.0.disable();
-        res.bytes_read = context.rx_idx;
-        res.complete = true;
-        context.mode = IrqReceptionMode::Idle;
-        context.rx_idx = 0;
-    }
-
-    /// # Safety
-    ///
-    /// This API allows creating multiple UART instances when releasing the TX structure as well.
-    /// The user must ensure that these instances are not used to create multiple overlapping
-    /// UART drivers.
-    pub unsafe fn release(self) -> Uart {
-        self.0.release()
-    }
-}
-
-pub mod tx_asynch;
-pub use tx_asynch::*;
-
-pub mod rx_asynch;
-pub use rx_asynch::*;
+//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/rtic/src/bin/uart-echo-rtic.rs)
+//! - [Flashloader exposing a CCSDS interface via UART](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
+//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
+//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
+pub use vorago_shared_periphs::uart::*;
diff --git a/va416xx-hal/src/uart/rx_asynch.rs b/va416xx-hal/src/uart/rx_asynch.rs
deleted file mode 100644
index 5e409b5..0000000
--- a/va416xx-hal/src/uart/rx_asynch.rs
+++ /dev/null
@@ -1,448 +0,0 @@
-//! # Async UART reception functionality for the VA416xx family.
-//!
-//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the
-//! [embedded_io_async::Read] trait.
-//! This trait allows for asynchronous reception of data streams. Please note that this module does
-//! not specify/declare the interrupt handlers which must be provided for async support to work.
-//! However, it provides two interrupt handlers:
-//!
-//! - [on_interrupt_rx]
-//! - [on_interrupt_rx_overwriting]
-//!
-//! The first two are used for the [RxAsync] struct, while the latter two are used with the
-//! [RxAsyncOverwriting] struct. The later two will overwrite old values in the used ring buffer.
-//!
-//! Error handling is performed in the user interrupt handler by checking the [AsyncUartErrors]
-//! structure returned by the interrupt handlers.
-//!
-//! # Example
-//!
-//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs)
-use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering};
-
-use critical_section::Mutex;
-use embassy_sync::waitqueue::AtomicWaker;
-use embedded_io::ErrorType;
-use portable_atomic::AtomicBool;
-use va416xx::uart0 as uart_base;
-
-use crate::enable_nvic_interrupt;
-
-use super::{Bank, Instance, Rx, UartErrors};
-
-static UART_RX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
-static RX_READ_ACTIVE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
-static RX_HAS_DATA: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
-
-struct RxFuture {
-    uart_idx: usize,
-}
-
-impl RxFuture {
-    pub fn new<Uart: Instance>(_rx: &mut Rx<Uart>) -> Self {
-        RX_READ_ACTIVE[Uart::IDX as usize].store(true, Ordering::Relaxed);
-        Self {
-            uart_idx: Uart::IDX as usize,
-        }
-    }
-}
-
-impl Future for RxFuture {
-    type Output = Result<(), Infallible>;
-
-    fn poll(
-        self: core::pin::Pin<&mut Self>,
-        cx: &mut core::task::Context<'_>,
-    ) -> core::task::Poll<Self::Output> {
-        UART_RX_WAKERS[self.uart_idx].register(cx.waker());
-        if RX_HAS_DATA[self.uart_idx].load(Ordering::Relaxed) {
-            return core::task::Poll::Ready(Ok(()));
-        }
-        core::task::Poll::Pending
-    }
-}
-
-#[derive(Debug, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct AsyncUartErrors {
-    /// Queue has overflowed, data might have been lost.
-    pub queue_overflow: bool,
-    /// UART errors.
-    pub uart_errors: UartErrors,
-}
-
-fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option<UartErrors> {
-    let rx_status = uart.rxstatus().read();
-    if rx_status.rxovr().bit_is_set()
-        || rx_status.rxfrm().bit_is_set()
-        || rx_status.rxpar().bit_is_set()
-    {
-        let mut errors_val = UartErrors::default();
-
-        if rx_status.rxovr().bit_is_set() {
-            errors_val.overflow = true;
-        }
-        if rx_status.rxfrm().bit_is_set() {
-            errors_val.framing = true;
-        }
-        if rx_status.rxpar().bit_is_set() {
-            errors_val.parity = true;
-        }
-        return Some(errors_val);
-    }
-    None
-}
-
-fn on_interrupt_rx_common_post_processing(
-    bank: Bank,
-    rx_enabled: bool,
-    read_some_data: bool,
-    irq_end: u32,
-) -> Option<UartErrors> {
-    let idx = bank as usize;
-    if read_some_data {
-        RX_HAS_DATA[idx].store(true, Ordering::Relaxed);
-        if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) {
-            UART_RX_WAKERS[idx].wake();
-        }
-    }
-
-    let mut errors = None;
-    let uart_regs = unsafe { bank.reg_block() };
-    // Check for RX errors
-    if rx_enabled {
-        errors = on_interrupt_handle_rx_errors(uart_regs);
-    }
-
-    // Clear the interrupt status bits
-    uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) });
-    errors
-}
-
-/// Interrupt handler with overwriting behaviour when the ring buffer is full.
-///
-/// Should be called in the user interrupt handler to enable
-/// asynchronous reception. This variant will overwrite old data in the ring buffer in case
-/// the ring buffer is full.
-pub fn on_interrupt_rx_overwriting<const N: usize>(
-    bank: Bank,
-    prod: &mut heapless::spsc::Producer<u8, N>,
-    shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
-) -> Result<(), AsyncUartErrors> {
-    on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer)
-}
-
-pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
-    bank: Bank,
-    prod: &mut heapless::spsc::Producer<u8, N>,
-    shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
-) -> Result<(), AsyncUartErrors> {
-    let uart_regs = unsafe { bank.reg_block() };
-    let irq_end = uart_regs.irq_end().read();
-    let enb_status = uart_regs.enable().read();
-    let rx_enabled = enb_status.rxenable().bit_is_set();
-    let mut read_some_data = false;
-    let mut queue_overflow = false;
-
-    // Half-Full interrupt. We have a guaranteed amount of data we can read.
-    if irq_end.irq_rx().bit_is_set() {
-        let available_bytes = uart_regs.rxfifoirqtrg().read().bits() as usize;
-
-        // If this interrupt bit is set, the trigger level is available at the very least.
-        // Read everything as fast as possible
-        for _ in 0..available_bytes {
-            let byte = uart_regs.data().read().bits();
-            if !prod.ready() {
-                queue_overflow = true;
-                critical_section::with(|cs| {
-                    let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
-                    cons_ref.as_mut().unwrap().dequeue();
-                });
-            }
-            prod.enqueue(byte as u8).ok();
-        }
-        read_some_data = true;
-    }
-
-    // Timeout, empty the FIFO completely.
-    if irq_end.irq_rx_to().bit_is_set() {
-        while uart_regs.rxstatus().read().rdavl().bit_is_set() {
-            // While there is data in the FIFO, write it into the reception buffer
-            let byte = uart_regs.data().read().bits();
-            if !prod.ready() {
-                queue_overflow = true;
-                critical_section::with(|cs| {
-                    let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
-                    cons_ref.as_mut().unwrap().dequeue();
-                });
-            }
-            prod.enqueue(byte as u8).ok();
-        }
-        read_some_data = true;
-    }
-
-    let uart_errors =
-        on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
-    if uart_errors.is_some() || queue_overflow {
-        return Err(AsyncUartErrors {
-            queue_overflow,
-            uart_errors: uart_errors.unwrap_or_default(),
-        });
-    }
-    Ok(())
-}
-
-/// Interrupt handler for asynchronous RX operations.
-///
-/// Should be called in the user interrupt handler to enable asynchronous reception.
-pub fn on_interrupt_rx<const N: usize>(
-    bank: Bank,
-    prod: &mut heapless::spsc::Producer<'_, u8, N>,
-) -> Result<(), AsyncUartErrors> {
-    on_interrupt_rx_async_heapless_queue(bank, prod)
-}
-
-pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
-    bank: Bank,
-    prod: &mut heapless::spsc::Producer<'_, u8, N>,
-) -> Result<(), AsyncUartErrors> {
-    let uart = unsafe { bank.reg_block() };
-    let irq_end = uart.irq_end().read();
-    let enb_status = uart.enable().read();
-    let rx_enabled = enb_status.rxenable().bit_is_set();
-    let mut read_some_data = false;
-    let mut queue_overflow = false;
-
-    // Half-Full interrupt. We have a guaranteed amount of data we can read.
-    if irq_end.irq_rx().bit_is_set() {
-        let available_bytes = uart.rxfifoirqtrg().read().bits() as usize;
-
-        // If this interrupt bit is set, the trigger level is available at the very least.
-        // Read everything as fast as possible
-        for _ in 0..available_bytes {
-            let byte = uart.data().read().bits();
-            if !prod.ready() {
-                queue_overflow = true;
-            }
-            prod.enqueue(byte as u8).ok();
-        }
-        read_some_data = true;
-    }
-
-    // Timeout, empty the FIFO completely.
-    if irq_end.irq_rx_to().bit_is_set() {
-        while uart.rxstatus().read().rdavl().bit_is_set() {
-            // While there is data in the FIFO, write it into the reception buffer
-            let byte = uart.data().read().bits();
-            if !prod.ready() {
-                queue_overflow = true;
-            }
-            prod.enqueue(byte as u8).ok();
-        }
-        read_some_data = true;
-    }
-
-    let uart_errors =
-        on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits());
-    if uart_errors.is_some() || queue_overflow {
-        return Err(AsyncUartErrors {
-            queue_overflow,
-            uart_errors: uart_errors.unwrap_or_default(),
-        });
-    }
-    Ok(())
-}
-
-struct ActiveReadGuard(usize);
-
-impl Drop for ActiveReadGuard {
-    fn drop(&mut self) {
-        RX_READ_ACTIVE[self.0].store(false, Ordering::Relaxed);
-    }
-}
-
-struct RxAsyncInner<Uart: Instance, const N: usize> {
-    rx: Rx<Uart>,
-    pub queue: heapless::spsc::Consumer<'static, u8, N>,
-}
-
-/// Core data structure to allow asynchronous UART reception.
-///
-/// If the ring buffer becomes full, data will be lost.
-pub struct RxAsync<Uart: Instance, const N: usize>(Option<RxAsyncInner<Uart, N>>);
-
-impl<Uart: Instance, const N: usize> ErrorType for RxAsync<Uart, N> {
-    /// Error reporting is done using the result of the interrupt functions.
-    type Error = Infallible;
-}
-
-fn stop_async_rx<Uart: Instance>(rx: &mut Rx<Uart>) {
-    rx.disable_interrupts();
-    rx.disable();
-    unsafe {
-        enable_nvic_interrupt(Uart::IRQ_RX);
-    }
-    rx.clear_fifo();
-}
-
-impl<Uart: Instance, const N: usize> RxAsync<Uart, N> {
-    /// Create a new asynchronous receiver.
-    ///
-    /// The passed [heapless::spsc::Consumer] will be used to asynchronously receive data which
-    /// is filled by the interrupt handler [on_interrupt_rx].
-    pub fn new(mut rx: Rx<Uart>, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
-        rx.disable_interrupts();
-        rx.disable();
-        rx.clear_fifo();
-        // Enable those together.
-        critical_section::with(|_| {
-            unsafe {
-                enable_nvic_interrupt(Uart::IRQ_RX);
-            }
-            rx.enable_interrupts();
-            rx.enable();
-        });
-        Self(Some(RxAsyncInner { rx, queue }))
-    }
-
-    pub fn stop(&mut self) {
-        stop_async_rx(&mut self.0.as_mut().unwrap().rx);
-    }
-
-    pub fn release(mut self) -> (Rx<Uart>, heapless::spsc::Consumer<'static, u8, N>) {
-        self.stop();
-        let inner = self.0.take().unwrap();
-        (inner.rx, inner.queue)
-    }
-}
-
-impl<Uart: Instance, const N: usize> Drop for RxAsync<Uart, N> {
-    fn drop(&mut self) {
-        self.stop();
-    }
-}
-
-impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsync<Uart, N> {
-    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
-        // Need to wait for the IRQ to read data and set this flag. If the queue is not
-        // empty, we can read data immediately.
-        if self.0.as_ref().unwrap().queue.len() == 0 {
-            RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
-        }
-        let _guard = ActiveReadGuard(Uart::IDX as usize);
-        let mut handle_data_in_queue = |consumer: &mut heapless::spsc::Consumer<'static, u8, N>| {
-            let data_to_read = consumer.len().min(buf.len());
-            for byte in buf.iter_mut().take(data_to_read) {
-                // We own the consumer and we checked that the amount of data is guaranteed to be available.
-                *byte = unsafe { consumer.dequeue_unchecked() };
-            }
-            data_to_read
-        };
-        let mut_ref = self.0.as_mut().unwrap();
-        let fut = RxFuture::new(&mut mut_ref.rx);
-        // Data is available, so read that data immediately.
-        let read_data = handle_data_in_queue(&mut mut_ref.queue);
-        if read_data > 0 {
-            return Ok(read_data);
-        }
-        // Await data.
-        let _ = fut.await;
-        Ok(handle_data_in_queue(&mut mut_ref.queue))
-    }
-}
-
-struct RxAsyncOverwritingInner<Uart: Instance, const N: usize> {
-    rx: Rx<Uart>,
-    pub shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
-}
-
-/// Core data structure to allow asynchronous UART reception.
-///
-/// If the ring buffer becomes full, the oldest data will be overwritten when using the
-/// [on_interrupt_rx_overwriting] interrupt handlers.
-pub struct RxAsyncOverwriting<Uart: Instance, const N: usize>(
-    Option<RxAsyncOverwritingInner<Uart, N>>,
-);
-
-impl<Uart: Instance, const N: usize> ErrorType for RxAsyncOverwriting<Uart, N> {
-    /// Error reporting is done using the result of the interrupt functions.
-    type Error = Infallible;
-}
-
-impl<Uart: Instance, const N: usize> RxAsyncOverwriting<Uart, N> {
-    /// Create a new asynchronous receiver.
-    ///
-    /// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data
-    /// which is filled by the interrupt handler. The shared property allows using it in the
-    /// interrupt handler to overwrite old data.
-    pub fn new(
-        mut rx: Rx<Uart>,
-        shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
-    ) -> Self {
-        rx.disable_interrupts();
-        rx.disable();
-        rx.clear_fifo();
-        // Enable those together.
-        critical_section::with(|_| {
-            rx.enable_interrupts();
-            rx.enable();
-        });
-        Self(Some(RxAsyncOverwritingInner {
-            rx,
-            shared_consumer,
-        }))
-    }
-
-    pub fn stop(&mut self) {
-        stop_async_rx(&mut self.0.as_mut().unwrap().rx);
-    }
-
-    pub fn release(mut self) -> Rx<Uart> {
-        self.stop();
-        let inner = self.0.take().unwrap();
-        inner.rx
-    }
-}
-
-impl<Uart: Instance, const N: usize> Drop for RxAsyncOverwriting<Uart, N> {
-    fn drop(&mut self) {
-        self.stop();
-    }
-}
-
-impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncOverwriting<Uart, N> {
-    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
-        // Need to wait for the IRQ to read data and set this flag. If the queue is not
-        // empty, we can read data immediately.
-
-        critical_section::with(|cs| {
-            let queue = self.0.as_ref().unwrap().shared_consumer.borrow(cs);
-            if queue.borrow().as_ref().unwrap().len() == 0 {
-                RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed);
-            }
-        });
-        let _guard = ActiveReadGuard(Uart::IDX as usize);
-        let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner<Uart, N>| {
-            critical_section::with(|cs| {
-                let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut();
-                let consumer = consumer_ref.as_mut().unwrap();
-                let data_to_read = consumer.len().min(buf.len());
-                for byte in buf.iter_mut().take(data_to_read) {
-                    // We own the consumer and we checked that the amount of data is guaranteed to be available.
-                    *byte = unsafe { consumer.dequeue_unchecked() };
-                }
-                data_to_read
-            })
-        };
-        let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx);
-        // Data is available, so read that data immediately.
-        let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
-        if read_data > 0 {
-            return Ok(read_data);
-        }
-        // Await data.
-        let _ = fut.await;
-        let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
-        Ok(read_data)
-    }
-}
diff --git a/va416xx-hal/src/uart/tx_asynch.rs b/va416xx-hal/src/uart/tx_asynch.rs
deleted file mode 100644
index a1adb8d..0000000
--- a/va416xx-hal/src/uart/tx_asynch.rs
+++ /dev/null
@@ -1,263 +0,0 @@
-//! # Async UART transmission functionality for the VA416xx family.
-//!
-//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait.
-//! This trait allows for asynchronous sending of data streams. Please note that this module does
-//! not specify/declare the interrupt handlers which must be provided for async support to work.
-//! However, it the [on_interrupt_tx] interrupt handler.
-//!
-//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
-//! for a given UART bank.
-//!
-//! # Example
-//!
-//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
-use core::{cell::RefCell, future::Future};
-
-use critical_section::Mutex;
-use embassy_sync::waitqueue::AtomicWaker;
-use embedded_io_async::Write;
-use portable_atomic::AtomicBool;
-
-use super::*;
-
-static UART_TX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
-static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 3] =
-    [const { Mutex::new(RefCell::new(TxContext::new())) }; 3];
-// Completion flag. Kept outside of the context structure as an atomic to avoid
-// critical section.
-static TX_DONE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
-
-/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
-/// UART bank.
-///
-/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
-/// the given UART bank.
-pub fn on_interrupt_tx(bank: Bank) {
-    let uart = unsafe { bank.reg_block() };
-    let idx = bank as usize;
-    let irq_enb = uart.irq_enb().read();
-    // IRQ is not related to TX.
-    if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() {
-        return;
-    }
-
-    let tx_status = uart.txstatus().read();
-    let unexpected_overrun = tx_status.wrlost().bit_is_set();
-    let mut context = critical_section::with(|cs| {
-        let context_ref = TX_CONTEXTS[idx].borrow(cs);
-        *context_ref.borrow()
-    });
-    context.tx_overrun = unexpected_overrun;
-    if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() {
-        uart.irq_enb().modify(|_, w| {
-            w.irq_tx().clear_bit();
-            w.irq_tx_empty().clear_bit();
-            w.irq_tx_status().clear_bit()
-        });
-        uart.enable().modify(|_, w| w.txenable().clear_bit());
-        // Write back updated context structure.
-        critical_section::with(|cs| {
-            let context_ref = TX_CONTEXTS[idx].borrow(cs);
-            *context_ref.borrow_mut() = context;
-        });
-        // Transfer is done.
-        TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
-        UART_TX_WAKERS[idx].wake();
-        return;
-    }
-    // Safety: We documented that the user provided slice must outlive the future, so we convert
-    // the raw pointer back to the slice here.
-    let slice = unsafe { core::slice::from_raw_parts(context.slice.data, context.slice.len) };
-    while context.progress < context.slice.len {
-        let wrrdy = uart.txstatus().read().wrrdy().bit_is_set();
-        if !wrrdy {
-            break;
-        }
-        // Safety: TX structure is owned by the future which does not write into the the data
-        // register, so we can assume we are the only one writing to the data register.
-        uart.data()
-            .write(|w| unsafe { w.bits(slice[context.progress] as u32) });
-        context.progress += 1;
-    }
-
-    // Write back updated context structure.
-    critical_section::with(|cs| {
-        let context_ref = TX_CONTEXTS[idx].borrow(cs);
-        *context_ref.borrow_mut() = context;
-    });
-}
-
-#[derive(Debug, Copy, Clone)]
-pub struct TxContext {
-    progress: usize,
-    tx_overrun: bool,
-    slice: RawBufSlice,
-}
-
-#[allow(clippy::new_without_default)]
-impl TxContext {
-    pub const fn new() -> Self {
-        Self {
-            progress: 0,
-            tx_overrun: false,
-            slice: RawBufSlice::new_empty(),
-        }
-    }
-}
-
-#[derive(Debug, Copy, Clone)]
-struct RawBufSlice {
-    data: *const u8,
-    len: usize,
-}
-
-/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
-unsafe impl Send for RawBufSlice {}
-
-impl RawBufSlice {
-    /// # Safety
-    ///
-    /// This function stores the raw pointer of the passed data slice. The user MUST ensure
-    /// that the slice outlives the data structure.
-    #[allow(dead_code)]
-    const unsafe fn new(data: &[u8]) -> Self {
-        Self {
-            data: data.as_ptr(),
-            len: data.len(),
-        }
-    }
-
-    const fn new_empty() -> Self {
-        Self {
-            data: core::ptr::null(),
-            len: 0,
-        }
-    }
-
-    /// # Safety
-    ///
-    /// This function stores the raw pointer of the passed data slice. The user MUST ensure
-    /// that the slice outlives the data structure.
-    pub unsafe fn set(&mut self, data: &[u8]) {
-        self.data = data.as_ptr();
-        self.len = data.len();
-    }
-}
-
-pub struct TxFuture {
-    uart_idx: usize,
-}
-
-impl TxFuture {
-    /// # Safety
-    ///
-    /// This function stores the raw pointer of the passed data slice. The user MUST ensure
-    /// that the slice outlives the data structure.
-    pub unsafe fn new<Uart: Instance>(tx: &mut Tx<Uart>, data: &[u8]) -> Self {
-        TX_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed);
-        tx.disable_interrupts();
-        tx.disable();
-        tx.clear_fifo();
-
-        let uart_tx = unsafe { tx.uart() };
-        let init_fill_count = core::cmp::min(data.len(), 16);
-        // We fill the FIFO.
-        for data in data.iter().take(init_fill_count) {
-            uart_tx.data().write(|w| unsafe { w.bits(*data as u32) });
-        }
-        critical_section::with(|cs| {
-            let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
-            let mut context = context_ref.borrow_mut();
-            context.slice.set(data);
-            context.progress = init_fill_count;
-
-            // Ensure those are enabled inside a critical section at the same time. Can lead to
-            // weird glitches otherwise.
-            tx.enable_interrupts();
-            tx.enable();
-        });
-        Self {
-            uart_idx: Uart::IDX as usize,
-        }
-    }
-}
-
-impl Future for TxFuture {
-    type Output = Result<usize, TxOverrunError>;
-
-    fn poll(
-        self: core::pin::Pin<&mut Self>,
-        cx: &mut core::task::Context<'_>,
-    ) -> core::task::Poll<Self::Output> {
-        UART_TX_WAKERS[self.uart_idx].register(cx.waker());
-        if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
-            let progress = critical_section::with(|cs| {
-                TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
-            });
-            return core::task::Poll::Ready(Ok(progress));
-        }
-        core::task::Poll::Pending
-    }
-}
-
-impl Drop for TxFuture {
-    fn drop(&mut self) {
-        let reg_block = match self.uart_idx {
-            0 => unsafe { pac::Uart0::reg_block() },
-            1 => unsafe { pac::Uart1::reg_block() },
-            2 => unsafe { pac::Uart2::reg_block() },
-            _ => unreachable!(),
-        };
-
-        disable_tx_interrupts(reg_block);
-        disable_tx(reg_block);
-    }
-}
-
-pub struct TxAsync<Uart: Instance> {
-    tx: Tx<Uart>,
-}
-
-impl<Uart: Instance> TxAsync<Uart> {
-    /// Create a new asynchronous TX object.
-    ///
-    /// This function also enable the NVIC interrupt, but does not enable the peripheral specific
-    /// interrupts.
-    pub fn new(tx: Tx<Uart>) -> Self {
-        // Safety: We own TX now.
-        unsafe { enable_nvic_interrupt(Uart::IRQ_TX) };
-        Self { tx }
-    }
-
-    /// This function also disables the NVIC interrupt.
-    pub fn release(self) -> Tx<Uart> {
-        disable_nvic_interrupt(Uart::IRQ_TX);
-        self.tx
-    }
-}
-
-#[derive(Debug, thiserror::Error)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[error("TX overrun error")]
-pub struct TxOverrunError;
-
-impl embedded_io_async::Error for TxOverrunError {
-    fn kind(&self) -> embedded_io_async::ErrorKind {
-        embedded_io_async::ErrorKind::Other
-    }
-}
-
-impl<Uart: Instance> embedded_io::ErrorType for TxAsync<Uart> {
-    type Error = TxOverrunError;
-}
-
-impl<Uart: Instance> Write for TxAsync<Uart> {
-    /// Write a buffer asynchronously.
-    ///
-    /// This implementation is not side effect free, and a started future might have already
-    /// written part of the passed buffer.
-    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
-        let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
-        fut.await
-    }
-}
diff --git a/va416xx-hal/src/wdt.rs b/va416xx-hal/src/wdt.rs
index 24b9cf4..ec7f6da 100644
--- a/va416xx-hal/src/wdt.rs
+++ b/va416xx-hal/src/wdt.rs
@@ -3,12 +3,12 @@
 //! ## Examples
 //!
 //! - [Watchdog simple example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/wdt.rs)
-use crate::time::Hertz;
-use crate::{
-    clock::{Clocks, PeripheralSelect},
-    pac,
-    prelude::SyscfgExt,
+use vorago_shared_periphs::{
+    enable_peripheral_clock, reset_peripheral_for_cycles, PeripheralSelect,
 };
+
+use crate::time::Hertz;
+use crate::{clock::Clocks, pac};
 use crate::{disable_nvic_interrupt, enable_nvic_interrupt};
 
 pub const WDT_UNLOCK_VALUE: u32 = 0x1ACC_E551;
@@ -39,23 +39,13 @@ pub fn disable_wdt_interrupts() {
 }
 
 impl Wdt {
-    pub fn new(
-        syscfg: &mut pac::Sysconfig,
-        wdt: pac::WatchDog,
-        clocks: &Clocks,
-        wdt_freq_ms: u32,
-    ) -> Self {
-        Self::start(syscfg, wdt, clocks, wdt_freq_ms)
+    pub fn new(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
+        Self::start(wdt, clocks, wdt_freq_ms)
     }
 
-    pub fn start(
-        syscfg: &mut pac::Sysconfig,
-        wdt: pac::WatchDog,
-        clocks: &Clocks,
-        wdt_freq_ms: u32,
-    ) -> Self {
-        syscfg.enable_peripheral_clock(PeripheralSelect::Watchdog);
-        syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Watchdog);
+    pub fn start(wdt: pac::WatchDog, clocks: &Clocks, wdt_freq_ms: u32) -> Self {
+        enable_peripheral_clock(PeripheralSelect::Watchdog);
+        reset_peripheral_for_cycles(PeripheralSelect::Watchdog, 2);
 
         let wdt_clock = clocks.apb2();
         let mut wdt_ctrl = Self {
diff --git a/vorago-peb1/Cargo.toml b/vorago-peb1/Cargo.toml
index 3432575..a1921cc 100644
--- a/vorago-peb1/Cargo.toml
+++ b/vorago-peb1/Cargo.toml
@@ -16,7 +16,7 @@ cortex-m-rt = "0.7"
 embedded-hal = "1"
 lis2dh12 = { version = "0.7", features = ["out_f32"] }
 
-va416xx-hal = { version = ">=0.3, <=0.5", features = ["va41630"] }
+va416xx-hal = { version = ">=0.3, <=0.5", path = "../va416xx-hal", features = ["va41630"] }
 
 [features]
 rt = ["va416xx-hal/rt"]
diff --git a/vorago-peb1/src/lib.rs b/vorago-peb1/src/lib.rs
index 5453cec..7e9f403 100644
--- a/vorago-peb1/src/lib.rs
+++ b/vorago-peb1/src/lib.rs
@@ -19,7 +19,7 @@ pub mod accelerometer {
     };
 
     // Accelerometer located on the GPIO board.
-    pub type Accelerometer = Lis2dh12<I2cMaster<pac::I2c0>>;
+    pub type Accelerometer = Lis2dh12<I2cMaster>;
 
     #[derive(Debug)]
     pub enum ConstructorError {
@@ -30,14 +30,12 @@ pub mod accelerometer {
 
     pub fn new_with_addr_detection(
         i2c: pac::I2c0,
-        sysconfig: &mut pac::Sysconfig,
         clocks: &Clocks,
     ) -> Result<Accelerometer, ConstructorError> {
         let mut i2c_master = I2cMaster::new(
             i2c,
-            sysconfig,
-            MasterConfig::default(),
             clocks,
+            MasterConfig::default(),
             I2cSpeed::Regular100khz,
         )
         .map_err(ConstructorError::ClkError)?;
@@ -47,7 +45,7 @@ pub mod accelerometer {
     }
 
     pub fn new_with_i2cm(
-        i2c: I2cMaster<pac::I2c0>,
+        i2c: I2cMaster,
         addr: lis2dh12::SlaveAddr,
     ) -> Result<Accelerometer, lis2dh12::Error<i2c::Error>> {
         Lis2dh12::new(i2c, addr)