/* mbed Microcontroller Library
 * Copyright (c) 2006-2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "mbed_assert.h"
#include "i2c_api.h"

#include <math.h>

#include "cmsis.h"
#include "pinmap.h"
#include "sercom.h"
#include "i2c_master.h"
#include "i2c_slave.h"

#include "pinmap_function.h"

#if DEVICE_I2C_ASYNCH
#include "i2c_master_interrupt.h"
#endif


#if DEVICE_I2C_ASYNCH
#define pI2C_S(obj)			(&obj->i2c)
#else
#define pI2C_S(obj)			obj
#endif

#define I2C_MASTER_DEFAULT_BAUD     100000

/**
 * \brief I2C modes enum
 *
 * I2C mode selection.
 */
enum i2c_mode {
    /** Master mode. */
    I2C_MODE_MASTER         = 0x5,
    /** Slave mode. */
    I2C_MODE_SLAVE          = 0x4,
};

/* Extern Variables */
extern uint8_t g_sys_init;

typedef void (*I2CHandler)(void);

#if DEVICE_I2C_ASYNCH
#define _SERCOM_INTERRUPT_HANDLERS(n, unused) (uint32_t)SERCOM##n##_Handler,

/* To save the i2c objects */
static uint32_t i2c_instances[SERCOM_INST_NUM] = {0};
const uint32_t sercom_irq_handlers[SERCOM_INST_NUM] = {
    MREPEAT(SERCOM_INST_NUM, _SERCOM_INTERRUPT_HANDLERS, ~)
};
#endif

/* Forward declaration */
enum status_code _i2c_master_wait_for_bus(
    struct i2c_master_module *const module);

enum status_code _i2c_master_address_response(
    struct i2c_master_module *const module);

enum status_code _i2c_master_send_hs_master_code(
    struct i2c_master_module *const module,
    uint8_t hs_master_code);

/* Adding function from ASF for compatibility */
static enum status_code _i2c_slave_wait_for_bus(
    struct i2c_slave_module *const module)
{
    /* Sanity check arguments. */
    MBED_ASSERT(module);
    MBED_ASSERT(module->hw);

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

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

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

/** Initialize the I2C peripheral
 *
 * Configures the pins used by I2C, sets a default format and frequency, and enables the peripheral
 * @param[out] obj  The I2C object to initialize
 * @param[in]  sda  The pin to use for SDA
 * @param[in]  scl  The pin to use for SCL
 * @return          void
 */
void i2c_init(i2c_t *obj, PinName sda, PinName scl)
{
    Sercom* hw;
    uint32_t mux_func;
    struct i2c_master_config config_i2c_master;

    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(sda != NC);
    MBED_ASSERT(scl != NC);

    if (g_sys_init == 0) {
        system_init();
        g_sys_init = 1;
    }

    pI2C_S(obj)->pins[0] = sda;
    pI2C_S(obj)->pins[1] = scl;

    /* Calculate SERCOM instance from pins */
    uint32_t sercom_index = pinmap_merge_sercom(sda, scl);
    if (sercom_index == (uint32_t)NC) {
        return;
    }
    hw = (Sercom*)pinmap_peripheral_sercom(NC, sercom_index);

    i2c_master_get_config_defaults(&config_i2c_master);

    /* SERCOM PAD0 - SDA */
    mux_func = pinmap_function_sercom(sda, sercom_index);
    if (mux_func == (uint32_t)NC) return;
    config_i2c_master.pinmux_pad0 = (sda << 16) | (mux_func & 0xFFFF);

    /* SERCOM PAD1 - SCL */
    mux_func = pinmap_function_sercom(scl, sercom_index);
    if (mux_func == (uint32_t)NC) return;
    config_i2c_master.pinmux_pad1 = (scl << 16) | (mux_func & 0xFFFF);

    /* Default baud rate is set to 100kHz */
    pI2C_S(obj)->baud_rate = I2C_MASTER_DEFAULT_BAUD;
    config_i2c_master.baud_rate = pI2C_S(obj)->baud_rate / 1000;

    while(i2c_master_init(&pI2C_S(obj)->master, hw, &config_i2c_master) != STATUS_OK);
    pI2C_S(obj)->mode = I2C_MODE_MASTER;

#if DEVICE_I2C_ASYNCH
    /* Save the i2c object */
    i2c_instances[sercom_index] = (uint32_t)obj;
#endif

    i2c_master_enable(&pI2C_S(obj)->master);
}

/** Configure the I2C frequency.
 *  @param obj The i2c object
 *  @param hz  Frequency in Hz
 */
void i2c_frequency(i2c_t *obj, int hz)
{
    /* Temporary variables. */
    int32_t baud_rate;
    int32_t tmp_baud;
    int32_t tmp_baud_hs;
    enum status_code tmp_status_code;

    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    /* Return if in Slave mode, slave do not have any baud to set */
    if (pI2C_S(obj)->mode != I2C_MODE_MASTER) return;

    SercomI2cm *const i2c_module = &(pI2C_S(obj)->master.hw->I2CM);

    /* Disable I2C Module */
    i2c_master_disable(&pI2C_S(obj)->master);

    baud_rate = hz / 1000; /* To kHz */

    /* Give a dummy supported value */
    pI2C_S(obj)->baud_rate_high_speed = I2C_MASTER_BAUD_RATE_3400KHZ;

    uint32_t sercom_index = _sercom_get_sercom_inst_index(pI2C_S(obj)->master.hw);

    /* Find and set baudrate. */
    tmp_baud = (int32_t)(div_ceil(
                             system_gclk_chan_get_hz(SERCOM0_GCLK_ID_CORE + sercom_index), (2000*(baud_rate))) - 5);

    /* Check that baudrate is supported at current speed. */
    if (tmp_baud > 255 || tmp_baud < 0) {
        /* Baud rate not supported. */
        tmp_status_code = STATUS_ERR_BAUDRATE_UNAVAILABLE;
    } else {
        /* Find baudrate for high speed */
        tmp_baud_hs = (int32_t)(div_ceil(
                                    system_gclk_chan_get_hz(SERCOM0_GCLK_ID_CORE + sercom_index),
                                    (2000*(pI2C_S(obj)->baud_rate_high_speed))) - 1);

        /* Check that baudrate is supported at current speed. */
        if (tmp_baud_hs > 255 || tmp_baud_hs < 0) {
            /* Baud rate not supported. */
            tmp_status_code = STATUS_ERR_BAUDRATE_UNAVAILABLE;
        }
    }
    if (tmp_status_code != STATUS_ERR_BAUDRATE_UNAVAILABLE) {
        /* Baud rate acceptable. */
        i2c_module->BAUD.reg = SERCOM_I2CM_BAUD_BAUD(tmp_baud) | SERCOM_I2CM_BAUD_HSBAUD(tmp_baud_hs);
        pI2C_S(obj)->baud_rate = hz;
    }

    /* Enable back the I2C Module */
    i2c_master_enable(&pI2C_S(obj)->master);
}

/** Send START command.
 *  @param obj The i2c object
 */
int  i2c_start(i2c_t *obj)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);

    if (pI2C_S(obj)->mode == I2C_MODE_MASTER) {
        pI2C_S(obj)->start_pending = 1;
    }
    return 0;
}

/** Send STOP command.
 *  @param obj The i2c object
 */
int  i2c_stop(i2c_t *obj)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);

    if (pI2C_S(obj)->mode == I2C_MODE_MASTER) {
        /* Send STOP command */
        i2c_master_send_stop(&pI2C_S(obj)->master);
    } else {
        SercomI2cs *const i2c_hw = &(pI2C_S(obj)->slave.hw->I2CS);
        /* Release line and wait for next start condition */
        i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x2);
    }

    pI2C_S(obj)->start_pending = 0;

    /* TODO: Wait till STOP is send */
    return 0;
}

/** Blocking reading data.
 *  @param obj     The i2c object
 *  @param address 7-bit address (last bit is 1)
 *  @param data    The buffer for receiving
 *  @param length  Number of bytes to read
 *  @param stop    Stop to be generated after the transfer is done
 *  @return        Number of read bytes
 */
int  i2c_read(i2c_t *obj, int address, char *data, int length, int stop)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    enum status_code tmp_status;

#if DEVICE_I2C_ASYNCH
    if (i2c_active(obj)) {
        /* I2C is busy with a job */
        return 0;
    }
#endif

    struct i2c_master_packet packet;
    packet.address = (address & 0xFF) >> 1;
    packet.data_length = length;
    packet.data = (uint8_t*)data;
    packet.ten_bit_address = false;
    packet.high_speed = false;

    if (stop) {
        tmp_status = i2c_master_read_packet_wait(&pI2C_S(obj)->master, &packet);
    } else {
        tmp_status = i2c_master_read_packet_wait_no_stop(&pI2C_S(obj)->master, &packet);
    }

    if (tmp_status == STATUS_OK) {
        return length;
    } else {
        /* Currently, no way to track no of bytes received, so return 0 if fail */
        return 0;
    }
}

/** Blocking sending data.
 *  @param obj     The i2c object
 *  @param address 7-bit address (last bit is 0)
 *  @param data    The buffer for sending
 *  @param length  Number of bytes to write
 *  @param stop    Stop to be generated after the transfer is done
 *  @return        Number of written bytes
 */
int  i2c_write(i2c_t *obj, int address, const char *data, int length, int stop)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    enum status_code tmp_status;

#if DEVICE_I2C_ASYNCH
    if (i2c_active(obj)) {
        /* I2C is busy with a job */
        return 0;
    }
#endif

    struct i2c_master_packet packet;
    packet.address = (address & 0xFF) >> 1;
    packet.data_length = length;
    packet.data = data;
    packet.ten_bit_address = false;
    packet.high_speed = false;

    if (stop) {
        tmp_status = i2c_master_write_packet_wait(&pI2C_S(obj)->master, &packet);
    } else {
        tmp_status = i2c_master_write_packet_wait_no_stop(&pI2C_S(obj)->master, &packet);
    }

    if (tmp_status == STATUS_OK) {
        return length;
    } else {
        /* Currently, no way to track no of bytes transmitted, so return 0 if fail */
        return 0;
    }
}

/** Reset I2C peripheral.
 *  @param obj The i2c object
 */
void i2c_reset(i2c_t *obj)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);

    /* Send STOP */
    i2c_stop(obj);

    pI2C_S(obj)->start_pending = 0;
}

/** Write address preceded by START condition.
 *  @param obj The i2c object
 *  @param address Address to be placed
 *  @param rw_flag read or write flag
 *  @return 1 if NAK was received, 0 if ACK was received, 2 for timeout.
 */
int  i2c_write_address(i2c_t *obj, int address, int rw_flag)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    enum status_code tmp_status;
    SercomI2cm *const i2c_module = &(pI2C_S(obj)->master.hw->I2CM);

    _i2c_master_wait_for_sync(&pI2C_S(obj)->master);

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

    /* Write 7-bit address + read/write flag */
    i2c_module->ADDR.reg = ((address & 0x7F) << 1) | (rw_flag & 0x01) | (0 << SERCOM_I2CM_ADDR_HS_Pos);

    /* Wait for response on bus. */
    tmp_status = _i2c_master_wait_for_bus(&pI2C_S(obj)->master);
    /* Check for error. */
    if (tmp_status != STATUS_OK) {
        return I2C_ERROR_BUS_BUSY;
    }
    /* Check for address response error unless previous error is detected. */
    tmp_status = _i2c_master_address_response(&pI2C_S(obj)->master);
    if (tmp_status != STATUS_OK) {
        return I2C_ERROR_NO_SLAVE;
    }

    return 0;
}

/** Read one byte.
 *  @param obj The i2c object
 *  @param last Acknowledge
 *  @return The read byte
 */
int  i2c_byte_read(i2c_t *obj, int last)
{
    int data = -1;
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    enum status_code tmp_status;

    if (pI2C_S(obj)->mode == I2C_MODE_MASTER) {
        SercomI2cm *const i2c_module = &(pI2C_S(obj)->master.hw->I2CM);

        if (last) {
            /* Set action to nack. */
            i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT;
        } else {
            /* Set action to ack. */
            i2c_module->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;
        }

        /* Check that bus ownership is not lost. */
        if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) {
            return -1; /* Return invalid data*/
        }

        /* Save data to buffer. */
        _i2c_master_wait_for_sync(&pI2C_S(obj)->master);
        data = i2c_module->DATA.reg;
        /* Wait for response. */
        tmp_status = _i2c_master_wait_for_bus(&pI2C_S(obj)->master);

        /* Check for error. */
        if (tmp_status != STATUS_OK) {
            return -1; /* Return invalid data*/
        }

    } else {
#if DEVICE_I2CSLAVE
        SercomI2cs *const i2c_hw = &(pI2C_S(obj)->slave.hw->I2CS);

        /* 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 -1; /* Return invalid data*/
        }

        if (i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH) {
            /* Request from master, Address not yet acknowledged */
            i2c_hw->CTRLB.reg &= ~SERCOM_I2CS_CTRLB_ACKACT;
            i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3);
            i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_AMATCH;
        }
        if (last) {
            /* Set action to nack. */
            i2c_hw->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT;
        } else {
            /* Set action to ack. */
            i2c_hw->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT;
        }

        /* Wait for next byte or stop condition */
        tmp_status = _i2c_slave_wait_for_bus(&pI2C_S(obj)->slave);
        if (tmp_status != STATUS_OK) {
            /* Timeout, return */
            return -1;
        }

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

        /* Read data */
        _i2c_slave_wait_for_sync(&pI2C_S(obj)->slave);
        data = i2c_hw->DATA.reg;
#endif
    }

    return data;
}

/** Write one byte.
 *  @param obj The i2c object
 *  @param data Byte to be written
 *  @return 1 if NAK was received, 0 if ACK was received, 2 for timeout.
 */
int  i2c_byte_write(i2c_t *obj, int data)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    enum status_code tmp_status;

    data = data & 0xFF;

    if (pI2C_S(obj)->mode == I2C_MODE_MASTER) {
        SercomI2cm *const i2c_module = &(pI2C_S(obj)->master.hw->I2CM);

        if (pI2C_S(obj)->start_pending) {
            pI2C_S(obj)->start_pending = 0;
            /* Write address */
            return i2c_write_address(obj, (data >> 1), (data & 0x01));
        } else {
            /* Write data */
            /* Check that bus ownership is not lost. */
            if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) {
                return I2C_ERROR_NO_SLAVE;
            }

            /* Write byte to slave. */
            _i2c_master_wait_for_sync(&pI2C_S(obj)->master);
            i2c_module->DATA.reg = data;

            /* Wait for response. */
            tmp_status = _i2c_master_wait_for_bus(&pI2C_S(obj)->master);
            /* Check for error. */
            if (tmp_status != STATUS_OK) {
                return I2C_ERROR_BUS_BUSY;
            }

            /* Check for NACK from slave. */
            if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) {
                /* Return bad data value. */
                return I2C_ERROR_NO_SLAVE;
            }
        }
    } else {
#if DEVICE_I2CSLAVE
        SercomI2cs *const i2c_hw = &(pI2C_S(obj)->slave.hw->I2CS);

        if (i2c_hw->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH) {
            /* Read request from master, Address not yet acknowledged */
            i2c_hw->CTRLB.reg &= ~SERCOM_I2CS_CTRLB_ACKACT;
            i2c_hw->CTRLB.reg |= SERCOM_I2CS_CTRLB_CMD(0x3);
            i2c_hw->INTFLAG.reg = SERCOM_I2CS_INTFLAG_AMATCH;
        }

        /* Write data */
        _i2c_slave_wait_for_sync(&pI2C_S(obj)->slave);
        i2c_hw->DATA.reg = data;

        /* Wait for response from master */
        tmp_status = _i2c_slave_wait_for_bus(&pI2C_S(obj)->slave);

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

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

            return I2C_ERROR_NO_SLAVE;
        }
#endif
    }

    return 0;
}


#if DEVICE_I2CSLAVE

/**
 * \defgroup SynchI2C Synchronous I2C Hardware Abstraction Layer for slave
 * @{
 */

/** Configure I2C as slave or master.
 *  @param obj The I2C object
 *  @param enable_slave configure I2C in slave mode or not
 *  @return void
 */
void i2c_slave_mode(i2c_t *obj, int enable_slave)
{
    int i;
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    uint32_t mux_func[2];
    uint32_t sercom_index = _sercom_get_sercom_inst_index(pI2C_S(obj)->master.hw);
    for (i=0; i<2; i++) {
        mux_func[i] = pinmap_function_sercom(pI2C_S(obj)->pins[0], sercom_index);
        if (mux_func[i] == NC) return;
    }

    if (enable_slave) {
        /* Disable I2C Master module if active */
        i2c_master_disable(&pI2C_S(obj)->master);

        struct i2c_slave_config config_i2c_slave;
        i2c_slave_get_config_defaults(&config_i2c_slave);

        /* SERCOM PAD0 - SDA */
        config_i2c_slave.pinmux_pad0 = (pI2C_S(obj)->pins[0] << 16) | (mux_func[0] & 0xFFFF);
        /* SERCOM PAD1 - SCL */
        config_i2c_slave.pinmux_pad1 = (pI2C_S(obj)->pins[1] << 16) | (mux_func[1] & 0xFFFF);

        i2c_slave_init(&pI2C_S(obj)->slave, pI2C_S(obj)->master.hw, &config_i2c_slave);

        pI2C_S(obj)->mode = I2C_MODE_SLAVE;

        i2c_slave_enable(&pI2C_S(obj)->slave);
    } else {
        if ((pI2C_S(obj)->master.hw) && (pI2C_S(obj)->mode == I2C_MODE_MASTER)) {
            /* Already configured, enable and return */
            i2c_master_enable(&pI2C_S(obj)->master);
            return;
        } else if ((pI2C_S(obj)->slave.hw) && (pI2C_S(obj)->mode == I2C_MODE_SLAVE)) {
            /* Disable slave */
            i2c_slave_disable(&pI2C_S(obj)->slave);
        }

        struct i2c_master_config config_i2c_master;
        /* SERCOM PAD0 - SDA */
        config_i2c_master.pinmux_pad0 = (pI2C_S(obj)->pins[0] << 16) | (mux_func[0] & 0xFFFF);
        /* SERCOM PAD1 - SCL */
        config_i2c_master.pinmux_pad1 = (pI2C_S(obj)->pins[1] << 16) | (mux_func[1] & 0xFFFF);
        /* Baud rate */
        config_i2c_master.baud_rate = pI2C_S(obj)->baud_rate / 1000;

        while(i2c_master_init(&pI2C_S(obj)->master, pI2C_S(obj)->master.hw, &config_i2c_master) != STATUS_OK);
        pI2C_S(obj)->mode = I2C_MODE_MASTER;

        i2c_master_enable(&pI2C_S(obj)->master);
    }
}

/** Check to see if the I2C slave has been addressed.
 *  @param obj The I2C object
 *  @return The status - 1 - read addresses, 2 - write to all slaves,
 *         3 write addressed, 0 - the slave has not been addressed
 */
int  i2c_slave_receive(i2c_t *obj)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->slave.hw);

    SercomI2cs *const i2c_module = &(pI2C_S(obj)->slave.hw->I2CS);

    if (i2c_module->INTFLAG.reg & SERCOM_I2CS_INTFLAG_AMATCH) {
        if (i2c_module->STATUS.reg & SERCOM_I2CS_STATUS_DIR) {
            /* Slave is read addressed */
            return 1;
        } else {
            if (!(i2c_module->DATA.reg & 0xFF)) {
                /* General call address detected */
                return 2;
            } else {
                /* Slave is write addressed */
                return 3;
            }
        }
    }

    return 0;
}

/** Blocking reading data.
 *  @param obj     The i2c slave object
 *  @param data    The buffer for receiving
 *  @param length  Number of bytes to read
 *  @return        Number of read bytes
 */
int  i2c_slave_read(i2c_t *obj, char *data, int length)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->slave.hw);

    if (!data || !length) return 0;

    enum status_code tmp_status;

    struct i2c_slave_packet packet;
    packet.data_length = length;
    packet.data = (uint8_t*)data;

    tmp_status = i2c_slave_read_packet_wait(&pI2C_S(obj)->slave, &packet);

    if (tmp_status == STATUS_OK) {
        return length;
    } else {
        /* Currently, no way to track no of bytes transmitted, so return 0 */
        return 0;
    }

}

/** Blocking writing data.
 *  @param obj     The i2c slave object
 *  @param data    The buffer for transmitting
 *  @param length  Number of bytes to write
 *  @return        Number of bytes written
 */
int  i2c_slave_write(i2c_t *obj, const char *data, int length)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->slave.hw);

    if (!data || !length) return 0;

    enum status_code tmp_status;

    struct i2c_slave_packet packet;
    packet.data_length = length;
    packet.data = data;

    tmp_status = i2c_slave_write_packet_wait(&pI2C_S(obj)->slave, &packet);

    if (tmp_status == STATUS_OK) {
        return length;
    } else {
        /* Currently, no way to track no of bytes transmitted, so return 0 */
        return 0;
    }
}

/** Configure I2C slave address.
 *  @param obj     The I2C object
 *  @param idx     Currently not used
 *  @param address The address to be set
 *  @param mask    Currently not used
 */
void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->slave.hw);

    /* Disable I2C Module */
    i2c_slave_disable(&pI2C_S(obj)->slave);

    SercomI2cs *const i2c_hw = &(pI2C_S(obj)->slave.hw->I2CS);

    address = (address & 0xFF) >> 1;
    if (!mask) {
        mask = (0xFE >> 1);
    }
    /* Set the address and address mask */
    i2c_hw->ADDR.reg = (address << SERCOM_I2CS_ADDR_ADDR_Pos) |
                       (mask << SERCOM_I2CS_ADDR_ADDRMASK_Pos) |
                       (0 << SERCOM_I2CS_ADDR_TENBITEN_Pos) |
                       (1 << SERCOM_I2CS_ADDR_GENCEN_Pos);

    /* Enable I2C Module */
    i2c_slave_enable(&pI2C_S(obj)->slave);
}

#endif /* DEVICE_I2CSLAVE */

/**@}*/

#if DEVICE_I2C_ASYNCH

/**
 * \defgroup AsynchI2C Asynchronous I2C Hardware Abstraction Layer
 * @{
 */

/**
* \internal
* Callback for transfer finish.
*
* \param[in,out]  module  Pointer to SPI software instance struct
*/
void i2c_transfer_complete_callback(struct i2c_master_module *const module)
{
    uint32_t sercom_index = _sercom_get_sercom_inst_index(module->hw);

    if (sercom_index >= SERCOM_INST_NUM) {
        /* TODO: Abort operation */
        return;
    }

    i2c_t *obj = (i2c_t*)i2c_instances[sercom_index];

    i2c_master_disable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_WRITE_COMPLETE);
    i2c_master_disable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_READ_COMPLETE);
    i2c_master_disable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_ERROR);

    /* Call the handler function */
    if (pI2C_S(obj)->handler) {
        ((I2CHandler)pI2C_S(obj)->handler)();
    }
}

/**
* \internal
* Callback for write complete. Initiate read from here.
*
* \param[in,out]  module  Pointer to SPI software instance struct
*/
void i2c_write_complete_callback(struct i2c_master_module *const module)
{
    uint32_t sercom_index = _sercom_get_sercom_inst_index(module->hw);

    if (sercom_index >= SERCOM_INST_NUM) {
        /* TODO: Abort operation */
        return;
    }

    i2c_t *obj = (i2c_t*)i2c_instances[sercom_index];

    if (!(pI2C_S(obj)->rd_packet.data) || (pI2C_S(obj)->rd_packet.data_length == 0)) {
        /* Call the handler function */
        i2c_transfer_complete_callback(module);
    } else {
        i2c_master_disable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_WRITE_COMPLETE);

        /* Register read complete callback */
        i2c_master_register_callback(&pI2C_S(obj)->master, i2c_transfer_complete_callback, I2C_MASTER_CALLBACK_READ_COMPLETE);
        i2c_master_enable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_READ_COMPLETE);

        /* Initiate read operation */
        if (pI2C_S(obj)->master.send_stop) {
            i2c_master_read_packet_job(&pI2C_S(obj)->master,&pI2C_S(obj)->rd_packet);
        } else {
            i2c_master_read_packet_job_no_stop(&pI2C_S(obj)->master, &pI2C_S(obj)->rd_packet);
        }
    }
}

/** Start i2c asynchronous transfer.
 *  @param obj       The I2C object
 *  @param tx        The buffer to send
 *  @param tx_length The number of words to transmit
 *  @param rx        The buffer to receive
 *  @param rx_length The number of words to receive
 *  @param address   The address to be set - 7bit or 9 bit
 *  @param stop      If true, stop will be generated after the transfer is done
 *  @param handler   The I2C IRQ handler to be set
 *  @param hint      DMA hint usage
 */
void i2c_transfer_asynch(i2c_t *obj, const void *tx, size_t tx_length, void *rx, size_t rx_length, uint32_t address, uint32_t stop, uint32_t handler, uint32_t event, DMAUsage hint)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    /* Return if in Slave mode */
    if (pI2C_S(obj)->mode != I2C_MODE_MASTER) return;

    uint32_t sercom_index = _sercom_get_sercom_inst_index(pI2C_S(obj)->master.hw);

    /* Init i2c packet. */
    pI2C_S(obj)->wr_packet.address     = address >> 1;
    pI2C_S(obj)->wr_packet.data_length = tx_length;
    pI2C_S(obj)->wr_packet.data        = tx;

    pI2C_S(obj)->rd_packet.address     = address >> 1;
    pI2C_S(obj)->rd_packet.data_length = rx_length;
    pI2C_S(obj)->rd_packet.data        = rx;

    /* Save event mask and handler function pointer */
    pI2C_S(obj)->events = event;
    pI2C_S(obj)->handler = handler;

    /* TODO: Current implementation is interrupt based only */

    /* Set interrupt handler to default handler of ASF */
    /* Enable interrupt */
    NVIC_SetVector((SERCOM0_IRQn + sercom_index), sercom_irq_handlers[sercom_index]);
    NVIC_EnableIRQ(SERCOM0_IRQn + sercom_index);

    /* Register callbacks */
    i2c_master_register_callback(&pI2C_S(obj)->master, i2c_transfer_complete_callback, I2C_MASTER_CALLBACK_ERROR);
    i2c_master_enable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_ERROR);
    if (tx && tx_length) {
        i2c_master_register_callback(&pI2C_S(obj)->master, i2c_write_complete_callback, I2C_MASTER_CALLBACK_WRITE_COMPLETE);
        i2c_master_enable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_WRITE_COMPLETE);

        /* Start I2C write */
        if (stop) {
            i2c_master_write_packet_job(&pI2C_S(obj)->master, &pI2C_S(obj)->wr_packet);
        } else {
            i2c_master_write_packet_job_no_stop(&pI2C_S(obj)->master, &pI2C_S(obj)->wr_packet);
        }
    } else if (rx && rx_length) {
        i2c_master_register_callback(&pI2C_S(obj)->master, i2c_transfer_complete_callback, I2C_MASTER_CALLBACK_READ_COMPLETE);
        i2c_master_enable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_READ_COMPLETE);

        /* Start I2C read */
        if (stop) {
            i2c_master_read_packet_job(&pI2C_S(obj)->master,&pI2C_S(obj)->rd_packet);
        } else {
            i2c_master_read_packet_job_no_stop(&pI2C_S(obj)->master, &pI2C_S(obj)->rd_packet);
        }
    } else {
        /* Nothing to transfer, invoke callback */
        i2c_transfer_complete_callback(&pI2C_S(obj)->master);
    }
}

/** The asynchronous IRQ handler
 *  @param obj The I2C object which holds the transfer information
 *  @return event flags if a transfer termination condition was met or 0 otherwise.
 */
uint32_t i2c_irq_handler_asynch(i2c_t *obj)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    uint32_t event_mask = pI2C_S(obj)->events;

    /* TODO: Current implementation is interrupt based only */

    switch (pI2C_S(obj)->master.status) {
        case STATUS_OK:
            /* Transfer is complete */
            return (I2C_EVENT_TRANSFER_COMPLETE & event_mask);
            break;
        case STATUS_ERR_BAD_ADDRESS:
            /* Received a NACK */
            return (I2C_EVENT_ERROR_NO_SLAVE & event_mask);
            break;
        case STATUS_ERR_PACKET_COLLISION:
            /* An error occurred in between transfer */
            return (I2C_EVENT_ERROR & event_mask);
            break;
        default:
            return 0;
    }

    return 0;
}

/** Attempts to determine if I2C peripheral is already in use.
 *  @param obj The I2C object
 *  @return non-zero if the I2C module is active or zero if it is not
 */
uint8_t i2c_active(i2c_t *obj)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    return (pI2C_S(obj)->master.status == STATUS_BUSY);
}

/** Abort ongoing asynchronous transaction.
 *  @param obj The I2C object
 */
void i2c_abort_asynch(i2c_t *obj)
{
    /* Sanity check arguments */
    MBED_ASSERT(obj);
    MBED_ASSERT(pI2C_S(obj)->master.hw);

    /* Pointer to the hardware module instance */
    SercomI2cm *const i2c_module = &(pI2C_S(obj)->master.hw->I2CM);

    /* Abort ongoing job */

    /* Stop packet operation */
    i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB;

    pI2C_S(obj)->master.buffer_length = 0;
    pI2C_S(obj)->master.buffer_remaining = 0;

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

    /* Disable any registered callback */
    i2c_master_disable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_WRITE_COMPLETE);
    i2c_master_disable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_READ_COMPLETE);
    i2c_master_disable_callback(&pI2C_S(obj)->master, I2C_MASTER_CALLBACK_ERROR);

    pI2C_S(obj)->master.status = STATUS_ABORTED;
}

#endif
