diff --git a/cpu/atxmega/Makefile.features b/cpu/atxmega/Makefile.features index a08cfd24e2..337ccbe070 100644 --- a/cpu/atxmega/Makefile.features +++ b/cpu/atxmega/Makefile.features @@ -6,6 +6,7 @@ include $(RIOTCPU)/avr8_common/Makefile.features FEATURES_PROVIDED += cpu_core_atxmega FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio periph_gpio_irq +FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_nvm FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += periph_timer periph_timer_periodic diff --git a/cpu/atxmega/include/periph_cpu.h b/cpu/atxmega/include/periph_cpu.h index f41ad112c2..765e3da030 100644 --- a/cpu/atxmega/include/periph_cpu.h +++ b/cpu/atxmega/include/periph_cpu.h @@ -264,6 +264,43 @@ typedef struct { cpu_int_lvl_t int_lvl[TIMER_CH_MAX_NUMOF]; /**< Interrupt channels level */ } timer_conf_t; +/** + * @name Override I2C clock speed values + * @{ + */ +#define HAVE_I2C_SPEED_T +typedef enum { + I2C_SPEED_LOW = 10000ul, /**< low speed mode: ~10 kbit/s */ + I2C_SPEED_NORMAL = 100000ul, /**< normal mode: ~100 kbit/s */ + I2C_SPEED_FAST = 400000ul, /**< fast mode: ~400 kbit/s */ + I2C_SPEED_FAST_PLUS = 1000000ul, /**< fast plus mode: ~1000 kbit/s */ + /* High speed is not supported without external hardware hacks */ + I2C_SPEED_HIGH = 3400000ul, /**< high speed mode: ~3400 kbit/s */ +} i2c_speed_t; +/** @} */ + +/** + * @name Use shared I2C functions + * @{ + */ +#define PERIPH_I2C_NEED_READ_REG +#define PERIPH_I2C_NEED_READ_REGS +#define PERIPH_I2C_NEED_WRITE_REG +#define PERIPH_I2C_NEED_WRITE_REGS +/** @} */ + +/** + * @brief I2C configuration structure + */ +typedef struct { + TWI_t *dev; /**< Pointer to hardware module registers */ + pwr_reduction_t pwr; /**< Power Management */ + gpio_t sda_pin; /**< SDA GPIO pin */ + gpio_t scl_pin; /**< SCL GPIO pin */ + i2c_speed_t speed; /**< Configured bus speed, actual speed may be lower but never higher */ + cpu_int_lvl_t int_lvl; /**< Serial Interrupt Level */ +} i2c_conf_t; + #ifdef __cplusplus } #endif diff --git a/cpu/atxmega/periph/i2c.c b/cpu/atxmega/periph/i2c.c new file mode 100644 index 0000000000..91195e7876 --- /dev/null +++ b/cpu/atxmega/periph/i2c.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_atxmega + * @ingroup cpu_atxmega_periph + * @{ + * + * @file + * @brief Low-level I2C driver implementation + * + * @author Gerson Fernando Budke + * + * @} + */ +#include + +#include "cpu.h" +#include "cpu_pm.h" +#include "periph/i2c.h" +#include "periph/gpio.h" +#include "periph/pm.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/** + * @brief Device Context allocation + */ +static struct { + mutex_t locks; + mutex_t xfer; + uint8_t *buffer; + size_t pos; + size_t len; + uint8_t flags; + int8_t status; +} i2c_ctx[I2C_NUMOF]; + +/** + * @brief Get the pointer to the base register of the given I2C device + * + * @param[in] dev I2C device identifier + * + * @return base register address + */ +static inline TWI_t* dev(i2c_t dev) +{ + return ((TWI_t*) (i2c_config[dev].dev)); +} + +static inline uint8_t _i2c_calc_baud(i2c_t i2c) +{ + uint16_t ftwi = (((CLOCK_CORECLOCK / (2*i2c_config[i2c].speed))-5)+1); + + if (ftwi > 255) { + DEBUG("[i2c] init: dev speed is lower than minimal speed supported.\n"); + return 0; + } + if (ftwi < 1) { + DEBUG("[i2c] init: dev speed is high than maximum speed supported.\n"); + return 0; + } + + return ftwi & 0xff; +} + +void i2c_init(i2c_t i2c) +{ + uint8_t baudrate; + + if (i2c >= I2C_NUMOF) { + DEBUG("[i2c] init: dev is invalid.\n"); + return; + } + + baudrate = _i2c_calc_baud(i2c); + if (baudrate == 0) { + DEBUG("[i2c] init: baudrate is invalid.\n"); + return; + } + + mutex_init(&i2c_ctx[i2c].locks); + mutex_init(&i2c_ctx[i2c].xfer); + mutex_lock(&i2c_ctx[i2c].xfer); + i2c_init_pins(i2c); + + pm_periph_enable(i2c_config[i2c].pwr); + dev(i2c)->MASTER.BAUD = baudrate; + pm_periph_disable(i2c_config[i2c].pwr); +} + +void i2c_init_pins(i2c_t i2c) +{ + gpio_init(i2c_config[i2c].sda_pin, GPIO_OPC_WRD_AND_PULL); + gpio_init(i2c_config[i2c].scl_pin, GPIO_OPC_WRD_AND_PULL); +} + +int i2c_acquire(i2c_t i2c) +{ + DEBUG("acquire\n"); + pm_block(3); + mutex_lock(&i2c_ctx[i2c].locks); + pm_periph_enable(i2c_config[i2c].pwr); + + dev(i2c)->MASTER.CTRLA = (i2c_config[i2c].int_lvl << TWI_MASTER_INTLVL_gp) + | TWI_MASTER_RIEN_bm + | TWI_MASTER_WIEN_bm + | TWI_MASTER_ENABLE_bm; + dev(i2c)->MASTER.STATUS = TWI_MASTER_BUSSTATE_IDLE_gc; + + return 0; +} + +void i2c_release(i2c_t i2c) +{ + dev(i2c)->MASTER.CTRLA = 0; + pm_periph_disable(i2c_config[i2c].pwr); + mutex_unlock(&i2c_ctx[i2c].locks); + pm_unblock(3); + DEBUG("release\n"); +} + +static int _i2c_transaction(i2c_t i2c, uint16_t addr, const void *data, + size_t len, uint8_t flags, bool is_read) +{ + if (flags & I2C_ADDR10) { + DEBUG("[i2c] xfer: no 10 bit address support.\n"); + return -EOPNOTSUPP; + } + if (data == NULL || len == 0) { + DEBUG("[i2c] xfer: invalid parameter.\n"); + return -EINVAL; + } + + i2c_ctx[i2c].buffer = (uint8_t *) data; + i2c_ctx[i2c].pos = 0; + i2c_ctx[i2c].len = len; + i2c_ctx[i2c].flags = flags; + + dev(i2c)->MASTER.ADDR = (addr << 1) | (is_read ? 0x01 : 0x00); + + mutex_lock(&i2c_ctx[i2c].xfer); + + DEBUG("[i2c] xfer: status = %d\n", i2c_ctx[i2c].status); + + return i2c_ctx[i2c].status; +} + +int i2c_write_bytes(i2c_t i2c, uint16_t addr, const void *data, size_t len, + uint8_t flags) +{ + return _i2c_transaction(i2c, addr, data, len, flags, false); +} + +int i2c_read_bytes(i2c_t i2c, uint16_t addr, void *data, size_t len, + uint8_t flags) +{ + return _i2c_transaction(i2c, addr, data, len, flags, true); +} + +/** + * @internal + * + * @brief TWI master write interrupt handler. + * + * Handles TWI transactions (master write) and responses to (N)ACK. + */ +static inline void _i2c_write_handler(int i2c) +{ + if (i2c_ctx[i2c].pos < i2c_ctx[i2c].len) { + const uint8_t* const data = i2c_ctx[i2c].buffer; + dev(i2c)->MASTER.DATA = data[i2c_ctx[i2c].pos++]; + } + else { + /* Send STOP condition to complete the transaction. */ + if (!(i2c_ctx[i2c].flags & I2C_NOSTOP)) { + dev(i2c)->MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; + } + + i2c_ctx[i2c].status = 0; + mutex_unlock(&i2c_ctx[i2c].xfer); + } +} + +/** + * @internal + * + * @brief TWI master read interrupt handler. + * + * This is the master read interrupt handler that takes care of reading bytes + * from the TWI slave device. + */ +static inline void _i2c_read_handler(int i2c) +{ + if (i2c_ctx[i2c].pos < i2c_ctx[i2c].len) { + uint8_t* const data = i2c_ctx[i2c].buffer; + data[i2c_ctx[i2c].pos++] = dev(i2c)->MASTER.DATA; + + /* If there is more to read, issue ACK and start a byte read. + * Otherwise, issue NACK and STOP to complete the transaction. + */ + if (i2c_ctx[i2c].pos < i2c_ctx[i2c].len) { + dev(i2c)->MASTER.CTRLC = TWI_MASTER_CMD_RECVTRANS_gc; + } + else { + uint8_t control_c = TWI_MASTER_ACKACT_bm; + + if (!(i2c_ctx[i2c].flags & I2C_NOSTOP)) { + control_c |= TWI_MASTER_CMD_STOP_gc; + } + + dev(i2c)->MASTER.CTRLC = control_c; + + i2c_ctx[i2c].status = 0; + mutex_unlock(&i2c_ctx[i2c].xfer); + } + } else { + /* Issue STOP and buffer overflow condition. */ + dev(i2c)->MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; + + i2c_ctx[i2c].status = -ENOMEM; + mutex_unlock(&i2c_ctx[i2c].xfer); + } +} + +/** + * @internal + * + * @brief Common TWI master interrupt service routine. + * + * Check current status and calls the appropriate handler. + */ +static inline void isr_handler(int i2c) +{ + avr8_enter_isr(); + + int8_t const m_status = dev(i2c)->MASTER.STATUS; + + if (m_status & TWI_MASTER_ARBLOST_bm) { + dev(i2c)->MASTER.STATUS = m_status | TWI_MASTER_ARBLOST_bm; + dev(i2c)->MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; + i2c_ctx[i2c].status = -EBUSY; + mutex_unlock(&i2c_ctx[i2c].xfer); + } + else if ((m_status & TWI_MASTER_BUSERR_bm) + || (m_status & TWI_MASTER_RXACK_bm)) { + + dev(i2c)->MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc; + i2c_ctx[i2c].status = ((m_status & TWI_MASTER_BUSERR_bm) > 0) + ? -EIO + : -ENXIO; + mutex_unlock(&i2c_ctx[i2c].xfer); + } + else if (m_status & TWI_MASTER_WIF_bm) { + _i2c_write_handler(i2c); + } + else if (m_status & TWI_MASTER_RIF_bm) { + _i2c_read_handler(i2c); + } + else { + i2c_ctx[i2c].status = -EPROTO; + mutex_unlock(&i2c_ctx[i2c].xfer); + } + + avr8_exit_isr(); +} + +#ifdef I2C_0_ISR +ISR(I2C_0_ISR, ISR_BLOCK) +{ + isr_handler(0); +} +#endif /* I2C_0_ISR */ + +#ifdef I2C_1_ISR +ISR(I2C_1_ISR, ISR_BLOCK) +{ + isr_handler(1); +} +#endif /* I2C_1_ISR */ + +#ifdef I2C_2_ISR +ISR(I2C_2_ISR, ISR_BLOCK) +{ + isr_handler(2); +} +#endif /* I2C_2_ISR */ + +#ifdef I2C_3_ISR +ISR(I2C_3_ISR, ISR_BLOCK) +{ + isr_handler(3); +} +#endif /* I2C_3_ISR */