From d4f6cc229d55a0c252ba6a821b9f1789e53d4bbc Mon Sep 17 00:00:00 2001 From: Kyle Wilson Date: Wed, 13 Nov 2024 13:48:57 -0600 Subject: [PATCH] STM32H5 I2C Driver Added I2C driver for the STM32H5. This driver uses the STM32H7 I2C driver as a base. The primary difference is setclock dynamically sets the I2C TIMINGR register instead of using hardcoded values. This allows the I2C peripherals to use any of the input clocks and set to any speed 0-1MHz. Additionally, Kconfig options were made available to set the Digital Noise Filter (DNF), Analog Noise Filter, I2C Clock source (i2c_ker_ck), as well as set i2c rise/fall times which are crucial to timing. Care must be taken when setting the clock source and filters, as not all settings are compatible with all i2c clock frequencies. --- arch/arm/src/stm32h5/Kconfig | 232 ++ arch/arm/src/stm32h5/Make.defs | 4 + .../src/stm32h5/hardware/stm32h56xxx_pinmap.h | 36 + .../arm/src/stm32h5/hardware/stm32h5xxx_i2c.h | 136 +- arch/arm/src/stm32h5/stm32.h | 1 + arch/arm/src/stm32h5/stm32_i2c.c | 3130 +++++++++++++++++ arch/arm/src/stm32h5/stm32_i2c.h | 89 + 7 files changed, 3560 insertions(+), 68 deletions(-) create mode 100644 arch/arm/src/stm32h5/stm32_i2c.c create mode 100644 arch/arm/src/stm32h5/stm32_i2c.h diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig index b8dbbaa895c88..e6425fda5a1e1 100644 --- a/arch/arm/src/stm32h5/Kconfig +++ b/arch/arm/src/stm32h5/Kconfig @@ -366,6 +366,30 @@ config STM32H5_LPUART1 select ARCH_HAVE_SERIAL_TERMIOS select STM32H5_USART +config STM32H5_I2C + bool + default n + +config STM32H5_I2C1 + bool "I2C1" + default n + select STM32H5_I2C + +config STM32H5_I2C2 + bool "I2C2" + default n + select STM32H5_I2C + +config STM32H5_I2C3 + bool "I2C3" + default n + select STM32H5_I2C + +config STM32H5_I2C4 + bool "I2C4" + default n + select STM32H5_I2C + endmenu @@ -1061,4 +1085,212 @@ config STM32H5_NO_PHY endmenu # Ethernet MAC Configuration + +menu "I2C Configuration" + depends on STM32H5_I2C + +menu "Clock Selection" + +choice + depends on STM32H5_I2C1 + prompt "I2C1 Input Clock Selection" + default STM32H5_I2C1_CLK_PCLK1 +config STM32H5_I2C1_CLK_CSI + bool "CSI" +config STM32H5_I2C1_CLK_HSI + bool "HSI" +config STM32H5_I2C1_CLK_PCLK1 + bool "PCLK1" +config STM32H5_I2C1_CLK_PLL3R + bool "PLL3R" +endchoice +choice + depends on STM32H5_I2C2 + prompt "I2C2 Input Clock Selection" + default STM32H5_I2C2_CLK_PCLK1 +config STM32H5_I2C2_CLK_CSI + bool "CSI" +config STM32H5_I2C2_CLK_HSI + bool "HSI" +config STM32H5_I2C2_CLK_PCLK1 + bool "PCLK1" +config STM32H5_I2C2_CLK_PLL3R + bool "PLL3R" +endchoice +choice + depends on STM32H5_I2C3 + prompt "I2C3 Input Clock Selection" + default STM32H5_I2C3_CLK_PCLK3 +config STM32H5_I2C3_CLK_CSI + bool "CSI" +config STM32H5_I2C3_CLK_HSI + bool "HSI" +config STM32H5_I2C3_CLK_PCLK3 + bool "PCLK3" +config STM32H5_I2C3_CLK_PLL3R + bool "PLL3R" +endchoice +choice + depends on STM32H5_I2C4 + prompt "I2C4 Input Clock Selection" + default STM32H5_I2C4_CLK_PCLK3 +config STM32H5_I2C4_CLK_CSI + bool "CSI" +config STM32H5_I2C4_CLK_HSI + bool "HSI" +config STM32H5_I2C4_CLK_PCLK3 + bool "PCLK3" +config STM32H5_I2C4_CLK_PLL3R + bool "PLL3R" +endchoice + +endmenu + +menu "Rise/Fall Override" +config STM32H5_I2C1_RF_OVERRIDE + bool "I2C1" + default n + depends on STM32H5_I2C1 +config STM32H5_I2C2_RF_OVERRIDE + bool "I2C2" + default n + depends on STM32H5_I2C2 +config STM32H5_I2C3_RF_OVERRIDE + bool "I2C3" + default n + depends on STM32H5_I2C3 +config STM32H5_I2C4_RF_OVERRIDE + bool "I2C4" + default n + depends on STM32H5_I2C4 + +menu "Rise/Fall Values" +config STM32H5_I2C1_RISE + int "I2C1 Rise Time (ns)" + range 0 1000 + default 20 + depends on STM32H5_I2C1_RF_OVERRIDE +config STM32H5_I2C1_FALL + int "I2C1 Fall Time (ns)" + range 0 300 + default 20 + depends on STM32H5_I2C1_RF_OVERRIDE +config STM32H5_I2C2_RISE + int "I2C2 Rise Time (ns)" + range 0 1000 + default 20 + depends on STM32H5_I2C2_RF_OVERRIDE +config STM32H5_I2C2_FALL + int "I2C2 Fall Time (ns)" + range 0 300 + default 20 + depends on STM32H5_I2C2_RF_OVERRIDE +config STM32H5_I2C3_RISE + int "I2C3 Rise Time (ns)" + range 0 1000 + default 20 + depends on STM32H5_I2C3_RF_OVERRIDE +config STM32H5_I2C3_FALL + int "I2C3 Fall Time (ns)" + range 0 300 + default 20 + depends on STM32H5_I2C3_RF_OVERRIDE +config STM32H5_I2C4_RISE + int "I2C4 Rise Time (ns)" + range 0 1000 + default 20 + depends on STM32H5_I2C4_RF_OVERRIDE +config STM32H5_I2C4_FALL + int "I2C4 Fall Time (ns)" + range 0 300 + default 20 + depends on STM32H5_I2C4_RF_OVERRIDE +endmenu +endmenu + +menu "Filtering" + +menu "Digital Filters" +config STM32H5_I2C1_DNF + int "I2C1 Digital Noise Filter" + range 0 15 + default 0 + depends on STM32H5_I2C1 +config STM32H5_I2C2_DNF + int "I2C2 Digital Noise Filter" + range 0 15 + default 0 + depends on STM32H5_I2C2 +config STM32H5_I2C3_DNF + int "I2C3 Digital Noise Filter" + range 0 15 + default 0 + depends on STM32H5_I2C3 +config STM32H5_I2C4_DNF + int "I2C4 Digital Noise Filter" + range 0 15 + default 0 + depends on STM32H5_I2C4 +endmenu + +menu "Analog Filters" +config STM32H5_I2C1_ANFOFF + int "Turn off I2C1 Analog Filter (0=on, 1=off)" + default 1 + range 0 1 + depends on STM32H5_I2C1 + +config STM32H5_I2C2_ANFOFF + int "Turn off I2C2 Analog Filter (0=on, 1=off)" + default 1 + range 0 1 + depends on STM32H5_I2C2 + +config STM32H5_I2C3_ANFOFF + int "Turn off I2C3 Analog Filter (0=on, 1=off)" + default 1 + range 0 1 + depends on STM32H5_I2C3 + +config STM32H5_I2C4_ANFOFF + int "Turn off I2C4 Analog Filter (0=on, 1=off)" + default 1 + range 0 1 + depends on STM32H5_I2C4 +endmenu + +endmenu + +config STM32H5_I2C_DYNTIMEO + bool "Use dynamic timeouts" + default n + depends on STM32H5_I2C + +config STM32H5_I2C_DYNTIMEO_USECPERBYTE + int "Timeout Microseconds per Byte" + default 500 + depends on STM32H5_I2C_DYNTIMEO + +config STM32H5_I2C_DYNTIMEO_STARTSTOP + int "Timeout for Start/Stop (Milliseconds)" + default 1000 + depends on STM32H5_I2C_DYNTIMEO + +config STM32H5_I2CTIMEOSEC + int "Timeout seconds" + default 0 + depends on STM32H5_I2C + +config STM32H5_I2CTIMEOMS + int "Timeout Milliseconds" + default 500 + depends on STM32H5_I2C && !STM32H5_I2C_DYNTIMEO + +config STM32H5_I2CTIMEOTICKS + int "Timeout for Done and Stop (ticks)" + default 500 + depends on STM32H5_I2C && !STM32H5_I2C_DYNTIMEO + +endmenu # "I2C Configuration" + endif # ARCH_CHIP_STM32H5 diff --git a/arch/arm/src/stm32h5/Make.defs b/arch/arm/src/stm32h5/Make.defs index 56cce6201a371..1c3e72053fd35 100644 --- a/arch/arm/src/stm32h5/Make.defs +++ b/arch/arm/src/stm32h5/Make.defs @@ -44,6 +44,10 @@ ifeq ($(CONFIG_TIMER),y) CHIP_CSRCS += stm32h5_tim_lowerhalf.c endif +ifeq ($(CONFIG_STM32H5_I2C),y) +CHIP_CSRCS += stm32_i2c.c +endif + # Required chip type specific files ifeq ($(CONFIG_STM32H5_STM32H5XXXX),y) diff --git a/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h b/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h index ee3ea1b1c176e..c857bb18e0d88 100644 --- a/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h +++ b/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h @@ -100,6 +100,42 @@ #define GPIO_MCO_0 (GPIO_ALT|GPIO_AF0|GPIO_PORTA|GPIO_PIN8) +/* I2C */ + +#define GPIO_I2C1_SDA_1 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN7) +#define GPIO_I2C1_SDA_2 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN9) +#define GPIO_I2C1_SCL_1 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN6) +#define GPIO_I2C1_SCL_2 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN8) +#define GPIO_I2C1_SMBA_1 (GPIO_ALT|GPIO_AF4|GPIO_PORTB|GPIO_PIN5) + +#define GPIO_I2C2_SDA_1 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN11) +#define GPIO_I2C2_SDA_2 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN12) +#define GPIO_I2C2_SDA_3 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN3) +#define GPIO_I2C2_SDA_4 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTH|GPIO_PIN5) +#define GPIO_I2C2_SCL_1 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN10) +#define GPIO_I2C2_SCL_2 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTF|GPIO_PIN1) +#define GPIO_I2C2_SCL_3 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTH|GPIO_PIN4) +#define GPIO_I2C2_SMBA_1 (GPIO_ALT|GPIO_AF4|GPIO_PORTH|GPIO_PIN6) + +#define GPIO_I2C3_SDA_1 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTC|GPIO_PIN9) +#define GPIO_I2C3_SDA_2 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTH|GPIO_PIN8) +#define GPIO_I2C3_SCL_1 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTA|GPIO_PIN8) +#define GPIO_I2C3_SCL_2 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTH|GPIO_PIN7) +#define GPIO_I2C3_SMBA_1 (GPIO_ALT|GPIO_AF4|GPIO_PORTA|GPIO_PIN9) +#define GPIO_I2C3_SMBA_2 (GPIO_ALT|GPIO_AF4|GPIO_PORTH|GPIO_PIN9) + +#define GPIO_I2C4_SDA_1 (GPIO_ALT|GPIO_AF6|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN7) +#define GPIO_I2C4_SDA_2 (GPIO_ALT|GPIO_AF6|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN9) +#define GPIO_I2C4_SDA_3 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTD|GPIO_PIN13) +#define GPIO_I2C4_SDA_4 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTF|GPIO_PIN15) +#define GPIO_I2C4_SCL_1 (GPIO_ALT|GPIO_AF6|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN6) +#define GPIO_I2C4_SCL_2 (GPIO_ALT|GPIO_AF6|GPIO_OPENDRAIN|GPIO_PORTB|GPIO_PIN8) +#define GPIO_I2C4_SCL_3 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTD|GPIO_PIN12) +#define GPIO_I2C4_SCL_4 (GPIO_ALT|GPIO_AF4|GPIO_OPENDRAIN|GPIO_PORTF|GPIO_PIN5) +#define GPIO_I2C4_SMBA_1 (GPIO_ALT|GPIO_AF6|GPIO_PORTB|GPIO_PIN5) +#define GPIO_I2C4_SMBA_2 (GPIO_ALT|GPIO_AF4|GPIO_PORTD|GPIO_PIN11) +#define GPIO_I2C4_SMBA_3 (GPIO_ALT|GPIO_AF4|GPIO_PORTF|GPIO_PIN13) + /* JTAG */ #define GPIO_JTCK_SWCLK_0 (GPIO_ALT|GPIO_AF0|GPIO_PORTA|GPIO_PIN14) diff --git a/arch/arm/src/stm32h5/hardware/stm32h5xxx_i2c.h b/arch/arm/src/stm32h5/hardware/stm32h5xxx_i2c.h index 5335dd8327af9..7e2a9c8df5096 100644 --- a/arch/arm/src/stm32h5/hardware/stm32h5xxx_i2c.h +++ b/arch/arm/src/stm32h5/hardware/stm32h5xxx_i2c.h @@ -27,74 +27,74 @@ /* Register Offsets *********************************************************/ -#define STM32_I2CCR1_OFFSET 0x0000 /* Control register 1 (32-bit) */ -#define STM32_I2CCR2_OFFSET 0x0004 /* Control register 2 (32-bit) */ -#define STM32_I2COAR1_OFFSET 0x0008 /* Own address register 1 (16-bit) */ -#define STM32_I2COAR2_OFFSET 0x000c /* Own address register 2 (16-bit) */ -#define STM32_I2CTIMINGR_OFFSET 0x0010 /* Timing register */ -#define STM32_I2CTIMEOUTR_OFFSET 0x0014 /* Timeout register */ -#define STM32_I2CISR_OFFSET 0x0018 /* Interrupt and Status register */ -#define STM32_I2CICR_OFFSET 0x001c /* Interrupt clear register */ -#define STM32_I2CPECR_OFFSET 0x0020 /* Packet error checking register */ -#define STM32_I2CRXDR_OFFSET 0x0024 /* Receive data register */ -#define STM32_I2CTXDR_OFFSET 0x0028 /* Transmit data register */ +#define STM32_I2C_CR1_OFFSET 0x0000 /* Control register 1 (32-bit) */ +#define STM32_I2C_CR2_OFFSET 0x0004 /* Control register 2 (32-bit) */ +#define STM32_I2C_OAR1_OFFSET 0x0008 /* Own address register 1 (16-bit) */ +#define STM32_I2C_OAR2_OFFSET 0x000c /* Own address register 2 (16-bit) */ +#define STM32_I2C_TIMINGR_OFFSET 0x0010 /* Timing register */ +#define STM32_I2C_TIMEOUTR_OFFSET 0x0014 /* Timeout register */ +#define STM32_I2C_ISR_OFFSET 0x0018 /* Interrupt and Status register */ +#define STM32_I2C_ICR_OFFSET 0x001c /* Interrupt clear register */ +#define STM32_I2C_PECR_OFFSET 0x0020 /* Packet error checking register */ +#define STM32_I2C_RXDR_OFFSET 0x0024 /* Receive data register */ +#define STM32_I2C_TXDR_OFFSET 0x0028 /* Transmit data register */ /* Register Addresses *******************************************************/ #if STM32H5_NI2C > 0 -# define STM32_I2C1_CR1 (STM32_I2C1_BASE+STM32_I2CCR1_OFFSET) -# define STM32_I2C1_CR2 (STM32_I2C1_BASE+STM32_I2CCR2_OFFSET) -# define STM32_I2C1_OAR1 (STM32_I2C1_BASE+STM32_I2COAR1_OFFSET) -# define STM32_I2C1_OAR2 (STM32_I2C1_BASE+STM32_I2COAR2_OFFSET) -# define STM32_I2C1_TIMINGR (STM32_I2C1_BASE+STM32_I2CTIMINGR_OFFSET) -# define STM32_I2C1_TIMEOUTR (STM32_I2C1_BASE+STM32_I2CTIMEOUTR_OFFSET) -# define STM32_I2C1_ISR (STM32_I2C1_BASE+STM32_I2CISR_OFFSET) -# define STM32_I2C1_ICR (STM32_I2C1_BASE+STM32_I2CICR_OFFSET) -# define STM32_I2C1_PECR (STM32_I2C1_BASE+STM32_I2CPECR_OFFSET) -# define STM32_I2C1_RXDR (STM32_I2C1_BASE+STM32_I2CRXDR_OFFSET) -# define STM32_I2C1_TXDR (STM32_I2C1_BASE+STM32_I2CTXDR_OFFSET) +# define STM32_I2C1_CR1 (STM32_I2C1_BASE+STM32_I2C_CR1_OFFSET) +# define STM32_I2C1_CR2 (STM32_I2C1_BASE+STM32_I2C_CR2_OFFSET) +# define STM32_I2C1_OAR1 (STM32_I2C1_BASE+STM32_I2C_OAR1_OFFSET) +# define STM32_I2C1_OAR2 (STM32_I2C1_BASE+STM32_I2C_OAR2_OFFSET) +# define STM32_I2C1_TIMINGR (STM32_I2C1_BASE+STM32_I2C_TIMINGR_OFFSET) +# define STM32_I2C1_TIMEOUTR (STM32_I2C1_BASE+STM32_I2C_TIMEOUTR_OFFSET) +# define STM32_I2C1_ISR (STM32_I2C1_BASE+STM32_I2C_ISR_OFFSET) +# define STM32_I2C1_ICR (STM32_I2C1_BASE+STM32_I2C_ICR_OFFSET) +# define STM32_I2C1_PECR (STM32_I2C1_BASE+STM32_I2C_PECR_OFFSET) +# define STM32_I2C1_RXDR (STM32_I2C1_BASE+STM32_I2C_RXDR_OFFSET) +# define STM32_I2C1_TXDR (STM32_I2C1_BASE+STM32_I2C_TXDR_OFFSET) #endif #if STM32H5_NI2C > 1 -# define STM32_I2C2_CR1 (STM32_I2C2_BASE+STM32_I2CCR1_OFFSET) -# define STM32_I2C2_CR2 (STM32_I2C2_BASE+STM32_I2CCR2_OFFSET) -# define STM32_I2C2_OAR1 (STM32_I2C2_BASE+STM32_I2COAR1_OFFSET) -# define STM32_I2C2_OAR2 (STM32_I2C2_BASE+STM32_I2COAR2_OFFSET) -# define STM32_I2C2_TIMINGR (STM32_I2C2_BASE+STM32_I2CTIMINGR_OFFSET) -# define STM32_I2C2_TIMEOUTR (STM32_I2C2_BASE+STM32_I2CTIMEOUTR_OFFSET) -# define STM32_I2C2_ISR (STM32_I2C2_BASE+STM32_I2CISR_OFFSET) -# define STM32_I2C2_ICR (STM32_I2C2_BASE+STM32_I2CICR_OFFSET) -# define STM32_I2C2_PECR (STM32_I2C2_BASE+STM32_I2CPECR_OFFSET) -# define STM32_I2C2_RXDR (STM32_I2C2_BASE+STM32_I2CRXDR_OFFSET) -# define STM32_I2C2_TXDR (STM32_I2C2_BASE+STM32_I2CTXDR_OFFSET) +# define STM32_I2C2_CR1 (STM32_I2C2_BASE+STM32_I2C_CR1_OFFSET) +# define STM32_I2C2_CR2 (STM32_I2C2_BASE+STM32_I2C_CR2_OFFSET) +# define STM32_I2C2_OAR1 (STM32_I2C2_BASE+STM32_I2C_OAR1_OFFSET) +# define STM32_I2C2_OAR2 (STM32_I2C2_BASE+STM32_I2C_OAR2_OFFSET) +# define STM32_I2C2_TIMINGR (STM32_I2C2_BASE+STM32_I2C_TIMINGR_OFFSET) +# define STM32_I2C2_TIMEOUTR (STM32_I2C2_BASE+STM32_I2C_TIMEOUTR_OFFSET) +# define STM32_I2C2_ISR (STM32_I2C2_BASE+STM32_I2C_ISR_OFFSET) +# define STM32_I2C2_ICR (STM32_I2C2_BASE+STM32_I2C_ICR_OFFSET) +# define STM32_I2C2_PECR (STM32_I2C2_BASE+STM32_I2C_PECR_OFFSET) +# define STM32_I2C2_RXDR (STM32_I2C2_BASE+STM32_I2C_RXDR_OFFSET) +# define STM32_I2C2_TXDR (STM32_I2C2_BASE+STM32_I2C_TXDR_OFFSET) #endif #if STM32H5_NI2C > 2 -# define STM32_I2C3_CR1 (STM32_I2C3_BASE+STM32_I2CCR1_OFFSET) -# define STM32_I2C3_CR2 (STM32_I2C3_BASE+STM32_I2CCR2_OFFSET) -# define STM32_I2C3_OAR1 (STM32_I2C3_BASE+STM32_I2COAR1_OFFSET) -# define STM32_I2C3_OAR2 (STM32_I2C3_BASE+STM32_I2COAR2_OFFSET) -# define STM32_I2C3_TIMINGR (STM32_I2C3_BASE+STM32_I2CTIMINGR_OFFSET) -# define STM32_I2C3_TIMEOUTR (STM32_I2C3_BASE+STM32_I2CTIMEOUTR_OFFSET) -# define STM32_I2C3_ISR (STM32_I2C3_BASE+STM32_I2CISR_OFFSET) -# define STM32_I2C3_ICR (STM32_I2C3_BASE+STM32_I2CICR_OFFSET) -# define STM32_I2C3_PECR (STM32_I2C3_BASE+STM32_I2CPECR_OFFSET) -# define STM32_I2C3_RXDR (STM32_I2C3_BASE+STM32_I2CRXDR_OFFSET) -# define STM32_I2C3_TXDR (STM32_I2C3_BASE+STM32_I2CTXDR_OFFSET) +# define STM32_I2C3_CR1 (STM32_I2C3_BASE+STM32_I2C_CR1_OFFSET) +# define STM32_I2C3_CR2 (STM32_I2C3_BASE+STM32_I2C_CR2_OFFSET) +# define STM32_I2C3_OAR1 (STM32_I2C3_BASE+STM32_I2C_OAR1_OFFSET) +# define STM32_I2C3_OAR2 (STM32_I2C3_BASE+STM32_I2C_OAR2_OFFSET) +# define STM32_I2C3_TIMINGR (STM32_I2C3_BASE+STM32_I2C_TIMINGR_OFFSET) +# define STM32_I2C3_TIMEOUTR (STM32_I2C3_BASE+STM32_I2C_TIMEOUTR_OFFSET) +# define STM32_I2C3_ISR (STM32_I2C3_BASE+STM32_I2C_ISR_OFFSET) +# define STM32_I2C3_ICR (STM32_I2C3_BASE+STM32_I2C_ICR_OFFSET) +# define STM32_I2C3_PECR (STM32_I2C3_BASE+STM32_I2C_PECR_OFFSET) +# define STM32_I2C3_RXDR (STM32_I2C3_BASE+STM32_I2C_RXDR_OFFSET) +# define STM32_I2C3_TXDR (STM32_I2C3_BASE+STM32_I2C_TXDR_OFFSET) #endif #if STM32H5_NI2C > 3 -# define STM32_I2C4_CR1 (STM32_I2C4_BASE+STM32_I2CCR1_OFFSET) -# define STM32_I2C4_CR2 (STM32_I2C4_BASE+STM32_I2CCR2_OFFSET) -# define STM32_I2C4_OAR1 (STM32_I2C4_BASE+STM32_I2COAR1_OFFSET) -# define STM32_I2C4_OAR2 (STM32_I2C4_BASE+STM32_I2COAR2_OFFSET) -# define STM32_I2C4_TIMINGR (STM32_I2C4_BASE+STM32_I2CTIMINGR_OFFSET) -# define STM32_I2C4_TIMEOUTR (STM32_I2C4_BASE+STM32_I2CTIMEOUTR_OFFSET) -# define STM32_I2C4_ISR (STM32_I2C4_BASE+STM32_I2CISR_OFFSET) -# define STM32_I2C4_ICR (STM32_I2C4_BASE+STM32_I2CICR_OFFSET) -# define STM32_I2C4_PECR (STM32_I2C4_BASE+STM32_I2CPECR_OFFSET) -# define STM32_I2C4_RXDR (STM32_I2C4_BASE+STM32_I2CRXDR_OFFSET) -# define STM32_I2C4_TXDR (STM32_I2C4_BASE+STM32_I2CTXDR_OFFSET) +# define STM32_I2C4_CR1 (STM32_I2C4_BASE+STM32_I2C_CR1_OFFSET) +# define STM32_I2C4_CR2 (STM32_I2C4_BASE+STM32_I2C_CR2_OFFSET) +# define STM32_I2C4_OAR1 (STM32_I2C4_BASE+STM32_I2C_OAR1_OFFSET) +# define STM32_I2C4_OAR2 (STM32_I2C4_BASE+STM32_I2C_OAR2_OFFSET) +# define STM32_I2C4_TIMINGR (STM32_I2C4_BASE+STM32_I2C_TIMINGR_OFFSET) +# define STM32_I2C4_TIMEOUTR (STM32_I2C4_BASE+STM32_I2C_TIMEOUTR_OFFSET) +# define STM32_I2C4_ISR (STM32_I2C4_BASE+STM32_I2C_ISR_OFFSET) +# define STM32_I2C4_ICR (STM32_I2C4_BASE+STM32_I2C_ICR_OFFSET) +# define STM32_I2C4_PECR (STM32_I2C4_BASE+STM32_I2C_PECR_OFFSET) +# define STM32_I2C4_RXDR (STM32_I2C4_BASE+STM32_I2C_RXDR_OFFSET) +# define STM32_I2C4_TXDR (STM32_I2C4_BASE+STM32_I2C_TXDR_OFFSET) #endif /* Register Bitfield Definitions ********************************************/ @@ -119,12 +119,12 @@ #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 */ +#define I2C_CR1_FMP (1 << 24) /* Bit 24: FMP enable */ /* Control register 2 */ @@ -204,6 +204,18 @@ # define I2C_TIMEOUTR_B(n) ((n) << I2C_TIMEOUTR_B_SHIFT) #define I2C_TIMEOUTR_TEXTEN (1 << 31) /* Bits 31: Extended clock timeout enable */ +/* 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) + /* Interrupt and Status register and interrupt clear register */ /* Common interrupt bits */ @@ -218,18 +230,6 @@ #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 \ diff --git a/arch/arm/src/stm32h5/stm32.h b/arch/arm/src/stm32h5/stm32.h index 28d97cac6f897..d1c0b94c9fbf5 100644 --- a/arch/arm/src/stm32h5/stm32.h +++ b/arch/arm/src/stm32h5/stm32.h @@ -42,4 +42,5 @@ #include "stm32_rcc.h" #include "stm32_uart.h" #include "stm32_lowputc.h" +#include "stm32_i2c.h" #endif /* __ARCH_ARM_SRC_STM32H5_STM32_H */ diff --git a/arch/arm/src/stm32h5/stm32_i2c.c b/arch/arm/src/stm32h5/stm32_i2c.c new file mode 100644 index 0000000000000..b0eabc8d50704 --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_i2c.c @@ -0,0 +1,3130 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_i2c.c + * STM32 I2C Hardware Layer - Device Driver + * + * Copyright (C) 2011 Uros Platise. All rights reserved. + * Author: Uros Platise + * + * With extensions and modifications for the F1, F2, and F4 by: + * + * Copyright (C) 2016-2017 Gregory Nutt. All rights reserved. + * Authors: Gregory Nutt + * John Wharington + * David Sidrane + * Bob Feretich + * Modified for STM32H7 by Mateusz Szafoni + * + * Major rewrite of ISR and supporting methods, including support + * for NACK and RELOAD by: + * + * Copyright (c) 2016 Doug Vetter. All rights reserved. + * Author: Doug Vetter + * + * Major rewrite of setclock to dynamically determine TIMINGR. + * Written by: + * + * Copyright (c) 2024 Kyle Wilson. All rights reserved. + * Author: Kyle Wilson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/* -------------------------------------------------------------------------- + * + * STM32 H5 I2C Driver + * + * Supports: + * - Master operation: + * Standard-mode (up to 100 kHz) + * Fast-mode (up to 400 kHz) + * Fast-mode Plus (up to 1 MHz) + * fI2CCLK clock source selection is based on + * STM32_RCC_CCIPR4_I2CxSRC + * + * - Multiple instances (shared bus) + * - Interrupt based operation + * - RELOAD support + * + * Unsupported, possible future work: + * - More effective error reporting to higher layers + * - Slave operation + * - Support of fI2CCLK frequencies other than 8MHz, 16MHz, 32MHz, 64MHz + * - Support for other input clocks besides HSI + * - Polled operation (code present but untested) + * - SMBus support + * - Multi-master support + * - IPMI + * + * Test Environment: + * + * - STM32H563ZI on ST Nucleo-144 Board + * + * Operational Status: + * + * All supported features have been tested and found to be operational. + * + * Although the RELOAD capability has been tested as it was required to + * implement the I2C_M_NOSTART flag on F3 hardware, the associated + * logic to support the transfer messages with more than 255 byte + * payloads has not been tested as the author lacked access to a real + * device supporting these types of transfers. + * + * Performance Benchmarks: TBD + * + * Time to transfer two messages, each a byte in length, in addition to + * the START condition, in interrupt mode: + * + * DEBUG enabled (development): TBDms + * Excessive delay here is caused by printing to the console and + * is of no concern. + * + * DEBUG disabled (production): TBSus + * Between Messages: TBDus + * Between Bytes: TBDus + * + * Implementation: + * + * - Device: structure as defined by the nuttx/i2c/i2c.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.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 F3 as an I2C Master and some other IC as a 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: + * + * One of: + * + * CONFIG_STM32H5_STM32H5XXXXX + * + * and one or more interfaces: + * + * CONFIG_STM32H5_I2C1 + * CONFIG_STM32H5_I2C2 + * CONFIG_STM32H5_I2C3 + * CONFIG_STM32H5_I2C4 + * + * To configure the ISR timeout using fixed values + * (CONFIG_STM32H5_I2C_DYNTIMEO=n): + * + * CONFIG_STM32H5_I2CTIMEOSEC (Timeout in seconds) + * CONFIG_STM32H5_I2CTIMEOMS (Timeout in milliseconds) + * CONFIG_STM32H5_I2CTIMEOTICKS (Timeout in ticks) + * + * To configure the ISR timeout using dynamic values + * (CONFIG_STM32H5_I2C_DYNTIMEO=y): + * + * CONFIG_STM32H5_I2C_DYNTIMEO_USECPERBYTE + * (Timeout in microseconds per byte) + * CONFIG_STM32H5_I2C_DYNTIMEO_STARTSTOP + * (Timeout for start/stop in msec) + * + * 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 + */ + +/**************************************************************************** + * 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 "stm32_rcc.h" +#include "stm32_i2c.h" +#include "stm32_gpio.h" +#include "hardware/stm32_pinmap.h" + +/* At least one I2C peripheral must be enabled */ + +#if defined(CONFIG_STM32H5_I2C1) || defined(CONFIG_STM32H5_I2C2) || \ + defined(CONFIG_STM32H5_I2C3) || defined(CONFIG_STM32H5_I2C4) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#undef INVALID_CLOCK_SOURCE + +/* 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_STM32H5_I2CTIMEOSEC) && !defined(CONFIG_STM32H5_I2CTIMEOMS) +# define CONFIG_STM32H5_I2CTIMEOSEC 0 +# define CONFIG_STM32H5_I2CTIMEOMS 500 /* Default is 500 milliseconds */ +# warning "Using Default 500 Ms Timeout" +#elif !defined(CONFIG_STM32H5_I2CTIMEOSEC) +# define CONFIG_STM32H5_I2CTIMEOSEC 0 /* User provided milliseconds */ +#elif !defined(CONFIG_STM32H5_I2CTIMEOMS) +# define CONFIG_STM32H5_I2CTIMEOMS 0 /* User provided seconds */ +#endif + +/* Interrupt wait time timeout in system timer ticks */ + +#ifndef CONFIG_STM32H5_I2CTIMEOTICKS +# define CONFIG_STM32H5_I2CTIMEOTICKS \ + (SEC2TICK(CONFIG_STM32H5_I2CTIMEOSEC) + MSEC2TICK(CONFIG_STM32H5_I2CTIMEOMS)) +#endif + +#ifndef CONFIG_STM32H5_I2C_DYNTIMEO_STARTSTOP +# define CONFIG_STM32H5_I2C_DYNTIMEO_STARTSTOP TICK2USEC(CONFIG_STM32H5_I2CTIMEOTICKS) +#endif + +/* Macros to convert a 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) + +/* Unused bit in I2c_ISR used to communicate a bad state has occurred in + * the isr processing + */ + +#define I2C_INT_BAD_STATE 0x8000000 + +/* 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 stm32_i2c_tracereset(p) +# define stm32_i2c_tracenew(p,s) +# define stm32_i2c_traceevent(p,e,a) +# define stm32_i2c_tracedump(p) +#endif + +#ifndef CONFIG_I2C_NTRACE +# define CONFIG_I2C_NTRACE 32 +#endif + +/* I2C Timing */ + +/* Input delay introduced by Analog Filter in nanoseconds */ + +#define STM32_I2C_TAF_MIN 50 +#define STM32_I2C_TAF_MAX 160 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Interrupt state */ + +enum stm32_intstate_e +{ + INTSTATE_IDLE = 0, /* No I2C activity */ + INTSTATE_WAITING, /* Waiting for completion of interrupt activity */ + INTSTATE_DONE, /* Interrupt activity complete */ +}; + +/* Trace events */ + +enum stm32_trace_e +{ + I2CEVENT_NONE = 0, + I2CEVENT_STATE_ERROR, + I2CEVENT_ISR_SHUTDOWN, + I2CEVENT_ISR_CALL, + I2CEVENT_ISR_EMPTY_CALL, + I2CEVENT_MSG_HANDLING, + I2CEVENT_POLL_NOT_READY, + I2CEVENT_EMPTY_MSG, + I2CEVENT_START, + I2CEVENT_ADDRESS_ACKED, + I2CEVENT_ADDRESS_NACKED, + I2CEVENT_NACK, + I2CEVENT_READ, + I2CEVENT_READ_ERROR, + I2CEVENT_WRITE_TO_DR, + I2CEVENT_WRITE_STOP, + I2CEVENT_WRITE_RESTART, + I2CEVENT_WRITE_NO_RESTART, + I2CEVENT_WRITE_ERROR, + I2CEVENT_WRITE_FLAG_ERROR, + I2CEVENT_TC_RESTART, + I2CEVENT_TC_NO_RESTART +}; + +/* Trace data */ + +struct stm32_trace_s +{ + uint32_t status; /* I2C 32-bit SR2|SR1 status */ + uint32_t count; /* Interrupt count when status change */ + enum stm32_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 stm32_i2c_config_s +{ + uint32_t base; /* I2C base address */ + uint32_t clk_reg; /* Clock enable register */ + uint32_t clk_bit; /* Clock enable bit */ + uint32_t reset_reg; /* Reset register */ + 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 stm32_i2c_priv_s +{ + /* Port configuration */ + + const struct stm32_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 stm32_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 stm32_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 stm32_i2c_inst_s +{ + const struct i2c_ops_s *ops; /* Standard I2C operations */ + struct stm32_i2c_priv_s *priv; /* Common driver private data structure */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static inline uint16_t stm32_i2c_getreg(struct stm32_i2c_priv_s *priv, + uint8_t offset); +static inline void stm32_i2c_putreg(struct stm32_i2c_priv_s *priv, + uint8_t offset, uint16_t value); +static inline void stm32_i2c_putreg32(struct stm32_i2c_priv_s *priv, + uint8_t offset, uint32_t value); +static inline void stm32_i2c_modifyreg32(struct stm32_i2c_priv_s *priv, + uint8_t offset, uint32_t clearbits, + uint32_t setbits); +#ifdef CONFIG_STM32H5_I2C_DYNTIMEO +static uint32_t stm32_i2c_toticks(int msgc, struct i2c_msg_s *msgs); +#endif /* CONFIG_STM32H5_I2C_DYNTIMEO */ +static inline int stm32_i2c_sem_waitdone(struct stm32_i2c_priv_s *priv); +static inline void stm32_i2c_sem_waitstop(struct stm32_i2c_priv_s *priv); +#ifdef CONFIG_I2C_TRACE +static void stm32_i2c_tracereset(struct stm32_i2c_priv_s *priv); +static void stm32_i2c_tracenew(struct stm32_i2c_priv_s *priv, + uint32_t status); +static void stm32_i2c_traceevent(struct stm32_i2c_priv_s *priv, + enum stm32_trace_e event, uint32_t parm); +static void stm32_i2c_tracedump(struct stm32_i2c_priv_s *priv); +#endif /* CONFIG_I2C_TRACE */ +static void stm32_i2c_setclock(struct stm32_i2c_priv_s *priv, + uint32_t frequency); +static inline void stm32_i2c_sendstart(struct stm32_i2c_priv_s *priv); +static inline void stm32_i2c_sendstop(struct stm32_i2c_priv_s *priv); +static inline +uint32_t stm32_i2c_getstatus(struct stm32_i2c_priv_s *priv); +static int stm32_i2c_isr_process(struct stm32_i2c_priv_s *priv); +#ifndef CONFIG_I2C_POLLED +static int stm32_i2c_isr(int irq, void *context, void *arg); +#endif +static int stm32_i2c_init(struct stm32_i2c_priv_s *priv); +static int stm32_i2c_deinit(struct stm32_i2c_priv_s *priv); + +static int stm32_i2c_process(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count); +static int stm32_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count); +#ifdef CONFIG_I2C_RESET +static int stm32_i2c_reset(struct i2c_master_s *dev); +#endif +#ifdef CONFIG_PM +static int stm32_i2c_pm_prepare(struct pm_callback_s *cb, int domain, + enum pm_state_e pmstate); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_I2C1 +static const struct stm32_i2c_config_s stm32_i2c1_config = +{ + .base = STM32_I2C1_BASE, + .clk_reg = STM32_RCC_APB1LENR, + .clk_bit = RCC_APB1LENR_I2C1EN, + .reset_reg = STM32_RCC_APB1LRSTR, + .reset_bit = RCC_APB1LRSTR_I2C1RST, + .scl_pin = GPIO_I2C1_SCL, + .sda_pin = GPIO_I2C1_SDA, +#ifndef CONFIG_I2C_POLLED + .ev_irq = STM32_IRQ_I2C1_EV, + .er_irq = STM32_IRQ_I2C1_ER +#endif +}; + +static struct stm32_i2c_priv_s stm32_i2c1_priv = +{ + .config = &stm32_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 = stm32_i2c_pm_prepare, +#endif +}; +#endif + +#ifdef CONFIG_STM32H5_I2C2 +static const struct stm32_i2c_config_s stm32_i2c2_config = +{ + .base = STM32_I2C2_BASE, + .clk_reg = STM32_RCC_APB1LENR, + .clk_bit = RCC_APB1LENR_I2C2EN, + .reset_reg = STM32_RCC_APB1LRSTR, + .reset_bit = RCC_APB1LRSTR_I2C2RST, + .scl_pin = GPIO_I2C2_SCL, + .sda_pin = GPIO_I2C2_SDA, +#ifndef CONFIG_I2C_POLLED + .ev_irq = STM32_IRQ_I2C2_EV, + .er_irq = STM32_IRQ_I2C2_ER +#endif +}; + +static struct stm32_i2c_priv_s stm32_i2c2_priv = +{ + .config = &stm32_i2c2_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 = stm32_i2c_pm_prepare, +#endif +}; +#endif + +#ifdef CONFIG_STM32H5_I2C3 +static const struct stm32_i2c_config_s stm32_i2c3_config = +{ + .base = STM32_I2C3_BASE, + .clk_reg = STM32_RCC_APB3ENR, + .clk_bit = RCC_APB3ENR_I2C3EN, + .reset_reg = STM32_RCC_APB3RSTR, + .reset_bit = RCC_APB3RSTR_I2C3RST, + .scl_pin = GPIO_I2C3_SCL, + .sda_pin = GPIO_I2C3_SDA, +#ifndef CONFIG_I2C_POLLED + .ev_irq = STM32_IRQ_I2C3_EV, + .er_irq = STM32_IRQ_I2C3_ER +#endif +}; + +static struct stm32_i2c_priv_s stm32_i2c3_priv = +{ + .config = &stm32_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 = stm32_i2c_pm_prepare, +#endif +}; +#endif + +#ifdef CONFIG_STM32H5_I2C4 +static const struct stm32_i2c_config_s stm32_i2c4_config = +{ + .base = STM32_I2C4_BASE, + .clk_reg = STM32_RCC_APB3ENR, + .clk_bit = RCC_APB3ENR_I2C4EN, + .reset_reg = STM32_RCC_APB3RSTR, + .reset_bit = RCC_APB3RSTR_I2C4RST, + .scl_pin = GPIO_I2C4_SCL, + .sda_pin = GPIO_I2C4_SDA, +#ifndef CONFIG_I2C_POLLED + .ev_irq = STM32_IRQ_I2C4_EV, + .er_irq = STM32_IRQ_I2C4_ER +#endif +}; + +static struct stm32_i2c_priv_s stm32_i2c4_priv = +{ + .config = &stm32_i2c4_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 = stm32_i2c_pm_prepare, +#endif +}; +#endif + +/* Device Structures, Instantiation */ + +static const struct i2c_ops_s stm32_i2c_ops = +{ + .transfer = stm32_i2c_transfer, +#ifdef CONFIG_I2C_RESET + .reset = stm32_i2c_reset, +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32_i2c_getreg + * + * Description: + * Get a 16-bit register value by offset + * + ****************************************************************************/ + +static inline uint16_t stm32_i2c_getreg(struct stm32_i2c_priv_s *priv, + uint8_t offset) +{ + return getreg16(priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32_i2c_getreg32 + * + * Description: + * Get a 32-bit register value by offset + * + ****************************************************************************/ + +static inline uint32_t stm32_i2c_getreg32(struct stm32_i2c_priv_s *priv, + uint8_t offset) +{ + return getreg32(priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32_i2c_putreg + * + * Description: + * Put a 16-bit register value by offset + * + ****************************************************************************/ + +static inline void stm32_i2c_putreg(struct stm32_i2c_priv_s *priv, + uint8_t offset, uint16_t value) +{ + putreg16(value, priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32_i2c_putreg32 + * + * Description: + * Put a 32-bit register value by offset + * + ****************************************************************************/ + +static inline void stm32_i2c_putreg32(struct stm32_i2c_priv_s *priv, + uint8_t offset, uint32_t value) +{ + putreg32(value, priv->config->base + offset); +} + +/**************************************************************************** + * Name: stm32_i2c_modifyreg32 + * + * Description: + * Modify a 32-bit register value by offset + * + ****************************************************************************/ + +static inline void stm32_i2c_modifyreg32(struct stm32_i2c_priv_s *priv, + uint8_t offset, uint32_t clearbits, + uint32_t setbits) +{ + modifyreg32(priv->config->base + offset, clearbits, setbits); +} + +/**************************************************************************** + * Name: stm32_i2c_toticks + * + * Description: + * Return a micro-second delay based on the number of bytes left to be + * processed. + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_I2C_DYNTIMEO +static uint32_t stm32_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_STM32H5_I2C_DYNTIMEO_USECPERBYTE * bytecount); +} +#endif + +/**************************************************************************** + * Name: stm32_i2c_enableinterrupts + * + * Description: + * Enable I2C interrupts + * + ****************************************************************************/ + +#ifndef CONFIG_I2C_POLLED +static inline void stm32_i2c_enableinterrupts(struct stm32_i2c_priv_s *priv) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_CR1_OFFSET, 0, + (I2C_CR1_TXRX | I2C_CR1_NACKIE)); +} +#endif + +/**************************************************************************** + * Name: stm32_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 stm32_i2c_sem_waitdone(struct stm32_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 stm32_i2c_process. + * The remainder of the interrupts, including error-related, are enabled + * here. + */ + + stm32_i2c_modifyreg32(priv, STM32_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_STM32H5_I2C_DYNTIMEO + ret = nxsem_tickwait_uninterruptible(&priv->sem_isr, + stm32_i2c_toticks(priv->msgc, priv->msgv)); +#else + ret = nxsem_tickwait_uninterruptible(&priv->sem_isr, + CONFIG_STM32H5_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 */ + + stm32_i2c_modifyreg32(priv, STM32_I2C_CR1_OFFSET, I2C_CR1_ALLINTS, 0); + + leave_critical_section(flags); + return ret; +} +#else +static inline int stm32_i2c_sem_waitdone(struct stm32_i2c_priv_s *priv) +{ + clock_t timeout; + clock_t start; + clock_t elapsed; + int ret; + + /* Get the timeout value */ + +#ifdef CONFIG_STM32H5_I2C_DYNTIMEO + timeout = stm32_i2c_toticks(priv->msgc, priv->msgv); +#else + timeout = CONFIG_STM32H5_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. + */ + + stm32_i2c_isr_process(priv); + } + + /* Loop until the transfer is complete. */ + + while (priv->intstate != INTSTATE_DONE && elapsed < timeout); + + i2cinfo("intstate: %d elapsed: %ld threshold: %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: stm32_i2c_set_7bit_address + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32_i2c_set_7bit_address(struct stm32_i2c_priv_s *priv) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, I2C_CR2_SADD7_MASK, + ((priv->msgv->addr & 0x7f) << I2C_CR2_SADD7_SHIFT)); +} + +/**************************************************************************** + * Name: stm32_i2c_set_bytes_to_transfer + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32_i2c_set_bytes_to_transfer(struct stm32_i2c_priv_s *priv, + uint8_t n_bytes) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, I2C_CR2_NBYTES_MASK, + (n_bytes << I2C_CR2_NBYTES_SHIFT)); +} + +/**************************************************************************** + * Name: stm32_i2c_set_write_transfer_dir + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32_i2c_set_write_transfer_dir(struct stm32_i2c_priv_s *priv) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, I2C_CR2_RD_WRN, 0); +} + +/**************************************************************************** + * Name: stm32_i2c_set_read_transfer_dir + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32_i2c_set_read_transfer_dir(struct stm32_i2c_priv_s *priv) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, 0, I2C_CR2_RD_WRN); +} + +/**************************************************************************** + * Name: stm32_i2c_enable_reload + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32_i2c_enable_reload(struct stm32_i2c_priv_s *priv) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, 0, I2C_CR2_RELOAD); +} + +/**************************************************************************** + * Name: stm32_i2c_disable_reload + * + * Description: + * + ****************************************************************************/ + +static inline void +stm32_i2c_disable_reload(struct stm32_i2c_priv_s *priv) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, I2C_CR2_RELOAD, 0); +} + +/**************************************************************************** + * Name: stm32_i2c_sem_waitstop + * + * Description: + * Wait for a STOP to complete + * + ****************************************************************************/ + +static inline void stm32_i2c_sem_waitstop(struct stm32_i2c_priv_s *priv) +{ + clock_t start; + clock_t elapsed; + clock_t timeout; + uint32_t cr; + uint32_t sr; + + /* Select a timeout */ + +#ifdef CONFIG_STM32H5_I2C_DYNTIMEO + timeout = USEC2TICK(CONFIG_STM32H5_I2C_DYNTIMEO_STARTSTOP); +#else + timeout = CONFIG_STM32H5_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 = stm32_i2c_getreg32(priv, STM32_I2C_CR2_OFFSET); + if ((cr & I2C_CR2_STOP) == 0) + { + return; + } + + /* Check for timeout error */ + + sr = stm32_i2c_getreg(priv, STM32_I2C_ISR_OFFSET); + if ((sr & I2C_INT_TIMEOUT) != 0) + { + 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: stm32_i2c_trace* + * + * Description: + * I2C trace instrumentation + * + ****************************************************************************/ + +#ifdef CONFIG_I2C_TRACE +static void stm32_i2c_traceclear(struct stm32_i2c_priv_s *priv) +{ + struct stm32_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 stm32_i2c_tracereset(struct stm32_i2c_priv_s *priv) +{ + /* Reset the trace info for a new data collection */ + + priv->tndx = 0; + priv->start_time = clock_systime_ticks(); + stm32_i2c_traceclear(priv); +} + +static void stm32_i2c_tracenew(struct stm32_i2c_priv_s *priv, + uint32_t status) +{ + struct stm32_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 */ + + stm32_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 stm32_i2c_traceevent(struct stm32_i2c_priv_s *priv, + enum stm32_trace_e event, uint32_t parm) +{ + struct stm32_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++; + stm32_i2c_traceclear(priv); + } +} + +static void stm32_i2c_tracedump(struct stm32_i2c_priv_s *priv) +{ + struct stm32_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: %08x COUNT: %3d EVENT: %2d PARM: %08x TIME: %d\n", + i + 1, trace->status, trace->count, trace->event, trace->parm, + (int)(trace->time - priv->start_time)); + } +} +#endif /* CONFIG_I2C_TRACE */ + +/**************************************************************************** + * Name: stm32_i2c_setclock + * + * Description: + * + * Sets the I2C bus clock frequency by configuring the I2C_TIMINGR + * register. + * + * This function supports bus clock frequencies of 0-1MHz. However, + * high input i2c clock speeds will result in higher minimum frequencies. + * For example, a 250 MHz clock will not support frequencies below ~33 KHz. + * + * Below are standard modes: + * 1000Khz (Fast Mode+) + * 400Khz (Fast Mode) + * 100Khz (Standard Mode) + * + * Attempts to set a frequency below the calculated minimum frequency + * will quietly provision the minimum frequency. Use care in selecting + * the input clock for the given application. . + * + * Clock Selection: + * + * The I2C peripheral clock can be provided by either PCLK1 (or PCLK3), + * PLL3_R, HSI, or CSI. + * + * PCLK1 >------|\ I2CCLK12 + * PLL3_R >------| |---------> + * HSI >------| | + * CSI >------|/ + * + * PCLK1 is the default. + * + * PCLK3 >------|\ I2CCLK34 + * PLL3_R >------| |---------> + * HSI >------| | + * CSI >------|/ + * + * PCLK3 is the default. + * + * References: + * + * I2C Specification, STM32H52x_3x_6x_7x Reference Manual RM0481. + * + ****************************************************************************/ + +static void stm32_i2c_setclock(struct stm32_i2c_priv_s *priv, + uint32_t frequency) +{ + /* STM32H5 variables */ + + uint8_t presc; + uint32_t t_presc_ns; + uint32_t tsync1; + uint32_t tsync2; + uint8_t scl_delay; + int32_t sda_delay_min; + uint8_t sda_delay; + uint16_t scl_h_period; + uint16_t scl_l_period; + uint8_t fmp = 0; + uint8_t anfoff; + uint8_t dnf; + + /* Clocks */ + + uint32_t i2c_ker_ck; + uint32_t t_i2cclk_ns; + uint32_t t_clk_ns; + int32_t tlow_ns; + int32_t thigh_ns; + uint8_t tlow_ratio_min; + + /* I2C Specifications */ + + uint32_t tsu_dat_min; + const uint8_t thd_dat_min = 0; + uint32_t tr_max; + uint32_t tf_max; + bool rf_override = false; + + /* Calculate timingr only if frequency changes */ + + if (frequency != priv->frequency) + { + /* Set timing specs based on i2c frequency + * Specifications pulled from secion 6.1 in + * UM10204 (NXP I2C Spec). Time in nanoseconds. + */ + + if (frequency <= 100000) + { + tsu_dat_min = 250; + tr_max = 1000; + tf_max = 300; + tlow_ratio_min = 47; + } + else if (frequency <= 400000) + { + tsu_dat_min = 100; + tr_max = 300; + tf_max = 300; + tlow_ratio_min = 52; + } + else if (frequency <= 1000000) + { + tsu_dat_min = 50; + tr_max = 120; + tf_max = 120; + tlow_ratio_min = 50; + fmp = 1; + } + else + { + /* If frequency > 1MHz, set to 1MHz and fast mode plus */ + + frequency = 1000000; + tsu_dat_min = 50; + tr_max = 120; + tf_max = 120; + tlow_ratio_min = 50; + fmp = 1; + } + + /* Added 1% to t_clk_ns to make sure frequency is not too fast */ + + t_clk_ns = (1010000000 + (frequency / 2)) / frequency; + + /* Obtain input clock period in nanoseconds. Each i2c peripheral can + * have separate settings. PCLK1 is the default clock. + */ + + switch (priv->config->base) + { +#ifdef CONFIG_STM32H5_I2C1 + case STM32_I2C1_BASE: +# if defined(CONFIG_STM32H5_I2C1_CLK_HSI) + i2c_ker_ck = STM32_HSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C1_CLK_CSI) + i2c_ker_ck = STM32_CSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C1_CLK_PCLK1) + i2c_ker_ck = STM32_PCLK1_FREQUENCY; +# else + i2c_ker_ck = STM32_PLL3R_FREQUENCY; +# endif + anfoff = CONFIG_STM32H5_I2C1_ANFOFF; + dnf = CONFIG_STM32H5_I2C1_DNF; +# ifdef CONFIG_STM32H5_I2C1_RF_OVERRIDE + tr_max = CONFIG_STM32H5_I2C1_RISE; + tf_max = CONFIG_STM32H5_I2C1_FALL; + rf_override = true; +# endif + break; +#endif +#ifdef CONFIG_STM32H5_I2C2 + case STM32_I2C2_BASE: +# if defined(CONFIG_STM32H5_I2C2_CLK_HSI) + i2c_ker_ck = STM32_HSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C2_CLK_CSI) + i2c_ker_ck = STM32_CSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C2_CLK_PCLK1) + i2c_ker_ck = STM32_PCLK1_FREQUENCY; +# else + i2c_ker_ck = STM32_PLL3R_FREQUENCY; +# endif + anfoff = CONFIG_STM32H5_I2C2_ANFOFF; + dnf = CONFIG_STM32H5_I2C2_DNF; +# ifdef CONFIG_STM32H5_I2C2_RF_OVERRIDE + tr_max = CONFIG_STM32H5_I2C2_RISE; + tf_max = CONFIG_STM32H5_I2C2_FALL; + rf_override = true; +# endif + break; +#endif +#ifdef CONFIG_STM32H5_I2C3 + case STM32_I2C3_BASE: +# if defined(CONFIG_STM32H5_I2C3_CLK_HSI) + i2c_ker_ck = STM32_HSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C3_CLK_CSI) + i2c_ker_ck = STM32_CSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C3_CLK_PCLK3) + i2c_ker_ck = STM32_PCLK3_FREQUENCY; +# else + i2c_ker_ck = STM32_PLL3R_FREQUENCY; +# endif + anfoff = CONFIG_STM32H5_I2C3_ANFOFF; + dnf = CONFIG_STM32H5_I2C3_DNF; +# ifdef CONFIG_STM32H5_I2C3_RF_OVERRIDE + tr_max = CONFIG_STM32H5_I2C3_RISE; + tf_max = CONFIG_STM32H5_I2C3_FALL; + rf_override = true; +# endif + break; +#endif +#ifdef CONFIG_STM32H5_I2C4 + case STM32_I2C4_BASE: +# if defined(CONFIG_STM32H5_I2C4_CLK_HSI) + i2c_ker_ck = STM32_HSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C4_CLK_CSI) + i2c_ker_ck = STM32_CSI_FREQUENCY; +# elif defined(CONFIG_STM32H5_I2C4_CLK_PCLK3) + i2c_ker_ck = STM32_PCLK3_FREQUENCY; +# else + i2c_ker_ck = STM32_PLL3R_FREQUENCY; +# endif + anfoff = CONFIG_STM32H5_I2C4_ANFOFF; + dnf = CONFIG_STM32H5_I2C4_DNF; +# ifdef CONFIG_STM32H5_I2C4_RF_OVERRIDE + tr_max = CONFIG_STM32H5_I2C4_RISE; + tf_max = CONFIG_STM32H5_I2C4_FALL; + rf_override = true; +# endif + break; +#endif + default: + } + + /* Set i2c_ker_ck period ti2cclk in nanoseconds */ + + t_i2cclk_ns = 1000000000 / i2c_ker_ck; + + /* See Section 48.4.9 and Figure 640 in RM0481. + * Use avg 40ns trise/tfall by default + */ + + if (rf_override) + { + tsync1 = tr_max + (!anfoff * STM32_I2C_TAF_MIN) + \ + ((dnf + 2) * t_i2cclk_ns); + tsync2 = tf_max + (!anfoff * STM32_I2C_TAF_MIN) + \ + ((dnf + 2) * t_i2cclk_ns); + } + else + { + tsync1 = 40 + (!anfoff * STM32_I2C_TAF_MIN) + \ + ((dnf + 2) * t_i2cclk_ns); + tsync2 = tsync1; + } + + /* See Figure 640 in RM0481. Add 3% margin to tlow. */ + + tlow_ns = (t_clk_ns * (tlow_ratio_min + 3)) / 100; + thigh_ns = t_clk_ns - tlow_ns; + tlow_ns -= tsync1; + thigh_ns -= tsync2; + + /* Iterate through prescaler values until t_presc is large enough + * to calculate a proper scl_l_period, scl_delay, and sda_delay + */ + + for (presc = 0; presc < 16; ++presc) + { + t_presc_ns = t_i2cclk_ns * (presc + 1); + + scl_l_period = ((tlow_ns + (t_presc_ns / 2)) / t_presc_ns) - 1; + if (scl_l_period > 255) + { + continue; + } + + scl_h_period = ((thigh_ns + (t_presc_ns / 2)) / t_presc_ns) - 1; + + /* See RM0481 Section 48.4.5 */ + + sda_delay_min = ((int32_t) (tf_max + thd_dat_min - \ + (!anfoff * STM32_I2C_TAF_MIN) - \ + ((dnf + 3) * t_i2cclk_ns)) / \ + (int32_t) t_presc_ns); + + if (sda_delay_min < 0) + { + sda_delay_min = 0; + } + + sda_delay = sda_delay_min + 1; + + if (sda_delay > 15) + { + continue; + } + + /* See RM0481 Rev 2 Section 48.4.5 + * SCL and SDA rise times are often identical + * However, still add tr_max/2 to scl_delay to account + * for potential differences in rise times. + */ + + scl_delay = (((tr_max / 2) + tsu_dat_min) / t_presc_ns) - 1; + + if (scl_delay > 15) + { + continue; + } + + break; + } + + /* Note: It is possible to exit this loop with invalid settings when + * using an improper i2c_ker_ck. Choose i2c_ker_ck wisely. + * Addtionally, take care setting the digital and analog filters. + */ + + /* I2C peripheral must be disabled to update clocking configuration. + * This will SW reset the device. + */ + + stm32_i2c_modifyreg32(priv, STM32_I2C_CR1_OFFSET, I2C_CR1_PE, 0); + + uint32_t timingr = + (presc << I2C_TIMINGR_PRESC_SHIFT) | + (scl_delay << I2C_TIMINGR_SCLDEL_SHIFT) | + (sda_delay << I2C_TIMINGR_SDADEL_SHIFT) | + (scl_h_period << I2C_TIMINGR_SCLH_SHIFT) | + (scl_l_period << I2C_TIMINGR_SCLL_SHIFT); + + stm32_i2c_putreg32(priv, STM32_I2C_TIMINGR_OFFSET, timingr); + priv->frequency = frequency; + + uint32_t cr1_reg_clr = I2C_CR1_DNF_MASK | I2C_CR1_ANFOFF | I2C_CR1_FMP; + + uint32_t cr1_reg_set = (dnf << I2C_CR1_DNF_SHIFT) | \ + (anfoff * I2C_CR1_ANFOFF) | \ + (fmp * I2C_CR1_FMP); + + /* Set DNF, ANFOFF, and FMP */ + + stm32_i2c_modifyreg32(priv, STM32_I2C_CR1_OFFSET, + cr1_reg_clr, cr1_reg_set); + } + + /* Enable I2C peripheral */ + + stm32_i2c_modifyreg32(priv, STM32_I2C_CR1_OFFSET, 0, I2C_CR1_PE); +} + +/**************************************************************************** + * Name: stm32_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 stm32_i2c_sendstart(struct stm32_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); + stm32_i2c_enable_reload(priv); + } + else + { + i2cinfo("RELOAD disable: dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + stm32_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) + { + stm32_i2c_set_bytes_to_transfer(priv, 255); + } + else + { + stm32_i2c_set_bytes_to_transfer(priv, priv->dcnt); + } + + /* Set the (7 bit) address. + * 10 bit addressing is not yet supported. + */ + + stm32_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) + { + stm32_i2c_set_read_transfer_dir(priv); + } + else + { + stm32_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); + + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, 0, I2C_CR2_START); +} + +/**************************************************************************** + * Name: stm32_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 stm32_i2c_sendstop(struct stm32_i2c_priv_s *priv) +{ + i2cinfo("Sending STOP\n"); + stm32_i2c_traceevent(priv, I2CEVENT_WRITE_STOP, 0); + + stm32_i2c_modifyreg32(priv, STM32_I2C_CR2_OFFSET, 0, I2C_CR2_STOP); +} + +/**************************************************************************** + * Name: stm32_i2c_getstatus + * + * Description: + * Get 32-bit status (SR1 and SR2 combined) + * + ****************************************************************************/ + +static inline uint32_t stm32_i2c_getstatus(struct stm32_i2c_priv_s *priv) +{ + return getreg32(priv->config->base + STM32_I2C_ISR_OFFSET); +} + +/**************************************************************************** + * Name: stm32_i2c_clearinterrupts + * + * Description: + * Clear all interrupts + * + ****************************************************************************/ + +static inline void stm32_i2c_clearinterrupts(struct stm32_i2c_priv_s *priv) +{ + stm32_i2c_modifyreg32(priv, STM32_I2C_ICR_OFFSET, 0, I2C_ICR_CLEARMASK); +} + +/**************************************************************************** + * Name: stm32_i2c_isr_process + * + * Description: + * Common interrupt service routine (ISR) that handles I2C protocol logic. + * This is instantiated for each configured I2C interface + * (I2C1, I2C2, I2C3). + * + * This ISR is activated and deactivated by: + * + * stm32_i2c_process + * and + * stm32_i2c_waitdone + * + * Input Parameters: + * priv - The private struct of the I2C driver. + * + ****************************************************************************/ + +static int stm32_i2c_isr_process(struct stm32_i2c_priv_s *priv) +{ + uint32_t status; + + /* Get state of the I2C controller */ + + status = stm32_i2c_getreg32(priv, STM32_I2C_ISR_OFFSET); + + i2cinfo("ENTER: status = 0x%08" PRIx32 "\n", status); + + /* Update private version of the state assuming a good state */ + + priv->status = status & ~I2C_INT_BAD_STATE; + + /* If this is a new transmission set up the trace table accordingly */ + + stm32_i2c_tracenew(priv, status); + stm32_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); + stm32_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); + stm32_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 + * acknowledgment. + * 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 */ + + stm32_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"); + stm32_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 */ + + stm32_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) + { + stm32_i2c_disable_reload(priv); + } + } + + /* Transmit current byte */ + + stm32_i2c_putreg(priv, STM32_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); + stm32_i2c_traceevent(priv, I2CEVENT_WRITE_ERROR, 0); + + /* Indicate the bad state, so that on termination HW will be + * reset + */ + + priv->status |= I2C_INT_BAD_STATE; + } + + 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. + */ + + stm32_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) + { + stm32_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 = stm32_i2c_getreg(priv, STM32_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 */ + + stm32_i2c_traceevent(priv, I2CEVENT_READ_ERROR, 0); + status = stm32_i2c_getreg(priv, STM32_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->status |= I2C_INT_BAD_STATE; + 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); + stm32_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++; + + stm32_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); + stm32_i2c_traceevent(priv, I2CEVENT_STOP, priv->dcnt); + + stm32_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); + + stm32_i2c_disable_reload(priv); + } + + /* Update NBYTES with length of current message */ + + i2cinfo("TCR: NEXT MSG dcnt = %i msgc = %i\n", + priv->dcnt, priv->msgc); + + stm32_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. + */ + + stm32_i2c_enable_reload(priv); + + stm32_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); + + stm32_i2c_set_bytes_to_transfer(priv, priv->dcnt); + + stm32_i2c_disable_reload(priv); + } + + 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 = stm32_i2c_getreg(priv, STM32_I2C_ISR_OFFSET); + i2cwarn("WARNING: EMPTY CALL: Stopping ISR: status 0x%08" PRIx32 "\n", + status); + stm32_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 can happen in interrupt based operation on ARLO & BUSY. + * + * This will happen during polled operation when the device is not + * in one of the supported states when polled. + */ + + else + { +#ifdef CONFIG_I2C_POLLED + stm32_i2c_traceevent(priv, I2CEVENT_POLL_DEV_NOT_RDY, 0); +#else + /* Read rest of the state */ + + status = stm32_i2c_getreg(priv, STM32_I2C_ISR_OFFSET); + + i2cerr("ERROR: Invalid state detected, status 0x%08" PRIx32 "\n", + status); + + /* set condition to terminate ISR and wake waiting thread */ + + priv->status |= I2C_INT_BAD_STATE; + priv->dcnt = -1; + priv->msgc = 0; + stm32_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 stm32_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"); + + stm32_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 + + /* 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. + */ + + status = stm32_i2c_getreg32(priv, STM32_I2C_ISR_OFFSET); + + /* Clear all interrupts */ + + stm32_i2c_modifyreg32(priv, STM32_I2C_ICR_OFFSET, + 0, I2C_ICR_CLEARMASK); + + /* Was a bad state detected in the processing? */ + + if (priv->status & I2C_INT_BAD_STATE) + { + /* SW reset device */ + + stm32_i2c_modifyreg32(priv, STM32_I2C_CR1_OFFSET, I2C_CR1_PE, 0); + } + + /* Update private status from above sans I2C_INT_BAD_STATE */ + + priv->status = status; + + /* 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 = stm32_i2c_getreg32(priv, STM32_I2C_ISR_OFFSET); + i2cinfo("EXIT: status = 0x%08" PRIx32 "\n", status); + + return OK; +} + +/**************************************************************************** + * Name: stm32_i2c_isr + * + * Description: + * Common I2C interrupt service routine + * + ****************************************************************************/ + +#ifndef CONFIG_I2C_POLLED +static int stm32_i2c_isr(int irq, void *context, void *arg) +{ + struct stm32_i2c_priv_s *priv = (struct stm32_i2c_priv_s *)arg; + + DEBUGASSERT(priv != NULL); + return stm32_i2c_isr_process(priv); +} +#endif + +/**************************************************************************** + * Name: stm32_i2c_init + * + * Description: + * Setup the I2C hardware, ready for operation with defaults + * + ****************************************************************************/ + +static int stm32_i2c_init(struct stm32_i2c_priv_s *priv) +{ + /* Power-up and configure GPIOs */ + + /* Enable power and reset the peripheral */ + + modifyreg32(priv->config->clk_reg, 0, priv->config->clk_bit); + modifyreg32(priv->config->reset_reg, 0, priv->config->reset_bit); + modifyreg32(priv->config->reset_reg, priv->config->reset_bit, 0); + + /* Configure pins */ + + if (stm32_configgpio(priv->config->scl_pin) < 0) + { + return ERROR; + } + + if (stm32_configgpio(priv->config->sda_pin) < 0) + { + stm32_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, stm32_i2c_isr, priv); + irq_attach(priv->config->er_irq, stm32_i2c_isr, priv); + up_enable_irq(priv->config->ev_irq); + up_enable_irq(priv->config->er_irq); +#endif + + /* - Provide means to set peripheral clock source via RCC_CCIPR4_I2CxSEL */ + + switch (priv->config->base) + { +#ifdef CONFIG_STM32H5_I2C1 + case STM32_I2C1_BASE: +# if defined(CONFIG_STM32H5_I2C1_CLK_HSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C1SEL_MASK, + RCC_CCIPR4_I2C1SEL_HSIKERCK); +# elif defined(CONFIG_STM32H5_I2C1_CLK_CSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C1SEL_MASK, + RCC_CCIPR4_I2C1SEL_CSIKERCK); +# elif defined(CONFIG_STM32H5_I2C1_CLK_PCLK1) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C1SEL_MASK, + RCC_CCIPR4_I2C1SEL_RCCPCLK1); +# else + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C1SEL_MASK, + RCC_CCIPR4_I2C1SEL_PLL3RCK); +# endif + break; +#endif +#ifdef CONFIG_STM32H5_I2C2 + case STM32_I2C2_BASE: +# if defined(CONFIG_STM32H5_I2C2_CLK_HSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C2SEL_MASK, + RCC_CCIPR4_I2C2SEL_HSIKERCK); +# elif defined(CONFIG_STM32H5_I2C2_CLK_CSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C2SEL_MASK, + RCC_CCIPR4_I2C2SEL_CSIKERCK); +# elif defined(CONFIG_STM32H5_I2C2_CLK_PCLK1) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C2SEL_MASK, + RCC_CCIPR4_I2C2SEL_PCLK1); +# else + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C2SEL_MASK, + RCC_CCIPR4_I2C2SEL_PLL3RCK); +# endif + break; +#endif +#ifdef CONFIG_STM32H5_I2C3 + case STM32_I2C3_BASE: +# if defined(CONFIG_STM32H5_I2C3_CLK_HSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C3SEL_MASK, + RCC_CCIPR4_I2C3SEL_HSIKERCK); +# elif defined(CONFIG_STM32H5_I2C3_CLK_CSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C3SEL_MASK, + RCC_CCIPR4_I2C3SEL_CSIKERCK); +# elif defined(CONFIG_STM32H5_I2C3_CLK_PCLK3) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C3SEL_MASK, + RCC_CCIPR4_I2C3SEL_PCLK3); +# else + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C3SEL_MASK, + RCC_CCIPR4_I2C3SEL_PLL3RCK); +# endif + break; +#endif +#ifdef CONFIG_STM32H5_I2C4 + case STM32_I2C4_BASE: +# if defined(CONFIG_STM32H5_I2C4_CLK_HSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C4SEL_MASK, + RCC_CCIPR4_I2C4SEL_HSIKERCK); +# elif defined(CONFIG_STM32H5_I2C4_CLK_CSI) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C4SEL_MASK, + RCC_CCIPR4_I2C4SEL_CSIKERCK); +# elif defined(CONFIG_STM32H5_I2C4_CLK_PCLK3) + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C4SEL_MASK, + RCC_CCIPR4_I2C4SEL_PCLK3); +# else + modifyreg32(STM32_RCC_CCIPR4, + RCC_CCIPR4_I2C4SEL_MASK, + RCC_CCIPR4_I2C4SEL_PLL3RCK); +# endif + break; +#endif + default: + } + + /* Force a frequency update */ + + priv->frequency = 0; + stm32_i2c_setclock(priv, 100000); + + return OK; +} + +/**************************************************************************** + * Name: stm32_i2c_deinit + * + * Description: + * Shutdown the I2C hardware + * + ****************************************************************************/ + +static int stm32_i2c_deinit(struct stm32_i2c_priv_s *priv) +{ + /* Disable I2C */ + + stm32_i2c_putreg32(priv, STM32_I2C_CR1_OFFSET, 0); + + /* Unconfigure GPIO pins */ + + stm32_unconfiggpio(priv->config->scl_pin); + stm32_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(priv->config->clk_reg, priv->config->clk_bit, 0); + + return OK; +} + +/**************************************************************************** + * Name: stm32_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 stm32_i2c_process(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count) +{ + struct stm32_i2c_inst_s *inst = (struct stm32_i2c_inst_s *)dev; + struct stm32_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 */ + + stm32_i2c_sem_waitstop(priv); + + /* Clear any pending error interrupts */ + + stm32_i2c_clearinterrupts(priv); + + /* Old transfers are done */ + + priv->msgv = msgs; + priv->msgc = count; + + /* Reset I2C trace logic */ + + stm32_i2c_tracereset(priv); + + /* Set I2C clock frequency toggles I2C_CR1_PE performing a SW reset! */ + + stm32_i2c_setclock(priv, msgs->frequency); + + /* Trigger start condition, then the process moves into the ISR. I2C + * interrupts will be enabled within stm32_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 stm32_i2c_sem_waitdone() below. + */ + + stm32_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 + */ + + stm32_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 = stm32_i2c_sem_waitdone(priv); + + cr1 = stm32_i2c_getreg32(priv, STM32_I2C_CR1_OFFSET); + cr2 = stm32_i2c_getreg32(priv, STM32_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 = stm32_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 stm32_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 stm32_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 = stm32_i2c_getstatus(priv); + + while ((status & I2C_ISR_BUSY) != 0) + { + if ((clock_systime_ticks() - start) > timeout) + { + i2cerr("ERROR: I2C Bus busy"); + errval = EBUSY; + break; + } + + status = stm32_i2c_getstatus(priv); + } + } + + /* Dump the trace result */ + + stm32_i2c_tracedump(priv); + nxmutex_unlock(&priv->lock); + + return -errval; +} + +/**************************************************************************** + * Name: stm32_i2c_transfer + * + * Description: + * Generic I2C transfer function + * + ****************************************************************************/ + +static int stm32_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count) +{ + struct stm32_i2c_priv_s *priv; + int ret; + + DEBUGASSERT(dev); + + /* Get I2C private structure */ + + priv = ((struct stm32_i2c_inst_s *)dev)->priv; + + /* Ensure that address or flags don't change meanwhile */ + + ret = nxmutex_lock(&priv->lock); + if (ret >= 0) + { + ret = stm32_i2c_process(dev, msgs, count); + } + + return ret; +} + +/**************************************************************************** + * Name: stm32_i2c_reset + * + * Description: + * Reset an I2C bus + * + ****************************************************************************/ + +#ifdef CONFIG_I2C_RESET +static int stm32_i2c_reset(struct i2c_master_s *dev) +{ + struct stm32_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 stm32_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 */ + + stm32_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); + + stm32_configgpio(sda_gpio); + stm32_configgpio(scl_gpio); + + /* Let SDA go high */ + + stm32_gpiowrite(sda_gpio, 1); + + /* Clock the bus until any slaves currently driving it let it go. */ + + clock_count = 0; + while (!stm32_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 (!stm32_gpioread(scl_gpio)) + { + /* Give up if we have tried too hard */ + + if (stretch_count++ > 10) + { + goto out; + } + + up_udelay(10); + } + + /* Drive SCL low */ + + stm32_gpiowrite(scl_gpio, 0); + up_udelay(10); + + /* Drive SCL high again */ + + stm32_gpiowrite(scl_gpio, 1); + up_udelay(10); + } + + /* Generate a start followed by a stop to reset slave + * state machines. + */ + + stm32_gpiowrite(sda_gpio, 0); + up_udelay(10); + stm32_gpiowrite(scl_gpio, 0); + up_udelay(10); + stm32_gpiowrite(scl_gpio, 1); + up_udelay(10); + stm32_gpiowrite(sda_gpio, 1); + up_udelay(10); + + /* Revert the GPIO configuration. */ + + stm32_unconfiggpio(sda_gpio); + stm32_unconfiggpio(scl_gpio); + + /* Re-init the port */ + + stm32_i2c_init(priv); + + /* Restore the frequency */ + + stm32_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: stm32_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 stm32_i2c_pm_prepare(struct pm_callback_s *cb, int domain, + enum pm_state_e pmstate) +{ + struct stm32_i2c_priv_s *priv = + (struct stm32_i2c_priv_s *)((char *)cb - + offsetof(struct stm32_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: stm32_i2cbus_initialize + * + * Description: + * Initialize one I2C bus + * + ****************************************************************************/ + +struct i2c_master_s *stm32_i2cbus_initialize(int port) +{ + struct stm32_i2c_priv_s *priv = NULL; /* private data of device with multiple instances */ + struct stm32_i2c_inst_s *inst = NULL; /* device, single instance */ + +#if defined(INVALID_CLOCK_SOURCE) +# warning STM32_I2C_INIT: Peripheral input clock must be HSI or the speed/timing calculations need to be redone. + return NULL; +#endif + + /* Get I2C private structure */ + + switch (port) + { +#ifdef CONFIG_STM32H5_I2C1 + case 1: + priv = (struct stm32_i2c_priv_s *)&stm32_i2c1_priv; + break; +#endif +#ifdef CONFIG_STM32H5_I2C2 + case 2: + priv = (struct stm32_i2c_priv_s *)&stm32_i2c2_priv; + break; +#endif +#ifdef CONFIG_STM32H5_I2C3 + case 3: + priv = (struct stm32_i2c_priv_s *)&stm32_i2c3_priv; + break; +#endif +#ifdef CONFIG_STM32H5_I2C4 + case 4: + priv = (struct stm32_i2c_priv_s *)&stm32_i2c4_priv; + break; +#endif + default: + return NULL; + } + + /* Allocate instance */ + + if (!(inst = kmm_malloc(sizeof(struct stm32_i2c_inst_s)))) + { + return NULL; + } + + /* Initialize instance */ + + inst->ops = &stm32_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) + { + stm32_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: stm32_i2cbus_uninitialize + * + * Description: + * Uninitialize an I2C bus + * + ****************************************************************************/ + +int stm32_i2cbus_uninitialize(struct i2c_master_s *dev) +{ + struct stm32_i2c_priv_s *priv; + + DEBUGASSERT(dev); + priv = ((struct stm32_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) */ + + stm32_i2c_deinit(priv); + nxmutex_unlock(&priv->lock); + + kmm_free(dev); + return OK; +} + +#endif /* CONFIG_STM32H5_I2C1 || CONFIG_STM32H5_I2C2 || \ + * CONFIG_STM32H5_I2C3 || CONFIG_STM32H5_I2C4 */ diff --git a/arch/arm/src/stm32h5/stm32_i2c.h b/arch/arm/src/stm32h5/stm32_i2c.h new file mode 100644 index 0000000000000..8f37a558f1d2b --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_i2c.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_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_STM32H5_STM32_I2C_H +#define __ARCH_ARM_SRC_STM32H5_STM32_I2C_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "chip.h" +#include "hardware/stm32h5xxx_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_STM32H5_I2C_DYNTIMEO +# if CONFIG_STM32H5_I2C_DYNTIMEO_USECPERBYTE < 1 +# warning "Ignoring CONFIG_STM32H5_I2C_DYNTIMEO because of CONFIG_STM32H5_I2C_DYNTIMEO_USECPERBYTE" +# undef CONFIG_STM32H5_I2C_DYNTIMEO +# endif +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32_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 *stm32_i2cbus_initialize(int port); + +/**************************************************************************** + * Name: stm32_i2cbus_uninitialize + * + * Description: + * De-initialize the selected I2C port, and power down the device. + * + * Input Parameters: + * Device structure as returned by the stm32_i2cbus_initialize() + * + * Returned Value: + * OK on success, ERROR when internal reference count mismatch or dev + * points to invalid hardware device. + * + ****************************************************************************/ + +int stm32_i2cbus_uninitialize(struct i2c_master_s *dev); + +#endif /* __ARCH_ARM_SRC_STM32H5_STM32_I2C_H */