Skip to content

Commit

Permalink
Add I2C slave mode to fix atsamd-rs#636
Browse files Browse the repository at this point in the history
  • Loading branch information
tgross35 committed Feb 22, 2023
1 parent 7d85efd commit 5eb454c
Show file tree
Hide file tree
Showing 5 changed files with 473 additions and 0 deletions.
3 changes: 3 additions & 0 deletions hal/src/sercom/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
144 changes: 144 additions & 0 deletions hal/src/sercom/i2c/client.rs
Original file line number Diff line number Diff line change
@@ -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<C: ClientAnyConfig> {
config: C,
}

impl<C: ClientAnyConfig> I2cClient<C> {
/// 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<F>(&mut self, update: F)
where
F: FnOnce(&mut ClientSpecificConfig<C>),
{
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<P: PadSet> AsRef<ClientConfig<P>> for I2cClient<ClientConfig<P>> {
#[inline]
fn as_ref(&self) -> &ClientConfig<P> {
self.config.as_ref()
}
}
182 changes: 182 additions & 0 deletions hal/src/sercom/i2c/client/config.rs
Original file line number Diff line number Diff line change
@@ -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<P>
where
P: PadSet,
{
pub(super) registers: Registers<P::Sercom>,
pads: P,
freq: Hertz,
}

impl<P: PadSet> ClientConfig<P> {
/// Create a new [`Config`] in the default configuration.
#[inline]
fn default(sercom: P::Sercom, pads: P, freq: impl Into<Hertz>) -> 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<Hertz>,
) -> Self {
sercom.enable_apb_clock(apb_clk_ctrl);
Self::default(sercom, pads, freq)
}
}

impl<P: PadSet> ClientConfig<P> {
/// 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<Self>
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 = ClientSpecificConfig<Self>> {
type Sercom: Sercom;
type Pads: PadSet<Sercom = Self::Sercom>;
}

/// Type alias to recover the specific [`Config`] type from an implementation of
/// [`AnyConfig`]
pub type ClientSpecificConfig<C> = ClientConfig<<C as ClientAnyConfig>::Pads>;

/// Type alias to recover the specific [`Sercom`] type from an implementation of
/// [`AnyConfig`]
pub type ConfigSercom<C> = <C as ClientAnyConfig>::Sercom;

impl<P: PadSet> Sealed for ClientConfig<P> {}

impl<P: PadSet> ClientAnyConfig for ClientConfig<P> {
type Sercom = P::Sercom;
type Pads = P;
}

impl<P: PadSet> AsRef<Self> for ClientConfig<P> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}

impl<P: PadSet> AsMut<Self> for ClientConfig<P> {
#[inline]
fn as_mut(&mut self) -> &mut Self {
self
}
}
20 changes: 20 additions & 0 deletions hal/src/sercom/i2c/client/flags.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 5eb454c

Please sign in to comment.