From 5eb454c28074a9b149d285bbca0cc078ef86903a Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 22 Feb 2023 02:07:18 -0500 Subject: [PATCH 1/4] Add I2C slave mode to fix #636 --- hal/src/sercom/i2c.rs | 3 + hal/src/sercom/i2c/client.rs | 144 ++++++++++++++++++++++ hal/src/sercom/i2c/client/config.rs | 182 ++++++++++++++++++++++++++++ hal/src/sercom/i2c/client/flags.rs | 20 +++ hal/src/sercom/i2c/client/reg.rs | 124 +++++++++++++++++++ 5 files changed, 473 insertions(+) create mode 100644 hal/src/sercom/i2c/client.rs create mode 100644 hal/src/sercom/i2c/client/config.rs create mode 100644 hal/src/sercom/i2c/client/flags.rs create mode 100644 hal/src/sercom/i2c/client/reg.rs diff --git a/hal/src/sercom/i2c.rs b/hal/src/sercom/i2c.rs index 64e9225132a..a69b8a681e7 100644 --- a/hal/src/sercom/i2c.rs +++ b/hal/src/sercom/i2c.rs @@ -276,6 +276,9 @@ pub use config::*; mod impl_ehal; +mod client; +pub use client::*; + /// Word size for an I2C message pub type Word = u8; diff --git a/hal/src/sercom/i2c/client.rs b/hal/src/sercom/i2c/client.rs new file mode 100644 index 00000000000..9d8c2dbef1c --- /dev/null +++ b/hal/src/sercom/i2c/client.rs @@ -0,0 +1,144 @@ +//! WIP module for I2C client/slave configuration + +mod config; +mod flags; +mod reg; + +use super::Error; +use super::InactiveTimeout; +use super::PadSet; +use super::Status; +use super::Word; +pub use config::{ClientAnyConfig, ClientConfig, ClientSpecificConfig}; +pub use flags::ClientFlags; +use reg::Registers; + +/// Abstraction over an I2C peripheral in client mode +pub struct I2cClient { + config: C, +} + +impl I2cClient { + /// Obtain a pointer to the `DATA` register. Necessary for DMA transfers. + #[inline] + pub fn data_ptr(&self) -> *mut Word { + self.config.as_ref().registers.data_ptr() + } + + /// Read the interrupt flags + #[inline] + pub fn read_flags(&self) -> ClientFlags { + self.config.as_ref().registers.read_flags() + } + + /// Clear interrupt status flags + #[inline] + pub fn clear_flags(&mut self, flags: ClientFlags) { + self.config.as_mut().registers.clear_flags(flags); + } + + /// Enable interrupts for the specified flags. + #[inline] + pub fn enable_interrupts(&mut self, flags: ClientFlags) { + self.config.as_mut().registers.enable_interrupts(flags); + } + + /// Disable interrupts for the specified flags. + #[inline] + pub fn disable_interrupts(&mut self, flags: ClientFlags) { + self.config.as_mut().registers.disable_interrupts(flags); + } + + /// Read the status flags + #[inline] + pub fn read_status(&self) -> Status { + self.config.as_ref().registers.read_status() + } + + /// Clear the status flags + #[inline] + pub fn clear_status(&mut self, status: Status) { + self.config.as_mut().registers.clear_status(status); + } + + #[cfg(feature = "dma")] + #[inline] + pub(super) fn start_dma_write(&mut self, address: u8, xfer_len: u8) { + self.config + .as_mut() + .registers + .start_dma_write(address, xfer_len) + } + + #[cfg(feature = "dma")] + #[inline] + pub(super) fn start_dma_read(&mut self, address: u8, xfer_len: u8) { + self.config + .as_mut() + .registers + .start_dma_read(address, xfer_len) + } + + #[cfg(feature = "dma")] + #[inline] + pub(super) fn check_bus_status(&self) -> Result<(), Error> { + self.config.as_ref().registers.check_bus_status() + } + + #[inline] + fn do_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + self.config.as_mut().registers.do_write(addr, bytes) + } + + #[inline] + fn do_read(&mut self, addr: u8, bytes: &mut [u8]) -> Result<(), Error> { + self.config.as_mut().registers.do_read(addr, bytes) + } + + #[inline] + fn do_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + self.config + .as_mut() + .registers + .do_write_read(addr, bytes, buffer) + } + #[inline] + fn cmd_stop(&mut self) { + self.config.as_mut().registers.cmd_stop() + } + + /// Reconfigure the I2C peripheral. + /// + /// Calling this method will temporarily disable the SERCOM peripheral, as + /// some registers are enable-protected. This may interrupt any ongoing + /// transactions. + /// + /// ``` + /// use atsamd_hal::sercom::i2c::I2c; + /// i2c.reconfigure(|c| c.set_run_in_standby(false)); + /// ``` + #[inline] + pub fn reconfigure(&mut self, update: F) + where + F: FnOnce(&mut ClientSpecificConfig), + { + self.config.as_mut().registers.enable_peripheral(false); + update(self.config.as_mut()); + self.config.as_mut().registers.enable_peripheral(true); + } + + /// Disable the I2C peripheral and return the underlying [`Config`] + #[inline] + pub fn disable(self) -> C { + let mut config = self.config; + config.as_mut().registers.disable(); + config + } +} + +impl AsRef> for I2cClient> { + #[inline] + fn as_ref(&self) -> &ClientConfig

{ + self.config.as_ref() + } +} diff --git a/hal/src/sercom/i2c/client/config.rs b/hal/src/sercom/i2c/client/config.rs new file mode 100644 index 00000000000..6eb27b0ca81 --- /dev/null +++ b/hal/src/sercom/i2c/client/config.rs @@ -0,0 +1,182 @@ +use super::{I2cClient, InactiveTimeout, PadSet, Registers}; +use crate::{ + sercom::{Sercom, APB_CLK_CTRL}, + time::Hertz, + typelevel::{Is, Sealed}, +}; + +/// A configurable, disabled I2C peripheral +/// +/// This `struct` represents a configurable I2C peripheral in its disabled +/// state. It is generic over the set of [`Pads`]. +/// Upon creation, the [`Config`] takes ownership of the +/// [`Sercom`] and resets it, returning it configured as an I2C peripheral +/// with a default configuration in Master mode. +/// +/// [`Config`] uses a builder-pattern API to configure the peripheral, +/// culminating in a call to [`enable`], which consumes the [`Config`] and +/// returns an enabled [`I2c`]. +/// +/// [`enable`]: Config::enable +/// [`Pads`]: super::Pads +pub struct ClientConfig

+where + P: PadSet, +{ + pub(super) registers: Registers, + pads: P, + freq: Hertz, +} + +impl ClientConfig

{ + /// Create a new [`Config`] in the default configuration. + #[inline] + fn default(sercom: P::Sercom, pads: P, freq: impl Into) -> Self { + let mut registers = Registers::new(sercom); + registers.swrst(); + registers.set_op_mode(); + Self { + registers, + pads, + freq: freq.into(), + } + } + + /// Create a new [`Config`] in the default configuration + /// + /// This function will enable the corresponding APB clock, reset the + /// [`Sercom`] peripheral, and return a [`Config`] in the default + /// configuration. The only available operating mode is currently Master. + /// + /// Note that [`Config`] takes ownership of both the + /// PAC [`Sercom`] struct as well as the [`Pads`](super::Pads). + /// + /// Users must configure GCLK manually. The `freq` parameter represents the + /// GCLK frequency for this [`Sercom`] instance. + #[inline] + pub fn new( + apb_clk_ctrl: &APB_CLK_CTRL, + mut sercom: P::Sercom, + pads: P, + freq: impl Into, + ) -> Self { + sercom.enable_apb_clock(apb_clk_ctrl); + Self::default(sercom, pads, freq) + } +} + +impl ClientConfig

{ + /// Obtain a reference to the PAC `SERCOM` struct + /// + /// # Safety + /// + /// Directly accessing the `SERCOM` could break the invariants of the + /// type-level tracking in this module, so it is unsafe. + #[inline] + pub unsafe fn sercom(&self) -> &P::Sercom { + &self.registers.sercom + } + + /// Trigger the [`Sercom`]'s SWRST and return a [`Config`] in the + /// default configuration. + #[inline] + pub fn reset(self) -> Self { + Self::default(self.registers.sercom, self.pads, self.freq) + } + + /// Consume the [`Config`], reset the peripheral, and return the + /// [`Sercom`] and [`Pads`](super::Pads) + #[inline] + pub fn free(mut self) -> (P::Sercom, P) { + self.registers.swrst(); + (self.registers.free(), self.pads) + } + + /// Run in standby mode (builder pattern version) + /// + /// When set, the I2C peripheral will run in standby mode. See the + /// datasheet for more details. + #[inline] + pub fn run_in_standby(mut self, set: bool) -> Self { + self.set_run_in_standby(set); + self + } + + /// Run in standby mode (setter version) + /// + /// When set, the I2C peripheral will run in standby mode. See the + /// datasheet for more details. + #[inline] + pub fn set_run_in_standby(&mut self, set: bool) { + self.registers.set_run_in_standby(set); + } + + /// Get the current run in standby mode + #[inline] + pub fn get_run_in_standby(&self) -> bool { + self.registers.get_run_in_standby() + } + + /// Enable the I2C peripheral + /// + /// I2C transactions are not possible until the peripheral is enabled. + #[inline] + pub fn enable(mut self) -> I2cClient + where + Self: ClientAnyConfig, + { + self.registers.enable(); + + I2cClient { config: self } + } +} + +//============================================================================= +// AnyConfig +//============================================================================= + +/// Type class for all possible [`Config`] types +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Config`] types. See the [`AnyKind`] documentation for more details on the +/// pattern. +/// +/// In addition to the normal, [`AnyKind`] associated types. This trait also +/// copies the [`Sercom`] type, to make it easier +/// to apply bounds to these types at the next level of abstraction. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +/// [type class]: crate::typelevel#type-classes +pub trait ClientAnyConfig: Is> { + type Sercom: Sercom; + type Pads: PadSet; +} + +/// Type alias to recover the specific [`Config`] type from an implementation of +/// [`AnyConfig`] +pub type ClientSpecificConfig = ClientConfig<::Pads>; + +/// Type alias to recover the specific [`Sercom`] type from an implementation of +/// [`AnyConfig`] +pub type ConfigSercom = ::Sercom; + +impl Sealed for ClientConfig

{} + +impl ClientAnyConfig for ClientConfig

{ + type Sercom = P::Sercom; + type Pads = P; +} + +impl AsRef for ClientConfig

{ + #[inline] + fn as_ref(&self) -> &Self { + self + } +} + +impl AsMut for ClientConfig

{ + #[inline] + fn as_mut(&mut self) -> &mut Self { + self + } +} diff --git a/hal/src/sercom/i2c/client/flags.rs b/hal/src/sercom/i2c/client/flags.rs new file mode 100644 index 00000000000..eca59062c81 --- /dev/null +++ b/hal/src/sercom/i2c/client/flags.rs @@ -0,0 +1,20 @@ +use bitflags::bitflags; +use modular_bitfield::specifiers::{B1, B5}; +use modular_bitfield::*; + +bitflags! { + /// Interrupt bitflags for I2C client transactions + /// + /// The available interrupt flags are `PREC`, `AMATCH`, `DRDY`, and `ERROR`. The binary format of + /// the underlying bits exactly matches the INTFLAG bits. + pub struct ClientFlags: u8 { + /// Stop received interrupt + const PREC = 0x01; + /// Address match interrupt + const AMATCH = 0x02; + /// Data ready interrupt + const DRDY = 0x08; + /// Error interrupt + const ERROR = 0x80; + } +} diff --git a/hal/src/sercom/i2c/client/reg.rs b/hal/src/sercom/i2c/client/reg.rs new file mode 100644 index 00000000000..12e469ce3d9 --- /dev/null +++ b/hal/src/sercom/i2c/client/reg.rs @@ -0,0 +1,124 @@ +use super::{ClientFlags, Status}; +use crate::pac; +use crate::sercom::*; + +/// Mirror of super::reg::Registers, except with client-specific +/// configuration +pub(super) struct Registers { + pub sercom: S, +} + +// SAFETY: It is safe to implement Sync for Registers, because it erases the +// interior mutability of the PAC SERCOM struct. +unsafe impl Sync for Registers {} + +impl Registers { + /// Create a new `Registers` instance + #[inline] + pub(super) fn new(sercom: S) -> Self { + Self { sercom } + } + + /// Helper function to access the underlying `I2CS` from the given + /// `SERCOM` + #[inline] + fn i2c_slave(&self) -> &pac::sercom0::I2CS { + self.sercom.i2cs() + } + + /// Get a pointer to the `DATA` register + pub(super) fn data_ptr(&self) -> *mut T { + self.i2c_slave().data.as_ptr() as *mut _ + } + + /// Free the `Registers` struct and return the underlying `Sercom` + /// instance + pub(super) fn free(self) -> S { + self.sercom + } + + /// Reset the SERCOM peripheral + pub(super) fn swrst(&mut self) { + self.i2c_slave().ctrla.write(|w| w.swrst().set_bit()); + while self.i2c_slave().syncbusy.read().swrst().bit_is_set() {} + } + + /// Configure the SERCOM to use I2C slave mode + pub(super) fn set_op_mode(&mut self) { + let mode = pac::sercom0::i2cm::ctrla::MODE_A::I2C_SLAVE; + self.i2c_master() + .ctrla + .modify(|_, w| w.mode().variant(mode)); + } + + /// Run in standby mode + /// + /// When set, the I2C peripheral will run in standby mode. See the + /// datasheet for more details. + #[inline] + pub(super) fn set_run_in_standby(&mut self, set: bool) { + self.i2c_slave().ctrla.modify(|_, w| w.runstdby().bit(set)); + } + + /// Get the current run in standby mode + #[inline] + pub(super) fn get_run_in_standby(&self) -> bool { + self.i2c_slave().ctrla.read().runstdby().bit() + } + + /// Set Smart Mode + #[inline] + pub(super) fn set_smart_mode(&mut self, set: bool) { + self.i2c_slave().ctrlb.modify(|_, w| w.smen().bit(set)); + } + + /// Get the current Smart Mode setting + #[inline] + pub(super) fn get_smart_mode(&self) -> bool { + self.i2c_slave().ctrlb.read().smen().bit() + } + + /// Clear specified interrupt flags + #[inline] + pub(super) fn clear_flags(&mut self, flags: ClientFlags) { + self.i2c_slave() + .intflag + .modify(|_, w| unsafe { w.bits(flags.bits()) }); + } + + /// Read interrupt flags + #[inline] + pub(super) fn read_flags(&self) -> ClientFlags { + ClientFlags::from_bits_truncate(self.i2c_slave().intflag.read().bits()) + } + + /// Enable specified interrupts + #[inline] + pub(super) fn enable_interrupts(&mut self, flags: ClientFlags) { + self.i2c_slave() + .intenset + .write(|w| unsafe { w.bits(flags.bits()) }); + } + + /// Disable specified interrupts + #[inline] + pub(super) fn disable_interrupts(&mut self, flags: ClientFlags) { + self.i2c_slave() + .intenclr + .write(|w| unsafe { w.bits(flags.bits()) }); + } + + /// Clear specified status flags + #[inline] + pub(super) fn clear_status(&mut self, status: Status) { + self.i2c_slave() + .status + .modify(|_, w| unsafe { w.bits(status.into()) }); + } + + /// Read status flags + #[inline] + pub(super) fn read_status(&self) -> Status { + self.i2c_slave().status.read().bits().into() + } +} From f676a50d8dfa6ef5744be476e15e4173e5aed986 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 22 Feb 2023 11:46:07 -0500 Subject: [PATCH 2/4] Add I2C mode to the type system --- hal/src/sercom/i2c.rs | 20 ++++++++++++++++---- hal/src/sercom/i2c/config.rs | 26 ++++++++++++++++---------- hal/src/sercom/i2c/impl_ehal.rs | 8 ++++---- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/hal/src/sercom/i2c.rs b/hal/src/sercom/i2c.rs index a69b8a681e7..b9407bd788f 100644 --- a/hal/src/sercom/i2c.rs +++ b/hal/src/sercom/i2c.rs @@ -276,8 +276,8 @@ pub use config::*; mod impl_ehal; -mod client; -pub use client::*; +// mod client; +// pub use client::*; /// Word size for an I2C message pub type Word = u8; @@ -296,18 +296,30 @@ pub enum InactiveTimeout { Us205 = 0x3, } +pub trait I2cMode {} +/// Marker type for I2C master mode +pub struct Master; +/// Marker type for I2C client/slave mode +pub struct Sleve; +impl I2cMode for Master {} +impl I2cMode for Sleve {} + /// Abstraction over a I2C peripheral, allowing to perform I2C transactions. -pub struct I2c { +pub struct I2c, M: I2cMode = Master> { config: C, } -impl I2c { +/// Implementation for both modes +impl, M: I2cMode> I2c { /// Obtain a pointer to the `DATA` register. Necessary for DMA transfers. #[inline] pub fn data_ptr(&self) -> *mut Word { self.config.as_ref().registers.data_ptr() } +} +/// Host-only implementation +impl> I2c { /// Read the interrupt flags #[inline] pub fn read_flags(&self) -> Flags { diff --git a/hal/src/sercom/i2c/config.rs b/hal/src/sercom/i2c/config.rs index c1ec5308079..e6c4533db3c 100644 --- a/hal/src/sercom/i2c/config.rs +++ b/hal/src/sercom/i2c/config.rs @@ -1,6 +1,6 @@ //! I2C [`Config`] definition and implementation -use super::{I2c, InactiveTimeout, PadSet, Registers}; +use super::{I2c, I2cMode, InactiveTimeout, Master, PadSet, Registers}; use crate::{ pac::sercom0::i2cm::ctrla::MODE_A, sercom::*, @@ -26,15 +26,17 @@ use crate::{ /// /// [`enable`]: Config::enable /// [`Pads`]: super::Pads -pub struct Config

+pub struct Config where P: PadSet, { pub(super) registers: Registers, pads: P, + op_mode: M, freq: Hertz, } +/// Generic configuration impl Config

{ /// Create a new [`Config`] in the default configuration. #[inline] @@ -45,6 +47,7 @@ impl Config

{ Self { registers, pads, + op_mode: Master, freq: freq.into(), } } @@ -72,7 +75,8 @@ impl Config

{ } } -impl Config

{ +/// Configuration relevant when in host mode +impl Config { /// Obtain a reference to the PAC `SERCOM` struct /// /// # Safety @@ -224,9 +228,9 @@ impl Config

{ /// /// I2C transactions are not possible until the peripheral is enabled. #[inline] - pub fn enable(mut self) -> I2c + pub fn enable(mut self) -> I2c where - Self: AnyConfig, + Self: AnyConfig, { self.registers.enable(); @@ -253,31 +257,33 @@ impl Config

{ pub trait AnyConfig: Is> { type Sercom: Sercom; type Pads: PadSet; + type Mode: I2cMode; // default to super::Master when associated_type_defaults is stable } /// Type alias to recover the specific [`Config`] type from an implementation of /// [`AnyConfig`] -pub type SpecificConfig = Config<::Pads>; +pub type SpecificConfig = Config<::Pads, ::Mode>; /// Type alias to recover the specific [`Sercom`] type from an implementation of /// [`AnyConfig`] pub type ConfigSercom = ::Sercom; -impl Sealed for Config

{} +impl Sealed for Config {} -impl AnyConfig for Config

{ +impl AnyConfig for Config { type Sercom = P::Sercom; type Pads = P; + type Mode = M; } -impl AsRef for Config

{ +impl AsRef for Config { #[inline] fn as_ref(&self) -> &Self { self } } -impl AsMut for Config

{ +impl AsMut for Config { #[inline] fn as_mut(&mut self) -> &mut Self { self diff --git a/hal/src/sercom/i2c/impl_ehal.rs b/hal/src/sercom/i2c/impl_ehal.rs index c582c6dbb4f..2721fa2238a 100644 --- a/hal/src/sercom/i2c/impl_ehal.rs +++ b/hal/src/sercom/i2c/impl_ehal.rs @@ -1,9 +1,9 @@ //! `embedded-hal` trait implementations for [`I2c`]s -use super::{config::AnyConfig, flags::Error, I2c}; +use super::{config::AnyConfig, flags::Error, I2c, Master}; use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; -impl Write for I2c { +impl> Write for I2c { type Error = Error; fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { @@ -13,7 +13,7 @@ impl Write for I2c { } } -impl Read for I2c { +impl> Read for I2c { type Error = Error; fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { @@ -23,7 +23,7 @@ impl Read for I2c { } } -impl WriteRead for I2c { +impl> WriteRead for I2c { type Error = Error; fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { From a5b52ba43b41773984b1f45fa87a8557a77ba608 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 22 Feb 2023 12:11:33 -0500 Subject: [PATCH 3/4] Update data_ptr call --- hal/src/sercom/i2c/config.rs | 7 ++++--- hal/src/sercom/i2c/reg.rs | 30 ++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/hal/src/sercom/i2c/config.rs b/hal/src/sercom/i2c/config.rs index e6c4533db3c..bafd03ed458 100644 --- a/hal/src/sercom/i2c/config.rs +++ b/hal/src/sercom/i2c/config.rs @@ -7,6 +7,7 @@ use crate::{ time::Hertz, typelevel::{Is, Sealed}, }; +use core::marker::PhantomData; //============================================================================= // Config @@ -30,9 +31,9 @@ pub struct Config where P: PadSet, { - pub(super) registers: Registers, + pub(super) registers: Registers, pads: P, - op_mode: M, + op_mode: PhantomData, freq: Hertz, } @@ -47,8 +48,8 @@ impl Config

{ Self { registers, pads, - op_mode: Master, freq: freq.into(), + op_mode: PhantomData, } } diff --git a/hal/src/sercom/i2c/reg.rs b/hal/src/sercom/i2c/reg.rs index a226782b8ed..6f3b4c5ce43 100644 --- a/hal/src/sercom/i2c/reg.rs +++ b/hal/src/sercom/i2c/reg.rs @@ -2,40 +2,50 @@ use super::flags::{BusState, Error}; use super::InactiveTimeout; -use super::{Flags, Status}; +use super::{Flags, I2cMode, Master, Status}; use crate::pac; use crate::sercom::*; use crate::time::Hertz; +use core::marker::PhantomData; const MASTER_ACT_READ: u8 = 2; const MASTER_ACT_STOP: u8 = 3; -pub(super) struct Registers { +pub(super) struct Registers { pub sercom: S, + mode: PhantomData, } // SAFETY: It is safe to implement Sync for Registers, because it erases the // interior mutability of the PAC SERCOM struct. -unsafe impl Sync for Registers {} +unsafe impl Sync for Registers {} -impl Registers { +impl Registers { /// Create a new `Registers` instance #[inline] pub(super) fn new(sercom: S) -> Self { - Self { sercom } + Self { + sercom, + mode: PhantomData, + } + } + + /// Get a pointer to the `DATA` register + pub(super) fn data_ptr(&self) -> *mut T { + // Use the master pointer, but both master and slave have the same data + // pointer. FIXME: once offset_of becomes available, add a debug assert + // for a sanity check. + self.sercom.i2cm().data.as_ptr().cast() } +} +impl Registers { /// Helper function to access the underlying `I2CM` from the given `SERCOM` #[inline] fn i2c_master(&self) -> &pac::sercom0::I2CM { self.sercom.i2cm() } - /// Get a pointer to the `DATA` register - pub(super) fn data_ptr(&self) -> *mut T { - self.i2c_master().data.as_ptr() as *mut _ - } - /// Free the `Registers` struct and return the underlying `Sercom` instance #[inline] pub(super) fn free(self) -> S { From 10dbf587b45ee5eb42db82a41c63c6e3e7b28f69 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 23 Feb 2023 02:44:56 -0500 Subject: [PATCH 4/4] WIP: work on adding abstraction over regblock --- hal/src/sercom/i2c.rs | 46 ++++++++---- hal/src/sercom/i2c/client.rs | 23 ------ hal/src/sercom/i2c/client/flags.rs | 17 ----- hal/src/sercom/i2c/config.rs | 65 +++++++++-------- hal/src/sercom/i2c/flags.rs | 18 +++++ hal/src/sercom/i2c/impl_ehal.rs | 8 +-- hal/src/sercom/i2c/mode.rs | 46 ++++++++++++ hal/src/sercom/i2c/reg.rs | 112 ++++++++++++++++------------- 8 files changed, 198 insertions(+), 137 deletions(-) create mode 100644 hal/src/sercom/i2c/mode.rs diff --git a/hal/src/sercom/i2c.rs b/hal/src/sercom/i2c.rs index b9407bd788f..1376abea13e 100644 --- a/hal/src/sercom/i2c.rs +++ b/hal/src/sercom/i2c.rs @@ -274,10 +274,12 @@ pub use flags::*; mod config; pub use config::*; +mod mode; +pub use mode::*; + mod impl_ehal; -// mod client; -// pub use client::*; +use crate::typelevel::Sealed; /// Word size for an I2C message pub type Word = u8; @@ -296,16 +298,8 @@ pub enum InactiveTimeout { Us205 = 0x3, } -pub trait I2cMode {} -/// Marker type for I2C master mode -pub struct Master; -/// Marker type for I2C client/slave mode -pub struct Sleve; -impl I2cMode for Master {} -impl I2cMode for Sleve {} - /// Abstraction over a I2C peripheral, allowing to perform I2C transactions. -pub struct I2c, M: I2cMode = Master> { +pub struct I2c, M: I2cMode = Host> { config: C, } @@ -319,7 +313,7 @@ impl, M: I2cMode> I2c { } /// Host-only implementation -impl> I2c { +impl> I2c { /// Read the interrupt flags #[inline] pub fn read_flags(&self) -> Flags { @@ -431,6 +425,34 @@ impl> I2c { } } +/// Client implementation +impl> I2c { + /// Read the interrupt flags + #[inline] + pub fn read_flags(&self) -> ClientFlags { + self.config.as_ref().registers.read_flags() + } + + /// Clear interrupt status flags + #[inline] + pub fn clear_flags(&mut self, flags: ClientFlags) { + self.config.as_mut().registers.clear_flags(flags); + } + + /// Enable interrupts for the specified flags. + #[inline] + pub fn enable_interrupts(&mut self, flags: ClientFlags) { + self.config.as_mut().registers.enable_interrupts(flags); + } + + /// Disable interrupts for the specified flags. + #[inline] + pub fn disable_interrupts(&mut self, flags: ClientFlags) { + self.config.as_mut().registers.disable_interrupts(flags); + } + +} + impl AsRef> for I2c> { #[inline] fn as_ref(&self) -> &Config

{ diff --git a/hal/src/sercom/i2c/client.rs b/hal/src/sercom/i2c/client.rs index 9d8c2dbef1c..652357e50cf 100644 --- a/hal/src/sercom/i2c/client.rs +++ b/hal/src/sercom/i2c/client.rs @@ -25,29 +25,6 @@ impl I2cClient { self.config.as_ref().registers.data_ptr() } - /// Read the interrupt flags - #[inline] - pub fn read_flags(&self) -> ClientFlags { - self.config.as_ref().registers.read_flags() - } - - /// Clear interrupt status flags - #[inline] - pub fn clear_flags(&mut self, flags: ClientFlags) { - self.config.as_mut().registers.clear_flags(flags); - } - - /// Enable interrupts for the specified flags. - #[inline] - pub fn enable_interrupts(&mut self, flags: ClientFlags) { - self.config.as_mut().registers.enable_interrupts(flags); - } - - /// Disable interrupts for the specified flags. - #[inline] - pub fn disable_interrupts(&mut self, flags: ClientFlags) { - self.config.as_mut().registers.disable_interrupts(flags); - } /// Read the status flags #[inline] diff --git a/hal/src/sercom/i2c/client/flags.rs b/hal/src/sercom/i2c/client/flags.rs index eca59062c81..48315a65f40 100644 --- a/hal/src/sercom/i2c/client/flags.rs +++ b/hal/src/sercom/i2c/client/flags.rs @@ -1,20 +1,3 @@ use bitflags::bitflags; use modular_bitfield::specifiers::{B1, B5}; use modular_bitfield::*; - -bitflags! { - /// Interrupt bitflags for I2C client transactions - /// - /// The available interrupt flags are `PREC`, `AMATCH`, `DRDY`, and `ERROR`. The binary format of - /// the underlying bits exactly matches the INTFLAG bits. - pub struct ClientFlags: u8 { - /// Stop received interrupt - const PREC = 0x01; - /// Address match interrupt - const AMATCH = 0x02; - /// Data ready interrupt - const DRDY = 0x08; - /// Error interrupt - const ERROR = 0x80; - } -} diff --git a/hal/src/sercom/i2c/config.rs b/hal/src/sercom/i2c/config.rs index bafd03ed458..a0b1b08d3fe 100644 --- a/hal/src/sercom/i2c/config.rs +++ b/hal/src/sercom/i2c/config.rs @@ -1,8 +1,7 @@ //! I2C [`Config`] definition and implementation -use super::{I2c, I2cMode, InactiveTimeout, Master, PadSet, Registers}; +use super::{Host, I2c, I2cMode, InactiveTimeout, PadSet, Registers}; use crate::{ - pac::sercom0::i2cm::ctrla::MODE_A, sercom::*, time::Hertz, typelevel::{Is, Sealed}, @@ -27,24 +26,46 @@ use core::marker::PhantomData; /// /// [`enable`]: Config::enable /// [`Pads`]: super::Pads -pub struct Config +pub struct Config where P: PadSet, { pub(super) registers: Registers, pads: P, - op_mode: PhantomData, freq: Hertz, + op_mode: PhantomData, } /// Generic configuration -impl Config

{ +impl Config { + /// Obtain a reference to the PAC `SERCOM` struct + /// + /// # Safety + /// + /// Directly accessing the `SERCOM` could break the invariants of the + /// type-level tracking in this module, so it is unsafe. + #[inline] + pub unsafe fn sercom(&self) -> &P::Sercom { + &self.registers.sercom + } + + /// Consume the [`Config`], reset the peripheral, and return the [`Sercom`] + /// and [`Pads`](super::Pads) + #[inline] + pub fn free(mut self) -> (P::Sercom, P) { + self.registers.swrst(); + (self.registers.free(), self.pads) + } +} + +/// Configuration relevant when in host mode +impl Config { /// Create a new [`Config`] in the default configuration. #[inline] fn default(sercom: P::Sercom, pads: P, freq: impl Into) -> Self { let mut registers = Registers::new(sercom); registers.swrst(); - registers.set_op_mode(MODE_A::I2C_MASTER); + registers.set_master_mode(); Self { registers, pads, @@ -74,21 +95,7 @@ impl Config

{ sercom.enable_apb_clock(apb_clk_ctrl); Self::default(sercom, pads, freq) } -} - -/// Configuration relevant when in host mode -impl Config { - /// Obtain a reference to the PAC `SERCOM` struct - /// - /// # Safety - /// - /// Directly accessing the `SERCOM` could break the invariants of the - /// type-level tracking in this module, so it is unsafe. - #[inline] - pub unsafe fn sercom(&self) -> &P::Sercom { - &self.registers.sercom - } - + /// Trigger the [`Sercom`]'s SWRST and return a [`Config`] in the /// default configuration. #[inline] @@ -96,14 +103,6 @@ impl Config { Config::default(self.registers.sercom, self.pads, self.freq) } - /// Consume the [`Config`], reset the peripheral, and return the [`Sercom`] - /// and [`Pads`](super::Pads) - #[inline] - pub fn free(mut self) -> (P::Sercom, P) { - self.registers.swrst(); - (self.registers.free(), self.pads) - } - /// Run in standby mode (builder pattern version) /// /// When set, the I2C peripheral will run in standby mode. See the @@ -229,9 +228,9 @@ impl Config { /// /// I2C transactions are not possible until the peripheral is enabled. #[inline] - pub fn enable(mut self) -> I2c + pub fn enable(mut self) -> I2c where - Self: AnyConfig, + Self: AnyConfig, { self.registers.enable(); @@ -255,10 +254,10 @@ impl Config { /// /// [`AnyKind`]: crate::typelevel#anykind-trait-pattern /// [type class]: crate::typelevel#type-classes -pub trait AnyConfig: Is> { +pub trait AnyConfig: Is> + Sealed { type Sercom: Sercom; type Pads: PadSet; - type Mode: I2cMode; // default to super::Master when associated_type_defaults is stable + type Mode: I2cMode; } /// Type alias to recover the specific [`Config`] type from an implementation of diff --git a/hal/src/sercom/i2c/flags.rs b/hal/src/sercom/i2c/flags.rs index c18bda686b3..9df0e0cc2d5 100644 --- a/hal/src/sercom/i2c/flags.rs +++ b/hal/src/sercom/i2c/flags.rs @@ -21,6 +21,24 @@ bitflags! { } } +bitflags! { + /// Interrupt bitflags for I2C client transactions + /// + /// The available interrupt flags are `PREC`, `AMATCH`, `DRDY`, and `ERROR`. The binary format of + /// the underlying bits exactly matches the INTFLAG bits. + pub struct ClientFlags: u8 { + /// Stop received interrupt + const PREC = 0x01; + /// Address match interrupt + const AMATCH = 0x02; + /// Data ready interrupt + const DRDY = 0x08; + /// Error interrupt + const ERROR = 0x80; + } +} + + /// Type representing the current bus state #[derive(BitfieldSpecifier, PartialEq)] pub enum BusState { diff --git a/hal/src/sercom/i2c/impl_ehal.rs b/hal/src/sercom/i2c/impl_ehal.rs index 2721fa2238a..14bcfeaeb91 100644 --- a/hal/src/sercom/i2c/impl_ehal.rs +++ b/hal/src/sercom/i2c/impl_ehal.rs @@ -1,9 +1,9 @@ //! `embedded-hal` trait implementations for [`I2c`]s -use super::{config::AnyConfig, flags::Error, I2c, Master}; +use super::{config::AnyConfig, flags::Error, Host, I2c}; use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; -impl> Write for I2c { +impl> Write for I2c { type Error = Error; fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { @@ -13,7 +13,7 @@ impl> Write for I2c { } } -impl> Read for I2c { +impl> Read for I2c { type Error = Error; fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { @@ -23,7 +23,7 @@ impl> Read for I2c { } } -impl> WriteRead for I2c { +impl> WriteRead for I2c { type Error = Error; fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { diff --git a/hal/src/sercom/i2c/mode.rs b/hal/src/sercom/i2c/mode.rs new file mode 100644 index 00000000000..54ebb42359a --- /dev/null +++ b/hal/src/sercom/i2c/mode.rs @@ -0,0 +1,46 @@ +//! I2C host/ + +use crate::pac; +use crate::typelevel::Sealed; +use crate::sercom::Sercom; +use super::{Flags, ClientFlags}; + +/// Representation of an I2C mode: host or client +pub trait I2cMode: Sealed { + // Allows for some method reuse + #[doc(hidden)] + type Flag; + /// Register block type + #[doc(hidden)] + type RegBlock; + + fn get_regblock(sercom: &S) -> Self::RegBlock; +} + +/// Marker type for I2C host mode +pub struct Host; + +impl Sealed for Host {} + +impl I2cMode for Host { + type Flag = Flags; + type RegBlock = crate::pac::sercom0::I2CM; + + fn get_regblock(sercom: &S) -> Self::RegBlock{ + sercom.i2cm() + } +} + +/// Marker type for I2C client mode +pub struct Client; + +impl Sealed for Client {} + +impl I2cMode for Client { + type Flag = ClientFlags; + type RegBlock = crate::pac::sercom0::I2CM; + + fn get_regblock(sercom: &S) -> Self::RegBlock { + sercom.i2cs() + } +} diff --git a/hal/src/sercom/i2c/reg.rs b/hal/src/sercom/i2c/reg.rs index 6f3b4c5ce43..cdb443cbf93 100644 --- a/hal/src/sercom/i2c/reg.rs +++ b/hal/src/sercom/i2c/reg.rs @@ -2,7 +2,7 @@ use super::flags::{BusState, Error}; use super::InactiveTimeout; -use super::{Flags, I2cMode, Master, Status}; +use super::{Flags, Host, Client, I2cMode, Status}; use crate::pac; use crate::sercom::*; use crate::time::Hertz; @@ -20,6 +20,7 @@ pub(super) struct Registers { // interior mutability of the PAC SERCOM struct. unsafe impl Sync for Registers {} +/// Common implementation for host and client modes impl Registers { /// Create a new `Registers` instance #[inline] @@ -35,15 +36,7 @@ impl Registers { // Use the master pointer, but both master and slave have the same data // pointer. FIXME: once offset_of becomes available, add a debug assert // for a sanity check. - self.sercom.i2cm().data.as_ptr().cast() - } -} - -impl Registers { - /// Helper function to access the underlying `I2CM` from the given `SERCOM` - #[inline] - fn i2c_master(&self) -> &pac::sercom0::I2CM { - self.sercom.i2cm() + self.regblock().data.as_ptr().cast() } /// Free the `Registers` struct and return the underlying `Sercom` instance @@ -52,17 +45,29 @@ impl Registers { self.sercom } + /// Helper function to access the underlying `I2CM` or `I2CS` from the given `SERCOM` + #[inline] + fn regblock(&self) -> M::RegBlock { + M::get_regblock(&self.sercom) + } + /// Reset the SERCOM peripheral #[inline] pub(super) fn swrst(&mut self) { - self.i2c_master().ctrla.write(|w| w.swrst().set_bit()); - while self.i2c_master().syncbusy.read().swrst().bit_is_set() {} + // Master and slave have these two registers in the same locations. + // FIXME: add offset_of sanity check + self.regblock().ctrla.write(|w| w.swrst().set_bit()); + while self.regblock().syncbusy.read().swrst().bit_is_set() {} } +} +/// Implementation for host mode +impl Registers { /// Configure the SERCOM to use I2C master mode #[inline] - pub(super) fn set_op_mode(&mut self, mode: pac::sercom0::i2cm::ctrla::MODE_A) { - self.i2c_master() + pub(super) fn set_master_mode(&mut self) { + let mode = pac::sercom0::i2cm::ctrla::MODE_A::I2C_MASTER; + self.regblock() .ctrla .modify(|_, w| w.mode().variant(mode)); } @@ -74,14 +79,14 @@ impl Registers { let baud = (clock_freq.0 / (2 * baud.into().0) - 1) as u8; unsafe { - self.i2c_master().baud.modify(|_, w| w.baud().bits(baud)); + self.regblock().baud.modify(|_, w| w.baud().bits(baud)); } } /// Get the contents of the `BAUD` register. #[inline] pub(super) fn get_baud(&self) -> u32 { - self.i2c_master().baud.read().bits() + self.regblock().baud.read().bits() } /// Set SCL Low Time-Out @@ -93,7 +98,7 @@ impl Registers { /// STATUS.BUSERR status bits will be set. #[inline] pub(super) fn set_low_timeout(&mut self, set: bool) { - self.i2c_master() + self.regblock() .ctrla .modify(|_, w| w.lowtouten().bit(set)); } @@ -107,7 +112,7 @@ impl Registers { /// STATUS.BUSERR status bits will be set. #[inline] pub(super) fn get_low_timeout(&mut self) -> bool { - self.i2c_master().ctrla.read().lowtouten().bit() + self.regblock().ctrla.read().lowtouten().bit() } /// Set the inactive timeout after which the bus state will be set to IDLE. @@ -117,7 +122,7 @@ impl Registers { // `unused_unsafe` is allowed here because `inactout().bits()` is unsafe on // thumbv6m targets, but not thumbv7em. #[allow(unused_unsafe)] - self.i2c_master() + self.regblock() .ctrla .modify(|_, w| unsafe { w.inactout().bits(timeout as u8) }) } @@ -125,7 +130,7 @@ impl Registers { /// Get the inactive timeout setting. #[inline] pub(super) fn get_inactive_timeout(&mut self) -> InactiveTimeout { - let timeout = self.i2c_master().ctrla.read().inactout().bits(); + let timeout = self.regblock().ctrla.read().inactout().bits(); match timeout { 0 => InactiveTimeout::Disabled, @@ -142,31 +147,31 @@ impl Registers { /// datasheet for more details. #[inline] pub(super) fn set_run_in_standby(&mut self, set: bool) { - self.i2c_master().ctrla.modify(|_, w| w.runstdby().bit(set)); + self.regblock().ctrla.modify(|_, w| w.runstdby().bit(set)); } /// Get the current run in standby mode #[inline] pub(super) fn get_run_in_standby(&self) -> bool { - self.i2c_master().ctrla.read().runstdby().bit() + self.regblock().ctrla.read().runstdby().bit() } /// Set Smart Mode #[inline] pub(super) fn set_smart_mode(&mut self, set: bool) { - self.i2c_master().ctrlb.modify(|_, w| w.smen().bit(set)); + self.regblock().ctrlb.modify(|_, w| w.smen().bit(set)); } /// Get the current Smart Mode setting #[inline] pub(super) fn get_smart_mode(&self) -> bool { - self.i2c_master().ctrlb.read().smen().bit() + self.regblock().ctrlb.read().smen().bit() } /// Clear specified interrupt flags #[inline] pub(super) fn clear_flags(&mut self, flags: Flags) { - self.i2c_master() + self.regblock() .intflag .modify(|_, w| unsafe { w.bits(flags.bits()) }); } @@ -174,13 +179,13 @@ impl Registers { /// Read interrupt flags #[inline] pub(super) fn read_flags(&self) -> Flags { - Flags::from_bits_truncate(self.i2c_master().intflag.read().bits()) + Flags::from_bits_truncate(self.regblock().intflag.read().bits()) } /// Enable specified interrupts #[inline] pub(super) fn enable_interrupts(&mut self, flags: Flags) { - self.i2c_master() + self.regblock() .intenset .write(|w| unsafe { w.bits(flags.bits()) }); } @@ -188,7 +193,7 @@ impl Registers { /// Disable specified interrupts #[inline] pub(super) fn disable_interrupts(&mut self, flags: Flags) { - self.i2c_master() + self.regblock() .intenclr .write(|w| unsafe { w.bits(flags.bits()) }); } @@ -196,7 +201,7 @@ impl Registers { /// Clear specified status flags #[inline] pub(super) fn clear_status(&mut self, status: Status) { - self.i2c_master() + self.regblock() .status .modify(|_, w| unsafe { w.bits(status.into()) }); } @@ -204,7 +209,7 @@ impl Registers { /// Read status flags #[inline] pub(super) fn read_status(&self) -> Status { - self.i2c_master().status.read().bits().into() + self.regblock().status.read().bits().into() } pub(super) fn check_bus_status(&self) -> Result<(), Error> { @@ -233,13 +238,13 @@ impl Registers { // RESET the `ADDR` register, then signal start and transmit encoded // address for a write transaction. unsafe { - self.i2c_master() + self.regblock() .addr .write(|w| w.addr().bits(encode_write_address(addr))); } // wait for transmission to complete - while !self.i2c_master().intflag.read().mb().bit_is_set() {} + while !self.regblock().intflag.read().mb().bit_is_set() {} self.read_status().check_bus_error() } @@ -254,21 +259,21 @@ impl Registers { self.check_bus_status()?; - self.i2c_master() + self.regblock() .intflag .modify(|_, w| w.error().clear_bit()); // RESET the `ADDR` register, then signal start (or repeated start if // appropriate) and transmit encoded address for a read transaction. unsafe { - self.i2c_master() + self.regblock() .addr .write(|w| w.addr().bits(encode_read_address(addr))); } // wait for transmission to complete loop { - let intflag = self.i2c_master().intflag.read(); + let intflag = self.regblock().intflag.read(); // If arbitration was lost, it will be signalled via the mb bit if intflag.mb().bit_is_set() { return Err(Error::ArbitrationLost); @@ -297,7 +302,7 @@ impl Registers { self.enable(); } - self.i2c_master().addr.write(|w| unsafe { + self.regblock().addr.write(|w| unsafe { w.addr().bits(encode_write_address(address)); w.len().bits(xfer_len); w.lenen().set_bit() @@ -322,7 +327,7 @@ impl Registers { self.enable(); } - self.i2c_master().addr.write(|w| unsafe { + self.regblock().addr.write(|w| unsafe { w.addr().bits(encode_read_address(address)); w.len().bits(xfer_len); w.lenen().set_bit() @@ -333,7 +338,7 @@ impl Registers { #[inline] pub(super) fn issue_command(&mut self, cmd: u8) { - self.i2c_master() + self.regblock() .ctrlb .modify(|_, w| unsafe { w.cmd().bits(cmd) }); @@ -348,7 +353,7 @@ impl Registers { #[inline] pub(super) fn cmd_read(&mut self) { unsafe { - self.i2c_master().ctrlb.modify(|_, w| { + self.regblock().ctrlb.modify(|_, w| { // clear bit means send ack w.ackact().clear_bit(); w.cmd().bits(MASTER_ACT_READ) @@ -361,11 +366,11 @@ impl Registers { pub(super) fn send_bytes(&mut self, bytes: &[u8]) -> Result<(), Error> { for b in bytes { unsafe { - self.i2c_master().data.write(|w| w.bits(*b)); + self.regblock().data.write(|w| w.bits(*b)); } loop { - let intflag = self.i2c_master().intflag.read(); + let intflag = self.regblock().intflag.read(); if intflag.mb().bit_is_set() || intflag.error().bit_is_set() { break; } @@ -377,8 +382,8 @@ impl Registers { #[inline] pub(super) fn read_one(&mut self) -> u8 { - while !self.i2c_master().intflag.read().sb().bit_is_set() {} - self.i2c_master().data.read().bits() + while !self.regblock().intflag.read().sb().bit_is_set() {} + self.regblock().data.read().bits() } #[inline] @@ -400,7 +405,7 @@ impl Registers { // arrange to send nack on next command to // stop slave from transmitting more data - self.i2c_master().ctrlb.modify(|_, w| w.ackact().set_bit()); + self.regblock().ctrlb.modify(|_, w| w.ackact().set_bit()); Ok(()) } @@ -435,7 +440,7 @@ impl Registers { #[inline] pub(super) fn bus_idle(&mut self) { // Set the bus idle - self.i2c_master() + self.regblock() .status .modify(|_, w| unsafe { w.busstate().bits(BusState::Idle as u8) }); // Wait for it to take effect @@ -444,7 +449,7 @@ impl Registers { #[inline] fn sync_sysop(&mut self) { - while self.i2c_master().syncbusy.read().sysop().bit_is_set() {} + while self.regblock().syncbusy.read().sysop().bit_is_set() {} } /// Enable the I2C peripheral @@ -467,10 +472,21 @@ impl Registers { /// synchronize. #[inline] pub(super) fn enable_peripheral(&mut self, enable: bool) { - self.i2c_master() + self.regblock() .ctrla .modify(|_, w| w.enable().bit(enable)); - while self.i2c_master().syncbusy.read().enable().bit_is_set() {} + while self.regblock().syncbusy.read().enable().bit_is_set() {} + } +} + +impl Registers { + /// Configure the SERCOM to use I2C slave mode + #[inline] + pub(super) fn set_slave_mode(&mut self) { + let mode = pac::sercom0::i2cs::ctrla::MODE_A::I2C_SLAVE; + self.regblock() + .ctrla + .modify(|_, w| w.mode().variant(mode)); } }