diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index f17c6bef6..e615c6307 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -653,9 +653,9 @@ fn main() {
                         crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false));
                         #decr_stop_refcount
                     }
-                    fn enable_bit() -> crate::rcc::ClockEnableBit {
-                        unsafe { crate::rcc::ClockEnableBit::new(#en_reg_offs, #en_bit_offs) }
-                    }
+
+                    const ENABLE_BIT: crate::rcc::ClockEnableBit =
+                        unsafe { crate::rcc::ClockEnableBit::new(#en_reg_offs, #en_bit_offs) };
                 }
 
                 impl crate::rcc::RccPeripheral for peripherals::#pname {}
diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs
index 0b2a56305..ef5fd0972 100644
--- a/embassy-stm32/src/i2c/mod.rs
+++ b/embassy-stm32/src/i2c/mod.rs
@@ -9,7 +9,7 @@ use core::future::Future;
 use core::iter;
 use core::marker::PhantomData;
 
-use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
+use embassy_hal_internal::{into_ref, Peripheral};
 use embassy_sync::waitqueue::AtomicWaker;
 #[cfg(feature = "time")]
 use embassy_time::{Duration, Instant};
@@ -18,6 +18,7 @@ use crate::dma::ChannelAndRequest;
 use crate::gpio::{AFType, Pull};
 use crate::interrupt::typelevel::Interrupt;
 use crate::mode::{Async, Blocking, Mode};
+use crate::rcc::{ClockEnableBit, SealedRccPeripheral};
 use crate::time::Hertz;
 use crate::{interrupt, peripherals};
 
@@ -72,8 +73,10 @@ impl Default for Config {
 }
 
 /// I2C driver.
-pub struct I2c<'d, T: Instance, M: Mode> {
-    _peri: PeripheralRef<'d, T>,
+pub struct I2c<'d, M: Mode> {
+    info: &'static Info,
+    state: &'static State,
+    kernel_clock: Hertz,
     tx_dma: Option<ChannelAndRequest<'d>>,
     rx_dma: Option<ChannelAndRequest<'d>>,
     #[cfg(feature = "time")]
@@ -81,9 +84,9 @@ pub struct I2c<'d, T: Instance, M: Mode> {
     _phantom: PhantomData<M>,
 }
 
-impl<'d, T: Instance> I2c<'d, T, Async> {
+impl<'d> I2c<'d, Async> {
     /// Create a new I2C driver.
-    pub fn new(
+    pub fn new<T: Instance>(
         peri: impl Peripheral<P = T> + 'd,
         scl: impl Peripheral<P = impl SclPin<T>> + 'd,
         sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
@@ -99,9 +102,9 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
     }
 }
 
-impl<'d, T: Instance> I2c<'d, T, Blocking> {
+impl<'d> I2c<'d, Blocking> {
     /// Create a new blocking I2C driver.
-    pub fn new_blocking(
+    pub fn new_blocking<T: Instance>(
         peri: impl Peripheral<P = T> + 'd,
         scl: impl Peripheral<P = impl SclPin<T>> + 'd,
         sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
@@ -112,10 +115,10 @@ impl<'d, T: Instance> I2c<'d, T, Blocking> {
     }
 }
 
-impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
+impl<'d, M: Mode> I2c<'d, M> {
     /// Create a new I2C driver.
-    fn new_inner(
-        peri: impl Peripheral<P = T> + 'd,
+    fn new_inner<T: Instance>(
+        _peri: impl Peripheral<P = T> + 'd,
         scl: impl Peripheral<P = impl SclPin<T>> + 'd,
         sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
         tx_dma: Option<ChannelAndRequest<'d>>,
@@ -123,7 +126,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         freq: Hertz,
         config: Config,
     ) -> Self {
-        into_ref!(peri, scl, sda);
+        into_ref!(scl, sda);
 
         T::enable_and_reset();
 
@@ -148,7 +151,9 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         unsafe { T::ErrorInterrupt::enable() };
 
         let mut this = Self {
-            _peri: peri,
+            info: T::info(),
+            state: T::state(),
+            kernel_clock: T::frequency(),
             tx_dma,
             rx_dma,
             #[cfg(feature = "time")]
@@ -217,19 +222,14 @@ impl State {
     }
 }
 
-trait SealedInstance: crate::rcc::RccPeripheral {
-    fn regs() -> crate::pac::i2c::I2c;
-    fn state() -> &'static State;
+struct Info {
+    regs: crate::pac::i2c::I2c,
+    pub(crate) enable_bit: ClockEnableBit,
 }
 
-/// I2C peripheral instance
-#[allow(private_bounds)]
-pub trait Instance: SealedInstance + 'static {
-    /// Event interrupt for this instance
-    type EventInterrupt: interrupt::typelevel::Interrupt;
-    /// Error interrupt for this instance
-    type ErrorInterrupt: interrupt::typelevel::Interrupt;
-}
+peri_trait!(
+    irqs: [EventInterrupt, ErrorInterrupt],
+);
 
 pin_trait!(SclPin, Instance);
 pin_trait!(SdaPin, Instance);
@@ -260,11 +260,15 @@ impl<T: Instance> interrupt::typelevel::Handler<T::ErrorInterrupt> for ErrorInte
 
 foreach_peripheral!(
     (i2c, $inst:ident) => {
+        #[allow(private_interfaces)]
         impl SealedInstance for peripherals::$inst {
-            fn regs() -> crate::pac::i2c::I2c {
-                crate::pac::$inst
+            fn info() -> &'static Info {
+                static INFO: Info = Info{
+                    regs: crate::pac::$inst,
+                    enable_bit: crate::peripherals::$inst::ENABLE_BIT,
+                };
+                &INFO
             }
-
             fn state() -> &'static State {
                 static STATE: State = State::new();
                 &STATE
@@ -278,7 +282,7 @@ foreach_peripheral!(
     };
 );
 
-impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> {
+impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, M> {
     type Error = Error;
 
     fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
@@ -286,7 +290,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d,
     }
 }
 
-impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> {
+impl<'d, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, M> {
     type Error = Error;
 
     fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
@@ -294,7 +298,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d,
     }
 }
 
-impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> {
+impl<'d, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, M> {
     type Error = Error;
 
     fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
@@ -318,11 +322,11 @@ impl embedded_hal_1::i2c::Error for Error {
     }
 }
 
-impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> {
+impl<'d, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, M> {
     type Error = Error;
 }
 
-impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> {
+impl<'d, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, M> {
     fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
         self.blocking_read(address, read)
     }
@@ -344,7 +348,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> {
     }
 }
 
-impl<'d, T: Instance> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> {
+impl<'d> embedded_hal_async::i2c::I2c for I2c<'d, Async> {
     async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
         self.read(address, read).await
     }
diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs
index ac4fa1b9e..0269e53aa 100644
--- a/embassy-stm32/src/i2c/v1.rs
+++ b/embassy-stm32/src/i2c/v1.rs
@@ -28,7 +28,7 @@ use crate::pac::i2c;
 // There's some more details there, and we might have a fix for you. But please let us know if you
 // hit a case like this!
 pub unsafe fn on_interrupt<T: Instance>() {
-    let regs = T::regs();
+    let regs = T::info().regs;
     // i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of
     // other stuff, so we wake the task on every interrupt.
     T::state().waker.wake();
@@ -41,9 +41,9 @@ pub unsafe fn on_interrupt<T: Instance>() {
     });
 }
 
-impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
+impl<'d, M: PeriMode> I2c<'d, M> {
     pub(crate) fn init(&mut self, freq: Hertz, _config: Config) {
-        T::regs().cr1().modify(|reg| {
+        self.info.regs.cr1().modify(|reg| {
             reg.set_pe(false);
             //reg.set_anfoff(false);
         });
@@ -67,39 +67,39 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         //
         // This presents as an ~infinite hang on read or write, as the START condition
         // is never generated, meaning the start event is never generated.
-        T::regs().cr1().modify(|reg| {
+        self.info.regs.cr1().modify(|reg| {
             reg.set_swrst(true);
         });
-        T::regs().cr1().modify(|reg| {
+        self.info.regs.cr1().modify(|reg| {
             reg.set_swrst(false);
         });
 
-        let timings = Timings::new(T::frequency(), freq);
+        let timings = Timings::new(self.kernel_clock, freq);
 
-        T::regs().cr2().modify(|reg| {
+        self.info.regs.cr2().modify(|reg| {
             reg.set_freq(timings.freq);
         });
-        T::regs().ccr().modify(|reg| {
+        self.info.regs.ccr().modify(|reg| {
             reg.set_f_s(timings.mode.f_s());
             reg.set_duty(timings.duty.duty());
             reg.set_ccr(timings.ccr);
         });
-        T::regs().trise().modify(|reg| {
+        self.info.regs.trise().modify(|reg| {
             reg.set_trise(timings.trise);
         });
 
-        T::regs().cr1().modify(|reg| {
+        self.info.regs.cr1().modify(|reg| {
             reg.set_pe(true);
         });
     }
 
-    fn check_and_clear_error_flags() -> Result<i2c::regs::Sr1, Error> {
+    fn check_and_clear_error_flags(info: &'static Info) -> Result<i2c::regs::Sr1, Error> {
         // Note that flags should only be cleared once they have been registered. If flags are
         // cleared otherwise, there may be an inherent race condition and flags may be missed.
-        let sr1 = T::regs().sr1().read();
+        let sr1 = info.regs.sr1().read();
 
         if sr1.timeout() {
-            T::regs().sr1().write(|reg| {
+            info.regs.sr1().write(|reg| {
                 reg.0 = !0;
                 reg.set_timeout(false);
             });
@@ -107,7 +107,7 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         }
 
         if sr1.pecerr() {
-            T::regs().sr1().write(|reg| {
+            info.regs.sr1().write(|reg| {
                 reg.0 = !0;
                 reg.set_pecerr(false);
             });
@@ -115,7 +115,7 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         }
 
         if sr1.ovr() {
-            T::regs().sr1().write(|reg| {
+            info.regs.sr1().write(|reg| {
                 reg.0 = !0;
                 reg.set_ovr(false);
             });
@@ -123,7 +123,7 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         }
 
         if sr1.af() {
-            T::regs().sr1().write(|reg| {
+            info.regs.sr1().write(|reg| {
                 reg.0 = !0;
                 reg.set_af(false);
             });
@@ -131,7 +131,7 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         }
 
         if sr1.arlo() {
-            T::regs().sr1().write(|reg| {
+            info.regs.sr1().write(|reg| {
                 reg.0 = !0;
                 reg.set_arlo(false);
             });
@@ -141,7 +141,7 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         // The errata indicates that BERR may be incorrectly detected. It recommends ignoring and
         // clearing the BERR bit instead.
         if sr1.berr() {
-            T::regs().sr1().write(|reg| {
+            info.regs.sr1().write(|reg| {
                 reg.0 = !0;
                 reg.set_berr(false);
             });
@@ -154,32 +154,32 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         if frame.send_start() {
             // Send a START condition
 
-            T::regs().cr1().modify(|reg| {
+            self.info.regs.cr1().modify(|reg| {
                 reg.set_start(true);
             });
 
             // Wait until START condition was generated
-            while !Self::check_and_clear_error_flags()?.start() {
+            while !Self::check_and_clear_error_flags(self.info)?.start() {
                 timeout.check()?;
             }
 
             // Check if we were the ones to generate START
-            if T::regs().cr1().read().start() || !T::regs().sr2().read().msl() {
+            if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() {
                 return Err(Error::Arbitration);
             }
 
             // Set up current address we're trying to talk to
-            T::regs().dr().write(|reg| reg.set_dr(addr << 1));
+            self.info.regs.dr().write(|reg| reg.set_dr(addr << 1));
 
             // Wait until address was sent
             // Wait for the address to be acknowledged
             // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
-            while !Self::check_and_clear_error_flags()?.addr() {
+            while !Self::check_and_clear_error_flags(self.info)?.addr() {
                 timeout.check()?;
             }
 
             // Clear condition by reading SR2
-            let _ = T::regs().sr2().read();
+            let _ = self.info.regs.sr2().read();
         }
 
         // Send bytes
@@ -189,7 +189,7 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
 
         if frame.send_stop() {
             // Send a STOP condition
-            T::regs().cr1().modify(|reg| reg.set_stop(true));
+            self.info.regs.cr1().modify(|reg| reg.set_stop(true));
         }
 
         // Fallthrough is success
@@ -200,18 +200,18 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         // Wait until we're ready for sending
         while {
             // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
-            !Self::check_and_clear_error_flags()?.txe()
+            !Self::check_and_clear_error_flags(self.info)?.txe()
         } {
             timeout.check()?;
         }
 
         // Push out a byte of data
-        T::regs().dr().write(|reg| reg.set_dr(byte));
+        self.info.regs.dr().write(|reg| reg.set_dr(byte));
 
         // Wait until byte is transferred
         while {
             // Check for any potential error conditions.
-            !Self::check_and_clear_error_flags()?.btf()
+            !Self::check_and_clear_error_flags(self.info)?.btf()
         } {
             timeout.check()?;
         }
@@ -222,14 +222,14 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
     fn recv_byte(&self, timeout: Timeout) -> Result<u8, Error> {
         while {
             // Check for any potential error conditions.
-            Self::check_and_clear_error_flags()?;
+            Self::check_and_clear_error_flags(self.info)?;
 
-            !T::regs().sr1().read().rxne()
+            !self.info.regs.sr1().read().rxne()
         } {
             timeout.check()?;
         }
 
-        let value = T::regs().dr().read().dr();
+        let value = self.info.regs.dr().read().dr();
         Ok(value)
     }
 
@@ -246,32 +246,32 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
 
         if frame.send_start() {
             // Send a START condition and set ACK bit
-            T::regs().cr1().modify(|reg| {
+            self.info.regs.cr1().modify(|reg| {
                 reg.set_start(true);
                 reg.set_ack(true);
             });
 
             // Wait until START condition was generated
-            while !Self::check_and_clear_error_flags()?.start() {
+            while !Self::check_and_clear_error_flags(self.info)?.start() {
                 timeout.check()?;
             }
 
             // Check if we were the ones to generate START
-            if T::regs().cr1().read().start() || !T::regs().sr2().read().msl() {
+            if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() {
                 return Err(Error::Arbitration);
             }
 
             // Set up current address we're trying to talk to
-            T::regs().dr().write(|reg| reg.set_dr((addr << 1) + 1));
+            self.info.regs.dr().write(|reg| reg.set_dr((addr << 1) + 1));
 
             // Wait until address was sent
             // Wait for the address to be acknowledged
-            while !Self::check_and_clear_error_flags()?.addr() {
+            while !Self::check_and_clear_error_flags(self.info)?.addr() {
                 timeout.check()?;
             }
 
             // Clear condition by reading SR2
-            let _ = T::regs().sr2().read();
+            let _ = self.info.regs.sr2().read();
         }
 
         // Receive bytes into buffer
@@ -280,7 +280,7 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
         }
 
         // Prepare to send NACK then STOP after next byte
-        T::regs().cr1().modify(|reg| {
+        self.info.regs.cr1().modify(|reg| {
             if frame.send_nack() {
                 reg.set_ack(false);
             }
@@ -346,17 +346,17 @@ impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
     // Async
 
     #[inline] // pretty sure this should always be inlined
-    fn enable_interrupts() -> () {
-        T::regs().cr2().modify(|w| {
+    fn enable_interrupts(info: &'static Info) -> () {
+        info.regs.cr2().modify(|w| {
             w.set_iterren(true);
             w.set_itevten(true);
         });
     }
 }
 
-impl<'d, T: Instance> I2c<'d, T, Async> {
+impl<'d> I2c<'d, Async> {
     async fn write_frame(&mut self, address: u8, write: &[u8], frame: FrameOptions) -> Result<(), Error> {
-        T::regs().cr2().modify(|w| {
+        self.info.regs.cr2().modify(|w| {
             // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for
             // reception.
             w.set_itbufen(false);
@@ -370,33 +370,31 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         // Sentinel to disable transfer when an error occurs or future is canceled.
         // TODO: Generate STOP condition on cancel?
         let on_drop = OnDrop::new(|| {
-            T::regs().cr2().modify(|w| {
+            self.info.regs.cr2().modify(|w| {
                 w.set_dmaen(false);
                 w.set_iterren(false);
                 w.set_itevten(false);
             })
         });
 
-        let state = T::state();
-
         if frame.send_start() {
             // Send a START condition
-            T::regs().cr1().modify(|reg| {
+            self.info.regs.cr1().modify(|reg| {
                 reg.set_start(true);
             });
 
             // Wait until START condition was generated
             poll_fn(|cx| {
-                state.waker.register(cx.waker());
+                self.state.waker.register(cx.waker());
 
-                match Self::check_and_clear_error_flags() {
+                match Self::check_and_clear_error_flags(self.info) {
                     Err(e) => Poll::Ready(Err(e)),
                     Ok(sr1) => {
                         if sr1.start() {
                             Poll::Ready(Ok(()))
                         } else {
                             // When pending, (re-)enable interrupts to wake us up.
-                            Self::enable_interrupts();
+                            Self::enable_interrupts(self.info);
                             Poll::Pending
                         }
                     }
@@ -405,25 +403,25 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             .await?;
 
             // Check if we were the ones to generate START
-            if T::regs().cr1().read().start() || !T::regs().sr2().read().msl() {
+            if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() {
                 return Err(Error::Arbitration);
             }
 
             // Set up current address we're trying to talk to
-            T::regs().dr().write(|reg| reg.set_dr(address << 1));
+            self.info.regs.dr().write(|reg| reg.set_dr(address << 1));
 
             // Wait for the address to be acknowledged
             poll_fn(|cx| {
-                state.waker.register(cx.waker());
+                self.state.waker.register(cx.waker());
 
-                match Self::check_and_clear_error_flags() {
+                match Self::check_and_clear_error_flags(self.info) {
                     Err(e) => Poll::Ready(Err(e)),
                     Ok(sr1) => {
                         if sr1.addr() {
                             Poll::Ready(Ok(()))
                         } else {
                             // When pending, (re-)enable interrupts to wake us up.
-                            Self::enable_interrupts();
+                            Self::enable_interrupts(self.info);
                             Poll::Pending
                         }
                     }
@@ -432,26 +430,26 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             .await?;
 
             // Clear condition by reading SR2
-            T::regs().sr2().read();
+            self.info.regs.sr2().read();
         }
 
         let dma_transfer = unsafe {
             // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to
             // this address from the memory after each TxE event.
-            let dst = T::regs().dr().as_ptr() as *mut u8;
+            let dst = self.info.regs.dr().as_ptr() as *mut u8;
 
             self.tx_dma.as_mut().unwrap().write(write, dst, Default::default())
         };
 
         // Wait for bytes to be sent, or an error to occur.
         let poll_error = poll_fn(|cx| {
-            state.waker.register(cx.waker());
+            self.state.waker.register(cx.waker());
 
-            match Self::check_and_clear_error_flags() {
+            match Self::check_and_clear_error_flags(self.info) {
                 Err(e) => Poll::Ready(Err::<(), Error>(e)),
                 Ok(_) => {
                     // When pending, (re-)enable interrupts to wake us up.
-                    Self::enable_interrupts();
+                    Self::enable_interrupts(self.info);
                     Poll::Pending
                 }
             }
@@ -463,7 +461,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             _ => Ok(()),
         }?;
 
-        T::regs().cr2().modify(|w| {
+        self.info.regs.cr2().modify(|w| {
             w.set_dmaen(false);
         });
 
@@ -473,16 +471,16 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             // 18.3.8 “Master transmitter: In the interrupt routine after the EOT interrupt, disable DMA
             // requests then wait for a BTF event before programming the Stop condition.”
             poll_fn(|cx| {
-                state.waker.register(cx.waker());
+                self.state.waker.register(cx.waker());
 
-                match Self::check_and_clear_error_flags() {
+                match Self::check_and_clear_error_flags(self.info) {
                     Err(e) => Poll::Ready(Err(e)),
                     Ok(sr1) => {
                         if sr1.btf() {
                             Poll::Ready(Ok(()))
                         } else {
                             // When pending, (re-)enable interrupts to wake us up.
-                            Self::enable_interrupts();
+                            Self::enable_interrupts(self.info);
                             Poll::Pending
                         }
                     }
@@ -490,7 +488,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             })
             .await?;
 
-            T::regs().cr1().modify(|w| {
+            self.info.regs.cr1().modify(|w| {
                 w.set_stop(true);
             });
         }
@@ -525,7 +523,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         // Some branches below depend on whether the buffer contains only a single byte.
         let single_byte = buffer.len() == 1;
 
-        T::regs().cr2().modify(|w| {
+        self.info.regs.cr2().modify(|w| {
             // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for
             // reception.
             w.set_itbufen(false);
@@ -541,34 +539,32 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         // Sentinel to disable transfer when an error occurs or future is canceled.
         // TODO: Generate STOP condition on cancel?
         let on_drop = OnDrop::new(|| {
-            T::regs().cr2().modify(|w| {
+            self.info.regs.cr2().modify(|w| {
                 w.set_dmaen(false);
                 w.set_iterren(false);
                 w.set_itevten(false);
             })
         });
 
-        let state = T::state();
-
         if frame.send_start() {
             // Send a START condition and set ACK bit
-            T::regs().cr1().modify(|reg| {
+            self.info.regs.cr1().modify(|reg| {
                 reg.set_start(true);
                 reg.set_ack(true);
             });
 
             // Wait until START condition was generated
             poll_fn(|cx| {
-                state.waker.register(cx.waker());
+                self.state.waker.register(cx.waker());
 
-                match Self::check_and_clear_error_flags() {
+                match Self::check_and_clear_error_flags(self.info) {
                     Err(e) => Poll::Ready(Err(e)),
                     Ok(sr1) => {
                         if sr1.start() {
                             Poll::Ready(Ok(()))
                         } else {
                             // When pending, (re-)enable interrupts to wake us up.
-                            Self::enable_interrupts();
+                            Self::enable_interrupts(self.info);
                             Poll::Pending
                         }
                     }
@@ -577,25 +573,25 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             .await?;
 
             // Check if we were the ones to generate START
-            if T::regs().cr1().read().start() || !T::regs().sr2().read().msl() {
+            if self.info.regs.cr1().read().start() || !self.info.regs.sr2().read().msl() {
                 return Err(Error::Arbitration);
             }
 
             // Set up current address we're trying to talk to
-            T::regs().dr().write(|reg| reg.set_dr((address << 1) + 1));
+            self.info.regs.dr().write(|reg| reg.set_dr((address << 1) + 1));
 
             // Wait for the address to be acknowledged
             poll_fn(|cx| {
-                state.waker.register(cx.waker());
+                self.state.waker.register(cx.waker());
 
-                match Self::check_and_clear_error_flags() {
+                match Self::check_and_clear_error_flags(self.info) {
                     Err(e) => Poll::Ready(Err(e)),
                     Ok(sr1) => {
                         if sr1.addr() {
                             Poll::Ready(Ok(()))
                         } else {
                             // When pending, (re-)enable interrupts to wake us up.
-                            Self::enable_interrupts();
+                            Self::enable_interrupts(self.info);
                             Poll::Pending
                         }
                     }
@@ -606,18 +602,18 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             // 18.3.8: When a single byte must be received: the NACK must be programmed during EV6
             // event, i.e. program ACK=0 when ADDR=1, before clearing ADDR flag.
             if frame.send_nack() && single_byte {
-                T::regs().cr1().modify(|w| {
+                self.info.regs.cr1().modify(|w| {
                     w.set_ack(false);
                 });
             }
 
             // Clear condition by reading SR2
-            T::regs().sr2().read();
+            self.info.regs.sr2().read();
         } else {
             // Before starting reception of single byte (but without START condition, i.e. in case
             // of continued frame), program NACK to emit at end of this byte.
             if frame.send_nack() && single_byte {
-                T::regs().cr1().modify(|w| {
+                self.info.regs.cr1().modify(|w| {
                     w.set_ack(false);
                 });
             }
@@ -627,7 +623,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         // condition either after clearing ADDR flag, or in the DMA Transfer Complete interrupt
         // routine.
         if frame.send_stop() && single_byte {
-            T::regs().cr1().modify(|w| {
+            self.info.regs.cr1().modify(|w| {
                 w.set_stop(true);
             });
         }
@@ -635,20 +631,20 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         let dma_transfer = unsafe {
             // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved
             // from this address from the memory after each RxE event.
-            let src = T::regs().dr().as_ptr() as *mut u8;
+            let src = self.info.regs.dr().as_ptr() as *mut u8;
 
             self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default())
         };
 
         // Wait for bytes to be received, or an error to occur.
         let poll_error = poll_fn(|cx| {
-            state.waker.register(cx.waker());
+            self.state.waker.register(cx.waker());
 
-            match Self::check_and_clear_error_flags() {
+            match Self::check_and_clear_error_flags(self.info) {
                 Err(e) => Poll::Ready(Err::<(), Error>(e)),
                 _ => {
                     // When pending, (re-)enable interrupts to wake us up.
-                    Self::enable_interrupts();
+                    Self::enable_interrupts(self.info);
                     Poll::Pending
                 }
             }
@@ -659,12 +655,12 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             _ => Ok(()),
         }?;
 
-        T::regs().cr2().modify(|w| {
+        self.info.regs.cr2().modify(|w| {
             w.set_dmaen(false);
         });
 
         if frame.send_stop() && !single_byte {
-            T::regs().cr1().modify(|w| {
+            self.info.regs.cr1().modify(|w| {
                 w.set_stop(true);
             });
         }
@@ -704,9 +700,9 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
     }
 }
 
-impl<'d, T: Instance, M: PeriMode> Drop for I2c<'d, T, M> {
+impl<'d, M: PeriMode> Drop for I2c<'d, M> {
     fn drop(&mut self) {
-        T::disable();
+        self.info.enable_bit.disable()
     }
 }
 
@@ -810,20 +806,20 @@ impl Timings {
     }
 }
 
-impl<'d, T: Instance, M: PeriMode> SetConfig for I2c<'d, T, M> {
+impl<'d, M: PeriMode> SetConfig for I2c<'d, M> {
     type Config = Hertz;
     type ConfigError = ();
     fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> {
-        let timings = Timings::new(T::frequency(), *config);
-        T::regs().cr2().modify(|reg| {
+        let timings = Timings::new(self.kernel_clock, *config);
+        self.info.regs.cr2().modify(|reg| {
             reg.set_freq(timings.freq);
         });
-        T::regs().ccr().modify(|reg| {
+        self.info.regs.ccr().modify(|reg| {
             reg.set_f_s(timings.mode.f_s());
             reg.set_duty(timings.duty.duty());
             reg.set_ccr(timings.ccr);
         });
-        T::regs().trise().modify(|reg| {
+        self.info.regs.trise().modify(|reg| {
             reg.set_trise(timings.trise);
         });
 
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index 12df98534..aa6daf786 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -10,7 +10,7 @@ use super::*;
 use crate::pac::i2c;
 
 pub(crate) unsafe fn on_interrupt<T: Instance>() {
-    let regs = T::regs();
+    let regs = T::info().regs;
     let isr = regs.isr().read();
 
     if isr.tcr() || isr.tc() {
@@ -23,16 +23,16 @@ pub(crate) unsafe fn on_interrupt<T: Instance>() {
     });
 }
 
-impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
+impl<'d, M: Mode> I2c<'d, M> {
     pub(crate) fn init(&mut self, freq: Hertz, _config: Config) {
-        T::regs().cr1().modify(|reg| {
+        self.info.regs.cr1().modify(|reg| {
             reg.set_pe(false);
             reg.set_anfoff(false);
         });
 
-        let timings = Timings::new(T::frequency(), freq.into());
+        let timings = Timings::new(self.kernel_clock, freq.into());
 
-        T::regs().timingr().write(|reg| {
+        self.info.regs.timingr().write(|reg| {
             reg.set_presc(timings.prescale);
             reg.set_scll(timings.scll);
             reg.set_sclh(timings.sclh);
@@ -40,16 +40,17 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
             reg.set_scldel(timings.scldel);
         });
 
-        T::regs().cr1().modify(|reg| {
+        self.info.regs.cr1().modify(|reg| {
             reg.set_pe(true);
         });
     }
 
     fn master_stop(&mut self) {
-        T::regs().cr2().write(|w| w.set_stop(true));
+        self.info.regs.cr2().write(|w| w.set_stop(true));
     }
 
     fn master_read(
+        info: &'static Info,
         address: u8,
         length: usize,
         stop: Stop,
@@ -63,7 +64,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
             // Wait for any previous address sequence to end
             // automatically. This could be up to 50% of a bus
             // cycle (ie. up to 0.5/freq)
-            while T::regs().cr2().read().start() {
+            while info.regs.cr2().read().start() {
                 timeout.check()?;
             }
         }
@@ -78,7 +79,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
             i2c::vals::Reload::COMPLETED
         };
 
-        T::regs().cr2().modify(|w| {
+        info.regs.cr2().modify(|w| {
             w.set_sadd((address << 1 | 0) as u16);
             w.set_add10(i2c::vals::Addmode::BIT7);
             w.set_dir(i2c::vals::Dir::READ);
@@ -91,13 +92,20 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         Ok(())
     }
 
-    fn master_write(address: u8, length: usize, stop: Stop, reload: bool, timeout: Timeout) -> Result<(), Error> {
+    fn master_write(
+        info: &'static Info,
+        address: u8,
+        length: usize,
+        stop: Stop,
+        reload: bool,
+        timeout: Timeout,
+    ) -> Result<(), Error> {
         assert!(length < 256);
 
         // Wait for any previous address sequence to end
         // automatically. This could be up to 50% of a bus
         // cycle (ie. up to 0.5/freq)
-        while T::regs().cr2().read().start() {
+        while info.regs.cr2().read().start() {
             timeout.check()?;
         }
 
@@ -110,7 +118,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         // Set START and prepare to send `bytes`. The
         // START bit can be set even if the bus is BUSY or
         // I2C is in slave mode.
-        T::regs().cr2().modify(|w| {
+        info.regs.cr2().modify(|w| {
             w.set_sadd((address << 1 | 0) as u16);
             w.set_add10(i2c::vals::Addmode::BIT7);
             w.set_dir(i2c::vals::Dir::WRITE);
@@ -123,10 +131,10 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         Ok(())
     }
 
-    fn master_continue(length: usize, reload: bool, timeout: Timeout) -> Result<(), Error> {
+    fn master_continue(info: &'static Info, length: usize, reload: bool, timeout: Timeout) -> Result<(), Error> {
         assert!(length < 256 && length > 0);
 
-        while !T::regs().isr().read().tcr() {
+        while !info.regs.isr().read().tcr() {
             timeout.check()?;
         }
 
@@ -136,7 +144,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
             i2c::vals::Reload::COMPLETED
         };
 
-        T::regs().cr2().modify(|w| {
+        info.regs.cr2().modify(|w| {
             w.set_nbytes(length as u8);
             w.set_reload(reload);
         });
@@ -145,27 +153,27 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
     }
 
     fn flush_txdr(&self) {
-        if T::regs().isr().read().txis() {
-            T::regs().txdr().write(|w| w.set_txdata(0));
+        if self.info.regs.isr().read().txis() {
+            self.info.regs.txdr().write(|w| w.set_txdata(0));
         }
-        if !T::regs().isr().read().txe() {
-            T::regs().isr().modify(|w| w.set_txe(true))
+        if !self.info.regs.isr().read().txe() {
+            self.info.regs.isr().modify(|w| w.set_txe(true))
         }
     }
 
     fn wait_txe(&self, timeout: Timeout) -> Result<(), Error> {
         loop {
-            let isr = T::regs().isr().read();
+            let isr = self.info.regs.isr().read();
             if isr.txe() {
                 return Ok(());
             } else if isr.berr() {
-                T::regs().icr().write(|reg| reg.set_berrcf(true));
+                self.info.regs.icr().write(|reg| reg.set_berrcf(true));
                 return Err(Error::Bus);
             } else if isr.arlo() {
-                T::regs().icr().write(|reg| reg.set_arlocf(true));
+                self.info.regs.icr().write(|reg| reg.set_arlocf(true));
                 return Err(Error::Arbitration);
             } else if isr.nackf() {
-                T::regs().icr().write(|reg| reg.set_nackcf(true));
+                self.info.regs.icr().write(|reg| reg.set_nackcf(true));
                 self.flush_txdr();
                 return Err(Error::Nack);
             }
@@ -176,17 +184,17 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
 
     fn wait_rxne(&self, timeout: Timeout) -> Result<(), Error> {
         loop {
-            let isr = T::regs().isr().read();
+            let isr = self.info.regs.isr().read();
             if isr.rxne() {
                 return Ok(());
             } else if isr.berr() {
-                T::regs().icr().write(|reg| reg.set_berrcf(true));
+                self.info.regs.icr().write(|reg| reg.set_berrcf(true));
                 return Err(Error::Bus);
             } else if isr.arlo() {
-                T::regs().icr().write(|reg| reg.set_arlocf(true));
+                self.info.regs.icr().write(|reg| reg.set_arlocf(true));
                 return Err(Error::Arbitration);
             } else if isr.nackf() {
-                T::regs().icr().write(|reg| reg.set_nackcf(true));
+                self.info.regs.icr().write(|reg| reg.set_nackcf(true));
                 self.flush_txdr();
                 return Err(Error::Nack);
             }
@@ -197,17 +205,17 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
 
     fn wait_tc(&self, timeout: Timeout) -> Result<(), Error> {
         loop {
-            let isr = T::regs().isr().read();
+            let isr = self.info.regs.isr().read();
             if isr.tc() {
                 return Ok(());
             } else if isr.berr() {
-                T::regs().icr().write(|reg| reg.set_berrcf(true));
+                self.info.regs.icr().write(|reg| reg.set_berrcf(true));
                 return Err(Error::Bus);
             } else if isr.arlo() {
-                T::regs().icr().write(|reg| reg.set_arlocf(true));
+                self.info.regs.icr().write(|reg| reg.set_arlocf(true));
                 return Err(Error::Arbitration);
             } else if isr.nackf() {
-                T::regs().icr().write(|reg| reg.set_nackcf(true));
+                self.info.regs.icr().write(|reg| reg.set_nackcf(true));
                 self.flush_txdr();
                 return Err(Error::Nack);
             }
@@ -226,6 +234,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         let last_chunk_idx = total_chunks.saturating_sub(1);
 
         Self::master_read(
+            self.info,
             address,
             read.len().min(255),
             Stop::Automatic,
@@ -236,14 +245,14 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
 
         for (number, chunk) in read.chunks_mut(255).enumerate() {
             if number != 0 {
-                Self::master_continue(chunk.len(), number != last_chunk_idx, timeout)?;
+                Self::master_continue(self.info, chunk.len(), number != last_chunk_idx, timeout)?;
             }
 
             for byte in chunk {
                 // Wait until we have received something
                 self.wait_rxne(timeout)?;
 
-                *byte = T::regs().rxdr().read().rxdata();
+                *byte = self.info.regs.rxdr().read().rxdata();
             }
         }
         Ok(())
@@ -262,6 +271,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         //
         // ST SAD+W
         if let Err(err) = Self::master_write(
+            self.info,
             address,
             write.len().min(255),
             Stop::Software,
@@ -276,7 +286,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
 
         for (number, chunk) in write.chunks(255).enumerate() {
             if number != 0 {
-                Self::master_continue(chunk.len(), number != last_chunk_idx, timeout)?;
+                Self::master_continue(self.info, chunk.len(), number != last_chunk_idx, timeout)?;
             }
 
             for byte in chunk {
@@ -290,7 +300,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
                     return Err(err);
                 }
 
-                T::regs().txdr().write(|w| w.set_txdata(*byte));
+                self.info.regs.txdr().write(|w| w.set_txdata(*byte));
             }
         }
         // Wait until the write finishes
@@ -348,6 +358,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
         let last_slice_index = write.len() - 1;
 
         if let Err(err) = Self::master_write(
+            self.info,
             address,
             first_length.min(255),
             Stop::Software,
@@ -370,6 +381,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
 
             if idx != 0 {
                 if let Err(err) = Self::master_continue(
+                    self.info,
                     slice_len.min(255),
                     (idx != last_slice_index) || (slice_len > 255),
                     timeout,
@@ -382,6 +394,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
             for (number, chunk) in slice.chunks(255).enumerate() {
                 if number != 0 {
                     if let Err(err) = Self::master_continue(
+                        self.info,
                         chunk.len(),
                         (number != last_chunk_idx) || (idx != last_slice_index),
                         timeout,
@@ -402,7 +415,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
 
                     // Put byte on the wire
                     //self.i2c.txdr.write(|w| w.txdata().bits(*byte));
-                    T::regs().txdr().write(|w| w.set_txdata(*byte));
+                    self.info.regs.txdr().write(|w| w.set_txdata(*byte));
                 }
             }
         }
@@ -413,7 +426,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
     }
 }
 
-impl<'d, T: Instance> I2c<'d, T, Async> {
+impl<'d> I2c<'d, Async> {
     async fn write_dma_internal(
         &mut self,
         address: u8,
@@ -425,7 +438,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         let total_len = write.len();
 
         let dma_transfer = unsafe {
-            let regs = T::regs();
+            let regs = self.info.regs;
             regs.cr1().modify(|w| {
                 w.set_txdmaen(true);
                 if first_slice {
@@ -437,11 +450,10 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             self.tx_dma.as_mut().unwrap().write(write, dst, Default::default())
         };
 
-        let state = T::state();
         let mut remaining_len = total_len;
 
         let on_drop = OnDrop::new(|| {
-            let regs = T::regs();
+            let regs = self.info.regs;
             regs.cr1().modify(|w| {
                 if last_slice {
                     w.set_txdmaen(false);
@@ -451,12 +463,13 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         });
 
         poll_fn(|cx| {
-            state.waker.register(cx.waker());
+            self.state.waker.register(cx.waker());
 
-            let isr = T::regs().isr().read();
+            let isr = self.info.regs.isr().read();
             if remaining_len == total_len {
                 if first_slice {
                     Self::master_write(
+                        self.info,
                         address,
                         total_len.min(255),
                         Stop::Software,
@@ -464,8 +477,8 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
                         timeout,
                     )?;
                 } else {
-                    Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, timeout)?;
-                    T::regs().cr1().modify(|w| w.set_tcie(true));
+                    Self::master_continue(self.info, total_len.min(255), (total_len > 255) || !last_slice, timeout)?;
+                    self.info.regs.cr1().modify(|w| w.set_tcie(true));
                 }
             } else if !(isr.tcr() || isr.tc()) {
                 // poll_fn was woken without an interrupt present
@@ -475,10 +488,10 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             } else {
                 let last_piece = (remaining_len <= 255) && last_slice;
 
-                if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
+                if let Err(e) = Self::master_continue(self.info, remaining_len.min(255), !last_piece, timeout) {
                     return Poll::Ready(Err(e));
                 }
-                T::regs().cr1().modify(|w| w.set_tcie(true));
+                self.info.regs.cr1().modify(|w| w.set_tcie(true));
             }
 
             remaining_len = remaining_len.saturating_sub(255);
@@ -509,7 +522,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         let total_len = buffer.len();
 
         let dma_transfer = unsafe {
-            let regs = T::regs();
+            let regs = self.info.regs;
             regs.cr1().modify(|w| {
                 w.set_rxdmaen(true);
                 w.set_tcie(true);
@@ -519,11 +532,10 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default())
         };
 
-        let state = T::state();
         let mut remaining_len = total_len;
 
         let on_drop = OnDrop::new(|| {
-            let regs = T::regs();
+            let regs = self.info.regs;
             regs.cr1().modify(|w| {
                 w.set_rxdmaen(false);
                 w.set_tcie(false);
@@ -531,11 +543,12 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
         });
 
         poll_fn(|cx| {
-            state.waker.register(cx.waker());
+            self.state.waker.register(cx.waker());
 
-            let isr = T::regs().isr().read();
+            let isr = self.info.regs.isr().read();
             if remaining_len == total_len {
                 Self::master_read(
+                    self.info,
                     address,
                     total_len.min(255),
                     Stop::Software,
@@ -551,10 +564,10 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
             } else {
                 let last_piece = remaining_len <= 255;
 
-                if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
+                if let Err(e) = Self::master_continue(self.info, remaining_len.min(255), !last_piece, timeout) {
                     return Poll::Ready(Err(e));
                 }
-                T::regs().cr1().modify(|w| w.set_tcie(true));
+                self.info.regs.cr1().modify(|w| w.set_tcie(true));
             }
 
             remaining_len = remaining_len.saturating_sub(255);
@@ -658,9 +671,9 @@ impl<'d, T: Instance> I2c<'d, T, Async> {
     }
 }
 
-impl<'d, T: Instance, M: Mode> Drop for I2c<'d, T, M> {
+impl<'d, M: Mode> Drop for I2c<'d, M> {
     fn drop(&mut self) {
-        T::disable();
+        self.info.enable_bit.disable();
     }
 }
 
@@ -788,12 +801,12 @@ impl Timings {
     }
 }
 
-impl<'d, T: Instance, M: Mode> SetConfig for I2c<'d, T, M> {
+impl<'d, M: Mode> SetConfig for I2c<'d, M> {
     type Config = Hertz;
     type ConfigError = ();
     fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> {
-        let timings = Timings::new(T::frequency(), *config);
-        T::regs().timingr().write(|reg| {
+        let timings = Timings::new(self.kernel_clock, *config);
+        self.info.regs.timingr().write(|reg| {
             reg.set_presc(timings.prescale);
             reg.set_scll(timings.scll);
             reg.set_sclh(timings.sclh);
diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs
index c102c0035..c78810a38 100644
--- a/embassy-stm32/src/i2s.rs
+++ b/embassy-stm32/src/i2s.rs
@@ -208,7 +208,7 @@ impl<'d> I2S<'d> {
             // rate to reach the proper audio sample frequency. The ODD bit in the SPI_I2SPR
             // register also has to be defined.
 
-            spi.regs.i2spr().modify(|w| {
+            spi.info.regs.i2spr().modify(|w| {
                 w.set_i2sdiv(div);
                 w.set_odd(match odd {
                     true => Odd::ODD,
@@ -235,7 +235,7 @@ impl<'d> I2S<'d> {
 
             // 5. The I2SE bit in SPI_I2SCFGR register must be set.
 
-            spi.regs.i2scfgr().modify(|w| {
+            spi.info.regs.i2scfgr().modify(|w| {
                 w.set_ckpol(config.clock_polarity.ckpol());
 
                 w.set_i2smod(true);
diff --git a/embassy-stm32/src/macros.rs b/embassy-stm32/src/macros.rs
index 9c459a932..7f8076043 100644
--- a/embassy-stm32/src/macros.rs
+++ b/embassy-stm32/src/macros.rs
@@ -1,16 +1,25 @@
 #![macro_use]
 
 macro_rules! peri_trait {
-    () => {
+    (
+        $(irqs: [$($irq:ident),*],)?
+    ) => {
         #[allow(private_interfaces)]
         pub(crate) trait SealedInstance {
-            const INFO: Info;
-            const STATE: &'static State;
+            #[allow(unused)]
+            fn info() -> &'static Info;
+            #[allow(unused)]
+            fn state() -> &'static State;
         }
 
-        /// SPI instance trait.
+        /// Peripheral instance trait.
         #[allow(private_bounds)]
-        pub trait Instance: Peripheral<P = Self> + SealedInstance + RccPeripheral {}
+        pub trait Instance: crate::Peripheral<P = Self> + SealedInstance + crate::rcc::RccPeripheral {
+            $($(
+                /// Interrupt for this peripheral.
+                type $irq: crate::interrupt::typelevel::Interrupt;
+            )*)?
+        }
     };
 }
 
@@ -18,8 +27,14 @@ macro_rules! peri_trait_impl {
     ($instance:ident, $info:expr) => {
         #[allow(private_interfaces)]
         impl SealedInstance for crate::peripherals::$instance {
-            const INFO: Info = $info;
-            const STATE: &'static State = &State::new();
+            fn info() -> &'static Info {
+                static INFO: Info = $info;
+                &INFO
+            }
+            fn state() -> &'static State {
+                static STATE: State = State::new();
+                &STATE
+            }
         }
         impl Instance for crate::peripherals::$instance {}
     };
diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs
index c413b62ef..28816256c 100644
--- a/embassy-stm32/src/rcc/mod.rs
+++ b/embassy-stm32/src/rcc/mod.rs
@@ -67,10 +67,11 @@ pub(crate) unsafe fn get_freqs() -> &'static Clocks {
 }
 
 pub(crate) trait SealedRccPeripheral {
+    const ENABLE_BIT: ClockEnableBit;
+
     fn frequency() -> Hertz;
     fn enable_and_reset_with_cs(cs: CriticalSection);
     fn disable_with_cs(cs: CriticalSection);
-    fn enable_bit() -> ClockEnableBit;
 
     fn enable_and_reset() {
         critical_section::with(|cs| Self::enable_and_reset_with_cs(cs))
@@ -151,7 +152,7 @@ pub(crate) struct ClockEnableBit {
 
 impl ClockEnableBit {
     /// Safety: offset+bit must correspond to a valid xxxEN bit.
-    pub(crate) unsafe fn new(offset: u8, bit: u8) -> Self {
+    pub(crate) const unsafe fn new(offset: u8, bit: u8) -> Self {
         Self { offset, bit }
     }
 
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs
index 5a2ee105d..0875cfe41 100644
--- a/embassy-stm32/src/spi/mod.rs
+++ b/embassy-stm32/src/spi/mod.rs
@@ -13,7 +13,7 @@ use crate::dma::{slice_ptr_parts, word, ChannelAndRequest};
 use crate::gpio::{AFType, AnyPin, Pull, SealedPin as _, Speed};
 use crate::mode::{Async, Blocking, Mode as PeriMode};
 use crate::pac::spi::{regs, vals, Spi as Regs};
-use crate::rcc::{ClockEnableBit, RccPeripheral};
+use crate::rcc::{ClockEnableBit, SealedRccPeripheral};
 use crate::time::Hertz;
 use crate::Peripheral;
 
@@ -93,8 +93,7 @@ impl Config {
 }
 /// SPI driver.
 pub struct Spi<'d, M: PeriMode> {
-    pub(crate) regs: Regs,
-    enable_bit: ClockEnableBit,
+    pub(crate) info: &'static Info,
     kernel_clock: Hertz,
     sck: Option<PeripheralRef<'d, AnyPin>>,
     mosi: Option<PeripheralRef<'d, AnyPin>>,
@@ -115,7 +114,7 @@ impl<'d, M: PeriMode> Spi<'d, M> {
         rx_dma: Option<ChannelAndRequest<'d>>,
         config: Config,
     ) -> Self {
-        let regs = T::INFO.regs;
+        let regs = T::info().regs;
         let kernel_clock = T::frequency();
         let br = compute_baud_rate(kernel_clock, config.frequency);
 
@@ -205,8 +204,7 @@ impl<'d, M: PeriMode> Spi<'d, M> {
         }
 
         Self {
-            regs,
-            enable_bit: T::enable_bit(),
+            info: T::info(),
             kernel_clock,
             sck,
             mosi,
@@ -228,7 +226,7 @@ impl<'d, M: PeriMode> Spi<'d, M> {
         let br = compute_baud_rate(self.kernel_clock, config.frequency);
 
         #[cfg(any(spi_v1, spi_f1, spi_v2))]
-        self.regs.cr1().modify(|w| {
+        self.info.regs.cr1().modify(|w| {
             w.set_cpha(cpha);
             w.set_cpol(cpol);
             w.set_br(br);
@@ -237,12 +235,12 @@ impl<'d, M: PeriMode> Spi<'d, M> {
 
         #[cfg(any(spi_v3, spi_v4, spi_v5))]
         {
-            self.regs.cfg2().modify(|w| {
+            self.info.regs.cfg2().modify(|w| {
                 w.set_cpha(cpha);
                 w.set_cpol(cpol);
                 w.set_lsbfirst(lsbfirst);
             });
-            self.regs.cfg1().modify(|w| {
+            self.info.regs.cfg1().modify(|w| {
                 w.set_mbr(br);
             });
         }
@@ -252,11 +250,11 @@ impl<'d, M: PeriMode> Spi<'d, M> {
     /// Get current SPI configuration.
     pub fn get_current_config(&self) -> Config {
         #[cfg(any(spi_v1, spi_f1, spi_v2))]
-        let cfg = self.regs.cr1().read();
+        let cfg = self.info.regs.cr1().read();
         #[cfg(any(spi_v3, spi_v4, spi_v5))]
-        let cfg = self.regs.cfg2().read();
+        let cfg = self.info.regs.cfg2().read();
         #[cfg(any(spi_v3, spi_v4, spi_v5))]
-        let cfg1 = self.regs.cfg1().read();
+        let cfg1 = self.info.regs.cfg1().read();
 
         let polarity = if cfg.cpol() == vals::Cpol::IDLELOW {
             Polarity::IdleLow
@@ -296,40 +294,40 @@ impl<'d, M: PeriMode> Spi<'d, M> {
 
         #[cfg(any(spi_v1, spi_f1))]
         {
-            self.regs.cr1().modify(|reg| {
+            self.info.regs.cr1().modify(|reg| {
                 reg.set_spe(false);
                 reg.set_dff(word_size)
             });
-            self.regs.cr1().modify(|reg| {
+            self.info.regs.cr1().modify(|reg| {
                 reg.set_spe(true);
             });
         }
         #[cfg(spi_v2)]
         {
-            self.regs.cr1().modify(|w| {
+            self.info.regs.cr1().modify(|w| {
                 w.set_spe(false);
             });
-            self.regs.cr2().modify(|w| {
+            self.info.regs.cr2().modify(|w| {
                 w.set_frxth(word_size.1);
                 w.set_ds(word_size.0);
             });
-            self.regs.cr1().modify(|w| {
+            self.info.regs.cr1().modify(|w| {
                 w.set_spe(true);
             });
         }
         #[cfg(any(spi_v3, spi_v4, spi_v5))]
         {
-            self.regs.cr1().modify(|w| {
+            self.info.regs.cr1().modify(|w| {
                 w.set_csusp(true);
             });
-            while self.regs.sr().read().eot() {}
-            self.regs.cr1().modify(|w| {
+            while self.info.regs.sr().read().eot() {}
+            self.info.regs.cr1().modify(|w| {
                 w.set_spe(false);
             });
-            self.regs.cfg1().modify(|w| {
+            self.info.regs.cfg1().modify(|w| {
                 w.set_dsize(word_size);
             });
-            self.regs.cr1().modify(|w| {
+            self.info.regs.cr1().modify(|w| {
                 w.set_csusp(false);
                 w.set_spe(true);
             });
@@ -340,22 +338,22 @@ impl<'d, M: PeriMode> Spi<'d, M> {
 
     /// Blocking write.
     pub fn blocking_write<W: Word>(&mut self, words: &[W]) -> Result<(), Error> {
-        self.regs.cr1().modify(|w| w.set_spe(true));
-        flush_rx_fifo(self.regs);
+        self.info.regs.cr1().modify(|w| w.set_spe(true));
+        flush_rx_fifo(self.info.regs);
         self.set_word_size(W::CONFIG);
         for word in words.iter() {
-            let _ = transfer_word(self.regs, *word)?;
+            let _ = transfer_word(self.info.regs, *word)?;
         }
         Ok(())
     }
 
     /// Blocking read.
     pub fn blocking_read<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> {
-        self.regs.cr1().modify(|w| w.set_spe(true));
-        flush_rx_fifo(self.regs);
+        self.info.regs.cr1().modify(|w| w.set_spe(true));
+        flush_rx_fifo(self.info.regs);
         self.set_word_size(W::CONFIG);
         for word in words.iter_mut() {
-            *word = transfer_word(self.regs, W::default())?;
+            *word = transfer_word(self.info.regs, W::default())?;
         }
         Ok(())
     }
@@ -364,11 +362,11 @@ impl<'d, M: PeriMode> Spi<'d, M> {
     ///
     /// This writes the contents of `data` on MOSI, and puts the received data on MISO in `data`, at the same time.
     pub fn blocking_transfer_in_place<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> {
-        self.regs.cr1().modify(|w| w.set_spe(true));
-        flush_rx_fifo(self.regs);
+        self.info.regs.cr1().modify(|w| w.set_spe(true));
+        flush_rx_fifo(self.info.regs);
         self.set_word_size(W::CONFIG);
         for word in words.iter_mut() {
-            *word = transfer_word(self.regs, *word)?;
+            *word = transfer_word(self.info.regs, *word)?;
         }
         Ok(())
     }
@@ -380,13 +378,13 @@ impl<'d, M: PeriMode> Spi<'d, M> {
     /// The transfer runs for `max(read.len(), write.len())` bytes. If `read` is shorter extra bytes are ignored.
     /// If `write` is shorter it is padded with zero bytes.
     pub fn blocking_transfer<W: Word>(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> {
-        self.regs.cr1().modify(|w| w.set_spe(true));
-        flush_rx_fifo(self.regs);
+        self.info.regs.cr1().modify(|w| w.set_spe(true));
+        flush_rx_fifo(self.info.regs);
         self.set_word_size(W::CONFIG);
         let len = read.len().max(write.len());
         for i in 0..len {
             let wb = write.get(i).copied().unwrap_or_default();
-            let rb = transfer_word(self.regs, wb)?;
+            let rb = transfer_word(self.info.regs, wb)?;
             if let Some(r) = read.get_mut(i) {
                 *r = rb;
             }
@@ -588,25 +586,25 @@ impl<'d> Spi<'d, Async> {
         }
 
         self.set_word_size(W::CONFIG);
-        self.regs.cr1().modify(|w| {
+        self.info.regs.cr1().modify(|w| {
             w.set_spe(false);
         });
 
-        let tx_dst = self.regs.tx_ptr();
+        let tx_dst = self.info.regs.tx_ptr();
         let tx_f = unsafe { self.tx_dma.as_mut().unwrap().write(data, tx_dst, Default::default()) };
 
-        set_txdmaen(self.regs, true);
-        self.regs.cr1().modify(|w| {
+        set_txdmaen(self.info.regs, true);
+        self.info.regs.cr1().modify(|w| {
             w.set_spe(true);
         });
         #[cfg(any(spi_v3, spi_v4, spi_v5))]
-        self.regs.cr1().modify(|w| {
+        self.info.regs.cr1().modify(|w| {
             w.set_cstart(true);
         });
 
         tx_f.await;
 
-        finish_dma(self.regs);
+        finish_dma(self.info.regs);
 
         Ok(())
     }
@@ -618,22 +616,22 @@ impl<'d> Spi<'d, Async> {
         }
 
         self.set_word_size(W::CONFIG);
-        self.regs.cr1().modify(|w| {
+        self.info.regs.cr1().modify(|w| {
             w.set_spe(false);
         });
 
         // SPIv3 clears rxfifo on SPE=0
         #[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
-        flush_rx_fifo(self.regs);
+        flush_rx_fifo(self.info.regs);
 
-        set_rxdmaen(self.regs, true);
+        set_rxdmaen(self.info.regs, true);
 
         let clock_byte_count = data.len();
 
-        let rx_src = self.regs.rx_ptr();
+        let rx_src = self.info.regs.rx_ptr();
         let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read(rx_src, data, Default::default()) };
 
-        let tx_dst = self.regs.tx_ptr();
+        let tx_dst = self.info.regs.tx_ptr();
         let clock_byte = 0x00u8;
         let tx_f = unsafe {
             self.tx_dma
@@ -642,18 +640,18 @@ impl<'d> Spi<'d, Async> {
                 .write_repeated(&clock_byte, clock_byte_count, tx_dst, Default::default())
         };
 
-        set_txdmaen(self.regs, true);
-        self.regs.cr1().modify(|w| {
+        set_txdmaen(self.info.regs, true);
+        self.info.regs.cr1().modify(|w| {
             w.set_spe(true);
         });
         #[cfg(any(spi_v3, spi_v4, spi_v5))]
-        self.regs.cr1().modify(|w| {
+        self.info.regs.cr1().modify(|w| {
             w.set_cstart(true);
         });
 
         join(tx_f, rx_f).await;
 
-        finish_dma(self.regs);
+        finish_dma(self.info.regs);
 
         Ok(())
     }
@@ -667,20 +665,20 @@ impl<'d> Spi<'d, Async> {
         }
 
         self.set_word_size(W::CONFIG);
-        self.regs.cr1().modify(|w| {
+        self.info.regs.cr1().modify(|w| {
             w.set_spe(false);
         });
 
         // SPIv3 clears rxfifo on SPE=0
         #[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
-        flush_rx_fifo(self.regs);
+        flush_rx_fifo(self.info.regs);
 
-        set_rxdmaen(self.regs, true);
+        set_rxdmaen(self.info.regs, true);
 
-        let rx_src = self.regs.rx_ptr();
+        let rx_src = self.info.regs.rx_ptr();
         let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read_raw(rx_src, read, Default::default()) };
 
-        let tx_dst = self.regs.tx_ptr();
+        let tx_dst = self.info.regs.tx_ptr();
         let tx_f = unsafe {
             self.tx_dma
                 .as_mut()
@@ -688,18 +686,18 @@ impl<'d> Spi<'d, Async> {
                 .write_raw(write, tx_dst, Default::default())
         };
 
-        set_txdmaen(self.regs, true);
-        self.regs.cr1().modify(|w| {
+        set_txdmaen(self.info.regs, true);
+        self.info.regs.cr1().modify(|w| {
             w.set_spe(true);
         });
         #[cfg(any(spi_v3, spi_v4, spi_v5))]
-        self.regs.cr1().modify(|w| {
+        self.info.regs.cr1().modify(|w| {
             w.set_cstart(true);
         });
 
         join(tx_f, rx_f).await;
 
-        finish_dma(self.regs);
+        finish_dma(self.info.regs);
 
         Ok(())
     }
@@ -728,7 +726,7 @@ impl<'d, M: PeriMode> Drop for Spi<'d, M> {
         self.mosi.as_ref().map(|x| x.set_as_disconnected());
         self.miso.as_ref().map(|x| x.set_as_disconnected());
 
-        self.enable_bit.disable();
+        self.info.enable_bit.disable();
     }
 }
 
@@ -1106,8 +1104,9 @@ mod word_impl {
     impl_word!(u32, 32 - 1);
 }
 
-struct Info {
-    regs: Regs,
+pub(crate) struct Info {
+    pub(crate) regs: Regs,
+    pub(crate) enable_bit: ClockEnableBit,
 }
 
 struct State {}
@@ -1134,6 +1133,7 @@ foreach_peripheral!(
     (spi, $inst:ident) => {
         peri_trait_impl!($inst, Info {
             regs: crate::pac::$inst,
+            enable_bit: crate::peripherals::$inst::ENABLE_BIT,
         });
     };
 );
diff --git a/examples/stm32h7/src/bin/i2c_shared.rs b/examples/stm32h7/src/bin/i2c_shared.rs
index 79d213ae4..6f4815582 100644
--- a/examples/stm32h7/src/bin/i2c_shared.rs
+++ b/examples/stm32h7/src/bin/i2c_shared.rs
@@ -6,11 +6,10 @@ use core::cell::RefCell;
 use defmt::*;
 use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
 use embassy_executor::Spawner;
-use embassy_stm32::bind_interrupts;
 use embassy_stm32::i2c::{self, I2c};
 use embassy_stm32::mode::Async;
-use embassy_stm32::peripherals::{self, I2C1};
 use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, peripherals};
 use embassy_sync::blocking_mutex::NoopMutex;
 use embassy_time::{Duration, Timer};
 use static_cell::StaticCell;
@@ -24,7 +23,7 @@ const SHTC3_WAKEUP: [u8; 2] = [0x35, 0x17];
 const SHTC3_MEASURE_RH_FIRST: [u8; 2] = [0x5c, 0x24];
 const SHTC3_SLEEP: [u8; 2] = [0xb0, 0x98];
 
-static I2C_BUS: StaticCell<NoopMutex<RefCell<I2c<'static, I2C1, Async>>>> = StaticCell::new();
+static I2C_BUS: StaticCell<NoopMutex<RefCell<I2c<'static, Async>>>> = StaticCell::new();
 
 bind_interrupts!(struct Irqs {
     I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
index 985ac8171..33149144c 100644
--- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
+++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
@@ -60,7 +60,7 @@ pub type SpeSpiCs = ExclusiveDevice<SpeSpi, Output<'static>, Delay>;
 pub type SpeInt = exti::ExtiInput<'static>;
 pub type SpeRst = Output<'static>;
 pub type Adin1110T = ADIN1110<SpeSpiCs>;
-pub type TempSensI2c = I2c<'static, peripherals::I2C3, Async>;
+pub type TempSensI2c = I2c<'static, Async>;
 
 static TEMP: AtomicI32 = AtomicI32::new(0);