mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
cpu/gd32v/periph_i2c: interrupt based driver
This commit is contained in:
parent
ea300c3d15
commit
5433daca32
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user