added prescaler for 16 bit pwm in LPC1347 target
Fork of mbed-dev by
Diff: targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/i2c_api.c
- Revision:
- 144:ef7eb2e8f9f7
- Parent:
- 52:4ce9155acc4d
--- a/targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/i2c_api.c Tue Aug 02 14:07:36 2016 +0000 +++ b/targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/i2c_api.c Fri Sep 02 15:07:44 2016 +0100 @@ -1,590 +1,583 @@ -/***************************************************************************//** - * @file i2c_api.c - ******************************************************************************* - * @section License - * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b> - ******************************************************************************* - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no - * obligation to support this Software. Silicon Labs is providing the - * Software "AS IS", with no express or implied warranties of any kind, - * including, but not limited to, any implied warranties of merchantability - * or fitness for any particular purpose or warranties against infringement - * of any proprietary rights of a third party. - * - * Silicon Labs will not be liable for any consequential, incidental, or - * special damages, or any other relief, or for any claim by any third party, - * arising from your use of this Software. - * - ******************************************************************************/ - -#include "device.h" -#include "clocking.h" -#include <stdio.h> - -#if DEVICE_I2C - -#include "mbed_assert.h" -#include "i2c_api.h" -#include "PeripheralPins.h" -#include "pinmap_function.h" -#include "sleepmodes.h" - -#include "em_i2c.h" -#include "em_cmu.h" - -/** Error flags indicating I2C transfer has failed somehow. */ -/* Notice that I2C_IF_TXOF (transmit overflow) is not really possible with */ -/* this SW supporting master mode. Likewise for I2C_IF_RXUF (receive underflow) */ -/* RXUF is only likely to occur with this SW if using a debugger peeking into */ -/* RXDATA register. Thus, we ignore those types of fault. */ -#define I2C_IF_ERRORS (I2C_IF_BUSERR | I2C_IF_ARBLOST) -#define I2C_TIMEOUT 100000 - -/* Prototypes */ -int block_and_wait_for_ack(I2C_TypeDef *i2c); -void i2c_enable(i2c_t *obj, uint8_t enable); -void i2c_enable_pins(i2c_t *obj, uint8_t enable); -void i2c_enable_interrupt(i2c_t *obj, uint32_t address, uint8_t enable); - -static uint8_t i2c_get_index(i2c_t *obj) -{ - uint8_t index = 0; - switch ((int)obj->i2c.i2c) { -#ifdef I2C0 - case I2C_0: - index = 0; - break; -#endif -#ifdef I2C1 - case I2C_1: - index = 1; - break; -#endif - default: - printf("I2C module not available.. Out of bound access."); - break; - } - return index; -} - -static CMU_Clock_TypeDef i2c_get_clock(i2c_t *obj) -{ - CMU_Clock_TypeDef clock; - switch ((int)obj->i2c.i2c) { -#ifdef I2C0 - case I2C_0: - clock = cmuClock_I2C0; - break; -#endif -#ifdef I2C1 - case I2C_1: - clock = cmuClock_I2C1; - break; -#endif - default: - printf("I2C module not available.. Out of bound access. (clock)"); - clock = cmuClock_HFPER; - break; - } - return clock; -} - -void i2c_init(i2c_t *obj, PinName sda, PinName scl) -{ - /* Find out which I2C peripheral we're asked to use */ - I2CName i2c_sda = (I2CName) pinmap_peripheral(sda, PinMap_I2C_SDA); - I2CName i2c_scl = (I2CName) pinmap_peripheral(scl, PinMap_I2C_SCL); - obj->i2c.i2c = (I2C_TypeDef*) pinmap_merge(i2c_sda, i2c_scl); - MBED_ASSERT(((int) obj->i2c.i2c) != NC); - - /* You need both SDA and SCL for I2C, so configuring one of them to NC is illegal */ - MBED_ASSERT((uint32_t)sda != (uint32_t)NC); - MBED_ASSERT((uint32_t)scl != (uint32_t)NC); - - /* Enable clock for the peripheral */ - CMU_ClockEnable(i2c_get_clock(obj), true); - - /* Initializing the I2C */ - /* Using default settings */ - I2C_Init_TypeDef i2cInit = I2C_INIT_DEFAULT; - I2C_Init(obj->i2c.i2c, &i2cInit); - - /* Enable pins at correct location */ -#ifdef I2C_ROUTE_SDAPEN - /* Find common location in pinmap */ - int loc_sda = pin_location(sda, PinMap_I2C_SDA); - int loc_scl = pin_location(scl, PinMap_I2C_SCL); - int loc = pinmap_merge(loc_sda, loc_scl); - MBED_ASSERT(loc != NC); - /* Set location */ - obj->i2c.i2c->ROUTE = I2C_ROUTE_SDAPEN | I2C_ROUTE_SCLPEN | (loc << _I2C_ROUTE_LOCATION_SHIFT); -#else - obj->i2c.i2c->ROUTEPEN = I2C_ROUTEPEN_SDAPEN | I2C_ROUTEPEN_SCLPEN; - obj->i2c.i2c->ROUTELOC0 = (pin_location(sda, PinMap_I2C_SDA) << _I2C_ROUTELOC0_SDALOC_SHIFT) | - (pin_location(scl, PinMap_I2C_SCL) << _I2C_ROUTELOC0_SCLLOC_SHIFT); -#endif - - /* Set up the pins for I2C use */ - /* Note: Set up pins in higher drive strength to reduce slew rate */ - /* Though this requires user knowledge, since drive strength is controlled per port, not pin */ - pin_mode(scl, WiredAndPullUp); - pin_mode(sda, WiredAndPullUp); - - /* Enable General Call Address Mode. That is; we respond to the general address (0x0) */ - obj->i2c.i2c->CTRL |= _I2C_CTRL_GCAMEN_MASK; - - /* We are assuming that there is only one master. So disable automatic arbitration */ - obj->i2c.i2c->CTRL |= _I2C_CTRL_ARBDIS_MASK; - - /* Set to master (needed if this I2C block was used previously as slave) */ - i2c_slave_mode(obj, false); - - /* Enable i2c */ - i2c_enable(obj, true); -} - -void i2c_enable(i2c_t *obj, uint8_t enable) -{ - I2C_Enable(obj->i2c.i2c, enable); - if (!enable) { - /* After a reset BUSY is usually set. We assume that we are the only master and call abort, - * which sends nothing on the bus, it just allows us to assume that the bus is idle */ - if (obj->i2c.i2c->STATE & I2C_STATE_BUSY) { - obj->i2c.i2c->CMD = I2C_CMD_ABORT; - } - } -} - -void i2c_enable_interrupt(i2c_t *obj, uint32_t address, uint8_t enable) -{ - IRQn_Type irq_number; - - switch (i2c_get_index(obj)) { -#ifdef I2C0 - case 0: - irq_number = I2C0_IRQn; - break; -#endif -#ifdef I2C1 - case 1: - irq_number = I2C1_IRQn; - break; -#endif - } - - NVIC_SetVector(irq_number, address); - /* Lower IRQ priority to avoid messing with asynch RX on UART */ - NVIC_SetPriority(irq_number, 1); - if (enable) { - NVIC_EnableIRQ(irq_number); - } else { - NVIC_DisableIRQ(irq_number); - } -} - -/* Set the frequency of the I2C interface */ -void i2c_frequency(i2c_t *obj, int hz) -{ - /* Set frequency. As the second argument is 0, - * HFPER clock frequency is used as reference freq */ - if (hz <= 0) return; - /* In I2C Normal mode (50% duty), we can go up to 100kHz */ - if (hz <= 100000) { - I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, hz, i2cClockHLRStandard); - } - /* In I2C Fast mode (6:3 ratio), we can go up to 400kHz */ - else if (hz <= 400000) { - I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, hz, i2cClockHLRAsymetric); - } - /* In I2C Fast+ mode (11:6 ratio), we can go up to 1 MHz */ - else if (hz <= 1000000) { - I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, hz, i2cClockHLRFast); - } - /* Cap requested frequency at 1MHz */ - else { - I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, 1000000, i2cClockHLRFast); - } -} - -/* Creates a start condition on the I2C bus */ -int i2c_start(i2c_t *obj) -{ - I2C_TypeDef *i2c = obj->i2c.i2c; - - /* Ensure buffers are empty */ - i2c->CMD = I2C_CMD_CLEARPC | I2C_CMD_CLEARTX; - if (i2c->IF & I2C_IF_RXDATAV) { - (void) i2c->RXDATA; - } - - /* Clear all pending interrupts prior to starting transfer. */ - i2c->IFC = _I2C_IFC_MASK; - - /* Send start */ - obj->i2c.i2c->CMD = I2C_CMD_START; - return 0; -} - -/* Creates a stop condition on the I2C bus */ -int i2c_stop(i2c_t *obj) -{ - obj->i2c.i2c->CMD = I2C_CMD_STOP; - - /* Wait for the stop to be sent */ - int timeout = I2C_TIMEOUT; - while (!(obj->i2c.i2c->IF & I2C_IF_MSTOP) && !timeout--); - - return 0; -} - -/* Returns number of bytes read */ -int i2c_read(i2c_t *obj, int address, char *data, int length, int stop) -{ - int retval; - - i2c_start(obj); - - retval = i2c_byte_write(obj, (address | 1)); - if ((!retval) || (length == 0)) { //Write address with W flag (last bit 1) - obj->i2c.i2c->CMD = I2C_CMD_STOP | I2C_CMD_ABORT; - while(obj->i2c.i2c->STATE & I2C_STATE_BUSY); // Wait until the bus is done - return (retval == 0 ? I2C_ERROR_NO_SLAVE : 0); //NACK or error when writing adress. Return 0 as 0 bytes were read - } - int i = 0; - while (i < length) { - uint8_t last = (i == length - 1); - data[i++] = i2c_byte_read(obj, last); - } - - if (stop) { - i2c_stop(obj); - } - - return length; -} - -int i2c_write(i2c_t *obj, int address, const char *data, int length, int stop) -{ - i2c_start(obj); - - if (!i2c_byte_write(obj, (address & 0xFE))) { - i2c_stop(obj); - return I2C_ERROR_NO_SLAVE; //NACK or error when writing adress. Return 0 as 0 bytes were written - } - int i; - for (i = 0; i < length; i++) { - if (!i2c_byte_write(obj, data[i])) { - i2c_stop(obj); - return i; - } - } - - if (stop) { - i2c_stop(obj); - } - - return length; -} - -void i2c_reset(i2c_t *obj) -{ - /* EMLib function */ - I2C_Reset(obj->i2c.i2c); -} - -int i2c_byte_read(i2c_t *obj, int last) -{ - int timeout = I2C_TIMEOUT; - /* Wait for data */ - while (!(obj->i2c.i2c->STATUS & I2C_STATUS_RXDATAV) && timeout--); - - if (timeout <= 0) { - return 0; //TODO Is this the correct way to handle this? - } - char data = obj->i2c.i2c->RXDATA; - - if (last) { - obj->i2c.i2c->CMD = I2C_CMD_NACK; - } else { - obj->i2c.i2c->CMD = I2C_CMD_ACK; - } - return data; -} - -int i2c_byte_write(i2c_t *obj, int data) -{ - obj->i2c.i2c->TXDATA = data; - return block_and_wait_for_ack(obj->i2c.i2c); -} - -/* - * Returns 1 for ACK. 0 for NACK, timeout or error. - */ -int block_and_wait_for_ack(I2C_TypeDef *i2c) -{ - uint32_t pending; - uint32_t timeout = I2C_TIMEOUT; - while (timeout > 0) { - timeout -= 1; - pending = i2c->IF; - /* If some sort of fault, abort transfer. */ - if (pending & I2C_IF_ERRORS) { - if (pending & I2C_IF_ARBLOST) { - /* If arbitration fault, it indicates either a slave device */ - /* not responding as expected, or other master which is not */ - /* supported by this SW. */ - return 0; - } else if (pending & I2C_IF_BUSERR) { - /* A bus error indicates a misplaced start or stop, which should */ - /* not occur in master mode controlled by this SW. */ - return 0; - } - } - - if (pending & I2C_IF_NACK) { - i2c->IFC = I2C_IFC_NACK; - return 0; //Received NACK - } else if (pending & I2C_IF_ACK) { - i2c->IFC = I2C_IFC_ACK; - return 1; //Got ACK - } - } - return 0; //Timeout -} - -#if DEVICE_I2CSLAVE - -#define NoData 0 -#define ReadAddressed 1 -#define WriteGeneral 2 -#define WriteAddressed 3 - - -void i2c_slave_mode(i2c_t *obj, int enable_slave) -{ - if(enable_slave) { - /* Reference manual note: DIV must be set to 1 during slave operation */ - obj->i2c.i2c->CLKDIV = 1; - obj->i2c.i2c->CTRL |= _I2C_CTRL_SLAVE_MASK; - obj->i2c.i2c->CTRL |= _I2C_CTRL_AUTOACK_MASK; //Slave implementation assumes auto acking - } else { - obj->i2c.i2c->CTRL &= ~_I2C_CTRL_SLAVE_MASK; - obj->i2c.i2c->CTRL &= ~_I2C_CTRL_AUTOACK_MASK; //Master implementation ACKs manually - /* function is only called with enable_slave = false through i2c_init(..), so frequency is - already guaranteed to be set */ - } -} - -int i2c_slave_receive(i2c_t *obj) -{ - - if(obj->i2c.i2c->IF & I2C_IF_ADDR) { - obj->i2c.i2c->IFC = I2C_IF_ADDR; //Clear interrupt - /*0x00 is the address for general write. - The address the master wrote is in RXDATA now - and reading it also frees the buffer for the next - write which can then be acked. */ - if(obj->i2c.i2c->RXDATA == 0x00) { - return WriteGeneral; //Read the address; - } - - if(obj->i2c.i2c->STATE & I2C_STATE_TRANSMITTER) { - return ReadAddressed; - } else { - return WriteAddressed; - } - } - - return NoData; - -} - -int i2c_slave_read(i2c_t *obj, char *data, int length) -{ - int count; - for (count = 0; count < length; count++) { - data[count] = i2c_byte_read(obj, 0); - } - - - return count; - -} - -int i2c_slave_write(i2c_t *obj, const char *data, int length) -{ - int count; - for (count = 0; count < length; count++) { - i2c_byte_write(obj, data[count]); - } - - return count; -} - -void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask) -{ - obj->i2c.i2c->SADDR = address; - obj->i2c.i2c->SADDRMASK = 0xFE;//mask; -} - -#endif //DEVICE_I2CSLAVE - -#ifdef DEVICE_I2C_ASYNCH - -#include "em_dma.h" -#include "dma_api_HAL.h" -#include "dma_api.h" -#include "sleep_api.h" -#include "buffer.h" - -/** 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) -{ - I2C_TransferReturn_TypeDef retval; - if(i2c_active(obj)) return; - if((tx_length == 0) && (rx_length == 0)) return; - // For now, we are assuming a solely interrupt-driven implementation. - - // Store transfer config - obj->i2c.xfer.addr = address; - - // Some combination of tx_length and rx_length will tell us what to do - if((tx_length > 0) && (rx_length == 0)) { - obj->i2c.xfer.flags = I2C_FLAG_WRITE; - //Store buffer info - obj->i2c.xfer.buf[0].data = (void *)tx; - obj->i2c.xfer.buf[0].len = (uint16_t) tx_length; - } else if ((tx_length == 0) && (rx_length > 0)) { - obj->i2c.xfer.flags = I2C_FLAG_READ; - //Store buffer info - obj->i2c.xfer.buf[0].data = rx; - obj->i2c.xfer.buf[0].len = (uint16_t) rx_length; - } else if ((tx_length > 0) && (rx_length > 0)) { - obj->i2c.xfer.flags = I2C_FLAG_WRITE_READ; - //Store buffer info - obj->i2c.xfer.buf[0].data = (void *)tx; - obj->i2c.xfer.buf[0].len = (uint16_t) tx_length; - obj->i2c.xfer.buf[1].data = rx; - obj->i2c.xfer.buf[1].len = (uint16_t) rx_length; - } - - if(address > 255) obj->i2c.xfer.flags |= I2C_FLAG_10BIT_ADDR; - - // Store event flags - obj->i2c.events = event; - - // Enable interrupt - i2c_enable_interrupt(obj, handler, true); - - // Kick off the transfer - retval = I2C_TransferInit(obj->i2c.i2c, &(obj->i2c.xfer)); - - if(retval == i2cTransferInProgress) { - blockSleepMode(EM1); - } else { - // something happened, and the transfer did not go through - // So, we need to clean up - - // Disable interrupt - i2c_enable_interrupt(obj, 0, false); - - // Block until free - while(i2c_active(obj)); - } -} - -/** The asynchronous IRQ handler - * @param obj The I2C object which holds the transfer information - * @return Returns event flags if a transfer termination condition was met or 0 otherwise. - */ -uint32_t i2c_irq_handler_asynch(i2c_t *obj) -{ - - // For now, we are assuming a solely interrupt-driven implementation. - - I2C_TransferReturn_TypeDef status = I2C_Transfer(obj->i2c.i2c); - switch(status) { - case i2cTransferInProgress: - // Still busy transferring, so let it. - return 0; - case i2cTransferDone: - // Transfer has completed - - // Disable interrupt - i2c_enable_interrupt(obj, 0, false); - - unblockSleepMode(EM1); - - return I2C_EVENT_TRANSFER_COMPLETE & obj->i2c.events; - case i2cTransferNack: - // A NACK has been received while an ACK was expected. This is usually because the slave did not respond to the address. - // Disable interrupt - i2c_enable_interrupt(obj, 0, false); - - unblockSleepMode(EM1); - - return I2C_EVENT_ERROR_NO_SLAVE & obj->i2c.events; - default: - // An error situation has arisen. - // Disable interrupt - i2c_enable_interrupt(obj, 0, false); - - unblockSleepMode(EM1); - - // return error - return I2C_EVENT_ERROR & obj->i2c.events; - } -} - -/** 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) -{ - return (obj->i2c.i2c->STATE & I2C_STATE_BUSY); -} - -/** Abort ongoing asynchronous transaction. - * @param obj The I2C object - */ -void i2c_abort_asynch(i2c_t *obj) -{ - // Do not deactivate I2C twice - if (!i2c_active(obj)) return; - - // Disable interrupt - i2c_enable_interrupt(obj, 0, false); - - // Abort - obj->i2c.i2c->CMD = I2C_CMD_STOP | I2C_CMD_ABORT; - - // Block until free - while(i2c_active(obj)); - - unblockSleepMode(EM1); -} - -#endif //DEVICE_I2C ASYNCH -#endif //DEVICE_I2C +/***************************************************************************//** + * @file i2c_api.c + ******************************************************************************* + * @section License + * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b> + ******************************************************************************* + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 "device.h" +#include "clocking.h" +#include <stdio.h> + +#if DEVICE_I2C + +#include "mbed_assert.h" +#include "i2c_api.h" +#include "PeripheralPins.h" +#include "pinmap_function.h" +#include "sleepmodes.h" + +#include "em_i2c.h" +#include "em_cmu.h" + +/** Error flags indicating I2C transfer has failed somehow. */ +/* Notice that I2C_IF_TXOF (transmit overflow) is not really possible with */ +/* this SW supporting master mode. Likewise for I2C_IF_RXUF (receive underflow) */ +/* RXUF is only likely to occur with this SW if using a debugger peeking into */ +/* RXDATA register. Thus, we ignore those types of fault. */ +#define I2C_IF_ERRORS (I2C_IF_BUSERR | I2C_IF_ARBLOST) +#define I2C_TIMEOUT 100000 + +/* Prototypes */ +int block_and_wait_for_ack(I2C_TypeDef *i2c); +void i2c_enable(i2c_t *obj, uint8_t enable); +void i2c_enable_pins(i2c_t *obj, uint8_t enable); +void i2c_enable_interrupt(i2c_t *obj, uint32_t address, uint8_t enable); + +static uint8_t i2c_get_index(i2c_t *obj) +{ + uint8_t index = 0; + switch ((int)obj->i2c.i2c) { +#ifdef I2C0 + case I2C_0: + index = 0; + break; +#endif +#ifdef I2C1 + case I2C_1: + index = 1; + break; +#endif + default: + printf("I2C module not available.. Out of bound access."); + break; + } + return index; +} + +static CMU_Clock_TypeDef i2c_get_clock(i2c_t *obj) +{ + CMU_Clock_TypeDef clock; + switch ((int)obj->i2c.i2c) { +#ifdef I2C0 + case I2C_0: + clock = cmuClock_I2C0; + break; +#endif +#ifdef I2C1 + case I2C_1: + clock = cmuClock_I2C1; + break; +#endif + default: + printf("I2C module not available.. Out of bound access. (clock)"); + clock = cmuClock_HFPER; + break; + } + return clock; +} + +void i2c_init(i2c_t *obj, PinName sda, PinName scl) +{ + /* Find out which I2C peripheral we're asked to use */ + I2CName i2c_sda = (I2CName) pinmap_peripheral(sda, PinMap_I2C_SDA); + I2CName i2c_scl = (I2CName) pinmap_peripheral(scl, PinMap_I2C_SCL); + obj->i2c.i2c = (I2C_TypeDef*) pinmap_merge(i2c_sda, i2c_scl); + MBED_ASSERT(((int) obj->i2c.i2c) != NC); + + /* You need both SDA and SCL for I2C, so configuring one of them to NC is illegal */ + MBED_ASSERT((uint32_t)sda != (uint32_t)NC); + MBED_ASSERT((uint32_t)scl != (uint32_t)NC); + + /* Enable clock for the peripheral */ + CMU_ClockEnable(i2c_get_clock(obj), true); + + /* Initializing the I2C */ + /* Using default settings */ + I2C_Init_TypeDef i2cInit = I2C_INIT_DEFAULT; + I2C_Init(obj->i2c.i2c, &i2cInit); + + /* Enable pins at correct location */ +#ifdef I2C_ROUTE_SDAPEN + /* Find common location in pinmap */ + int loc_sda = pin_location(sda, PinMap_I2C_SDA); + int loc_scl = pin_location(scl, PinMap_I2C_SCL); + int loc = pinmap_merge(loc_sda, loc_scl); + MBED_ASSERT(loc != NC); + /* Set location */ + obj->i2c.i2c->ROUTE = I2C_ROUTE_SDAPEN | I2C_ROUTE_SCLPEN | (loc << _I2C_ROUTE_LOCATION_SHIFT); +#else + obj->i2c.i2c->ROUTEPEN = I2C_ROUTEPEN_SDAPEN | I2C_ROUTEPEN_SCLPEN; + obj->i2c.i2c->ROUTELOC0 = (pin_location(sda, PinMap_I2C_SDA) << _I2C_ROUTELOC0_SDALOC_SHIFT) | + (pin_location(scl, PinMap_I2C_SCL) << _I2C_ROUTELOC0_SCLLOC_SHIFT); +#endif + + /* Set up the pins for I2C use */ + /* Note: Set up pins in higher drive strength to reduce slew rate */ + /* Though this requires user knowledge, since drive strength is controlled per port, not pin */ + pin_mode(scl, WiredAndPullUp); + pin_mode(sda, WiredAndPullUp); + + /* Enable General Call Address Mode. That is; we respond to the general address (0x0) */ + obj->i2c.i2c->CTRL |= _I2C_CTRL_GCAMEN_MASK; + + /* We are assuming that there is only one master. So disable automatic arbitration */ + obj->i2c.i2c->CTRL |= _I2C_CTRL_ARBDIS_MASK; + + /* Set to master (needed if this I2C block was used previously as slave) */ + i2c_slave_mode(obj, false); + + /* Enable i2c */ + i2c_enable(obj, true); +} + +void i2c_enable(i2c_t *obj, uint8_t enable) +{ + I2C_Enable(obj->i2c.i2c, enable); + if (!enable) { + /* After a reset BUSY is usually set. We assume that we are the only master and call abort, + * which sends nothing on the bus, it just allows us to assume that the bus is idle */ + if (obj->i2c.i2c->STATE & I2C_STATE_BUSY) { + obj->i2c.i2c->CMD = I2C_CMD_ABORT; + } + } +} + +void i2c_enable_interrupt(i2c_t *obj, uint32_t address, uint8_t enable) +{ + IRQn_Type irq_number; + + switch (i2c_get_index(obj)) { +#ifdef I2C0 + case 0: + irq_number = I2C0_IRQn; + break; +#endif +#ifdef I2C1 + case 1: + irq_number = I2C1_IRQn; + break; +#endif + } + + NVIC_SetVector(irq_number, address); + /* Lower IRQ priority to avoid messing with asynch RX on UART */ + NVIC_SetPriority(irq_number, 1); + if (enable) { + NVIC_EnableIRQ(irq_number); + } else { + NVIC_DisableIRQ(irq_number); + } +} + +/* Set the frequency of the I2C interface */ +void i2c_frequency(i2c_t *obj, int hz) +{ + /* Set frequency. As the second argument is 0, + * HFPER clock frequency is used as reference freq */ + if (hz <= 0) return; + /* In I2C Normal mode (50% duty), we can go up to 100kHz */ + if (hz <= 100000) { + I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, hz, i2cClockHLRStandard); + } + /* In I2C Fast mode (6:3 ratio), we can go up to 400kHz */ + else if (hz <= 400000) { + I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, hz, i2cClockHLRAsymetric); + } + /* In I2C Fast+ mode (11:6 ratio), we can go up to 1 MHz */ + else if (hz <= 1000000) { + I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, hz, i2cClockHLRFast); + } + /* Cap requested frequency at 1MHz */ + else { + I2C_BusFreqSet(obj->i2c.i2c, REFERENCE_FREQUENCY, 1000000, i2cClockHLRFast); + } +} + +/* Creates a start condition on the I2C bus */ +int i2c_start(i2c_t *obj) +{ + I2C_TypeDef *i2c = obj->i2c.i2c; + + /* Ensure buffers are empty */ + i2c->CMD = I2C_CMD_CLEARPC | I2C_CMD_CLEARTX; + if (i2c->IF & I2C_IF_RXDATAV) { + (void) i2c->RXDATA; + } + + /* Clear all pending interrupts prior to starting transfer. */ + i2c->IFC = _I2C_IFC_MASK; + + /* Send start */ + obj->i2c.i2c->CMD = I2C_CMD_START; + return 0; +} + +/* Creates a stop condition on the I2C bus */ +int i2c_stop(i2c_t *obj) +{ + obj->i2c.i2c->CMD = I2C_CMD_STOP; + + /* Wait for the stop to be sent */ + int timeout = I2C_TIMEOUT; + while (!(obj->i2c.i2c->IF & I2C_IF_MSTOP) && !timeout--); + + return 0; +} + +/* Returns number of bytes read */ +int i2c_read(i2c_t *obj, int address, char *data, int length, int stop) +{ + int retval; + + i2c_start(obj); + + retval = i2c_byte_write(obj, (address | 1)); + if ((!retval) || (length == 0)) { //Write address with W flag (last bit 1) + obj->i2c.i2c->CMD = I2C_CMD_STOP | I2C_CMD_ABORT; + while(obj->i2c.i2c->STATE & I2C_STATE_BUSY); // Wait until the bus is done + return (retval == 0 ? I2C_ERROR_NO_SLAVE : 0); //NACK or error when writing adress. Return 0 as 0 bytes were read + } + int i = 0; + while (i < length) { + uint8_t last = (i == length - 1); + data[i++] = i2c_byte_read(obj, last); + } + + if (stop) { + i2c_stop(obj); + } + + return length; +} + +int i2c_write(i2c_t *obj, int address, const char *data, int length, int stop) +{ + i2c_start(obj); + + if (!i2c_byte_write(obj, (address & 0xFE))) { + i2c_stop(obj); + return I2C_ERROR_NO_SLAVE; //NACK or error when writing adress. Return 0 as 0 bytes were written + } + int i; + for (i = 0; i < length; i++) { + if (!i2c_byte_write(obj, data[i])) { + i2c_stop(obj); + return i; + } + } + + if (stop) { + i2c_stop(obj); + } + + return length; +} + +void i2c_reset(i2c_t *obj) +{ + /* EMLib function */ + I2C_Reset(obj->i2c.i2c); +} + +int i2c_byte_read(i2c_t *obj, int last) +{ + int timeout = I2C_TIMEOUT; + /* Wait for data */ + while (!(obj->i2c.i2c->STATUS & I2C_STATUS_RXDATAV) && timeout--); + + if (timeout <= 0) { + return 0; //TODO Is this the correct way to handle this? + } + char data = obj->i2c.i2c->RXDATA; + + if (last) { + obj->i2c.i2c->CMD = I2C_CMD_NACK; + } else { + obj->i2c.i2c->CMD = I2C_CMD_ACK; + } + return data; +} + +int i2c_byte_write(i2c_t *obj, int data) +{ + obj->i2c.i2c->TXDATA = data; + return block_and_wait_for_ack(obj->i2c.i2c); +} + +/* + * Returns 1 for ACK. 0 for NACK, timeout or error. + */ +int block_and_wait_for_ack(I2C_TypeDef *i2c) +{ + uint32_t pending; + uint32_t timeout = I2C_TIMEOUT; + while (timeout > 0) { + timeout -= 1; + pending = i2c->IF; + /* If some sort of fault, abort transfer. */ + if (pending & I2C_IF_ERRORS) { + if (pending & I2C_IF_ARBLOST) { + /* If arbitration fault, it indicates either a slave device */ + /* not responding as expected, or other master which is not */ + /* supported by this SW. */ + return 0; + } else if (pending & I2C_IF_BUSERR) { + /* A bus error indicates a misplaced start or stop, which should */ + /* not occur in master mode controlled by this SW. */ + return 0; + } + } + + if (pending & I2C_IF_NACK) { + i2c->IFC = I2C_IFC_NACK; + return 0; //Received NACK + } else if (pending & I2C_IF_ACK) { + i2c->IFC = I2C_IFC_ACK; + return 1; //Got ACK + } + } + return 0; //Timeout +} + +#if DEVICE_I2CSLAVE + +#define NoData 0 +#define ReadAddressed 1 +#define WriteGeneral 2 +#define WriteAddressed 3 + + +void i2c_slave_mode(i2c_t *obj, int enable_slave) +{ + if(enable_slave) { + /* Reference manual note: DIV must be set to 1 during slave operation */ + obj->i2c.i2c->CLKDIV = 1; + obj->i2c.i2c->CTRL |= _I2C_CTRL_SLAVE_MASK; + obj->i2c.i2c->CTRL |= _I2C_CTRL_AUTOACK_MASK; //Slave implementation assumes auto acking + } else { + obj->i2c.i2c->CTRL &= ~_I2C_CTRL_SLAVE_MASK; + obj->i2c.i2c->CTRL &= ~_I2C_CTRL_AUTOACK_MASK; //Master implementation ACKs manually + /* function is only called with enable_slave = false through i2c_init(..), so frequency is + already guaranteed to be set */ + } +} + +int i2c_slave_receive(i2c_t *obj) +{ + + if(obj->i2c.i2c->IF & I2C_IF_ADDR) { + obj->i2c.i2c->IFC = I2C_IF_ADDR; //Clear interrupt + /*0x00 is the address for general write. + The address the master wrote is in RXDATA now + and reading it also frees the buffer for the next + write which can then be acked. */ + if(obj->i2c.i2c->RXDATA == 0x00) { + return WriteGeneral; //Read the address; + } + + if(obj->i2c.i2c->STATE & I2C_STATE_TRANSMITTER) { + return ReadAddressed; + } else { + return WriteAddressed; + } + } + + return NoData; + +} + +int i2c_slave_read(i2c_t *obj, char *data, int length) +{ + int count; + for (count = 0; count < length; count++) { + data[count] = i2c_byte_read(obj, 0); + } + + + return count; + +} + +int i2c_slave_write(i2c_t *obj, const char *data, int length) +{ + int count; + for (count = 0; count < length; count++) { + i2c_byte_write(obj, data[count]); + } + + return count; +} + +void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask) +{ + obj->i2c.i2c->SADDR = address; + obj->i2c.i2c->SADDRMASK = 0xFE;//mask; +} + +#endif //DEVICE_I2CSLAVE + +#ifdef DEVICE_I2C_ASYNCH + +#include "em_dma.h" +#include "dma_api_HAL.h" +#include "dma_api.h" +#include "sleep_api.h" +#include "buffer.h" + +/** 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) +{ + I2C_TransferReturn_TypeDef retval; + if(i2c_active(obj)) return; + if((tx_length == 0) && (rx_length == 0)) return; + // For now, we are assuming a solely interrupt-driven implementation. + + // Store transfer config + obj->i2c.xfer.addr = address; + + // Some combination of tx_length and rx_length will tell us what to do + if((tx_length > 0) && (rx_length == 0)) { + obj->i2c.xfer.flags = I2C_FLAG_WRITE; + //Store buffer info + obj->i2c.xfer.buf[0].data = (void *)tx; + obj->i2c.xfer.buf[0].len = (uint16_t) tx_length; + } else if ((tx_length == 0) && (rx_length > 0)) { + obj->i2c.xfer.flags = I2C_FLAG_READ; + //Store buffer info + obj->i2c.xfer.buf[0].data = rx; + obj->i2c.xfer.buf[0].len = (uint16_t) rx_length; + } else if ((tx_length > 0) && (rx_length > 0)) { + obj->i2c.xfer.flags = I2C_FLAG_WRITE_READ; + //Store buffer info + obj->i2c.xfer.buf[0].data = (void *)tx; + obj->i2c.xfer.buf[0].len = (uint16_t) tx_length; + obj->i2c.xfer.buf[1].data = rx; + obj->i2c.xfer.buf[1].len = (uint16_t) rx_length; + } + + if(address > 255) obj->i2c.xfer.flags |= I2C_FLAG_10BIT_ADDR; + + // Store event flags + obj->i2c.events = event; + + // Enable interrupt + i2c_enable_interrupt(obj, handler, true); + + // Kick off the transfer + retval = I2C_TransferInit(obj->i2c.i2c, &(obj->i2c.xfer)); + + if(retval == i2cTransferInProgress) { + blockSleepMode(EM1); + } else { + // something happened, and the transfer did not go through + // So, we need to clean up + + // Disable interrupt + i2c_enable_interrupt(obj, 0, false); + + // Block until free + while(i2c_active(obj)); + } +} + +/** The asynchronous IRQ handler + * @param obj The I2C object which holds the transfer information + * @return Returns event flags if a transfer termination condition was met or 0 otherwise. + */ +uint32_t i2c_irq_handler_asynch(i2c_t *obj) +{ + + // For now, we are assuming a solely interrupt-driven implementation. + + I2C_TransferReturn_TypeDef status = I2C_Transfer(obj->i2c.i2c); + switch(status) { + case i2cTransferInProgress: + // Still busy transferring, so let it. + return 0; + case i2cTransferDone: + // Transfer has completed + + // Disable interrupt + i2c_enable_interrupt(obj, 0, false); + + unblockSleepMode(EM1); + + return I2C_EVENT_TRANSFER_COMPLETE & obj->i2c.events; + case i2cTransferNack: + // A NACK has been received while an ACK was expected. This is usually because the slave did not respond to the address. + // Disable interrupt + i2c_enable_interrupt(obj, 0, false); + + unblockSleepMode(EM1); + + return I2C_EVENT_ERROR_NO_SLAVE & obj->i2c.events; + default: + // An error situation has arisen. + // Disable interrupt + i2c_enable_interrupt(obj, 0, false); + + unblockSleepMode(EM1); + + // return error + return I2C_EVENT_ERROR & obj->i2c.events; + } +} + +/** 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) +{ + return (obj->i2c.i2c->STATE & I2C_STATE_BUSY); +} + +/** Abort ongoing asynchronous transaction. + * @param obj The I2C object + */ +void i2c_abort_asynch(i2c_t *obj) +{ + // Do not deactivate I2C twice + if (!i2c_active(obj)) return; + + // Disable interrupt + i2c_enable_interrupt(obj, 0, false); + + // Abort + obj->i2c.i2c->CMD = I2C_CMD_STOP | I2C_CMD_ABORT; + + // Block until free + while(i2c_active(obj)); + + unblockSleepMode(EM1); +} + +#endif //DEVICE_I2C ASYNCH +#endif //DEVICE_I2C