1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
19269: cpu/gd32v/periph_i2c: interrupt based driver r=gschorcht a=gschorcht

### Contribution description

This PR provides an interrupt-driven version of the I2C low-level driver.

The existing I2C low-level driver for GDVF103 uses a busy-waiting approach where the status register is continuously polled while waiting for a certain status when sending or receiving. The MCU is thus occupied the whole time during a send or receive operation.

The driver provided with this PR uses an interrupt-driven approach. This is, while waiting for a certain status when sending or receiving, the calling thread is suspended and woken up by interrupts.

Since the I2C controller allows to receive up to two bytes before the application has to react, receiving a single byte, two bytes or more than two bytes needs a different handling for correct receiption. This requires a tricky implementation which distinguish a number of different case. There the driver requires 860 byte more ROM and 8 byte more RAM.

### Testing procedure

The driver should work with any I2C sensor/actuator. It was tested with
- `tests/driver_bmp180`
   <details>
  
   ```
    main(): This is RIOT! (Version: 2023.04-devel-355-g940c7-cpu/gd32v/periph_i2c_interrupt_driven)
    BMP180 test application
    
    +------------Initializing------------+
    Initialization successful
    
    +------------Calibration------------+
    AC1: 8448
    AC2: -1208
    AC3: -14907
    AC4: 33310
    AC5: 24774
    AC6: 19213
    B1: 6515
    B2: 49
    MB: -32768
    MC: -11786
    MD: 2958
    
    +--------Starting Measurements--------+
    Temperature [°C]: 22.0
    Pressure [hPa]: 1006.49
    Pressure at see level [hPa]: 1025.55
    Altitude [m]: 157
    
    +-------------------------------------+
    Temperature [°C]: 22.0
    Pressure [hPa]: 1006.56
    Pressure at see level [hPa]: 1025.58
    Altitude [m]: 157
    
    +-------------------------------------+
   ```
   
   </details>
- `tests/driver_ccs811`
   <details>
  
   ```
    main(): This is RIOT! (Version: 2023.04-devel-355-g940c7-cpu/gd32v/periph_i2c_interrupt_driven)
    CCS811 test application
    
    +------------Initializing------------+
    
    +--------Starting Measurements--------+
    TVOC [ppb]: 0
    eCO2 [ppm]: 0
    +-------------------------------------+
    TVOC [ppb]: 0
    eCO2 [ppm]: 0
    +-------------------------------------+
    TVOC [ppb]: 0
    eCO2 [ppm]: 0
    +-------------------------------------+
    TVOC [ppb]: 0
    eCO2 [ppm]: 400
    +-------------------------------------+
    TVOC [ppb]: 0
    eCO2 [ppm]: 400
    +-------------------------------------+
    TVOC [ppb]: 0
    eCO2 [ppm]: 400
    +-------------------------------------+
    TVOC [ppb]: 7
    eCO2 [ppm]: 446
    +-------------------------------------+
    TVOC [ppb]: 7
    eCO2 [ppm]: 446
    +-------------------------------------+
    TVOC [ppb]: 7
    eCO2 [ppm]: 446
    +-------------------------------------+
    TVOC [ppb]: 7
    eCO2 [ppm]: 446
    +-------------------------------------+
    ```
   
   </details>
- `tests/driver_sht3x`
   <details>
  
    ```
    main(): This is RIOT! (Version: 2023.04-devel-355-g940c7-cpu/gd32v/periph_i2c_interrupt_driven)
    SHT3X test application
    
    +------------Initializing------------+
    Initialization successful
    
    
    +--------Starting Measurements--------+
    Temperature [°C]: 21.46
    Relative Humidity [%]: 54.50
    +-------------------------------------+
    Temperature [°C]: 21.47
    Relative Humidity [%]: 54.53
    +-------------------------------------+
    Temperature [°C]: 21.46
    Relative Humidity [%]: 54.48
    +-------------------------------------+
    Temperature [°C]: 21.46
    Relative Humidity [%]: 54.47
    +-------------------------------------+
    ```
   
   </details>
- `tests/driver_l3gxxxx`
   <details>
  
    ```
    main(): This is RIOT! (Version: 2023.04-devel-375-g75547-cpu/gd32v/periph_i2c_interrupt_driven)
    L3GXXXX gyroscope driver test application
    
    Initializing L3GXXXX sensor
    [OK]
    
    gyro [dps] x:    +0, y:    -1, z:    -2
    gyro [dps] x:    +0, y:    +0, z:    +0
    gyro [dps] x:    +0, y:    +0, z:    +0
    gyro [dps] x:    +0, y:    +0, z:    +0
    gyro [dps] x:    +0, y:    +0, z:    +0
    gyro [dps] x:    +0, y:    +0, z:    +0
    gyro [dps] x:    -1, y:    +0, z:    +4
    gyro [dps] x:    +0, y:    +0, z:   -21
    gyro [dps] x:    +0, y:    +0, z:    +6
    gyro [dps] x:   -43, y:    +0, z:   -13
    gyro [dps] x:   -21, y:    -2, z:    +0
    gyro [dps] x:    +0, y:    +1, z:    +3
    gyro [dps] x:   +25, y:    +0, z:    +0
    ```
   
   </details>
- `tests/driver_hd44780` with `pcf8574a` I2C interface

### Issues/PRs references



Co-authored-by: Gunar Schorcht <gunar@schorcht.net>
This commit is contained in:
bors[bot] 2023-02-18 07:54:59 +00:00 committed by GitHub
commit 9998b7ac05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 569 additions and 226 deletions

View File

@ -30,6 +30,7 @@ config CPU_FAM_GD32V
select MODULE_PERIPH_CLIC if TEST_KCONFIG
select MODULE_PERIPH_WDT if MODULE_PERIPH_PM && HAS_PERIPH_WDT
select ZTIMER_USEC if MODULE_PERIPH_I2C
select PACKAGE_NMSIS_SDK
menu "GD32V configuration"

View File

@ -7,4 +7,8 @@ ifneq (,$(filter periph_pm,$(USEMODULE)))
FEATURES_REQUIRED += periph_wdt
endif
ifneq (,$(filter periph_i2c,$(USEMODULE)))
USEMODULE += ztimer_usec
endif
include $(RIOTCPU)/riscv_common/Makefile.dep

View File

@ -397,7 +397,7 @@ typedef struct {
gpio_t scl_pin; /**< scl pin number */
gpio_t sda_pin; /**< sda pin number */
uint32_t rcu_mask; /**< bit in clock enable register */
uint8_t irqn; /**< I2C event interrupt number */
IRQn_Type irqn; /**< I2C event interrupt number */
} i2c_conf_t;
/**

View File

@ -1,9 +1,5 @@
/*
* Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de>
* 2014 FU Berlin
* 2018 Inria
* 2018 HAW Hamburg
* 2023 Gunar Schorcht <gunar@schorcht.net>
* Copyright (C) 2023 Gunar Schorcht
*
* 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
@ -16,21 +12,8 @@
* @{
*
* @file
* @brief Low-level I2C driver implementation
* @brief Low-level I2C driver implementation for GD32VF103
*
* This driver is a modified copy of the I2C driver for the STM32F1 family.
*
* @note This implementation only implements the 7-bit addressing polling mode.
*
* @author Peter Kietzmann <peter.kietzmann@haw-hamburg.de>
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Thomas Eichinger <thomas.eichinger@fu-berlin.de>
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @author Toon Stegen <toon.stegen@altran.com>
* @author Vincent Dupont <vincent@otakeys.com>
* @author Víctor Ariño <victor.arino@triagnosys.com>
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
* @author Kevin Weiss <kevin.weiss@haw-hamburg.de>
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @}
@ -43,49 +26,80 @@
#include "cpu.h"
#include "irq.h"
#include "mutex.h"
#ifdef MODULE_PM_LAYERED
#include "pm_layered.h"
#endif
#include "panic.h"
#include "periph/i2c.h"
#include "periph/gpio.h"
#include "periph_conf.h"
#include "ztimer.h"
/* Some DEBUG statements may cause delays that alter i2c functionality */
#define ENABLE_DEBUG 0
#define ENABLE_DEBUG 0
#include "debug.h"
#define TICK_TIMEOUT (0xFFFF)
#define I2C_TIMEOUT_CYCLES 1000 /* clock cycles */
#define I2C_IRQ_PRIO (1)
#define I2C_IRQ_PRIO (1)
#define I2C_FLAG_READ (I2C_READ)
#define I2C_FLAG_WRITE (0)
#define I2C_ERROR_FLAGS_USED (I2C_STAT0_AERR_Msk | I2C_STAT0_LOSTARB_Msk | \
I2C_STAT0_BERR_Msk)
#define I2C_ERROR_FLAGS_OTHER (I2C_STAT0_OUERR_Msk | I2C_STAT0_PECERR_Msk | \
I2C_STAT0_SMBTO_Msk | I2C_STAT0_SMBALT_Msk)
#define I2C_ERROR_FLAGS (I2C_ERROR_FLAGS_USED | I2C_ERROR_FLAGS_OTHER)
#define ERROR_FLAGS (I2C_STAT0_AERR_Msk | I2C_STAT0_LOSTARB_Msk | I2C_STAT0_BERR_Msk)
#define I2C_INT_EV_ERR_FLAGS (I2C_CTL1_EVIE_Msk | I2C_CTL1_ERRIE_Msk)
#define I2C_INT_ALL_FLAGS (I2C_CTL1_BUFIE_Msk | I2C_INT_EV_ERR_FLAGS)
/* static function definitions */
static void _init(i2c_t dev);
static void _init_pins(i2c_t dev);
static void _init_clk(I2C_Type *i2c, uint32_t speed);
static void _deinit_pins(i2c_t dev);
static int _wait_for_bus(i2c_t dev);
static void _wait_for_irq(i2c_t dev);
static void _irq_handler(unsigned irqn);
static int _start(I2C_Type *dev, uint8_t address_byte, uint8_t flags,
size_t length);
static int _stop(I2C_Type *dev);
typedef enum {
I2C_OK,
I2C_START_SENT,
I2C_ADDR_SENT,
I2C_ADDR10_SENT,
I2C_BT_COMPLETE,
I2C_TXB_EMPTY,
I2C_RXB_NOT_EMPTY,
I2C_RXB_NOT_EMPTY_BT_COMPLETE,
I2C_TIMEOUT,
I2C_ACK_ERR,
I2C_ARB_LOST,
I2C_BUS_ERROR,
I2C_OTHER_ERROR,
} _i2c_state_t;
static int _is_sr1_mask_set(I2C_Type *i2c, uint32_t mask, uint8_t flags);
static inline int _wait_for_bus(I2C_Type *i2c);
typedef struct {
mutex_t dev_lock;
mutex_t irq_lock;
_i2c_state_t state;
void (*isr)(unsigned irqn);
} _i2c_dev_t;
/**
* @brief Array holding one pre-initialized mutex for each I2C device
*/
static mutex_t locks[I2C_NUMOF];
_i2c_dev_t _i2c_dev[] = {
{
.dev_lock = MUTEX_INIT,
.irq_lock = MUTEX_INIT_LOCKED,
.state = I2C_OK,
},
{
.dev_lock = MUTEX_INIT,
.irq_lock = MUTEX_INIT_LOCKED,
.state = I2C_OK,
},
};
void i2c_init(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_init(&locks[dev]);
assert(i2c_config[dev].dev != NULL);
/* Configure pins in idle state as open drain outputs to keep the bus lines
@ -94,9 +108,16 @@ void i2c_init(i2c_t dev)
periph_clk_en(APB1, i2c_config[dev].rcu_mask);
/* enable set event interrupt handler and enable the event interrupt */
clic_set_handler(i2c_config[dev].irqn, _irq_handler);
clic_enable_interrupt(i2c_config[dev].irqn, CPU_DEFAULT_IRQ_PRIO);
/* enable set error interrupt handler and enable the error interrupt */
clic_set_handler(i2c_config[dev].irqn + 1, _irq_handler);
clic_enable_interrupt(i2c_config[dev].irqn + 1, CPU_DEFAULT_IRQ_PRIO);
_init(dev);
periph_clk_dis(APB1, i2c_config[dev].rcu_mask);
_i2c_dev[dev].state = I2C_OK;
}
static void _init_pins(i2c_t dev)
@ -121,11 +142,8 @@ static void _init_clk(I2C_Type *i2c, uint32_t speed)
i2c->CTL1 = (CLOCK_APB1 / MHZ(1)) | I2C_CTL1_ERRIE_Msk;
i2c->CKCFG = CLOCK_APB1 / (2 * speed);
i2c->RT = (CLOCK_APB1 / 1000000) + 1;
/* configure device */
i2c->SADDR0 |= (1 << 14); /* datasheet: bit 14 should be kept 1 */
i2c->SADDR0 &= ~I2C_SADDR0_ADDFORMAT_Msk; /* make sure we are in 7-bit address mode */
/* Clear flags */
i2c->STAT0 &= ~ERROR_FLAGS;
i2c->STAT0 &= ~I2C_ERROR_FLAGS;
/* enable device */
i2c->CTL0 |= I2C_CTL0_I2CEN_Msk;
}
@ -155,7 +173,8 @@ void i2c_acquire(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_lock(&locks[dev]);
/* lock the device */
mutex_lock(&_i2c_dev[dev].dev_lock);
/* block DEEP_SLEEP mode */
pm_block(GD32V_PM_DEEPSLEEP);
@ -174,9 +193,9 @@ void i2c_release(i2c_t dev)
assert(dev < I2C_NUMOF);
/* disable device */
i2c_config[dev].dev->CTL0 &= ~(I2C_CTL0_I2CEN_Msk);
i2c_config[dev].dev->CTL0 &= ~I2C_CTL0_I2CEN_Msk;
_wait_for_bus(i2c_config[dev].dev);
_wait_for_bus(dev);
/* Disabling the clock switches off the I2C controller, which results in
* LOW bus lines. To avoid that the used GPIOs then draw some milliamps
@ -189,18 +208,210 @@ void i2c_release(i2c_t dev)
/* unblock DEEP_SLEEP mode */
pm_unblock(GD32V_PM_DEEPSLEEP);
mutex_unlock(&locks[dev]);
/* unlock the device */
mutex_unlock(&_i2c_dev[dev].dev_lock);
}
int i2c_read_bytes(i2c_t dev, uint16_t address, void *data, size_t length,
static int _i2c_start_cmd(i2c_t dev)
{
DEBUG("START cmd, dev=%d\n", dev);
I2C_Type *i2c = i2c_config[dev].dev;
/* clear error flags */
i2c->STAT0 &= ~I2C_ERROR_FLAGS;
/* send start condition and wait for interrupt */
i2c->CTL0 |= I2C_CTL0_START_Msk;
_wait_for_irq(dev);
switch (_i2c_dev[dev].state) {
case I2C_START_SENT:
return 0;
case I2C_ARB_LOST:
return -EAGAIN;
case I2C_TIMEOUT:
return -ETIMEDOUT;
default:
/* on other errors */
return -EINVAL;
}
}
static int _i2c_stop_cmd(i2c_t dev)
{
DEBUG("STOP cmd, dev=%d\n", dev);
I2C_Type *i2c = i2c_config[dev].dev;
/* clear error flags */
i2c->STAT0 &= ~I2C_ERROR_FLAGS;
/* send start condition */
i2c->CTL0 |= I2C_CTL0_STOP_Msk;
return 0;
}
static int _i2c_stop_cmd_and_wait(i2c_t dev)
{
_i2c_stop_cmd(dev);
return _wait_for_bus(dev);
}
static int _i2c_addr_cmd(i2c_t dev, uint8_t *addr, uint8_t size)
{
if (size == 1) {
DEBUG("ADDR cmd, dev=%d, addr=%02x\n", dev, addr[0]);
}
else{
DEBUG("ADDR cmd, dev=%d, addr=%02x%02x\n", dev, addr[0], addr[1]);
}
I2C_Type *i2c = i2c_config[dev].dev;
/* clear error flags */
i2c->STAT0 &= ~I2C_ERROR_FLAGS;
/* read STAT0 followed by writing the first address byte to the
* DATA register to clear SBSEND and then wait for interrupt */
i2c->STAT0;
i2c->DATA = addr[0];
_wait_for_irq(dev);
if ((_i2c_dev[dev].state == I2C_ADDR10_SENT)) {
/* first byte is sent and indicates a 10 bit address */
assert(size == 2);
/* read STAT0 followed by writing the second byte of the 10-bit
* address to the DATA register to clear ADD10SEND and then wait for
* interrupt */
i2c->STAT0;
i2c->DATA = addr[1];
_wait_for_irq(dev);
}
switch (_i2c_dev[dev].state) {
case I2C_ADDR_SENT:
/* Since the ADDSEND flag is needed to control the ACK bit while
* receiving bytes, it is intentionally not cleared here, but must
* be explicitly cleared in `i2c_read_bytes` and `i2c_read_bytes */
return 0;
case I2C_ARB_LOST:
return -EAGAIN;
case I2C_TIMEOUT:
return -ETIMEDOUT;
case I2C_ACK_ERR:
return -ENXIO;
default:
/* on other errors */
return -EINVAL;
}
}
int _i2c_write_cmd(i2c_t dev, uint8_t data)
{
DEBUG("WRITE cmd, dev=%d\n", dev);
I2C_Type *i2c = i2c_config[dev].dev;
/* clear error flags */
i2c->STAT0 &= ~I2C_ERROR_FLAGS;
/* send data byte and wait for BTC interrupt */
i2c->DATA = data;
_wait_for_irq(dev);
switch (_i2c_dev[dev].state) {
case I2C_BT_COMPLETE:
/* read STAT0 followed by reading DATA to clear the BTC flag */
i2c->STAT0;
i2c->DATA;
return 0;
case I2C_ARB_LOST:
return -EAGAIN;
case I2C_TIMEOUT:
return -ETIMEDOUT;
case I2C_ACK_ERR:
return -EIO;
default:
/* on other errors */
return -EINVAL;
}
}
int _i2c_read_cmd(i2c_t dev, uint8_t *data)
{
DEBUG("READ cmd, dev=%d\n", dev);
I2C_Type *i2c = i2c_config[dev].dev;
/* clear error flags */
i2c->STAT0 &= ~I2C_ERROR_FLAGS;
if (i2c->STAT0 & (I2C_STAT0_RBNE_Msk || I2C_STAT0_BTC_Msk)) {
_i2c_dev[dev].state = I2C_RXB_NOT_EMPTY;
}
else {
/* buffer interrupts have to be enabled for read */
i2c_config[dev].dev->CTL1 |= I2C_CTL1_BUFIE_Msk;
/* wait for interrupt */
_wait_for_irq(dev);
}
switch (_i2c_dev[dev].state) {
case I2C_RXB_NOT_EMPTY_BT_COMPLETE:
case I2C_RXB_NOT_EMPTY:
/* RBNE is cleared by reding STAT0 followed by reading the data register */
i2c->STAT0;
*data = i2c->DATA;
return 0;
case I2C_ARB_LOST:
return -EAGAIN;
case I2C_TIMEOUT:
return -ETIMEDOUT;
default:
/* on other errors */
return -EINVAL;
}
}
static int _i2c_wait_rbne_btc(i2c_t dev)
{
DEBUG("WAIT RNBE+BTC cmd, dev=%d\n", dev);
I2C_Type *i2c = i2c_config[dev].dev;
/* clear error flags */
i2c->STAT0 &= ~I2C_ERROR_FLAGS;
_wait_for_irq(dev);
switch (_i2c_dev[dev].state) {
case I2C_RXB_NOT_EMPTY_BT_COMPLETE:
return 0;
case I2C_ARB_LOST:
return -EAGAIN;
case I2C_TIMEOUT:
return -ETIMEDOUT;
default:
/* on other errors */
return -EINVAL;
}
}
int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t length,
uint8_t flags)
{
assert(dev < I2C_NUMOF);
assert(data);
assert(length);
DEBUG("i2c_read_bytes, dev=%d len=%d flags=%02x\n", dev, length, flags);
I2C_Type *i2c = i2c_config[dev].dev;
DEBUG("[i2c] read_bytes: Starting\n");
/* Repeated start of read operations is not supported. This is exactly the
/* Repeated START of read operations is not supported. This is exactly the
* case if the previous transfer was a read operation (I2C_STAT1_TR == 0)
* and was not terminated by a STOP condition (I2C_STAT1_I2CBSY == 1) and
* the START condition is to be used (I2C_NOSTART == 0).
@ -210,206 +421,333 @@ int i2c_read_bytes(i2c_t dev, uint16_t address, void *data, size_t length,
return -EOPNOTSUPP;
}
int ret = _start(i2c, (address << 1) | I2C_FLAG_READ, flags, length);
if (ret < 0) {
if (ret == -ETIMEDOUT) {
_init(dev);
int res;
_i2c_dev[dev].state = I2C_OK;
/* set the ACK bit */
i2c->CTL0 |= I2C_CTL0_ACKEN_Msk;
/* clear the POAP bit to indicate that ACK bit controls the current byte */
i2c->CTL0 &= ~I2C_CTL0_POAP_Msk;
if (!(flags & I2C_NOSTOP)) {
/* Since the I2C controller allows to receive up to two bytes before
* the application has to react, receiving a single byte, two bytes
* or more than two bytes needs a different handling for correct
* reception if the reception is not to be continued (I2C_NOSTOP
* is not set) */
if (length == 1) {
/* If a single byte is to be received clear the ACK bit before
* any byte is received and keep the POAP bit cleared to indicate
* that the ACK bit controls the ACK of the currecnt byte. */
i2c->CTL0 &= ~I2C_CTL0_ACKEN_Msk;
}
return ret;
else if (length == 2) {
/* If exactly 2 bytes are to be received, keep the ACK bit set but
* also set the POAP bit to indicate that the ACK bit controls the
* next byte that is received in shift register. The ACK bit is then
* cleared after the ADDSEND flag is cleared and thus the reception
* of first byte has already been started. Thus an ACK is generated
* for the first byte and a NACK for the second byte. */
i2c->CTL0 |= I2C_CTL0_POAP_Msk;
}
/* In all other cases the ACK flag is kept set while the POAP flag
* is kept cleared, so that the ACK bit always controls the byte
* currently to be received in the shift register. To clear the ACK bit
* before the last byte is received in the shift register, reading of
* bytes from the DATA register is stopped when there are 3 bytes left
* to receive until the BTC flag is set. The BTC flag then indicates
* that the DATA register then contains the third last byte and the
* shift register the second last byte. The ACK flag is then cleared
* before reading of the bytes from the DATA register continues. */
}
/* if I2C_NOSTART is not set, send START condition and ADDR */
if (!(flags & I2C_NOSTART)) {
res = _i2c_start_cmd(dev);
if (res != 0) {
_i2c_stop_cmd_and_wait(dev);
return res;
}
/* address handling */
if (flags & I2C_ADDR10) {
/* prepare 10 bit address bytes */
uint8_t addr10[2];
addr10[0] = 0xf0 | (addr & 0x0300) >> 7 | I2C_READ;
addr10[1] = addr & 0xff;
/* send ADDR without read flag */
res = _i2c_addr_cmd(dev, addr10, 2);
}
else {
/* send ADDR without read flag */
uint8_t addr7 = addr << 1 | I2C_READ;
res = _i2c_addr_cmd(dev, &addr7, 1);
}
if (res != 0) {
_i2c_stop_cmd_and_wait(dev);
return res;
}
}
/* read STAT0 followed by reading STAT1 to clear the ADDSEND flag */
i2c->STAT0;
i2c->STAT1;
/* read data */
uint8_t *buffer = (uint8_t*)data;
for (size_t i = 0; i < length; i++) {
if (i + 1 == length && !(flags & I2C_NOSTOP)) {
/* If data is already in the buffer we must clear before sending
* a stop. If I2C_NOSTOP was called up to two extra bytes may be
* clocked out on the line however they get ignored in the firmware.*/
if ((i2c->STAT0 & I2C_STAT0_RBNE_Msk) && (length == 1)) {
((uint8_t*)data)[i] = i2c->DATA;
return _stop(i2c);
if (!(flags & I2C_NOSTOP)) {
/* if the reception is not to be continued (I2C_NOSTOP is not set) */
if (i == 0 && (length == 2)) {
/* The shift register already receives the first byte after
* the ADDSEND flag has been cleared. Since the POAP bit is
* set in the case that exactly two bytes are to be received,
* the change of the ACK bit during the reception of a byte
* does not affect the generated ACK of the byte currently
* received, but of the byte to be received next. Since the
* ACK bit and the POAP bit were set in this case, an ACK
* is generated for the first byte. Clearing the ACK bit here
* then generates a NACK for the next (last) byte. */
i2c->CTL0 &= ~I2C_CTL0_ACKEN_Msk;
}
/* STOP must also be sent before final read */
ret = _stop(i2c);
if (ret < 0) {
return ret;
else if (i == (length - 3)) {
/* To clear the ACK flag before the last byte is received
* in the shift register, reading of bytes from the DATA
* register is stopped when there are 3 bytes left to
* receive until the BTC flag is set. The BTC flag is then
* set when the third last received byte is in DATA register
* and the second last byte is in shift register. */
res = _i2c_wait_rbne_btc(dev);
if (res != 0) {
_i2c_stop_cmd_and_wait(dev);
return res;
}
/* The ACK flag is then cleared before reading of the bytes
* from the DATA register continues to generate an NACK
* for the last byte. */
i2c->CTL0 &= ~I2C_CTL0_ACKEN_Msk;
}
}
/* Wait for reception to complete */
ret = _is_sr1_mask_set(i2c, I2C_STAT0_RBNE_Msk, flags);
if (ret < 0) {
return ret;
res = _i2c_read_cmd(dev, buffer + i);
if (res != 0) {
_i2c_stop_cmd_and_wait(dev);
return res;
}
((uint8_t*)data)[i] = i2c->DATA;
}
DEBUG("[i2c] read_bytes: Finished reading bytes\n");
if (flags & I2C_NOSTOP) {
return 0;
if (!(flags & I2C_NOSTOP)) {
/* If the transfer is not to be continued (I2C_NOSTOP is not set), set
* the STOP bit and wait a short time until the I2CBSY bit is cleared. */
return _i2c_stop_cmd_and_wait(dev);
}
return _wait_for_bus(i2c);
return 0;
}
int i2c_write_bytes(i2c_t dev, uint16_t address, const void *data,
int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data,
size_t length, uint8_t flags)
{
assert(dev < I2C_NUMOF);
assert(data);
assert(length);
int ret;
DEBUG("i2c_write_bytes, dev=%d len=%d flags=%02x\n", dev, length, flags);
int res;
_i2c_dev[dev].state = I2C_OK;
/* if I2C_NOSTART is not set, send START condition and ADDR */
if (!(flags & I2C_NOSTART)) {
res = _i2c_start_cmd(dev);
if (res != 0) {
_i2c_stop_cmd_and_wait(dev);
return res;
}
/* address handling */
if (flags & I2C_ADDR10) {
/* prepare 10 bit address bytes */
uint8_t addr10[2];
addr10[0] = 0xf0 | (addr & 0x0300) >> 7;
addr10[1] = addr & 0xff;
/* send ADDR without read flag */
res = _i2c_addr_cmd(dev, addr10, 2);
}
else {
/* send ADDR without read flag */
uint8_t addr7 = addr << 1;
res = _i2c_addr_cmd(dev, &addr7, 1);
}
if (res != 0) {
_i2c_stop_cmd_and_wait(dev);
return res;
}
}
/* read STAT0 followed by reading STAT1 to clear the ADDSEND flag */
i2c_config[dev].dev->STAT0;
i2c_config[dev].dev->STAT1;
/* send data */
uint8_t *buffer = (uint8_t*)data;
for (size_t i = 0; i < length; i++) {
res = _i2c_write_cmd(dev, buffer[i]);
if (res != 0) {
_i2c_stop_cmd_and_wait(dev);
return res;
}
}
if (!(flags & I2C_NOSTOP)) {
/* If the transfer is not to be continued (I2C_NOSTOP is not set), set
* the STOP bit and wait a short time until the I2CBSY bit is cleared. */
return _i2c_stop_cmd_and_wait(dev);
}
return 0;
}
void _i2c_transfer_timeout(void *arg)
{
i2c_t dev = (i2c_t)(uintptr_t)arg;
/* set result to timeout */
_i2c_dev[dev].state = I2C_TIMEOUT;
/* wake up the thread that is waiting for the results */
mutex_unlock(&_i2c_dev[dev].irq_lock);
}
static void _wait_for_irq(i2c_t dev)
{
#if defined(MODULE_ZTIMER_USEC)
ztimer_t timer = { .callback = _i2c_transfer_timeout,
.arg = (void *)dev };
uint32_t timeout = ((I2C_TIMEOUT_CYCLES * MHZ(1)) / i2c_config[dev].speed) + 1;
ztimer_set(ZTIMER_USEC, &timer, timeout);
#elif defined(MODULE_ZTIMER_MSEC)
ztimer_t timer = { .callback = _i2c_transfer_timeout,
.arg = (void *)dev };
uint32_t timeout = ((I2C_TIMEOUT_CYCLES * KHZ(1)) / i2c_config[dev].speed) + 1;
ztimer_set(ZTIMER_MSEC, &timer, timeout);
#else
#warning "I2C timeout handling requires to use ztimer_msec or ztimer_usec"
#endif
/* enable only event and error interrupts, buffer interrupts are only used in read */
i2c_config[dev].dev->CTL1 |= I2C_INT_EV_ERR_FLAGS;
/* wait for buffer, event or error interrupt */
mutex_lock(&_i2c_dev[dev].irq_lock);
#if defined(MODULE_ZTIMER_USEC)
ztimer_remove(ZTIMER_USEC, &timer);
#elif defined(MODULE_ZTIMER_MSEC)
ztimer_remove(ZTIMER_MSEC, &timer);
#endif
if (_i2c_dev[dev].state == I2C_TIMEOUT) {
DEBUG("error: timeout, dev=%d\n", dev);
}
}
#define TICK_TIMEOUT 0x00000fffUL
static inline int _wait_for_bus(i2c_t dev)
{
uint16_t tick = TICK_TIMEOUT;
/* short busy waiting for the bus becoming free (I2CBSY is cleared) */
while ((i2c_config[dev].dev->STAT1 & I2C_STAT1_I2CBSY_Msk) && tick--) {}
if (!tick) {
DEBUG("error: timeout, dev=%d\n", dev);
return -ETIMEDOUT;
}
return 0;
}
static void _irq_handler(unsigned irqn)
{
static_assert(I2C0_ER_IRQn == I2C0_EV_IRQn + 1);
static_assert(I2C1_ER_IRQn == I2C1_EV_IRQn + 1);
i2c_t dev = ((irqn == i2c_config[0].irqn) ||
(irqn == (i2c_config[0].irqn + 1))) ? I2C_DEV(0) : I2C_DEV(1);
assert(dev < I2C_NUMOF);
I2C_Type *i2c = i2c_config[dev].dev;
assert(i2c != NULL);
DEBUG("[i2c] write_bytes: Starting\n");
/* Length is 0 in start since we don't need to preset the stop bit */
ret = _start(i2c, (address << 1) | I2C_FLAG_WRITE, flags, 0);
if (ret < 0) {
if (ret == -ETIMEDOUT) {
_init(dev);
}
return ret;
}
/* Send out data bytes */
for (size_t i = 0; i < length; i++) {
DEBUG("[i2c] write_bytes: Waiting for TX reg to be free\n");
ret = _is_sr1_mask_set(i2c, I2C_STAT0_TBE_Msk, flags);
if (ret < 0) {
return ret;
assert(i2c != NULL);
unsigned state = i2c->STAT0;
/* disable buffer, event and error interrupts */
i2c->CTL1 &= ~(I2C_INT_ALL_FLAGS);
DEBUG("STAT0: %08x, dev=%d\n", state, dev);
if (state & I2C_ERROR_FLAGS) {
/* any error happened */
if (state & I2C_ERROR_FLAGS_USED) {
if (state & I2C_STAT0_LOSTARB_Msk) {
DEBUG("error: arbitration lost, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_ARB_LOST;
}
else if (state & I2C_STAT0_AERR_Msk) {
DEBUG("error: ACK error, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_ACK_ERR;
}
else if (state & I2C_STAT0_BERR_Msk) {
DEBUG("error: bus error, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_BUS_ERROR;
}
}
DEBUG("[i2c] write_bytes: TX is free so send byte\n");
i2c->DATA = ((uint8_t*)data)[i];
if (state & I2C_ERROR_FLAGS_OTHER) {
/* PEC calculation and SMBus are not used,
* according errors are simply handled as other errors */
DEBUG("error: other error, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_OTHER_ERROR;
}
/* clear interrupt flags for errors */
i2c->CTL1 &= ~(I2C_ERROR_FLAGS);
}
/* Wait for tx reg to be empty so other calls will no interfere */
ret = _is_sr1_mask_set(i2c, I2C_STAT0_TBE_Msk, flags);
if (ret < 0) {
return ret;
else if (state & I2C_STAT0_SBSEND_Msk) {
DEBUG("START sent, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_START_SENT;
}
if (flags & I2C_NOSTOP) {
return 0;
else if (state & I2C_STAT0_ADDSEND_Msk) {
DEBUG("ADDR sent, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_ADDR_SENT;
}
else if (state & I2C_STAT0_ADD10SEND_Msk) {
DEBUG("ADDR10 first byte sent, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_ADDR10_SENT;
}
else if (state & I2C_STAT0_RBNE_Msk) {
if (state & I2C_STAT0_BTC_Msk) {
DEBUG("RX buffer not empty + BT completed, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_RXB_NOT_EMPTY_BT_COMPLETE;
}
else {
DEBUG("RX buffer not empty, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_RXB_NOT_EMPTY;
}
}
else if (state & I2C_STAT0_BTC_Msk) {
DEBUG("BT completed, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_BT_COMPLETE;
}
else if (state & I2C_STAT0_TBE_Msk) {
DEBUG("TX buffer empty, dev=%d\n", dev);
_i2c_dev[dev].state = I2C_TXB_EMPTY;
}
else {
/* End transmission */
DEBUG("[i2c] write_bytes: Ending transmission\n");
ret = _stop(i2c);
if (ret < 0) {
return ret;
}
DEBUG("[i2c] write_bytes: STOP condition was send out\n");
_i2c_dev[dev].state = I2C_OK;
}
return _wait_for_bus(i2c);
}
static int _start(I2C_Type *i2c, uint8_t address_byte, uint8_t flags,
size_t length)
{
assert(i2c != NULL);
if ((flags & I2C_ADDR10) ||
(!(i2c->STAT1 & I2C_STAT1_I2CBSY_Msk) && (flags & I2C_NOSTART))) {
return -EOPNOTSUPP;
}
/* Clear flags */
i2c->STAT0 &= ~ERROR_FLAGS;
if (!(flags & I2C_NOSTART)) {
DEBUG("[i2c] start: Generate start condition\n");
/* Generate start condition */
i2c->CTL0 |= I2C_CTL0_START_Msk | I2C_CTL0_ACKEN_Msk;
/* Wait for SB flag to be set */
int ret = _is_sr1_mask_set(i2c, I2C_STAT0_SBSEND_Msk, flags & ~I2C_NOSTOP);
if (ret < 0) {
return ret;
}
DEBUG("[i2c] start: Start condition generated\n");
DEBUG("[i2c] start: Generating address\n");
/* Send address and read/write flag */
if ((i2c->STAT0 & I2C_STAT0_SBSEND_Msk)) {
i2c->DATA = (address_byte);
}
if (!(flags & I2C_NOSTOP) && length == 1) {
i2c->CTL0 &= ~(I2C_CTL0_ACKEN_Msk);
}
/* Wait for ADDR flag to be set */
ret = _is_sr1_mask_set(i2c, I2C_STAT0_ADDSEND_Msk, flags & ~I2C_NOSTOP);
if (ret == -EIO){
/* Since NACK happened during start it means no device connected */
return -ENXIO;
}
/* Wait until I2C_STAT0_ADDSEND is cleared. To clear I2C_STAT0_ADDSEND
* it is necessary to read STAT0 followed by reading STAT1 */
while ((i2c->STAT0 & I2C_STAT0_ADDSEND_Msk) && i2c->STAT1) { }
if (!(flags & I2C_NOSTOP) && length == 1) {
/* Stop must also be sent before final read */
i2c->CTL0 |= (I2C_CTL0_STOP_Msk);
}
DEBUG("[i2c] start: Address generated\n");
return ret;
}
return 0;
}
static int _is_sr1_mask_set(I2C_Type *i2c, uint32_t mask, uint8_t flags)
{
DEBUG("[i2c] _is_sr1_mask_set: waiting to set %04X\n", (uint16_t)mask);
uint16_t tick = TICK_TIMEOUT;
while (tick--) {
uint32_t sr1 = i2c->STAT0;
if (sr1 & I2C_STAT0_AERR_Msk) {
DEBUG("[i2c] is_sr1_mask_set: NACK received\n");
i2c->STAT0 &= ~ERROR_FLAGS;
if (!(flags & I2C_NOSTOP)) {
_stop(i2c);
}
return -EIO;
}
if ((sr1 & I2C_STAT0_LOSTARB_Msk) || (sr1 & I2C_STAT0_BERR_Msk)) {
DEBUG("[i2c] is_sr1_mask_set: arb lost or bus ERROR_FLAGS\n");
i2c->STAT0 &= ~ERROR_FLAGS;
_stop(i2c);
return -EAGAIN;
}
if (sr1 & mask) {
i2c->STAT0 &= ~ERROR_FLAGS;
return 0;
}
}
/*
* If timeout occurs this means a problem that must be handled on a higher
* level. A SWRST is recommended by the datasheet.
*/
i2c->STAT0 &= ~ERROR_FLAGS;
_stop(i2c);
return -ETIMEDOUT;
}
static int _stop(I2C_Type *i2c)
{
/* send STOP condition */
DEBUG("[i2c] stop: Generate stop condition\n");
i2c->CTL0 &= ~(I2C_CTL0_ACKEN_Msk);
i2c->CTL0 |= I2C_CTL0_STOP_Msk;
uint16_t tick = TICK_TIMEOUT;
while ((i2c->CTL0 & I2C_CTL0_STOP_Msk) && tick--) {}
if (!tick) {
return -ETIMEDOUT;
}
DEBUG("[i2c] stop: Stop condition succeeded\n");
if (_wait_for_bus(i2c) < 0) {
return -ETIMEDOUT;
}
DEBUG("[i2c] stop: Bus is free\n");
return 0;
}
static inline int _wait_for_bus(I2C_Type *i2c)
{
uint16_t tick = TICK_TIMEOUT;
while ((i2c->STAT1 & I2C_STAT1_I2CBSY_Msk) && tick--) {}
if (!tick) {
return -ETIMEDOUT;
}
return 0;
mutex_unlock(&_i2c_dev[dev].irq_lock);
}