/**
 * \file
 *
 * \brief SAM SERCOM I2C Master Driver
 *
 * Copyright (c) 2012-2018 Microchip Technology Inc. and its subsidiaries.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip
 * software and any derivatives exclusively with Microchip products.
 * It is your responsibility to comply with third party license terms applicable
 * to your use of third party software (including open source software) that
 * may accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
 * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
 * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
 * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
 * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
 * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
 * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
 * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE FULLEST EXTENT
 * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
 * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
 * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="https://www.microchip.com/support/">Microchip Support</a>
 */

#ifndef I2C_MASTER_H_INCLUDED
#define I2C_MASTER_H_INCLUDED

#include "i2c_common.h"
#include <sercom.h>
#include <pinmux.h>

#if I2C_MASTER_CALLBACK_MODE == true
#  include <sercom_interrupt.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

#ifndef PINMUX_DEFAULT
#  define PINMUX_DEFAULT 0
#endif

/**
 * \addtogroup asfdoc_sam0_sercom_i2c_group
 *
 * @{
 */

/**
 * \brief I<SUP>2</SUP>C master packet for read/write
 *
 * Structure to be used when transferring I<SUP>2</SUP>C master packets.
 */
struct i2c_master_packet {
	/** Address to slave device  */
	uint16_t address;
	/** Length of data array */
	uint16_t data_length;
	/** Data array containing all data to be transferred */
	uint8_t *data;
	/** Use 10-bit addressing. Set to false if the feature is not supported by the device  */
	bool ten_bit_address;
	/** Use high speed transfer. Set to false if the feature is not supported by the device */
	bool high_speed;
	/** High speed mode master code (0000 1XXX), valid when high_speed is true */
	uint8_t hs_master_code;
};

/** \brief Interrupt flags
 *
 * Flags used when reading or setting interrupt flags.
 */
enum i2c_master_interrupt_flag {
	/** Interrupt flag used for write */
	I2C_MASTER_INTERRUPT_WRITE = 0,
	/** Interrupt flag used for read */
	I2C_MASTER_INTERRUPT_READ  = 1,
};

/**
 * \brief Values for hold time after start bit.
 *
 * Values for the possible I<SUP>2</SUP>C master mode SDA internal hold times after start
 * bit has been sent.
 */
enum i2c_master_start_hold_time {
	/** Internal SDA hold time disabled */
	I2C_MASTER_START_HOLD_TIME_DISABLED = SERCOM_I2CM_CTRLA_SDAHOLD(0),
	/** Internal SDA hold time 50ns - 100ns */
	I2C_MASTER_START_HOLD_TIME_50NS_100NS = SERCOM_I2CM_CTRLA_SDAHOLD(1),
	/** Internal SDA hold time 300ns - 600ns */
	I2C_MASTER_START_HOLD_TIME_300NS_600NS = SERCOM_I2CM_CTRLA_SDAHOLD(2),
	/** Internal SDA hold time 400ns - 800ns */
	I2C_MASTER_START_HOLD_TIME_400NS_800NS = SERCOM_I2CM_CTRLA_SDAHOLD(3),
};

/**
 * \brief Values for inactive bus time-out.
 *
 * If the inactive bus time-out is enabled and the bus is inactive for
 * longer than the time-out setting, the bus state logic will be set to idle.
 */
enum i2c_master_inactive_timeout {
	/** Inactive bus time-out disabled */
	I2C_MASTER_INACTIVE_TIMEOUT_DISABLED = SERCOM_I2CM_CTRLA_INACTOUT(0),
	/** Inactive bus time-out 5-6 SCL cycle time-out */
	I2C_MASTER_INACTIVE_TIMEOUT_55US = SERCOM_I2CM_CTRLA_INACTOUT(1),
	/** Inactive bus time-out 10-11 SCL cycle time-out */
	I2C_MASTER_INACTIVE_TIMEOUT_105US = SERCOM_I2CM_CTRLA_INACTOUT(2),
	/** Inactive bus time-out 20-21 SCL cycle time-out */
	I2C_MASTER_INACTIVE_TIMEOUT_205US = SERCOM_I2CM_CTRLA_INACTOUT(3),
};

/**
 * \brief I<SUP>2</SUP>C frequencies
 *
 * Values for I<SUP>2</SUP>C speeds supported by the module. The driver
 * will also support setting any other value, in which case set
 * the value in the \ref i2c_master_config at desired value divided by 1000.
 *
 * Example: If 10KHz operation is required, give baud_rate in the configuration
 * structure the value 10.
 */
enum i2c_master_baud_rate {
	/** Baud rate at 100KHz (Standard-mode) */
	I2C_MASTER_BAUD_RATE_100KHZ = 100,
	/** Baud rate at 400KHz (Fast-mode) */
	I2C_MASTER_BAUD_RATE_400KHZ = 400,
#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED
	/** Baud rate at 1MHz (Fast-mode Plus) */
	I2C_MASTER_BAUD_RATE_1000KHZ = 1000,
	/** Baud rate at 3.4MHz (High-speed mode) */
	I2C_MASTER_BAUD_RATE_3400KHZ = 3400,
#endif
};

#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED
/**
 * \brief Enum for the transfer speed
 *
 * Enum for the transfer speed.
 */
enum i2c_master_transfer_speed {
	/** Standard-mode (Sm) up to 100KHz and Fast-mode (Fm) up to 400KHz */
	I2C_MASTER_SPEED_STANDARD_AND_FAST = SERCOM_I2CM_CTRLA_SPEED(0),
	/** Fast-mode Plus (Fm+) up to 1MHz */
	I2C_MASTER_SPEED_FAST_MODE_PLUS = SERCOM_I2CM_CTRLA_SPEED(1),
	/** High-speed mode (Hs-mode) up to 3.4MHz */
	I2C_MASTER_SPEED_HIGH_SPEED = SERCOM_I2CM_CTRLA_SPEED(2),
};
#endif

#if I2C_MASTER_CALLBACK_MODE == true
/**
 * \brief Callback types
 *
 * The available callback types for the I<SUP>2</SUP>C master module.
 */
enum i2c_master_callback {
	/** Callback for packet write complete */
	I2C_MASTER_CALLBACK_WRITE_COMPLETE = 0,
	/** Callback for packet read complete */
	I2C_MASTER_CALLBACK_READ_COMPLETE  = 1,
	/** Callback for error */
	I2C_MASTER_CALLBACK_ERROR          = 2,
#  if !defined(__DOXYGEN__)
	/** Total number of callbacks */
	_I2C_MASTER_CALLBACK_N             = 3,
#  endif
};

#  if !defined(__DOXYGEN__)
/* Prototype for software module */
struct i2c_master_module;

typedef void (*i2c_master_callback_t)(
		struct i2c_master_module *const module);
#  endif
#endif

/**
 * \brief SERCOM I<SUP>2</SUP>C Master driver software device instance structure.
 *
 * SERCOM I<SUP>2</SUP>C Master 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_master_module {
#if !defined(__DOXYGEN__)
	/** Hardware instance initialized for the struct */
	Sercom *hw;
	/** Module lock */
	volatile bool locked;
	/** Unknown bus state timeout */
	uint16_t unknown_bus_state_timeout;
	/** Buffer write timeout value */
	uint16_t buffer_timeout;
	/** If true, stop condition will be sent after a read/write */
	bool send_stop;
	/** If true, nack signal will be sent after a read/write */
	bool send_nack;
#  if I2C_MASTER_CALLBACK_MODE == true
	/** Pointers to callback functions */
	volatile i2c_master_callback_t callbacks[_I2C_MASTER_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
	 */
	volatile uint16_t buffer_remaining;
	/** Data buffer for packet write and read */
	volatile uint8_t *buffer;
	/** Save direction of async request. 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 I<SUP>2</SUP>C Master device
 *
 * This is the configuration structure for the I<SUP>2</SUP>C Master device. It
 * is used as an argument for \ref i2c_master_init to provide the desired
 * configurations for the module. The structure should be initialized using the
 * \ref i2c_master_get_config_defaults.
 */
struct i2c_master_config {
	/** Baud rate (in KHz) for I<SUP>2</SUP>C operations in
	 * standard-mode, Fast-mode, and Fast-mode Plus Transfers,
	 * \ref i2c_master_baud_rate */
	uint32_t baud_rate;
#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED
	/** Baud rate (in KHz) for I<SUP>2</SUP>C operations in
	 * High-speed mode, \ref i2c_master_baud_rate */
	uint32_t baud_rate_high_speed;
	/** Transfer speed mode */
	enum i2c_master_transfer_speed transfer_speed;
#endif
	/** GCLK generator to use as clock source */
	enum gclk_generator generator_source;
	/** Bus hold time after start signal on data line */
	enum i2c_master_start_hold_time start_hold_time;
	/** Unknown bus state \ref asfdoc_sam0_sercom_i2c_unknown_bus_timeout "timeout" */
	uint16_t unknown_bus_state_timeout;
	/** Timeout for packet write to wait for slave */
	uint16_t buffer_timeout;
	/** 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;
	/** Inactive bus time out */
	enum i2c_master_inactive_timeout inactive_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;
	/** Set to enable maser SCL low extend time-out */
	bool master_scl_low_extend_timeout;
#endif
	/** Get more accurate BAUD, considering rise time(required for standard-mode and Fast-mode) */
	uint16_t sda_scl_rise_time_ns;
};

/**
 * \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_master_lock(
		struct i2c_master_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_master_unlock(struct i2c_master_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[in]  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_master_is_syncing (
		const struct i2c_master_module *const module)
{
	/* Sanity check */
	Assert(module);
	Assert(module->hw);

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

#if defined(FEATURE_SERCOM_SYNCBUSY_SCHEME_VERSION_1)
	return (i2c_hw->STATUS.reg & SERCOM_I2CM_STATUS_SYNCBUSY);
#elif defined(FEATURE_SERCOM_SYNCBUSY_SCHEME_VERSION_2)
	return (i2c_hw->SYNCBUSY.reg & SERCOM_I2CM_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_master_wait_for_sync(
		const struct i2c_master_module *const module)
{
	/* Sanity check */
	Assert(module);

	while (i2c_master_is_syncing(module)) {
		/* Wait for I2C module to sync. */
	}
}
#endif

/**
 * \brief Gets the I<SUP>2</SUP>C master default configurations
 *
 * Use to initialize the configuration structure to known default values.
 *
 * The default configuration is as follows:
 * - Baudrate 100KHz
 * - GCLK generator 0
 * - Do not run in standby
 * - Start bit hold time 300ns - 600ns
 * - Buffer timeout = 65535
 * - Unknown bus status timeout = 65535
 * - Do not run in standby
 * - PINMUX_DEFAULT for SERCOM pads
 *
 * Those default configuration only available if the device supports it:
 * - High speed baudrate 3.4MHz
 * - Standard-mode and Fast-mode transfer speed
 * - SCL stretch disabled
 * - Slave SCL low extend time-out disabled
 * - Master SCL low extend time-out disabled
 *
 * \param[out] config  Pointer to configuration structure to be initiated
 */
static inline void i2c_master_get_config_defaults(
		struct i2c_master_config *const config)
{
	/*Sanity check argument */
	Assert(config);
	config->baud_rate        = I2C_MASTER_BAUD_RATE_100KHZ;
#ifdef FEATURE_I2C_FAST_MODE_PLUS_AND_HIGH_SPEED
	config->baud_rate_high_speed = I2C_MASTER_BAUD_RATE_3400KHZ;
	config->transfer_speed       = I2C_MASTER_SPEED_STANDARD_AND_FAST;
#endif
	config->generator_source = GCLK_GENERATOR_0;
	config->run_in_standby   = false;
	config->start_hold_time  = I2C_MASTER_START_HOLD_TIME_300NS_600NS;
	config->buffer_timeout   = 65535;
	config->unknown_bus_state_timeout = 65535;
	config->pinmux_pad0      = PINMUX_DEFAULT;
	config->pinmux_pad1      = PINMUX_DEFAULT;
	config->scl_low_timeout  = false;
	config->inactive_timeout = I2C_MASTER_INACTIVE_TIMEOUT_DISABLED;
#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;
	config->master_scl_low_extend_timeout  = false;
#endif
	/* The typical value is 215ns */
	config->sda_scl_rise_time_ns = 215;
}

enum status_code i2c_master_init(
		struct i2c_master_module *const module,
		Sercom *const hw,
		const struct i2c_master_config *const config);

/**
 * \brief Enables the I<SUP>2</SUP>C module
 *
 * Enables the requested I<SUP>2</SUP>C module and set the bus state to IDLE
 * after the specified \ref asfdoc_sam0_sercom_i2c_timeout "timeout" period if no
 * stop bit is detected.
 *
 * \param[in]  module  Pointer to the software module struct
 */
static inline void i2c_master_enable(
		const struct i2c_master_module *const module)
{
	/* Sanity check of arguments */
	Assert(module);
	Assert(module->hw);

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

	/* Timeout counter used to force bus state */
	uint32_t timeout_counter = 0;

	/* Wait for module to sync */
	_i2c_master_wait_for_sync(module);

	/* Enable module */
	i2c_module->CTRLA.reg |= SERCOM_I2CM_CTRLA_ENABLE;

#if I2C_MASTER_CALLBACK_MODE == true
	/* Enable module interrupts */
	system_interrupt_enable(_sercom_get_interrupt_vector(module->hw));
#endif
	/* Start timeout if bus state is unknown */
	while (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(1))) {
		timeout_counter++;
		if(timeout_counter >= (module->unknown_bus_state_timeout)) {
			/* Timeout, force bus state to idle */
			i2c_module->STATUS.reg = SERCOM_I2CM_STATUS_BUSSTATE(1);
			/* Workaround #1 */
			return;
		}
	}
}

/**
 * \brief Disable the I<SUP>2</SUP>C module
 *
 * Disables the requested I<SUP>2</SUP>C module.
 *
 * \param[in]  module  Pointer to the software module struct
 */
static inline void i2c_master_disable(
		const struct i2c_master_module *const module)
{
	/* Sanity check of arguments */
	Assert(module);
	Assert(module->hw);

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

#if I2C_MASTER_CALLBACK_MODE == true
		/* Disable module interrupts */
		system_interrupt_disable(_sercom_get_interrupt_vector(module->hw));
#endif

	/* Wait for module to sync */
	_i2c_master_wait_for_sync(module);

	/* Disbale interrupt */
	i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
	/* Clear interrupt flag */
	i2c_module->INTFLAG.reg = SERCOM_I2CM_INTFLAG_MASK;

	/* Disable module */
	i2c_module->CTRLA.reg &= ~SERCOM_I2CM_CTRLA_ENABLE;

}

void i2c_master_reset(struct i2c_master_module *const module);

/** @} */

/**
* \name Read and Write
* @{
*/

enum status_code i2c_master_read_packet_wait(
		struct i2c_master_module *const module,
		struct i2c_master_packet *const packet);

enum status_code i2c_master_read_packet_wait_no_stop(
		struct i2c_master_module *const module,
		struct i2c_master_packet *const packet);

enum status_code i2c_master_write_packet_wait(
		struct i2c_master_module *const module,
		struct i2c_master_packet *const packet);

enum status_code i2c_master_write_packet_wait_no_stop(
		struct i2c_master_module *const module,
		struct i2c_master_packet *const packet);

void i2c_master_send_stop(struct i2c_master_module *const module);

void i2c_master_send_nack(struct i2c_master_module *const module);

enum status_code i2c_master_read_byte(
		struct i2c_master_module *const module,
		uint8_t *byte);

enum status_code i2c_master_write_byte(
		struct i2c_master_module *const module,
		uint8_t byte);

enum status_code i2c_master_read_packet_wait_no_nack(
		struct i2c_master_module *const module,
		struct i2c_master_packet *const packet);

/** @} */

#ifdef FEATURE_I2C_DMA_SUPPORT
/**
* \name SERCOM I2C Master with DMA Interfaces
* @{
*/

/**
 * \brief Set I<SUP>2</SUP>C for DMA transfer with slave address and transfer size.
 *
 * This function will set the slave address, transfer size and enable the auto transfer
 * mode for DMA.
 *
 * \param[in,out] module Pointer to the driver instance to lock
 * \param[in] addr I<SUP>2</SUP>C slave address
 * \param[in] length I<SUP>2</SUP>C transfer length with DMA
 * \param[in] direction I<SUP>2</SUP>C transfer direction
 *
 */
static inline void i2c_master_dma_set_transfer(struct i2c_master_module *const module,
		uint16_t addr, uint8_t length, enum i2c_transfer_direction direction)
{
	module->hw->I2CM.ADDR.reg =
		SERCOM_I2CM_ADDR_ADDR(addr<<1) |
		SERCOM_I2CM_ADDR_LENEN |
		SERCOM_I2CM_ADDR_LEN(length) |
		direction;
}

/** @} */
#endif

/** @} */

#ifdef __cplusplus
}
#endif

#endif /* I2C_MASTER_H_INCLUDED */
