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

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

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

	/* Disable module */
	i2c_master_disable(module);

#if I2C_MASTER_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_master_wait_for_sync(module);

	/* Reset module */
	i2c_module->CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST;
}
/**
 * \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);

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

	/* Find index to save next value in buffer */
	uint16_t buffer_index = module->buffer_length - module->buffer_remaining;

	module->buffer_remaining--;

	if (!module->buffer_remaining) {
		/* Send nack */
		i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT;
		if (module->send_stop) {
			/* Send stop condition */
			_i2c_master_wait_for_sync(module);
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
		}
	} else {
		i2c_module->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;
	}

	/* Read byte from slave and put in buffer */
	_i2c_master_wait_for_sync(module);
	module->buffer[buffer_index] = i2c_module->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);

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

	/* Check for ack from slave */
	if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK)
	{
		/* Set status */
		module->status = STATUS_ERR_OVERFLOW;
		/* Do not write more data */
		return;
	}

	/* Find index to get next byte in buffer */
	uint16_t buffer_index = module->buffer_length - module->buffer_remaining;

	module->buffer_remaining--;

	/* Write byte from buffer to slave */
	_i2c_master_wait_for_sync(module);
	i2c_module->DATA.reg = module->buffer[buffer_index];
}
/**
 * \brief Sends stop condition on bus
 *
 * Sends a stop condition on bus.
 *
 * \note This function can only be used after the
 *       \ref i2c_master_write_packet_wait_no_stop function. If a stop condition
 *       is to be sent after a read, the \ref i2c_master_read_packet_wait
 *       function must be used.
 *
 * \param[in] module  Pointer to the software instance struct
 */
void i2c_master_send_stop(struct i2c_master_module *const module)
{
	/* Sanity check */
	Assert(module);
	Assert(module->hw);

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

	/* Send stop command */
	_i2c_master_wait_for_sync(module);
	i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
}
/**
 * \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);

	SercomI2cm *const i2c_module = &(module->hw->I2CM);
	bool sclsm_flag = i2c_module->CTRLA.bit.SCLSM;

	/* Find index to save next value in buffer */
	uint16_t buffer_index = module->buffer_length;
	buffer_index -= module->buffer_remaining;

	module->buffer_remaining--;

	if (sclsm_flag) {
		if (module->send_nack && module->buffer_remaining == 1) {
			/* Set action to NACK. */
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT;
		}
	} else {
		if (module->send_nack && module->buffer_remaining == 0) {
			/* Set action to NACK. */
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT;
		}
	}

	if (module->buffer_remaining == 0) {
		if (module->send_stop) {
			/* Send stop condition */
			_i2c_master_wait_for_sync(module);
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
		}
	}
	
	/* Read byte from slave and put in buffer */
	_i2c_master_wait_for_sync(module);
	module->buffer[buffer_index] = i2c_module->DATA.reg;
}
/**
 * \internal
 * Acts on slave address response. Checks for errors concerning master->slave
 * handshake.
 *
 * \param[in,out] module  Pointer to software module structure
 */
static void _i2c_master_async_address_response(
		struct i2c_master_module *const module)
{
	/* Sanity check arguments. */
	Assert(module);
	Assert(module->hw);

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

	/* Check for error. Ignore bus-error; workaround for bus state stuck in
	 * BUSY.
	 */
	if (i2c_module->INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB)
	{
		/* Clear write interrupt flag */
		i2c_module->INTFLAG.reg = SERCOM_I2CM_INTENCLR_MB;

		/* Check arbitration */
		if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_ARBLOST) {
			/* Return busy */
			module->status = STATUS_ERR_PACKET_COLLISION;
		}
	}

	if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) {
		/* Return bad address value */
		module->status           = STATUS_ERR_BAD_ADDRESS;
		module->buffer_remaining = 0;

		if (module->send_stop) {
			/* Send stop condition */
			_i2c_master_wait_for_sync(module);
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
		}
	}

	module->buffer_length = module->buffer_remaining;

	/* Check for status OK. */
	if (module->status == STATUS_BUSY) {
		/* Call function based on transfer direction. */
		if (module->transfer_direction == I2C_TRANSFER_WRITE) {
			_i2c_master_write(module);
		} else {
			_i2c_master_read(module);
		}
	}
}
/**
 * \internal
 * Starts blocking write operation.
 *
 * \param[in,out] module  Pointer to software module struct.
 * \param[in,out] packet  Pointer to I<SUP>2</SUP>C packet to transfer.
 *
 * \return Status of reading packet.
 * \retval STATUS_OK                    The packet was read successfully
 * \retval STATUS_ERR_TIMEOUT           If no response was given within
 *                                      specified timeout period
 * \retval STATUS_ERR_DENIED            If error on bus
 * \retval STATUS_ERR_PACKET_COLLISION  If arbitration is lost
 * \retval STATUS_ERR_BAD_ADDRESS       If slave is busy, or no slave
 *                                      acknowledged the address
 */
static enum status_code _i2c_master_write_packet(
		struct i2c_master_module *const module,
		struct i2c_master_packet *const packet)
{
	SercomI2cm *const i2c_module = &(module->hw->I2CM);

	/* Return value. */
	enum status_code tmp_status;
	uint16_t tmp_data_length = packet->data_length;

	_i2c_master_wait_for_sync(module);

	/* Set address and direction bit. Will send start command on bus. */
	i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_WRITE;

	/* Wait for response on bus. */
	tmp_status = _i2c_master_wait_for_bus(module);

	/* Check for address response error unless previous error is
	 * detected. */
	if (tmp_status == STATUS_OK) {
		tmp_status = _i2c_master_address_response(module);
	}

	/* Check that no error has occurred. */
	if (tmp_status == STATUS_OK) {
		/* Buffer counter. */
		uint16_t buffer_counter = 0;

		/* Write data buffer. */
		while (tmp_data_length--) {
			/* Check that bus ownership is not lost. */
			if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) {
				return STATUS_ERR_PACKET_COLLISION;
			}

			/* Write byte to slave. */
			_i2c_master_wait_for_sync(module);
			i2c_module->DATA.reg = packet->data[buffer_counter++];

			/* Wait for response. */
			tmp_status = _i2c_master_wait_for_bus(module);

			/* Check for error. */
			if (tmp_status != STATUS_OK) {
				break;
			}

			/* Check for NACK from slave. */
			if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) {
				/* Return bad data value. */
				tmp_status = STATUS_ERR_OVERFLOW;
				break;
			}
		}

		if (module->send_stop) {
			/* Stop command */
			_i2c_master_wait_for_sync(module);
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
		}
	}

	return tmp_status;
}
/**
 * \internal
 * Starts blocking read operation.
 *
 * \param[in,out] module  Pointer to software module struct.
 * \param[in,out] packet  Pointer to I<SUP>2</SUP>C packet to transfer.
 *
 * \return Status of reading packet.
 * \retval STATUS_OK                    The packet was read successfully
 * \retval STATUS_ERR_TIMEOUT           If no response was given within
 *                                      specified timeout period
 * \retval STATUS_ERR_DENIED            If error on bus
 * \retval STATUS_ERR_PACKET_COLLISION  If arbitration is lost
 * \retval STATUS_ERR_BAD_ADDRESS       If slave is busy, or no slave
 *                                      acknowledged the address
 *
 */
static enum status_code _i2c_master_read_packet(
		struct i2c_master_module *const module,
		struct i2c_master_packet *const packet)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);
	Assert(packet);

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

	/* Return value. */
	enum status_code tmp_status;
	uint16_t tmp_data_length = packet->data_length;

	/* Written buffer counter. */
	uint16_t counter = 0;

	/* Set address and direction bit. Will send start command on bus. */
	i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_READ;

	/* Wait for response on bus. */
	tmp_status = _i2c_master_wait_for_bus(module);

	/* Set action to ack. */
	i2c_module->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;

	/* Check for address response error unless previous error is
	 * detected. */
	if (tmp_status == STATUS_OK) {
		tmp_status = _i2c_master_address_response(module);
	}

	/* Check that no error has occurred. */
	if (tmp_status == STATUS_OK) {
		/* Read data buffer. */
		while (tmp_data_length--) {
			/* Check that bus ownership is not lost. */
			if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) {
				return STATUS_ERR_PACKET_COLLISION;
			}

			if (tmp_data_length == 0) {
				/* Set action to NACK */
				i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT;
			} else {
				/* Save data to buffer. */
				_i2c_master_wait_for_sync(module);
				packet->data[counter++] = i2c_module->DATA.reg;
				/* Wait for response. */
				tmp_status = _i2c_master_wait_for_bus(module);
			}

			/* Check for error. */
			if (tmp_status != STATUS_OK) {
				break;
			}
		}

		if (module->send_stop) {
			/* Send stop command unless arbitration is lost. */
			_i2c_master_wait_for_sync(module);
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
		}

		/* Save last data to buffer. */
		_i2c_master_wait_for_sync(module);
		packet->data[counter] = i2c_module->DATA.reg;
	}

	return tmp_status;
}
/**
 * \internal
 * Interrupt handler for I<SUP>2</SUP>C master.
 *
 * \param[in] instance  SERCOM instance that triggered the interrupt
 */
void _i2c_master_interrupt_handler(
		uint8_t instance)
{
	/* Get software module for callback handling */
	struct i2c_master_module *module =
			(struct i2c_master_module*)_sercom_instances[instance];

	Assert(module);

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

	/* Combine callback registered and enabled masks */
	uint8_t callback_mask = module->enabled_callback &
			module->registered_callback;

	/* Check if the module should respond to address ack */
	if ((module->buffer_length <= 0) && (module->buffer_remaining > 0)) {
		/* Call function for address response */
		_i2c_master_async_address_response(module);

	/* 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)) {
		/* Stop packet operation */
		i2c_module->INTENCLR.reg =
				SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB;

		module->buffer_length = 0;
		module->status        = STATUS_OK;

		if (module->send_stop) {
			/* Send stop condition */
			_i2c_master_wait_for_sync(module);
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3);
		} else {
			/* Clear write interrupt flag */
			i2c_module->INTFLAG.reg = SERCOM_I2CM_INTENCLR_MB;
		}
		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)){
		/* Check that bus ownership is not lost */
		if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) {
			module->status = STATUS_ERR_PACKET_COLLISION;
		} else 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)) {

		/* Stop packet operation */
		i2c_module->INTENCLR.reg =
				SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB;
		module->buffer_length = 0;
		module->status        = STATUS_OK;

		/* Call appropriate callback if enabled and registered */
		if ((callback_mask & (1 << I2C_MASTER_CALLBACK_READ_COMPLETE))
				&& (module->transfer_direction == I2C_TRANSFER_READ)) {
			module->callbacks[I2C_MASTER_CALLBACK_READ_COMPLETE](module);
		} else if ((callback_mask & (1 << I2C_MASTER_CALLBACK_WRITE_COMPLETE))
				&& (module->transfer_direction == I2C_TRANSFER_WRITE)) {
			module->callbacks[I2C_MASTER_CALLBACK_WRITE_COMPLETE](module);
		}
	}

	/* Check for error */
	if ((module->status != STATUS_BUSY) && (module->status != STATUS_OK)) {
		/* Stop packet operation */
		i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB |
				SERCOM_I2CM_INTENCLR_SB;

		module->buffer_length = 0;
		module->buffer_remaining = 0;

		/* Send nack and stop command unless arbitration is lost */
		if ((module->status != STATUS_ERR_PACKET_COLLISION) &&
				module->send_stop) {
			_i2c_master_wait_for_sync(module);
			i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT |
					SERCOM_I2CM_CTRLB_CMD(3);
		}

		/* Call error callback if enabled and registered */
		if (callback_mask & (1 << I2C_MASTER_CALLBACK_ERROR)) {
			module->callbacks[I2C_MASTER_CALLBACK_ERROR](module);
		}
	}
}