From e0f99d93acbd488e4b8c0b0eb80a1dafd3ea580b Mon Sep 17 00:00:00 2001 From: Sergey Nikitenko Date: Sun, 8 Jan 2023 20:34:08 +0300 Subject: [PATCH] stm32wb: add i2c driver --- arch/arm/src/stm32wb/Kconfig | 13 + arch/arm/src/stm32wb/Make.defs | 4 +- arch/arm/src/stm32wb/hardware/stm32wb_i2c.h | 220 ++ arch/arm/src/stm32wb/stm32wb_i2c.c | 2652 +++++++++++++++++++ arch/arm/src/stm32wb/stm32wb_i2c.h | 89 + 5 files changed, 2976 insertions(+), 2 deletions(-) create mode 100644 arch/arm/src/stm32wb/hardware/stm32wb_i2c.h create mode 100644 arch/arm/src/stm32wb/stm32wb_i2c.c create mode 100644 arch/arm/src/stm32wb/stm32wb_i2c.h diff --git a/arch/arm/src/stm32wb/Kconfig b/arch/arm/src/stm32wb/Kconfig index 93c3a80134..36da938c9e 100644 --- a/arch/arm/src/stm32wb/Kconfig +++ b/arch/arm/src/stm32wb/Kconfig @@ -551,6 +551,19 @@ config STM32WB_LPUART1 select ARCH_HAVE_LPUART1 select STM32WB_USART +config STM32WB_I2C1 + bool "I2C1" + default n + select I2C + select STM32WB_I2C + +config STM32WB_I2C3 + bool "I2C3" + default n + depends on STM32WB_HAVE_I2C3 + select I2C + select STM32WB_I2C + comment "APB2 Peripherals" config STM32WB_SYSCFG diff --git a/arch/arm/src/stm32wb/Make.defs b/arch/arm/src/stm32wb/Make.defs index 886776d368..f6cc5a77bd 100644 --- a/arch/arm/src/stm32wb/Make.defs +++ b/arch/arm/src/stm32wb/Make.defs @@ -29,10 +29,10 @@ include armv7-m/Make.defs CHIP_CSRCS = stm32wb_allocateheap.c stm32wb_exti_gpio.c stm32wb_gpio.c CHIP_CSRCS += stm32wb_irq.c stm32wb_lowputc.c stm32wb_rcc.c stm32wb_spi.c -CHIP_CSRCS += stm32wb_serial.c stm32wb_start.c stm32wb_waste.c stm32wb_uid.c +CHIP_CSRCS += stm32wb_serial.c stm32wb_i2c.c stm32wb_start.c stm32wb_waste.c CHIP_CSRCS += stm32wb_rcc_lse.c stm32wb_rcc_lsi.c CHIP_CSRCS += stm32wb_pwr.c stm32wb_tim.c -CHIP_CSRCS += stm32wb_flash.c +CHIP_CSRCS += stm32wb_flash.c stm32wb_uid.c ifneq ($(CONFIG_ARCH_IDLE_CUSTOM),y) CHIP_CSRCS += stm32wb_idle.c diff --git a/arch/arm/src/stm32wb/hardware/stm32wb_i2c.h b/arch/arm/src/stm32wb/hardware/stm32wb_i2c.h new file mode 100644 index 0000000000..d4f6a53b6b --- /dev/null +++ b/arch/arm/src/stm32wb/hardware/stm32wb_i2c.h @@ -0,0 +1,220 @@ +/**************************************************************************** + * arch/arm/src/stm32wb/hardware/stm32wb_i2c.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32WB_HARDWARE_STM32WB_I2C_H +#define __ARCH_ARM_SRC_STM32WB_HARDWARE_STM32WB_I2C_H + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Register Offsets *********************************************************/ + +#define STM32WB_I2C_CR1_OFFSET 0x0000 /* Control register 1 (32-bit) */ +#define STM32WB_I2C_CR2_OFFSET 0x0004 /* Control register 2 (32-bit) */ +#define STM32WB_I2C_OAR1_OFFSET 0x0008 /* Own address register 1 (16-bit) */ +#define STM32WB_I2C_OAR2_OFFSET 0x000c /* Own address register 2 (16-bit) */ +#define STM32WB_I2C_TIMINGR_OFFSET 0x0010 /* Timing register */ +#define STM32WB_I2C_TIMEOUTR_OFFSET 0x0014 /* Timeout register */ +#define STM32WB_I2C_ISR_OFFSET 0x0018 /* Interrupt and Status register */ +#define STM32WB_I2C_ICR_OFFSET 0x001c /* Interrupt clear register */ +#define STM32WB_I2C_PECR_OFFSET 0x0020 /* Packet error checking register */ +#define STM32WB_I2C_RXDR_OFFSET 0x0024 /* Receive data register */ +#define STM32WB_I2C_TXDR_OFFSET 0x0028 /* Transmit data register */ + +/* Register Addresses *******************************************************/ + +#define STM32WB_I2C1_CR1 (STM32WB_I2C1_BASE + STM32WB_I2C_CR1_OFFSET) +#define STM32WB_I2C1_CR2 (STM32WB_I2C1_BASE + STM32WB_I2C_CR2_OFFSET) +#define STM32WB_I2C1_OAR1 (STM32WB_I2C1_BASE + STM32WB_I2C_OAR1_OFFSET) +#define STM32WB_I2C1_OAR2 (STM32WB_I2C1_BASE + STM32WB_I2C_OAR2_OFFSET) +#define STM32WB_I2C1_TIMINGR (STM32WB_I2C1_BASE + STM32WB_I2C_TIMINGR_OFFSET) +#define STM32WB_I2C1_TIMEOUTR (STM32WB_I2C1_BASE + STM32WB_I2C_TIMEOUTR_OFFSET) +#define STM32WB_I2C1_ISR (STM32WB_I2C1_BASE + STM32WB_I2C_ISR_OFFSET) +#define STM32WB_I2C1_ICR (STM32WB_I2C1_BASE + STM32WB_I2C_ICR_OFFSET) +#define STM32WB_I2C1_PECR (STM32WB_I2C1_BASE + STM32WB_I2C_PECR_OFFSET) +#define STM32WB_I2C1_RXDR (STM32WB_I2C1_BASE + STM32WB_I2C_RXDR_OFFSET) +#define STM32WB_I2C1_TXDR (STM32WB_I2C1_BASE + STM32WB_I2C_TXDR_OFFSET) + +#ifdef CONFIG_STM32WB_HAVE_I2C3 +# define STM32WB_I2C3_CR1 (STM32WB_I2C3_BASE + STM32WB_I2C_CR1_OFFSET) +# define STM32WB_I2C3_CR2 (STM32WB_I2C3_BASE + STM32WB_I2C_CR2_OFFSET) +# define STM32WB_I2C3_OAR1 (STM32WB_I2C3_BASE + STM32WB_I2C_OAR1_OFFSET) +# define STM32WB_I2C3_OAR2 (STM32WB_I2C3_BASE + STM32WB_I2C_OAR2_OFFSET) +# define STM32WB_I2C3_TIMINGR (STM32WB_I2C3_BASE + STM32WB_I2C_TIMINGR_OFFSET) +# define STM32WB_I2C3_TIMEOUTR (STM32WB_I2C3_BASE + STM32WB_I2C_TIMEOUTR_OFFSET) +# define STM32WB_I2C3_ISR (STM32WB_I2C3_BASE + STM32WB_I2C_ISR_OFFSET) +# define STM32WB_I2C3_ICR (STM32WB_I2C3_BASE + STM32WB_I2C_ICR_OFFSET) +# define STM32WB_I2C3_PECR (STM32WB_I2C3_BASE + STM32WB_I2C_PECR_OFFSET) +# define STM32WB_I2C3_RXDR (STM32WB_I2C3_BASE + STM32WB_I2C_RXDR_OFFSET) +# define STM32WB_I2C3_TXDR (STM32WB_I2C3_BASE + STM32WB_I2C_TXDR_OFFSET) +#endif + +/* Register Bitfield Definitions ********************************************/ + +/* Control register 1 */ + +#define I2C_CR1_PE (1 << 0) /* Bit 0: Peripheral Enable */ +#define I2C_CR1_TXIE (1 << 1) /* Bit 1: TX Interrupt enable */ +#define I2C_CR1_RXIE (1 << 2) /* Bit 2: RX Interrupt enable */ +#define I2C_CR1_ADDRIE (1 << 3) /* Bit 3: Address match interrupt enable (slave) */ +#define I2C_CR1_NACKIE (1 << 4) /* Bit 4: Not acknowledge received interrupt enable */ +#define I2C_CR1_STOPIE (1 << 5) /* Bit 5: STOP detection interrupt enable */ +#define I2C_CR1_TCIE (1 << 6) /* Bit 6: Transfer Complete interrupt enable */ +#define I2C_CR1_ERRIE (1 << 7) /* Bit 7: Error interrupts enable */ +#define I2C_CR1_DNF_SHIFT (8) /* Bits 8-11: Digital noise filter */ +#define I2C_CR1_DNF_MASK (0xf << I2C_CR1_DNF_SHIFT) +# define I2C_CR1_DNF_DISABLE (0 << I2C_CR1_DNF_SHIFT) +# define I2C_CR1_DNF(n) ((n) << I2C_CR1_DNF_SHIFT) /* Up to n * Ti2cclk, n=1..15 */ + +#define I2C_CR1_ANFOFF (1 << 12) /* Bit 12: Analog noise filter OFF */ +#define I2C_CR1_TXDMAEN (1 << 14) /* Bit 14: DMA transmission requests enable */ +#define I2C_CR1_RXDMAEN (1 << 15) /* Bit 15: DMA reception requests enable */ +#define I2C_CR1_SBC (1 << 16) /* Bit 16: Slave byte control */ +#define I2C_CR1_NOSTRETCH (1 << 17) /* Bit 17: Clock stretching disable */ +#define I2C_CR1_WUPEN (1 << 18) /* Bit 18: Wakeup from STOP enable */ +#define I2C_CR1_GCEN (1 << 19) /* Bit 19: General call enable */ +#define I2C_CR1_SMBHEN (1 << 20) /* Bit 20: SMBus Host address enable */ +#define I2C_CR1_SMBDEN (1 << 21) /* Bit 21: SMBus Device Default address enable */ +#define I2C_CR1_ALERTEN (1 << 22) /* Bit 22: SMBus alert enable */ +#define I2C_CR1_PECEN (1 << 23) /* Bit 23: PEC enable */ + +/* Control register 2 */ + +#define I2C_CR2_SADD10_SHIFT (0) /* Bits 0-9: Slave 10-bit address (master) */ +#define I2C_CR2_SADD10_MASK (0x3ff << I2C_CR2_SADD10_SHIFT) +#define I2C_CR2_SADD7_SHIFT (1) /* Bits 1-7: Slave 7-bit address (master) */ +#define I2C_CR2_SADD7_MASK (0x7f << I2C_CR2_SADD7_SHIFT) +#define I2C_CR2_RD_WRN (1 << 10) /* Bit 10: Transfer direction (master) */ +#define I2C_CR2_ADD10 (1 << 11) /* Bit 11: 10-bit addressing mode (master) */ +#define I2C_CR2_HEAD10R (1 << 12) /* Bit 12: 10-bit address header only read direction (master) */ +#define I2C_CR2_START (1 << 13) /* Bit 13: Start generation */ +#define I2C_CR2_STOP (1 << 14) /* Bit 14: Stop generation (master) */ +#define I2C_CR2_NACK (1 << 15) /* Bit 15: NACK generation (slave) */ +#define I2C_CR2_NBYTES_SHIFT (16) /* Bits 16-23: Number of bytes */ +#define I2C_CR2_NBYTES_MASK (0xff << I2C_CR2_NBYTES_SHIFT) +#define I2C_CR2_RELOAD (1 << 24) /* Bit 24: NBYTES reload mode */ +#define I2C_CR2_AUTOEND (1 << 25) /* Bit 25: Automatic end mode (master) */ +#define I2C_CR2_PECBYTE (1 << 26) /* Bit 26: Packet error checking byte */ + +/* Own address register 1 */ + +#define I2C_OAR1_OA1_10_SHIFT (0) /* Bits 0-9: 10-bit interface address */ +#define I2C_OAR1_OA1_10_MASK (0x3ff << I2C_OAR1_OA1_10_SHIFT) +#define I2C_OAR1_OA1_7_SHIFT (1) /* Bits 1-7: 7-bit interface address */ +#define I2C_OAR1_OA1_7_MASK (0x7f << I2C_OAR1_OA1_7_SHIFT) +#define I2C_OAR1_OA1MODE (1 << 10) /* Bit 10: Own Address 1 10-bit mode */ +#define I2C_OAR1_OA1EN (1 << 15) /* Bit 15: Own Address 1 enable */ + +/* Own address register 2 */ + +#define I2C_OAR2_OA2_SHIFT (1) /* Bits 1-7: 7-bit interface address */ +#define I2C_OAR2_OA2_MASK (0x7f << I2C_OAR2_OA2_SHIFT) +#define I2C_OAR2_OA2MSK_SHIFT (8) /* Bits 8-10: Own Address 2 masks */ +#define I2C_OAR2_OA2MSK_MASK (0x7 << I2C_OAR2_OA2MSK_SHIFT) +# define I2C_OAR2_OA2MSK_NONE (0x0 << I2C_OAR2_OA2MSK_SHIFT) /* No mask */ +# define I2C_OAR2_OA2MSK_2_7 (0x1 << I2C_OAR2_OA2MSK_SHIFT) /* Only OA2[7:2] are compared */ +# define I2C_OAR2_OA2MSK_3_7 (0x2 << I2C_OAR2_OA2MSK_SHIFT) /* Only OA2[7:3] are compared */ +# define I2C_OAR2_OA2MSK_4_7 (0x3 << I2C_OAR2_OA2MSK_SHIFT) /* Only OA2[7:4] are compared */ +# define I2C_OAR2_OA2MSK_5_7 (0x4 << I2C_OAR2_OA2MSK_SHIFT) /* Only OA2[7:5] are compared */ +# define I2C_OAR2_OA2MSK_6_7 (0x5 << I2C_OAR2_OA2MSK_SHIFT) /* Only OA2[7:6] are compared */ +# define I2C_OAR2_OA2MSK_7 (0x6 << I2C_OAR2_OA2MSK_SHIFT) /* Only OA2[7] is compared */ +# define I2C_OAR2_OA2MSK_ALL (0x7 << I2C_OAR2_OA2MSK_SHIFT) /* All 7-bit addresses acknowledged */ + +#define I2C_OAR2_OA2EN (1 << 15) /* Bit 15: Own Address 2 enable */ + +/* Timing register */ + +#define I2C_TIMINGR_SCLL_SHIFT (0) /* Bits 0-7: SCL low period (master) */ +#define I2C_TIMINGR_SCLL_MASK (0xff << I2C_TIMINGR_SCLL_SHIFT) +# define I2C_TIMINGR_SCLL(n) (((n)-1) << I2C_TIMINGR_SCLL_SHIFT) /* tSCLL = n x tPRESC */ + +#define I2C_TIMINGR_SCLH_SHIFT (8) /* Bits 8-15: SCL high period (master) */ +#define I2C_TIMINGR_SCLH_MASK (0xff << I2C_TIMINGR_SCLH_SHIFT) +# define I2C_TIMINGR_SCLH(n) (((n)-1) << I2C_TIMINGR_SCLH_SHIFT) /* tSCLH = n x tPRESC */ + +#define I2C_TIMINGR_SDADEL_SHIFT (16) /* Bits 16-19: Data hold time */ +#define I2C_TIMINGR_SDADEL_MASK (0xf << I2C_TIMINGR_SDADEL_SHIFT) +# define I2C_TIMINGR_SDADEL(n) ((n) << I2C_TIMINGR_SDADEL_SHIFT) /* tSDADEL= n x tPRESC */ + +#define I2C_TIMINGR_SCLDEL_SHIFT (20) /* Bits 20-23: Data setup time */ +#define I2C_TIMINGR_SCLDEL_MASK (0xf << I2C_TIMINGR_SCLDEL_SHIFT) +# define I2C_TIMINGR_SCLDEL(n) (((n)-1) << I2C_TIMINGR_SCLDEL_SHIFT) /* tSCLDEL = n x tPRESC */ + +#define I2C_TIMINGR_PRESC_SHIFT (28) /* Bits 28-31: Timing prescaler */ +#define I2C_TIMINGR_PRESC_MASK (15 << I2C_TIMINGR_PRESC_SHIFT) +# define I2C_TIMINGR_PRESC(n) (((n)-1) << I2C_TIMINGR_PRESC_SHIFT) /* tPRESC = n x tI2CCLK */ + +/* Timeout register */ + +#define I2C_TIMEOUTR_A_SHIFT (0) /* Bits 0-11: Bus Timeout A */ +#define I2C_TIMEOUTR_A_MASK (0xfff << I2C_TIMEOUTR_A_SHIFT) +# define I2C_TIMEOUTR_A(n) ((n) << I2C_TIMEOUTR_A_SHIFT) +#define I2C_TIMEOUTR_TIDLE (1 << 12) /* Bit 12: Idle clock timeout detection */ +#define I2C_TIMEOUTR_TIMOUTEN (1 << 15) /* Bit 15: Clock timeout enable */ +#define I2C_TIMEOUTR_B_SHIFT (16) /* Bits 16-27: Bus Timeout B */ +#define I2C_TIMEOUTR_B_MASK (0xfff << I2C_TIMEOUTR_B_SHIFT) +# define I2C_TIMEOUTR_B(n) ((n) << I2C_TIMEOUTR_B_SHIFT) +#define I2C_TIMEOUTR_TEXTEN (1 << 31) /* Bits 31: Extended clock timeout enable */ + +/* Interrupt and Status register and interrupt clear register */ + +/* Common interrupt bits */ + +#define I2C_INT_ADDR (1 << 3) /* Bit 3: Address matched (slave) */ +#define I2C_INT_NACK (1 << 4) /* Bit 4: Not Acknowledge received flag */ +#define I2C_INT_STOP (1 << 5) /* Bit 5: Stop detection flag */ +#define I2C_INT_BERR (1 << 8) /* Bit 8: Bus error */ +#define I2C_INT_ARLO (1 << 9) /* Bit 9: Arbitration lost */ +#define I2C_INT_OVR (1 << 10) /* Bit 10: Overrun/Underrun (slave) */ +#define I2C_INT_PECERR (1 << 11) /* Bit 11: PEC Error in reception */ +#define I2C_INT_TIMEOUT (1 << 12) /* Bit 12: Timeout or tLOW detection flag */ +#define I2C_INT_ALERT (1 << 13) /* Bit 13: SMBus alert */ + +/* Fields unique to the Interrupt and Status register */ + +#define I2C_ISR_TXE (1 << 0) /* Bit 0: Transmit data register empty (transmitters) */ +#define I2C_ISR_TXIS (1 << 1) /* Bit 1: Transmit interrupt status (transmitters) */ +#define I2C_ISR_RXNE (1 << 2) /* Bit 2: Receive data register not empty (receivers) */ +#define I2C_ISR_TC (1 << 6) /* Bit 6: Transfer Complete (master) */ +#define I2C_ISR_TCR (1 << 7) /* Bit 7: Transfer Complete Reload */ +#define I2C_ISR_BUSY (1 << 15) /* Bit 15: Bus busy */ +#define I2C_ISR_DIR (1 << 16) /* Bit 16: Transfer direction (slave) */ +#define I2C_ISR_ADDCODE_SHIFT (17) /* Bits 17-23: Address match code (slave) */ +#define I2C_ISR_ADDCODE_MASK (0x7f << I2C_ISR_ADDCODE_SHIFT) + +#define I2C_ISR_ERRORMASK (I2C_INT_BERR | I2C_INT_ARLO | I2C_INT_OVR | I2C_INT_PECERR | I2C_INT_TIMEOUT) + +#define I2C_ICR_CLEARMASK (I2C_INT_ADDR | I2C_INT_NACK | I2C_INT_STOP | I2C_INT_BERR | I2C_INT_ARLO \ + | I2C_INT_OVR | I2C_INT_PECERR | I2C_INT_TIMEOUT | I2C_INT_ALERT) + +/* Packet error checking register */ + +#define I2C_PECR_MASK (0xff) + +/* Receive data register */ + +#define I2C_RXDR_MASK (0xff) + +/* Transmit data register */ + +#define I2C_TXDR_MASK (0xff) + +#endif /* __ARCH_ARM_SRC_STM32WB_HARDWARE_STM32WB_I2C_H */ diff --git a/arch/arm/src/stm32wb/stm32wb_i2c.c b/arch/arm/src/stm32wb/stm32wb_i2c.c new file mode 100644 index 0000000000..196db4234e --- /dev/null +++ b/arch/arm/src/stm32wb/stm32wb_i2c.c @@ -0,0 +1,2652 @@ +/**************************************************************************** + * arch/arm/src/stm32wb/stm32wb_i2c.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* -------------------------------------------------------------------------- + * + * STM32 WB I2C Driver based on L4 I2C Driver: + * + * STM32 WB and L4 have identical I2C hardware, differences are only in + * clocking. + * + * Supports: + * - Master operation: + * Standard-mode (up to 100 kHz) + * Fast-mode (up to 400 kHz) + * Fast-mode+ (up to 1 MHz) + * Clock source selection is based on STM32WB_RCC_CCIPR register + * + * - Multiple instances (shared bus) + * - Interrupt based operation + * - RELOAD support + * - I2C_M_NOSTART support + * + * Test Environment: + * - STM32WB55RG based board + * + * Unsupported, possible future work: + * - Wakeup from Stop mode + * - More effective error reporting to higher layers + * - Slave operation + * - Support of clock source frequencies other than 64MHz + * - Polled operation (code present but untested) + * - SMBus support + * - Multi-master support + * - IPMI + * + * Implementation: + * + * - Device: structure as defined by the nuttx/i2c/i2c_master.h + * + * - Instance: represents each individual access to the I2C driver, obtained + * by the i2c_init(); it extends the Device structure from the + * nuttx/i2c/i2c_master.h; + * Instance points to OPS, to common I2C Hardware private data and + * contains its own private data including frequency, address and mode + * of operation. + * + * - Private: Private data of an I2C Hardware + * + * High Level Functional Description + * + * This driver works with I2C "messages" (struct i2c_msg_s), which carry a + * buffer intended to transfer data to, or store data read from, the I2C bus. + * + * As the hardware can only transmit or receive one byte at a time the basic + * job of the driver (and the ISR specifically) is to process each message in + * the order they are stored in the message list, one byte at a time. When + * no messages are left the ISR exits and returns the result to the caller. + * + * The order of the list of I2C messages provided to the driver is important + * and dependent upon the hardware in use. A typical I2C transaction between + * the MCU as an I2C Master and some other IC as an I2C Slave requires two + * messages that communicate the: + * + * 1) Subaddress (register offset on the slave device) + * 2) Data sent to or read from the device + * + * These messages will typically be one byte in length but may be up to 2^31 + * bytes in length. Incidentally, the maximum length is limited only because + * i2c_msg_s.length is a signed int for some odd reason. + * + * Interrupt mode relies on the following interrupt events: + * + * TXIS - Transmit interrupt + * (data transmitted to bus and acknowledged) + * NACKF - Not Acknowledge Received + * (data transmitted to bus and NOT acknowledged) + * RXNE - Receive interrupt + * (data received from bus) + * TC - Transfer Complete + * (All bytes in message transferred) + * TCR - Transfer Complete (Reload) + * (Current batch of bytes in message transferred) + * + * The driver currently supports Single Master mode only. Slave mode is not + * supported. Additionally, the driver runs in Software End Mode (AUTOEND + * disabled) so the driver is responsible for telling the hardware what to + * do at the end of a transfer. + * + * -------------------------------------------------------------------------- + * + * Configuration: + * + * To use this driver, enable the following configuration variable: + * + * CONFIG_STM32WB_I2C + * + * and one or more interfaces: + * + * CONFIG_STM32WB_I2C1 + * CONFIG_STM32WB_I2C3 + * + * To configure the ISR timeout using fixed values + * (CONFIG_STM32WB_I2C_DYNTIMEO=n): + * + * CONFIG_STM32WB_I2CTIMEOSEC (Timeout in seconds) + * CONFIG_STM32WB_I2CTIMEOMS (Timeout in milliseconds) + * CONFIG_STM32WB_I2CTIMEOTICKS (Timeout in ticks) + * + * To configure the ISR timeout using dynamic values + * (CONFIG_STM32WB_I2C_DYNTIMEO=y): + * + * CONFIG_STM32WB_I2C_DYNTIMEO_USECPERBYTE + * (Timeout in microseconds per byte) + * CONFIG_STM32WB_I2C_DYNTIMEO_STARTSTOP + * (Timeout for start/stop in milliseconds) + * + * Debugging output enabled with: + * + * CONFIG_DEBUG_FEATURES and CONFIG_DEBUG_I2C_{ERROR|WARN|INFO} + * + * ISR Debugging output may be enabled with: + * + * CONFIG_DEBUG_FEATURES and CONFIG_DEBUG_I2C_INFO + * + * -------------------------------------------------------------------------- + * + * References (STM32WB): + * + * RM0434: + * ST STM32WB55xx and STM32WB35xx Reference Manual + * + * RM0471: + * ST STM32WB50CG and STM32WB30CE Reference Manual + * + * -------------------------------------------------------------------------- + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "arm_internal.h" +#include "stm32wb_gpio.h" +#include "stm32wb_rcc.h" +#include "stm32wb_i2c.h" +#include "stm32wb_waste.h" + +/* At least one I2C peripheral must be enabled */ + +#if defined(CONFIG_STM32WB_I2C1) || defined(CONFIG_STM32WB_I2C3) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* CONFIG_I2C_POLLED may be set so that I2C interrupts will not be used. + * Instead, CPU-intensive polling will be used. + */ + +/* Interrupt wait timeout in seconds and milliseconds */ + +#if !defined(CONFIG_STM32WB_I2CTIMEOSEC) && !defined(CONFIG_STM32WB_I2CTIMEOMS) +# define CONFIG_STM32WB_I2CTIMEOSEC 0 +# define CONFIG_STM32WB_I2CTIMEOMS 500 /* Default is 500 milliseconds */ +# warning "Using Default 500 Ms Timeout" +#elif !defined(CONFIG_STM32WB_I2CTIMEOSEC) +# define CONFIG_STM32WB_I2CTIMEOSEC 0 /* User provided milliseconds */ +#elif !defined(CONFIG_STM32WB_I2CTIMEOMS) +# define CONFIG_STM32WB_I2CTIMEOMS 0 /* User provided seconds */ +#endif + +/* Interrupt wait time timeout in system timer ticks */ + +#ifndef CONFIG_STM32WB_I2CTIMEOTICKS +# define CONFIG_STM32WB_I2CTIMEOTICKS \ + (SEC2TICK(CONFIG_STM32WB_I2CTIMEOSEC) + MSEC2TICK(CONFIG_STM32WB_I2CTIMEOMS)) +#endif + +#ifndef CONFIG_STM32WB_I2C_DYNTIMEO_STARTSTOP +# define CONFIG_STM32WB_I2C_DYNTIMEO_STARTSTOP TICK2USEC(CONFIG_STM32WB_I2CTIMEOTICKS) +#endif + +/* Macros to convert an I2C pin to a GPIO output */ + +#define I2C_OUTPUT (GPIO_OUTPUT | GPIO_FLOAT | GPIO_OPENDRAIN | \ + GPIO_SPEED_50MHz | GPIO_OUTPUT_SET) + +#define MKI2C_OUTPUT(p) (((p) & (GPIO_PORT_MASK | GPIO_PIN_MASK)) | I2C_OUTPUT) + +#define I2C_CR1_TXRX (I2C_CR1_RXIE | I2C_CR1_TXIE) +#define I2C_CR1_ALLINTS (I2C_CR1_TXRX | I2C_CR1_TCIE | I2C_CR1_ERRIE) + +/* I2C event tracing + * + * To enable tracing statements which show the details of the state machine + * enable the following configuration variable: + * + * CONFIG_I2C_TRACE + * + * Note: This facility uses syslog, which sends output to the console by + * default. No other debug configuration variables are required. + */ + +#ifndef CONFIG_I2C_TRACE +# define stm32wb_i2c_tracereset(p) +# define stm32wb_i2c_tracenew(p,s) +# define stm32wb_i2c_traceevent(p,e,a) +# define stm32wb_i2c_tracedump(p) +#endif + +#ifndef CONFIG_I2C_NTRACE +# define CONFIG_I2C_NTRACE 32 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Interrupt state */ + +enum stm32wb_intstate_e +{ + INTSTATE_IDLE = 0, /* No I2C activity */ + INTSTATE_WAITING, /* Waiting for completion of interrupt activity */ + INTSTATE_DONE, /* Interrupt activity complete */ +}; + +/* Trace events */ + +enum stm32wb_trace_e +{ + I2CEVENT_NONE = 0, + I2CEVENT_STATE_ERROR, + I2CEVENT_ISR_SHUTDOWN, + I2CEVENT_ISR_CALL, + I2CEVENT_ISR_EMPTY_CALL, + I2CEVENT_POLL_DEV_NOT_RDY, + I2CEVENT_ADDRESS_ACKED, + I2CEVENT_ADDRESS_NACKED, + I2CEVENT_RCVBYTE, + I2CEVENT_READ, + I2CEVENT_READ_ERROR, + I2CEVENT_WRITE, + I2CEVENT_WRITE_TO_DR, + I2CEVENT_WRITE_STOP, + I2CEVENT_WRITE_ERROR, + I2CEVENT_TC_NO_RESTART, + I2CEVENT_STOP +}; + +/* Trace data */ + +struct stm32wb_trace_s +{ + uint32_t status; /* I2C 32-bit SR2|SR1 status */ + uint32_t count; /* Interrupt count when status change */ + enum stm32wb_intstate_e event; /* Last event that occurred with this status */ + uint32_t parm; /* Parameter associated with the event */ + clock_t time; /* First of event or first status */ +}; + +/* I2C Device hardware configuration */ + +struct stm32wb_i2c_config_s +{ + uint32_t base; /* I2C base address */ + uint32_t clk_bit; /* Clock enable bit */ + uint32_t reset_bit; /* Reset bit */ + uint32_t scl_pin; /* GPIO configuration for SCL as SCL */ + uint32_t sda_pin; /* GPIO configuration for SDA as SDA */ +#ifndef CONFIG_I2C_POLLED + uint32_t ev_irq; /* Event IRQ */ + uint32_t er_irq; /* Error IRQ */ +#endif +}; + +/* I2C Device Private Data */ + +struct stm32wb_i2c_priv_s +{ + /* Port configuration */ + + const struct stm32wb_i2c_config_s *config; + + int refs; /* Reference count */ + mutex_t lock; /* Mutual exclusion mutex */ +#ifndef CONFIG_I2C_POLLED + sem_t sem_isr; /* Interrupt wait semaphore */ +#endif + volatile uint8_t intstate; /* Interrupt handshake (see enum stm32wb_intstate_e) */ + + uint8_t msgc; /* Message count */ + struct i2c_msg_s *msgv; /* Message list */ + uint8_t *ptr; /* Current message buffer */ + uint32_t frequency; /* Current I2C frequency */ + int dcnt; /* Current message bytes remaining to transfer */ + uint16_t flags; /* Current message flags */ + bool astart; /* START sent */ + + /* I2C trace support */ + +#ifdef CONFIG_I2C_TRACE + int tndx; /* Trace array index */ + clock_t start_time; /* Time when the trace was started */ + + /* The actual trace data */ + + struct stm32wb_trace_s trace[CONFIG_I2C_NTRACE]; +#endif + + uint32_t status; /* End of transfer SR2|SR1 status */ + +#ifdef CONFIG_PM + struct pm_callback_s pm_cb; /* PM callbacks */ +#endif +}; + +/* I2C Device, Instance */ + +struct stm32wb_i2c_inst_s +{ + const struct i2c_ops_s *ops; /* Standard I2C operations */ + struct stm32wb_i2c_priv_s *priv; /* Common driver private data structure */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static inline +uint16_t stm32wb_i2c_getreg(struct stm32wb_i2c_priv_s *priv, + uint8_t offset); +static inline +void stm32wb_i2c_putreg(struct stm32wb_i2c_priv_s *priv, + uint8_t offset, uint16_t value); +static inline +void stm32wb_i2c_putreg32(struct stm32wb_i2c_priv_s *priv, + uint8_t offset, uint32_t value); +static inline +void stm32wb_i2c_modifyreg32(struct stm32wb_i2c_priv_s *priv, + uint8_t offset, uint32_t clearbits, + uint32_t setbits); +#ifdef CONFIG_STM32WB_I2C_DYNTIMEO +static uint32_t stm32wb_i2c_toticks(int msgc, struct i2c_msg_s *msgs); +#endif /* CONFIG_STM32WB_I2C_DYNTIMEO */ +static inline +int stm32wb_i2c_sem_waitdone(struct stm32wb_i2c_priv_s *priv); +static inline +void stm32wb_i2c_sem_waitstop(struct stm32wb_i2c_priv_s *priv); +#ifdef CONFIG_I2C_TRACE +static void stm32wb_i2c_tracereset(struct stm32wb_i2c_priv_s *priv); +static void stm32wb_i2c_tracenew(struct stm32wb_i2c_priv_s *priv, + uint32_t status); +static void +stm32wb_i2c_traceevent(struct stm32wb_i2c_priv_s *priv, + enum stm32wb_trace_e event, uint32_t parm); +static void stm32wb_i2c_tracedump(struct stm32wb_i2c_priv_s *priv); +#endif /* CONFIG_I2C_TRACE */ +static void stm32wb_i2c_setclock(struct stm32wb_i2c_priv_s *priv, + uint32_t frequency); +static inline +void stm32wb_i2c_sendstart(struct stm32wb_i2c_priv_s *priv); +static inline void stm32wb_i2c_sendstop(struct stm32wb_i2c_priv_s *priv); +static inline +uint32_t stm32wb_i2c_getstatus(struct stm32wb_i2c_priv_s *priv); +static int stm32wb_i2c_isr_process(struct stm32wb_i2c_priv_s *priv); +#ifndef CONFIG_I2C_POLLED +static int stm32wb_i2c_isr(int irq, void *context, void *arg); +#endif +static int stm32wb_i2c_init(struct stm32wb_i2c_priv_s *priv); +static int stm32wb_i2c_deinit(struct stm32wb_i2c_priv_s *priv); + +static int stm32wb_i2c_process(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count); +static int stm32wb_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count); +#ifdef CONFIG_I2C_RESET +static int stm32wb_i2c_reset(struct i2c_master_s *dev); +#endif +#ifdef CONFIG_PM +static int stm32wb_i2c_pm_prepare(struct pm_callback_s *cb, int domain, + enum pm_state_e pmstate); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_STM32WB_I2C1 +static const struct stm32wb_i2c_config_s stm32wb_i2c1_config = +{ + .base = STM32WB_I2C1_BASE, + .clk_bit = RCC_APB1ENR1_I2C1EN, + .reset_bit = RCC_APB1RSTR1_I2C1RST, + .scl_pin = GPIO_I2C1_SCL, + .sda_pin = GPIO_I2C1_SDA, +#ifndef CONFIG_I2C_POLLED + .ev_irq = STM32WB_IRQ_I2C1EV, + .er_irq = STM32WB_IRQ_I2C1ER +#endif +}; + +static struct stm32wb_i2c_priv_s stm32wb_i2c1_priv = +{ + .config = &stm32wb_i2c1_config, + .refs = 0, + .lock = NXMUTEX_INITIALIZER, +#ifndef CONFIG_I2C_POLLED + .sem_isr = SEM_INITIALIZER(0), +#endif + .intstate = INTSTATE_IDLE, + .msgc = 0, + .msgv = NULL, + .ptr = NULL, + .frequency = 0, + .dcnt = 0, + .flags = 0, + .status = 0, +#ifdef CONFIG_PM + .pm_cb.prepare = stm32wb_i2c_pm_prepare, +#endif +}; +#endif + +#ifdef CONFIG_STM32WB_I2C3 +static const struct stm32wb_i2c_config_s stm32wb_i2c3_config = +{ + .base = STM32WB_I2C3_BASE, + .clk_bit = RCC_APB1ENR1_I2C3EN, + .reset_bit = RCC_APB1RSTR1_I2C3RST, + .scl_pin = GPIO_I2C3_SCL, + .sda_pin = GPIO_I2C3_SDA, +#ifndef CONFIG_I2C_POLLED + .ev_irq = STM32WB_IRQ_I2C3EV, + .er_irq = STM32WB_IRQ_I2C3ER +#endif +}; + +static struct stm32wb_i2c_priv_s stm32wb_i2c3_priv = +{ + .config = &stm32wb_i2c3_config, + .refs = 0, + .lock = NXMUTEX_INITIALIZER, +#ifndef CONFIG_I2C_POLLED + .sem_isr = SEM_INITIALIZER(0), +#endif + .intstate = INTSTATE_IDLE, + .msgc = 0, + .msgv = NULL, + .ptr = NULL, + .frequency = 0, + .dcnt = 0, + .flags = 0, + .status = 0, +#ifdef CONFIG_PM + .pm_cb.prepare = stm32wb_i2c_pm_prepare, +#endif +}; +#endif + +/* Device Structures, Instantiation */ + +static const struct i2c_ops_s stm32wb_i2c_ops = +{ + .transfer = stm32wb_i2c_transfer, +#ifdef CONFIG_I2C_RESET + .reset = stm32wb_i2c_reset +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32wb_i2c_getreg + * + * Description: + * Get a 16-bit register value by offset + * + ****************************************************************************/ + +static inline +uint16_t stm32wb_i2c_getreg(struct stm32wb_i2c_priv_s *priv, + uint8_t offset) +{ + return getreg16(priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32wb_i2c_getreg32 + * + * Description: + * Get a 32-bit register value by offset + * + ****************************************************************************/ + +static inline +uint32_t stm32wb_i2c_getreg32(struct stm32wb_i2c_priv_s *priv, + uint8_t offset) +{ + return getreg32(priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32wb_i2c_putreg + * + * Description: + * Put a 16-bit register value by offset + * + ****************************************************************************/ + +static inline void stm32wb_i2c_putreg(struct stm32wb_i2c_priv_s *priv, + uint8_t offset, uint16_t value) +{ + putreg16(value, priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32wb_i2c_putreg32 + * + * Description: + * Put a 32-bit register value by offset + * + ****************************************************************************/ + +static inline void stm32wb_i2c_putreg32(struct stm32wb_i2c_priv_s *priv, + uint8_t offset, uint32_t value) +{ + putreg32(value, priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32wb_i2c_modifyreg32 + * + * Description: + * Modify a 32-bit register value by offset + * + ****************************************************************************/ + +static inline +void stm32wb_i2c_modifyreg32(struct stm32wb_i2c_priv_s *priv, + uint8_t offset, uint32_t clearbits, + uint32_t setbits) +{ + modifyreg32(priv->config->base + offset, clearbits, setbits); +} + +/**************************************************************************** + * Name: stm32wb_i2c_toticks + * + * Description: + * Return a micro-second delay based on the number of bytes left to be + * processed. + * + ****************************************************************************/ + +#ifdef CONFIG_STM32WB_I2C_DYNTIMEO +static uint32_t stm32wb_i2c_toticks(int msgc, struct i2c_msg_s *msgs) +{ + size_t bytecount = 0; + int i; + + /* Count the number of bytes left to process */ + + for (i = 0; i < msgc; i++) + { + bytecount += msgs[i].length; + } + + /* Then return a number of microseconds based on a user provided scaling + * factor. + */ + + return USEC2TICK(CONFIG_STM32WB_I2C_DYNTIMEO_USECPERBYTE * bytecount); +} +#endif + +/**************************************************************************** + * Name: stm32wb_i2c_enableinterrupts + * + * Description: + * Enable I2C interrupts + * + ****************************************************************************/ + +#ifndef CONFIG_I2C_POLLED +static inline +void stm32wb_i2c_enableinterrupts(struct stm32wb_i2c_priv_s *priv) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR1_OFFSET, 0, + (I2C_CR1_TXRX | I2C_CR1_NACKIE)); +} +#endif + +/**************************************************************************** + * Name: stm32wb_i2c_sem_waitdone + * + * Description: + * Wait for a transfer to complete + * + * There are two versions of this function. The first is included when using + * interrupts while the second is used if polling (CONFIG_I2C_POLLED=y). + * + ****************************************************************************/ + +#ifndef CONFIG_I2C_POLLED +static inline +int stm32wb_i2c_sem_waitdone(struct stm32wb_i2c_priv_s *priv) +{ + irqstate_t flags; + int ret; + + flags = enter_critical_section(); + + /* Enable I2C interrupts */ + + /* The TXIE and RXIE interrupts are enabled initially in + * stm32wb_i2c_process. The remainder of the interrupts, including + * error-related, are enabled here. + */ + + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR1_OFFSET, 0, + (I2C_CR1_ALLINTS & ~I2C_CR1_TXRX)); + + /* Signal the interrupt handler that we are waiting */ + + priv->intstate = INTSTATE_WAITING; + do + { + /* Wait until either the transfer is complete or the timeout expires */ + +#ifdef CONFIG_STM32WB_I2C_DYNTIMEO + ret = nxsem_tickwait_uninterruptible(&priv->sem_isr, + stm32wb_i2c_toticks(priv->msgc, priv->msgv)); +#else + ret = nxsem_tickwait_uninterruptible(&priv->sem_isr, + CONFIG_STM32WB_I2CTIMEOTICKS); +#endif + if (ret < 0) + { + /* Break out of the loop on irrecoverable errors. This would + * include timeouts and mystery errors reported by + * nxsem_tickwait_uninterruptible. + */ + + break; + } + } + + /* Loop until the interrupt level transfer is complete. */ + + while (priv->intstate != INTSTATE_DONE); + + /* Set the interrupt state back to IDLE */ + + priv->intstate = INTSTATE_IDLE; + + /* Disable I2C interrupts */ + + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR1_OFFSET, I2C_CR1_ALLINTS, 0); + + leave_critical_section(flags); + return ret; +} +#else +static inline +int stm32wb_i2c_sem_waitdone(struct stm32wb_i2c_priv_s *priv) +{ + clock_t timeout; + clock_t start; + clock_t elapsed; + int ret; + + /* Get the timeout value */ + +#ifdef CONFIG_STM32WB_I2C_DYNTIMEO + timeout = stm32wb_i2c_toticks(priv->msgc, priv->msgv); +#else + timeout = CONFIG_STM32WB_I2CTIMEOTICKS; +#endif + + /* Signal the interrupt handler that we are waiting. NOTE: Interrupts + * are currently disabled but will be temporarily re-enabled below when + * nxsem_tickwait_uninterruptible() sleeps. + */ + + priv->intstate = INTSTATE_WAITING; + start = clock_systime_ticks(); + + do + { + /* Calculate the elapsed time */ + + elapsed = clock_systime_ticks() - start; + + /* Poll by simply calling the timer interrupt handler until it + * reports that it is done. + */ + + stm32wb_i2c_isr_process(priv); + } + + /* Loop until the transfer is complete. */ + + while (priv->intstate != INTSTATE_DONE && elapsed < timeout); + + i2cinfo("intstate: %d elapsed: %ld timeout: %ld status: 0x%08" PRIx32 "\n", + priv->intstate, (long)elapsed, (long)timeout, priv->status); + + /* Set the interrupt state back to IDLE */ + + ret = priv->intstate == INTSTATE_DONE ? OK : -ETIMEDOUT; + priv->intstate = INTSTATE_IDLE; + return ret; +} +#endif + +/**************************************************************************** + * Name: stm32wb_i2c_set_7bit_address + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32wb_i2c_set_7bit_address(struct stm32wb_i2c_priv_s *priv) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, I2C_CR2_SADD7_MASK, + (priv->msgv->addr << I2C_CR2_SADD7_SHIFT) & + I2C_CR2_SADD7_MASK); +} + +/**************************************************************************** + * Name: stm32wb_i2c_set_bytes_to_transfer + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32wb_i2c_set_bytes_to_transfer(struct stm32wb_i2c_priv_s *priv, + uint8_t n_bytes) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, I2C_CR2_NBYTES_MASK, + (n_bytes << I2C_CR2_NBYTES_SHIFT)); +} + +/**************************************************************************** + * Name: stm32wb_i2c_set_write_transfer_dir + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32wb_i2c_set_write_transfer_dir(struct stm32wb_i2c_priv_s *priv) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, I2C_CR2_RD_WRN, 0); +} + +/**************************************************************************** + * Name: stm32wb_i2c_set_read_transfer_dir + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32wb_i2c_set_read_transfer_dir(struct stm32wb_i2c_priv_s *priv) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, + 0, I2C_CR2_RD_WRN); +} + +/**************************************************************************** + * Name: stm32wb_i2c_enable_reload + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32wb_i2c_enable_reload(struct stm32wb_i2c_priv_s *priv) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, + 0, I2C_CR2_RELOAD); +} + +/**************************************************************************** + * Name: stm32wb_i2c_disable_reload + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32wb_i2c_disable_reload(struct stm32wb_i2c_priv_s *priv) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, + I2C_CR2_RELOAD, 0); +} + +/**************************************************************************** + * Name: stm32wb_i2c_sem_waitstop + * + * Description: + * Wait for a STOP to complete + * + ****************************************************************************/ + +static inline +void stm32wb_i2c_sem_waitstop(struct stm32wb_i2c_priv_s *priv) +{ + clock_t start; + clock_t elapsed; + clock_t timeout; + uint32_t cr; + uint32_t sr; + + /* Select a timeout */ + +#ifdef CONFIG_STM32WB_I2C_DYNTIMEO + timeout = USEC2TICK(CONFIG_STM32WB_I2C_DYNTIMEO_STARTSTOP); +#else + timeout = CONFIG_STM32WB_I2CTIMEOTICKS; +#endif + + /* Wait as stop might still be in progress */ + + start = clock_systime_ticks(); + do + { + /* Calculate the elapsed time */ + + elapsed = clock_systime_ticks() - start; + + /* Check for STOP condition */ + + cr = stm32wb_i2c_getreg32(priv, STM32WB_I2C_CR2_OFFSET); + if ((cr & I2C_CR2_STOP) == 0) + { + return; + } + + /* Check for timeout error */ + + sr = stm32wb_i2c_getreg(priv, STM32WB_I2C_ISR_OFFSET); + if ((sr & I2C_INT_TIMEOUT) != 0) + { + i2cerr("ERROR: waiting for a STOP isr timeout, elapsed: %lu\n", + elapsed); + return; + } + } + + /* Loop until the stop is complete or a timeout occurs. */ + + while (elapsed < timeout); + + /* If we get here then a timeout occurred with the STOP condition + * still pending. + */ + + i2cinfo("Timeout with CR: %04" PRIx32 " SR: %04" PRIx32 "\n", cr, sr); +} + +/**************************************************************************** + * Name: stm32wb_i2c_trace* + * + * Description: + * I2C trace instrumentation + * + ****************************************************************************/ + +#ifdef CONFIG_I2C_TRACE +static void stm32wb_i2c_traceclear(struct stm32wb_i2c_priv_s *priv) +{ + struct stm32wb_trace_s *trace = &priv->trace[priv->tndx]; + + trace->status = 0; /* I2C 32-bit status */ + trace->count = 0; /* Interrupt count when status change */ + trace->event = I2CEVENT_NONE; /* Last event that occurred with this status */ + trace->parm = 0; /* Parameter associated with the event */ + trace->time = 0; /* Time of first status or event */ +} + +static void stm32wb_i2c_tracereset(struct stm32wb_i2c_priv_s *priv) +{ + /* Reset the trace info for a new data collection */ + + priv->tndx = 0; + priv->start_time = clock_systime_ticks(); + stm32wb_i2c_traceclear(priv); +} + +static void stm32wb_i2c_tracenew(struct stm32wb_i2c_priv_s *priv, + uint32_t status) +{ + struct stm32wb_trace_s *trace = &priv->trace[priv->tndx]; + + /* Is the current entry uninitialized? Has the status changed? */ + + if (trace->count == 0 || status != trace->status) + { + /* Yes.. Was it the status changed? */ + + if (trace->count != 0) + { + /* Yes.. bump up the trace index + * (unless we are out of trace entries) + */ + + if (priv->tndx >= (CONFIG_I2C_NTRACE - 1)) + { + i2cerr("ERROR: Trace table overflow\n"); + return; + } + + priv->tndx++; + trace = &priv->trace[priv->tndx]; + } + + /* Initialize the new trace entry */ + + stm32wb_i2c_traceclear(priv); + trace->status = status; + trace->count = 1; + trace->time = clock_systime_ticks(); + } + else + { + /* Just increment the count of times that we have seen this status */ + + trace->count++; + } +} + +static void stm32wb_i2c_traceevent(struct stm32wb_i2c_priv_s *priv, + enum stm32wb_trace_e event, uint32_t parm) +{ + struct stm32wb_trace_s *trace; + + if (event != I2CEVENT_NONE) + { + trace = &priv->trace[priv->tndx]; + + /* Initialize the new trace entry */ + + trace->event = event; + trace->parm = parm; + + /* Bump up the trace index (unless we are out of trace entries) */ + + if (priv->tndx >= (CONFIG_I2C_NTRACE - 1)) + { + i2cerr("ERROR: Trace table overflow\n"); + return; + } + + priv->tndx++; + stm32wb_i2c_traceclear(priv); + } +} + +static void stm32wb_i2c_tracedump(struct stm32wb_i2c_priv_s *priv) +{ + struct stm32wb_trace_s *trace; + int i; + + syslog(LOG_DEBUG, "Elapsed time: %d\n", + (int)(clock_systime_ticks() - priv->start_time)); + + for (i = 0; i < priv->tndx; i++) + { + trace = &priv->trace[i]; + syslog(LOG_DEBUG, + "%2d. STATUS: %08lx CNT: %3ld EVT: %2d PARM: %08lx TIME: %d\n", + i + 1, trace->status, trace->count, trace->event, trace->parm, + (int)(trace->time - priv->start_time)); + } +} +#endif /* CONFIG_I2C_TRACE */ + +/**************************************************************************** + * Name: stm32wb_i2c_setclock + * + * Description: + * + * Sets the I2C bus clock frequency by configuring the I2C_TIMINGR + * register. + * + * This function supports bus clock frequencies of: + * + * 1000Khz (Fast Mode+) + * 400Khz (Fast Mode) + * 100Khz (Standard Mode) + * 10Khz (Standard Mode) + * + * Attempts to set a different frequency will quietly provision the default + * of 10Khz. + * + * The only differences between the various modes of operation (std, fast, + * fast+) are the bus clock speed and setup/hold times. Setup/hold times + * are specified as a MINIMUM time for the given mode, and naturally std + * mode has the longest minimum times. As a result, by provisioning + * setup/hold times for std mode they are also compatible with fast/fast+, + * though some performance degradation occurs in fast/fast+ as a result of + * the times being somewhat longer than strictly required. The values + * remain as they are because reliability is favored over performance. + * + * Clock Selection: + * + * The I2C peripheral clock can be provided by either PCLK1, SYSCLK or the + * HSI. + * + * PCLK1 >------|\ I2CCLK + * SYSCLK >------| |---------> + * HSI >------|/ + * + * PCLK is the default and is expected to be 64Mhz. + * SYSCLK option is not supported. + * + ****************************************************************************/ + +static void stm32wb_i2c_setclock(struct stm32wb_i2c_priv_s *priv, + uint32_t frequency) +{ + uint32_t pe; + uint32_t timingr; + + if (frequency != priv->frequency) + { + /* I2C peripheral must be disabled to update clocking configuration */ + + pe = stm32wb_i2c_getreg32(priv, STM32WB_I2C_CR1_OFFSET) & I2C_CR1_PE; + if (pe) + { + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR1_OFFSET, + I2C_CR1_PE, 0); + } + +#if defined(STM32WB_I2C_USE_HSI16) + switch (frequency) + { + case 100000ul: + timingr = 0x00303d5b; + break; + + case 400000ul: + timingr = 0x0010061a; + break; + + case 1000000ul: + timingr = 0x00000107; + break; + + default: + timingr = 0x40003eff; + break; + } +#else + +#if STM32WB_PCLK1_FREQUENCY != 64000000ul +# error Unsupported I2C configuration. +#endif + + switch (frequency) + { + case 100000ul: + timingr = 0x10707dbc; + break; + + case 400000ul: + timingr = 0x00602173; + break; + + case 1000000ul: + timingr = 0x00300b29; + break; + + default: + timingr = 0xe010a9ff; + break; + } +#endif + + stm32wb_i2c_putreg32(priv, STM32WB_I2C_TIMINGR_OFFSET, timingr); + + if (pe) + { + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR1_OFFSET, + 0, I2C_CR1_PE); + } + + priv->frequency = frequency; + } +} + +/**************************************************************************** + * Name: stm32wb_i2c_sendstart + * + * Description: + * Send the START condition / force Master mode + * + * A START condition in I2C consists of a single byte that contains both the + * 7 bit slave address and a read/write bit (0 = WRITE, 1 = READ). If the + * address is recognized by one of the slave devices that slave device will + * ACK the byte so that data transfers can begin. + * + * A RESTART (or repeated START per the I2CSPEC) is simply a START condition + * issued in the middle of a transfer (i.e. after the initial START and + * before a STOP). A RESTART sends a new address byte and R/W bit to the + * bus. A RESTART is optional in most cases but mandatory in the event the + * transfer direction is changed. + * + * Most of the time reading data from an I2C slave requires a WRITE of the + * subaddress followed by a READ (and hence a RESTART in between). Writing + * to an I2C slave typically requires only WRITE operations and hence no + * RESTARTs. + * + * This function is therefore called both at the beginning of a transfer + * (START) and at appropriate times during a transfer (RESTART). + * + ****************************************************************************/ + +static inline +void stm32wb_i2c_sendstart(struct stm32wb_i2c_priv_s *priv) +{ + bool next_norestart = false; + + /* Set the private "current message" data used in protocol processing. + * + * ptr: A pointer to the start of the current message buffer. This is + * advanced after each byte in the current message is transferred. + * + * dcnt: A running counter of the bytes in the current message waiting to + * be transferred. This is decremented each time a byte is + * transferred. The hardware normally accepts a maximum of 255 bytes + * per transfer but can support more via the RELOAD mechanism. + * If dcnt initially exceeds 255, the RELOAD mechanism will be + * enabled automatically. + * + * flags: Used to characterize handling of the current message. + * + * The default flags value is 0 which specifies: + * + * - A transfer direction of WRITE (R/W bit = 0) + * - RESTARTs between all messages + * + * The following flags can be used to override this behavior as follows: + * + * - I2C_M_READ: Sets the transfer direction to READ (R/W bit = 1) + * - I2C_M_NOSTART: Prevents a RESTART from being issued prior to the + * transfer of the message (where allowed by the protocol). + * + */ + + priv->ptr = priv->msgv->buffer; + priv->dcnt = priv->msgv->length; + priv->flags = priv->msgv->flags; + + if ((priv->flags & I2C_M_NOSTART) == 0) + { + /* Flag the first byte as an address byte */ + + priv->astart = true; + } + + /* Enabling RELOAD allows the transfer of: + * + * - individual messages with a payload exceeding 255 bytes + * - multiple messages back to back without a RESTART in between + * + * so we enable it if either of those conditions exist and disable + * it otherwise. + */ + + /* Check if there are multiple messages and the next is a continuation */ + + if (priv->msgc > 1) + { + next_norestart = (((priv->msgv + 1)->flags & I2C_M_NOSTART) != 0); + } + + if (next_norestart || priv->dcnt > 255) + { + i2cinfo("RELOAD enabled: dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + stm32wb_i2c_enable_reload(priv); + } + else + { + i2cinfo("RELOAD disable: dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + stm32wb_i2c_disable_reload(priv); + } + + /* Set the number of bytes to transfer (I2C_CR2->NBYTES) to the number of + * bytes in the current message or 255, whichever is lower so as to not + * exceed the hardware maximum allowed. + */ + + if (priv->dcnt > 255) + { + stm32wb_i2c_set_bytes_to_transfer(priv, 255); + } + else + { + stm32wb_i2c_set_bytes_to_transfer(priv, priv->dcnt); + } + + /* Set the (7 bit) address. + * 10 bit addressing is not yet supported. + */ + + stm32wb_i2c_set_7bit_address(priv); + + /* The flag of the current message is used to determine the direction of + * transfer required for the current message. + */ + + if (priv->flags & I2C_M_READ) + { + stm32wb_i2c_set_read_transfer_dir(priv); + } + else + { + stm32wb_i2c_set_write_transfer_dir(priv); + } + + /* Set the I2C_CR2->START bit to 1 to instruct the hardware to send the + * START condition using the address and transfer direction data entered. + */ + + i2cinfo("Sending START: dcnt=%i msgc=%i flags=0x%04x\n", + priv->dcnt, priv->msgc, priv->flags); + + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, 0, I2C_CR2_START); +} + +/**************************************************************************** + * Name: stm32wb_i2c_sendstop + * + * Description: + * Send the STOP conditions + * + * A STOP condition can be requested by setting the STOP bit in the I2C_CR2 + * register. Setting the STOP bit clears the TC flag and the STOP condition + * is sent on the bus. + * + ****************************************************************************/ + +static inline +void stm32wb_i2c_sendstop(struct stm32wb_i2c_priv_s *priv) +{ + i2cinfo("Sending STOP\n"); + stm32wb_i2c_traceevent(priv, I2CEVENT_WRITE_STOP, 0); + + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR2_OFFSET, + 0, I2C_CR2_STOP); +} + +/**************************************************************************** + * Name: stm32wb_i2c_getstatus + * + * Description: + * Get 32-bit status (SR1 and SR2 combined) + * + ****************************************************************************/ + +static inline +uint32_t stm32wb_i2c_getstatus(struct stm32wb_i2c_priv_s *priv) +{ + return getreg32(priv->config->base + STM32WB_I2C_ISR_OFFSET); +} + +/**************************************************************************** + * Name: stm32wb_i2c_clearinterrupts + * + * Description: + * Clear all interrupts + * + ****************************************************************************/ + +static inline +void stm32wb_i2c_clearinterrupts(struct stm32wb_i2c_priv_s *priv) +{ + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_ICR_OFFSET, + 0, I2C_ICR_CLEARMASK); +} + +/**************************************************************************** + * Name: stm32wb_i2c_isr_process + * + * Description: + * Common interrupt service routine (ISR) that handles I2C protocol logic. + * This is instantiated for each configured I2C interface (I2C1, I2C3). + * + * This ISR is activated and deactivated by: + * + * stm32wb_i2c_process + * and + * stm32wb_i2c_waitdone + * + * Input Parameters: + * priv - The private struct of the I2C driver. + * + ****************************************************************************/ + +static int stm32wb_i2c_isr_process(struct stm32wb_i2c_priv_s *priv) +{ + uint32_t status; + + /* Get state of the I2C controller */ + + status = stm32wb_i2c_getreg32(priv, STM32WB_I2C_ISR_OFFSET); + + i2cinfo("ENTER: status = 0x%08" PRIx32 "\n", status); + + /* Update private version of the state */ + + priv->status = status; + + /* If this is a new transmission set up the trace table accordingly */ + + stm32wb_i2c_tracenew(priv, status); + stm32wb_i2c_traceevent(priv, I2CEVENT_ISR_CALL, 0); + + /* ------------------- Start of I2C protocol handling ------------------ */ + + /* I2C protocol logic follows. It's organized in an if else chain such that + * only one mode of operation is executed every time the ISR is called. + * + * If you need to add additional states to support new features be sure + * they continue the chain (i.e. begin with "else if") and are placed + * before the empty call / error states at the end of the chain. + */ + + /* NACK Handling + * + * This branch is only triggered when the NACK (Not Acknowledge Received) + * interrupt occurs. This interrupt will only fire when the + * I2C_CR1->NACKIE bit is 1. + * + * I2C_ISR->NACKF is set by hardware when a NACK is received after a byte + * is transmitted and the slave fails to acknowledge it. This is the + * opposite of, and mutually exclusive to, the I2C_ISR->TXIS event. + * + * In response to the NACK the hardware automatically triggers generation + * of a STOP condition, terminating the transfer. The only valid response + * to this state is to exit the ISR and report the failure. + * + * To differentiate an "address NACK" from a NACK that might occur during + * the transfer of other bytes the "priv->astart" parameter is + * used. This flag is set to TRUE in sendstart() and set to FALSE when + * the first TXIS event is received, which would be after the first byte + * (the address) is transmitted successfully (acknowledged). + */ + + if (status & I2C_INT_NACK) + { + if (priv->astart == true) + { + /* NACK received on first (address) byte: address is invalid */ + + i2cinfo("NACK: Address invalid: dcnt=%i " + "msgc=%i status=0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + stm32wb_i2c_traceevent(priv, I2CEVENT_ADDRESS_NACKED, + priv->msgv->addr); + } + else + { + /* NACK received on regular byte */ + + i2cinfo("NACK: NACK received: dcnt=%i " + "msgc=%i status=0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + stm32wb_i2c_traceevent(priv, I2CEVENT_ADDRESS_NACKED, + priv->msgv->addr); + } + + /* Set flags to terminate message transmission: + * + * set message length to -1 to indicate last byte of message sent + * set message count to 0 to indicate no more messages to send + * + * As we fall through the logic in the ISR the message handling block + * will be triggered by these flags and signal the ISR to terminate. + */ + + priv->dcnt = -1; + priv->msgc = 0; + } + + /* Transmit Interrupt Status (TXIS) Handler + * + * This branch is only triggered when the TXIS interrupt occurs. This + * interrupt will only fire when the I2C_CR1->TXIE bit is 1. + * + * This indicates the transmit data register I2C_TXDR has been emptied + * following the successful transmission of a byte and slave + * acknowledgement. In this state the I2C_TXDR register is ready to accept + * another byte for transmission. The TXIS bit will be cleared + * automatically when the next byte is written to I2C_TXDR. + * + * The number of TXIS events during the transfer corresponds to NBYTES. + * + * The TXIS flag is not set when a NACK is received. + * + * When RELOAD is disabled (RELOAD=0) and NBYTES data have been + * transferred: + * + * - In Automatic End Mode (AUTOEND=1), a STOP is automatically sent. + * + * Note: Automatic End Mode is not currently supported. + * + * - In Software End Mode (AUTOEND=0), the TC event occurs and the SCL + * line is stretched low in order to allow software actions (STOP, + * RESTART). + * + * When RELOAD is enabled (RELOAD=1) and NBYTES bytes have been transferred + * a TCR event occurs instead and that handler simply updates NBYTES which + * causes TXIS events to continue. The process repeats until all bytes in + * the message have been transferred. + */ + + else if ((priv->flags & (I2C_M_READ)) == 0 && + (status & (I2C_ISR_TXIS)) != 0) + { + /* TXIS interrupt occurred, address valid, ready to transmit */ + + stm32wb_i2c_traceevent(priv, I2CEVENT_WRITE, 0); + i2cinfo("TXIS: ENTER dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + + /* The first event after the address byte is sent will be either TXIS + * or NACKF so it's safe to set the astart flag to false on + * the first TXIS event to indicate that it is no longer necessary to + * check for address validity. + */ + + if (priv->astart == true) + { + i2cinfo("TXIS: Address Valid\n"); + stm32wb_i2c_traceevent(priv, I2CEVENT_ADDRESS_ACKED, + priv->msgv->addr); + priv->astart = false; + } + + /* If one or more bytes in the current message are ready to transmit */ + + if (priv->dcnt > 0) + { + /* Prepare to transmit the current byte */ + + stm32wb_i2c_traceevent(priv, I2CEVENT_WRITE_TO_DR, priv->dcnt); + i2cinfo("TXIS: Write Data 0x%02x\n", *priv->ptr); + + /* Decrement byte counter */ + + priv->dcnt--; + + /* If we are about to transmit the last byte in the current + * message + */ + + if (priv->dcnt == 0) + { + /* If this is also the last message to send, disable RELOAD so + * TC fires next and issues STOP condition. If we don't do + * this TCR will fire next, and since there are no bytes to + * send we can't write NBYTES to clear TCR so it will fire + * forever. + */ + + if (priv->msgc == 1) + { + stm32wb_i2c_disable_reload(priv); + } + } + + /* Transmit current byte */ + + stm32wb_i2c_putreg(priv, STM32WB_I2C_TXDR_OFFSET, *priv->ptr); + + /* Advance to next byte */ + + priv->ptr++; + } + else + { + /* Unsupported state */ + + i2cerr("ERROR: TXIS Unsupported state detected, dcnt=%i, " + "status 0x%08" PRIx32 "\n", + priv->dcnt, status); + stm32wb_i2c_traceevent(priv, I2CEVENT_WRITE_ERROR, 0); + } + + i2cinfo("TXIS: EXIT dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + } + + /* Receive Buffer Not Empty (RXNE) State Handler + * + * This branch is only triggered when the RXNE interrupt occurs. This + * interrupt will only fire when the I2C_CR1->RXIE bit is 1. + * + * This indicates data has been received from the bus and is waiting to + * be read from the I2C_RXDR register. When I2C_RXDR is read this bit + * is automatically cleared and then an ACK or NACK is sent depending on + * whether we have more bytes to receive. + * + * When RELOAD is disabled and bytes remain to be transferred an + * acknowledge is automatically sent on the bus and the RXNE events + * continue until the last byte is received. + * + * When RELOAD is disabled (RELOAD=0) and BYTES have been transferred: + * + * - In Automatic End Mode (AUTOEND=1), a NACK and a STOP are + * automatically sent after the last received byte. + * + * Note: Automatic End Mode is not currently supported. + * + * - In Software End Mode (AUTOEND=0), a NACK is automatically sent after + * the last received byte, the TC event occurs and the SCL line is + * stretched low in order to allow software actions (STOP, RESTART). + * + * When RELOAD is enabled (RELOAD=1) and NBYTES bytes have been transferred + * a TCR event occurs and that handler simply updates NBYTES which causes + * RXNE events to continue until all bytes have been transferred. + */ + + else if ((priv->flags & (I2C_M_READ)) != 0 && (status & I2C_ISR_RXNE) != 0) + { + /* When read flag is set and the receive buffer is not empty + * (RXNE is set) then the driver can read from the data register. + */ + + stm32wb_i2c_traceevent(priv, I2CEVENT_READ, 0); + i2cinfo("RXNE: ENTER dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + + /* If more bytes in the current message */ + + if (priv->dcnt > 0) + { + stm32wb_i2c_traceevent(priv, I2CEVENT_RCVBYTE, priv->dcnt); + + /* No interrupts or context switches may occur in the following + * sequence. Otherwise, additional bytes may be received. + */ + +#ifdef CONFIG_I2C_POLLED + irqstate_t state = enter_critical_section(); +#endif + /* Receive a byte */ + + *priv->ptr = stm32wb_i2c_getreg(priv, STM32WB_I2C_RXDR_OFFSET); + + i2cinfo("RXNE: Read Data 0x%02x\n", *priv->ptr); + + /* Advance buffer to the next byte in the message */ + + priv->ptr++; + + /* Signal byte received */ + + priv->dcnt--; + +#ifdef CONFIG_I2C_POLLED + leave_critical_section(state); +#endif + } + else + { + /* Unsupported state */ + + stm32wb_i2c_traceevent(priv, I2CEVENT_READ_ERROR, 0); + status = stm32wb_i2c_getreg(priv, STM32WB_I2C_ISR_OFFSET); + i2cerr("ERROR: RXNE Unsupported state detected, dcnt=%i, " + "status 0x%08" PRIx32 "\n", + priv->dcnt, status); + + /* Set signals that will terminate ISR and wake waiting thread */ + + priv->dcnt = -1; + priv->msgc = 0; + } + + i2cinfo("RXNE: EXIT dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + } + + /* Transfer Complete (TC) State Handler + * + * This branch is only triggered when the TC interrupt occurs. This + * interrupt will only fire when: + * + * I2C_CR1->TCIE = 1 (Transfer Complete Interrupts Enabled) + * I2C_CR2->RELOAD = 0 (Reload Mode Disabled) + * I2C_CR2->AUTOEND = 0 (Autoend Mode Disabled, i.e. Software End Mode) + * + * This event indicates that the number of bytes initially defined + * in NBYTES, meaning, the number of bytes in the current message + * (priv->dcnt) has been successfully transmitted or received. + * + * When the TC interrupt occurs we have two choices to clear it and move + * on, regardless of the transfer direction: + * + * - if more messages follow, perform a repeated START if required + * and then fall through to transmit or receive the next message. + * + * - if no messages follow, perform a STOP and set flags needed to + * exit the ISR. + * + * The fact that the hardware must either RESTART or STOP when a TC + * event occurs explains why, when messages must be sent back to back + * (i.e. without a restart by specifying the I2C_M_NOSTART flag), + * RELOAD mode must be enabled and TCR event(s) must be generated + * instead. See the TCR handler for more. + */ + + else if ((status & I2C_ISR_TC) != 0) + { + i2cinfo("TC: ENTER dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + + /* Prior message has been sent successfully. Or there could have + * been an error that set msgc to 0; So test for that case as + * we do not want to decrement msgc less then zero nor move msgv + * past the last message. + */ + + if (priv->msgc > 0) + { + priv->msgc--; + } + + /* Are there additional messages remain to be transmitted / received? */ + + if (priv->msgc > 0) + { + i2cinfo("TC: RESTART: dcnt=%i, msgc=%i\n", + priv->dcnt, priv->msgc); + stm32wb_i2c_traceevent(priv, I2CEVENT_TC_NO_RESTART, priv->msgc); + + /* Issue a START condition. + * + * Note that the first thing sendstart does is update the + * private structure "current message" data (ptr, dcnt, flags) + * so they all reflect the next message in the list so we + * update msgv before we get there. + */ + + /* Advance to the next message in the list */ + + priv->msgv++; + + stm32wb_i2c_sendstart(priv); + } + else + { + /* Issue a STOP conditions. + * + * No additional messages to transmit / receive, so the + * transfer is indeed complete. Nothing else to do but + * issue a STOP and exit. + */ + + i2cinfo("TC: STOP: dcnt=%i msgc=%i\n", + priv->dcnt, priv->msgc); + stm32wb_i2c_traceevent(priv, I2CEVENT_STOP, priv->dcnt); + + stm32wb_i2c_sendstop(priv); + + /* Set signals that will terminate ISR and wake waiting thread */ + + priv->dcnt = -1; + priv->msgc = 0; + } + + i2cinfo("TC: EXIT dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + } + + /* Transfer Complete (Reload) State Handler + * + * This branch is only triggered when the TCR interrupt occurs. This + * interrupt will only fire when: + * + * I2C_CR1->TCIE = 1 (Transfer Complete Interrupts Enabled) + * I2C_CR2->RELOAD = 1 (Reload Mode Active) + * I2C_CR2->AUTOEND = 0 (Autoend Mode Disabled, i.e. Software End Mode) + * + * This is similar to the TC event except that TCR assumes that additional + * bytes are available to transfer. So despite what its name might imply + * the transfer really isn't complete. + * + * There are two reasons RELOAD would be enabled: + * + * 1) We're trying to send a message with a payload greater than 255 bytes. + * 2) We're trying to send messages back to back, regardless of their + * payload size, to avoid a RESTART (i.e. I2C_M_NOSTART flag is set). + * + * These conditions may be true simultaneously, as would be the case if + * we're sending multiple messages with payloads > 255 bytes. So we only + * advance to the next message if we arrive here and dcnt is 0, meaning, + * we're finished with the last message and ready to move to the next. + * + * This logic supports the transfer of bytes limited only by the size of + * the i2c_msg_s length variable. The SCL line will be stretched low + * until NBYTES is written with a non-zero value, allowing the transfer + * to continue. + * + * TODO: RESTARTs are required by the I2CSPEC if the next message transfer + * direction changes. Right now the NORESTART flag overrides this + * behavior. May have to introduce logic to issue sendstart, assuming it's + * legal with the hardware in the TCR state. + */ + + else if ((status & I2C_ISR_TCR) != 0) + { + i2cinfo("TCR: ENTER dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + + /* If no more bytes in the current message to transfer */ + + if (priv->dcnt == 0) + { + /* Prior message has been sent successfully */ + + priv->msgc--; + + /* Advance to the next message in the list */ + + priv->msgv++; + + /* Update current message data */ + + priv->ptr = priv->msgv->buffer; + priv->dcnt = priv->msgv->length; + priv->flags = priv->msgv->flags; + + /* If this is the last message, disable reload so the + * TC event fires next time. + */ + + if (priv->msgc == 0) + { + i2cinfo("TCR: DISABLE RELOAD: dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + + stm32wb_i2c_disable_reload(priv); + } + + /* Update NBYTES with length of current message */ + + i2cinfo("TCR: NEXT MSG dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + + stm32wb_i2c_set_bytes_to_transfer(priv, priv->dcnt); + } + else + { + /* More bytes in the current (greater than 255 byte payload + * length) message, so set NBYTES according to the bytes + * remaining in the message, up to a maximum each cycle of 255. + */ + + if (priv->dcnt > 255) + { + i2cinfo( + "TCR: ENABLE RELOAD: NBYTES = 255 dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + + /* More than 255 bytes to transfer so the RELOAD bit is + * set in order to generate a TCR event rather than a TC + * event when 255 bytes are successfully transferred. + * This forces us to return here to update NBYTES and + * continue until NBYTES is set to less than 255 bytes, + * at which point RELOAD will be disabled and a TC + * event will (eventually) follow to officially terminate + * the transfer. + */ + + stm32wb_i2c_enable_reload(priv); + + stm32wb_i2c_set_bytes_to_transfer(priv, 255); + } + else + { + /* Less than 255 bytes left to transfer, which means we'll + * complete the transfer of all bytes in the current message + * the next time around. + * + * This means we need to disable the RELOAD functionality so + * we receive a TC event next time which will allow us to + * either RESTART and continue sending the contents of the + * next message or send a STOP condition and exit the ISR. + */ + + i2cinfo("TCR: DISABLE RELOAD: NBYTES = dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + + stm32wb_i2c_disable_reload(priv); + + stm32wb_i2c_set_bytes_to_transfer(priv, priv->dcnt); + } + + i2cinfo("TCR: EXIT dcnt = %i msgc = %i status 0x%08" PRIx32 "\n", + priv->dcnt, priv->msgc, status); + } + } + + /* Empty call handler + * + * Case to handle an empty call to the ISR where it has nothing to + * do and should exit immediately. + */ + + else if (priv->dcnt == -1 && priv->msgc == 0) + { + status = stm32wb_i2c_getreg(priv, STM32WB_I2C_ISR_OFFSET); + i2cwarn("WARNING: EMPTY CALL: Stopping ISR: status 0x%08" PRIx32 "\n", + status); + stm32wb_i2c_traceevent(priv, I2CEVENT_ISR_EMPTY_CALL, 0); + } + + /* Error handler + * + * We get to this branch only if we can't handle the current state. + * + * This should not happen in interrupt based operation. + * + * This will happen during polled operation when the device is not + * in one of the supported states when polled. + */ + + else + { +#ifdef CONFIG_I2C_POLLED + stm32wb_i2c_traceevent(priv, I2CEVENT_POLL_DEV_NOT_RDY, 0); +#else + /* Read rest of the state */ + + status = stm32wb_i2c_getreg(priv, STM32WB_I2C_ISR_OFFSET); + + i2cerr("ERROR: Invalid state detected, status 0x%08" PRIx32 "\n", + status); + + /* set condition to terminate ISR and wake waiting thread */ + + priv->dcnt = -1; + priv->msgc = 0; + stm32wb_i2c_traceevent(priv, I2CEVENT_STATE_ERROR, 0); +#endif + } + + /* --------------------- End of I2C protocol handling ------------------ */ + + /* Message Handling + * + * Transmission of the whole message chain has been completed. We have to + * terminate the ISR and wake up stm32wb_i2c_process() that is waiting for + * the ISR cycle to handle the sending/receiving of the messages. + */ + + if (priv->dcnt == -1 && priv->msgc == 0) + { + i2cinfo("MSG: Shutting down I2C ISR\n"); + + stm32wb_i2c_traceevent(priv, I2CEVENT_ISR_SHUTDOWN, 0); + + /* Clear pointer to message content to reflect we are done + * with the current transaction. + */ + + priv->msgv = NULL; + +#ifdef CONFIG_I2C_POLLED + priv->intstate = INTSTATE_DONE; +#else + + status = stm32wb_i2c_getreg32(priv, STM32WB_I2C_ISR_OFFSET); + + /* Update private state to capture NACK which is used in combination + * with the astart flag to report the type of NACK received (address + * vs data) to the upper layers once we exit the ISR. + * + * Note: We do this prior to clearing interrupts because the NACKF + * flag will naturally be cleared by that process. + */ + + priv->status = status; + + /* Clear all interrupts */ + + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_ICR_OFFSET, + 0, I2C_ICR_CLEARMASK); + + /* If a thread is waiting then inform it transfer is complete */ + + if (priv->intstate == INTSTATE_WAITING) + { + nxsem_post(&priv->sem_isr); + priv->intstate = INTSTATE_DONE; + } +#endif + } + + status = stm32wb_i2c_getreg32(priv, STM32WB_I2C_ISR_OFFSET); + i2cinfo("EXIT: status = 0x%08" PRIx32 "\n", status); + + return OK; +} + +/**************************************************************************** + * Name: stm32wb_i2c_isr + * + * Description: + * Common I2C interrupt service routine + * + ****************************************************************************/ + +#ifndef CONFIG_I2C_POLLED +static int stm32wb_i2c_isr(int irq, void *context, void *arg) +{ + struct stm32wb_i2c_priv_s *priv = (struct stm32wb_i2c_priv_s *)arg; + + DEBUGASSERT(priv != NULL); + return stm32wb_i2c_isr_process(priv); +} +#endif + +/**************************************************************************** + * Name: stm32wb_i2c_init + * + * Description: + * Setup the I2C hardware, ready for operation with defaults + * + ****************************************************************************/ + +static int stm32wb_i2c_init(struct stm32wb_i2c_priv_s *priv) +{ + /* Power-up and configure GPIOs */ + + /* Enable power and reset the peripheral */ + + modifyreg32(STM32WB_RCC_APB1ENR1, 0, priv->config->clk_bit); + modifyreg32(STM32WB_RCC_APB1RSTR1, 0, priv->config->reset_bit); + modifyreg32(STM32WB_RCC_APB1RSTR1, priv->config->reset_bit, 0); + + /* Configure pins */ + + if (stm32wb_configgpio(priv->config->scl_pin) < 0) + { + return ERROR; + } + + if (stm32wb_configgpio(priv->config->sda_pin) < 0) + { + stm32wb_unconfiggpio(priv->config->scl_pin); + return ERROR; + } + +#ifndef CONFIG_I2C_POLLED + /* Attach error and event interrupts to the ISRs */ + + irq_attach(priv->config->ev_irq, stm32wb_i2c_isr, priv); + irq_attach(priv->config->er_irq, stm32wb_i2c_isr, priv); + up_enable_irq(priv->config->ev_irq); + up_enable_irq(priv->config->er_irq); +#endif + + /* TODO: + * - Provide means to set peripheral clock source via RCC_CCIPR_I2CxSEL + * - Make clock source Kconfigurable (currently PCLK = 64MHz) + */ + + /* Force a frequency update */ + + priv->frequency = 0; + stm32wb_i2c_setclock(priv, 100000); + + /* Enable I2C peripheral */ + + stm32wb_i2c_modifyreg32(priv, STM32WB_I2C_CR1_OFFSET, 0, I2C_CR1_PE); + + return OK; +} + +/**************************************************************************** + * Name: stm32wb_i2c_deinit + * + * Description: + * Shutdown the I2C hardware + * + ****************************************************************************/ + +static int stm32wb_i2c_deinit(struct stm32wb_i2c_priv_s *priv) +{ + /* Disable I2C */ + + stm32wb_i2c_putreg32(priv, STM32WB_I2C_CR1_OFFSET, 0); + + /* Unconfigure GPIO pins */ + + stm32wb_unconfiggpio(priv->config->scl_pin); + stm32wb_unconfiggpio(priv->config->sda_pin); + +#ifndef CONFIG_I2C_POLLED + + /* Disable and detach interrupts */ + + up_disable_irq(priv->config->ev_irq); + up_disable_irq(priv->config->er_irq); + irq_detach(priv->config->ev_irq); + irq_detach(priv->config->er_irq); +#endif + + /* Disable clocking */ + + modifyreg32(STM32WB_RCC_APB1ENR1, priv->config->clk_bit, 0); + + return OK; +} + +/**************************************************************************** + * Name: stm32wb_i2c_process + * + * Description: + * Common I2C transfer logic + * + * Initiates a master mode transaction on the I2C bus to transfer the + * provided messages to and from the slave devices. + * + ****************************************************************************/ + +static int stm32wb_i2c_process(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count) +{ + struct stm32wb_i2c_inst_s *inst = (struct stm32wb_i2c_inst_s *)dev; + struct stm32wb_i2c_priv_s *priv = inst->priv; + uint32_t status = 0; + uint32_t cr1; + uint32_t cr2; + int errval = 0; + int waitrc = 0; + + DEBUGASSERT(count > 0); + + /* Wait for any STOP in progress */ + + stm32wb_i2c_sem_waitstop(priv); + + /* Clear any pending error interrupts */ + + stm32wb_i2c_clearinterrupts(priv); + + /* Old transfers are done */ + + priv->msgv = msgs; + priv->msgc = count; + + /* Reset I2C trace logic */ + + stm32wb_i2c_tracereset(priv); + + /* Set I2C clock frequency (on change it toggles I2C_CR1_PE !) */ + + stm32wb_i2c_setclock(priv, msgs->frequency); + + /* Trigger start condition, then the process moves into the ISR. I2C + * interrupts will be enabled within stm32wb_i2c_waitdone(). + */ + + priv->status = 0; + +#ifndef CONFIG_I2C_POLLED + /* Enable transmit and receive interrupts here so when we send the start + * condition below the ISR will fire if the data was sent and some + * response from the slave received. All other interrupts relevant to + * our needs are enabled in stm32wb_i2c_sem_waitdone() below. + */ + + stm32wb_i2c_enableinterrupts(priv); +#endif + + /* Trigger START condition generation, which also sends the slave address + * with read/write flag and the data in the first message + */ + + stm32wb_i2c_sendstart(priv); + + /* Wait for the ISR to tell us that the transfer is complete by attempting + * to grab the semaphore that is initially locked by the ISR. If the ISR + * does not release the lock so we can obtain it here prior to the end of + * the timeout period waitdone returns error and we report a timeout. + */ + + waitrc = stm32wb_i2c_sem_waitdone(priv); + + cr1 = stm32wb_i2c_getreg32(priv, STM32WB_I2C_CR1_OFFSET); + cr2 = stm32wb_i2c_getreg32(priv, STM32WB_I2C_CR2_OFFSET); +#if !defined(CONFIG_DEBUG_I2C) + UNUSED(cr1); + UNUSED(cr2); +#endif + + /* Status after a normal / good exit is usually 0x00000001, meaning the TXE + * bit is set. That occurs as a result of the I2C_TXDR register being + * empty, and it naturally will be after the last byte is transmitted. + * This bit is cleared when we attempt communications again and re-enable + * the peripheral. The priv->status field can hold additional information + * like a NACK, so we reset the status field to include that information. + */ + + status = stm32wb_i2c_getstatus(priv); + + /* The priv->status field can hold additional information like a NACK + * event so we include that information. + */ + + status = priv->status & 0xffffffff; + + if (waitrc < 0) + { + /* Connection timed out */ + + errval = ETIMEDOUT; + i2cerr("ERROR: Waitdone timed out CR1: 0x%08" PRIx32 + " CR2: 0x%08" PRIx32 " status: 0x%08" PRIx32 "\n", + cr1, cr2, status); + } + else + { + i2cinfo("Waitdone success: CR1: 0x%08" PRIx32 " CR2: 0x%08" PRIx32 + " status: 0x%08" PRIx32 "\n", + cr1, cr2, status); + } + + UNUSED(cr1); + UNUSED(cr2); + + i2cinfo("priv->status: 0x%08" PRIx32 "\n", priv->status); + + /* Check for error status conditions */ + + if ((status & (I2C_INT_BERR | + I2C_INT_ARLO | + I2C_INT_OVR | + I2C_INT_PECERR | + I2C_INT_TIMEOUT | + I2C_INT_NACK)) != 0) + + { + /* one or more errors in the mask are present */ + + if (status & I2C_INT_BERR) + { + /* Bus Error, ignore it because of errata (revision A,Z) */ + + i2cerr("ERROR: I2C Bus Error\n"); + + /* errval = EIO; */ + } + else if (status & I2C_INT_ARLO) + { + /* Arbitration Lost (master mode) */ + + i2cerr("ERROR: I2C Arbitration Lost\n"); + errval = EAGAIN; + } + + else if (status & I2C_INT_OVR) + { + /* Overrun/Underrun */ + + i2cerr("ERROR: I2C Overrun/Underrun\n"); + errval = EIO; + } + else if (status & I2C_INT_PECERR) + { + /* PEC Error in reception (SMBus Only) */ + + i2cerr("ERROR: I2C PEC Error\n"); + errval = EPROTO; + } + else if (status & I2C_INT_TIMEOUT) + { + /* Timeout or Tlow Error (SMBus Only) */ + + i2cerr("ERROR: I2C Timeout / Tlow Error\n"); + errval = ETIME; + } + else if (status & I2C_INT_NACK) + { + /* NACK Received, flag as "communication error on send" */ + + if (priv->astart == TRUE) + { + i2cwarn("WARNING: I2C Address NACK\n"); + errval = EADDRNOTAVAIL; + } + else + { + i2cwarn("WARNING: I2C Data NACK\n"); + errval = ECOMM; + } + } + else + { + /* Unrecognized error */ + + i2cerr("ERROR: I2C Unrecognized Error"); + errval = EINTR; + } + } + + /* This is not an error, but should not happen. The BUSY signal can be + * present if devices on the bus are in an odd state and need to be reset. + * NOTE: + * We will only see this busy indication if stm32wb_i2c_sem_waitdone() + * fails above; Otherwise it is cleared. + */ + + else if ((status & I2C_ISR_BUSY) != 0) + { + /* I2C Bus Busy + * + * This is a status condition rather than an error. + * + * We will only see this busy indication if stm32wb_i2c_sem_waitdone() + * fails above; Otherwise it is cleared by the hardware when the ISR + * wraps up the transfer with a STOP condition. + */ + + clock_t start = clock_systime_ticks(); + clock_t timeout = USEC2TICK(USEC_PER_SEC / priv->frequency) + 1; + + status = stm32wb_i2c_getstatus(priv); + + while (status & I2C_ISR_BUSY) + { + if ((clock_systime_ticks() - start) > timeout) + { + i2cerr("ERROR: I2C Bus busy"); + errval = EBUSY; + break; + } + + status = stm32wb_i2c_getstatus(priv); + } + } + + /* Dump the trace result */ + + stm32wb_i2c_tracedump(priv); + nxmutex_unlock(&priv->lock); + + return -errval; +} + +/**************************************************************************** + * Name: stm32wb_i2c_transfer + * + * Description: + * Generic I2C transfer function + * + ****************************************************************************/ + +static int stm32wb_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, + int count) +{ + int ret; + + /* Ensure that address or flags don't change meanwhile */ + + ret = nxmutex_lock(&((struct stm32wb_i2c_inst_s *)dev)->priv->lock); + if (ret >= 0) + { + ret = stm32wb_i2c_process(dev, msgs, count); + } + + return ret; +} + +/**************************************************************************** + * Name: stm32wb_i2c_reset + * + * Description: + * Reset an I2C bus + * + ****************************************************************************/ + +#ifdef CONFIG_I2C_RESET +static int stm32wb_i2c_reset(struct i2c_master_s * dev) +{ + struct stm32wb_i2c_priv_s *priv; + unsigned int clock_count; + unsigned int stretch_count; + uint32_t scl_gpio; + uint32_t sda_gpio; + uint32_t frequency; + int ret; + + DEBUGASSERT(dev); + + /* Get I2C private structure */ + + priv = ((struct stm32wb_i2c_inst_s *)dev)->priv; + + /* Our caller must own a ref */ + + DEBUGASSERT(priv->refs > 0); + + /* Lock out other clients */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = -EIO; + + /* Save the current frequency */ + + frequency = priv->frequency; + + /* De-init the port */ + + stm32wb_i2c_deinit(priv); + + /* Use GPIO configuration to un-wedge the bus */ + + scl_gpio = MKI2C_OUTPUT(priv->config->scl_pin); + sda_gpio = MKI2C_OUTPUT(priv->config->sda_pin); + + stm32wb_configgpio(sda_gpio); + stm32wb_configgpio(scl_gpio); + + /* Let SDA go high */ + + stm32wb_gpiowrite(sda_gpio, 1); + + /* Clock the bus until any slaves currently driving it let it go. */ + + clock_count = 0; + while (!stm32wb_gpioread(sda_gpio)) + { + /* Give up if we have tried too hard */ + + if (clock_count++ > 10) + { + goto out; + } + + /* Sniff to make sure that clock stretching has finished. + * + * If the bus never relaxes, the reset has failed. + */ + + stretch_count = 0; + while (!stm32wb_gpioread(scl_gpio)) + { + /* Give up if we have tried too hard */ + + if (stretch_count++ > 10) + { + goto out; + } + + up_udelay(10); + } + + /* Drive SCL low */ + + stm32wb_gpiowrite(scl_gpio, 0); + up_udelay(10); + + /* Drive SCL high again */ + + stm32wb_gpiowrite(scl_gpio, 1); + up_udelay(10); + } + + /* Generate a start followed by a stop to reset slave + * state machines. + */ + + stm32wb_gpiowrite(sda_gpio, 0); + up_udelay(10); + stm32wb_gpiowrite(scl_gpio, 0); + up_udelay(10); + stm32wb_gpiowrite(scl_gpio, 1); + up_udelay(10); + stm32wb_gpiowrite(sda_gpio, 1); + up_udelay(10); + + /* Revert the GPIO configuration. */ + + stm32wb_unconfiggpio(sda_gpio); + stm32wb_unconfiggpio(scl_gpio); + + /* Re-init the port */ + + stm32wb_i2c_init(priv); + + /* Restore the frequency */ + + stm32wb_i2c_setclock(priv, frequency); + ret = OK; + +out: + + /* Release the port for re-use by other clients */ + + nxmutex_unlock(&priv->lock); + return ret; +} +#endif /* CONFIG_I2C_RESET */ + +/**************************************************************************** + * Name: stm32wb_i2c_pm_prepare + * + * Description: + * Request the driver to prepare for a new power state. This is a + * warning that the system is about to enter into a new power state. The + * driver should begin whatever operations that may be required to enter + * power state. The driver may abort the state change mode by returning + * a non-zero value from the callback function. + * + * Input Parameters: + * cb - Returned to the driver. The driver version of the callback + * structure may include additional, driver-specific state + * data at the end of the structure. + * domain - Identifies the activity domain of the state change + * pmstate - Identifies the new PM state + * + * Returned Value: + * 0 (OK) means the event was successfully processed and that the driver + * is prepared for the PM state change. Non-zero means that the driver + * is not prepared to perform the tasks needed achieve this power setting + * and will cause the state change to be aborted. NOTE: The prepare + * method will also be recalled when reverting from lower back to higher + * power consumption modes (say because another driver refused a lower + * power state change). Drivers are not permitted to return non-zero + * values when reverting back to higher power consumption modes! + * + ****************************************************************************/ + +#ifdef CONFIG_PM +static int stm32wb_i2c_pm_prepare(struct pm_callback_s *cb, int domain, + enum pm_state_e pmstate) +{ + struct stm32wb_i2c_priv_s *priv = + (struct stm32wb_i2c_priv_s *)((char *)cb - + offsetof(struct stm32wb_i2c_priv_s, pm_cb)); + + /* Logic to prepare for a reduced power state goes here. */ + + switch (pmstate) + { + case PM_NORMAL: + case PM_IDLE: + break; + + case PM_STANDBY: + case PM_SLEEP: + + /* Check if exclusive lock for I2C bus is held. */ + + if (nxmutex_is_locked(&priv->lock)) + { + /* Exclusive lock is held, do not allow entry to deeper PM + * states. + */ + + return -EBUSY; + } + + break; + + default: + + /* Should not get here */ + + break; + } + + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32wb_i2cbus_initialize + * + * Description: + * Initialize one I2C bus + * + ****************************************************************************/ + +struct i2c_master_s *stm32wb_i2cbus_initialize(int port) +{ + struct stm32wb_i2c_priv_s *priv = NULL; /* private data of device with multiple instances */ + struct stm32wb_i2c_inst_s *inst = NULL; /* device, single instance */ + + /* Get I2C private structure */ + + switch (port) + { +#ifdef CONFIG_STM32WB_I2C1 + case 1: + priv = (struct stm32wb_i2c_priv_s *)&stm32wb_i2c1_priv; + break; +#endif +#ifdef CONFIG_STM32WB_I2C3 + case 3: + priv = (struct stm32wb_i2c_priv_s *)&stm32wb_i2c3_priv; + break; +#endif + default: + return NULL; + } + + /* Allocate instance */ + + if (!(inst = kmm_malloc(sizeof(struct stm32wb_i2c_inst_s)))) + { + return NULL; + } + + /* Initialize instance */ + + inst->ops = &stm32wb_i2c_ops; + inst->priv = priv; + + /* Init private data for the first time, increment refs count, + * power-up hardware and configure GPIOs. + */ + + nxmutex_lock(&priv->lock); + if (priv->refs++ == 0) + { + stm32wb_i2c_init(priv); + +#ifdef CONFIG_PM + /* Register to receive power management callbacks */ + + DEBUGVERIFY(pm_register(&priv->pm_cb)); +#endif + } + + nxmutex_unlock(&priv->lock); + return (struct i2c_master_s *)inst; +} + +/**************************************************************************** + * Name: stm32wb_i2cbus_uninitialize + * + * Description: + * Uninitialize an I2C bus + * + ****************************************************************************/ + +int stm32wb_i2cbus_uninitialize(struct i2c_master_s *dev) +{ + struct stm32wb_i2c_priv_s *priv; + + DEBUGASSERT(dev); + priv = ((struct stm32wb_i2c_inst_s *)dev)->priv; + + /* Decrement refs and check for underflow */ + + if (priv->refs == 0) + { + return ERROR; + } + + nxmutex_lock(&priv->lock); + if (--priv->refs) + { + nxmutex_unlock(&priv->lock); + kmm_free(dev); + return OK; + } + +#ifdef CONFIG_PM + /* Unregister power management callbacks */ + + pm_unregister(&priv->pm_cb); +#endif + + /* Disable power and other HW resource (GPIO's) */ + + stm32wb_i2c_deinit(priv); + nxmutex_unlock(&priv->lock); + + kmm_free(dev); + return OK; +} +#endif /* CONFIG_STM32WB_I2C1 || CONFIG_STM32WB_I2C3 */ diff --git a/arch/arm/src/stm32wb/stm32wb_i2c.h b/arch/arm/src/stm32wb/stm32wb_i2c.h new file mode 100644 index 0000000000..d337a28d2c --- /dev/null +++ b/arch/arm/src/stm32wb/stm32wb_i2c.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * arch/arm/src/stm32wb/stm32wb_i2c.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32WB_STM32WB_I2C_H +#define __ARCH_ARM_SRC_STM32WB_STM32WB_I2C_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "chip.h" +#include "hardware/stm32wb_i2c.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* If a dynamic timeout is selected, then a non-negative, non-zero micro- + * seconds per byte value must be provided as well. + */ + +#ifdef CONFIG_STM32WB_I2C_DYNTIMEO +# if CONFIG_STM32WB_I2C_DYNTIMEO_USECPERBYTE < 1 +# warning "Ignoring CONFIG_STM32WB_I2C_DYNTIMEO because of CONFIG_STM32WB_I2C_DYNTIMEO_USECPERBYTE" +# undef CONFIG_STM32WB_I2C_DYNTIMEO +# endif +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32wb_i2cbus_initialize + * + * Description: + * Initialize the selected I2C port. And return a unique instance of struct + * struct i2c_master_s. This function may be called to obtain multiple + * instances of the interface, each of which may be set up with a + * different frequency and slave address. + * + * Input Parameters: + * Port number (for hardware that has multiple I2C interfaces) + * + * Returned Value: + * Valid I2C device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct i2c_master_s *stm32wb_i2cbus_initialize(int port); + +/**************************************************************************** + * Name: stm32wb_i2cbus_uninitialize + * + * Description: + * De-initialize the selected I2C port, and power down the device. + * + * Input Parameters: + * Device structure as returned by the stm32wb_i2cbus_initialize() + * + * Returned Value: + * OK on success, ERROR when internal reference count mismatch or dev + * points to invalid hardware device. + * + ****************************************************************************/ + +int stm32wb_i2cbus_uninitialize(struct i2c_master_s *dev); + +#endif /* __ARCH_ARM_SRC_STM32WB_STM32WB_I2C_H */