t
Fork of mbed-dev by
Diff: targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/i2c_api.c
- Revision:
- 15:a81a8d6c1dfe
- Child:
- 64:41a834223ea3
diff -r d797fbdad187 -r a81a8d6c1dfe targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/i2c_api.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/i2c_api.c Wed Nov 04 16:30:11 2015 +0000 @@ -0,0 +1,993 @@ +/* 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