From 5355a2435c4023a0575ce7f2375369670c1bd62a Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Thu, 26 Jan 2023 18:38:56 +0100 Subject: [PATCH] cpu/dg32v: add periph_i2c support --- boards/common/gd32v/include/cfg_i2c_default.h | 61 +++ cpu/gd32v/include/periph_cpu.h | 17 +- cpu/gd32v/include/vendor/gd32vf103_periph.h | 4 +- cpu/gd32v/periph/i2c.c | 415 ++++++++++++++++++ 4 files changed, 489 insertions(+), 8 deletions(-) create mode 100644 boards/common/gd32v/include/cfg_i2c_default.h create mode 100644 cpu/gd32v/periph/i2c.c diff --git a/boards/common/gd32v/include/cfg_i2c_default.h b/boards/common/gd32v/include/cfg_i2c_default.h new file mode 100644 index 0000000000..8b3e60f921 --- /dev/null +++ b/boards/common/gd32v/include/cfg_i2c_default.h @@ -0,0 +1,61 @@ +/* + * 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 + * directory for more details. + */ + +/** + * @ingroup boards_common_gd32v + * @{ + * + * @file + * @brief Default I2C configuration for GD32VF103 boards + * + * @author Gunar Schorcht + */ + +#ifndef CFG_I2C_DEFAULT_H +#define CFG_I2C_DEFAULT_H + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name I2C configuration + * + * @note This board may require external pullup resistors for i2c operation. + * @{ + */ +static const i2c_conf_t i2c_config[] = { + { + .dev = I2C0, + .speed = I2C_SPEED_NORMAL, + .scl_pin = GPIO_PIN(PORT_B, 6), + .sda_pin = GPIO_PIN(PORT_B, 7), + .rcu_mask = RCU_APB1EN_I2C0EN_Msk, + .irqn = I2C0_EV_IRQn, + }, + { + .dev = I2C1, + .speed = I2C_SPEED_NORMAL, + .scl_pin = GPIO_PIN(PORT_B, 10), + .sda_pin = GPIO_PIN(PORT_B, 11), + .rcu_mask = RCU_APB1EN_I2C1EN_Msk, + .irqn = I2C1_EV_IRQn, + } +}; + +#define I2C_NUMOF ARRAY_SIZE(i2c_config) +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* CFG_I2C_DEFAULT_H */ +/** @} */ diff --git a/cpu/gd32v/include/periph_cpu.h b/cpu/gd32v/include/periph_cpu.h index c8d59a5d92..1effb186c7 100644 --- a/cpu/gd32v/include/periph_cpu.h +++ b/cpu/gd32v/include/periph_cpu.h @@ -24,6 +24,7 @@ #include "cpu.h" #include "clic.h" #include "kernel_defines.h" +#include "macros/units.h" #ifdef __cplusplus extern "C" { @@ -243,8 +244,10 @@ typedef struct { */ #define HAVE_I2C_SPEED_T typedef enum { - I2C_SPEED_NORMAL, /**< normal mode: ~100kbit/s */ - I2C_SPEED_FAST, /**< fast mode: ~400kbit/s */ + I2C_SPEED_LOW = KHZ(10), /**< low speed mode: ~10kit/s */ + I2C_SPEED_NORMAL = KHZ(100), /**< normal mode: ~100kbit/s */ + I2C_SPEED_FAST = KHZ(400), /**< fast mode: ~400kbit/s */ + I2C_SPEED_FAST_PLUS = MHZ(1), /**< fast plus mode: ~1Mbit/s */ } i2c_speed_t; /** @} */ #endif /* ndef DOXYGEN */ @@ -253,10 +256,12 @@ typedef enum { * @brief I2C configuration options */ typedef struct { - uint32_t addr; /**< device base address */ - gpio_t scl; /**< SCL pin */ - gpio_t sda; /**< SDA pin */ - i2c_speed_t speed; /**< I2C speed */ + I2C_Type *dev; /**< i2c device */ + i2c_speed_t speed; /**< i2c bus speed */ + 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 */ } i2c_conf_t; /** diff --git a/cpu/gd32v/include/vendor/gd32vf103_periph.h b/cpu/gd32v/include/vendor/gd32vf103_periph.h index 2b7727bb98..70cb840f56 100644 --- a/cpu/gd32v/include/vendor/gd32vf103_periph.h +++ b/cpu/gd32v/include/vendor/gd32vf103_periph.h @@ -11549,8 +11549,8 @@ typedef struct { /*!< (@ 0x40002C00) WWDGT Struct //#define GPIOC_BASE 0x40011000UL //#define GPIOD_BASE 0x40011400UL //#define GPIOE_BASE 0x40011800UL -//#define I2C0_BASE 0x40005400UL -//#define I2C1_BASE 0x40005800UL +#define I2C0_BASE 0x40005400UL +#define I2C1_BASE 0x40005800UL //#define ECLIC_BASE 0xD2000000UL //#define PMU_BASE 0x40007000UL //#define RCU_BASE 0x40021000UL diff --git a/cpu/gd32v/periph/i2c.c b/cpu/gd32v/periph/i2c.c new file mode 100644 index 0000000000..dc98f92f3a --- /dev/null +++ b/cpu/gd32v/periph/i2c.c @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * 2014 FU Berlin + * 2018 Inria + * 2018 HAW Hamburg + * 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 + * directory for more details. + */ + +/** + * @ingroup cpu_gd32v + * @ingroup drivers_periph_i2c + * @{ + * + * @file + * @brief Low-level I2C driver implementation + * + * 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 + * + * @} + */ + +#include +#include +#include + +#include "cpu.h" +#include "irq.h" +#include "mutex.h" +#include "pm_layered.h" +#include "panic.h" + +#include "periph/i2c.h" +#include "periph/gpio.h" +#include "periph_conf.h" + +/* Some DEBUG statements may cause delays that alter i2c functionality */ +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define TICK_TIMEOUT (0xFFFF) + +#define I2C_IRQ_PRIO (1) +#define I2C_FLAG_READ (I2C_READ) +#define I2C_FLAG_WRITE (0) + +#define ERROR_FLAGS (I2C_STAT0_AERR_Msk | I2C_STAT0_LOSTARB_Msk | I2C_STAT0_BERR_Msk) + +/* 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]; + +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 + * in HIGH state */ + _deinit_pins(dev); + + periph_clk_en(APB1, i2c_config[dev].rcu_mask); + + _init(dev); + + periph_clk_dis(APB1, i2c_config[dev].rcu_mask); +} + +static void _init_pins(i2c_t dev) +{ + /* This is needed in case the remapped pins are used */ + if (i2c_config[dev].scl_pin == GPIO_PIN(PORT_B, 8) || + i2c_config[dev].sda_pin == GPIO_PIN(PORT_B, 9)) { + /* The remapping periph clock must first be enabled */ + RCU->APB2EN |= RCU_APB2EN_AFEN_Msk; + /* Then the remap can occur */ + AFIO->PCF0 |= AFIO_PCF0_I2C0_REMAP_Msk; + } + gpio_init_af(i2c_config[dev].scl_pin, GPIO_AF_OUT_OD); + gpio_init_af(i2c_config[dev].sda_pin, GPIO_AF_OUT_OD); +} + +static void _init_clk(I2C_Type *i2c, uint32_t speed) +{ + /* disable device and set ACK bit */ + i2c->CTL0 = I2C_CTL0_ACKEN_Msk; + /* configure I2C clock */ + 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; + /* enable device */ + i2c->CTL0 |= I2C_CTL0_I2CEN_Msk; +} + +static void _init(i2c_t dev) +{ + I2C_Type *i2c = i2c_config[dev].dev; + + /* make peripheral soft reset */ + i2c->CTL0 |= I2C_CTL0_SRESET_Msk; + i2c->CTL0 &= ~I2C_CTL0_SRESET_Msk; + + /* configure I2C clock */ + _init_clk(i2c, i2c_config[dev].speed); +} + +static void _deinit_pins(i2c_t dev) +{ + /* GD32V doesn't support GPIO_OD_PU mode, i.e. external pull-ups required */ + gpio_init(i2c_config[dev].scl_pin, GPIO_OD); + gpio_init(i2c_config[dev].sda_pin, GPIO_OD); + gpio_set(i2c_config[dev].scl_pin); + gpio_set(i2c_config[dev].sda_pin); +} + +void i2c_acquire(i2c_t dev) +{ + assert(dev < I2C_NUMOF); + + mutex_lock(&locks[dev]); + + /* block DEEP_SLEEP mode */ + pm_block(GD32V_PM_DEEPSLEEP); + + periph_clk_en(APB1, i2c_config[dev].rcu_mask); + + /* set the alternate function of the pins */ + _init_pins(dev); + + /* enable device */ + i2c_config[dev].dev->CTL0 |= I2C_CTL0_I2CEN_Msk; +} + +void i2c_release(i2c_t dev) +{ + assert(dev < I2C_NUMOF); + + /* disable device */ + i2c_config[dev].dev->CTL0 &= ~(I2C_CTL0_I2CEN_Msk); + + _wait_for_bus(i2c_config[dev].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 + * of current via the pull-up resistors, the used GPIOs are set back to + * GPIO_OD mode and HIGH. */ + _deinit_pins(dev); + + periph_clk_dis(APB1, i2c_config[dev].rcu_mask); + + /* unblock DEEP_SLEEP mode */ + pm_unblock(GD32V_PM_DEEPSLEEP); + + mutex_unlock(&locks[dev]); +} + +int i2c_read_bytes(i2c_t dev, uint16_t address, void *data, size_t length, + uint8_t flags) +{ + assert(dev < I2C_NUMOF); + + 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 + * 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). + */ + if (((i2c->STAT1 & (I2C_STAT1_I2CBSY_Msk | I2C_STAT1_TR_Msk)) == I2C_STAT1_I2CBSY_Msk) && + !(flags & I2C_NOSTART)) { + return -EOPNOTSUPP; + } + + int ret = _start(i2c, (address << 1) | I2C_FLAG_READ, flags, length); + if (ret < 0) { + if (ret == -ETIMEDOUT) { + _init(dev); + } + return ret; + } + + 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; + } + } + /* Wait for reception to complete */ + ret = _is_sr1_mask_set(i2c, I2C_STAT0_RBNE_Msk, flags); + if (ret < 0) { + return ret; + } + ((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); + + int ret; + + 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; + } + 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; + } + DEBUG("[i2c] write_bytes: STOP condition was send out\n"); + } + + 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; +}