Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add I2C slave mode to fix #636 #671

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would try to standardize what terminology we use: master/slave vs host/client. Personally I don't care either way, with maybe a slight preference towards whatever the datasheet uses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bringing this up, I meant to ask. The datasheet does use host/client, so I can go ahead with that.

// pub use client::*;

/// Word size for an I2C message
pub type Word = u8;

Expand All @@ -293,18 +296,30 @@ pub enum InactiveTimeout {
Us205 = 0x3,
}

pub trait I2cMode {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make this trait sealed so that it can't be implemented outside of this crate:

pub trait I2cMode: Sealed {}

and also add a short doc comment.

/// 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<C: AnyConfig> {
pub struct I2c<C: AnyConfig<Mode = M>, M: I2cMode = Master> {
config: C,
}

impl<C: AnyConfig> I2c<C> {
/// Implementation for both modes
impl<C: AnyConfig<Mode = M>, M: I2cMode> I2c<C, M> {
/// 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<C: AnyConfig<Mode = Master>> I2c<C, Master> {
/// Read the interrupt flags
#[inline]
pub fn read_flags(&self) -> Flags {
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