diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig index ebba830b7b..634872840b 100644 --- a/arch/arm/src/stm32h5/Kconfig +++ b/arch/arm/src/stm32h5/Kconfig @@ -35,6 +35,8 @@ config STM32H5_STM32H5XXXX config STM32H5_STM32H56XXX bool default n + select STM32H5_HAVE_FDCAN1 + select STM32H5_HAVE_FDCAN2 select STM32H5_HAVE_LPUART1 select STM32H5_HAVE_USART1 select STM32H5_HAVE_USART2 @@ -210,6 +212,14 @@ config STM32H5_HAVE_PHY_POLLED bool default n +config STM32H5_HAVE_FDCAN1 + bool + default n + +config STM32H5_HAVE_FDCAN2 + bool + default n + config STM32H5_HAVE_LPUART1 bool default n @@ -1322,6 +1332,292 @@ config STM32H5_NO_PHY endmenu # Ethernet MAC Configuration +menu "FDCAN driver configuration" + depends on STM32H5_FDCAN + +choice + prompt "FDCAN character driver or SocketCAN support" + default STM32H5_FDCAN_CHARDRIVER + +config STM32H5_FDCAN_CHARDRIVER + bool "STM32 FDCAN character driver support" + select ARCH_HAVE_CAN_ERRORS + select CAN + +config STM32H5_FDCAN_SOCKET + bool "STM32 FDCAN SocketCAN support" + select NET_CAN_HAVE_ERRORS + select NET_CAN_HAVE_CANFD + +endchoice # FDCAN character driver or SocketCAN support + +config STM32H5_FDCAN_REGDEBUG + bool "CAN Register level debug" + depends on DEBUG_CAN_INFO + default n + ---help--- + Output detailed register-level CAN device debug information. + Requires also CONFIG_DEBUG_CAN_INFO. + +config STM32H5_FDCAN_QUEUE_MODE + bool "FDCAN QUEUE mode (vs FIFO mode)" + default n + +menu "FDCAN1 device driver options" + depends on STM32H5_FDCAN1 + +choice + prompt "FDCAN1 frame format" + default STM32H5_FDCAN1_ISO11898_1 + +config STM32H5_FDCAN1_ISO11898_1 + bool "ISO11898-1" + ---help--- + Enable ISO11898-1 frame format + +config STM32H5_FDCAN1_NONISO_FORMAT + bool "Non ISO" + ---help--- + Enable Non ISO, Bosch CAN FD Specification V1.0 + +endchoice # FDCAN1 frame format + +choice + prompt "FDCAN1 mode" + default STM32H5_FDCAN1_CLASSIC + +config STM32H5_FDCAN1_CLASSIC + bool "Classic CAN" + ---help--- + Enable Clasic CAN mode + +config STM32H5_FDCAN1_FD + bool "CAN FD" + depends on CAN_FD || NET_CAN_CANFD + ---help--- + Enable CAN FD mode + +config STM32H5_FDCAN1_FD_BRS + bool "CAN FD with fast bit rate switching" + depends on CAN_FD || NET_CAN_CANFD + ---help--- + Enable CAN FD mode with fast bit rate switching mode. + +endchoice # FDCAN1 mode + +menu "FDCAN1 Bit Timing" + +config STM32H5_FDCAN1_AUTO_BIT_TIMING + bool "FDCAN1 Automatic Bit Timing" + default y + ---help--- + Automatically determine FDCAN1 bit timing (nominal and data) based on bitrate. + +comment "Nominal Bit Timing" + +config STM32H5_FDCAN1_BITRATE + int "FDCAN bitrate" + default 500000 + range 0 1000000 + ---help--- + FDCAN1 bitrate in bits per second. Required if STM32H5_FDCAN1 is defined. + +config STM32H5_FDCAN1_NTSEG1 + int "FDCAN1 NTSEG1 (PropSeg + PhaseSeg1)" + default 6 + range 1 256 + depends on !STM32H5_FDCAN1_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN1_NTSEG2 + int "FDCAN1 NTSEG2 (PhaseSeg2)" + default 7 + range 1 128 + depends on !STM32H5_FDCAN1_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN1_NSJW + int "FDCAN1 synchronization jump width" + default 1 + range 1 128 + depends on !STM32H5_FDCAN1_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +comment "Data Bit Timing" + depends on CAN_FD && STM32H5_FDCAN1_FD_BRS + +config STM32H5_FDCAN1_DBITRATE + int "FDCAN1 data bitrate" + default 2000000 + depends on CAN_FD && STM32H5_FDCAN1_FD_BRS + ---help--- + FDCAN1 bitrate in bits per second. Required if operating in FD mode with bit rate switching (BRS). + +config STM32H5_FDCAN1_DTSEG1 + int "FDCAN1 DTSEG1 (PropSeg + PhaseSeg1 of data phase)" + default 4 + range 1 31 + depends on CAN_FD && STM32H5_FDCAN1_FD_BRS && !STM32H5_FDCAN1_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN1_DTSEG2 + int "FDCAN1 DTSEG2 (PhaseSeg2 of data phase)" + default 4 + range 1 15 + depends on CAN_FD && STM32H5_FDCAN1_FD_BRS && !STM32H5_FDCAN1_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN1_DSJW + int "FDCAN1 fast synchronization jump width" + default 2 + range 1 15 + depends on CAN_FD && STM32H5_FDCAN1_FD_BRS && !STM32H5_FDCAN1_AUTO_BIT_TIMING + ---help--- + The duration of a synchronization jump is Tcan_clk x DSJW. + +endmenu # FDCAN1 Bit Timing + +config STM32H5_FDCAN1_LOOPBACK + bool "Enable FDCAN1 loopback mode" + default n + ---help--- + Enable the FDCAN1 local loopback mode for testing purposes. + +endmenu # FDCAN1 device driver options + +menu "FDCAN2 device driver options" + depends on STM32H5_FDCAN2 + +choice + prompt "FDCAN2 frame format" + default STM32H5_FDCAN2_ISO11898_1 + +config STM32H5_FDCAN2_ISO11898_1 + bool "ISO11898-1" + ---help--- + Enable ISO11898-1 frame format + +config STM32H5_FDCAN2_NONISO_FORMAT + bool "Non ISO" + ---help--- + Enable Non ISO, Bosch CAN FD Specification V1.0 + +endchoice # FDCAN2 frame format + +choice + prompt "FDCAN2 mode" + default STM32H5_FDCAN2_CLASSIC + +config STM32H5_FDCAN2_CLASSIC + bool "Classic CAN" + ---help--- + Enable Clasic CAN mode + +config STM32H5_FDCAN2_FD + bool "CAN FD" + depends on CAN_FD || NET_CAN_CANFD + ---help--- + Enable CAN FD mode + +config STM32H5_FDCAN2_FD_BRS + bool "CAN FD with fast bit rate switching" + depends on CAN_FD || NET_CAN_CANFD + ---help--- + Enable CAN FD mode with fast bit rate switching mode. + +endchoice # FDCAN2 mode + +menu "FDCAN2 Bit Timing" + +config STM32H5_FDCAN2_AUTO_BIT_TIMING + bool "FDCAN2 Automatic Bit Timing" + default y + ---help--- + Automatically determine FDCAN2 bit timing (nominal and data) based on bitrate. + +comment "Nominal Bit Timing" + +config STM32H5_FDCAN2_BITRATE + int "FDCAN bitrate" + default 500000 + range 0 1000000 + ---help--- + FDCAN2 bitrate in bits per second. Required if STM32H5_FDCAN2 is defined. + +config STM32H5_FDCAN2_NTSEG1 + int "FDCAN2 NTSEG1 (PropSeg + PhaseSeg1)" + default 6 + range 1 256 + depends on !STM32H5_FDCAN2_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN2_NTSEG2 + int "FDCAN2 NTSEG2 (PhaseSeg2)" + default 7 + range 1 128 + depends on !STM32H5_FDCAN2_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN2_NSJW + int "FDCAN2 synchronization jump width" + default 1 + range 1 128 + depends on !STM32H5_FDCAN2_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +comment "Data Bit Timing" + depends on CAN_FD && STM32H5_FDCAN2_FD_BRS + +config STM32H5_FDCAN2_DBITRATE + int "FDCAN2 data bitrate" + default 2000000 + depends on CAN_FD && STM32H5_FDCAN2_FD_BRS + ---help--- + FDCAN2 bitrate in bits per second. Required if operating in FD mode with bit rate switching (BRS). + +config STM32H5_FDCAN2_DTSEG1 + int "FDCAN2 DTSEG1 (PropSeg + PhaseSeg1 of data phase)" + default 4 + range 1 31 + depends on CAN_FD && STM32H5_FDCAN2_FD_BRS && !STM32H5_FDCAN2_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN2_DTSEG2 + int "FDCAN2 DTSEG2 (PhaseSeg2 of data phase)" + default 4 + range 1 15 + depends on CAN_FD && STM32H5_FDCAN2_FD_BRS && !STM32H5_FDCAN2_AUTO_BIT_TIMING + ---help--- + The length of the bit time is Tquanta * (SyncSeg + PropSeg + PhaseSeg1 + PhaseSeg2). + +config STM32H5_FDCAN2_DSJW + int "FDCAN2 fast synchronization jump width" + default 2 + range 1 15 + depends on CAN_FD && STM32H5_FDCAN2_FD_BRS && !STM32H5_FDCAN2_AUTO_BIT_TIMING + ---help--- + The duration of a synchronization jump is Tcan_clk x DSJW. + +endmenu # FDCAN2 Bit Timing + +config STM32H5_FDCAN2_LOOPBACK + bool "Enable FDCAN2 loopback mode" + default n + ---help--- + Enable the FDCAN2 local loopback mode for testing purposes. + +endmenu # FDCAN2 device driver options + +endmenu # "FDCAN driver configuration" menu "I2C Configuration" depends on STM32H5_I2C diff --git a/arch/arm/src/stm32h5/Make.defs b/arch/arm/src/stm32h5/Make.defs index 452d284d14..598d16cf5e 100644 --- a/arch/arm/src/stm32h5/Make.defs +++ b/arch/arm/src/stm32h5/Make.defs @@ -54,6 +54,10 @@ ifeq ($(CONFIG_ADC),y) CHIP_CSRCS += stm32_adc.c endif +ifeq ($(STM32H5_FDCAN_CHARDRIVER),y) +CHIP_CSRCS += stm32_fdcan.c +endif + ifeq ($(CONFIG_STM32H5_SPI),y) CHIP_CSRCS += stm32_spi.c endif diff --git a/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h b/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h index 1379eb0ff9..e455ff208c 100644 --- a/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h +++ b/arch/arm/src/stm32h5/hardware/stm32h56xxx_pinmap.h @@ -98,6 +98,32 @@ #define GPIO_ETH_RMII_TXD1_2 (GPIO_ALT|GPIO_AF11|GPIO_PUSHPULL|GPIO_PORTG|GPIO_PIN12) #define GPIO_ETH_RMII_TXD1_3 (GPIO_ALT|GPIO_AF11|GPIO_PUSHPULL|GPIO_PORTG|GPIO_PIN14) +/* FDCAN */ + +#define GPIO_FDCAN1_RX_1 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTA | GPIO_PIN11) +#define GPIO_FDCAN1_RX_2 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTB | GPIO_PIN8) +#define GPIO_FDCAN1_RX_3 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTD | GPIO_PIN0) +#define GPIO_FDCAN1_RX_4 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTE | GPIO_PIN0) +#define GPIO_FDCAN1_RX_5 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTH | GPIO_PIN14) +#define GPIO_FDCAN1_RX_6 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTI | GPIO_PIN9) +#define GPIO_FDCAN1_RX_7 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTI | GPIO_PIN10) + +#define GPIO_FDCAN1_TX_1 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTA | GPIO_PIN12) +#define GPIO_FDCAN1_TX_2 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTB | GPIO_PIN9) +#define GPIO_FDCAN1_TX_3 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTB | GPIO_PIN7) +#define GPIO_FDCAN1_TX_4 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTD | GPIO_PIN1) +#define GPIO_FDCAN1_TX_5 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTD | GPIO_PIN5) +#define GPIO_FDCAN1_TX_6 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTE | GPIO_PIN1) +#define GPIO_FDCAN1_TX_7 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTH | GPIO_PIN13) + +#define GPIO_FDCAN2_RX_1 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTB | GPIO_PIN5) +#define GPIO_FDCAN2_RX_2 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTB | GPIO_PIN12) +#define GPIO_FDCAN2_RX_3 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTD | GPIO_PIN9) + +#define GPIO_FDCAN2_TX_1 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTB | GPIO_PIN6) +#define GPIO_FDCAN2_TX_2 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTB | GPIO_PIN13) +#define GPIO_FDCAN2_TX_3 (GPIO_ALT | GPIO_AF9 | GPIO_PUSHPULL | GPIO_PORTA | GPIO_PIN10) + /* Clocks outputs */ #define GPIO_MCO_0 (GPIO_ALT|GPIO_AF0|GPIO_PORTA|GPIO_PIN8) diff --git a/arch/arm/src/stm32h5/stm32_fdcan.c b/arch/arm/src/stm32h5/stm32_fdcan.c new file mode 100644 index 0000000000..2816d9b29c --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_fdcan.c @@ -0,0 +1,3598 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_fdcan.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + *s + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "arm_internal.h" +#include "stm32_fdcan.h" +#include "hardware/stm32_pinmap.h" +#include "stm32_gpio.h" +#include "stm32_rcc.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configure FDCAN TQ Clock *************************************************/ + +/* Define STM32_FDCAN_PDIV (FDCAN_CKDIV PDIV[3:0] Value) in board.h */ + +#ifdef STM32_FDCAN_PDIV +# define FDCANCLK_PDIV STM32_FDCAN_PDIV +#else +# define FDCANCLK_PDIV (0) +#endif + +/* Define STM32_FDCAN_FREQUENCY (fdcan_ker_ck) in board.h */ + +#if FDCANCLK_PDIV == 0 +# define STM32_FDCANCLK_FREQUENCY (STM32_FDCAN_FREQUENCY / (1)) +#else +# define STM32_FDCANCLK_FREQUENCY (STM32_FDCAN_FREQUENCY / (2 * FDCANCLK_PDIV)) +#endif + +/* General Configuration ****************************************************/ + +/* FDCAN Message RAM */ + +# define FDCAN_MSGRAM_WORDS (212) +# define STM32_CANRAM1_BASE (STM32_FDCAN_SRAM_BASE + 0x0000) +# define STM32_CANRAM2_BASE (STM32_FDCAN_SRAM_BASE + 1*(FDCAN_MSGRAM_WORDS * 4)) + +# ifdef CONFIG_STM32H5_FDCAN1 +# define FDCAN1_STDFILTER_SIZE (28) +# define FDCAN1_EXTFILTER_SIZE (8) +# define FDCAN1_RXFIFO0_SIZE (3) +# define FDCAN1_RXFIFO1_SIZE (3) +# define FDCAN1_TXEVENTFIFO_SIZE (3) +# define FDCAN1_TXFIFIOQ_SIZE (3) + +# define FDCAN1_STDFILTER_WORDS (28) +# define FDCAN1_EXTFILTER_WORDS (16) +# define FDCAN1_RXFIFO0_WORDS (54) +# define FDCAN1_RXFIFO1_WORDS (54) +# define FDCAN1_TXEVENTFIFO_WORDS (6) +# define FDCAN1_TXFIFIOQ_WORDS (54) +# endif +# ifdef CONFIG_STM32H5_FDCAN2 +# define FDCAN2_STDFILTER_SIZE (28) +# define FDCAN2_EXTFILTER_SIZE (8) +# define FDCAN2_RXFIFO0_SIZE (3) +# define FDCAN2_RXFIFO1_SIZE (3) +# define FDCAN2_TXEVENTFIFO_SIZE (3) +# define FDCAN2_TXFIFIOQ_SIZE (3) + +# define FDCAN2_STDFILTER_WORDS (28) +# define FDCAN2_EXTFILTER_WORDS (16) +# define FDCAN2_RXFIFO0_WORDS (54) +# define FDCAN2_RXFIFO1_WORDS (54) +# define FDCAN2_TXEVENTFIFO_WORDS (6) +# define FDCAN2_TXFIFIOQ_WORDS (54) +# endif + +/* FDCAN1 Configuration *****************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN1 + +/* Bit timing */ + +# ifndef CONFIG_STM32H5_FDCAN1_AUTO_BIT_TIMING + +# define FDCAN1_NTSEG1 (CONFIG_STM32H5_FDCAN1_NTSEG1 - 1) +# define FDCAN1_NTSEG2 (CONFIG_STM32H5_FDCAN1_NTSEG2 - 1) +# define FDCAN1_NBRP ((STM32_FDCANCLK_FREQUENCY / \ + ((FDCAN1_NTSEG1 + FDCAN1_NTSEG2 + 3) * \ + CONFIG_STM32H5_FDCAN1_BITRATE)) - 1) +# define FDCAN1_NSJW (CONFIG_STM32H5_FDCAN1_NSJW - 1) + +# if FDCAN1_NTSEG1 > FDCAN_NBTP_NTSEG1_MAX +# error Invalid FDCAN1 NTSEG1 +# endif +# if FDCAN1_NTSEG2 > FDCAN_NBTP_NTSEG2_MAX +# error Invalid FDCAN1 NTSEG2 +# endif +# if FDCAN1_NSJW > FDCAN_NBTP_NSJW_MAX +# error Invalid FDCAN1 NSJW +# endif +# if FDCAN1_NBRP > FDCAN_NBTP_NBRP_MAX +# error Invalid FDCAN1 NBRP +# endif + +# ifdef CONFIG_STM32H5_FDCAN1_FD_BRS +# define FDCAN1_DTSEG1 (CONFIG_STM32H5_FDCAN1_DTSEG1 - 1) +# define FDCAN1_DTSEG2 (CONFIG_STM32H5_FDCAN1_DTSEG2 - 1) +# define FDCAN1_DBRP ((STM32_FDCANCLK_FREQUENCY / \ + ((FDCAN1_DTSEG1 + FDCAN1_DTSEG2 + 3) * \ + CONFIG_STM32H5_FDCAN1_DBITRATE)) - 1) +# define FDCAN1_DSJW (CONFIG_STM32H5_FDCAN1_DSJW - 1) +# else +# define FDCAN1_DTSEG1 1 +# define FDCAN1_DTSEG2 1 +# define FDCAN1_DBRP 1 +# define FDCAN1_DSJW 1 +# endif /* CONFIG_STM32_FDCAN1_FD_BRS */ + +# if FDCAN1_DTSEG1 > FDCAN_DBTP_DTSEG1_MAX +# error Invalid FDCAN1 DTSEG1 +# endif +# if FDCAN1_DTSEG2 > FDCAN_DBTP_DTSEG2_MAX +# error Invalid FDCAN1 DTSEG2 +# endif +# if FDCAN1_DBRP > FDCAN_DBTP_DBRP_MAX +# error Invalid FDCAN1 DBRP +# endif +# if FDCAN1_DSJW > FDCAN_DBTP_DSJW_MAX +# error Invalid FDCAN1 DSJW +# endif +# endif + +/* FDCAN1 Message RAM Configuration *****************************************/ + +/* FDCAN1 Message RAM Layout */ + +# define FDCAN1_STDFILTER_INDEX 0 +# define FDCAN1_EXTFILTERS_INDEX (FDCAN1_STDFILTER_INDEX + FDCAN1_STDFILTER_WORDS) +# define FDCAN1_RXFIFO0_INDEX (FDCAN1_EXTFILTERS_INDEX + FDCAN1_EXTFILTER_WORDS) +# define FDCAN1_RXFIFO1_INDEX (FDCAN1_RXFIFO0_INDEX + FDCAN1_RXFIFO0_WORDS) +# define FDCAN1_TXEVENTFIFO_INDEX (FDCAN1_RXFIFO1_INDEX + FDCAN1_RXFIFO1_WORDS) +# define FDCAN1_TXFIFOQ_INDEX (FDCAN1_TXEVENTFIFO_INDEX + FDCAN1_TXEVENTFIFO_WORDS) +# define FDCAN1_MSGRAM_WORDS (FDCAN1_TXFIFOQ_INDEX + FDCAN1_TXFIFIOQ_WORDS) + +#endif /* CONFIG_STM32H5_FDCAN1 */ + +/* FDCAN2 Configuration *****************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN2 + +/* Bit timing */ + +# ifndef CONFIG_STM32H5_FDCAN2_AUTO_BIT_TIMING + +# define FDCAN2_NTSEG1 (CONFIG_STM32H5_FDCAN2_NTSEG1 - 1) +# define FDCAN2_NTSEG2 (CONFIG_STM32H5_FDCAN2_NTSEG2 - 1) +# define FDCAN2_NBRP (((STM32_FDCANCLK_FREQUENCY / \ + ((FDCAN2_NTSEG1 + FDCAN2_NTSEG2 + 3) * \ + CONFIG_STM32H5_FDCAN2_BITRATE)) - 1)) +# define FDCAN2_NSJW (CONFIG_STM32H5_FDCAN2_NSJW - 1) + +# if FDCAN2_NTSEG1 > FDCAN_NBTP_NTSEG1_MAX +# error Invalid FDCAN2 NTSEG1 +# endif +# if FDCAN2_NTSEG2 > FDCAN_NBTP_NTSEG2_MAX +# error Invalid FDCAN2 NTSEG2 +# endif +# if FDCAN2_NSJW > FDCAN_NBTP_NSJW_MAX +# error Invalid FDCAN2 NSJW +# endif +# if FDCAN2_NBRP > FDCAN_NBTP_NBRP_MAX +# error Invalid FDCAN1 NBRP +# endif + +# ifdef CONFIG_STM32H5_FDCAN2_FD_BRS +# define FDCAN2_DTSEG1 (CONFIG_STM32H5_FDCAN2_DTSEG1 - 1) +# define FDCAN2_DTSEG2 (CONFIG_STM32H5_FDCAN2_DTSEG2 - 1) +# define FDCAN2_DBRP (((STM32_FDCANCLK_FREQUENCY / \ + ((FDCAN2_DTSEG1 + FDCAN2_DTSEG2 + 3) * \ + CONFIG_STM32H5_FDCAN2_DBITRATE)) - 1)) +# define FDCAN2_DSJW (CONFIG_STM32H5_FDCAN2_DSJW - 1) +# else +# define FDCAN2_DTSEG1 1 +# define FDCAN2_DTSEG2 1 +# define FDCAN2_DBRP 1 +# define FDCAN2_DSJW 1 +# endif /* CONFIG_STM32_FDCAN2_FD_BRS */ + +# if FDCAN2_DTSEG1 > FDCAN_DBTP_DTSEG1_MAX +# error Invalid FDCAN2 DTSEG1 +# endif +# if FDCAN2_DTSEG2 > FDCAN_DBTP_DTSEG2_MAX +# error Invalid FDCAN2 DTSEG2 +# endif +# if FDCAN2_DBRP > FDCAN_DBTP_DBRP_MAX +# error Invalid FDCAN2 DBRP +# endif +# if FDCAN2_DSJW > FDCAN_DBTP_DSJW_MAX +# error Invalid FDCAN2 DSJW +# endif + +# endif + +/* FDCAN2 Message RAM Configuration *****************************************/ + +/* FDCAN2 Message RAM Layout */ + +# define FDCAN2_STDFILTER_INDEX 0 +# define FDCAN2_EXTFILTERS_INDEX (FDCAN2_STDFILTER_INDEX + FDCAN2_STDFILTER_WORDS) +# define FDCAN2_RXFIFO0_INDEX (FDCAN2_EXTFILTERS_INDEX + FDCAN2_EXTFILTER_WORDS) +# define FDCAN2_RXFIFO1_INDEX (FDCAN2_RXFIFO0_INDEX + FDCAN2_RXFIFO0_WORDS) +# define FDCAN2_TXEVENTFIFO_INDEX (FDCAN2_RXFIFO1_INDEX + FDCAN2_RXFIFO1_WORDS) +# define FDCAN2_TXFIFOQ_INDEX (FDCAN2_TXEVENTFIFO_INDEX + FDCAN2_TXEVENTFIFO_WORDS) +# define FDCAN2_MSGRAM_WORDS (FDCAN2_TXFIFOQ_INDEX + FDCAN2_TXFIFIOQ_WORDS) + +#endif /* CONFIG_STM32H5_FDCAN2 */ + +/* Interrupts ***************************************************************/ + +/* Common interrupts + * + * FDCAN_INT_TSW - Timestamp Wraparound + * FDCAN_INT_MRAF - Message RAM Access Failure + * FDCAN_INT_TOO - Timeout Occurred + * FDCAN_INT_ELO - Error Logging Overflow + * FDCAN_INT_EP - Error Passive + * FDCAN_INT_EW - Warning Status + * FDCAN_INT_BO - Bus_Off Status + * FDCAN_INT_WDI - Watchdog Interrupt + * FDCAN_INT_PEA - Protocol Error in Arbritration Phase + * FDCAN_INT_PED - Protocol Error in Data Phase + */ + +#define FDCAN_CMNERR_INTS (FDCAN_INT_MRAF | FDCAN_INT_TOO | FDCAN_INT_EP | \ + FDCAN_INT_BO | FDCAN_INT_WDI | FDCAN_INT_PEA | \ + FDCAN_INT_PED) +#define FDCAN_COMMON_INTS FDCAN_CMNERR_INTS + +/* RXFIFO mode interrupts + * + * FDCAN_INT_RF0N - Receive FIFO 0 New Message + * FDCAN_INT_RF0F - Receive FIFO 0 Full + * FDCAN_INT_RF0L - Receive FIFO 0 Message Lost + * FDCAN_INT_RF1N - Receive FIFO 1 New Message + * FDCAN_INT_RF1F - Receive FIFO 1 Full + * FDCAN_INT_RF1L - Receive FIFO 1 Message Lost + * FDCAN_INT_HPM - High Priority Message Received + * + */ + +#define FDCAN_RXCOMMON_INTS 0 +#define FDCAN_RXFIFO0_INTS (FDCAN_INT_RF0N | FDCAN_INT_RF0L) +#define FDCAN_RXFIFO1_INTS (FDCAN_INT_RF1N | FDCAN_INT_RF1L) +#define FDCAN_RXFIFO_INTS (FDCAN_RXFIFO0_INTS | FDCAN_RXFIFO1_INTS | \ + FDCAN_INT_HPM | FDCAN_RXCOMMON_INTS) + +#define FDCAN_RXERR_INTS (FDCAN_INT_RF0L | FDCAN_INT_RF1L) + +/* TX FIFOQ mode interrupts + * + * FDCAN_INT_TFE - Tx FIFO Empty + * + * TX Event FIFO interrupts + * + * FDCAN_INT_TEFN - Tx Event FIFO New Entry + * FDCAN_INT_TEFF - Tx Event FIFO Full + * FDCAN_INT_TEFL - Tx Event FIFO Element Lost + * + * Mode-independent TX-related interrupts + * + * FDCAN_INT_TC - Transmission Completed + * FDCAN_INT_TCF - Transmission Cancellation Finished + */ + +#define FDCAN_TXCOMMON_INTS (FDCAN_INT_TC | FDCAN_INT_TCF) +#define FDCAN_TXFIFOQ_INTS (FDCAN_INT_TFE | FDCAN_TXCOMMON_INTS) +#define FDCAN_TXEVFIFO_INTS (FDCAN_INT_TEFN | FDCAN_INT_TEFF | \ + FDCAN_INT_TEFL) +#define FDCAN_TXDEDBUF_INTS FDCAN_TXCOMMON_INTS + +#define FDCAN_TXERR_INTS (FDCAN_INT_TEFL | FDCAN_INT_PEA | FDCAN_INT_PED) + +/* Common-, TX- and RX-Error-Mask */ + +#define FDCAN_ANYERR_INTS (FDCAN_CMNERR_INTS | FDCAN_RXERR_INTS | FDCAN_TXERR_INTS) + +/* Convenience macro for clearing all interrupts */ + +#define FDCAN_INT_ALL 0x3fcfffff + +/* Debug ********************************************************************/ + +/* Debug configurations that may be enabled just for testing FDCAN */ + +#ifndef CONFIG_DEBUG_CAN_INFO +# undef CONFIG_STM32H5_FDCAN_REGDEBUG +#endif + +#undef STM32H5_FDCAN_LOOPBACK +#if defined(CONFIG_STM32H5_FDCAN1_LOOPBACK) || \ + defined(CONFIG_STM32H5_FDCAN2_LOOPBACK) +# define STM32H5_FDCAN_LOOPBACK 1 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* CAN frame format */ + +enum stm32_frameformat_e +{ + FDCAN_ISO11898_1_FORMAT = 0, /* Frame format according to ISO11898-1 */ + FDCAN_NONISO_BOSCH_V1_FORMAT = 1 /* Frame format according to Bosch CAN FD V1.0 */ +}; + +/* CAN mode of operation */ + +enum stm32_canmode_e +{ + FDCAN_CLASSIC_MODE = 0, /* Classic CAN operation */ + FDCAN_FD_MODE = 1, /* CAN FD operation */ + FDCAN_FD_BRS_MODE = 2 /* CAN FD operation with bit rate switching */ +}; + +/* CAN driver state */ + +enum can_state_s +{ + FDCAN_STATE_UNINIT = 0, /* Not yet initialized */ + FDCAN_STATE_RESET, /* Initialized, reset state */ + FDCAN_STATE_SETUP, /* fdcan_setup() has been called */ + FDCAN_STATE_DISABLED /* Disabled by a fdcan_shutdown() */ +}; + +/* This structure describes the FDCAN message RAM layout */ + +struct stm32_msgram_s +{ + volatile uint32_t *stdfilters; /* Standard filters */ + volatile uint32_t *extfilters; /* Extended filters */ + volatile uint32_t *rxfifo0; /* RX FIFO0 */ + volatile uint32_t *rxfifo1; /* RX FIFO1 */ + volatile uint32_t *txeventfifo; /* TX event FIFO */ + volatile uint32_t *txfifoq; /* TX FIFO queue */ +}; + +/* This structure provides the constant configuration of a FDCAN peripheral */ + +struct stm32_config_s +{ + uint32_t rxpinset; /* RX pin configuration */ + uint32_t txpinset; /* TX pin configuration */ + uintptr_t base; /* Base address of the FDCAN registers */ + uint32_t baud; /* Configured baud */ + uint32_t data_baud; /* Configured data baud */ + uint32_t nbtp; /* Nominal bit timing/prescaler register setting */ + uint32_t dbtp; /* Data bit timing/prescaler register setting */ + uint8_t port; /* FDCAN port number (1 or 2) */ + uint8_t irq0; /* FDCAN peripheral IRQ number for interrupt line 0 */ + uint8_t irq1; /* FDCAN peripheral IRQ number for interrupt line 1 */ + uint8_t mode; /* See enum stm32_canmode_e */ + uint8_t format; /* See enum stm32_frameformat_e */ + uint8_t nstdfilters; /* Number of standard filters */ + uint8_t nextfilters; /* Number of extended filters */ + uint8_t nrxfifo0; /* Number of RX FIFO0 elements */ + uint8_t nrxfifo1; /* Number of RX FIFO1 elements */ + uint8_t ntxeventfifo; /* Number of TXevent FIFO elements */ + uint8_t ntxfifoq; /* Number of TX FIFO queue elements */ + uint8_t rxfifo0esize; /* RX FIFO0 element size (words) */ + uint8_t rxfifo1esize; /* RX FIFO1 element size (words) */ + uint8_t txeventesize; /* TXevent element size (words) */ + uint8_t txbufferesize; /* TX buffer element size (words) */ +#ifdef STM32H5_FDCAN_LOOPBACK + bool loopback; /* True: Loopback mode */ +#endif + + /* FDCAN message RAM layout */ + + struct stm32_msgram_s msgram; +}; + +/* This structure provides the current state of a FDCAN peripheral */ + +struct stm32_fdcan_s +{ + /* The constant configuration */ + + const struct stm32_config_s *config; + + uint8_t state; /* See enum can_state_s */ +#ifdef CONFIG_CAN_EXTID + uint8_t nextalloc; /* Number of allocated extended filters */ +#endif + uint8_t nstdalloc; /* Number of allocated standard filters */ + uint32_t nbtp; /* Current nominal bit timing */ + uint32_t dbtp; /* Current data bit timing */ + uint32_t rxints; /* Configured RX interrupts */ + uint32_t txints; /* Configured TX interrupts */ + +#ifdef CONFIG_CAN_EXTID + uint32_t extfilters[2]; /* Extended filter bit allocator. 2*32=64 */ +#endif + uint32_t stdfilters[4]; /* Standard filter bit allocator. 4*32=128 */ + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG + uintptr_t regaddr; /* Last register address read */ + uint32_t regval; /* Last value read from the register */ + unsigned int count; /* Number of times that the value was read */ +#endif +}; + +struct fdcan_bitseg +{ + uint32_t bitrate; + uint8_t sjw; + uint8_t bs1; + uint8_t bs2; + uint8_t prescaler; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* FDCAN Register access */ + +static uint32_t fdcan_getreg(struct stm32_fdcan_s *priv, int offset); +static void fdcan_putreg(struct stm32_fdcan_s *priv, int offset, + uint32_t regval); +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG +static void fdcan_dumpregs(struct stm32_fdcan_s *priv, + const char *msg); +static void fdcan_dumprxregs(struct stm32_fdcan_s *priv, + const char *msg); +static void fdcan_dumptxregs(struct stm32_fdcan_s *priv, + const char *msg); +static void fdcan_dumpramlayout(struct stm32_fdcan_s *priv); +#else +# define fdcan_dumpregs(priv,msg) +# define fdcan_dumprxregs(priv,msg) +# define fdcan_dumptxregs(priv,msg) +# define fdcan_dumpramlayout(priv) +#endif + +/* FDCAN helpers */ + +static uint8_t fdcan_dlc2bytes(struct stm32_fdcan_s *priv, uint8_t dlc); + +#ifdef CONFIG_CAN_EXTID +static int fdcan_add_extfilter(struct stm32_fdcan_s *priv, + struct canioc_extfilter_s *extconfig); +static int fdcan_del_extfilter(struct stm32_fdcan_s *priv, int ndx); +#endif +static int fdcan_add_stdfilter(struct stm32_fdcan_s *priv, + struct canioc_stdfilter_s *stdconfig); +static int fdcan_del_stdfilter(struct stm32_fdcan_s *priv, int ndx); + +static int +fdcan_start_busoff_recovery_sequence(struct stm32_fdcan_s *priv); + +/* CAN driver methods */ + +static void fdcan_reset(struct can_dev_s *dev); +static int fdcan_setup(struct can_dev_s *dev); +static void fdcan_shutdown(struct can_dev_s *dev); +static void fdcan_rxint(struct can_dev_s *dev, bool enable); +static void fdcan_txint(struct can_dev_s *dev, bool enable); +static int fdcan_ioctl(struct can_dev_s *dev, int cmd, + unsigned long arg); +static int fdcan_remoterequest(struct can_dev_s *dev, uint16_t id); +static int fdcan_send(struct can_dev_s *dev, struct can_msg_s *msg); +static bool fdcan_txready(struct can_dev_s *dev); +static bool fdcan_txempty(struct can_dev_s *dev); + +/* FDCAN interrupt handling */ + +#ifdef CONFIG_CAN_ERRORS +static void fdcan_error(struct can_dev_s *dev, uint32_t status); +#endif +static void fdcan_receive(struct can_dev_s *dev, + volatile uint32_t *rxbuffer, + unsigned long nwords); +static int fdcan_interrupt(int irq, void *context, void *arg); + +/* Hardware initialization */ + +static int fdcan_hw_initialize(struct stm32_fdcan_s *priv); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct can_ops_s g_fdcanops = +{ + .co_reset = fdcan_reset, + .co_setup = fdcan_setup, + .co_shutdown = fdcan_shutdown, + .co_rxint = fdcan_rxint, + .co_txint = fdcan_txint, + .co_ioctl = fdcan_ioctl, + .co_remoterequest = fdcan_remoterequest, + .co_send = fdcan_send, + .co_txready = fdcan_txready, + .co_txempty = fdcan_txempty, +}; + +#ifdef CONFIG_STM32H5_FDCAN1 +/* Message RAM allocation */ + +/* Constant configuration */ + +static const struct stm32_config_s g_fdcan1const = +{ + .rxpinset = GPIO_FDCAN1_RX, + .txpinset = GPIO_FDCAN1_TX, + .base = STM32_FDCAN1_BASE, + .baud = CONFIG_STM32H5_FDCAN1_BITRATE, +#if defined(CONFIG_STM32H5_FDCAN1_FD_BRS) + .data_baud = CONFIG_STM32H5_FDCAN1_DBITRATE, +#endif +#ifndef CONFIG_STM32H5_FDCAN1_AUTO_BIT_TIMING + .nbtp = FDCAN_NBTP_NBRP(FDCAN1_NBRP) | + FDCAN_NBTP_NTSEG1(FDCAN1_NTSEG1) | + FDCAN_NBTP_NTSEG2(FDCAN1_NTSEG2) | + FDCAN_NBTP_NSJW(FDCAN1_NSJW), + .dbtp = FDCAN_DBTP_DBRP(FDCAN1_DBRP) | + FDCAN_DBTP_DTSEG1(FDCAN1_DTSEG1) | + FDCAN_DBTP_DTSEG2(FDCAN1_DTSEG2) | + FDCAN_DBTP_DSJW(FDCAN1_DSJW), +#endif + .port = 1, + .irq0 = STM32_IRQ_FDCAN1_IT0, + .irq1 = STM32_IRQ_FDCAN1_IT1, +#if defined(CONFIG_STM32H5_FDCAN1_CLASSIC) + .mode = FDCAN_CLASSIC_MODE, +#elif defined(CONFIG_STM32H5_FDCAN1_FD) + .mode = FDCAN_FD_MODE, +#else + .mode = FDCAN_FD_BRS_MODE, +#endif +#if defined(CONFIG_STM32H5_FDCAN1_NONISO_FORMAT) + .format = FDCAN_NONISO_BOSCH_V1_FORMAT, +#else + .format = FDCAN_ISO11898_1_FORMAT, +#endif + .nstdfilters = FDCAN1_STDFILTER_SIZE, + .nextfilters = FDCAN1_EXTFILTER_SIZE, + .nrxfifo0 = FDCAN1_RXFIFO0_SIZE, + .nrxfifo1 = FDCAN1_RXFIFO1_SIZE, + .ntxeventfifo = FDCAN1_TXEVENTFIFO_SIZE, + .ntxfifoq = FDCAN1_TXFIFIOQ_SIZE, + .rxfifo0esize = (FDCAN1_RXFIFO0_WORDS / FDCAN1_RXFIFO0_SIZE), + .rxfifo1esize = (FDCAN1_RXFIFO1_WORDS / FDCAN1_RXFIFO1_SIZE), + .txeventesize = (FDCAN1_TXEVENTFIFO_WORDS / FDCAN1_TXEVENTFIFO_SIZE), + .txbufferesize = (FDCAN1_TXFIFIOQ_WORDS / FDCAN1_TXFIFIOQ_SIZE), + +#ifdef CONFIG_STM32H5_FDCAN1_LOOPBACK + .loopback = true, +#endif + + /* FDCAN1 Message RAM */ + + .msgram = + { + (uint32_t *)(STM32_CANRAM1_BASE + (FDCAN1_STDFILTER_INDEX << 2)), + (uint32_t *)(STM32_CANRAM1_BASE + (FDCAN1_EXTFILTERS_INDEX << 2)), + (uint32_t *)(STM32_CANRAM1_BASE + (FDCAN1_RXFIFO0_INDEX << 2)), + (uint32_t *)(STM32_CANRAM1_BASE + (FDCAN1_RXFIFO1_INDEX << 2)), + (uint32_t *)(STM32_CANRAM1_BASE + (FDCAN1_TXEVENTFIFO_INDEX << 2)), + (uint32_t *)(STM32_CANRAM1_BASE + (FDCAN1_TXFIFOQ_INDEX << 2)) + } +}; + +/* FDCAN1 variable driver state */ + +static struct stm32_fdcan_s g_fdcan1priv; +static struct can_dev_s g_fdcan1dev; + +#endif /* CONFIG_STM32H5_FDCAN1 */ + +#ifdef CONFIG_STM32H5_FDCAN2 +/* FDCAN2 message RAM allocation */ + +/* FDCAN2 constant configuration */ + +static const struct stm32_config_s g_fdcan2const = +{ + .rxpinset = GPIO_FDCAN2_RX, + .txpinset = GPIO_FDCAN2_TX, + .base = STM32_FDCAN2_BASE, + .baud = CONFIG_STM32H5_FDCAN2_BITRATE, +#if defined(CONFIG_STM32H5_FDCAN2_FD_BRS) + .data_baud = CONFIG_STM32H5_FDCAN2_DBITRATE, +#endif +#ifndef CONFIG_STM32H5_FDCAN2_AUTO_BIT_TIMING + .nbtp = FDCAN_NBTP_NBRP(FDCAN2_NBRP) | + FDCAN_NBTP_NTSEG1(FDCAN2_NTSEG1) | + FDCAN_NBTP_NTSEG2(FDCAN2_NTSEG2) | + FDCAN_NBTP_NSJW(FDCAN2_NSJW), + .dbtp = FDCAN_DBTP_DBRP(FDCAN2_DBRP) | + FDCAN_DBTP_DTSEG1(FDCAN2_DTSEG1) | + FDCAN_DBTP_DTSEG2(FDCAN2_DTSEG2) | + FDCAN_DBTP_DSJW(FDCAN2_DSJW), +#endif + .port = 2, + .irq0 = STM32_IRQ_FDCAN2_IT0, + .irq1 = STM32_IRQ_FDCAN2_IT1, +#if defined(CONFIG_STM32H5_FDCAN2_CLASSIC) + .mode = FDCAN_CLASSIC_MODE, +#elif defined(CONFIG_STM32H5_FDCAN2_FD) + .mode = FDCAN_FD_MODE, +#else + .mode = FDCAN_FD_BRS_MODE, +#endif +#if defined(CONFIG_STM32H5_FDCAN2_NONISO_FORMAT) + .format = FDCAN_NONISO_BOSCH_V1_FORMAT, +#else + .format = FDCAN_ISO11898_1_FORMAT, +#endif + .nstdfilters = FDCAN2_STDFILTER_SIZE, + .nextfilters = FDCAN2_EXTFILTER_SIZE, + .nrxfifo0 = FDCAN2_RXFIFO0_SIZE, + .nrxfifo1 = FDCAN2_RXFIFO1_SIZE, + .ntxeventfifo = FDCAN2_TXEVENTFIFO_SIZE, + .ntxfifoq = FDCAN2_TXFIFIOQ_SIZE, + .rxfifo0esize = (FDCAN2_RXFIFO0_WORDS / FDCAN2_RXFIFO0_SIZE), + .rxfifo1esize = (FDCAN2_RXFIFO1_WORDS / FDCAN2_RXFIFO1_SIZE), + .txeventesize = (FDCAN2_TXEVENTFIFO_WORDS / FDCAN2_TXEVENTFIFO_SIZE), + .txbufferesize = (FDCAN2_TXFIFIOQ_WORDS / FDCAN2_TXFIFIOQ_SIZE), + +#ifdef CONFIG_STM32H5_FDCAN2_LOOPBACK + .loopback = true, +#endif + + /* FDCAN2 Message RAM */ + + .msgram = + { + (uint32_t *)(STM32_CANRAM2_BASE + (FDCAN2_STDFILTER_INDEX << 2)), + (uint32_t *)(STM32_CANRAM2_BASE + (FDCAN2_EXTFILTERS_INDEX << 2)), + (uint32_t *)(STM32_CANRAM2_BASE + (FDCAN2_RXFIFO0_INDEX << 2)), + (uint32_t *)(STM32_CANRAM2_BASE + (FDCAN2_RXFIFO1_INDEX << 2)), + (uint32_t *)(STM32_CANRAM2_BASE + (FDCAN2_TXEVENTFIFO_INDEX << 2)), + (uint32_t *)(STM32_CANRAM2_BASE + (FDCAN2_TXFIFOQ_INDEX << 2)) + } +}; + +/* FDCAN2 variable driver state */ + +static struct stm32_fdcan_s g_fdcan2priv; +static struct can_dev_s g_fdcan2dev; + +#endif /* CONFIG_STM32H5_FDCAN2 */ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: fdcan_getreg + * + * Description: + * Read the value of a FDCAN register. + * + * Input Parameters: + * priv - A reference to the FDCAN peripheral state + * offset - The offset to the register to read + * + * Returned Value: + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG +static uint32_t fdcan_getreg(struct stm32_fdcan_s *priv, int offset) +{ + const struct stm32_config_s *config = priv->config; + uintptr_t regaddr = 0; + uint32_t regval = 0; + + /* Read the value from the register */ + + regaddr = config->base + offset; + regval = getreg32(regaddr); + + /* Is this the same value that we read from the same register last time? + * Are we polling the register? If so, suppress some of the output. + */ + + if (regaddr == priv->regaddr && regval == priv->regval) + { + if (priv->count == 0xffffffff || ++priv->count > 3) + { + if (priv->count == 4) + { + caninfo("...\n"); + } + + return regval; + } + } + + /* No this is a new address or value */ + + else + { + /* Did we print "..." for the previous value? */ + + if (priv->count > 3) + { + /* Yes.. then show how many times the value repeated */ + + caninfo("[repeats %d more times]\n", priv->count - 3); + } + + /* Save the new address, value, and count */ + + priv->regaddr = regaddr; + priv->regval = regval; + priv->count = 1; + } + + /* Show the register value read */ + + caninfo("%08" PRIx32 "->%08" PRIx32 "\n", regaddr, regval); + return regval; +} + +#else +static uint32_t fdcan_getreg(struct stm32_fdcan_s *priv, int offset) +{ + const struct stm32_config_s *config = priv->config; + return getreg32(config->base + offset); +} + +#endif + +/**************************************************************************** + * Name: fdcan_putreg + * + * Description: + * Set the value of a FDCAN register. + * + * Input Parameters: + * priv - A reference to the FDCAN peripheral state + * offset - The offset to the register to write + * regval - The value to write to the register + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG +static void fdcan_putreg(struct stm32_fdcan_s *priv, int offset, + uint32_t regval) +{ + const struct stm32_config_s *config = priv->config; + uintptr_t regaddr = config->base + offset; + + /* Show the register value being written */ + + caninfo("%08" PRIx32 "->%08" PRIx32 "\n", regaddr, regval); + + /* Write the value */ + + putreg32(regval, regaddr); +} + +#else +static void fdcan_putreg(struct stm32_fdcan_s *priv, int offset, + uint32_t regval) +{ + const struct stm32_config_s *config = priv->config; + putreg32(regval, config->base + offset); +} + +#endif + +/**************************************************************************** + * Name: fdcan_dumpctrlregs + * + * Description: + * Dump the contents of all CAN control registers + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG +static void fdcan_dumpregs(struct stm32_fdcan_s *priv, + const char *msg) +{ + const struct stm32_config_s *config = priv->config; + + caninfo("CAN%d Control and Status Registers: %s\n", config->port, msg); + caninfo(" Base: %08" PRIx32 "\n", config->base); + + /* CAN control and status registers */ + + caninfo(" CCCR: %08" PRIx32 " TEST: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_CCCR_OFFSET), + getreg32(config->base + STM32_FDCAN_TEST_OFFSET)); + + caninfo(" NBTP: %08" PRIx32 " DBTP: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_NBTP_OFFSET), + getreg32(config->base + STM32_FDCAN_DBTP_OFFSET)); + + caninfo(" IE: %08" PRIx32 " TIE: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_IE_OFFSET), + getreg32(config->base + STM32_FDCAN_TXBTIE_OFFSET)); + + caninfo(" ILE: %08" PRIx32 " ILS: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_ILE_OFFSET), + getreg32(config->base + STM32_FDCAN_ILS_OFFSET)); + + caninfo(" TXBC: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_TXBC_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: stm32can_dumprxregs + * + * Description: + * Dump the contents of all Rx status registers + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG +static void fdcan_dumprxregs(struct stm32_fdcan_s *priv, + const char *msg) +{ + const struct stm32_config_s *config = priv->config; + + caninfo("CAN%d Rx Registers: %s\n", config->port, msg); + caninfo(" Base: %08" PRIx32 "\n", config->base); + + caninfo(" PSR: %08" PRIx32 " ECR: %08" PRIx32 + " HPMS: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_PSR_OFFSET), + getreg32(config->base + STM32_FDCAN_ECR_OFFSET), + getreg32(config->base + STM32_FDCAN_HPMS_OFFSET)); + + caninfo(" RXF0S: %08" PRIx32 " RXF0A: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_RXF0S_OFFSET), + getreg32(config->base + STM32_FDCAN_RXF0A_OFFSET)); + + caninfo(" RXF1S: %08" PRIx32 " RXF1A: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_RXF1S_OFFSET), + getreg32(config->base + STM32_FDCAN_RXF1A_OFFSET)); + + caninfo(" IR: %08" PRIx32 " IE: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_IR_OFFSET), + getreg32(config->base + STM32_FDCAN_IE_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: stm32can_dumptxregs + * + * Description: + * Dump the contents of all Tx buffer registers + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG +static void fdcan_dumptxregs(struct stm32_fdcan_s *priv, + const char *msg) +{ + const struct stm32_config_s *config = priv->config; + + caninfo("CAN%d Tx Registers: %s\n", config->port, msg); + caninfo(" Base: %08" PRIx32 "\n", config->base); + + caninfo(" PSR: %08" PRIx32 " ECR: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_PSR_OFFSET), + getreg32(config->base + STM32_FDCAN_ECR_OFFSET)); + + caninfo(" TXQFS: %08" PRIx32 " TXBAR: %08" PRIx32 + " TXBRP: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_TXFQS_OFFSET), + getreg32(config->base + STM32_FDCAN_TXBAR_OFFSET), + getreg32(config->base + STM32_FDCAN_TXBRP_OFFSET)); + + caninfo(" TXBTO: %08" PRIx32 " TXBCR: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_TXBTO_OFFSET), + getreg32(config->base + STM32_FDCAN_TXBCR_OFFSET)); + + caninfo(" TXEFS: %08" PRIx32 " TXEFA: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_TXEFS_OFFSET), + getreg32(config->base + STM32_FDCAN_TXEFA_OFFSET)); + + caninfo(" IR: %08" PRIx32 " IE: %08" PRIx32 + " TIE: %08" PRIx32 "\n", + getreg32(config->base + STM32_FDCAN_IR_OFFSET), + getreg32(config->base + STM32_FDCAN_IE_OFFSET), + getreg32(config->base + STM32_FDCAN_TXBTIE_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: stm32can_dumpramlayout + * + * Description: + * Print the layout of the message RAM + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG +static void fdcan_dumpramlayout(struct stm32_fdcan_s *priv) +{ + const struct stm32_config_s *config = priv->config; + + caninfo(" ******* FDCAN%d Message RAM layout *******\n", config->port); + caninfo(" Start # Elmnt Elmnt size (words)\n"); + + if (config->nstdfilters > 0) + { + caninfo("STD filters %p %4d %2d\n", + config->msgram.stdfilters, + config->nstdfilters, + 1); + } + + if (config->nextfilters > 0) + { + caninfo("EXT filters %p %4d %2d\n", + config->msgram.extfilters, + config->nextfilters, + 2); + } + + if (config->nrxfifo0 > 0) + { + caninfo("RX FIFO 0 %p %4d %2d\n", + config->msgram.rxfifo0, + config->nrxfifo0, + config->rxfifo0esize); + } + + if (config->nrxfifo1 > 0) + { + caninfo("RX FIFO 1 %p %4d %2d\n", + config->msgram.rxfifo1, + config->nrxfifo1, + config->rxfifo1esize); + } + + if (config->ntxeventfifo > 0) + { + caninfo("TX EVENT %p %4d %2d\n", + config->msgram.txeventfifo, + config->ntxeventfifo, + config->txeventesize); + } + + if (config->ntxfifoq > 0) + { + caninfo("TX FIFO %p %4d %2d\n", + config->msgram.txfifoq, + config->ntxfifoq, + config->txbufferesize); + } +} +#endif + +/**************************************************************************** + * Name: fdcan_dlc2bytes + * + * Description: + * In the CAN FD format, the coding of the DLC differs from the standard + * CAN format. The DLC codes 0 to 8 have the same coding as in standard + * CAN. But the codes 9 to 15 all imply a data field of 8 bytes with + * standard CAN. In CAN FD mode, the values 9 to 15 are encoded to values + * in the range 12 to 64. + * + * Input Parameters: + * dlc - the DLC value to convert to a byte count + * + * Returned Value: + * The number of bytes corresponding to the DLC value. + * + ****************************************************************************/ + +static uint8_t fdcan_dlc2bytes(struct stm32_fdcan_s *priv, uint8_t dlc) +{ + if (dlc > 8) + { +#ifdef CONFIG_CAN_FD + if (priv->config->mode == FDCAN_CLASSIC_MODE) + { + return 8; + } + else + { + switch (dlc) + { + case 9: + return 12; + case 10: + return 16; + case 11: + return 20; + case 12: + return 24; + case 13: + return 32; + case 14: + return 48; + default: + case 15: + return 64; + } + } +#else + return 8; +#endif + } + + return dlc; +} + +/**************************************************************************** + * Name: fdcan_add_extfilter + * + * Description: + * Add an address filter for a extended 29 bit address. + * + * Input Parameters: + * priv - An instance of the FDCAN driver state structure. + * extconfig - The configuration of the extended filter + * + * Returned Value: + * A non-negative filter ID is returned on success. Otherwise a negated + * errno value is returned to indicate the nature of the error. + * + ****************************************************************************/ + +#ifdef CONFIG_CAN_EXTID +static int fdcan_add_extfilter(struct stm32_fdcan_s *priv, + struct canioc_extfilter_s *extconfig) +{ + const struct stm32_config_s *config = NULL; + volatile uint32_t *extfilter = NULL; + uint32_t regval = 0; + int word = 0; + int bit = 0; + int ndx = 0; + + DEBUGASSERT(priv != NULL && priv->config != NULL && extconfig != NULL); + config = priv->config; + + /* Find an unused standard filter */ + + for (ndx = 0; ndx < config->nextfilters; ndx++) + { + /* Is this filter assigned? */ + + word = ndx >> 5; + bit = ndx & 0x1f; + + if ((priv->extfilters[word] & (1 << bit)) == 0) + { + /* No, assign the filter */ + + DEBUGASSERT(priv->nextalloc < priv->config->nstdfilters); + priv->extfilters[word] |= (1 << bit); + priv->nextalloc++; + + extfilter = config->msgram.extfilters + (ndx << 1); + + /* Format and write filter word F0 */ + + DEBUGASSERT(extconfig->xf_id1 <= CAN_MAX_EXTMSGID); + regval = EXTFILTER_F0_EFID1(extconfig->xf_id1); + + if (extconfig->xf_prio == 0) + { + regval |= EXTFILTER_F0_EFEC_FIFO0; + } + else + { + regval |= EXTFILTER_F0_EFEC_FIFO1; + } + + extfilter[0] = regval; + + /* Format and write filter word F1 */ + + DEBUGASSERT(extconfig->xf_id2 <= CAN_MAX_EXTMSGID); + regval = EXTFILTER_F1_EFID2(extconfig->xf_id2); + + switch (extconfig->xf_type) + { + case CAN_FILTER_DUAL: + { + regval |= EXTFILTER_F1_EFT_DUAL; + break; + } + + case CAN_FILTER_MASK: + { + regval |= EXTFILTER_F1_EFT_CLASSIC; + break; + } + + case CAN_FILTER_RANGE: + { + regval |= EXTFILTER_F1_EFT_RANGE; + break; + } + + default: + { + return -EINVAL; + } + } + + extfilter[1] = regval; + + /* Is this the first extended filter? */ + + if (priv->nextalloc == 1) + { + /* Enable the Initialization state */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= FDCAN_CCCR_INIT; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Wait for initialization mode to take effect */ + + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & + FDCAN_CCCR_INIT) == 0); + + /* Enable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= (FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Update the Global Filter Configuration so that received + * messages are rejected if they do not match the acceptance + * filter. + * + * ANFE=2: Discard all rejected frames + */ + + regval = fdcan_getreg(priv, STM32_FDCAN_RXGFC_OFFSET); + regval &= ~FDCAN_RXGFC_ANFE_MASK; + regval |= FDCAN_RXGFC_ANFE_REJECTED; + fdcan_putreg(priv, STM32_FDCAN_RXGFC_OFFSET, regval); + + /* Disable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~(FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + } + + return ndx; + } + } + + DEBUGASSERT(priv->nextalloc == priv->config->nextfilters); + + return -EAGAIN; +} +#endif + +/**************************************************************************** + * Name: fdcan_del_extfilter + * + * Description: + * Remove an address filter for a standard 29 bit address. + * + * Input Parameters: + * priv - An instance of the FDCAN driver state structure. + * ndx - The filter index previously returned by the + * fdcan_add_extfilter(). + * + * Returned Value: + * Zero (OK) is returned on success. Otherwise a negated errno value is + * returned to indicate the nature of the error. + * + ****************************************************************************/ + +#ifdef CONFIG_CAN_EXTID +static int fdcan_del_extfilter(struct stm32_fdcan_s *priv, int ndx) +{ + const struct stm32_config_s *config = NULL; + volatile uint32_t *extfilter = NULL; + uint32_t regval = 0; + int word = 0; + int bit = 0; + + DEBUGASSERT(priv != NULL && priv->config != NULL); + config = priv->config; + + /* Check user Parameters */ + + DEBUGASSERT(ndx >= 0 || ndx < config->nextfilters); + + if (ndx < 0 || ndx >= config->nextfilters) + { + return -EINVAL; + } + + word = ndx >> 5; + bit = ndx & 0x1f; + + /* Check if this filter is really assigned */ + + if ((priv->extfilters[word] & (1 << bit)) == 0) + { + /* No, error out */ + + return -ENOENT; + } + + /* Release the filter */ + + priv->extfilters[word] &= ~(1 << bit); + + DEBUGASSERT(priv->nextalloc > 0); + priv->nextalloc--; + + /* Was that the last extended filter? */ + + if (priv->nextalloc == 0) + { + /* Enable the Initialization state */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= FDCAN_CCCR_INIT; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Wait for initialization mode to take effect */ + + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & + FDCAN_CCCR_INIT) == 0); + + /* Enable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= (FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* If there are no extended filters, then modify Global Filter + * Configuration so that all rejected messages are places in RX + * FIFO0. + * + * ANFE=0: Store all rejected extended frame in RX FIFO0 + */ + + regval = fdcan_getreg(priv, STM32_FDCAN_RXGFC_OFFSET); + regval &= ~FDCAN_RXGFC_ANFE_MASK; + regval |= FDCAN_RXGFC_ANFE_RX_FIFO0; + fdcan_putreg(priv, STM32_FDCAN_RXGFC_OFFSET, regval); + + /* Disable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~(FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + } + + /* Deactivate the filter last so that no messages are lost. */ + + extfilter = config->msgram.extfilters + (ndx << 1); + *extfilter++ = 0; + *extfilter = 0; + + return OK; +} +#endif + +/**************************************************************************** + * Name: fdcan_add_stdfilter + * + * Description: + * Add an address filter for a standard 11 bit address. + * + * Input Parameters: + * priv - An instance of the FDCAN driver state structure. + * stdconfig - The configuration of the standard filter + * + * Returned Value: + * A non-negative filter ID is returned on success. Otherwise a negated + * errno value is returned to indicate the nature of the error. + * + ****************************************************************************/ + +static int fdcan_add_stdfilter(struct stm32_fdcan_s *priv, + struct canioc_stdfilter_s *stdconfig) +{ + const struct stm32_config_s *config = NULL; + volatile uint32_t *stdfilter = NULL; + uint32_t regval = 0; + int word = 0; + int bit = 0; + int ndx = 0; + + DEBUGASSERT(priv != NULL && priv->config != NULL); + config = priv->config; + + /* Find an unused standard filter */ + + for (ndx = 0; ndx < config->nstdfilters; ndx++) + { + /* Is this filter assigned? */ + + word = ndx >> 5; + bit = ndx & 0x1f; + + if ((priv->stdfilters[word] & (1 << bit)) == 0) + { + /* No, assign the filter */ + + DEBUGASSERT(priv->nstdalloc < priv->config->nstdfilters); + priv->stdfilters[word] |= (1 << bit); + priv->nstdalloc++; + + /* Format and write filter word S0 */ + + stdfilter = config->msgram.stdfilters + ndx; + + DEBUGASSERT(stdconfig->sf_id1 <= CAN_MAX_STDMSGID); + regval = STDFILTER_S0_SFID1(stdconfig->sf_id1); + + DEBUGASSERT(stdconfig->sf_id2 <= CAN_MAX_STDMSGID); + regval |= STDFILTER_S0_SFID2(stdconfig->sf_id2); + + if (stdconfig->sf_prio == 0) + { + regval |= STDFILTER_S0_SFEC_FIFO0; + } + else + { + regval |= STDFILTER_S0_SFEC_FIFO1; + } + + switch (stdconfig->sf_type) + { + case CAN_FILTER_DUAL: + { + regval |= STDFILTER_S0_SFT_DUAL; + break; + } + + case CAN_FILTER_MASK: + { + regval |= STDFILTER_S0_SFT_CLASSIC; + break; + } + + case CAN_FILTER_RANGE: + { + regval |= STDFILTER_S0_SFT_RANGE; + break; + } + + default: + { + return -EINVAL; + } + } + + *stdfilter = regval; + + /* Is this the first standard filter? */ + + if (priv->nstdalloc == 1) + { + /* Enable the Initialization state */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= FDCAN_CCCR_INIT; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Wait for initialization mode to take effect */ + + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & + FDCAN_CCCR_INIT) == 0); + + /* Enable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= (FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Update the Global Filter Configuration so that received + * messages are rejected if they do not match the acceptance + * filter. + * + * ANFS=2: Discard all rejected frames + */ + + regval = fdcan_getreg(priv, STM32_FDCAN_RXGFC_OFFSET); + regval &= ~FDCAN_RXGFC_ANFS_MASK; + regval |= FDCAN_RXGFC_ANFS_REJECTED; + fdcan_putreg(priv, STM32_FDCAN_RXGFC_OFFSET, regval); + + /* Disable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~(FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + } + + return ndx; + } + } + + DEBUGASSERT(priv->nstdalloc == priv->config->nstdfilters); + return -EAGAIN; +} + +/**************************************************************************** + * Name: fdcan_del_stdfilter + * + * Description: + * Remove an address filter for a standard 29 bit address. + * + * Input Parameters: + * priv - An instance of the FDCAN driver state structure. + * ndx - The filter index previously returned by the + * fdcan_add_stdfilter(). + * + * Returned Value: + * Zero (OK) is returned on success. Otherwise a negated errno value is + * returned to indicate the nature of the error. + * + ****************************************************************************/ + +static int fdcan_del_stdfilter(struct stm32_fdcan_s *priv, int ndx) +{ + const struct stm32_config_s *config = NULL; + volatile uint32_t *stdfilter = NULL; + uint32_t regval = 0; + int word = 0; + int bit = 0; + + DEBUGASSERT(priv != NULL && priv->config != NULL); + config = priv->config; + + /* Check Userspace Parameters */ + + DEBUGASSERT(ndx >= 0 || ndx < config->nstdfilters); + + if (ndx < 0 || ndx >= config->nstdfilters) + { + return -EINVAL; + } + + word = ndx >> 5; + bit = ndx & 0x1f; + + /* Check if this filter is really assigned */ + + if ((priv->stdfilters[word] & (1 << bit)) == 0) + { + /* No, error out */ + + return -ENOENT; + } + + /* Release the filter */ + + priv->stdfilters[word] &= ~(1 << bit); + + DEBUGASSERT(priv->nstdalloc > 0); + priv->nstdalloc--; + + /* Was that the last standard filter? */ + + if (priv->nstdalloc == 0) + { + /* Enable the Initialization state */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= FDCAN_CCCR_INIT; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Wait for initialization mode to take effect */ + + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & + FDCAN_CCCR_INIT) == 0); + + /* Enable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= (FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* If there are no standard filters, then modify Global Filter + * Configuration so that all rejected messages are places in RX + * FIFO0. + * + * ANFS=0: Store all rejected extended frame in RX FIFO0 + */ + + regval = fdcan_getreg(priv, STM32_FDCAN_RXGFC_OFFSET); + regval &= ~FDCAN_RXGFC_ANFS_MASK; + regval |= FDCAN_RXGFC_ANFS_RX_FIFO0; + fdcan_putreg(priv, STM32_FDCAN_RXGFC_OFFSET, regval); + + /* Disable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~(FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + } + + /* Deactivate the filter last so that no messages are lost. */ + + stdfilter = config->msgram.stdfilters + ndx; + *stdfilter = 0; + + return OK; +} + +/**************************************************************************** + * Name: fdcan_start_busoff_recovery_sequence + * + * Description: + * This function initiates the BUS-OFF recovery sequence. + * CAN Specification Rev. 2.0 or ISO11898-1:2015. + * According the STM32G4 datasheet section 44.3.2 Software initialziation. + * + * Input Parameters: + * priv - An instance of the FDCAN driver state structure. + * + * Returned Value: + * Zero (OK) is returned on success. Otherwise a negated errno value is + * returned to indicate the nature of the error. + * + ****************************************************************************/ + +static int +fdcan_start_busoff_recovery_sequence(struct stm32_fdcan_s *priv) +{ + uint32_t regval = 0; + + DEBUGASSERT(priv); + + /* Only start BUS-OFF recovery if we are in BUS-OFF state */ + + regval = fdcan_getreg(priv, STM32_FDCAN_PSR_OFFSET); + if ((regval & FDCAN_PSR_BO) == 0) + { + return -EPERM; + } + + /* Disable initialization mode to issue the recovery sequence */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~FDCAN_CCCR_INIT; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + return OK; +} + +/**************************************************************************** + * Name: fdcan_reset + * + * Description: + * Reset the FDCAN device. Called early to initialize the hardware. This + * function is called, before fdcan_setup() and on error conditions. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void fdcan_reset(struct can_dev_s *dev) +{ + struct stm32_fdcan_s *priv = NULL; + const struct stm32_config_s *config = NULL; + uint32_t regval = 0; + irqstate_t flags; + + DEBUGASSERT(dev); + priv = dev->cd_priv; + DEBUGASSERT(priv); + config = priv->config; + DEBUGASSERT(config); + + caninfo("FDCAN%d\n", config->port); + UNUSED(config); + + /* Disable all interrupts */ + + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, 0); + fdcan_putreg(priv, STM32_FDCAN_TXBTIE_OFFSET, 0); + + /* Make sure that all buffers are released. + * + * REVISIT: What if a thread is waiting for a buffer? The following + * will not wake up any waiting threads. + */ + + /* Disable the FDCAN controller. + * REVISIT: Should fdcan_shutdown() be called here? + */ + + /* Reset the FD CAN. + * REVISIT: Since there is only a single reset for both FDCAN + * controllers, do we really want to use the RCC reset here? + * This will nuke operation of the second controller if another + * device is registered. + */ + + flags = enter_critical_section(); + regval = getreg32(STM32_RCC_APB1HRSTR); + regval |= RCC_APB1HRSTR_FDCANRST; + putreg32(regval, STM32_RCC_APB1HRSTR); + + regval &= ~RCC_APB1HRSTR_FDCANRST; + putreg32(regval, STM32_RCC_APB1HRSTR); + leave_critical_section(flags); + + priv->state = FDCAN_STATE_RESET; +} + +/**************************************************************************** + * Name: fdcan_setup + * + * Description: + * Configure the FDCAN. This method is called the first time that the FDCAN + * device is opened. This will occur when the port is first opened. + * This setup includes configuring and attaching FDCAN interrupts. + * All FDCAN interrupts are disabled upon return. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int fdcan_setup(struct can_dev_s *dev) +{ + struct stm32_fdcan_s *priv = NULL; + const struct stm32_config_s *config = NULL; + int ret = 0; + + DEBUGASSERT(dev); + priv = dev->cd_priv; + DEBUGASSERT(priv); + config = priv->config; + DEBUGASSERT(config); + + caninfo("FDCAN%d\n", config->port); + + /* FDCAN hardware initialization */ + + ret = fdcan_hw_initialize(priv); + if (ret < 0) + { + canerr("ERROR: FDCAN%d H/W initialization failed: %d\n", + config->port, ret); + return ret; + } + + fdcan_dumpregs(priv, "After hardware initialization"); + + /* Attach the FDCAN interrupt handlers */ + + ret = irq_attach(config->irq0, fdcan_interrupt, dev); + if (ret < 0) + { + canerr("ERROR: Failed to attach FDCAN%d line 0 IRQ (%d)", + config->port, config->irq0); + return ret; + } + + ret = irq_attach(config->irq1, fdcan_interrupt, dev); + if (ret < 0) + { + canerr("ERROR: Failed to attach FDCAN%d line 1 IRQ (%d)", + config->port, config->irq1); + return ret; + } + + priv->state = FDCAN_STATE_SETUP; + + /* Enable the interrupts at the NVIC (they are still disabled at the FDCAN + * peripheral). + */ + + up_enable_irq(config->irq0); + up_enable_irq(config->irq1); + + return OK; +} + +/**************************************************************************** + * Name: fdcan_shutdown + * + * Description: + * Disable the FDCAN. This method is called when the FDCAN device + * is closed. This method reverses the operation the setup method. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void fdcan_shutdown(struct can_dev_s *dev) +{ + struct stm32_fdcan_s *priv = NULL; + const struct stm32_config_s *config = NULL; + uint32_t regval = 0; + + DEBUGASSERT(dev); + priv = dev->cd_priv; + DEBUGASSERT(priv); + config = priv->config; + DEBUGASSERT(config); + + caninfo("FDCAN%d\n", config->port); + + /* Disable FDCAN interrupts at the NVIC */ + + up_disable_irq(config->irq0); + up_disable_irq(config->irq1); + + /* Disable all interrupts from the FDCAN peripheral */ + + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, 0); + fdcan_putreg(priv, STM32_FDCAN_TXBTIE_OFFSET, 0); + + /* Detach the FDCAN interrupt handler */ + + irq_detach(config->irq0); + irq_detach(config->irq1); + + /* Disable device by setting the Clock Stop Request bit */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= FDCAN_CCCR_CSR; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Wait for Init and Clock Stop Acknowledge bits to verify + * device is in the powered down state + */ + + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & FDCAN_CCCR_INIT) + == 0); + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & FDCAN_CCCR_CSA) + == 0); + priv->state = FDCAN_STATE_DISABLED; +} + +/**************************************************************************** + * Name: fdcan_rxint + * + * Description: + * Call to enable or disable RX interrupts. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void fdcan_rxint(struct can_dev_s *dev, bool enable) +{ + struct stm32_fdcan_s *priv = dev->cd_priv; + uint32_t regval = 0; + + DEBUGASSERT(priv && priv->config); + + caninfo("FDCAN%d enable: %d\n", priv->config->port, enable); + + /* Enable/disable the receive interrupts */ + + regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET); + + if (enable) + { + regval |= priv->rxints | FDCAN_COMMON_INTS; + } + else + { + regval &= ~priv->rxints; + } + + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval); +} + +/**************************************************************************** + * Name: fdcan_txint + * + * Description: + * Call to enable or disable TX interrupts. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void fdcan_txint(struct can_dev_s *dev, bool enable) +{ + struct stm32_fdcan_s *priv = dev->cd_priv; + uint32_t regval = 0; + + DEBUGASSERT(priv && priv->config); + + caninfo("FDCAN%d enable: %d\n", priv->config->port, enable); + + /* Enable/disable the receive interrupts */ + + regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET); + + if (enable) + { + regval |= priv->txints | FDCAN_COMMON_INTS; + } + else + { + regval &= ~priv->txints; + } + + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval); +} + +/**************************************************************************** + * Name: fdcan_ioctl + * + * Description: + * All ioctl calls will be routed through this method + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int fdcan_ioctl(struct can_dev_s *dev, int cmd, unsigned long arg) +{ + struct stm32_fdcan_s *priv = NULL; + int ret = -ENOTTY; + + caninfo("cmd=%04x arg=%lu\n", cmd, arg); + + DEBUGASSERT(dev && dev->cd_priv); + priv = dev->cd_priv; + + /* Handle the command */ + + switch (cmd) + { + /* CANIOC_GET_BITTIMING: + * Description: Return the current bit timing settings + * Argument: A pointer to a write-able instance of struct + * canioc_bittiming_s in which current bit timing + * values will be returned. + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + * Dependencies: None + */ + + case CANIOC_GET_BITTIMING: + { + struct canioc_bittiming_s *bt = + (struct canioc_bittiming_s *)arg; + uint32_t regval; + uint32_t nbrp; + + DEBUGASSERT(bt != NULL); + +#ifdef CONFIG_CAN_FD + if (bt->type == CAN_BITTIMING_DATA) + { + regval = fdcan_getreg(priv, STM32_FDCAN_DBTP_OFFSET); + bt->bt_sjw = ((regval & FDCAN_DBTP_DSJW_MASK) >> + FDCAN_DBTP_DSJW_SHIFT) + 1; + bt->bt_tseg1 = ((regval & FDCAN_DBTP_DTSEG1_MASK) >> + FDCAN_DBTP_DTSEG1_SHIFT) + 1; + bt->bt_tseg2 = ((regval & FDCAN_DBTP_DTSEG2_MASK) >> + FDCAN_DBTP_DTSEG2_SHIFT) + 1; + + nbrp = ((regval & FDCAN_DBTP_DBRP_MASK) >> + FDCAN_DBTP_DBRP_SHIFT) + 1; + bt->bt_baud = STM32_FDCANCLK_FREQUENCY / nbrp / + (bt->bt_tseg1 + bt->bt_tseg2 + 1); + } + else +#endif + { + regval = fdcan_getreg(priv, STM32_FDCAN_NBTP_OFFSET); + bt->bt_sjw = ((regval & FDCAN_NBTP_NSJW_MASK) >> + FDCAN_NBTP_NSJW_SHIFT) + 1; + bt->bt_tseg1 = ((regval & FDCAN_NBTP_NTSEG1_MASK) >> + FDCAN_NBTP_NTSEG1_SHIFT) + 1; + bt->bt_tseg2 = ((regval & FDCAN_NBTP_NTSEG2_MASK) >> + FDCAN_NBTP_NTSEG2_SHIFT) + 1; + + nbrp = ((regval & FDCAN_NBTP_NBRP_MASK) >> + FDCAN_NBTP_NBRP_SHIFT) + 1; + bt->bt_baud = STM32_FDCANCLK_FREQUENCY / nbrp / + (bt->bt_tseg1 + bt->bt_tseg2 + 1); + } + + ret = OK; + } + break; + + /* CANIOC_SET_BITTIMING: + * Description: Set new current bit timing values + * Argument: A pointer to a read-able instance of struct + * canioc_bittiming_s in which the new bit timing + * values are provided. + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + * Dependencies: None + * + * REVISIT: There is probably a limitation here: If there are + * multiple threads trying to send CAN packets, when one of these + * threads reconfigures the bitrate, the FDCAN hardware will be reset + * and the context of operation will be lost. Hence, this IOCTL can + * only safely be executed in quiescent time periods. + */ + + case CANIOC_SET_BITTIMING: + { + const struct canioc_bittiming_s *bt = + (const struct canioc_bittiming_s *)arg; + uint32_t nbrp; + uint32_t ntseg1; + uint32_t ntseg2; + uint32_t nsjw; + uint32_t ie; + uint8_t state; + + DEBUGASSERT(bt != NULL); + DEBUGASSERT(bt->bt_baud < STM32_FDCANCLK_FREQUENCY); + DEBUGASSERT(bt->bt_sjw > 0 && bt->bt_sjw <= 16); + DEBUGASSERT(bt->bt_tseg1 > 1 && bt->bt_tseg1 <= 64); + DEBUGASSERT(bt->bt_tseg2 > 0 && bt->bt_tseg2 <= 16); + + /* Extract bit timing data */ + + ntseg1 = bt->bt_tseg1 - 1; + ntseg2 = bt->bt_tseg2 - 1; + nsjw = bt->bt_sjw - 1; + + nbrp = (uint32_t) + (((float) STM32_FDCANCLK_FREQUENCY / + ((float)(ntseg1 + ntseg2 + 3) * (float)bt->bt_baud)) - 1); + + /* Save the value of the new bit timing register */ + +#ifdef CONFIG_CAN_FD + if (bt->type == CAN_BITTIMING_DATA) + { + priv->dbtp = FDCAN_DBTP_DBRP(nbrp) | + FDCAN_DBTP_DTSEG1(ntseg1) | + FDCAN_DBTP_DTSEG2(ntseg2) | + FDCAN_DBTP_DSJW(nsjw); + } + else +#endif + { + priv->nbtp = FDCAN_NBTP_NBRP(nbrp) | + FDCAN_NBTP_NTSEG1(ntseg1) | + FDCAN_NBTP_NTSEG2(ntseg2) | + FDCAN_NBTP_NSJW(nsjw); + } + + /* We need to reset to instantiate the new timing. Save + * current state information so that recover to this + * state. + */ + + ie = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET); + state = priv->state; + + /* Reset the FDCAN */ + + fdcan_reset(dev); + ret = OK; + + /* If we have previously been setup, then setup again */ + + if (state == FDCAN_STATE_SETUP) + { + ret = fdcan_setup(dev); + } + + /* We we have successfully re-initialized, then restore the + * interrupt state. + * + * REVISIT: Since the hardware was reset, any pending TX + * activity was lost. Should we disable TX interrupts? + */ + + if (ret == OK) + { + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie & ~priv->txints); + } + } + break; + +#ifdef CONFIG_CAN_EXTID + /* CANIOC_ADD_EXTFILTER: + * Description: Add an address filter for a extended 29 bit + * address. + * Argument: A reference to struct canioc_extfilter_s + * Returned Value: A non-negative filter ID is returned on success. + * Otherwise -1 (ERROR) is returned with the errno + * variable set to indicate the nature of the error. + */ + + case CANIOC_ADD_EXTFILTER: + { + DEBUGASSERT(arg != 0); + + ret = fdcan_add_extfilter(priv, + (struct canioc_extfilter_s *)arg); + } + break; + + /* CANIOC_DEL_EXTFILTER: + * Description: Remove an address filter for a standard 29 bit + * address. + * Argument: The filter index previously returned by the + * CANIOC_ADD_EXTFILTER command + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + */ + + case CANIOC_DEL_EXTFILTER: + { + DEBUGASSERT(arg <= priv->config->nextfilters); + ret = fdcan_del_extfilter(priv, (int)arg); + } + break; +#endif + + /* CANIOC_ADD_STDFILTER: + * Description: Add an address filter for a standard 11 bit + * address. + * Argument: A reference to struct canioc_stdfilter_s + * Returned Value: A non-negative filter ID is returned on success. + * Otherwise -1 (ERROR) is returned with the errno + * variable set to indicate the nature of the error. + */ + + case CANIOC_ADD_STDFILTER: + { + DEBUGASSERT(arg != 0); + + ret = fdcan_add_stdfilter(priv, + (struct canioc_stdfilter_s *)arg); + } + break; + + /* CANIOC_DEL_STDFILTER: + * Description: Remove an address filter for a standard 11 bit + * address. + * Argument: The filter index previously returned by the + * CANIOC_ADD_STDFILTER command + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + */ + + case CANIOC_DEL_STDFILTER: + { + DEBUGASSERT(arg <= priv->config->nstdfilters); + ret = fdcan_del_stdfilter(priv, (int)arg); + } + break; + + /* CANIOC_BUSOFF_RECOVERY: + * Description : Initiates the BUS - OFF recovery sequence + * Argument : None + * Returned Value : Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + * Dependencies : None + */ + + case CANIOC_BUSOFF_RECOVERY: + { + ret = fdcan_start_busoff_recovery_sequence(priv); + } + break; + + /* Unsupported/unrecognized command */ + + default: + canerr("ERROR: Unrecognized command: %04x\n", cmd); + break; + } + + return ret; +} + +/**************************************************************************** + * Name: fdcan_remoterequest + * + * Description: + * Send a remote request + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int fdcan_remoterequest(struct can_dev_s *dev, uint16_t id) +{ + /* REVISIT: Remote request not implemented */ + + return -ENOSYS; +} + +/**************************************************************************** + * Name: fdcan_send + * + * Description: + * Send one can message. + * + * One CAN-message consists of a maximum of 10 bytes. A message is + * composed of at least the first 2 bytes (when there are no data bytes). + * + * Byte 0: Bits 0-7: Bits 3-10 of the 11-bit CAN identifier + * Byte 1: Bits 5-7: Bits 0-2 of the 11-bit CAN identifier + * Bit 4: Remote Transmission Request (RTR) + * Bits 0-3: Data Length Code (DLC) + * Bytes 2-10: CAN data + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int fdcan_send(struct can_dev_s *dev, struct can_msg_s *msg) +{ + struct stm32_fdcan_s *priv = NULL; + const struct stm32_config_s *config = NULL; + volatile uint32_t *txbuffer = NULL; + const uint8_t *src = NULL; + uint32_t *dest = NULL; + uint32_t regval = 0; + unsigned int ndx = 0; + unsigned int nbytes = 0; + uint32_t wordbuffer = 0; + unsigned int i = 0; + + DEBUGASSERT(dev); + priv = dev->cd_priv; + DEBUGASSERT(priv && priv->config); + config = priv->config; + + caninfo("CAN%" PRIu8 " ID: %" PRIu32 " DLC: %" PRIu8 "\n", + config->port, (uint32_t)msg->cm_hdr.ch_id, msg->cm_hdr.ch_dlc); + + fdcan_dumptxregs(priv, "Before send"); + + /* That that FIFO elements were configured */ + + DEBUGASSERT(config->ntxfifoq > 0); + + /* Get our reserved Tx FIFO/queue put index */ + + regval = fdcan_getreg(priv, STM32_FDCAN_TXFQS_OFFSET); + DEBUGASSERT((regval & FDCAN_TXFQS_TFQF) == 0); + + ndx = (regval & FDCAN_TXFQS_TFQPI_MASK) >> FDCAN_TXFQS_TFQPI_SHIFT; + + /* And the TX buffer corresponding to this index */ + + txbuffer = (config->msgram.txfifoq + ndx * config->txbufferesize); + + /* Format the TX FIFOQ entry + * + * Format word T0: + * Transfer message ID (ID) - Value from message structure + * Remote Transmission Request (RTR) - Value from message structure + * Extended Identifier (XTD) - Depends on configuration. + * Error state indicator (ESI) - ESI bit in CAN FD + * + * Format word T1: + * Data Length Code (DLC) - Value from message structure + * Bit Rate Switch (BRS) - Bit rate switching for CAN FD + * FD format (FDF) - Frame transmited in CAN FD format + * Event FIFO Control (EFC) - Do not store events. + * Message Marker (MM) - Always zero + */ + + txbuffer[0] = 0; + txbuffer[1] = 0; + +#ifdef CONFIG_CAN_EXTID + if (msg->cm_hdr.ch_extid == 1) + { + DEBUGASSERT(msg->cm_hdr.ch_id <= CAN_MAX_EXTMSGID); + + txbuffer[0] |= BUFFER_R0_EXTID(msg->cm_hdr.ch_id) | BUFFER_R0_XTD; + } + else +#endif + { + DEBUGASSERT(msg->cm_hdr.ch_id <= CAN_MAX_STDMSGID); + + txbuffer[0] |= BUFFER_R0_STDID(msg->cm_hdr.ch_id); + } + + if (msg->cm_hdr.ch_rtr == 1) + { + txbuffer[0] |= BUFFER_R0_RTR; + } + + txbuffer[1] |= BUFFER_R1_DLC(msg->cm_hdr.ch_dlc); + +#ifdef CONFIG_CAN_FD + /* CAN FD Format */ + + if (msg->cm_hdr.ch_edl == 1) + { + txbuffer[1] |= BUFFER_R1_FDF; + + if (msg->cm_hdr.ch_brs == 1) + { + txbuffer[1] |= BUFFER_R1_BRS; + } + + if (msg->cm_hdr.ch_esi == 1) + { + txbuffer[0] |= BUFFER_R0_ESI; + } + } + else +#endif + { + txbuffer[0] &= ~BUFFER_R0_ESI; + txbuffer[1] &= ~BUFFER_R1_FDF; + txbuffer[1] &= ~BUFFER_R1_BRS; + } + + /* Followed by the amount of data corresponding to the DLC (T2..) */ + + dest = (uint32_t *)&txbuffer[2]; + src = msg->cm_data; + nbytes = fdcan_dlc2bytes(priv, msg->cm_hdr.ch_dlc); + + /* Writes must be word length */ + + for (i = 0; i < nbytes; i += 4) + { + /* Little endian is assumed */ + + wordbuffer = src[0] | + (src[1] << 8) | + (src[2] << 16) | + (src[3] << 24); + src += 4; + + *dest++ = wordbuffer; + } + + /* Enable transmit interrupts from the TX FIFOQ buffer by setting TC + * interrupt bit in IR (also requires that the TC interrupt is enabled) + */ + + fdcan_putreg(priv, STM32_FDCAN_TXBTIE_OFFSET, (1 << ndx)); + + /* And request to send the packet */ + + fdcan_putreg(priv, STM32_FDCAN_TXBAR_OFFSET, (1 << ndx)); + + return OK; +} + +/**************************************************************************** + * Name: fdcan_txready + * + * Description: + * Return true if the FDCAN hardware can accept another TX message. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * True if the FDCAN hardware is ready to accept another TX message. + * + ****************************************************************************/ + +static bool fdcan_txready(struct can_dev_s *dev) +{ + struct stm32_fdcan_s *priv = dev->cd_priv; + uint32_t regval = 0; + bool notfull = false; + + /* Return the state of the TX FIFOQ. Return TRUE if the TX FIFO/Queue is + * not full. + */ + + regval = fdcan_getreg(priv, STM32_FDCAN_TXFQS_OFFSET); + notfull = ((regval & FDCAN_TXFQS_TFQF) == 0); + + return notfull; +} + +/**************************************************************************** + * Name: fdcan_txempty + * + * Description: + * Return true if all message have been sent. If for example, the FDCAN + * hardware implements FIFOs, then this would mean the transmit FIFO is + * empty. This method is called when the driver needs to make sure that + * all characters are "drained" from the TX hardware before calling + * co_shutdown(). + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * True if there are no pending TX transfers in the FDCAN hardware. + * + ****************************************************************************/ + +static bool fdcan_txempty(struct can_dev_s *dev) +{ + struct stm32_fdcan_s *priv = dev->cd_priv; + uint32_t regval = 0; +#ifndef CONFIG_STM32H5_FDCAN_QUEUE_MODE + int tffl = 0; + bool empty = false; +#endif + + DEBUGASSERT(priv != NULL && priv->config != NULL); + + /* Return the state of the TX FIFOQ. Return TRUE if the TX FIFO/Queue is + * empty. We don't have a reliable indication that the FIFO is empty, so + * we have to use some heuristics. + */ + + regval = fdcan_getreg(priv, STM32_FDCAN_TXFQS_OFFSET); + if ((regval & FDCAN_TXFQS_TFQF) != 0) + { + return false; + } + + /* Tx FIFO Free Level */ + +#ifndef CONFIG_STM32H5_FDCAN_QUEUE_MODE + tffl = (regval & FDCAN_TXFQS_TFFL_MASK) >> FDCAN_TXFQS_TFFL_SHIFT; + empty = (tffl >= priv->config->ntxfifoq); + return empty; +#else + return true; +#endif +} + +/**************************************************************************** + * Name: fdcan_error + * + * Description: + * Report a CAN error + * + * Input Parameters: + * dev - CAN-common state data + * status - Interrupt status with error bits set + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_CAN_ERRORS +static void fdcan_error(struct can_dev_s *dev, uint32_t status) +{ + struct stm32_fdcan_s *priv = dev->cd_priv; + struct can_hdr_s hdr; + uint32_t psr = 0; + uint16_t errbits = 0; + uint8_t data[CAN_ERROR_DLC]; + int ret = 0; + + /* Encode error bits */ + + errbits = 0; + memset(data, 0, sizeof(data)); + + /* Always fill in "static" error conditions, but set the signaling bit + * only if the condition has changed (see IRQ-Flags below) + * They have to be filled in every time CAN_ERROR_CONTROLLER is set. + */ + + psr = fdcan_getreg(priv, STM32_FDCAN_PSR_OFFSET); + if ((psr & FDCAN_PSR_EP) != 0) + { + data[1] |= (CAN_ERROR1_RXPASSIVE | CAN_ERROR1_TXPASSIVE); + } + + if ((psr & FDCAN_PSR_EW) != 0) + { + data[1] |= (CAN_ERROR1_RXWARNING | CAN_ERROR1_TXWARNING); + } + + if ((status & (FDCAN_INT_EP | FDCAN_INT_EW)) != 0) + { + /* "Error Passive" or "Error Warning" status changed */ + + errbits |= CAN_ERROR_CONTROLLER; + } + + if ((status & FDCAN_INT_PEA) != 0) + { + /* Protocol Error in Arbitration Phase */ + + if ((psr & FDCAN_PSR_LEC_MASK) != 0) + { + /* Error code present */ + + if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_STUFF_ERROR)) != 0) + { + /* Stuff Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_STUFF; + } + + if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_FORM_ERROR)) != 0) + { + /* Format Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_FORM; + } + + if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_ACK_ERROR)) != 0) + { + /* Acknowledge Error */ + + errbits |= CAN_ERROR_NOACK; + } + + if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_BIT0_ERROR)) != 0) + { + /* Bit0 Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_BIT0; + } + + if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_BIT1_ERROR)) != 0) + { + /* Bit1 Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_BIT1; + } + + if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_CRC_ERROR)) != 0) + { + /* Receive CRC Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[3] |= (CAN_ERROR3_CRCSEQ | CAN_ERROR3_CRCDEL); + } + + if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_NO_CHANGE)) != 0) + { + /* No Change in Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_UNSPEC; + } + } + } + + if ((status & FDCAN_INT_PED) != 0) + { + /* Protocol Error in Data Phase */ + + if ((psr & FDCAN_PSR_DLEC_MASK) != 0) + { + /* Error code present */ + + if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_STUFF_ERROR)) != 0) + { + /* Stuff Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_STUFF; + } + + if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_FORM_ERROR)) != 0) + { + /* Format Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_FORM; + } + + if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_ACK_ERROR)) != 0) + { + /* Acknowledge Error */ + + errbits |= CAN_ERROR_NOACK; + } + + if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_BIT0_ERROR)) != 0) + { + /* Bit0 Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_BIT0; + } + + if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_BIT1_ERROR)) != 0) + { + /* Bit1 Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_BIT1; + } + + if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_CRC_ERROR)) != 0) + { + /* Receive CRC Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[3] |= (CAN_ERROR3_CRCSEQ | CAN_ERROR3_CRCDEL); + } + + if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_NO_CHANGE)) != 0) + { + /* No Change in Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_UNSPEC; + } + } + } + + if ((status & FDCAN_INT_BO) != 0) + { + /* Bus_Off Status changed */ + + if ((psr & FDCAN_PSR_BO) != 0) + { + errbits |= CAN_ERROR_BUSOFF; + } + else + { + errbits |= CAN_ERROR_RESTARTED; + } + } + + if ((status & (FDCAN_INT_RF0L | FDCAN_INT_RF1L)) != 0) + { + /* Receive FIFO 0/1 Message Lost + * Receive FIFO 1 Message Lost + */ + + errbits |= CAN_ERROR_CONTROLLER; + data[1] |= CAN_ERROR1_RXOVERFLOW; + } + + if ((status & FDCAN_INT_TEFL) != 0) + { + /* Tx Event FIFO Element Lost */ + + errbits |= CAN_ERROR_CONTROLLER; + data[1] |= CAN_ERROR1_TXOVERFLOW; + } + + if ((status & FDCAN_INT_TOO) != 0) + { + /* Timeout Occurred */ + + errbits |= CAN_ERROR_TXTIMEOUT; + } + + if ((status & (FDCAN_INT_MRAF | FDCAN_INT_ELO)) != 0) + { + /* Message RAM Access Failure + * Error Logging Overflow + */ + + errbits |= CAN_ERROR_CONTROLLER; + data[1] |= CAN_ERROR1_UNSPEC; + } + + if (errbits != 0) + { + /* Format the CAN header for the error report. */ + + hdr.ch_id = errbits; + hdr.ch_dlc = CAN_ERROR_DLC; + hdr.ch_rtr = 0; + hdr.ch_error = 1; +#ifdef CONFIG_CAN_EXTID + hdr.ch_extid = 0; +#endif + hdr.ch_tcf = 0; + + /* And provide the error report to the upper half logic */ + + ret = can_receive(dev, &hdr, data); + if (ret < 0) + { + canerr("ERROR: can_receive failed: %d\n", ret); + } + } +} +#endif /* CONFIG_CAN_ERRORS */ + +/**************************************************************************** + * Name: fdcan_receive + * + * Description: + * Receive an FDCAN messages + * + * Input Parameters: + * dev - CAN-common state data + * rxbuffer - The RX buffer containing the received messages + * nwords - The length of the RX buffer (element size in words). + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void fdcan_receive(struct can_dev_s *dev, + volatile uint32_t *rxbuffer, + unsigned long nwords) +{ + struct can_hdr_s hdr; + int ret = 0; + + fdcan_dumprxregs(dev->cd_priv, "Before receive"); + + /* Format the CAN header */ + + /* Word R0 contains the CAN ID */ + +#ifdef CONFIG_CAN_ERRORS + hdr.ch_error = 0; +#endif + hdr.ch_tcf = 0; + + /* Extract the RTR bit */ + + hdr.ch_rtr = ((rxbuffer[0] & BUFFER_R0_RTR) != 0); + +#ifdef CONFIG_CAN_EXTID + if ((rxbuffer[0] & BUFFER_R0_XTD) != 0) + { + /* Save the extended ID of the newly received message */ + + hdr.ch_id = (rxbuffer[0] & BUFFER_R0_EXTID_MASK) >> + BUFFER_R0_EXTID_SHIFT; + hdr.ch_extid = 1; + } + else + { + hdr.ch_id = (rxbuffer[0] & BUFFER_R0_STDID_MASK) >> + BUFFER_R0_STDID_SHIFT; + hdr.ch_extid = 0; + } + +#else + if ((rxbuffer[0] & BUFFER_R0_XTD) != 0) + { + /* Drop any messages with extended IDs */ + + canerr("ERROR: Received message with extended identifier. Dropped\n"); + + return; + } + + /* Save the standard ID of the newly received message */ + + hdr.ch_id = (rxbuffer[0] & BUFFER_R0_STDID_MASK) >> BUFFER_R0_STDID_SHIFT; +#endif + + /* Word R1 contains the DLC and timestamp */ + + hdr.ch_dlc = (rxbuffer[1] & BUFFER_R1_DLC_MASK) >> BUFFER_R1_DLC_SHIFT; + +#ifdef CONFIG_CAN_FD + /* CAN FD format */ + + hdr.ch_esi = ((rxbuffer[0] & BUFFER_R0_ESI) != 0); + hdr.ch_edl = ((rxbuffer[1] & BUFFER_R1_FDF) != 0); + hdr.ch_brs = ((rxbuffer[1] & BUFFER_R1_BRS) != 0); +#else + if ((rxbuffer[1] & BUFFER_R1_FDF) != 0) + { + /* Drop any FD CAN messages if not supported */ + + canerr("ERROR: Received CAN FD message. Dropped\n"); + + return; + } +#endif + + /* And provide the CAN message to the upper half logic */ + + ret = can_receive(dev, &hdr, (uint8_t *)&rxbuffer[2]); + if (ret < 0) + { + canerr("ERROR: can_receive failed: %d\n", ret); + } +} + +/**************************************************************************** + * Name: fdcan_interrupt + * + * Description: + * Common FDCAN interrupt handler + * + * Input Parameters: + * dev - CAN-common state data + * + * Returned Value: + * None + * + ****************************************************************************/ + +static int fdcan_interrupt(int irq, void *context, void *arg) +{ + struct can_dev_s *dev = (struct can_dev_s *)arg; + struct stm32_fdcan_s *priv = NULL; + const struct stm32_config_s *config = NULL; + uint32_t ir = 0; + uint32_t ie = 0; + uint32_t pending = 0; + uint32_t regval = 0; + uint32_t psr = 0; + unsigned int nelem = 0; + unsigned int ndx = 0; + + DEBUGASSERT(dev != NULL); + priv = dev->cd_priv; + DEBUGASSERT(priv && priv->config); + config = priv->config; + + /* Get the set of pending interrupts. */ + + ir = fdcan_getreg(priv, STM32_FDCAN_IR_OFFSET); + ie = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET); + + pending = (ir & ie); + + /* Check for any errors */ + + if ((pending & FDCAN_ANYERR_INTS) != 0) + { + /* Check for common errors */ + + if ((pending & FDCAN_CMNERR_INTS) != 0) + { + canerr("ERROR: Common %08" PRIx32 "\n", + pending & FDCAN_CMNERR_INTS); + + /* When a protocol error ocurrs, the problem is recorded in + * the LEC/DLEC fields of the PSR register. In lieu of + * seprate interrupt flags for each error, the hardware + * groups procotol errors under a single interrupt each for + * arbitration and data phases. + * + * These errors have a tendency to flood the system with + * interrupts, so they are disabled here until we get a + * successful transfer/receive on the hardware + */ + + psr = fdcan_getreg(priv, STM32_FDCAN_PSR_OFFSET); + + if ((psr & FDCAN_PSR_LEC_MASK) != 0) + { + canerr("ERROR: PSR %08" PRIx32 "\n", psr); + ie &= ~(FDCAN_INT_PEA | FDCAN_INT_PED); + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie); + caninfo("disabled protocol error intterupts\n"); + } + + /* Clear the error indications */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_CMNERR_INTS); + } + + /* Check for transmission errors */ + + if ((pending & FDCAN_TXERR_INTS) != 0) + { + canerr("ERROR: TX %08" PRIx32 "\n", + pending & FDCAN_TXERR_INTS); + + /* An Acknowledge-Error will occur if for example the device + * is not connected to the bus. + * + * The CAN-Standard states that the Chip has to retry the + * message forever, which will produce an ACKE every time. + * To prevent this Interrupt-Flooding and the high CPU-Load + * we disable the ACKE here as long we didn't transfer at + * least one message successfully (see FDCAN_INT_TC below). + */ + + /* Clear the error indications */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_TXERR_INTS); + } + + /* Check for reception errors */ + + if ((pending & FDCAN_RXERR_INTS) != 0) + { + canerr("ERROR: RX %08" PRIx32 "\n", + pending & FDCAN_RXERR_INTS); + + /* To prevent Interrupt-Flooding the current active + * RX error interrupts are disabled. After successfully + * receiving at least one CAN packet all RX error interrupts + * are turned back on. + * + * The Interrupt-Flooding can for example occur if the + * configured CAN speed does not match the speed of the other + * CAN nodes in the network. + */ + + ie &= ~(pending & FDCAN_RXERR_INTS); + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie); + + /* Clear the error indications */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_RXERR_INTS); + } + +#ifdef CONFIG_CAN_ERRORS + /* Report errors */ + + fdcan_error(dev, pending & FDCAN_ANYERR_INTS); +#endif + } + + /* Check for successful completion of a transmission */ + + if ((pending & FDCAN_INT_TC) != 0) + { + /* Check if we have disabled the ACKE in the error-handling above + * (see FDCAN_TXERR_INTS) to prevent Interrupt-Flooding and + * re-enable the error interrupt here again. + */ + + if ((ie & (FDCAN_INT_PEA | FDCAN_INT_PED)) == 0) + { + ie |= (FDCAN_INT_PEA | FDCAN_INT_PED); + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie); + caninfo("Renabled protocol error intterupts\n"); + } + + /* Clear the pending TX completion interrupt (and all + * other TX-related interrupts) + */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, priv->txints); + + /* Check all TX buffers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_TXBTO_OFFSET); + for (ndx = 0; ndx < config->ntxfifoq; ndx++) + { + if ((regval & (1 << ndx)) != 0) + { + /* Tell the upper half that the transfer is finished. */ + + can_txdone(dev); + } + } + } + else if ((pending & priv->txints) != 0) + { + /* Clear unhandled TX events */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, priv->txints); + } + + /* Clear the RX FIFO1 new message interrupt */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_INT_RF1N); + pending &= ~FDCAN_INT_RF1N; + + /* We treat RX FIFO1 as the "high priority" queue: We will process + * all messages in RX FIFO1 before processing any message from RX + * FIFO0. + */ + + for (; ; ) + { + /* Check if there is anything in RX FIFO1 */ + + regval = fdcan_getreg(priv, STM32_FDCAN_RXF1S_OFFSET); + nelem = (regval & FDCAN_RXFS_FFL_MASK) >> FDCAN_RXFS_FFL_SHIFT; + if (nelem == 0) + { + /* Break out of the loop if RX FIFO1 is empty */ + + break; + } + + /* Clear the RX FIFO1 interrupt (and all other FIFO1-related + * interrupts) + */ + + /* Handle the newly received message in FIFO1 */ + + ndx = (regval & FDCAN_RXFS_FGI_MASK) >> FDCAN_RXFS_FGI_SHIFT; + + if ((regval & FDCAN_RXFS_RFL) != 0) + { + canerr("ERROR: Message lost: %08" PRIx32 "\n", regval); + } + else + { + fdcan_receive(dev, + config->msgram.rxfifo1 + + (ndx * priv->config->rxfifo1esize), + priv->config->rxfifo1esize); + + /* Turning back on all configured RX error interrupts */ + + ie |= (priv->rxints & FDCAN_RXERR_INTS); + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie); + } + + /* Acknowledge reading the FIFO entry */ + + fdcan_putreg(priv, STM32_FDCAN_RXF1A_OFFSET, ndx); + } + + /* Check for successful reception of a new message in RX FIFO0 */ + + /* Clear the RX FIFO0 new message interrupt */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_INT_RF0N); + pending &= ~FDCAN_INT_RF0N; + + /* Check if there is anything in RX FIFO0 */ + + regval = fdcan_getreg(priv, STM32_FDCAN_RXF0S_OFFSET); + nelem = (regval & FDCAN_RXFS_FFL_MASK) >> FDCAN_RXFS_FFL_SHIFT; + if (nelem > 0) + { + /* Handle the newly received message in FIFO0 */ + + ndx = (regval & FDCAN_RXFS_FGI_MASK) >> FDCAN_RXFS_FGI_SHIFT; + + if ((regval & FDCAN_RXFS_RFL) != 0) + { + canerr("ERROR: Message lost: %08" PRIx32 "\n", regval); + } + else + { + fdcan_receive(dev, + config->msgram.rxfifo0 + + (ndx * priv->config->rxfifo0esize), + priv->config->rxfifo0esize); + + /* Turning back on all configured RX error interrupts */ + + ie |= (priv->rxints & FDCAN_RXERR_INTS); + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie); + } + + /* Acknowledge reading the FIFO entry */ + + fdcan_putreg(priv, STM32_FDCAN_RXF0A_OFFSET, ndx); + } + + /* Clear unhandled RX interrupts */ + + if ((pending & priv->rxints) != 0) + { + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, priv->rxints); + } + + return OK; +} + +/**************************************************************************** + * Name: fdcan_hw_initialize + * + * Description: + * FDCAN hardware initialization + * + * Input Parameters: + * priv - A pointer to the private data structure for this FDCAN peripheral + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int fdcan_hw_initialize(struct stm32_fdcan_s *priv) +{ + const struct stm32_config_s *config = priv->config; + volatile uint32_t *msgram = NULL; + uint32_t regval = 0; + uint32_t cntr = 0; + + caninfo("FDCAN%d\n", config->port); + + /* Clean message RAM */ + + msgram = config->msgram.stdfilters; + cntr = (FDCAN_MSGRAM_WORDS + 1); + while (cntr > 0) + { + *msgram++ = 0; + cntr--; + } + + /* Configure FDCAN pins */ + + stm32_configgpio(config->rxpinset); + stm32_configgpio(config->txpinset); + + /* Renable device if previosuly disabled in fdcan_shutdown() */ + + if (priv->state == FDCAN_STATE_DISABLED) + { + /* Reset Clock Stop Request bit */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~FDCAN_CCCR_CSR; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Wait for Clock Stop Acknowledge bit reset to indicate + * device is operational + */ + + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & FDCAN_CCCR_CSA) + != 0); + } + + /* Enable the Initialization state */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= FDCAN_CCCR_INIT; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Wait for initialization mode to take effect */ + + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & FDCAN_CCCR_INIT) + == 0); + + /* Enable writing to configuration registers */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= FDCAN_CCCR_CCE; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Global Filter Configuration: + * + * ANFS=0: Store all non matching standard frame in RX FIFO0 + * ANFE=0: Store all non matching extended frame in RX FIFO0 + */ + + regval = FDCAN_RXGFC_ANFE_RX_FIFO0 | FDCAN_RXGFC_ANFS_RX_FIFO0; + fdcan_putreg(priv, STM32_FDCAN_RXGFC_OFFSET, regval); + + /* Extended ID Filter AND mask */ + + fdcan_putreg(priv, STM32_FDCAN_XIDAM_OFFSET, 0x1fffffff); + + /* Disable all interrupts */ + + fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, 0); + fdcan_putreg(priv, STM32_FDCAN_TXBTIE_OFFSET, 0); + + /* All interrupts directed to Line 0. But disable both interrupt lines 0 + * and 1 for now. + * + * REVISIT: Only interrupt line 0 is used by this driver. + */ + + fdcan_putreg(priv, STM32_FDCAN_ILS_OFFSET, 0); + fdcan_putreg(priv, STM32_FDCAN_ILE_OFFSET, 0); + + /* Clear all pending interrupts. */ + + fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_INT_ALL); + + /* Configure FDCAN bit timing */ + + fdcan_putreg(priv, STM32_FDCAN_NBTP_OFFSET, priv->nbtp); + fdcan_putreg(priv, STM32_FDCAN_DBTP_OFFSET, priv->dbtp); + + /* Configure message RAM starting addresses and sizes. */ + + regval = FDCAN_RXGFC_LSS(config->nstdfilters); + regval |= FDCAN_RXGFC_LSE(config->nextfilters); + fdcan_putreg(priv, STM32_FDCAN_RXGFC_OFFSET, regval); + + /* Dump RAM layout */ + + fdcan_dumpramlayout(priv); + + /* Configure Message Filters */ + + /* Disable all standard filters */ + + msgram = config->msgram.stdfilters; + cntr = config->nstdfilters; + while (cntr > 0) + { + *msgram++ = STDFILTER_S0_SFEC_DISABLE; + cntr--; + } + + /* Disable all extended filters */ + + msgram = config->msgram.extfilters; + cntr = config->nextfilters; + while (cntr > 0) + { + *msgram = EXTFILTER_F0_EFEC_DISABLE; + msgram = msgram + 2; + cntr--; + } + + /* Input clock divider configuration */ + + regval = FDCANCLK_PDIV; + fdcan_putreg(priv, STM32_FDCAN_CKDIV_OFFSET, regval); + + /* CC control register */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~(FDCAN_CCCR_NISO | FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE); + + /* Select ISO11898-1 or Non ISO Bosch CAN FD Specification V1.0 */ + + switch (config->format) + { + case FDCAN_ISO11898_1_FORMAT: + { + break; + } + + case FDCAN_NONISO_BOSCH_V1_FORMAT: + { + regval |= FDCAN_CCCR_NISO; + break; + } + + default: + { + return -EINVAL; + } + } + + /* Select Classic CAN mode or FD mode with or without fast bit rate + * switching + */ + + switch (config->mode) + { + case FDCAN_CLASSIC_MODE: + { + break; + } + +#ifdef CONFIG_CAN_FD + case FDCAN_FD_MODE: + { + regval |= FDCAN_CCCR_FDOE; + break; + } + + case FDCAN_FD_BRS_MODE: + { + regval |= (FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE); + break; + } +#endif + + default: + { + return -EINVAL; + } + } + + /* Set the initial CAN mode */ + + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + /* Enable FIFO/Queue mode */ + + regval = fdcan_getreg(priv, STM32_FDCAN_TXBC_OFFSET); +#ifdef CONFIG_STM32H5_FDCAN_QUEUE_MODE + regval |= FDCAN_TXBC_TFQM; +#else + regval &= ~FDCAN_TXBC_TFQM; +#endif + fdcan_putreg(priv, STM32_FDCAN_TXBC_OFFSET, regval); + +#ifdef STM32H5_FDCAN_LOOPBACK + /* Is loopback mode selected for this peripheral? */ + + if (config->loopback) + { + /* FDCAN_CCCR_TEST - Test mode enable + * FDCAN_CCCR_MON - Bus monitoring mode (for internal loopback) + * FDCAN_TEST_LBCK - Loopback mode + */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval |= (FDCAN_CCCR_TEST | FDCAN_CCCR_MON); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + regval = fdcan_getreg(priv, STM32_FDCAN_TEST_OFFSET); + regval |= FDCAN_TEST_LBCK; + fdcan_putreg(priv, STM32_FDCAN_TEST_OFFSET, regval); + } +#endif + + /* Configure interrupt lines */ + + /* Select RX-related interrupts */ + + priv->rxints = FDCAN_RXFIFO_INTS; + + /* Select TX-related interrupts */ + + priv->txints = FDCAN_TXFIFOQ_INTS; + + /* Direct all interrupts to Line 0. + * + * Bits in the ILS register correspond to each FDCAN interrupt; A bit + * set to '1' is directed to interrupt line 1; a bit cleared to '0' + * is directed interrupt line 0. + * + * REVISIT: Nothing is done here. Only interrupt line 0 is used by + * this driver and ILS was already cleared above. + */ + + /* Enable only interrupt line 0. */ + + fdcan_putreg(priv, STM32_FDCAN_ILE_OFFSET, FDCAN_ILE_EINT0); + + /* Disable initialization mode to enable normal operation */ + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + regval &= ~FDCAN_CCCR_INIT; + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + return OK; +} + +/**************************************************************************** + * Name: fdcan_bittiming + * + * Description: + * Convert desired bitrate to FDCAN bit segment values + * The computed values apply to both data and arbitration phases + * + * Input Parameters: + * timing - structure to store bit timing + * + * Returned Value: + * OK on success; >0 on failure. + ****************************************************************************/ + +int32_t fdcan_bittiming(struct fdcan_bitseg *timing) +{ + /* Implementation ported from PX4's uavcan_drivers/stm32[h7] + * + * Ref. "Automatic Baudrate Detection in CANopen Networks", U. Koppe + * MicroControl GmbH & Co. KG + * CAN in Automation, 2003 + * + * According to the source, optimal quanta per bit are: + * Bitrate Optimal Maximum + * 1000 kbps 8 10 + * 500 kbps 16 17 + * 250 kbps 16 17 + * 125 kbps 16 17 + */ + + const uint32_t target_bitrate = timing->bitrate; + static const int32_t max_bs1 = 16; + static const int32_t max_bs2 = 8; + const uint8_t max_quanta_per_bit = (timing->bitrate >= 1000000) ? 10 : 17; + static const int max_sp_location = 900; + + /* Computing (prescaler * BS): + * BITRATE = 1 / (PRESCALER * (1 / PCLK) * (1 + BS1 + BS2)) + * BITRATE = PCLK / (PRESCALER * (1 + BS1 + BS2)) + * let: + * BS = 1 + BS1 + BS2 + * (BS == total number of time quanta per bit) + * PRESCALER_BS = PRESCALER * BS + * ==> + * PRESCALER_BS = PCLK / BITRATE + */ + + const uint32_t prescaler_bs = STM32_FDCANCLK_FREQUENCY / target_bitrate; + + /* Find prescaler value such that the number of quanta per bit is highest */ + + uint8_t bs1_bs2_sum = max_quanta_per_bit - 1; + + while ((prescaler_bs % (1 + bs1_bs2_sum)) != 0) + { + if (bs1_bs2_sum <= 2) + { + nerr("Target bitrate too high - no solution possible."); + return 1; /* No solution */ + } + + bs1_bs2_sum--; + } + + const uint32_t prescaler = prescaler_bs / (1 + bs1_bs2_sum); + + if ((prescaler < 1U) || (prescaler > 1024U)) + { + nerr("Target bitrate invalid - bad prescaler."); + return 2; /* No solution */ + } + + /* Now we have a constraint: (BS1 + BS2) == bs1_bs2_sum. + * We need to find the values so that the sample point is as close as + * possible to the optimal value. + * + * Solve[(1 + bs1)/(1 + bs1 + bs2) == 7/8, bs2] + * (Where 7/8 is 0.875, the recommended sample point location) + * {{bs2 -> (1 + bs1)/7}} + * + * Hence: + * bs2 = (1 + bs1) / 7 + * bs1 = (7 * bs1_bs2_sum - 1) / 8 + * + * Sample point location can be computed as follows: + * Sample point location = (1 + bs1) / (1 + bs1 + bs2) + * + * Since the optimal solution is so close to the maximum, we prepare two + * solutions, and then pick the best one: + * - With rounding to nearest + * - With rounding to zero + */ + + /* First attempt with rounding to nearest */ + + uint8_t bs1 = (uint8_t)((7 * bs1_bs2_sum - 1) + 4) / 8; + uint8_t bs2 = (uint8_t)(bs1_bs2_sum - bs1); + uint16_t sample_point_permill = + (uint16_t)(1000 * (1 + bs1) / (1 + bs1 + bs2)); + + if (sample_point_permill > max_sp_location) + { + /* Second attempt with rounding to zero */ + + bs1 = (7 * bs1_bs2_sum - 1) / 8; + bs2 = bs1_bs2_sum - bs1; + } + + bool valid = (bs1 >= 1) && (bs1 <= max_bs1) && (bs2 >= 1) && + (bs2 <= max_bs2); + + /* Final validation + * Helpful Python: + * def sample_point_from_btr(x): + * assert 0b0011110010000000111111000000000 & x == 0 + * ts2,ts1,brp = (x>>20)&7, (x>>16)&15, x&511 + * return (1+ts1+1)/(1+ts1+1+ts2+1) + */ + + if (target_bitrate != (STM32_FDCANCLK_FREQUENCY / + (prescaler * (1 + bs1 + bs2))) || !valid) + { + nerr("Target bitrate invalid - solution does not match."); + return 3; /* Solution not found */ + } + +#ifdef CONFIG_STM32H5_FDCAN_REGDEBUG + ninfo("[fdcan] CLK_FREQ %lu, target_bitrate %lu, prescaler %lu, bs1 %d" + ", bs2 %d\n", CLK_FREQ, target_bitrate, prescaler_bs, bs1 - 1, + bs2 - 1); +#endif + + timing->bs1 = (uint8_t)(bs1 - 1); + timing->bs2 = (uint8_t)(bs2 - 1); + timing->prescaler = (uint16_t)(prescaler - 1); + timing->sjw = 0; /* Which means one */ + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32_fdcaninitialize + * + * Description: + * Initialize the selected FDCAN port + * + * Input Parameters: + * port - Port number (for hardware that has multiple FDCAN interfaces), + * 1=FDCAN1, 2=FDCAN2 + * + * Returned Value: + * Valid CAN device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct can_dev_s *stm32_fdcaninitialize(int port) +{ + struct can_dev_s *dev = NULL; + struct stm32_fdcan_s *priv = NULL; + const struct stm32_config_s *config = NULL; + bool auto_bit_timing = true; + struct fdcan_bitseg timing; + + caninfo("FDCAN%d\n", port); + + /* Select FDCAN peripheral to be initialized */ + +#ifdef CONFIG_STM32H5_FDCAN1 + if (port == FDCAN1) + { + /* Select the FDCAN1 device structure */ + + dev = &g_fdcan1dev; + priv = &g_fdcan1priv; + config = &g_fdcan1const; +#ifndef CONFIG_STM32H5_FDCAN1_AUTO_BIT_TIMING + auto_bit_timing = false; +#endif + } + else +#endif +#ifdef CONFIG_STM32H5_FDCAN2 + if (port == FDCAN2) + { + /* Select the FDCAN2 device structure */ + + dev = &g_fdcan2dev; + priv = &g_fdcan2priv; + config = &g_fdcan2const; +#ifndef CONFIG_STM32H5_FDCAN2_AUTO_BIT_TIMING + auto_bit_timing = false; +#endif + } + else +#endif + { + canerr("ERROR: Unsupported port %d\n", port); + return NULL; + } + + /* Perform one time data initialization */ + + memset(priv, 0, sizeof(struct stm32_fdcan_s)); + priv->config = config; + + /* Set the initial bit timing. This might change subsequently + * due to IOCTL command processing. + */ + + if (auto_bit_timing == false) + { + priv->nbtp = config->nbtp; + priv->dbtp = config->dbtp; + } + else + { + timing.bitrate = config->baud; + if (fdcan_bittiming(&timing) != OK) + { + printf("ERROR: Invalid CAN nominal phase timings\n"); + return NULL; + } + + priv->nbtp = FDCAN_NBTP_NBRP(timing.prescaler) | + FDCAN_NBTP_NTSEG1(timing.bs1) | + FDCAN_NBTP_NTSEG2(timing.bs2) | + FDCAN_NBTP_NSJW(timing.sjw); + + if (config->mode == FDCAN_FD_BRS_MODE) + { + timing.bitrate = config->data_baud; + if (fdcan_bittiming(&timing) != OK) + { + printf("ERROR: Invalid CAN data phase timings\n"); + return NULL; + } + + priv->dbtp = FDCAN_DBTP_DBRP(timing.prescaler) | + FDCAN_DBTP_DTSEG1(timing.bs1) | + FDCAN_DBTP_DTSEG2(timing.bs2) | + FDCAN_DBTP_DSJW(timing.sjw); + } + else + { + priv->dbtp = 0; + } + } + + dev->cd_ops = &g_fdcanops; + dev->cd_priv = (void *)priv; + + /* And put the hardware in the initial state */ + + fdcan_reset(dev); + + return dev; +} diff --git a/arch/arm/src/stm32h5/stm32_fdcan.h b/arch/arm/src/stm32h5/stm32_fdcan.h new file mode 100644 index 0000000000..5f770fc7a8 --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_fdcan.h @@ -0,0 +1,111 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_fdcan.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_FDCAN_H +#define __ARCH_ARM_SRC_STM32H5_STM32_FDCAN_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "chip.h" +#include "hardware/stm32_fdcan.h" + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Port numbers for use with stm32_fdcan_initialize() */ + +#define FDCAN1 1 +#define FDCAN2 2 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_FDCAN_CHARDRIVER + +/**************************************************************************** + * Name: stm32_fdcaninitialize + * + * Description: + * Initialize the selected FDCAN port + * + * Input Parameters: + * Port number (for hardware that has multiple FDCAN interfaces) + * + * Returned Value: + * Valid FDCAN device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct can_dev_s *stm32_fdcaninitialize(int port); +#endif + +#ifdef CONFIG_STM32H5_FDCAN_SOCKET + +/**************************************************************************** + * Name: stm32_fdcansockinitialize + * + * Description: + * Initialize the selected FDCAN port as SocketCAN interface + * + * Input Parameters: + * Port number (for hardware that has multiple FDCAN interfaces) + * + * Returned Value: + * OK on success; Negated errno on failure. + * + ****************************************************************************/ + +int stm32_fdcansockinitialize(int port); +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_ARM_SRC_STM32H5_STM32_FDCAN_H */