You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2022/02/06 09:09:20 UTC
[incubator-nuttx] 01/03: arch/stm32: add FDCAN SocketCAN support
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git
commit 341bbe38d5b15d2aaad83a89108bd6285b7543aa
Author: raiden00pl <ra...@railab.me>
AuthorDate: Sat Jan 22 15:10:27 2022 +0100
arch/stm32: add FDCAN SocketCAN support
---
arch/arm/src/stm32/Kconfig | 12 +-
arch/arm/src/stm32/Make.defs | 3 +
arch/arm/src/stm32/stm32_fdcan.h | 22 +
arch/arm/src/stm32/stm32_fdcan_sock.c | 3196 +++++++++++++++++++++++++++++++++
4 files changed, 3226 insertions(+), 7 deletions(-)
diff --git a/arch/arm/src/stm32/Kconfig b/arch/arm/src/stm32/Kconfig
index 6ba361f..c8d8c03 100644
--- a/arch/arm/src/stm32/Kconfig
+++ b/arch/arm/src/stm32/Kconfig
@@ -2769,14 +2769,12 @@ config STM32_BKPSRAM
config STM32_CAN1
bool "CAN1"
default n
- select CAN
select STM32_CAN
depends on STM32_HAVE_CAN1
config STM32_CAN2
bool "CAN2"
default n
- select CAN
select STM32_CAN
depends on STM32_HAVE_CAN2
@@ -3391,11 +3389,9 @@ config STM32_I2C
config STM32_CAN
bool
- select ARCH_HAVE_CAN_ERRORS
config STM32_FDCAN
bool
- select ARCH_HAVE_CAN_ERRORS
config STM32_TIM
bool
@@ -10863,6 +10859,7 @@ choice
config STM32_CAN_CHARDRIVER
bool "STM32 CAN character driver support"
select ARCH_HAVE_CAN_ERRORS
+ select CAN
config STM32_CAN_SOCKET
bool "STM32 CAN SocketCAN support"
@@ -10915,11 +10912,12 @@ choice
config STM32_FDCAN_CHARDRIVER
bool "STM32 FDCAN character driver support"
- select ARCH_HAVE_FDCAN_ERRORS
+ select ARCH_HAVE_CAN_ERRORS
+ select CAN
config STM32_FDCAN_SOCKET
- bool "STM32 FDCAN SocketCAN support (not supported yet)"
- select NET_FDCAN_HAVE_ERRORS
+ bool "STM32 FDCAN SocketCAN support"
+ select NET_CAN_HAVE_ERRORS
endchoice # FDCAN character driver or SocketCAN support
diff --git a/arch/arm/src/stm32/Make.defs b/arch/arm/src/stm32/Make.defs
index 3328db8..e484536 100644
--- a/arch/arm/src/stm32/Make.defs
+++ b/arch/arm/src/stm32/Make.defs
@@ -249,6 +249,9 @@ ifeq ($(CONFIG_STM32_FDCAN),y)
ifeq ($(CONFIG_STM32_FDCAN_CHARDRIVER),y)
CHIP_CSRCS += stm32_fdcan.c
endif
+ifeq ($(CONFIG_STM32_FDCAN_SOCKET),y)
+CHIP_CSRCS += stm32_fdcan_sock.c
+endif
endif
ifeq ($(CONFIG_STM32_IWDG),y)
diff --git a/arch/arm/src/stm32/stm32_fdcan.h b/arch/arm/src/stm32/stm32_fdcan.h
index 34d2794..e895e2b 100644
--- a/arch/arm/src/stm32/stm32_fdcan.h
+++ b/arch/arm/src/stm32/stm32_fdcan.h
@@ -65,6 +65,8 @@ extern "C"
* Public Function Prototypes
****************************************************************************/
+#ifdef CONFIG_STM32_FDCAN_CHARDRIVER
+
/****************************************************************************
* Name: stm32_fdcaninitialize
*
@@ -80,6 +82,26 @@ extern "C"
****************************************************************************/
FAR struct can_dev_s *stm32_fdcaninitialize(int port);
+#endif
+
+#ifdef CONFIG_STM32_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)
diff --git a/arch/arm/src/stm32/stm32_fdcan_sock.c b/arch/arm/src/stm32/stm32_fdcan_sock.c
new file mode 100644
index 0000000..fafecbe
--- /dev/null
+++ b/arch/arm/src/stm32/stm32_fdcan_sock.c
@@ -0,0 +1,3196 @@
+/****************************************************************************
+ * arch/arm/src/stm32/stm32_fdcan_sock.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 <nuttx/config.h>
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <arch/board/board.h>
+#include <nuttx/irq.h>
+#include <nuttx/arch.h>
+
+#include <nuttx/wqueue.h>
+#include <nuttx/can.h>
+#include <nuttx/net/netdev.h>
+#include <nuttx/net/can.h>
+#include <netpacket/can.h>
+
+#include "arm_internal.h"
+#include "arm_arch.h"
+
+#include "stm32_fdcan.h"
+#include "hardware/stm32_pinmap.h"
+#include "stm32_gpio.h"
+#include "stm32_rcc.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Pool configuration *******************************************************/
+
+#define POOL_SIZE (1)
+
+/* Work queue support is required. */
+
+#if !defined(CONFIG_SCHED_WORKQUEUE)
+# error Work queue support is required
+#endif
+
+/* The low priority work queue is preferred. If it is not enabled, LPWORK
+ * will be the same as HPWORK.
+ *
+ * NOTE: However, the network should NEVER run on the high priority work
+ * queue! That queue is intended only to service short back end interrupt
+ * processing that never suspends. Suspending the high priority work queue
+ * may bring the system to its knees!
+ */
+
+#define CANWORK LPWORK
+
+/* Clock source *************************************************************/
+
+#define FDCANCLK_PDIV (0)
+
+#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 ****************************************************/
+
+#if defined(CONFIG_STM32_STM32G4XXX)
+
+/* FDCAN Message RAM */
+
+# define FDCAN_MSGRAM_WORDS (212)
+# define STM32_CANRAM1_BASE (STM32_CANRAM_BASE + 0x0000)
+# define STM32_CANRAM2_BASE (STM32_CANRAM_BASE + 1*(FDCAN_MSGRAM_WORDS * 4) + 4)
+# define STM32_CANRAM3_BASE (STM32_CANRAM_BASE + 2*(FDCAN_MSGRAM_WORDS * 4) + 4)
+
+# ifdef CONFIG_STM32_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_STM32_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
+# ifdef CONFIG_STM32_FDCAN3
+# define FDCAN3_STDFILTER_SIZE (28)
+# define FDCAN3_EXTFILTER_SIZE (8)
+# define FDCAN3_RXFIFO0_SIZE (3)
+# define FDCAN3_RXFIFO1_SIZE (3)
+# define FDCAN3_TXEVENTFIFO_SIZE (3)
+# define FDCAN3_TXFIFIOQ_SIZE (3)
+
+# define FDCAN3_STDFILTER_WORDS (28)
+# define FDCAN3_EXTFILTER_WORDS (16)
+# define FDCAN3_RXFIFO0_WORDS (54)
+# define FDCAN3_RXFIFO1_WORDS (54)
+# define FDCAN3_TXEVENTFIFO_WORDS (6)
+# define FDCAN3_TXFIFIOQ_WORDS (54)
+# endif
+#else
+# error
+#endif
+
+/* FDCAN1 Configuration *****************************************************/
+
+#ifdef CONFIG_STM32_FDCAN1
+
+/* Bit timing */
+
+# define FDCAN1_NTSEG1 (CONFIG_STM32_FDCAN1_NTSEG1 - 1)
+# define FDCAN1_NTSEG2 (CONFIG_STM32_FDCAN1_NTSEG2 - 1)
+# define FDCAN1_NBRP ((STM32_FDCANCLK_FREQUENCY / \
+ ((FDCAN1_NTSEG1 + FDCAN1_NTSEG2 + 3) * \
+ CONFIG_STM32_FDCAN1_BITRATE)) - 1)
+# define FDCAN1_NSJW (CONFIG_STM32_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_STM32_FDCAN1_FD_BRS
+# define FDCAN1_DTSEG1 (CONFIG_STM32_FDCAN1_DTSEG1 - 1)
+# define FDCAN1_DTSEG2 (CONFIG_STM32_FDCAN1_DTSEG2 - 1)
+# define FDCAN1_DBRP ((STM32_FDCANCLK_FREQUENCY / \
+ ((FDCAN1_DTSEG1 + FDCAN1_DTSEG2 + 3) * \
+ CONFIG_STM32_FDCAN1_DBITRATE)) - 1)
+# define FDCAN1_DSJW (CONFIG_STM32_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
+
+/* 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_STM32_FDCAN1 */
+
+/* FDCAN2 Configuration *****************************************************/
+
+#ifdef CONFIG_STM32_FDCAN2
+
+/* Bit timing */
+
+# define FDCAN2_NTSEG1 (CONFIG_STM32_FDCAN2_NTSEG1 - 1)
+# define FDCAN2_NTSEG2 (CONFIG_STM32_FDCAN2_NTSEG2 - 1)
+# define FDCAN2_NBRP (((STM32_FDCANCLK_FREQUENCY / \
+ ((FDCAN2_NTSEG1 + FDCAN2_NTSEG2 + 3) * \
+ CONFIG_STM32_FDCAN2_BITRATE)) - 1))
+# define FDCAN2_NSJW (CONFIG_STM32_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_STM32_FDCAN2_FD_BRS
+# define FDCAN2_DTSEG1 (CONFIG_STM32_FDCAN2_DTSEG1 - 1)
+# define FDCAN2_DTSEG2 (CONFIG_STM32_FDCAN2_DTSEG2 - 1)
+# define FDCAN2_DBRP (((STM32_FDCANCLK_FREQUENCY / \
+ ((FDCAN2_DTSEG1 + FDCAN2_DTSEG2 + 3) * \
+ CONFIG_STM32_FDCAN2_DBITRATE)) - 1))
+# define FDCAN2_DSJW (CONFIG_STM32_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
+
+/* 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_STM32_FDCAN2 */
+
+/* FDCAN3 Configuration *****************************************************/
+
+#ifdef CONFIG_STM32_FDCAN3
+
+/* Bit timing */
+
+# define FDCAN3_NTSEG1 (CONFIG_STM32_FDCAN3_NTSEG1 - 1)
+# define FDCAN3_NTSEG2 (CONFIG_STM32_FDCAN3_NTSEG2 - 1)
+# define FDCAN3_NBRP (((STM32_FDCANCLK_FREQUENCY / \
+ ((FDCAN3_NTSEG1 + FDCAN3_NTSEG2 + 3) * \
+ CONFIG_STM32_FDCAN3_BITRATE)) - 1))
+# define FDCAN3_NSJW (CONFIG_STM32_FDCAN3_NSJW - 1)
+
+# if FDCAN3_NTSEG1 > FDCAN_NBTP_NTSEG1_MAX
+# error Invalid FDCAN3 NTSEG1
+# endif
+# if FDCAN3_NTSEG2 > FDCAN_NBTP_NTSEG2_MAX
+# error Invalid FDCAN3 NTSEG2
+# endif
+# if FDCAN3_NSJW > FDCAN_NBTP_NSJW_MAX
+# error Invalid FDCAN3 NSJW
+# endif
+# if FDCAN3_NBRP > FDCAN_NBTP_NBRP_MAX
+# error Invalid FDCAN1 NBRP
+# endif
+
+# ifdef CONFIG_STM32_FDCAN3_FD_BRS
+# define FDCAN3_DTSEG1 (CONFIG_STM32_FDCAN3_DTSEG1 - 1)
+# define FDCAN3_DTSEG2 (CONFIG_STM32_FDCAN3_DTSEG2 - 1)
+# define FDCAN3_DBRP (((STM32_FDCANCLK_FREQUENCY / \
+ ((FDCAN3_DTSEG1 + FDCAN3_DTSEG2 + 3) * \
+ CONFIG_STM32_FDCAN3_DBITRATE)) - 1))
+# define FDCAN3_DSJW (CONFIG_STM32_FDCAN3_DSJW - 1)
+# else
+# define FDCAN3_DTSEG1 1
+# define FDCAN3_DTSEG2 1
+# define FDCAN3_DBRP 1
+# define FDCAN3_DSJW 1
+# endif /* CONFIG_STM32_FDCAN3_FD_BRS */
+
+# if FDCAN3_DTSEG1 > FDCAN_DBTP_DTSEG1_MAX
+# error Invalid FDCAN3 DTSEG1
+# endif
+# if FDCAN3_DTSEG2 > FDCAN_DBTP_DTSEG2_MAX
+# error Invalid FDCAN3 DTSEG2
+# endif
+# if FDCAN3_DBRP > FDCAN_DBTP_DBRP_MAX
+# error Invalid FDCAN3 DBRP
+# endif
+# if FDCAN3_DSJW > FDCAN_DBTP_DSJW_MAX
+# error Invalid FDCAN3 DSJW
+# endif
+
+/* FDCAN3 Message RAM Configuration *****************************************/
+
+/* FDCAN3 Message RAM Layout */
+
+# define FDCAN3_STDFILTER_INDEX 0
+# define FDCAN3_EXTFILTERS_INDEX (FDCAN3_STDFILTER_INDEX + FDCAN3_STDFILTER_WORDS)
+# define FDCAN3_RXFIFO0_INDEX (FDCAN3_EXTFILTERS_INDEX + FDCAN3_EXTFILTER_WORDS)
+# define FDCAN3_RXFIFO1_INDEX (FDCAN3_RXFIFO0_INDEX + FDCAN3_RXFIFO0_WORDS)
+# define FDCAN3_TXEVENTFIFO_INDEX (FDCAN3_RXFIFO1_INDEX + FDCAN3_RXFIFO1_WORDS)
+# define FDCAN3_TXFIFOQ_INDEX (FDCAN3_TXEVENTFIFO_INDEX + FDCAN3_TXEVENTFIFO_WORDS)
+# define FDCAN3_MSGRAM_WORDS (FDCAN3_TXFIFOQ_INDEX + FDCAN3_TXFIFIOQ_WORDS)
+
+#endif /* CONFIG_STM32_FDCAN3 */
+
+/* Loopback mode */
+
+#undef STM32_FDCAN_LOOPBACK
+#if defined(CONFIG_STM32_FDCAN1_LOOPBACK) || \
+ defined(CONFIG_STM32_FDCAN2_LOOPBACK) || \
+ defined(CONFIG_STM32_FDCAN3_LOOPBACK)
+# define STM32_FDCAN_LOOPBACK 1
+#endif
+
+/* 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)
+
+/* 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_RXFIFO0_INTS (FDCAN_INT_RF0N | FDCAN_INT_RF0L)
+#define FDCAN_RXFIFO1_INTS (FDCAN_INT_RF1N | FDCAN_INT_RF1L)
+
+#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_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_NET_INFO
+# undef CONFIG_STM32_FDCAN_REGDEBUG
+#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 */
+#ifdef CONFIG_CAN_FD
+ FDCAN_FD_MODE = 1, /* CAN FD operation */
+ FDCAN_FD_BRS_MODE = 2 /* CAN FD operation with bit rate switching */
+#endif
+};
+
+/* 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 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 STM32_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 */
+
+#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_STM32_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
+
+ bool bifup; /* true:ifup false:ifdown */
+ struct net_driver_s dev; /* Interface understood by the network */
+
+ struct work_s irqwork; /* For deferring interrupt work to the wq */
+ struct work_s pollwork; /* For deferring poll work to the work wq */
+
+ /* A pointers to the list of TX/RX descriptors */
+
+ FAR struct can_frame *txdesc;
+ FAR struct can_frame *rxdesc;
+
+ /* TX/RX pool */
+
+ uint8_t tx_pool[sizeof(struct can_frame)*POOL_SIZE];
+ uint8_t rx_pool[sizeof(struct can_frame)*POOL_SIZE];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* FDCAN Register access */
+
+static uint32_t fdcan_getreg(FAR struct stm32_fdcan_s *priv, int offset);
+static void fdcan_putreg(FAR struct stm32_fdcan_s *priv, int offset,
+ uint32_t regval);
+#ifdef CONFIG_STM32_FDCAN_REGDEBUG
+static void fdcan_dumpregs(FAR struct stm32_fdcan_s *priv,
+ FAR const char *msg);
+static void fdcan_dumprxregs(FAR struct stm32_fdcan_s *priv,
+ FAR const char *msg);
+static void fdcan_dumptxregs(FAR struct stm32_fdcan_s *priv,
+ FAR const char *msg);
+static void fdcan_dumpramlayout(FAR 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
+
+/* CAN interrupt enable functions */
+
+static void fdcan_rx0int(FAR struct stm32_fdcan_s *priv, bool enable);
+static void fdcan_rx1int(FAR struct stm32_fdcan_s *priv, bool enable);
+static void fdcan_txint(FAR struct stm32_fdcan_s *priv, bool enable);
+#ifdef CONFIG_NET_CAN_ERRORS
+static void fdcan_errint(FAR struct stm32_fdcan_s *priv, bool enable);
+#endif
+
+/* Common TX logic */
+
+static int fdcan_send(FAR struct stm32_fdcan_s *priv);
+static bool fdcan_txready(FAR struct stm32_fdcan_s *priv);
+static int fdcan_txpoll(struct net_driver_s *dev);
+
+/* CAN RX interrupt handling */
+
+static void fdcan_rx0interrupt_work(FAR void *arg);
+static void fdcan_rx1interrupt_work(FAR void *arg);
+
+/* CAN TX interrupt handling */
+
+static void fdcan_txdone_work(FAR void *arg);
+static void fdcan_txdone(FAR struct stm32_fdcan_s *priv);
+
+#ifdef CONFIG_NET_CAN_ERRORS
+/* CAN errors interrupt handling */
+
+static void fdcan_error_work(FAR void *arg);
+#endif
+
+/* FDCAN interrupt handling */
+
+#ifdef CONFIG_NET_CAN_ERRORS
+static void fdcan_error(FAR struct stm32_fdcan_s *priv, uint32_t status);
+#endif
+static void fdcan_receive(FAR struct stm32_fdcan_s *priv,
+ FAR volatile uint32_t *rxbuffer,
+ unsigned long nwords);
+static int fdcan_interrupt(int irq, void *context, FAR void *arg);
+
+/* Initialization */
+
+static void fdcan_reset(FAR struct stm32_fdcan_s *priv);
+static int fdcan_setup(FAR struct stm32_fdcan_s *priv);
+static void fdcan_shutdown(FAR struct stm32_fdcan_s *priv);
+
+/* FDCAN helpers */
+
+static uint8_t fdcan_dlc2bytes(FAR struct stm32_fdcan_s *priv, uint8_t dlc);
+
+#if 0 /* not used for now */
+static int
+fdcan_start_busoff_recovery_sequence(FAR struct stm32_fdcan_s *priv);
+#endif
+
+/* Hardware initialization */
+
+static int fdcan_hw_initialize(FAR struct stm32_fdcan_s *priv);
+
+/* NuttX callback functions */
+
+static int fdcan_ifup(struct net_driver_s *dev);
+static int fdcan_ifdown(struct net_driver_s *dev);
+
+static void fdcan_txavail_work(FAR void *arg);
+static int fdcan_txavail(struct net_driver_s *dev);
+
+#ifdef CONFIG_NETDEV_IOCTL
+static int fdcan_netdev_ioctl(struct net_driver_s *dev, int cmd,
+ unsigned long arg);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+#ifdef CONFIG_STM32_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_STM32_FDCAN1_BITRATE,
+ .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),
+ .port = 1,
+ .irq0 = STM32_IRQ_FDCAN1_0,
+ .irq1 = STM32_IRQ_FDCAN1_1,
+#if defined(CONFIG_STM32_FDCAN1_CLASSIC)
+ .mode = FDCAN_CLASSIC_MODE,
+#elif defined(CONFIG_STM32_FDCAN1_FD)
+ .mode = FDCAN_FD_MODE,
+#else
+ .mode = FDCAN_FD_BRS_MODE,
+#endif
+#if defined(CONFIG_STM32_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_STM32_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;
+
+#endif /* CONFIG_STM32_FDCAN1 */
+
+#ifdef CONFIG_STM32_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_STM32_FDCAN2_BITRATE,
+ .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),
+ .port = 2,
+ .irq0 = STM32_IRQ_FDCAN2_0,
+ .irq1 = STM32_IRQ_FDCAN2_1,
+#if defined(CONFIG_STM32_FDCAN2_CLASSIC)
+ .mode = FDCAN_CLASSIC_MODE,
+#elif defined(CONFIG_STM32_FDCAN2_FD)
+ .mode = FDCAN_FD_MODE,
+#else
+ .mode = FDCAN_FD_BRS_MODE,
+#endif
+#if defined(CONFIG_STM32_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_STM32_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;
+
+#endif /* CONFIG_STM32_FDCAN2 */
+
+#ifdef CONFIG_STM32_FDCAN3
+/* FDCAN3 message RAM allocation */
+
+/* FDCAN3 constant configuration */
+
+static const struct stm32_config_s g_fdcan3const =
+{
+ .rxpinset = GPIO_FDCAN3_RX,
+ .txpinset = GPIO_FDCAN3_TX,
+ .base = STM32_FDCAN3_BASE,
+ .baud = CONFIG_STM32_FDCAN3_BITRATE,
+ .nbtp = FDCAN_NBTP_NBRP(FDCAN3_NBRP) |
+ FDCAN_NBTP_NTSEG1(FDCAN3_NTSEG1) |
+ FDCAN_NBTP_NTSEG2(FDCAN3_NTSEG2) |
+ FDCAN_NBTP_NSJW(FDCAN3_NSJW),
+ .dbtp = FDCAN_DBTP_DBRP(FDCAN3_DBRP) |
+ FDCAN_DBTP_DTSEG1(FDCAN3_DTSEG1) |
+ FDCAN_DBTP_DTSEG2(FDCAN3_DTSEG2) |
+ FDCAN_DBTP_DSJW(FDCAN3_DSJW),
+ .port = 3,
+ .irq0 = STM32_IRQ_FDCAN3_0,
+ .irq1 = STM32_IRQ_FDCAN3_1,
+#if defined(CONFIG_STM32_FDCAN3_CLASSIC)
+ .mode = FDCAN_CLASSIC_MODE,
+#elif defined(CONFIG_STM32_FDCAN3_FD)
+ .mode = FDCAN_FD_MODE,
+#else
+ .mode = FDCAN_FD_BRS_MODE,
+#endif
+#if defined(CONFIG_STM32_FDCAN3_NONISO_FORMAT)
+ .format = FDCAN_NONISO_BOSCH_V1_FORMAT,
+#else
+ .format = FDCAN_ISO11898_1_FORMAT,
+#endif
+ .nstdfilters = FDCAN3_STDFILTER_SIZE,
+ .nextfilters = FDCAN3_EXTFILTER_SIZE,
+ .nrxfifo0 = FDCAN3_RXFIFO0_SIZE,
+ .nrxfifo1 = FDCAN3_RXFIFO1_SIZE,
+ .ntxeventfifo = FDCAN3_TXEVENTFIFO_SIZE,
+ .ntxfifoq = FDCAN3_TXFIFIOQ_SIZE,
+ .rxfifo0esize = (FDCAN3_RXFIFO0_WORDS / FDCAN3_RXFIFO0_SIZE),
+ .rxfifo1esize = (FDCAN3_RXFIFO1_WORDS / FDCAN3_RXFIFO1_SIZE),
+ .txeventesize = (FDCAN3_TXEVENTFIFO_WORDS / FDCAN3_TXEVENTFIFO_SIZE),
+ .txbufferesize = (FDCAN3_TXFIFIOQ_WORDS / FDCAN3_TXFIFIOQ_SIZE),
+
+#ifdef CONFIG_STM32_FDCAN3_LOOPBACK
+ .loopback = true,
+#endif
+
+ /* FDCAN3 Message RAM */
+
+ .msgram =
+ {
+ (uint32_t *)(STM32_CANRAM3_BASE + (FDCAN3_STDFILTER_INDEX << 2)),
+ (uint32_t *)(STM32_CANRAM3_BASE + (FDCAN3_EXTFILTERS_INDEX << 2)),
+ (uint32_t *)(STM32_CANRAM3_BASE + (FDCAN3_RXFIFO0_INDEX << 2)),
+ (uint32_t *)(STM32_CANRAM3_BASE + (FDCAN3_RXFIFO1_INDEX << 2)),
+ (uint32_t *)(STM32_CANRAM3_BASE + (FDCAN3_TXEVENTFIFO_INDEX << 2)),
+ (uint32_t *)(STM32_CANRAM3_BASE + (FDCAN3_TXFIFOQ_INDEX << 2))
+ }
+};
+
+/* FDCAN3 variable driver state */
+
+static struct stm32_fdcan_s g_fdcan3priv;
+
+#endif /* CONFIG_STM32_FDCAN3 */
+
+/****************************************************************************
+ * 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_STM32_FDCAN_REGDEBUG
+static uint32_t fdcan_getreg(FAR struct stm32_fdcan_s *priv, int offset)
+{
+ FAR 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)
+ {
+ ninfo("...\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 */
+
+ ninfo("[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 */
+
+ ninfo("%08" PRIx32 "->%08" PRIx32 "\n", regaddr, regval);
+ return regval;
+}
+
+#else
+static uint32_t fdcan_getreg(FAR struct stm32_fdcan_s *priv, int offset)
+{
+ FAR 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_STM32_FDCAN_REGDEBUG
+static void fdcan_putreg(FAR struct stm32_fdcan_s *priv, int offset,
+ uint32_t regval)
+{
+ FAR const struct stm32_config_s *config = priv->config;
+ uintptr_t regaddr = config->base + offset;
+
+ /* Show the register value being written */
+
+ ninfo("%08" PRIx32 "->%08" PRIx32 "\n", regaddr, regval);
+
+ /* Write the value */
+
+ putreg32(regval, regaddr);
+}
+
+#else
+static void fdcan_putreg(FAR struct stm32_fdcan_s *priv, int offset,
+ uint32_t regval)
+{
+ FAR 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_STM32_FDCAN_REGDEBUG
+static void fdcan_dumpregs(FAR struct stm32_fdcan_s *priv,
+ FAR const char *msg)
+{
+ FAR const struct stm32_config_s *config = priv->config;
+
+ ninfo("CAN%d Control and Status Registers: %s\n", config->port, msg);
+ ninfo(" Base: %08" PRIx32 "\n", config->base);
+
+ /* CAN control and status registers */
+
+ ninfo(" CCCR: %08" PRIx32 " TEST: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_CCCR_OFFSET),
+ getreg32(config->base + STM32_FDCAN_TEST_OFFSET));
+
+ ninfo(" NBTP: %08" PRIx32 " DBTP: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_NBTP_OFFSET),
+ getreg32(config->base + STM32_FDCAN_DBTP_OFFSET));
+
+ ninfo(" IE: %08" PRIx32 " TIE: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_IE_OFFSET),
+ getreg32(config->base + STM32_FDCAN_TXBTIE_OFFSET));
+
+ ninfo(" ILE: %08" PRIx32 " ILS: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_ILE_OFFSET),
+ getreg32(config->base + STM32_FDCAN_ILS_OFFSET));
+
+ ninfo(" TXBC: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_TXBC_OFFSET));
+}
+#endif
+
+/****************************************************************************
+ * Name: fdcan_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_STM32_FDCAN_REGDEBUG
+static void fdcan_dumprxregs(FAR struct stm32_fdcan_s *priv,
+ FAR const char *msg)
+{
+ FAR const struct stm32_config_s *config = priv->config;
+
+ ninfo("CAN%d Rx Registers: %s\n", config->port, msg);
+ ninfo(" Base: %08" PRIx32 "\n", config->base);
+
+ ninfo(" 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));
+
+ ninfo(" RXF0S: %08" PRIx32 " RXF0A: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_RXF0S_OFFSET),
+ getreg32(config->base + STM32_FDCAN_RXF0A_OFFSET));
+
+ ninfo(" RXF1S: %08" PRIx32 " RXF1A: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_RXF1S_OFFSET),
+ getreg32(config->base + STM32_FDCAN_RXF1A_OFFSET));
+
+ ninfo(" IR: %08" PRIx32 " IE: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_IR_OFFSET),
+ getreg32(config->base + STM32_FDCAN_IE_OFFSET));
+}
+#endif
+
+/****************************************************************************
+ * Name: fdcan_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_STM32_FDCAN_REGDEBUG
+static void fdcan_dumptxregs(FAR struct stm32_fdcan_s *priv,
+ FAR const char *msg)
+{
+ FAR const struct stm32_config_s *config = priv->config;
+
+ ninfo("CAN%d Tx Registers: %s\n", config->port, msg);
+ ninfo(" Base: %08" PRIx32 "\n", config->base);
+
+ ninfo(" PSR: %08" PRIx32 " ECR: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_PSR_OFFSET),
+ getreg32(config->base + STM32_FDCAN_ECR_OFFSET));
+
+ ninfo(" 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));
+
+ ninfo(" TXBTO: %08" PRIx32 " TXBCR: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_TXBTO_OFFSET),
+ getreg32(config->base + STM32_FDCAN_TXBCR_OFFSET));
+
+ ninfo(" TXEFS: %08" PRIx32 " TXEFA: %08" PRIx32 "\n",
+ getreg32(config->base + STM32_FDCAN_TXEFS_OFFSET),
+ getreg32(config->base + STM32_FDCAN_TXEFA_OFFSET));
+
+ ninfo(" 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: fdcan_dumpramlayout
+ *
+ * Description:
+ * Print the layout of the message RAM
+ *
+ * Input Parameters:
+ * priv - A reference to the CAN block status
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_STM32_FDCAN_REGDEBUG
+static void fdcan_dumpramlayout(FAR struct stm32_fdcan_s *priv)
+{
+ FAR const struct stm32_config_s *config = priv->config;
+
+ ninfo(" ******* FDCAN%d Message RAM layout *******\n", config->port);
+ ninfo(" Start # Elmnt Elmnt size (words)\n");
+
+ if (config->nstdfilters > 0)
+ {
+ ninfo("STD filters %p %4d %2d\n",
+ config->msgram.stdfilters,
+ config->nstdfilters,
+ 1);
+ }
+
+ if (config->nextfilters > 0)
+ {
+ ninfo("EXT filters %p %4d %2d\n",
+ config->msgram.extfilters,
+ config->nextfilters,
+ 2);
+ }
+
+ if (config->nrxfifo0 > 0)
+ {
+ ninfo("RX FIFO 0 %p %4d %2d\n",
+ config->msgram.rxfifo0,
+ config->nrxfifo0,
+ config->rxfifo0esize);
+ }
+
+ if (config->nrxfifo1 > 0)
+ {
+ ninfo("RX FIFO 1 %p %4d %2d\n",
+ config->msgram.rxfifo1,
+ config->nrxfifo1,
+ config->rxfifo1esize);
+ }
+
+ if (config->ntxeventfifo > 0)
+ {
+ ninfo("TX EVENT %p %4d %2d\n",
+ config->msgram.txeventfifo,
+ config->ntxeventfifo,
+ config->txeventesize);
+ }
+
+ if (config->ntxfifoq > 0)
+ {
+ ninfo("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(FAR 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_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.
+ *
+ ****************************************************************************/
+
+#if 0 /* not used for now */
+static int
+fdcan_start_busoff_recovery_sequence(FAR 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;
+}
+#endif
+
+/****************************************************************************
+ * 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(FAR struct stm32_fdcan_s *priv)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+ irqstate_t flags;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("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_APB1RSTR1);
+ regval |= RCC_APB1RSTR1_FDCANRST;
+ putreg32(regval, STM32_RCC_APB1RSTR1);
+
+ regval &= ~RCC_APB1RSTR1_FDCANRST;
+ putreg32(regval, STM32_RCC_APB1RSTR1);
+ 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(FAR struct stm32_fdcan_s *priv)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ int ret = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("FDCAN%d\n", config->port);
+
+ /* FDCAN hardware initialization */
+
+ ret = fdcan_hw_initialize(priv);
+ if (ret < 0)
+ {
+ nerr("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, priv);
+ if (ret < 0)
+ {
+ nerr("ERROR: Failed to attach FDCAN%d line 0 IRQ (%d)",
+ config->port, config->irq0);
+ return ret;
+ }
+
+ ret = irq_attach(config->irq1, fdcan_interrupt, priv);
+ if (ret < 0)
+ {
+ nerr("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(FAR struct stm32_fdcan_s *priv)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("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_rx0int
+ *
+ * Description:
+ * Call to enable or disable RX0 interrupts.
+ *
+ * Input Parameters:
+ * priv - reference to the private CAN driver state structure
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+static void fdcan_rx0int(FAR struct stm32_fdcan_s *priv, bool enable)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("CAN%" PRIu8 "RX0 enable: %d\n", config->port, enable);
+
+ /* Enable/disable the FIFO 0 message pending interrupt */
+
+ regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET);
+
+ if (enable)
+ {
+ regval |= FDCAN_RXFIFO0_INTS;
+ }
+ else
+ {
+ regval &= ~FDCAN_RXFIFO0_INTS;
+ }
+
+ fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval);
+}
+
+/****************************************************************************
+ * Name: fdcan_rx1int
+ *
+ * Description:
+ * Call to enable or disable RX1 interrupts.
+ *
+ * Input Parameters:
+ * priv - reference to the private CAN driver state structure
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+static void fdcan_rx1int(FAR struct stm32_fdcan_s *priv, bool enable)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("CAN%" PRIu8 "RX1 enable: %d\n", config->port, enable);
+
+ /* Enable/disable the FIFO 1 message pending interrupt */
+
+ regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET);
+
+ if (enable)
+ {
+ regval |= FDCAN_RXFIFO1_INTS;
+ }
+ else
+ {
+ regval &= ~FDCAN_RXFIFO1_INTS;
+ }
+
+ 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(FAR struct stm32_fdcan_s *priv, bool enable)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("CAN%" PRIu8 "TX enable: %d\n", config->port, enable);
+
+ /* Enable/disable the receive interrupts */
+
+ regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET);
+
+ if (enable)
+ {
+ regval |= FDCAN_TXFIFOQ_INTS;
+ }
+ else
+ {
+ regval &= ~FDCAN_TXFIFOQ_INTS;
+ }
+
+ fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval);
+}
+
+#ifdef CONFIG_NET_CAN_ERRORS
+/****************************************************************************
+ * Name: fdcan_txint
+ *
+ * Description:
+ * Call to enable or disable CAN SCE interrupts.
+ *
+ * Input Parameters:
+ * priv - reference to the private CAN driver state structure
+ *
+ * Returned Value:
+ * None
+ *
+ ****************************************************************************/
+
+static void fdcan_errint(FAR struct stm32_fdcan_s *priv, bool enable)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("CAN%" PRIu8 "ERR enable: %d\n", config->port, enable);
+
+ /* Enable/disable the transmit mailbox interrupt */
+
+ regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET);
+ if (enable)
+ {
+ regval |= FDCAN_ANYERR_INTS;
+ }
+ else
+ {
+ regval &= ~FDCAN_ANYERR_INTS;
+ }
+
+ fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval);
+}
+#endif
+
+/****************************************************************************
+ * 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(FAR struct stm32_fdcan_s *priv)
+{
+ struct can_frame *frame = NULL;
+ FAR const struct stm32_config_s *config = NULL;
+ FAR volatile uint32_t *txbuffer = NULL;
+ FAR const uint8_t *src = NULL;
+ FAR 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(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ frame = (struct can_frame *)priv->dev.d_buf;
+
+ ninfo("CAN%" PRIu8 " ID: %" PRIu32 " DLC: %" PRIu8 "\n",
+ config->port, (uint32_t)frame->can_id, frame->can_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.
+ */
+
+#ifdef CONFIG_CAN_EXTID
+ if (frame->can_id & CAN_EFF_FLAG)
+ {
+ DEBUGASSERT(frame->can_id < (1 << 29));
+
+ regval = BUFFER_R0_EXTID(frame->can_id) | BUFFER_R0_XTD;
+ }
+ else
+#endif
+ {
+ DEBUGASSERT(frame->can_id < (1 << 11));
+
+ regval = BUFFER_R0_STDID(frame->can_id);
+ }
+
+ if (frame->can_id & CAN_RTR_FLAG)
+ {
+ regval |= BUFFER_R0_RTR;
+ }
+
+ txbuffer[0] = regval;
+
+ /* Format word T1:
+ * Data Length Code (DLC) - Value from message structure
+ * Event FIFO Control (EFC) - Do not store events.
+ * Message Marker (MM) - Always zero
+ */
+
+ txbuffer[1] = BUFFER_R1_DLC(frame->can_dlc);
+
+ /* Followed by the amount of data corresponding to the DLC (T2..) */
+
+ dest = (FAR uint32_t *)&txbuffer[2];
+ src = frame->data;
+ nbytes = fdcan_dlc2bytes(priv, frame->can_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(FAR struct stm32_fdcan_s *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_rx0interrupt_work
+ *
+ * Description:
+ * CAN RX FIFO 0 worker
+ *
+ ****************************************************************************/
+
+static void fdcan_rx0interrupt_work(FAR void *arg)
+{
+ FAR struct stm32_fdcan_s *priv = (FAR struct stm32_fdcan_s *)arg;
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+ unsigned int nelem = 0;
+ unsigned int ndx = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ /* Clear the RX FIFO0 new message interrupt */
+
+ fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_INT_RF0N);
+
+ 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)
+ {
+ nerr("ERROR: Message lost: %08" PRIx32 "\n", regval);
+ }
+ else
+ {
+ fdcan_receive(priv,
+ config->msgram.rxfifo0 +
+ (ndx * priv->config->rxfifo0esize),
+ priv->config->rxfifo0esize);
+
+#ifdef CONFIG_NET_CAN_ERRORS
+ /* Turning back on all configured RX error interrupts */
+
+ regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET);
+ regval |= FDCAN_RXERR_INTS;
+ fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval);
+#endif
+ }
+
+ /* Acknowledge reading the FIFO entry */
+
+ fdcan_putreg(priv, STM32_FDCAN_RXF0A_OFFSET, ndx);
+ }
+
+ /* Re-enable CAN RX interrupts */
+
+ fdcan_rx0int(priv, true);
+}
+
+/****************************************************************************
+ * Name: fdcan_rx1interrupt_work
+ *
+ * Description:
+ * CAN RX FIFO 1 worker
+ *
+ ****************************************************************************/
+
+static void fdcan_rx1interrupt_work(FAR void *arg)
+{
+ FAR struct stm32_fdcan_s *priv = (FAR struct stm32_fdcan_s *)arg;
+ FAR const struct stm32_config_s *config = NULL;
+ uint32_t regval = 0;
+ unsigned int nelem = 0;
+ unsigned int ndx = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ /* Clear the RX FIFO1 new message interrupt */
+
+ fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_INT_RF1N);
+
+ /* 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)
+ {
+ /* 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)
+ {
+ nerr("ERROR: Message lost: %08" PRIx32 "\n", regval);
+ }
+ else
+ {
+ fdcan_receive(priv,
+ config->msgram.rxfifo1 +
+ (ndx * priv->config->rxfifo1esize),
+ priv->config->rxfifo1esize);
+
+#ifdef CONFIG_NET_CAN_ERRORS
+ /* Turning back on all configured RX error interrupts */
+
+ regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET);
+ regval |= FDCAN_RXERR_INTS;
+ fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval);
+#endif
+ }
+
+ /* Acknowledge reading the FIFO entry */
+
+ fdcan_putreg(priv, STM32_FDCAN_RXF1A_OFFSET, ndx);
+ }
+
+ /* Re-enable CAN RX interrupts */
+
+ fdcan_rx1int(priv, true);
+}
+
+/****************************************************************************
+ * Name: fdcan_txdone_work
+ ****************************************************************************/
+
+static void fdcan_txdone_work(FAR void *arg)
+{
+ FAR struct stm32_fdcan_s *priv = (FAR struct stm32_fdcan_s *)arg;
+
+ fdcan_txdone(priv);
+
+ /* There should be space for a new TX in any event. Poll the network for
+ * new XMIT data
+ */
+
+ net_lock();
+ devif_timer(&priv->dev, 0, fdcan_txpoll);
+ net_unlock();
+}
+
+/****************************************************************************
+ * Name: fdcan_txdone
+ ****************************************************************************/
+
+static void fdcan_txdone(FAR struct stm32_fdcan_s *priv)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ unsigned int ndx = 0;
+ uint32_t regval = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ /* Clear the pending TX completion interrupt (and all
+ * other TX-related interrupts)
+ */
+
+ fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_TXFIFOQ_INTS);
+
+ /* 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. */
+
+ NETDEV_TXDONE(&priv->dev);
+ }
+ }
+
+#ifdef CONFIG_NET_CAN_ERRORS
+ /* Turning back on PEA and PED error interrupts */
+
+ regval = fdcan_getreg(priv, STM32_FDCAN_IE_OFFSET);
+ regval |= (FDCAN_INT_PEA | FDCAN_INT_PED);
+ fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, regval);
+#endif
+
+ /* Re-enable TX interrupts */
+
+ fdcan_txint(priv, true);
+}
+
+#ifdef CONFIG_NET_CAN_ERRORS
+/****************************************************************************
+ * Name: fdcan_error_work
+ ****************************************************************************/
+
+static void fdcan_error_work(FAR void *arg)
+{
+ FAR struct stm32_fdcan_s *priv = (FAR struct stm32_fdcan_s *)arg;
+ uint32_t pending = 0;
+ uint32_t ir = 0;
+ uint32_t ie = 0;
+ uint32_t psr = 0;
+
+ /* 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 common errors */
+
+ if ((pending & FDCAN_CMNERR_INTS) != 0)
+ {
+ /* 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)
+ {
+ ie &= ~(FDCAN_INT_PEA | FDCAN_INT_PED);
+ fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie);
+ }
+
+ /* Clear the error indications */
+
+ fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_CMNERR_INTS);
+ }
+
+ /* Check for transmission errors */
+
+ if ((pending & FDCAN_TXERR_INTS) != 0)
+ {
+ /* 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)
+ {
+ /* 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);
+ }
+
+ /* Report errors */
+
+ fdcan_error(priv, pending & FDCAN_ANYERR_INTS);
+
+ /* Re-enable ERROR interrupts */
+
+ fdcan_errint(priv, true);
+}
+
+/****************************************************************************
+ * 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
+ *
+ ****************************************************************************/
+
+static void fdcan_error(FAR struct stm32_fdcan_s *priv, uint32_t status)
+{
+ struct can_frame *frame = (struct can_frame *)priv->rxdesc;
+ uint32_t psr = 0;
+ uint16_t errbits = 0;
+ uint8_t data[CAN_ERR_DLC];
+
+ DEBUGASSERT(priv != NULL);
+
+ /* 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_ERR_CRTL_RX_PASSIVE | CAN_ERR_CRTL_TX_PASSIVE);
+ }
+
+ if ((psr & FDCAN_PSR_EW) != 0)
+ {
+ data[1] |= (CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING);
+ }
+
+ if ((status & (FDCAN_INT_EP | FDCAN_INT_EW)) != 0)
+ {
+ /* "Error Passive" or "Error Warning" status changed */
+
+ errbits |= CAN_ERR_CRTL;
+ }
+
+ 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_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_STUFF;
+ }
+
+ if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_FORM_ERROR)) != 0)
+ {
+ /* Format Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_FORM;
+ }
+
+ if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_ACK_ERROR)) != 0)
+ {
+ /* Acknowledge Error */
+
+ errbits |= CAN_ERR_ACK;
+ }
+
+ if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_BIT0_ERROR)) != 0)
+ {
+ /* Bit0 Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_BIT0;
+ }
+
+ if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_BIT1_ERROR)) != 0)
+ {
+ /* Bit1 Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_BIT1;
+ }
+
+ if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_CRC_ERROR)) != 0)
+ {
+ /* Receive CRC Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[3] |= (CAN_ERR_PROT_LOC_CRCSEQ | CAN_ERR_PROT_LOC_CRCDEL);
+ }
+
+ if ((psr & FDCAN_PSR_LEC(FDCAN_PSR_EC_NO_CHANGE)) != 0)
+ {
+ /* No Change in Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_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_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_STUFF;
+ }
+
+ if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_FORM_ERROR)) != 0)
+ {
+ /* Format Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_FORM;
+ }
+
+ if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_ACK_ERROR)) != 0)
+ {
+ /* Acknowledge Error */
+
+ errbits |= CAN_ERR_ACK;
+ }
+
+ if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_BIT0_ERROR)) != 0)
+ {
+ /* Bit0 Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_BIT0;
+ }
+
+ if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_BIT1_ERROR)) != 0)
+ {
+ /* Bit1 Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_BIT1;
+ }
+
+ if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_CRC_ERROR)) != 0)
+ {
+ /* Receive CRC Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[3] |= (CAN_ERR_PROT_LOC_CRCSEQ | CAN_ERR_PROT_LOC_CRCDEL);
+ }
+
+ if ((psr & FDCAN_PSR_DLEC(FDCAN_PSR_EC_NO_CHANGE)) != 0)
+ {
+ /* No Change in Error */
+
+ errbits |= CAN_ERR_PROT;
+ data[2] |= CAN_ERR_PROT_UNSPEC;
+ }
+ }
+ }
+
+ if ((status & FDCAN_INT_BO) != 0)
+ {
+ /* Bus_Off Status changed */
+
+ if ((psr & FDCAN_PSR_BO) != 0)
+ {
+ errbits |= CAN_ERR_BUSOFF;
+ }
+ else
+ {
+ errbits |= CAN_ERR_RESTARTED;
+ }
+ }
+
+ if ((status & (FDCAN_INT_RF0L | FDCAN_INT_RF1L)) != 0)
+ {
+ /* Receive FIFO 0/1 Message Lost
+ * Receive FIFO 1 Message Lost
+ */
+
+ errbits |= CAN_ERR_CRTL;
+ data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
+ }
+
+ if ((status & FDCAN_INT_TEFL) != 0)
+ {
+ /* Tx Event FIFO Element Lost */
+
+ errbits |= CAN_ERR_CRTL;
+ data[1] |= CAN_ERR_CRTL_TX_OVERFLOW;
+ }
+
+ if ((status & FDCAN_INT_TOO) != 0)
+ {
+ /* Timeout Occurred */
+
+ errbits |= CAN_ERR_TXTIMEOUT;
+ }
+
+ if ((status & (FDCAN_INT_MRAF | FDCAN_INT_ELO)) != 0)
+ {
+ /* Message RAM Access Failure
+ * Error Logging Overflow
+ */
+
+ errbits |= CAN_ERR_CRTL;
+ data[1] |= CAN_ERR_CRTL_UNSPEC;
+ }
+
+ if (errbits != 0)
+ {
+ nerr("ERROR: errbits = %08" PRIx16 "\n", errbits);
+
+ /* Copy frame */
+
+ frame->can_id = errbits;
+ frame->can_dlc = CAN_ERR_DLC;
+
+ memcpy(frame->data, data, CAN_ERR_DLC);
+
+ /* Copy the buffer pointer to priv->dev.. Set amount of data
+ * in priv->dev.d_len
+ */
+
+ priv->dev.d_len = sizeof(struct can_frame);
+ priv->dev.d_buf = (uint8_t *)frame;
+
+ /* Send to socket interface */
+
+ NETDEV_ERRORS(&priv->dev);
+
+ can_input(&priv->dev);
+
+ /* Point the packet buffer back to the next Tx buffer that will be
+ * used during the next write. If the write queue is full, then
+ * this will point at an active buffer, which must not be written
+ * to. This is OK because devif_poll won't be called unless the
+ * queue is not full.
+ */
+
+ priv->dev.d_buf = (uint8_t *)priv->txdesc;
+ }
+}
+#endif /* CONFIG_NET_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(FAR struct stm32_fdcan_s *priv,
+ FAR volatile uint32_t *rxbuffer,
+ unsigned long nwords)
+{
+ FAR struct can_frame *frame = (struct can_frame *)priv->rxdesc;
+ uint32_t regval = 0;
+
+ fdcan_dumprxregs(dev->cd_priv, "Before receive");
+
+ /* Format the CAN header */
+
+ /* Work R0 contains the CAN ID */
+
+ regval = *rxbuffer++;
+
+ /* Extract the RTR bit */
+
+ if ((regval & BUFFER_R0_RTR) != 0)
+ {
+ frame->can_id |= CAN_RTR_FLAG;
+ }
+
+#ifdef CONFIG_CAN_EXTID
+ if ((regval & BUFFER_R0_XTD) != 0)
+ {
+ /* Save the extended ID of the newly received message */
+
+ frame->can_id = (regval & BUFFER_R0_EXTID_MASK) >>
+ BUFFER_R0_EXTID_SHIFT;
+ frame->can_id |= CAN_EFF_FLAG;
+ }
+ else
+ {
+ frame->can_id = (regval & BUFFER_R0_STDID_MASK) >>
+ BUFFER_R0_STDID_SHIFT;
+ frame->can_id &= ~CAN_EFF_FLAG;
+ }
+#else
+ if ((regval & BUFFER_R0_XTD) != 0)
+ {
+ /* Drop any messages with extended IDs */
+
+ return;
+ }
+
+ /* Save the standard ID of the newly received message */
+
+ frame->can_id = (regval & BUFFER_R0_STDID_MASK) >> BUFFER_R0_STDID_SHIFT;
+#endif
+
+ /* Word R1 contains the DLC and timestamp */
+
+ regval = *rxbuffer++;
+
+ frame->can_dlc = (regval & BUFFER_R1_DLC_MASK) >> BUFFER_R1_DLC_SHIFT;
+
+ /* Save the message data */
+
+ memcpy(frame->data, (void *)rxbuffer, frame->can_dlc);
+
+ /* Copy the buffer pointer to priv->dev.. Set amount of data
+ * in priv->dev.d_len
+ */
+
+ priv->dev.d_len = sizeof(struct can_frame);
+ priv->dev.d_buf = (uint8_t *)frame;
+
+ /* Send to socket interface */
+
+ NETDEV_RXPACKETS(&priv->dev);
+
+ can_input(&priv->dev);
+
+ /* Point the packet buffer back to the next Tx buffer that will be
+ * used during the next write. If the write queue is full, then
+ * this will point at an active buffer, which must not be written
+ * to. This is OK because devif_poll won't be called unless the
+ * queue is not full.
+ */
+
+ priv->dev.d_buf = (uint8_t *)priv->txdesc;
+}
+
+/****************************************************************************
+ * Name: fdcan_interrupt
+ *
+ * Description:
+ * Common FDCAN interrupt handler
+ *
+ * irq - The IRQ number of the interrupt.
+ * context - The register state save array at the time of the interrupt.
+ *
+ * Returned Value:
+ * Zero on success; a negated errno on failure
+ *
+ ****************************************************************************/
+
+static int fdcan_interrupt(int irq, void *context, FAR void *arg)
+{
+ FAR struct stm32_fdcan_s *priv = (FAR struct stm32_fdcan_s *)arg;
+ uint32_t pending = 0;
+
+ DEBUGASSERT(priv != NULL);
+
+ /* Get the set of pending interrupts. */
+
+ pending = fdcan_getreg(priv, STM32_FDCAN_IR_OFFSET);
+
+#ifdef CONFIG_NET_CAN_ERRORS
+ /* Check for any errors */
+
+ if ((pending & FDCAN_ANYERR_INTS) != 0)
+ {
+ /* Disable further CAN ERROR interrupts and schedule to perform the
+ * interrupt processing on the worker thread
+ */
+
+ fdcan_errint(priv, false);
+ work_queue(CANWORK, &priv->irqwork, fdcan_error_work, priv, 0);
+ }
+#endif
+
+ /* Check for successful completion of a transmission */
+
+ if ((pending & FDCAN_INT_TC) != 0)
+ {
+ /* Disable further TX CAN interrupts. here can be no race
+ * condition here.
+ */
+
+ fdcan_txint(priv, false);
+ work_queue(CANWORK, &priv->irqwork, fdcan_txdone_work, priv, 0);
+ }
+ else if ((pending & FDCAN_TXFIFOQ_INTS) != 0)
+ {
+ /* Clear unhandled TX events */
+
+ fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_TXFIFOQ_INTS);
+ }
+
+ if (pending & FDCAN_INT_RF1N)
+ {
+ /* Disable further CAN RX interrupts and schedule to perform the
+ * interrupt processing on the worker thread
+ */
+
+ fdcan_rx1int(priv, false);
+ work_queue(CANWORK, &priv->irqwork,
+ fdcan_rx1interrupt_work, priv, 0);
+ }
+
+ /* Clear the RX FIFO0 new message interrupt */
+
+ if (pending & FDCAN_INT_RF0N)
+ {
+ /* Disable further CAN RX interrupts and schedule to perform the
+ * interrupt processing on the worker thread
+ */
+
+ fdcan_rx0int(priv, false);
+ work_queue(CANWORK, &priv->irqwork,
+ fdcan_rx0interrupt_work, priv, 0);
+ }
+
+ 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)
+{
+ FAR const struct stm32_config_s *config = NULL;
+ FAR volatile uint32_t *msgram = NULL;
+ uint32_t regval = 0;
+ uint32_t cntr = 0;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ ninfo("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_STM32_FDCAN_QUEUE_MODE
+ regval |= FDCAN_TXBC_TFQM;
+#else
+ regval &= ~FDCAN_TXBC_TFQM;
+#endif
+ fdcan_putreg(priv, STM32_FDCAN_TXBC_OFFSET, regval);
+
+#ifdef STM32_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 */
+
+ /* 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;
+}
+
+/****************************************************************************
+ * Function: fdcan_ifup
+ *
+ * Description:
+ * NuttX Callback: Bring up the Ethernet interface when an IP address is
+ * provided
+ *
+ * Input Parameters:
+ * dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static int fdcan_ifup(struct net_driver_s *dev)
+{
+ FAR struct stm32_fdcan_s *priv =
+ (FAR struct stm32_fdcan_s *)dev->d_private;
+ FAR const struct stm32_config_s *config = NULL;
+
+ DEBUGASSERT(priv);
+ config = priv->config;
+ DEBUGASSERT(config);
+
+ /* Setup CAN */
+
+ fdcan_setup(priv);
+
+ /* Enable interrupts */
+
+ fdcan_rx0int(priv, true);
+ fdcan_rx1int(priv, true);
+ fdcan_txint(priv, true);
+#ifdef CONFIG_NET_CAN_ERRORS
+ fdcan_errint(priv, true);
+#endif
+
+ /* Enable the interrupts at the NVIC */
+
+ up_enable_irq(config->irq0);
+ up_enable_irq(config->irq1);
+
+ priv->bifup = true;
+
+ priv->txdesc = (struct can_frame *)priv->tx_pool;
+ priv->rxdesc = (struct can_frame *)priv->rx_pool;
+
+ priv->dev.d_buf = (uint8_t *)priv->txdesc;
+
+ return OK;
+}
+
+/****************************************************************************
+ * Function: fdcan_ifdown
+ *
+ * Description:
+ * NuttX Callback: Stop the interface.
+ *
+ * Input Parameters:
+ * dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+static int fdcan_ifdown(struct net_driver_s *dev)
+{
+ FAR struct stm32_fdcan_s *priv =
+ (FAR struct stm32_fdcan_s *)dev->d_private;
+
+ /* Disable CAN interrupts */
+
+ fdcan_shutdown(priv);
+
+ /* Reset CAN */
+
+ fdcan_reset(priv);
+
+ return OK;
+}
+
+/****************************************************************************
+ * Function: fdcan_txpoll
+ *
+ * Description:
+ * The transmitter is available, check if the network has any outgoing
+ * packets ready to send. This is a callback from devif_poll().
+ * devif_poll() may be called:
+ *
+ * 1. When the preceding TX packet send is complete,
+ * 2. When the preceding TX packet send timesout and the interface is reset
+ * 3. During normal TX polling
+ *
+ * Input Parameters:
+ * dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ * OK on success; a negated errno on failure
+ *
+ * Assumptions:
+ * May or may not be called from an interrupt handler. In either case,
+ * global interrupts are disabled, either explicitly or indirectly through
+ * interrupt handling logic.
+ *
+ ****************************************************************************/
+
+static int fdcan_txpoll(struct net_driver_s *dev)
+{
+ FAR struct stm32_fdcan_s *priv =
+ (FAR struct stm32_fdcan_s *)dev->d_private;
+
+ /* If the polling resulted in data that should be sent out on the network,
+ * the field d_len is set to a value > 0.
+ */
+
+ if (priv->dev.d_len > 0)
+ {
+ if (!devif_loopback(&priv->dev))
+ {
+ fdcan_txdone(priv);
+
+ /* Send the packet */
+
+ fdcan_send(priv);
+
+ /* Check if there is room in the device to hold another packet. If
+ * not, return a non-zero value to terminate the poll.
+ */
+
+ if (fdcan_txready(priv) == false)
+ {
+ return -EBUSY;
+ }
+ }
+ }
+
+ /* If zero is returned, the polling will continue until all connections
+ * have been examined.
+ */
+
+ return 0;
+}
+
+/****************************************************************************
+ * Function: fdcan_txavail_work
+ *
+ * Description:
+ * Perform an out-of-cycle poll on the worker thread.
+ *
+ * Input Parameters:
+ * arg - Reference to the NuttX driver state structure (cast to void*)
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * Called on the higher priority worker thread.
+ *
+ ****************************************************************************/
+
+static void fdcan_txavail_work(FAR void *arg)
+{
+ FAR struct stm32_fdcan_s *priv = (FAR struct stm32_fdcan_s *)arg;
+
+ /* Ignore the notification if the interface is not yet up */
+
+ net_lock();
+ if (priv->bifup)
+ {
+ /* Check if there is room in the hardware to hold another outgoing
+ * packet.
+ */
+
+ if (fdcan_txready(priv))
+ {
+ /* No, there is space for another transfer. Poll the network for
+ * new XMIT data.
+ */
+
+ devif_timer(&priv->dev, 0, fdcan_txpoll);
+ }
+ }
+
+ net_unlock();
+}
+
+/****************************************************************************
+ * Function: fdcan_txavail
+ *
+ * Description:
+ * Driver callback invoked when new TX data is available. This is a
+ * stimulus perform an out-of-cycle poll and, thereby, reduce the TX
+ * latency.
+ *
+ * Input Parameters:
+ * dev - Reference to the NuttX driver state structure
+ *
+ * Returned Value:
+ * None
+ *
+ * Assumptions:
+ * Called in normal user mode
+ *
+ ****************************************************************************/
+
+static int fdcan_txavail(struct net_driver_s *dev)
+{
+ FAR struct stm32_fdcan_s *priv =
+ (FAR struct stm32_fdcan_s *)dev->d_private;
+
+ /* Is our single work structure available? It may not be if there are
+ * pending interrupt actions and we will have to ignore the Tx
+ * availability action.
+ */
+
+ if (work_available(&priv->pollwork))
+ {
+ /* Schedule to serialize the poll on the worker thread. */
+
+ fdcan_txavail_work(priv);
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Function: fdcan_ioctl
+ *
+ * Description:
+ * PHY ioctl command handler
+ *
+ * Input Parameters:
+ * dev - Reference to the NuttX driver state structure
+ * cmd - ioctl command
+ * arg - Argument accompanying the command
+ *
+ * Returned Value:
+ * Zero (OK) on success; a negated errno value on failure.
+ *
+ * Assumptions:
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NETDEV_IOCTL
+static int fdcan_netdev_ioctl(struct net_driver_s *dev, int cmd,
+ unsigned long arg);
+{
+ FAR struct stm32_fdcan_s *priv =
+ (FAR struct stm32_fdcan_s *)dev->d_private;
+ int ret = OK;
+
+ DEBUGASSERT(priv);
+
+ switch (cmd)
+ {
+ /* TODO */
+
+ default:
+ ret = -ENOTTY;
+ break;
+ }
+
+ return ret;
+}
+#endif /* CONFIG_NETDEV_IOCTL */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: stm32_cansockinitialize
+ *
+ * Description:
+ * Initialize the selected FDCAN port as CAN socket 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)
+{
+ FAR struct stm32_fdcan_s *priv = NULL;
+ FAR const struct stm32_config_s *config = NULL;
+ int ret = OK;
+
+ ninfo("FDCAN%d\n", port);
+
+ /* Select FDCAN peripheral to be initialized */
+
+#ifdef CONFIG_STM32_FDCAN1
+ if (port == FDCAN1)
+ {
+ /* Select the FDCAN1 device structure */
+
+ priv = &g_fdcan1priv;
+ config = &g_fdcan1const;
+ }
+ else
+#endif
+#ifdef CONFIG_STM32_FDCAN2
+ if (port == FDCAN2)
+ {
+ /* Select the FDCAN2 device structure */
+
+ priv = &g_fdcan2priv;
+ config = &g_fdcan2const;
+ }
+ else
+#endif
+#ifdef CONFIG_STM32_FDCAN3
+ if (port == FDCAN3)
+ {
+ /* Select the FDCAN3 device structure */
+
+ priv = &g_fdcan3priv;
+ config = &g_fdcan3const;
+ }
+ else
+#endif
+ {
+ nerr("ERROR: Unsupported port %d\n", port);
+ ret = -EINVAL;
+ goto errout;
+ }
+
+ /* 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.
+ */
+
+ priv->nbtp = config->nbtp;
+ priv->dbtp = config->dbtp;
+
+ /* Initialize the driver structure */
+
+ priv->dev.d_ifup = fdcan_ifup;
+ priv->dev.d_ifdown = fdcan_ifdown;
+ priv->dev.d_txavail = fdcan_txavail;
+#ifdef CONFIG_NETDEV_IOCTL
+ priv->dev.d_ioctl = fdcan_netdev_ioctl;
+#endif
+ priv->dev.d_private = priv;
+
+ /* Put the interface in the down state. This usually amounts to resetting
+ * the device and/or calling fdcan_ifdown().
+ */
+
+ ninfo("callbacks done\n");
+
+ fdcan_ifdown(&priv->dev);
+
+ /* Register the device with the OS so that socket IOCTLs can be performed */
+
+ ret = netdev_register(&priv->dev, NET_LL_CAN);
+
+errout:
+ return ret;
+}