/**
* \file
*
* \brief SAM SERCOM I2C Slave Driver
*
* Copyright (c) 2013-2016 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \asf_license_stop
*
*/
/*
* Support and FAQ: visit Atmel Support
*/
#ifndef I2C_SLAVE_H_INCLUDED
#define I2C_SLAVE_H_INCLUDED
#include "i2c_common.h"
#include
#include
#if I2C_SLAVE_CALLBACK_MODE == true
# include
#endif
#ifndef PINMUX_DEFAULT
# define PINMUX_DEFAULT 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup asfdoc_sam0_sercom_i2c_group
*
* @{
*
*/
/**
* \name I2C Slave Status Flags
*
* I2C slave status flags, returned by \ref i2c_slave_get_status() and cleared
* by \ref i2c_slave_clear_status().
* @{
*/
/** Address Match.
* \note Should only be cleared internally by driver.
*/
#define I2C_SLAVE_STATUS_ADDRESS_MATCH (1UL << 0)
/** Data Ready. */
#define I2C_SLAVE_STATUS_DATA_READY (1UL << 1)
/** Stop Received. */
#define I2C_SLAVE_STATUS_STOP_RECEIVED (1UL << 2)
/** Clock Hold.
* \note Cannot be cleared, only valid when I2C_SLAVE_STATUS_ADDRESS_MATCH is
* set.
*/
#define I2C_SLAVE_STATUS_CLOCK_HOLD (1UL << 3)
/** SCL Low Timeout. */
#define I2C_SLAVE_STATUS_SCL_LOW_TIMEOUT (1UL << 4)
/** Repeated Start.
* \note Cannot be cleared, only valid when I2C_SLAVE_STATUS_ADDRESS_MATCH is
* set.
*/
#define I2C_SLAVE_STATUS_REPEATED_START (1UL << 5)
/** Received not acknowledge.
* \note Cannot be cleared.
*/
#define I2C_SLAVE_STATUS_RECEIVED_NACK (1UL << 6)
/** Transmit Collision. */
#define I2C_SLAVE_STATUS_COLLISION (1UL << 7)
/** Bus error. */
#define I2C_SLAVE_STATUS_BUS_ERROR (1UL << 8)
/** @} */
/**
* \brief I2C slave packet for read/write
*
* Structure to be used when transferring I2C slave packets.
*/
struct i2c_slave_packet {
/** Length of data array */
uint16_t data_length;
/** Data array containing all data to be transferred */
uint8_t *data;
};
#if I2C_SLAVE_CALLBACK_MODE == true
/**
* \brief Callback types
*
* The available callback types for the I2C slave.
*/
enum i2c_slave_callback {
/** Callback for packet write complete */
I2C_SLAVE_CALLBACK_WRITE_COMPLETE,
/** Callback for packet read complete */
I2C_SLAVE_CALLBACK_READ_COMPLETE,
/**
* Callback for read request from master - can be used to
* issue a write
*/
I2C_SLAVE_CALLBACK_READ_REQUEST,
/**
* Callback for write request from master - can be used to issue a read
*/
I2C_SLAVE_CALLBACK_WRITE_REQUEST,
/** Callback for error */
I2C_SLAVE_CALLBACK_ERROR,
/**
* Callback for error in last transfer. Discovered on a new address
* interrupt.
*/
I2C_SLAVE_CALLBACK_ERROR_LAST_TRANSFER,
# if !defined(__DOXYGEN__)
/** Total number of callbacks */
_I2C_SLAVE_CALLBACK_N,
# endif
};
# if !defined(__DOXYGEN__)
/** Software module prototype */
struct i2c_slave_module;
/** Callback type */
typedef void (*i2c_slave_callback_t)(
struct i2c_slave_module *const module);
# endif
#endif
/**
* \brief Enum for the possible SDA hold times with respect to the negative
* edge of SCL
*
* Enum for the possible SDA hold times with respect to the negative edge
* of SCL.
*/
enum i2c_slave_sda_hold_time {
/** SDA hold time disabled */
I2C_SLAVE_SDA_HOLD_TIME_DISABLED =
((SERCOM_I2CS_CTRLA_SDAHOLD_Msk & ((0) << SERCOM_I2CS_CTRLA_SDAHOLD_Pos))),
/** SDA hold time 50ns - 100ns */
I2C_SLAVE_SDA_HOLD_TIME_50NS_100NS =
((SERCOM_I2CS_CTRLA_SDAHOLD_Msk & ((1) << SERCOM_I2CS_CTRLA_SDAHOLD_Pos))),
/** SDA hold time 300ns - 600ns */
I2C_SLAVE_SDA_HOLD_TIME_300NS_600NS =
((SERCOM_I2CS_CTRLA_SDAHOLD_Msk & ((2) << SERCOM_I2CS_CTRLA_SDAHOLD_Pos))),
/** SDA hold time 400ns - 800ns */
I2C_SLAVE_SDA_HOLD_TIME_400NS_800NS =
((SERCOM_I2CS_CTRLA_SDAHOLD_Msk & ((3) << SERCOM_I2CS_CTRLA_SDAHOLD_Pos))),
};
/**
* \brief Enum for the possible address modes
*
* Enum for the possible address modes.
*/
enum i2c_slave_address_mode {
/** Address match on address_mask used as a mask to address */
I2C_SLAVE_ADDRESS_MODE_MASK = SERCOM_I2CS_CTRLB_AMODE(0),
/** Address math on both address and address_mask */
I2C_SLAVE_ADDRESS_MODE_TWO_ADDRESSES = SERCOM_I2CS_CTRLB_AMODE(1),
/**
* Address match on range of addresses between and including address and
* address_mask
*/
I2C_SLAVE_ADDRESS_MODE_RANGE = SERCOM_I2CS_CTRLB_AMODE(2),
};
/**
* \brief Enum for the direction of a request
*
* Enum for the direction of a request.
*/
enum i2c_slave_direction {
/** Read */
I2C_SLAVE_DIRECTION_READ,
/** Write */
I2C_SLAVE_DIRECTION_WRITE,
/** No direction */
I2C_SLAVE_DIRECTION_NONE,
};
#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED
/**
* \brief Enum for the transfer speed
*
* Enum for the transfer speed.
*/
enum i2c_slave_transfer_speed {
/** Standard-mode (Sm) up to 100KHz and Fast-mode (Fm) up to 400KHz */
I2C_SLAVE_SPEED_STANDARD_AND_FAST = SERCOM_I2CS_CTRLA_SPEED(0),
/** Fast-mode Plus (Fm+) up to 1MHz */
I2C_SLAVE_SPEED_FAST_MODE_PLUS = SERCOM_I2CS_CTRLA_SPEED(1),
/** High-speed mode (Hs-mode) up to 3.4MHz */
I2C_SLAVE_SPEED_HIGH_SPEED = SERCOM_I2CS_CTRLA_SPEED(2),
};
#endif
/**
* \brief SERCOM I2C slave driver software device instance structure.
*
* SERCOM I2C slave driver software instance structure, used to
* retain software state information of an associated hardware module instance.
*
* \note The fields of this structure should not be altered by the user
* application; they are reserved for module-internal use only.
*/
struct i2c_slave_module {
#if !defined(__DOXYGEN__)
/** Hardware instance initialized for the struct */
Sercom *hw;
/** Module lock */
volatile bool locked;
/** Timeout value for polled functions */
uint16_t buffer_timeout;
# ifdef FEATURE_I2C_10_BIT_ADDRESS
/** Using 10-bit addressing for the slave */
bool ten_bit_address;
# endif
# if I2C_SLAVE_CALLBACK_MODE == true
/** Nack on address match */
bool nack_on_address;
/** Pointers to callback functions */
volatile i2c_slave_callback_t callbacks[_I2C_SLAVE_CALLBACK_N];
/** Mask for registered callbacks */
volatile uint8_t registered_callback;
/** Mask for enabled callbacks */
volatile uint8_t enabled_callback;
/** The total number of bytes to transfer */
volatile uint16_t buffer_length;
/**
* Counter used for bytes left to send in write and to count number of
* obtained bytes in read
*/
uint16_t buffer_remaining;
/** Data buffer for packet write and read */
volatile uint8_t *buffer;
/** Save direction of request from master. 1 = read, 0 = write. */
volatile enum i2c_transfer_direction transfer_direction;
/** Status for status read back in error callback */
volatile enum status_code status;
# endif
#endif
};
/**
* \brief Configuration structure for the I2C slave device
*
* This is the configuration structure for the I2C slave device. It is used
* as an argument for \ref i2c_slave_init to provide the desired
* configurations for the module. The structure should be initialized using the
* \ref i2c_slave_get_config_defaults.
*/
struct i2c_slave_config {
/** Set to enable the SCL low timeout */
bool enable_scl_low_timeout;
/** SDA hold time with respect to the negative edge of SCL */
enum i2c_slave_sda_hold_time sda_hold_time;
/** Timeout to wait for master in polled functions */
uint16_t buffer_timeout;
/** Addressing mode */
enum i2c_slave_address_mode address_mode;
/** Address or upper limit of address range */
uint16_t address;
/** Address mask, second address, or lower limit of address range */
uint16_t address_mask;
#ifdef FEATURE_I2C_10_BIT_ADDRESS
/** Enable 10-bit addressing */
bool ten_bit_address;
#endif
/**
* Enable general call address recognition (general call address
* is defined as 0000000 with direction bit 0).
*/
bool enable_general_call_address;
#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED
/** Transfer speed mode */
enum i2c_slave_transfer_speed transfer_speed;
#endif
#if I2C_SLAVE_CALLBACK_MODE == true
/**
* Enable NACK on address match (this can be changed after initialization
* via the \ref i2c_slave_enable_nack_on_address and
* \ref i2c_slave_disable_nack_on_address functions).
*/
bool enable_nack_on_address;
#endif
/** GCLK generator to use as clock source */
enum gclk_generator generator_source;
/** Set to keep module active in sleep modes */
bool run_in_standby;
/** PAD0 (SDA) pinmux */
uint32_t pinmux_pad0;
/** PAD1 (SCL) pinmux */
uint32_t pinmux_pad1;
/** Set to enable SCL low time-out */
bool scl_low_timeout;
#ifdef FEATURE_I2C_SCL_STRETCH_MODE
/** Set to enable SCL stretch only after ACK bit (required for high speed) */
bool scl_stretch_only_after_ack_bit;
#endif
#ifdef FEATURE_I2C_SCL_EXTEND_TIMEOUT
/** Set to enable slave SCL low extend time-out */
bool slave_scl_low_extend_timeout;
#endif
};
/**
* \name Lock/Unlock
* @{
*/
/**
* \brief Attempt to get lock on driver instance
*
* This function checks the instance's lock, which indicates whether or not it
* is currently in use, and sets the lock if it was not already set.
*
* The purpose of this is to enable exclusive access to driver instances, so
* that, e.g., transactions by different services will not interfere with each
* other.
*
* \param[in,out] module Pointer to the driver instance to lock
*
* \retval STATUS_OK If the module was locked
* \retval STATUS_BUSY If the module was already locked
*/
static inline enum status_code i2c_slave_lock(
struct i2c_slave_module *const module)
{
enum status_code status;
system_interrupt_enter_critical_section();
if (module->locked) {
status = STATUS_BUSY;
} else {
module->locked = true;
status = STATUS_OK;
}
system_interrupt_leave_critical_section();
return status;
}
/**
* \brief Unlock driver instance
*
* This function clears the instance lock, indicating that it is available for
* use.
*
* \param[in,out] module Pointer to the driver instance to lock
*
* \retval STATUS_OK If the module was locked
* \retval STATUS_BUSY If the module was already locked
*/
static inline void i2c_slave_unlock(struct i2c_slave_module *const module)
{
module->locked = false;
}
/** @} */
/**
* \name Configuration and Initialization
* @{
*/
/**
* \brief Returns the synchronization status of the module
*
* Returns the synchronization status of the module.
*
* \param[out] module Pointer to software module structure
*
* \return Status of the synchronization.
* \retval true Module is busy synchronizing
* \retval false Module is not synchronizing
*/
static inline bool i2c_slave_is_syncing(
const struct i2c_slave_module *const module)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
SercomI2cs *const i2c_hw = &(module->hw->I2CS);
/* Return sync status */
#if defined(FEATURE_SERCOM_SYNCBUSY_SCHEME_VERSION_1)
return (i2c_hw->STATUS.reg & SERCOM_I2CS_STATUS_SYNCBUSY);
#elif defined(FEATURE_SERCOM_SYNCBUSY_SCHEME_VERSION_2)
return (i2c_hw->SYNCBUSY.reg & SERCOM_I2CS_SYNCBUSY_MASK);
#else
# error Unknown SERCOM SYNCBUSY scheme!
#endif
}
#if !defined(__DOXYGEN__)
/**
* \internal Wait for hardware module to sync
*
* \param[in] module Pointer to software module structure
*/
static void _i2c_slave_wait_for_sync(
const struct i2c_slave_module *const module)
{
/* Sanity check */
Assert(module);
while (i2c_slave_is_syncing(module)) {
/* Wait for I2C module to sync */
}
}
#endif
///@cond INTERNAL
/**
* \internal Workaround for errata 13574
* Instead set ACK/NACK of CTRLB
*
* This errata exist in part revisions of SAM D20/D21/D09
* D10/D11/L21/L22/DA1/C20/C21/R30, but workaround can be works in all
* revision of those device. As this function operation
* should be use less cpu time as possible, so caller
* function can ignore to check revision number, and use
* this workaround in all revision of those device.
*
* \param[in,out] module Pointer to software module structure
* \param[in] send_ack true send ACK, false send NACK
*/
static inline void _i2c_slave_set_ctrlb_ackact(
struct i2c_slave_module *const module,
bool send_ack)
{
Assert(module);
Assert(module->hw);
SercomI2cs *const i2c_hw = &(module->hw->I2CS);
#if (SAMD20 || SAMD21 || SAMD09 || SAMD10 || SAMD11 || SAML21 || SAMDA1 || \
SAML22 || SAMC20 || SAMC21 || SAMR30)
/* Workaround, Following two write are atomic */
system_interrupt_enter_critical_section();
i2c_hw->STATUS.reg = 0;
if (send_ack == true) {
i2c_hw->CTRLB.reg = 0;
}
else {
i2c_hw->CTRLB.reg = SERCOM_I2CS_CTRLB_ACKACT;
}
system_interrupt_leave_critical_section();
#else
/* Normal operation */
if (send_ack == true) {
i2c_hw->CTRLB.reg &= ~SERCOM_I2CS_CTRLB_ACKACT;
}
else {
i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_ACKACT;
}
#endif
return;
}
/**
* \internal Workaround for SAM0 errata 13574,
* instead Set CMD3 of CTRLB
*
* This errata exist in part revisions of SAM D20/D21/D09
* D10/D11/L21/DA1/C20/C21/R30, but workaround can be works in all
* revision of those device. As this function operation
* should be use less cpu time as possible, so caller
* function can ignore to check revision number, and use
* this workaround in all revision of those device.
*
* \param[in,out] module Pointer to software module structure
*/
static inline void _i2c_slave_set_ctrlb_cmd3(
struct i2c_slave_module *const module)
{
Assert(module);
Assert(module->hw);
SercomI2cs *const i2c_hw = &(module->hw->I2CS);
#if (SAMD20 || SAMD21 || SAMD09 || SAMD10 || SAMD11 || SAML21 || SAML22 || \
SAMDA1 || SAMC20 || SAMC21 || SAMR30)
/* Workaround */
/*
* Below code instead i2c_hw->CTRLB.reg = SERCOM_I2CS_CTRLB_CMD(0x3);
* CMD=0x3 clears all interrupts, so to keep the result similar
* PREC is cleared if it was set
*/
if (i2c_hw->INTFLAG.bit.PREC) {
i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC;
}
i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_AMATCH;
#else
/* Normal operation */
i2c_hw->CTRLB.reg = SERCOM_I2CS_CTRLB_CMD(0x3);
#endif
return;
}
///@endcond
/**
* \brief Gets the I2C slave default configurations
*
* This will initialize the configuration structure to known default values.
*
* The default configuration is as follows:
* - Disable SCL low timeout
* - 300ns - 600ns SDA hold time
* - Buffer timeout = 65535
* - Address with mask
* - Address = 0
* - Address mask = 0 (one single address)
* - General call address disabled
* - Address nack disabled if the interrupt driver is used
* - GCLK generator 0
* - Do not run in standby
* - PINMUX_DEFAULT for SERCOM pads
*
* Those default configuration only available if the device supports it:
* - Not using 10-bit addressing
* - Standard-mode and Fast-mode transfer speed
* - SCL stretch disabled
* - Slave SCL low extend time-out disabled
*
* \param[out] config Pointer to configuration structure to be initialized
*/
static inline void i2c_slave_get_config_defaults(
struct i2c_slave_config *const config)
{
/*Sanity check argument */
Assert(config);
config->enable_scl_low_timeout = false;
config->sda_hold_time = I2C_SLAVE_SDA_HOLD_TIME_300NS_600NS;
config->buffer_timeout = 65535;
config->address_mode = I2C_SLAVE_ADDRESS_MODE_MASK;
config->address = 0;
config->address_mask = 0;
#ifdef FEATURE_I2C_10_BIT_ADDRESS
config->ten_bit_address = false;
#endif
config->enable_general_call_address = false;
#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED
config->transfer_speed = I2C_SLAVE_SPEED_STANDARD_AND_FAST;
#endif
#if I2C_SLAVE_CALLBACK_MODE == true
config->enable_nack_on_address = false;
#endif
config->generator_source = GCLK_GENERATOR_0;
config->run_in_standby = false;
config->pinmux_pad0 = PINMUX_DEFAULT;
config->pinmux_pad1 = PINMUX_DEFAULT;
config->scl_low_timeout = false;
#ifdef FEATURE_I2C_SCL_STRETCH_MODE
config->scl_stretch_only_after_ack_bit = false;
#endif
#ifdef FEATURE_I2C_SCL_EXTEND_TIMEOUT
config->slave_scl_low_extend_timeout = false;
#endif
}
enum status_code i2c_slave_init(struct i2c_slave_module *const module,
Sercom *const hw,
const struct i2c_slave_config *const config);
/**
* \brief Enables the I2C module
*
* This will enable the requested I2C module.
*
* \param[in] module Pointer to the software module struct
*/
static inline void i2c_slave_enable(
const struct i2c_slave_module *const module)
{
/* Sanity check of arguments */
Assert(module);
Assert(module->hw);
SercomI2cs *const i2c_hw = &(module->hw->I2CS);
#if I2C_SLAVE_CALLBACK_MODE == true
/* Enable global interrupt for module */
system_interrupt_enable(_sercom_get_interrupt_vector(module->hw));
#endif
/* Wait for module to sync */
_i2c_slave_wait_for_sync(module);
/* Enable module */
i2c_hw->CTRLA.reg |= SERCOM_I2CS_CTRLA_ENABLE;
}
/**
* \brief Disables the I2C module
*
* This will disable the I2C module specified in the provided software module
* structure.
*
* \param[in] module Pointer to the software module struct
*/
static inline void i2c_slave_disable(
const struct i2c_slave_module *const module)
{
/* Sanity check of arguments */
Assert(module);
Assert(module->hw);
SercomI2cs *const i2c_hw = &(module->hw->I2CS);
#if I2C_SLAVE_CALLBACK_MODE == true
/* Disable interrupts */
i2c_hw->INTENCLR.reg = SERCOM_I2CS_INTENSET_PREC |
SERCOM_I2CS_INTENSET_AMATCH | SERCOM_I2CS_INTENSET_DRDY;
/* Clear interrupt flags */
i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC | SERCOM_I2CS_INTFLAG_AMATCH |
SERCOM_I2CS_INTFLAG_DRDY;
/* Disable global interrupt for module */
system_interrupt_disable(_sercom_get_interrupt_vector(module->hw));
#endif
/* Wait for module to sync */
_i2c_slave_wait_for_sync(module);
/* Disable module */
i2c_hw->CTRLA.reg &= ~SERCOM_I2CS_CTRLA_ENABLE;
}
void i2c_slave_reset(
struct i2c_slave_module *const module);
/** @} */
/**
* \name Read and Write
* @{
*/
enum status_code i2c_slave_write_packet_wait(
struct i2c_slave_module *const module,
struct i2c_slave_packet *const packet);
enum status_code i2c_slave_read_packet_wait(
struct i2c_slave_module *const module,
struct i2c_slave_packet *const packet);
enum i2c_slave_direction i2c_slave_get_direction_wait(
struct i2c_slave_module *const module);
/** @} */
/**
* \name Status Management
* @{
*/
uint32_t i2c_slave_get_status(
struct i2c_slave_module *const module);
void i2c_slave_clear_status(
struct i2c_slave_module *const module,
uint32_t status_flags);
/** @} */
#ifdef FEATURE_I2C_DMA_SUPPORT
/**
* \name SERCOM I2C slave with DMA Interfaces
* @{
*/
/**
* \brief Read SERCOM I2C interrupt status.
*
* Read I2C interrupt status for DMA transfer.
*
* \param[in,out] module Pointer to the driver instance to lock
*
*/
static inline uint8_t i2c_slave_dma_read_interrupt_status(struct i2c_slave_module *const module)
{
return (uint8_t)module->hw->I2CS.INTFLAG.reg;
}
/**
* \brief Write SERCOM I2C interrupt status.
*
* Write I2C interrupt status for DMA transfer.
*
* \param[in,out] module Pointer to the driver instance to lock
* \param[in] flag Interrupt flag status
*
*/
static inline void i2c_slave_dma_write_interrupt_status(struct i2c_slave_module *const module,
uint8_t flag)
{
module->hw->I2CS.INTFLAG.reg = flag;
}
/** @} */
#endif
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* I2C_SLAVE_H_INCLUDED */