Newer
Older
mbed-os / hal / targets / hal / TARGET_Atmel / TARGET_SAM_CortexM0P / drivers / sercom / i2c / i2c_sam0 / i2c_slave.c
@Mihail Stoyanov Mihail Stoyanov on 23 May 2016 24 KB Simplify layout:
/**
 * \file
 *
 * \brief SAM I2C Slave Driver
 *
 * Copyright (C) 2013-2015 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 <a href="http://www.atmel.com/design-support/">Atmel Support</a>
 */

#include "i2c_slave.h"
#if I2C_SLAVE_CALLBACK_MODE == true
#  include "i2c_slave_interrupt.h"
#endif

/**
 * \internal Sets configuration to module
 *
 * \param[out] module  Pointer to software module structure
 * \param[in]  config  Configuration structure with configurations to set
 *
 * \return Status of setting configuration.
 * \retval STATUS_OK                       Module was configured correctly
 * \retval STATUS_ERR_ALREADY_INITIALIZED  If setting other GCLK generator than
 *                                         previously set
 */
static enum status_code _i2c_slave_set_config(
    struct i2c_slave_module *const module,
    const struct i2c_slave_config *const config)
{
    uint32_t tmp_ctrla;

    /* Sanity check arguments. */
    Assert(module);
    Assert(module->hw);
    Assert(config);

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);
    Sercom *const sercom_hw = module->hw;

    module->buffer_timeout = config->buffer_timeout;
    module->ten_bit_address = config->ten_bit_address;

    struct system_pinmux_config pin_conf;
    system_pinmux_get_config_defaults(&pin_conf);

    uint32_t pad0 = config->pinmux_pad0;
    uint32_t pad1 = config->pinmux_pad1;

    /* SERCOM PAD0 - SDA */
    if (pad0 == PINMUX_DEFAULT) {
        pad0 = _sercom_get_default_pad(sercom_hw, 0);
    }
    pin_conf.mux_position = pad0 & 0xFFFF;
    pin_conf.direction    = SYSTEM_PINMUX_PIN_DIR_OUTPUT_WITH_READBACK;
    system_pinmux_pin_set_config(pad0 >> 16, &pin_conf);

    /* SERCOM PAD1 - SCL */
    if (pad1 == PINMUX_DEFAULT) {
        pad1 = _sercom_get_default_pad(sercom_hw, 1);
    }
    pin_conf.mux_position = pad1 & 0xFFFF;
    pin_conf.direction    = SYSTEM_PINMUX_PIN_DIR_OUTPUT_WITH_READBACK;
    system_pinmux_pin_set_config(pad1 >> 16, &pin_conf);

    /* Prepare config to write to register CTRLA */
    if (config->run_in_standby || system_is_debugger_present()) {
        tmp_ctrla = SERCOM_I2CS_CTRLA_RUNSTDBY;
    } else {
        tmp_ctrla = 0;
    }

    tmp_ctrla |= ((uint32_t)config->sda_hold_time |
                  config->transfer_speed |
                  (config->scl_low_timeout << SERCOM_I2CS_CTRLA_LOWTOUTEN_Pos) |
                  (config->scl_stretch_only_after_ack_bit << SERCOM_I2CS_CTRLA_SCLSM_Pos) |
                  (config->slave_scl_low_extend_timeout << SERCOM_I2CS_CTRLA_SEXTTOEN_Pos));

    i2c_hw->CTRLA.reg |= tmp_ctrla;

    /* Set CTRLB configuration */
    i2c_hw->CTRLB.reg = SERCOM_I2CS_CTRLB_SMEN | config->address_mode;

    i2c_hw->ADDR.reg = config->address << SERCOM_I2CS_ADDR_ADDR_Pos |
                       config->address_mask << SERCOM_I2CS_ADDR_ADDRMASK_Pos |
                       config->ten_bit_address << SERCOM_I2CS_ADDR_TENBITEN_Pos |
                       config->enable_general_call_address << SERCOM_I2CS_ADDR_GENCEN_Pos;

    return STATUS_OK;
}

/**
 * \brief Initializes the requested I<SUP>2</SUP>C hardware module
 *
 * Initializes the SERCOM I<SUP>2</SUP>C Slave device requested and sets the provided
 * software module struct.  Run this function before any further use of
 * the driver.
 *
 * \param[out] module  Pointer to software module struct
 * \param[in]  hw      Pointer to the hardware instance
 * \param[in]  config  Pointer to the configuration struct
 *
 * \return Status of initialization.
 * \retval STATUS_OK                       Module initiated correctly
 * \retval STATUS_ERR_DENIED               If module is enabled
 * \retval STATUS_BUSY                     If module is busy resetting
 * \retval STATUS_ERR_ALREADY_INITIALIZED  If setting other GCLK generator than
 *                                         previously set
 */
enum status_code i2c_slave_init(
    struct i2c_slave_module *const module,
    Sercom *const hw,
    const struct i2c_slave_config *const config)
{
    /* Sanity check arguments. */
    Assert(module);
    Assert(hw);
    Assert(config);

    /* Initialize software module */
    module->hw = hw;

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);

    /* Check if module is enabled. */
    if (i2c_hw->CTRLA.reg & SERCOM_I2CS_CTRLA_ENABLE) {
        return STATUS_ERR_DENIED;
    }

    /* Check if reset is in progress. */
    if (i2c_hw->CTRLA.reg & SERCOM_I2CS_CTRLA_SWRST) {
        return STATUS_BUSY;
    }

    uint32_t sercom_index = _sercom_get_sercom_inst_index(module->hw);
    uint32_t pm_index, gclk_index;
#if (SAML21) || (SAMC20) || (SAMC21)
#if (SAML21)
    if (sercom_index == 5) {
        pm_index     = MCLK_APBDMASK_SERCOM5_Pos;
        gclk_index   = SERCOM5_GCLK_ID_CORE;
    } else {
        pm_index     = sercom_index + MCLK_APBCMASK_SERCOM0_Pos;
        gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
    }
#else
    pm_index     = sercom_index + MCLK_APBCMASK_SERCOM0_Pos;
    gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
#endif
#else
    pm_index     = sercom_index + PM_APBCMASK_SERCOM0_Pos;
    gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
#endif

    /* Turn on module in PM */
#if (SAML21)
    if (sercom_index == 5) {
        system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBD, 1 << pm_index);
    } else {
        system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index);
    }
#else
    system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index);
#endif

    /* Set up the GCLK for the module */
    struct system_gclk_chan_config gclk_chan_conf;
    system_gclk_chan_get_config_defaults(&gclk_chan_conf);
    gclk_chan_conf.source_generator = config->generator_source;
    system_gclk_chan_set_config(gclk_index, &gclk_chan_conf);
    system_gclk_chan_enable(gclk_index);
    sercom_set_gclk_generator(config->generator_source, false);

#if I2C_SLAVE_CALLBACK_MODE == true
    /* Get sercom instance index. */
    uint8_t instance_index = _sercom_get_sercom_inst_index(module->hw);

    /* Save software module in interrupt handler. */
    _sercom_set_handler(instance_index, _i2c_slave_interrupt_handler);

    /* Save software module. */
    _sercom_instances[instance_index] = module;

    /* Initialize values in module. */
    module->registered_callback = 0;
    module->enabled_callback = 0;
    module->buffer_length = 0;
    module->nack_on_address = config->enable_nack_on_address;
#endif

    /* Set SERCOM module to operate in I2C slave mode. */
    i2c_hw->CTRLA.reg = SERCOM_I2CS_CTRLA_MODE(0x4);

    /* Set config and return status. */
    return _i2c_slave_set_config(module, config);
}

/**
 * \brief Resets the hardware module
 *
 * This will reset the module to hardware defaults.
 *
 * \param[in,out] module  Pointer to software module structure
 */
void i2c_slave_reset(
    struct i2c_slave_module *const module)
{
    /* Sanity check arguments. */
    Assert(module);
    Assert(module->hw);

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);

#if I2C_SLAVE_CALLBACK_MODE == true
    /* Reset module instance. */
    module->registered_callback = 0;
    module->enabled_callback = 0;
    module->buffer_length = 0;
    module->buffer_remaining = 0;
    module->buffer = NULL;
#endif

    /* Disable module */
    i2c_slave_disable(module);

#if I2C_SLAVE_CALLBACK_MODE == true
    /* Clear all pending interrupts. */
    system_interrupt_enter_critical_section();
    system_interrupt_clear_pending(_sercom_get_interrupt_vector(module->hw));
    system_interrupt_leave_critical_section();
#endif

    /* Wait for sync. */
    _i2c_slave_wait_for_sync(module);

    /* Reset module. */
    i2c_hw->CTRLA.reg = SERCOM_I2CS_CTRLA_SWRST;
}

/**
 * \internal Waits for answer on bus
 *
 * \param[in]  module  Pointer to software module structure
 *
 * \return Status of bus.
 * \retval STATUS_OK           If given response from slave device
 * \retval STATUS_ERR_TIMEOUT  If no response was given within specified timeout
 *                             period
 */
static enum status_code _i2c_slave_wait_for_bus(
    struct i2c_slave_module *const module)
{
    /* Sanity check arguments. */
    Assert(module);
    Assert(module->hw);

    SercomI2cm *const i2c_module = &(module->hw->I2CM);

    /* Wait for reply. */
    uint16_t timeout_counter = 0;
    while ((!(i2c_module->INTFLAG.reg & SERCOM_I2CS_INTFLAG_DRDY)) &&
            (!(i2c_module->INTFLAG.reg & SERCOM_I2CS_INTFLAG_PREC)) &&
            (!(i2c_module->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH))) {

        /* Check timeout condition. */
        if (++timeout_counter >= module->buffer_timeout) {
            return STATUS_ERR_TIMEOUT;
        }
    }
    return STATUS_OK;
}

/**
 * \brief Writes a packet to the master
 *
 * Writes a packet to the master. This will wait for the master to issue
 * a request.
 *
 * \param[in]  module  Pointer to software module structure
 * \param[in]  packet  Packet to write to master
 *
 * \return Status of packet write.
 * \retval STATUS_OK                Packet was written successfully
 * \retval STATUS_ERR_DENIED        Start condition not received, another
 *                                  interrupt flag is set
 * \retval STATUS_ERR_IO            There was an error in the previous transfer
 * \retval STATUS_ERR_BAD_FORMAT    Master wants to write data
 * \retval STATUS_ERR_INVALID_ARG   Invalid argument(s) was provided
 * \retval STATUS_ERR_BUSY          The I<SUP>2</SUP>C module is busy with a job
 * \retval STATUS_ERR_ERR_OVERFLOW  Master NACKed before entire packet was
 *                                  transferred
 * \retval STATUS_ERR_TIMEOUT       No response was given within the timeout
 *                                  period
 */
enum status_code i2c_slave_write_packet_wait(
    struct i2c_slave_module *const module,
    struct i2c_slave_packet *const packet)
{
    /* Sanity check arguments. */
    Assert(module);
    Assert(module->hw);
    Assert(packet);

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);

    uint16_t length = packet->data_length;

    if (length == 0) {
        return STATUS_ERR_INVALID_ARG;
    }

#if I2C_SLAVE_CALLBACK_MODE == true
    /* Check if the module is busy with a job or AMATCH is enabled */
    if (module->buffer_remaining > 0 ||
            (i2c_hw->INTENSET.reg & SERCOM_I2CS_INTFLAG_AMATCH)) {
        return STATUS_BUSY;
    }
#endif

    enum status_code status;
    /* Wait for master to send address packet */
    status = _i2c_slave_wait_for_bus(module);

    if (status != STATUS_OK) {
        /* Timeout, return */
        return status;
    }
    if (!(i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH)) {
        /* Not address interrupt, something is wrong */
        return STATUS_ERR_DENIED;
    }

    if (module->ten_bit_address) {
        /* ACK the first address */
        i2c_hw->CTRLB.reg &= ~SERCOM_I2CS_CTRLB_ACKACT;
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3);

        /* Wait for address interrupt */
        status = _i2c_slave_wait_for_bus(module);

        if (status != STATUS_OK) {
            /* Timeout, return */
            return STATUS_ERR_TIMEOUT;
        }

        if (!(i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH)) {
            /* Not address interrupt, something is wrong */
            return STATUS_ERR_DENIED;
        }
    }

    /* Check if there was an error in last transfer */
    if (i2c_hw->STATUS.reg & (SERCOM_I2CS_STATUS_BUSERR |
                              SERCOM_I2CS_STATUS_COLL | SERCOM_I2CS_STATUS_LOWTOUT)) {
        return STATUS_ERR_IO;
    }

    /* Check direction */
    if (!(i2c_hw->STATUS.reg & SERCOM_I2CS_STATUS_DIR)) {
        /* Write request from master, send NACK and return */
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_ACKACT;
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3);
        return STATUS_ERR_BAD_FORMAT;
    }

    /* Read request from master, ACK address */
    i2c_hw->CTRLB.reg &= ~SERCOM_I2CS_CTRLB_ACKACT;
    i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3);

    uint16_t i = 0;

    /* Wait for data interrupt */
    status = _i2c_slave_wait_for_bus(module);
    if (status != STATUS_OK) {
        /* Timeout, return */
        return status;
    }

    while (length--) {
        /* Write data */
        _i2c_slave_wait_for_sync(module);
        i2c_hw->DATA.reg = packet->data[i++];

        /* Wait for response from master */
        status = _i2c_slave_wait_for_bus(module);

        if (status != STATUS_OK) {
            /* Timeout, return */
            return status;
        }

        if (i2c_hw->STATUS.reg & SERCOM_I2CS_STATUS_RXNACK &&
                length !=0) {
            /* NACK from master, abort */
            /* Release line */
            i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x02);

            return STATUS_ERR_OVERFLOW;
        }
        /* ACK from master, continue writing */
    }

    /* Release line */
    i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x02);

    return STATUS_OK;
}

/**
 * \brief Reads a packet from the master
 *
 * Reads a packet from the master. This will wait for the master to issue a
 * request.
 *
 * \param[in]  module  Pointer to software module structure
 * \param[out] packet  Packet to read from master
 *
 * \return Status of packet read.
 * \retval STATUS_OK                Packet was read successfully
 * \retval STATUS_ABORTED           Master sent stop condition or repeated
 *                                  start before specified length of bytes
 *                                  was received
 * \retval STATUS_ERR_IO            There was an error in the previous transfer
 * \retval STATUS_ERR_DENIED        Start condition not received, another
 *                                  interrupt flag is set
 * \retval STATUS_ERR_INVALID_ARG   Invalid argument(s) was provided
 * \retval STATUS_ERR_BUSY          The I<SUP>2</SUP>C module is busy with a job
 * \retval STATUS_ERR_BAD_FORMAT    Master wants to read data
 * \retval STATUS_ERR_ERR_OVERFLOW  Last byte received overflows buffer
 */
enum status_code i2c_slave_read_packet_wait(
    struct i2c_slave_module *const module,
    struct i2c_slave_packet *const packet)
{
    /* Sanity check arguments. */
    Assert(module);
    Assert(module->hw);
    Assert(packet);

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);

    uint16_t length = packet->data_length;

    if (length == 0) {
        return STATUS_ERR_INVALID_ARG;
    }

#if I2C_SLAVE_CALLBACK_MODE == true
    /* Check if the module is busy with a job or AMATCH is enabled */
    if (module->buffer_remaining > 0 ||
            (i2c_hw->INTENSET.reg & SERCOM_I2CS_INTFLAG_AMATCH)) {
        return STATUS_BUSY;
    }
#endif

    enum status_code status;

    /* Wait for master to send address packet */
    status = _i2c_slave_wait_for_bus(module);
    if (status != STATUS_OK) {
        /* Timeout, return */
        return status;
    }

    if (!(i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH)) {
        /* Not address interrupt, something is wrong */
        return STATUS_ERR_DENIED;
    }

    /* Check if there was an error in the last transfer */
    if (i2c_hw->STATUS.reg & (SERCOM_I2CS_STATUS_BUSERR |
                              SERCOM_I2CS_STATUS_COLL | SERCOM_I2CS_STATUS_LOWTOUT)) {
        return STATUS_ERR_IO;
    }
    /* Check direction */
    if ((i2c_hw->STATUS.reg & SERCOM_I2CS_STATUS_DIR)) {
        /* Read request from master, send NACK and return */
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_ACKACT;
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3);
        return STATUS_ERR_BAD_FORMAT;
    }

    /* Write request from master, ACK address */
    i2c_hw->CTRLB.reg &= ~SERCOM_I2CS_CTRLB_ACKACT;
    i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3);

    uint16_t i = 0;
    while (length--) {

        /* Wait for next byte or stop condition */
        status = _i2c_slave_wait_for_bus(module);
        if (status != STATUS_OK) {
            /* Timeout, return */
            return status;
        }

        if ((i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_PREC) ||
                i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH) {
            /* Master sent stop condition, or repeated start, read done */
            /* Clear stop flag */
            i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC;
            return STATUS_ABORTED;
        }

        /* Read data */
        _i2c_slave_wait_for_sync(module);
        packet->data[i++] = i2c_hw->DATA.reg;

    }

    /* Packet read done, wait for packet to NACK, Stop or repeated start */
    status = _i2c_slave_wait_for_bus(module);

    if (i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_DRDY) {
        /* Buffer is full, send NACK */
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_ACKACT;
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x2);
    }
    if (i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_PREC) {
        /* Clear stop flag */
        i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC;
    }
    return STATUS_OK;
}

/**
 * \brief Waits for a start condition on the bus
 *
 * \note This function is only available for 7-bit slave addressing.
 *
 * Waits for the master to issue a start condition on the bus.
 * Note that this function does not check for errors in the last transfer,
 * this will be discovered when reading or writing.
 *
 * \param[in]  module  Pointer to software module structure
 *
 * \return Direction of the current transfer, when in slave mode.
 * \retval I2C_SLAVE_DIRECTION_NONE   No request from master within timeout
 *                                    period
 * \retval I2C_SLAVE_DIRECTION_READ   Write request from master
 * \retval I2C_SLAVE_DIRECTION_WRITE  Read request from master
 */
enum i2c_slave_direction i2c_slave_get_direction_wait(
    struct i2c_slave_module *const module)
{
    /* Sanity check arguments. */
    Assert(module);
    Assert(module->hw);

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);

    enum status_code status;

    /* Wait for address interrupt */
    status = _i2c_slave_wait_for_bus(module);

    if (status != STATUS_OK) {
        /* Timeout, return */
        return I2C_SLAVE_DIRECTION_NONE;
    }

    if (!(i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH)) {
        /* Not address interrupt, something is wrong */
        return I2C_SLAVE_DIRECTION_NONE;
    }

    /* Check direction */
    if ((i2c_hw->STATUS.reg & SERCOM_I2CS_STATUS_DIR)) {
        /* Read request from master */
        return I2C_SLAVE_DIRECTION_WRITE;
    } else {
        /* Write request from master */
        return I2C_SLAVE_DIRECTION_READ;
    }
}

/**
 * \brief Retrieves the current module status
 *
 * Checks the status of the module and returns it as a bitmask of status
 * flags.
 *
 * \param[in] module      Pointer to the I<SUP>2</SUP>C slave software device struct
 *
 * \return Bitmask of status flags.
 *
 * \retval I2C_SLAVE_STATUS_ADDRESS_MATCH   A valid address has been received
 * \retval I2C_SLAVE_STATUS_DATA_READY      A I<SUP>2</SUP>C slave byte transmission is
 *                                          successfully completed
 * \retval I2C_SLAVE_STATUS_STOP_RECEIVED   A stop condition is detected for a
 *                                          transaction being processed
 * \retval I2C_SLAVE_STATUS_CLOCK_HOLD      The slave is holding the SCL line
 *                                          low
 * \retval I2C_SLAVE_STATUS_SCL_LOW_TIMEOUT An SCL low time-out has occurred
 * \retval I2C_SLAVE_STATUS_REPEATED_START  Indicates a repeated start, only
 *                                          valid if \ref
 *                                          I2C_SLAVE_STATUS_ADDRESS_MATCH is
 *                                          set
 * \retval I2C_SLAVE_STATUS_RECEIVED_NACK   The last data packet sent was not
 *                                          acknowledged
 * \retval I2C_SLAVE_STATUS_COLLISION       The I<SUP>2</SUP>C slave was not able to
 *                                          transmit a high data or NACK bit
 * \retval I2C_SLAVE_STATUS_BUS_ERROR       An illegal bus condition has
 *                                          occurred on the bus
 */
uint32_t i2c_slave_get_status(
    struct i2c_slave_module *const module)
{
    /* Sanity check arguments */
    Assert(module);
    Assert(module->hw);

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);

    uint8_t intflags = i2c_hw->INTFLAG.reg;
    uint8_t status = i2c_hw->STATUS.reg;
    uint32_t status_flags = 0;

    /* Check Address Match flag */
    if (intflags & SERCOM_I2CS_INTFLAG_AMATCH) {
        status_flags |= I2C_SLAVE_STATUS_ADDRESS_MATCH;
    }
    /* Check Data Ready flag */
    if (intflags & SERCOM_I2CS_INTFLAG_DRDY) {
        status_flags |= I2C_SLAVE_STATUS_DATA_READY;
    }
    /* Check Stop flag */
    if (intflags & SERCOM_I2CS_INTFLAG_PREC) {
        status_flags |= I2C_SLAVE_STATUS_STOP_RECEIVED;
    }
    /* Check Clock Hold */
    if (status & SERCOM_I2CS_STATUS_CLKHOLD) {
        status_flags |= I2C_SLAVE_STATUS_CLOCK_HOLD;
    }
    /* Check SCL Low Timeout */
    if (status & SERCOM_I2CS_STATUS_LOWTOUT) {
        status_flags |= I2C_SLAVE_STATUS_SCL_LOW_TIMEOUT;
    }
    /* Check Repeated Start */
    if (status & SERCOM_I2CS_STATUS_SR) {
        status_flags |= I2C_SLAVE_STATUS_REPEATED_START;
    }
    /* Check Received Not Acknowledge */
    if (status & SERCOM_I2CS_STATUS_RXNACK) {
        status_flags |= I2C_SLAVE_STATUS_RECEIVED_NACK;
    }
    /* Check Transmit Collision */
    if (status & SERCOM_I2CS_STATUS_COLL) {
        status_flags |= I2C_SLAVE_STATUS_COLLISION;
    }
    /* Check Bus Error */
    if (status & SERCOM_I2CS_STATUS_BUSERR) {
        status_flags |= I2C_SLAVE_STATUS_BUS_ERROR;
    }

    return status_flags;
}

/**
 * \brief Clears a module status flag
 *
 * Clears the given status flag of the module.
 *
 * \note Not all status flags can be cleared.
 *
 * \param[in] module         Pointer to the I<SUP>2</SUP>C software device struct
 * \param[in] status_flags   Bit mask of status flags to clear
 *
 */
void i2c_slave_clear_status(
    struct i2c_slave_module *const module,
    uint32_t status_flags)
{
    /* Sanity check arguments */
    Assert(module);
    Assert(module->hw);

    SercomI2cs *const i2c_hw = &(module->hw->I2CS);

    /* Clear Address Match flag */
    if (status_flags & I2C_SLAVE_STATUS_ADDRESS_MATCH) {
        i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_AMATCH;
    }
    /* Clear Data Ready flag */
    if (status_flags & I2C_SLAVE_STATUS_DATA_READY) {
        i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_DRDY;
    }
    /* Clear Stop flag */
    if (status_flags & I2C_SLAVE_STATUS_STOP_RECEIVED) {
        i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_PREC;
    }
    /* Clear SCL Low Timeout */
    if (status_flags & I2C_SLAVE_STATUS_SCL_LOW_TIMEOUT) {
        i2c_hw->STATUS.reg = SERCOM_I2CS_STATUS_LOWTOUT;
    }
    /* Clear Transmit Collision */
    if (status_flags & I2C_SLAVE_STATUS_COLLISION) {
        i2c_hw->STATUS.reg = SERCOM_I2CS_STATUS_COLL;
    }
    /* Clear Bus Error */
    if (status_flags & I2C_SLAVE_STATUS_BUS_ERROR) {
        i2c_hw->STATUS.reg = SERCOM_I2CS_STATUS_BUSERR;
    }
}