arch/arm/src/stm32h5/stm32_fdcan.c (1,917 lines of code) (raw):

/**************************************************************************** * 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 <nuttx/config.h> #include <stdio.h> #include <sys/types.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <errno.h> #include <debug.h> #include <assert.h> #include <arch/board/board.h> #include <nuttx/irq.h> #include <nuttx/arch.h> #include <nuttx/semaphore.h> #include <nuttx/can/can.h> #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; }