From 3fd04b76e446b1d4a63e2b4f6815db01fadeced4 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Sun, 12 Feb 2023 18:45:03 +0100 Subject: [PATCH] cpu/gd32v/periph_i2c: interrupt based driver --- cpu/gd32v/Kconfig | 1 + cpu/gd32v/Makefile.dep | 4 + cpu/gd32v/include/periph_cpu.h | 2 +- cpu/gd32v/periph/i2c.c | 752 ++++++++++++++++++++++++--------- 4 files changed, 551 insertions(+), 208 deletions(-) diff --git a/cpu/gd32v/Kconfig b/cpu/gd32v/Kconfig index 6bdde87cc361a..e732c6eb85b0e 100644 --- a/cpu/gd32v/Kconfig +++ b/cpu/gd32v/Kconfig @@ -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 MODULE_ZTIMER_USEC if MODULE_PERIPH_I2C select PACKAGE_NMSIS_SDK menu "GD32V configuration" diff --git a/cpu/gd32v/Makefile.dep b/cpu/gd32v/Makefile.dep index 578c9c82d2213..21499ed6185cf 100644 --- a/cpu/gd32v/Makefile.dep +++ b/cpu/gd32v/Makefile.dep @@ -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 diff --git a/cpu/gd32v/include/periph_cpu.h b/cpu/gd32v/include/periph_cpu.h index 84aceead84521..9051edb42d482 100644 --- a/cpu/gd32v/include/periph_cpu.h +++ b/cpu/gd32v/include/periph_cpu.h @@ -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; /** diff --git a/cpu/gd32v/periph/i2c.c b/cpu/gd32v/periph/i2c.c index dc98f92f3a19f..9917f225fc535 100644 --- a/cpu/gd32v/periph/i2c.c +++ b/cpu/gd32v/periph/i2c.c @@ -1,9 +1,5 @@ /* - * Copyright (C) 2017 Kaspar Schleiser - * 2014 FU Berlin - * 2018 Inria - * 2018 HAW Hamburg - * 2023 Gunar Schorcht + * 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 - * @author Hauke Petersen - * @author Thomas Eichinger - * @author Kaspar Schleiser - * @author Toon Stegen - * @author Vincent Dupont - * @author Víctor Ariño - * @author Alexandre Abadie - * @author Kevin Weiss * @author Gunar Schorcht * * @} @@ -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 _start(I2C_Type *dev, uint8_t address_byte, uint8_t flags, - size_t length); -static int _stop(I2C_Type *dev); - -static int _is_sr1_mask_set(I2C_Type *i2c, uint32_t mask, uint8_t flags); -static inline int _wait_for_bus(I2C_Type *i2c); - -/** - * @brief Array holding one pre-initialized mutex for each I2C device - */ -static mutex_t locks[I2C_NUMOF]; +static int _wait_for_bus(i2c_t dev); +static void _wait_for_irq(i2c_t dev); +static void _irq_handler(unsigned irqn); + +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; + +typedef struct { + mutex_t dev_lock; + mutex_t irq_lock; + _i2c_state_t state; + void (*isr)(unsigned irqn); +} _i2c_dev_t; + +_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); +} + +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; + } } -int i2c_read_bytes(i2c_t dev, uint16_t address, void *data, size_t length, +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; + } + 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; } - return ret; + /* 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. */ } - 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); - } - /* STOP must also be sent before final read */ - ret = _stop(i2c); - if (ret < 0) { - return ret; - } + /* 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); } - /* Wait for reception to complete */ - ret = _is_sr1_mask_set(i2c, I2C_STAT0_RBNE_Msk, flags); - if (ret < 0) { - return ret; + 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; } - ((uint8_t*)data)[i] = i2c->DATA; - } - DEBUG("[i2c] read_bytes: Finished reading bytes\n"); - if (flags & I2C_NOSTOP) { - return 0; } - return _wait_for_bus(i2c); -} -int i2c_write_bytes(i2c_t dev, uint16_t address, const void *data, - size_t length, uint8_t flags) -{ - assert(dev < I2C_NUMOF); + /* read STAT0 followed by reading STAT1 to clear the ADDSEND flag */ + i2c->STAT0; + i2c->STAT1; - int ret; + /* read data */ + uint8_t *buffer = (uint8_t*)data; - 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; + 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; + } + 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; + } } - DEBUG("[i2c] write_bytes: TX is free so send byte\n"); - i2c->DATA = ((uint8_t*)data)[i]; - } - /* 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; - } - if (flags & I2C_NOSTOP) { - return 0; - } - else { - /* End transmission */ - DEBUG("[i2c] write_bytes: Ending transmission\n"); - ret = _stop(i2c); - if (ret < 0) { - return ret; + res = _i2c_read_cmd(dev, buffer + i); + if (res != 0) { + _i2c_stop_cmd_and_wait(dev); + return res; } - DEBUG("[i2c] write_bytes: STOP condition was send out\n"); } - return _wait_for_bus(i2c); + 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; } -static int _start(I2C_Type *i2c, uint8_t address_byte, uint8_t flags, - size_t length) +int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, + size_t length, uint8_t flags) { - assert(i2c != NULL); + assert(dev < I2C_NUMOF); + assert(data); + assert(length); - if ((flags & I2C_ADDR10) || - (!(i2c->STAT1 & I2C_STAT1_I2CBSY_Msk) && (flags & I2C_NOSTART))) { - return -EOPNOTSUPP; - } + DEBUG("i2c_write_bytes, dev=%d len=%d flags=%02x\n", dev, length, flags); - /* Clear flags */ - i2c->STAT0 &= ~ERROR_FLAGS; + int res; + _i2c_dev[dev].state = I2C_OK; + + /* if I2C_NOSTART is not set, send START condition and ADDR */ 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; + res = _i2c_start_cmd(dev); + if (res != 0) { + _i2c_stop_cmd_and_wait(dev); + return res; } - 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); + /* 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); } - if (!(flags & I2C_NOSTOP) && length == 1) { - i2c->CTL0 &= ~(I2C_CTL0_ACKEN_Msk); + else { + /* send ADDR without read flag */ + uint8_t addr7 = addr << 1; + res = _i2c_addr_cmd(dev, &addr7, 1); } - /* 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; + 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; - /* 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) { } + /* send data */ + uint8_t *buffer = (uint8_t*)data; - if (!(flags & I2C_NOSTOP) && length == 1) { - /* Stop must also be sent before final read */ - i2c->CTL0 |= (I2C_CTL0_STOP_Msk); + 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; } - DEBUG("[i2c] start: Address generated\n"); - return ret; } + + 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; } -static int _is_sr1_mask_set(I2C_Type *i2c, uint32_t mask, uint8_t flags) +void _i2c_transfer_timeout(void *arg) { - 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; - } + 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); } - /* - * 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) +#define TICK_TIMEOUT 0x00000fffUL + +static inline int _wait_for_bus(i2c_t dev) { - /* 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--) {} + /* 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; } - 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) +static void _irq_handler(unsigned irqn) { - uint16_t tick = TICK_TIMEOUT; - while ((i2c->STAT1 & I2C_STAT1_I2CBSY_Msk) && tick--) {} - if (!tick) { - return -ETIMEDOUT; + 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); + + 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; + } + } + 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); } - return 0; + else if (state & I2C_STAT0_SBSEND_Msk) { + DEBUG("START sent, dev=%d\n", dev); + _i2c_dev[dev].state = I2C_START_SENT; + } + 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 { + _i2c_dev[dev].state = I2C_OK; + } + + mutex_unlock(&_i2c_dev[dev].irq_lock); }