/**
* \file
*
* \brief I2C Master Interrupt Driver for SAMB
*
* Copyright (C) 2015-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
*/
#include "i2c_master_interrupt.h"
void *_i2c_instances;
/**
* \internal
* Read next data. Used by interrupt handler to get next data byte from slave.
*
* \param[in,out] module Pointer to software module structure
*/
static void _i2c_master_read(
struct i2c_master_module *const module)
{
/* Sanity check arguments. */
Assert(module);
Assert(module->hw);
I2c *const i2c_module = module->hw;
/* Find index to save next value in buffer */
uint16_t buffer_index = module->buffer_length - module->buffer_remaining;
module->buffer_remaining--;
module->buffer[buffer_index] = i2c_module->RECEIVE_DATA.reg;
}
/**
* \internal
*
* Write next data. Used by interrupt handler to send next data byte to slave.
*
* \param[in,out] module Pointer to software module structure
*/
static void _i2c_master_write(struct i2c_master_module *const module)
{
/* Sanity check arguments. */
Assert(module);
Assert(module->hw);
I2c *const i2c_module = module->hw;
/* Find index to get next byte in buffer */
volatile uint16_t buffer_index = module->buffer_length - module->buffer_remaining;
module->buffer_remaining--;
/* Write byte from buffer to slave */
i2c_module->TRANSMIT_DATA.reg = module->buffer[buffer_index];
if (module->buffer_remaining <= 0) {
i2c_module->TX_INTERRUPT_MASK.reg = I2C_TX_INTERRUPT_MASK_TX_FIFO_EMPTY_MASK;
}
}
/**
* \brief Registers callback for the specified callback type
*
* Associates the given callback function with the
* specified callback type.
*
* To enable the callback, the \ref i2c_master_enable_callback function
* must be used.
*
* \param[in,out] module Pointer to the software module struct
* \param[in] callback Pointer to the function desired for the
* specified callback
* \param[in] callback_type Callback type to register
*/
void i2c_master_register_callback(
struct i2c_master_module *const module,
const i2c_master_callback_t callback,
enum i2c_master_callback callback_type)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
Assert(callback);
/* Register callback */
module->callbacks[callback_type] = callback;
/* Set corresponding bit to set callback as registered */
module->registered_callback |= (1 << callback_type);
}
/**
* \brief Unregisters callback for the specified callback type
*
* When called, the currently registered callback for the given callback type
* will be removed.
*
* \param[in,out] module Pointer to the software module struct
* \param[in] callback_type Specifies the callback type to unregister
*/
void i2c_master_unregister_callback(
struct i2c_master_module *const module,
enum i2c_master_callback callback_type)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
/* Register callback */
module->callbacks[callback_type] = NULL;
/* Clear corresponding bit to set callback as unregistered */
module->registered_callback &= ~(1 << callback_type);
}
/**
* \internal
* Starts a read packet operation.
*
* \param[in,out] module Pointer to software module struct
* \param[in,out] packet Pointer to I2C packet to transfer
*
* \return Status of starting reading I2C packet.
* \retval STATUS_OK If reading was started successfully
* \retval STATUS_BUSY If module is currently busy with another transfer
*/
static enum status_code _i2c_master_read_packet(
struct i2c_master_module *const module,
struct i2c_master_packet *const packet)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
Assert(packet);
I2c *const i2c_module = module->hw;
/* Save packet to software module */
module->buffer = packet->data;
module->buffer_remaining = packet->data_length;
module->transfer_direction = I2C_TRANSFER_READ;
module->status = STATUS_BUSY;
i2c_wait_for_idle(i2c_module);
/* Flush the FIFO */
i2c_module->I2C_FLUSH.reg = 1;
/* Enable I2C on bus (start condition) */
i2c_module->I2C_ONBUS.reg = I2C_ONBUS_ONBUS_ENABLE_1;
/* Set address and direction bit. Will send start command on bus */
i2c_module->TRANSMIT_DATA.reg = I2C_TRANSMIT_DATA_ADDRESS_FLAG_1 |
(packet->address << 1) | module->transfer_direction;
/* Enable interrupts */
i2c_module->RX_INTERRUPT_MASK.reg = I2C_RX_INTERRUPT_MASK_RX_FIFO_NOT_EMPTY_MASK;
return STATUS_OK;
}
/**
* \brief Initiates a read packet operation
*
* Reads a data packet from the specified slave address on the I2C
* bus. This is the non-blocking equivalent of \ref i2c_master_read_packet_wait.
*
* \param[in,out] module Pointer to software module struct
* \param[in,out] packet Pointer to I2C packet to transfer
*
* \return Status of starting reading I2C packet.
* \retval STATUS_OK If reading was started successfully
* \retval STATUS_BUSY If module is currently busy with another transfer
*/
enum status_code i2c_master_read_packet_job(
struct i2c_master_module *const module,
struct i2c_master_packet *const packet)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
Assert(packet);
/* Check if the I2C module is busy with a job */
if (module->buffer_remaining > 0) {
return STATUS_BUSY;
}
/* Make sure we send STOP */
module->no_stop = false;
/* Start reading */
return _i2c_master_read_packet(module, packet);
}
/**
* \brief Initiates a read packet operation without sending a STOP condition when done
*
* Reads a data packet from the specified slave address on the I2C bus without
* sending a stop condition, thus retaining ownership of the bus when done.
* To end the transaction, a \ref i2c_master_read_packet_wait "read" or
* \ref i2c_master_write_packet_wait "write" with stop condition must be
* performed.
*
* This is the non-blocking equivalent of \ref i2c_master_read_packet_wait_no_stop.
*
* \param[in,out] module Pointer to software module struct
* \param[in,out] packet Pointer to I2C packet to transfer
*
* \return Status of starting reading I2C packet.
* \retval STATUS_OK If reading was started successfully
* \retval STATUS_BUSY If module is currently busy with another operation
*/
enum status_code i2c_master_read_packet_job_no_stop(
struct i2c_master_module *const module,
struct i2c_master_packet *const packet)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
Assert(packet);
/* Check if the I2C module is busy with a job */
if (module->buffer_remaining > 0) {
return STATUS_BUSY;
}
/* Make sure we don't send STOP */
module->no_stop = true;
/* Start reading */
return _i2c_master_read_packet(module, packet);
}
/**
* \internal Initiates a write packet operation
*
* \param[in,out] module Pointer to software module struct
* \param[in,out] packet Pointer to I2C packet to transfer
*
* \return Status of starting writing I2C packet job.
* \retval STATUS_OK If writing was started successfully
* \retval STATUS_BUSY If module is currently busy with another transfer
*/
static enum status_code _i2c_master_write_packet(
struct i2c_master_module *const module,
struct i2c_master_packet *const packet)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
Assert(packet);
I2c *const i2c_module = module->hw;
/* Save packet to software module */
module->buffer = packet->data;
module->buffer_remaining = packet->data_length;
module->transfer_direction = I2C_TRANSFER_WRITE;
module->status = STATUS_BUSY;
/* Enable I2C on bus (start condition) */
i2c_module->I2C_ONBUS.reg = I2C_ONBUS_ONBUS_ENABLE_1;
/* Set address and direction bit, will send start command on bus */
i2c_module->TRANSMIT_DATA.reg = I2C_TRANSMIT_DATA_ADDRESS_FLAG_1 |
(packet->address << 1) | module->transfer_direction;
/* Enable interrupts */
i2c_module->TX_INTERRUPT_MASK.reg = I2C_TX_INTERRUPT_MASK_TX_FIFO_EMPTY_MASK;
return STATUS_OK;
}
/**
* \brief Initiates a write packet operation
*
* Writes a data packet to the specified slave address on the I2C
* bus. This is the non-blocking equivalent of \ref i2c_master_write_packet_wait.
*
* \param[in,out] module Pointer to software module struct
* \param[in,out] packet Pointer to I2C packet to transfer
*
* \return Status of starting writing I2C packet job.
* \retval STATUS_OK If writing was started successfully
* \retval STATUS_BUSY If module is currently busy with another transfer
*/
enum status_code i2c_master_write_packet_job(
struct i2c_master_module *const module,
struct i2c_master_packet *const packet)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
Assert(packet);
/* Check if the I2C module is busy with another job. */
if (module->buffer_remaining > 0) {
return STATUS_BUSY;
}
/* Make sure we send STOP at end*/
module->no_stop = false;
/* Start write operation */
return _i2c_master_write_packet(module, packet);
}
/**
* \brief Initiates a write packet operation without sending a STOP condition when done
*
* Writes a data packet to the specified slave address on the I2C bus
* without sending a stop condition, thus retaining ownership of the bus when
* done. To end the transaction, a \ref i2c_master_read_packet_wait "read" or
* \ref i2c_master_write_packet_wait "write" with stop condition or sending
* a stop with the \ref i2c_master_send_stop function must be performed.
*
* This is the non-blocking equivalent of \ref i2c_master_write_packet_wait_no_stop.
*
* \param[in,out] module Pointer to software module struct
* \param[in,out] packet Pointer to I2C packet to transfer
*
* \return Status of starting writing I2C packet job.
* \retval STATUS_OK If writing was started successfully
* \retval STATUS_BUSY If module is currently busy with another
*/
enum status_code i2c_master_write_packet_job_no_stop(
struct i2c_master_module *const module,
struct i2c_master_packet *const packet)
{
/* Sanity check */
Assert(module);
Assert(module->hw);
Assert(packet);
/* Check if the I2C module is busy with another job. */
if (module->buffer_remaining > 0) {
return STATUS_BUSY;
}
/* Do not send stop condition when done */
module->no_stop = true;
/* Start write operation */
return _i2c_master_write_packet(module, packet);
}
/**
* Interrupt handler for I2C master.
*/
void _i2c_master_isr_handler(void)
{
/* Get software module for callback handling */
struct i2c_master_module *module =
(struct i2c_master_module*)_i2c_instances;
Assert(module);
I2c *const i2c_module = module->hw;
/* Combine callback registered and enabled masks */
uint8_t callback_mask = module->enabled_callback &
module->registered_callback;
if ((module->buffer_length <= 0) && (module->buffer_remaining > 0)) {
module->buffer_length = module->buffer_remaining;
/* Check if buffer write is done */
} else if ((module->buffer_length > 0) && (module->buffer_remaining <= 0) &&
(module->status == STATUS_BUSY) &&
(module->transfer_direction == I2C_TRANSFER_WRITE)) {
/* Disable write interrupt flag */
i2c_module->TX_INTERRUPT_MASK.reg = 0;
module->buffer_length = 0;
module->status = STATUS_OK;
if (!module->no_stop) {
/* Send stop condition */
i2c_module->I2C_ONBUS.reg = I2C_ONBUS_ONBUS_ENABLE_0;
}
if (callback_mask & (1 << I2C_MASTER_CALLBACK_WRITE_COMPLETE)) {
module->callbacks[I2C_MASTER_CALLBACK_WRITE_COMPLETE](module);
}
/* Continue buffer write/read */
} else if ((module->buffer_length > 0) && (module->buffer_remaining > 0)){
if (module->transfer_direction == I2C_TRANSFER_WRITE) {
_i2c_master_write(module);
} else {
_i2c_master_read(module);
}
}
/* Check if read buffer transfer is complete */
if ((module->buffer_length > 0) && (module->buffer_remaining <= 0) &&
(module->status == STATUS_BUSY) &&
(module->transfer_direction == I2C_TRANSFER_READ)) {
/* Disable read interrupt flag */
i2c_module->RX_INTERRUPT_MASK.reg = 0;
module->buffer_length = 0;
module->status = STATUS_OK;
if (!module->no_stop) {
/* Send stop condition */
i2c_module->I2C_ONBUS.reg = I2C_ONBUS_ONBUS_ENABLE_0;
}
if ((callback_mask & (1 << I2C_MASTER_CALLBACK_READ_COMPLETE))
&& (module->transfer_direction == I2C_TRANSFER_READ)) {
module->callbacks[I2C_MASTER_CALLBACK_READ_COMPLETE](module);
}
}
if (module->transfer_direction == I2C_TRANSFER_READ) {
if (module->hw == I2C0) {
NVIC_ClearPendingIRQ(I2C0_RX_IRQn);
} else if (module->hw == I2C1) {
NVIC_ClearPendingIRQ(I2C1_RX_IRQn);
}
} else {
if (module->hw == I2C0) {
NVIC_ClearPendingIRQ(I2C0_TX_IRQn);
} else if (module->hw == I2C1) {
NVIC_ClearPendingIRQ(I2C1_TX_IRQn);
}
}
}