| /**************************************************************************** |
| * arch/arm/src/stm32h7/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 |
| * |
| * 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 <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <debug.h> |
| #include <errno.h> |
| |
| #include <nuttx/can.h> |
| #include <nuttx/wdog.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/net/netdev.h> |
| #include <nuttx/net/can.h> |
| #include <netpacket/can.h> |
| |
| #if defined(CONFIG_NET_CAN_RAW_TX_DEADLINE) || defined(CONFIG_NET_TIMESTAMP) |
| #include <sys/time.h> |
| #endif |
| |
| #include <arch/board/board.h> |
| |
| #include "arm_internal.h" |
| #include "chip.h" |
| #include "stm32.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define FDCAN_CMNERR_INTS (FDCAN_IE_MRAFE | FDCAN_IE_TOOE | FDCAN_IE_EPE | \ |
| FDCAN_IE_BOE | FDCAN_IE_WDIE | FDCAN_IE_PEAE | \ |
| FDCAN_IE_PEDE) |
| |
| #define FDCAN_RXERR_INTS (FDCAN_IE_RF0LE | FDCAN_IE_RF1LE) |
| |
| #define FDCAN_TXERR_INTS (FDCAN_IE_TEFLE | FDCAN_IE_PEAE | FDCAN_IE_PEDE) |
| |
| /* Common-, TX- and RX-Error-Mask */ |
| |
| #define FDCAN_ANYERR_INTS (FDCAN_CMNERR_INTS | FDCAN_RXERR_INTS | FDCAN_TXERR_INTS) |
| |
| #define CAN_ERROR_PASSIVE_THRESHOLD 128 |
| |
| #define CAN_ERROR_WARNING_THRESHOLD 96 |
| |
| /* General Configuration ****************************************************/ |
| |
| #if !defined(CONFIG_SCHED_WORKQUEUE) |
| # error Work queue support is required; HPWORK is recommended |
| #else |
| |
| /* If processing is not done at the interrupt level, then work queue support |
| * is required. |
| * |
| * The high-priority work queue is suggested in order to minimize latency of |
| * critical Rx/Tx transactions on the CAN bus. |
| */ |
| |
| # if defined(CONFIG_STM32H7_FDCAN_HPWORK) |
| # define CANWORK HPWORK |
| # elif defined(CONFIG_STM32H7_FDCAN_LPWORK) |
| # define CANWORK LPWORK |
| # else |
| # define CANWORK LPWORK |
| # endif |
| #endif |
| |
| #ifdef CONFIG_NET_CAN_RAW_TX_DEADLINE |
| # define TX_TIMEOUT_WQ |
| #endif |
| |
| /* Message RAM Configuration ************************************************/ |
| |
| #define WORD_LENGTH 4U |
| |
| /* Define number of Rx / Tx elements in message RAM; note that elements are |
| * given sizes in number of words (4-byte chunks) |
| * |
| * Up to 64 Rx elements and 32 Tx elements may be configured per interface |
| * |
| * Note there are a total of 2560 words available shared between all FDCAN |
| * interfaces for Rx, Tx, and filter storage |
| */ |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| # define FIFO_ELEMENT_SIZE 18 /* size (in Words) of a FIFO element in message RAM (CANFD_MTU / 4) */ |
| # define NUM_RX_FIFO0 14 /* 14 elements max for RX FIFO0 */ |
| # define NUM_RX_FIFO1 0 /* No elements for RX FIFO1 */ |
| # define NUM_TX_FIFO 7 /* 7 elements max for TX FIFO */ |
| #else |
| # define FIFO_ELEMENT_SIZE 4 /* size (in Words) of a FIFO element in message RAM (CAN_MTU / 4) */ |
| # define NUM_RX_FIFO0 64 /* 64 elements max for RX FIFO0 */ |
| # define NUM_RX_FIFO1 0 /* No elements for RX FIFO1 */ |
| # define NUM_TX_FIFO 32 /* 32 elements max for TX FIFO */ |
| #endif |
| |
| /* Intermediate message buffering *******************************************/ |
| |
| #define POOL_SIZE 1 |
| |
| #if defined(CONFIG_NET_CAN_RAW_TX_DEADLINE) || defined(CONFIG_NET_TIMESTAMP) |
| #define MSG_DATA sizeof(struct timeval) |
| #else |
| #define MSG_DATA 0 |
| #endif |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| # define FRAME_TYPE struct canfd_frame |
| #else |
| # define FRAME_TYPE struct can_frame |
| #endif |
| |
| /* CAN Clock Configuration **************************************************/ |
| |
| #define STM32_FDCANCLK STM32_HSE_FREQUENCY |
| #define CLK_FREQ STM32_FDCANCLK |
| #define PRESDIV_MAX 256 |
| |
| /* Interrupts ***************************************************************/ |
| |
| #define FDCAN_IR_MASK 0x3fcfffff /* Mask of all non-reserved bits in FDCAN_IR */ |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* CAN ID word, as defined by FDCAN device (Note xtd/rtr/esi bit positions) */ |
| |
| union can_id_u |
| { |
| volatile uint32_t can_id; |
| struct |
| { |
| volatile uint32_t extid : 29; |
| volatile uint32_t resex : 3; |
| }; |
| struct |
| { |
| volatile uint32_t res : 18; |
| volatile uint32_t stdid : 11; |
| volatile uint32_t rtr : 1; |
| volatile uint32_t xtd : 1; |
| volatile uint32_t esi : 1; |
| }; |
| }; |
| |
| /* Union of 4 bytes as 1 register */ |
| |
| union payload_u |
| { |
| volatile uint32_t word; |
| struct |
| { |
| volatile uint32_t b00 : 8; |
| volatile uint32_t b01 : 8; |
| volatile uint32_t b02 : 8; |
| volatile uint32_t b03 : 8; |
| }; |
| }; |
| |
| /* Message RAM Structures ***************************************************/ |
| |
| /* Rx FIFO Element Header -- RM0433 pg 2536 */ |
| |
| union rx_fifo_header_u |
| { |
| struct |
| { |
| volatile uint32_t w0; |
| volatile uint32_t w1; |
| }; |
| |
| struct |
| { |
| /* First word */ |
| |
| union can_id_u id; |
| |
| /* Second word */ |
| |
| volatile uint32_t rxts : 16; /* Rx timestamp */ |
| volatile uint32_t dlc : 4; /* Data length code */ |
| volatile uint32_t brs : 1; /* Bitrate switching */ |
| volatile uint32_t fdf : 1; /* FD frame */ |
| volatile uint32_t res : 2; /* Reserved for Tx Event */ |
| volatile uint32_t fidx : 7; /* Filter index */ |
| volatile uint32_t anmf : 1; /* Accepted non-matching frame */ |
| }; |
| }; |
| |
| /* Tx FIFO Element Header -- RM0433 pg 2538 */ |
| |
| union tx_fifo_header_u |
| { |
| struct |
| { |
| volatile uint32_t w0; |
| volatile uint32_t w1; |
| }; |
| |
| struct |
| { |
| /* First word */ |
| |
| union can_id_u id; |
| |
| /* Second word */ |
| |
| volatile uint32_t res1 : 16; /* Reserved for Tx Event timestamp */ |
| volatile uint32_t dlc : 4; /* Data length code */ |
| volatile uint32_t brs : 1; /* Bitrate switching */ |
| volatile uint32_t fdf : 1; /* FD frame */ |
| volatile uint32_t res2 : 1; /* Reserved for Tx Event */ |
| volatile uint32_t efc : 1; /* Event FIFO control */ |
| volatile uint32_t mm : 8; /* Message marker (user data; copied to Tx Event) */ |
| }; |
| }; |
| |
| /* Rx FIFO Element */ |
| |
| struct rx_fifo_s |
| { |
| union rx_fifo_header_u header; |
| #ifdef CONFIG_NET_CAN_CANFD |
| union payload_u data[16]; /* 64-byte FD payload */ |
| #else |
| union payload_u data[2]; /* 8-byte Classic payload */ |
| #endif |
| }; |
| |
| /* Tx FIFO Element */ |
| |
| struct tx_fifo_s |
| { |
| union tx_fifo_header_u header; |
| #ifdef CONFIG_NET_CAN_CANFD |
| union payload_u data[16]; /* 64-byte FD payload */ |
| #else |
| union payload_u data[2]; /* 8-byte Classic payload */ |
| #endif |
| }; |
| |
| /* Tx Mailbox Status Tracking */ |
| |
| #define TX_ABORT -1 |
| #define TX_FREE 0 |
| #define TX_BUSY 1 |
| |
| struct txmbstats |
| { |
| #ifdef CONFIG_NET_CAN_RAW_TX_DEADLINE |
| struct timeval deadline; |
| struct wdog_s txtimeout; |
| #endif |
| int8_t pending; |
| }; |
| |
| /* FDCAN Device hardware configuration **************************************/ |
| |
| struct fdcan_config_s |
| { |
| uint32_t tx_pin; /* GPIO configuration for TX */ |
| uint32_t rx_pin; /* GPIO configuration for RX */ |
| uint32_t mb_irq[2]; /* FDCAN Interrupt 0, 1 (Rx, Tx) */ |
| }; |
| |
| struct fdcan_bitseg |
| { |
| uint32_t bitrate; |
| uint8_t sjw; |
| uint8_t bs1; |
| uint8_t bs2; |
| uint8_t prescaler; |
| }; |
| |
| struct fdcan_message_ram |
| { |
| uint32_t filt_stdid_addr; |
| uint32_t filt_extid_addr; |
| uint32_t rxfifo0_addr; |
| uint32_t rxfifo1_addr; |
| uint32_t txfifo_addr; |
| uint8_t n_stdfilt; |
| uint8_t n_extfilt; |
| uint8_t n_rxfifo0; |
| uint8_t n_rxfifo1; |
| uint8_t n_txfifo; |
| }; |
| |
| /* FDCAN device structures **************************************************/ |
| |
| #ifdef CONFIG_STM32H7_FDCAN1 |
| static const struct fdcan_config_s stm32_fdcan0_config = |
| { |
| .tx_pin = GPIO_CAN1_TX, |
| .rx_pin = GPIO_CAN1_RX, |
| .mb_irq = |
| { |
| STM32_IRQ_FDCAN1_0, |
| STM32_IRQ_FDCAN1_1, |
| }, |
| }; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN2 |
| static const struct fdcan_config_s stm32_fdcan1_config = |
| { |
| .tx_pin = GPIO_CAN2_TX, |
| .rx_pin = GPIO_CAN2_RX, |
| .mb_irq = |
| { |
| STM32_IRQ_FDCAN2_0, |
| STM32_IRQ_FDCAN2_1 , |
| }, |
| }; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN3 |
| # error "FDCAN3 support not yet added to stm32h7x3xx header files (pinmap, irq, etc.)" |
| static const struct fdcan_config_s stm32_fdcan2_config = |
| { |
| .tx_pin = GPIO_CAN3_TX, |
| .rx_pin = GPIO_CAN3_RX, |
| .mb_irq = |
| { |
| STM32_IRQ_FDCAN3_0, |
| STM32_IRQ_FDCAN3_1 , |
| }, |
| }; |
| #endif |
| |
| /* The fdcan_driver_s encapsulates all state information for a single |
| * hardware interface |
| */ |
| |
| struct fdcan_driver_s |
| { |
| const struct fdcan_config_s *config; /* Pin config */ |
| uint8_t iface_idx; /* FDCAN interface index (0 or 1) */ |
| uint32_t base; /* FDCAN base address */ |
| |
| struct fdcan_bitseg arbi_timing; /* Timing for arbitration phase */ |
| #ifdef CONFIG_NET_CAN_CANFD |
| struct fdcan_bitseg data_timing; /* Timing for data phase */ |
| #endif |
| |
| struct fdcan_message_ram message_ram; /* Start addresses for each reagion of Message RAM */ |
| struct rx_fifo_s *rx; /* Pointer to Rx FIFO0 in Message RAM */ |
| struct tx_fifo_s *tx; /* Pointer to Tx mailboxes in Message RAM */ |
| |
| /* Work queue configs for deferring interrupt and poll work */ |
| |
| struct work_s rxwork; |
| struct work_s txcwork; |
| struct work_s txdwork; |
| struct work_s pollwork; |
| #ifdef CONFIG_NET_CAN_ERRORS |
| struct work_s irqwork; |
| #endif |
| |
| uint32_t irflags; /* Used to copy IR flags from IRQ context to work_queue */ |
| |
| /* Intermediate storage of Tx / Rx frames outside of Message RAM */ |
| |
| uint8_t tx_pool[(sizeof(FRAME_TYPE)+MSG_DATA)*POOL_SIZE]; |
| uint8_t rx_pool[(sizeof(FRAME_TYPE)+MSG_DATA)*POOL_SIZE]; |
| |
| struct net_driver_s dev; /* Interface understood by the network */ |
| bool bifup; /* true:ifup false:ifdown */ |
| |
| struct txmbstats txmb[NUM_TX_FIFO]; /* Track deadline and status of every Tx entry */ |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_STM32H7_FDCAN1 |
| static struct fdcan_driver_s g_fdcan0; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN2 |
| static struct fdcan_driver_s g_fdcan1; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN3 |
| static struct fdcan_driver_s g_fdcan2; |
| #endif |
| |
| static bool g_apb1h_init = false; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Common TX logic */ |
| |
| static bool fdcan_txringfull(struct fdcan_driver_s *priv); |
| static int fdcan_transmit(struct fdcan_driver_s *priv); |
| static int fdcan_txpoll(struct net_driver_s *dev); |
| |
| /* Helper functions */ |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| static void fdcan_dumpregs(struct fdcan_driver_s *priv); |
| #endif |
| |
| int32_t fdcan_bittiming(struct fdcan_bitseg *timing); |
| |
| static void fdcan_apb1hreset(void); |
| static void fdcan_setinit(uint32_t base, uint32_t init); |
| static void fdcan_setenable(uint32_t base, uint32_t enable); |
| static void fdcan_setconfig(uint32_t base, uint32_t config_enable); |
| static bool fdcan_waitccr_change(uint32_t base, |
| uint32_t mask, |
| uint32_t target_state); |
| |
| static void fdcan_enable_interrupts(struct fdcan_driver_s *priv); |
| static void fdcan_disable_interrupts(struct fdcan_driver_s *priv); |
| |
| /* Interrupt handling */ |
| |
| static void fdcan_receive(struct fdcan_driver_s *priv); |
| static void fdcan_receive_work(void *arg); |
| static void fdcan_txdone(struct fdcan_driver_s *priv); |
| static void fdcan_txdone_work(void *arg); |
| |
| static int fdcan_interrupt(int irq, void *context, |
| void *arg); |
| |
| static void fdcan_check_errors(struct fdcan_driver_s *priv); |
| |
| /* Watchdog timer expirations */ |
| |
| #ifdef TX_TIMEOUT_WQ |
| static void fdcan_txtimeout_work(void *arg); |
| static void fdcan_txtimeout_expiry(wdparm_t arg); |
| #endif |
| |
| /* NuttX networking stack 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(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 |
| |
| /* Initialization and Reset */ |
| |
| static int fdcan_initialize(struct fdcan_driver_s *priv); |
| static void fdcan_reset(struct fdcan_driver_s *priv); |
| |
| #ifdef CONFIG_NET_CAN_ERRORS |
| |
| /* CAN errors interrupt handling */ |
| |
| static void fdcan_error_work(void *arg); |
| static void fdcan_error(struct fdcan_driver_s *priv, uint32_t status, |
| uint32_t psr); |
| static void fdcan_errint(struct fdcan_driver_s *priv, bool enable); |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: fdcan_dumpregs |
| * |
| * Dump common register values to the console for debugging purposes. |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| static void fdcan_dumpregs(struct fdcan_driver_s *priv) |
| { |
| printf("-------------- FDCAN Reg Dump ----------------\n"); |
| printf("CAN%d Base: 0x%lx\n", priv->iface_idx, priv->base); |
| |
| uint32_t regval; |
| regval = getreg32(priv->base + STM32_FDCAN_CCCR_OFFSET); |
| printf("CCCR = 0x%lx\n", regval); |
| regval = getreg32(priv->base + STM32_FDCAN_ECR_OFFSET); |
| printf("ECR = 0x%lx\n", regval); |
| |
| regval = getreg32(priv->base + STM32_FDCAN_NBTP_OFFSET); |
| printf("NBTP = 0x%lx\n", regval); |
| regval = getreg32(priv->base + STM32_FDCAN_DBTP_OFFSET); |
| printf("DBTP = 0x%lx\n", regval); |
| |
| regval = getreg32(priv->base + STM32_FDCAN_TXBC_OFFSET); |
| printf("TXBC = 0x%lx\n", regval); |
| regval = getreg32(priv->base + STM32_FDCAN_RXF0C_OFFSET); |
| printf("RXF0C = 0x%lx\n", regval); |
| |
| regval = getreg32(priv->base + STM32_FDCAN_TXESC_OFFSET); |
| printf("TXESC = 0x%lx\n", regval); |
| regval = getreg32(priv->base + STM32_FDCAN_RXESC_OFFSET); |
| printf("RXESC = 0x%lx\n", regval); |
| |
| regval = getreg32(priv->base + STM32_FDCAN_IE_OFFSET); |
| printf("IE = 0x%lx\n", regval); |
| regval = getreg32(priv->base + STM32_FDCAN_ILE_OFFSET); |
| printf("ILE = 0x%lx\n", regval); |
| regval = getreg32(priv->base + STM32_FDCAN_ILS_OFFSET); |
| printf("ILS = 0x%lx\n", regval); |
| |
| /* Print out some possibly interesting unhandled interrupts */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_IR_OFFSET); |
| printf("IR = 0x%lx\n", regval); |
| |
| if (regval & FDCAN_IR_PEA || regval & FDCAN_IR_PED) |
| { |
| /* Protocol error -- check protocol status register for details */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_PSR_OFFSET); |
| printf("--PSR.LEC = %" PRId32 "\n", regval & FDCAN_PSR_LEC_MASK); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * 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 = CLK_FREQ / 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 != (CLK_FREQ / (prescaler * (1 + bs1 + bs2))) || !valid) |
| { |
| nerr("Target bitrate invalid - solution does not match."); |
| return 3; /* Solution not found */ |
| } |
| |
| #ifdef CONFIG_STM32H7_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; |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_txringfull |
| * |
| * Description: |
| * Check if all of the TX descriptors are in use. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * true is the TX ring is full; false if there are free slots at the |
| * head index. |
| * |
| ****************************************************************************/ |
| |
| static bool fdcan_txringfull(struct fdcan_driver_s *priv) |
| { |
| /* TODO: Decide if this needs to be checked every time, or just during init |
| * Check that we even _have_ a Tx FIFO allocated |
| */ |
| |
| uint32_t regval = getreg32(priv->base + STM32_FDCAN_TXBC_OFFSET); |
| if ((regval & FDCAN_TXBC_TFQS) == 0) |
| { |
| nerr("No Tx FIFO buffers assigned? Check your message RAM config\n"); |
| return true; |
| } |
| |
| /* Check if the Tx queue is full */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_TXFQS_OFFSET); |
| if ((regval & FDCAN_TXFQS_TFQF) == FDCAN_TXFQS_TFQF) |
| { |
| return true; /* Sorry, out of room, try back later */ |
| } |
| |
| return false; |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_transmit |
| * |
| * Description: |
| * Start hardware transmission of the data contained in priv->d_buf. Called |
| * either from the txdone interrupt handling or from watchdog based polling |
| * |
| * Input Parameters: |
| * priv - Reference to the 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_transmit(struct fdcan_driver_s *priv) |
| { |
| irqstate_t flags = enter_critical_section(); |
| |
| /* First, check if there are any slots available in the queue */ |
| |
| uint32_t regval = getreg32(priv->base + STM32_FDCAN_TXFQS_OFFSET); |
| if ((regval & FDCAN_TXFQS_TFQF) == FDCAN_TXFQS_TFQF) |
| { |
| /* Tx FIFO / Queue is full */ |
| |
| leave_critical_section(flags); |
| return -EBUSY; |
| } |
| |
| /* Next, get the next available FIFO index from the controller */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_TXFQS_OFFSET); |
| const uint8_t mbi = (regval & FDCAN_TXFQS_TFQPI) >> |
| FDCAN_TXFQS_TFQPI_SHIFT; |
| |
| /* Now, we can copy the CAN frame to the FIFO (in message RAM) */ |
| |
| if (mbi >= NUM_TX_FIFO) |
| { |
| nerr("Invalid Tx mailbox index encountered in transmit\n"); |
| leave_critical_section(flags); |
| return -EIO; |
| } |
| |
| struct tx_fifo_s *mb = &priv->tx[mbi]; |
| |
| /* Setup timeout deadline if enabled */ |
| |
| #ifdef CONFIG_NET_CAN_RAW_TX_DEADLINE |
| int32_t timeout = 0; |
| struct timespec ts; |
| clock_systime_timespec(&ts); |
| |
| if (priv->dev.d_sndlen > priv->dev.d_len) |
| { |
| /* Tx deadline is stored in d_buf after frame data */ |
| |
| struct timeval *tv = |
| (struct timeval *)(priv->dev.d_buf + priv->dev.d_len); |
| priv->txmb[mbi].deadline = *tv; |
| timeout = (tv->tv_sec - ts.tv_sec)*CLK_TCK |
| + ((tv->tv_usec - ts.tv_nsec / 1000)*CLK_TCK) / 1000000; |
| if (timeout < 0) |
| { |
| leave_critical_section(flags); |
| return 0; /* No transmission for you! */ |
| } |
| } |
| else |
| { |
| /* Default TX deadline defined in NET_CAN_RAW_DEFAULT_TX_DEADLINE */ |
| |
| if (CONFIG_NET_CAN_RAW_DEFAULT_TX_DEADLINE > 0) |
| { |
| timeout = ((CONFIG_NET_CAN_RAW_DEFAULT_TX_DEADLINE / 1000000) |
| *CLK_TCK); |
| priv->txmb[mbi].deadline.tv_sec = ts.tv_sec + |
| CONFIG_NET_CAN_RAW_DEFAULT_TX_DEADLINE / 1000000; |
| priv->txmb[mbi].deadline.tv_usec = (ts.tv_nsec / 1000) + |
| CONFIG_NET_CAN_RAW_DEFAULT_TX_DEADLINE % 1000000; |
| } |
| else |
| { |
| priv->txmb[mbi].deadline.tv_sec = 0; |
| priv->txmb[mbi].deadline.tv_usec = 0; |
| } |
| } |
| #endif |
| |
| /* Attempt to write frame */ |
| |
| union tx_fifo_header_u header; |
| |
| if (priv->dev.d_len == sizeof(struct can_frame)) |
| { |
| struct can_frame *frame = (struct can_frame *)priv->dev.d_buf; |
| |
| if (frame->can_id & CAN_EFF_FLAG) |
| { |
| header.id.xtd = 1; |
| header.id.extid = frame->can_id & CAN_EFF_MASK; |
| } |
| else |
| { |
| header.id.xtd = 0; |
| header.id.stdid = frame->can_id & CAN_SFF_MASK; |
| } |
| |
| header.id.esi = frame->can_id & CAN_ERR_FLAG ? 1 : 0; |
| header.id.rtr = frame->can_id & CAN_RTR_FLAG ? 1 : 0; |
| header.dlc = frame->can_dlc; |
| header.brs = 0; /* No bitrate switching */ |
| header.fdf = 0; /* Classic CAN frame, not CAN-FD */ |
| header.efc = 0; /* Don't store Tx events */ |
| header.mm = mbi; /* Mailbox Marker for our own use; just store FIFO index */ |
| |
| /* Store into message RAM */ |
| |
| mb->header.w0 = header.w0; |
| mb->header.w1 = header.w1; |
| mb->data[0].word = *(uint32_t *)&frame->data[0]; |
| mb->data[1].word = *(uint32_t *)&frame->data[4]; |
| } |
| #ifdef CONFIG_NET_CAN_CANFD |
| else /* CAN FD frame */ |
| { |
| struct canfd_frame *frame = (struct canfd_frame *)priv->dev.d_buf; |
| |
| if (frame->can_id & CAN_EFF_FLAG) |
| { |
| header.id.xtd = 1; |
| header.id.extid = frame->can_id & CAN_EFF_MASK; |
| } |
| else |
| { |
| header.id.xtd = 0; |
| header.id.stdid = frame->can_id & CAN_SFF_MASK; |
| } |
| |
| const bool brs = |
| (priv->arbi_timing.bitrate == priv->data_timing.bitrate) ? 0 : 1; |
| |
| header.id.esi = (frame->can_id & CAN_ERR_FLAG) ? 1 : 0; |
| header.id.rtr = (frame->can_id & CAN_RTR_FLAG) ? 1 : 0; |
| header.dlc = len_to_can_dlc[frame->len]; |
| header.brs = brs; /* Bitrate switching */ |
| header.fdf = 1; /* CAN-FD frame */ |
| header.efc = 0; /* Don't store Tx events */ |
| header.mm = mbi; /* Mailbox Marker for our own use; just store FIFO index */ |
| |
| /* Store into message RAM */ |
| |
| mb->header.w1 = header.w1; |
| mb->header.w0 = header.w0; |
| |
| uint32_t *frame_data_word = (uint32_t *)&frame->data[0]; |
| |
| for (int i = 0; i < (frame->len + 4 - 1) / 4; i++) |
| { |
| mb->data[i].word = frame_data_word[i]; |
| } |
| } |
| #endif |
| |
| /* GO - Submit the transmission request for this element */ |
| |
| putreg32(1 << mbi, priv->base + STM32_FDCAN_TXBAR_OFFSET); |
| |
| /* Increment statistics */ |
| |
| NETDEV_TXPACKETS(&priv->dev); |
| |
| priv->txmb[mbi].pending = TX_BUSY; |
| |
| #ifdef TX_TIMEOUT_WQ |
| /* Setup the TX timeout watchdog (perhaps restarting the timer) */ |
| |
| if (timeout > 0) |
| { |
| wd_start(&priv->txmb[mbi].txtimeout, timeout + 1, |
| fdcan_txtimeout_expiry, (wdparm_t)priv); |
| } |
| #endif |
| |
| leave_critical_section(flags); |
| |
| 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) |
| { |
| struct fdcan_driver_s *priv = |
| (struct fdcan_driver_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) |
| { |
| /* Send the packet */ |
| |
| fdcan_transmit(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_txringfull(priv)) |
| { |
| return -EBUSY; |
| } |
| } |
| |
| /* If zero is returned, the polling will continue until all connections |
| * have been examined. |
| */ |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_receive |
| * |
| * Description: |
| * An interrupt was received indicating the availability of a new RX packet |
| * Schedule the message receipt and socket notification |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_receive(struct fdcan_driver_s *priv) |
| { |
| /* Check the interrupt value to determine which FIFO to read */ |
| |
| uint32_t regval = getreg32(priv->base + STM32_FDCAN_IR_OFFSET); |
| |
| const uint32_t ir_fifo0 = FDCAN_IR_RF0N | FDCAN_IR_RF0F; |
| const uint32_t ir_fifo1 = FDCAN_IR_RF1N | FDCAN_IR_RF1F; |
| |
| if (regval & ir_fifo0) |
| { |
| regval = ir_fifo0; |
| } |
| else if (regval & ir_fifo1) |
| { |
| regval = ir_fifo1; |
| } |
| else |
| { |
| nerr("ERROR: Bad RX IR flags"); |
| return; |
| } |
| |
| /* Store the Rx FIFO IR flags for use in the deferred work function */ |
| |
| priv->irflags = regval; |
| |
| /* Write the corresponding interrupt bits to reset these interrupts */ |
| |
| putreg32(regval, priv->base + STM32_FDCAN_IR_OFFSET); |
| |
| /* Schedule the actual Rx work immediately from HPWORK context */ |
| |
| work_queue(CANWORK, &priv->rxwork, fdcan_receive_work, priv, 0); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_receive_work |
| * |
| * Description: |
| * An frame was received; read the frame into the intermediate rx_pool and |
| * notify the upper-half driver. |
| * While we're here, also check for errors and timeouts. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Scheduled in worker thread (HPWORK / LPWORK) |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_receive_work(void *arg) |
| { |
| irqstate_t flags = enter_critical_section(); |
| |
| struct fdcan_driver_s *priv = (struct fdcan_driver_s *)arg; |
| |
| /* Check which FIFO triggered this work */ |
| |
| uint32_t irflags = priv->irflags; |
| |
| const uint32_t ir_fifo0 = FDCAN_IR_RF0N | FDCAN_IR_RF0F; |
| const uint32_t ir_fifo1 = FDCAN_IR_RF1N | FDCAN_IR_RF1F; |
| uint8_t fifo_id; |
| |
| if (irflags & ir_fifo0) |
| { |
| fifo_id = 0; |
| } |
| else if (irflags & ir_fifo1) |
| { |
| fifo_id = 1; |
| } |
| else |
| { |
| nerr("ERROR: Bad RX IR flags"); |
| leave_critical_section(flags); |
| return; |
| } |
| |
| /* Bitwise register definitions are the same for FIFO 0/1 |
| * |
| * FDCAN_RXFnC_F0S: Rx FIFO Size |
| * FDCAN_RXFnS_RF0L: Rx Message Lost |
| * FDCAN_RXFnS_F0FL: Rx FIFO Fill Level |
| * FDCAN_RXFnS_F0GI: Rx FIFO Get Index |
| * |
| * So we will use only the RX FIFO0 register definitions for simplicity |
| */ |
| |
| uint32_t offset_rxfnc = (fifo_id == 0) ? STM32_FDCAN_RXF0C_OFFSET |
| : STM32_FDCAN_RXF1C_OFFSET; |
| uint32_t offset_rxfns = (fifo_id == 0) ? STM32_FDCAN_RXF0S_OFFSET |
| : STM32_FDCAN_RXF1S_OFFSET; |
| uint32_t offset_rxfna = (fifo_id == 0) ? STM32_FDCAN_RXF0A_OFFSET |
| : STM32_FDCAN_RXF1A_OFFSET; |
| |
| volatile uint32_t *const rxfnc = (uint32_t *)(priv->base + offset_rxfnc); |
| volatile uint32_t *const rxfns = (uint32_t *)(priv->base + offset_rxfns); |
| volatile uint32_t *const rxfna = (uint32_t *)(priv->base + offset_rxfna); |
| |
| /* Check number of elements in message RAM allocated to this FIFO */ |
| |
| if ((*rxfnc & FDCAN_RXF0C_F0S) == 0) |
| { |
| nerr("ERROR: No RX FIFO elements allocated"); |
| leave_critical_section(flags); |
| return; |
| } |
| |
| /* Check for message lost; count an error */ |
| |
| if ((*rxfns & FDCAN_RXF0S_RF0L) != 0) |
| { |
| NETDEV_RXERRORS(&priv->dev); |
| } |
| |
| /* Check number of elements available (fill level) */ |
| |
| const uint8_t n_elem = (*rxfns & FDCAN_RXF0S_F0FL); |
| |
| if (n_elem == 0) |
| { |
| nerr("RX interrupt but 0 frames available"); |
| leave_critical_section(flags); |
| return; |
| } |
| |
| struct rx_fifo_s *rf = NULL; |
| |
| while ((*rxfns & FDCAN_RXF0S_F0FL) > 0) |
| { |
| /* Copy the frame from message RAM */ |
| |
| const uint8_t index = (*rxfns & FDCAN_RXF0S_F0GI) >> |
| FDCAN_RXF0S_F0GI_SHIFT; |
| |
| rf = &priv->rx[index]; |
| |
| /* Read the frame contents */ |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| if (rf->header.fdf) /* CAN FD frame */ |
| { |
| struct canfd_frame *frame = (struct canfd_frame *)priv->rx_pool; |
| |
| if (rf->header.id.xtd) |
| { |
| frame->can_id = CAN_EFF_MASK & rf->header.id.extid; |
| frame->can_id |= CAN_EFF_FLAG; |
| } |
| else |
| { |
| frame->can_id = CAN_SFF_MASK & rf->header.id.stdid; |
| } |
| |
| if (rf->header.id.rtr) |
| { |
| frame->can_id |= CAN_RTR_FLAG; |
| } |
| |
| frame->len = can_dlc_to_len[rf->header.dlc]; |
| |
| uint32_t *frame_data_word = (uint32_t *)&frame->data[0]; |
| |
| for (int i = 0; i < (frame->len + 4 - 1) / 4; i++) |
| { |
| frame_data_word[i] = rf->data[i].word; |
| } |
| |
| /* Acknowledge receipt of this FIFO element */ |
| |
| putreg32(index, rxfna); |
| |
| /* Copy the buffer pointer to priv->dev |
| * Set amount of data in priv->dev.d_len |
| */ |
| |
| priv->dev.d_len = sizeof(struct canfd_frame); |
| priv->dev.d_buf = (uint8_t *)frame; |
| } |
| else /* CAN 2.0 Frame */ |
| #endif |
| { |
| struct can_frame *frame = (struct can_frame *)priv->rx_pool; |
| |
| if (rf->header.id.xtd) |
| { |
| frame->can_id = CAN_EFF_MASK & rf->header.id.extid; |
| frame->can_id |= CAN_EFF_FLAG; |
| } |
| else |
| { |
| frame->can_id = CAN_SFF_MASK & rf->header.id.stdid; |
| } |
| |
| if (rf->header.id.rtr) |
| { |
| frame->can_id |= CAN_RTR_FLAG; |
| } |
| |
| frame->can_dlc = rf->header.dlc; |
| |
| *(uint32_t *)&frame->data[0] = rf->data[0].word; |
| *(uint32_t *)&frame->data[4] = rf->data[1].word; |
| |
| /* Acknowledge receipt of this FIFO element */ |
| |
| putreg32(index, rxfna); |
| |
| /* 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 */ |
| |
| can_input(&priv->dev); |
| |
| /* Update iface statistics */ |
| |
| NETDEV_RXPACKETS(&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 = priv->tx_pool; |
| } |
| |
| /* Check for errors and abort-transmission requests */ |
| |
| fdcan_check_errors(priv); |
| |
| #ifdef CONFIG_NET_CAN_ERRORS |
| uint32_t regval; |
| |
| /* Turning back on all configured RX error interrupts */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_IE_OFFSET); |
| regval |= FDCAN_RXERR_INTS; |
| putreg32(regval, priv->base + STM32_FDCAN_IE_OFFSET); |
| #endif |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_txdone |
| * |
| * Description: |
| * An interrupt was received indicating that the last TX packet(s) is done |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called from interrupt context |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_txdone(struct fdcan_driver_s *priv) |
| { |
| /* Read and reset the interrupt flag */ |
| |
| uint32_t ir = getreg32(priv->base + STM32_FDCAN_IR_OFFSET); |
| if (ir & FDCAN_IR_TC) |
| { |
| putreg32(FDCAN_IR_TC, priv->base + STM32_FDCAN_IR_OFFSET); |
| } |
| else |
| { |
| nerr("Unexpected FCAN interrupt on line 1\n"); |
| return; |
| } |
| |
| /* Schedule to perform the TX timeout processing on the worker thread */ |
| |
| work_queue(CANWORK, &priv->txdwork, fdcan_txdone_work, priv, 0); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_txdone_work |
| * |
| * Description: |
| * Process completed transmissions, including canceling their watchdog |
| * timers if applicable |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Scheduled in worker thread (HPWORK / LPWORK) |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_txdone_work(void *arg) |
| { |
| irqstate_t flags = enter_critical_section(); |
| |
| struct fdcan_driver_s *priv = (struct fdcan_driver_s *)arg; |
| |
| /* Update counters for successful transmissions */ |
| |
| for (uint8_t i = 0; i < NUM_TX_FIFO; i++) |
| { |
| if ((getreg32(priv->base + STM32_FDCAN_TXBTO_OFFSET) & (1 << i)) > 0) |
| { |
| /* Transmission Occurred in buffer i |
| * (Not necessarily a 'new' transmission, however) |
| * Check that it's a new transmission, not a previously handled |
| * transmission |
| */ |
| |
| struct txmbstats *txi = &priv->txmb[i]; |
| |
| if (txi->pending == TX_BUSY) |
| { |
| /* This is a transmission that just now completed */ |
| |
| NETDEV_TXDONE(&priv->dev); |
| |
| txi->pending = TX_FREE; |
| |
| #ifdef TX_TIMEOUT_WQ |
| /* We are here because a transmission completed, so the |
| * corresponding watchdog can be canceled. |
| */ |
| |
| wd_cancel(&priv->txmb[i].txtimeout); |
| #endif |
| } |
| } |
| } |
| |
| /* Check for errors and abort-transmission requests */ |
| |
| fdcan_check_errors(priv); |
| |
| #ifdef CONFIG_NET_CAN_ERRORS |
| uint32_t regval = 0; |
| |
| /* Turning back on PEA and PED error interrupts */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_IE_OFFSET); |
| regval |= (FDCAN_IE_PEAE | FDCAN_IE_PEDE); |
| putreg32(regval, priv->base + STM32_FDCAN_IE_OFFSET); |
| #endif |
| |
| /* There should be space for a new TX in any event |
| * Poll the network for new data to transmit |
| */ |
| |
| devif_poll(&priv->dev, fdcan_txpoll); |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_interrupt |
| * |
| * Description: |
| * Common handler for all enabled FDCAN interrupts |
| * |
| * Input Parameters: |
| * irq - Number of the IRQ that generated the interrupt |
| * context - Interrupt register state save info (architecture-specific) |
| * arg - Unused |
| * |
| * Returned Value: |
| * Zero on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int fdcan_interrupt(int irq, void *context, |
| void *arg) |
| { |
| struct fdcan_driver_s *priv = (struct fdcan_driver_s *)arg; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| #ifdef CONFIG_NET_CAN_ERRORS |
| uint32_t pending = 0; |
| |
| /* Get the set of pending interrupts. */ |
| |
| pending = getreg32(priv->base + STM32_FDCAN_IR_OFFSET); |
| |
| /* 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 |
| |
| if (irq == priv->config->mb_irq[0]) |
| { |
| fdcan_receive(priv); |
| } |
| else if (irq == priv->config->mb_irq[1]) |
| { |
| fdcan_txdone(priv); |
| } |
| else |
| { |
| nerr("Unexpected IRQ [%d]\n", irq); |
| return -EINVAL; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_check_errors |
| * |
| * Description: |
| * Check error flags and cancel any timed out transmissions |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * 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 void fdcan_check_errors(struct fdcan_driver_s *priv) |
| { |
| /* Read CAN Error Logging counter (This also resets the error counter) */ |
| |
| uint32_t regval = getreg32(priv->base + STM32_FDCAN_ECR_OFFSET) |
| & FDCAN_ECR_CEL; |
| const uint8_t cel = (uint8_t)(regval >> FDCAN_ECR_CEL_SHIFT); |
| |
| if (cel > 0) |
| { |
| /* We've had some errors; check the status of the device */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_CCCR_OFFSET); |
| bool restricted_op_mode = (regval & FDCAN_CCCR_ASM) > 0; |
| |
| if (restricted_op_mode) |
| { |
| nerr("Tx handler message RAM error -- resctricted mode enabled\n"); |
| |
| /* Reset the CCCR.ASM register to exit restricted op mode */ |
| |
| putreg32(regval & ~FDCAN_CCCR_ASM, |
| priv->base + STM32_FDCAN_CCCR_OFFSET); |
| } |
| } |
| |
| /* Serve abort requests */ |
| |
| for (uint8_t i = 0; i < NUM_TX_FIFO; i++) |
| { |
| struct txmbstats *txi = &priv->txmb[i]; |
| |
| regval = getreg32(priv->base + STM32_FDCAN_TXBRP_OFFSET); |
| if (txi->pending == TX_ABORT && ((1 << i) & regval)) |
| { |
| /* Request to Cancel Tx item */ |
| |
| putreg32(1 << i, priv->base + STM32_FDCAN_TXBCR_OFFSET); |
| txi->pending = TX_FREE; |
| NETDEV_TXERRORS(&priv->dev); |
| #ifdef TX_TIMEOUT_WQ |
| wd_cancel(&priv->txmb[i].txtimeout); |
| #endif |
| } |
| } |
| } |
| |
| #ifdef TX_TIMEOUT_WQ |
| /**************************************************************************** |
| * Function: fdcan_txtimeout_work |
| * |
| * Description: |
| * Perform TX timeout related work from the worker thread |
| * |
| * Input Parameters: |
| * arg - The argument passed when work_queue() as called. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_txtimeout_work(void *arg) |
| { |
| struct fdcan_driver_s *priv = (struct fdcan_driver_s *)arg; |
| |
| struct timespec ts; |
| struct timeval *now = (struct timeval *)&ts; |
| clock_systime_timespec(&ts); |
| now->tv_usec = ts.tv_nsec / 1000; /* timespec to timeval conversion */ |
| |
| /* The watchdog timed out, yet we still check mailboxes in case the |
| * transmit function transmitted a new frame |
| */ |
| |
| for (int mbi = 0; mbi < NUM_TX_FIFO; mbi++) |
| { |
| if (priv->txmb[mbi].deadline.tv_sec != 0 |
| && (now->tv_sec > priv->txmb[mbi].deadline.tv_sec |
| || now->tv_usec > priv->txmb[mbi].deadline.tv_usec)) |
| { |
| NETDEV_TXTIMEOUTS(&priv->dev); |
| priv->txmb[mbi].pending = TX_ABORT; |
| } |
| } |
| |
| fdcan_check_errors(priv); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_txtimeout_expiry |
| * |
| * Description: |
| * Our TX watchdog timed out. Called from the timer interrupt handler. |
| * |
| * Input Parameters: |
| * arg - Pointer to the private FDCAN driver state structure |
| * |
| * Assumptions: |
| * Global interrupts are disabled by the watchdog logic. |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_txtimeout_expiry(wdparm_t arg) |
| { |
| struct fdcan_driver_s *priv = (struct fdcan_driver_s *)arg; |
| |
| /* Schedule to perform the TX timeout processing on the worker thread */ |
| |
| work_queue(CANWORK, &priv->txcwork, fdcan_txtimeout_work, priv, 0); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: fdcan_apb1hreset |
| * |
| * Description: |
| * Reset the periheral bus clock used by FDCAN |
| * Note that this will reset all configuration of all FDCAN peripherals |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_apb1hreset(void) |
| { |
| /* Reset the FDCAN's peripheral bus clock */ |
| |
| modifyreg32(STM32_RCC_APB1HRSTR, 0, RCC_APB1HRSTR_FDCANRST); |
| modifyreg32(STM32_RCC_APB1HRSTR, RCC_APB1HRSTR_FDCANRST, 0); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_setinit |
| * |
| * Description: |
| * Enter / Exit initialization mode |
| * |
| * Input Parameters: |
| * base - The base pointer of the FDCAN peripheral |
| * init - true: Enter init mode; false: Exit init mode |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_setinit(uint32_t base, uint32_t init) |
| { |
| if (init) |
| { |
| /* Enter hardware initialization mode */ |
| |
| modifyreg32(base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_INIT); |
| fdcan_waitccr_change(base, FDCAN_CCCR_INIT, FDCAN_CCCR_INIT); |
| } |
| else |
| { |
| /* Exit hardware initialization mode */ |
| |
| modifyreg32(base + STM32_FDCAN_CCCR_OFFSET, FDCAN_CCCR_INIT, 0); |
| fdcan_waitccr_change(base, FDCAN_CCCR_INIT, 0); |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_setenable |
| * |
| * Description: |
| * Power On / Power Off the device with the Clock Stop Request bit |
| * |
| * Input Parameters: |
| * base - The base pointer of the FDCAN peripheral |
| * init - true: Power on the device; false: Power off the device |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_setenable(uint32_t base, uint32_t enable) |
| { |
| if (enable) |
| { |
| /* Clear CSR bit */ |
| |
| modifyreg32(base + STM32_FDCAN_CCCR_OFFSET, FDCAN_CCCR_CSR, 0); |
| fdcan_waitccr_change(base, FDCAN_CCCR_CSA, 0); |
| } |
| else |
| { |
| /* Set CSR bit */ |
| |
| modifyreg32(base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_CSR); |
| fdcan_waitccr_change(base, FDCAN_CCCR_CSA, 1); |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_setconfig |
| * |
| * Description: |
| * Enter / Exit Configuration Changes Enabled mode |
| * |
| * Input Parameters: |
| * base - The base pointer of the FDCAN peripheral |
| * init - true: Enter config mode; false: Exit config mode |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_setconfig(uint32_t base, uint32_t config_enable) |
| { |
| if (config_enable) |
| { |
| /* Configuration Changes Enabled (CCE) mode */ |
| |
| modifyreg32(base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_CCE); |
| fdcan_waitccr_change(base, FDCAN_CCCR_CCE, 1); |
| } |
| else |
| { |
| /* Exit CCE mode */ |
| |
| modifyreg32(base + STM32_FDCAN_CCCR_OFFSET, FDCAN_CCCR_CCE, 0); |
| fdcan_waitccr_change(base, FDCAN_CCCR_CCE, 0); |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_waitccr_change |
| * |
| * Description: |
| * Wait for the CCR register to accept a requested change. |
| * Timeout after ~10ms. |
| * |
| * Input Parameters: |
| * base - The base pointer of the FDCAN peripheral |
| * mask - Mask to apply to the CCR register value |
| * target_state - Target masked value to wait for |
| * |
| * Returned Value: |
| * true on success; false on timeout |
| * |
| ****************************************************************************/ |
| |
| static bool fdcan_waitccr_change(uint32_t base, uint32_t mask, |
| uint32_t target_state) |
| { |
| const unsigned timeout = 1000; |
| for (unsigned wait_ack = 0; wait_ack < timeout; wait_ack++) |
| { |
| const bool state = (getreg32(base + STM32_FDCAN_CCCR_OFFSET) & mask); |
| if (state == target_state) |
| { |
| return true; |
| } |
| |
| up_udelay(10); |
| } |
| |
| return false; |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_enable_interrupts |
| * |
| * Description: |
| * Enable all interrupts used by this driver |
| * |
| * Input Parameters: |
| * priv - Pointer to the private FDCAN driver state structure |
| * |
| * Assumptions: |
| * The peripheral is in Configuration Changes Enabled (CCE) mode |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_enable_interrupts(struct fdcan_driver_s *priv) |
| { |
| /* Enable both interrupt lines at the device level */ |
| |
| const uint32_t ile = FDCAN_ILE_EINT0 | FDCAN_ILE_EINT1; |
| modifyreg32(priv->base + STM32_FDCAN_ILE_OFFSET, 0, ile); |
| |
| /* Enable both lines at the NVIC level */ |
| |
| up_enable_irq(priv->config->mb_irq[0]); |
| up_enable_irq(priv->config->mb_irq[1]); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_disable_interrupts |
| * |
| * Description: |
| * Disable all interrupts used by this driver |
| * |
| * Input Parameters: |
| * priv - Pointer to the private FDCAN driver state structure |
| * |
| * Assumptions: |
| * The peripheral is in Configuration Changes Enabled (CCE) mode |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_disable_interrupts(struct fdcan_driver_s *priv) |
| { |
| /* Disable both lines at the NVIC level */ |
| |
| up_disable_irq(priv->config->mb_irq[0]); |
| up_disable_irq(priv->config->mb_irq[1]); |
| |
| /* Disable both interrupt lines at the device level */ |
| |
| const uint32_t ile = FDCAN_ILE_EINT0 | FDCAN_ILE_EINT1; |
| modifyreg32(priv->base + STM32_FDCAN_ILE_OFFSET, ile, 0); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_ifup |
| * |
| * Description: |
| * NuttX Callback: Bring up the CAN interface when a socket is opened |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * The device is initialized and waiting to be brought online |
| ****************************************************************************/ |
| |
| static int fdcan_ifup(struct net_driver_s *dev) |
| { |
| struct fdcan_driver_s *priv = |
| (struct fdcan_driver_s *)dev->d_private; |
| |
| /* Wake up the device and perform all initialization */ |
| |
| irqstate_t flags = enter_critical_section(); |
| |
| fdcan_initialize(priv); |
| |
| fdcan_setinit(priv->base, 1); |
| fdcan_setconfig(priv->base, 1); |
| |
| /* Enable interrupts (at both device and NVIC level) */ |
| |
| fdcan_enable_interrupts(priv); |
| |
| /* Leave init mode */ |
| |
| fdcan_setinit(priv->base, 0); |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| fdcan_dumpregs(priv); |
| #endif |
| |
| leave_critical_section(flags); |
| |
| priv->bifup = true; |
| |
| 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) |
| { |
| struct fdcan_driver_s *priv = |
| (struct fdcan_driver_s *)dev->d_private; |
| |
| fdcan_reset(priv); |
| |
| priv->bifup = false; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * 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(void *arg) |
| { |
| struct fdcan_driver_s *priv = (struct fdcan_driver_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_txringfull(priv)) |
| { |
| /* There is space for another transfer. Poll the network for |
| * new XMIT data. |
| */ |
| |
| devif_poll(&priv->dev, fdcan_txpoll); |
| } |
| } |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_txavail |
| * |
| * Description: |
| * Driver callback invoked when new TX data is available. This is a |
| * stimulus to 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) |
| { |
| struct fdcan_driver_s *priv = |
| (struct fdcan_driver_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_netdev_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) |
| { |
| struct fdcan_driver_s *priv = dev->d_private; |
| |
| int ret; |
| |
| switch (cmd) |
| { |
| #ifdef CONFIG_NETDEV_CAN_BITRATE_IOCTL |
| case SIOCGCANBITRATE: /* Get bitrate from a CAN controller */ |
| { |
| struct can_ioctl_data_s *req = |
| (struct can_ioctl_data_s *)((uintptr_t)arg); |
| req->arbi_bitrate = priv->arbi_timing.bitrate / 1000; /* kbit/s */ |
| #ifdef CONFIG_NET_CAN_CANFD |
| req->data_bitrate = priv->data_timing.bitrate / 1000; /* kbit/s */ |
| #else |
| req->data_bitrate = 0; |
| #endif |
| ret = OK; |
| } |
| break; |
| |
| case SIOCSCANBITRATE: /* Set bitrate of a CAN controller */ |
| { |
| struct can_ioctl_data_s *req = |
| (struct can_ioctl_data_s *)((uintptr_t)arg); |
| |
| priv->arbi_timing.bitrate = req->arbi_bitrate * 1000; |
| #ifdef CONFIG_NET_CAN_CANFD |
| priv->data_timing.bitrate = req->data_bitrate * 1000; |
| #endif |
| |
| /* Reset CAN controller and start with new timings */ |
| |
| ret = fdcan_initialize(priv); |
| |
| if (ret == OK) |
| { |
| ret = fdcan_ifup(dev); |
| } |
| } |
| break; |
| #endif /* CONFIG_NETDEV_CAN_BITRATE_IOCTL */ |
| |
| #ifdef CONFIG_NETDEV_CAN_FILTER_IOCTL |
| case SIOCACANEXTFILTER: |
| { |
| /* TODO: Add hardware-level filter... */ |
| |
| stm32_addextfilter(priv, (struct canioc_extfilter_s *)arg); |
| } |
| break; |
| |
| case SIOCDCANEXTFILTER: |
| { |
| /* TODO: Delete hardware-level filter... */ |
| |
| stm32_delextfilter(priv, (struct canioc_extfilter_s *)arg); |
| } |
| break; |
| |
| case SIOCACANSTDFILTER: |
| { |
| /* TODO: Add hardware-level filter... */ |
| |
| stm32_addstdfilter(priv, (struct canioc_stdfilter_s *)arg); |
| } |
| break; |
| |
| case SIOCDCANSTDFILTER: |
| { |
| /* TODO: Delete hardware-level filter... */ |
| |
| stm32_delstdfilter(priv, (struct canioc_stdfilter_s *)arg); |
| } |
| break; |
| #endif |
| |
| default: |
| ret = -ENOTSUP; |
| break; |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_NETDEV_IOCTL */ |
| |
| /**************************************************************************** |
| * Function: fdcan_initialize |
| * |
| * Description: |
| * Initialize FDCAN device |
| * |
| * Input Parameters: |
| * priv - Pointer to the private FDCAN driver state structure |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| int fdcan_initialize(struct fdcan_driver_s *priv) |
| { |
| uint32_t regval; |
| |
| irqstate_t flags = enter_critical_section(); |
| |
| /* Reset the peripheral clock bus (only do this once) */ |
| |
| if (!g_apb1h_init) |
| { |
| fdcan_apb1hreset(); |
| g_apb1h_init = true; |
| } |
| |
| /* Exit Power-down / Sleep mode */ |
| |
| fdcan_setenable(priv->base, 1); |
| |
| /* Enter Initialization mode */ |
| |
| fdcan_setinit(priv->base, 1); |
| |
| /* Enter Configuration Changes Enabled mode */ |
| |
| fdcan_setconfig(priv->base, 1); |
| |
| /* Disable interrupts while we configure the hardware */ |
| |
| putreg32(0, priv->base + STM32_FDCAN_IE_OFFSET); |
| |
| /* Compute CAN bit timings for this bitrate */ |
| |
| /* Nominal / arbitration phase bitrate */ |
| |
| if (fdcan_bittiming(&priv->arbi_timing) != OK) |
| { |
| fdcan_setinit(priv->base, 0); |
| leave_critical_section(flags); |
| return -EIO; |
| } |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| const struct fdcan_bitseg *tim = &priv->arbi_timing; |
| ninfo("[fdcan][arbi] Timings: presc=%u sjw=%u bs1=%u bs2=%u\r\n", |
| tim->prescaler, tim->sjw, tim->bs1, tim->bs2); |
| #endif |
| |
| /* Set bit timings and prescalers (Nominal bitrate) */ |
| |
| regval = ((priv->arbi_timing.sjw << FDCAN_NBTP_NSJW_SHIFT) | |
| (priv->arbi_timing.bs1 << FDCAN_NBTP_NTSEG1_SHIFT) | |
| (priv->arbi_timing.bs2 << FDCAN_NBTP_TSEG2_SHIFT) | |
| (priv->arbi_timing.prescaler << FDCAN_NBTP_NBRP_SHIFT)); |
| putreg32(regval, priv->base + STM32_FDCAN_NBTP_OFFSET); |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| /* CAN-FD Data phase bitrate */ |
| |
| if (fdcan_bittiming(&priv->data_timing) != OK) |
| { |
| fdcan_setinit(priv->base, 0); |
| leave_critical_section(flags); |
| return -EIO; |
| } |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| tim = &priv->data_timing; |
| ninfo("[fdcan][data] Timings: presc=%u sjw=%u bs1=%u bs2=%u\r\n", |
| tim->prescaler, tim->sjw, tim->bs1, tim->bs2); |
| #endif |
| |
| /* Set bit timings and prescalers (Data bitrate) */ |
| |
| regval = ((priv->data_timing.sjw << FDCAN_DBTP_DSJW_SHIFT) | |
| (priv->data_timing.bs1 << FDCAN_DBTP_DTSEG1_SHIFT) | |
| (priv->data_timing.bs2 << FDCAN_DBTP_DTSEG2_SHIFT) | |
| (priv->data_timing.prescaler << FDCAN_DBTP_DBRP_SHIFT)); |
| #endif /* CONFIG_NET_CAN_CANFD */ |
| |
| /* Be sure to fill data-phase register even if we're not using CAN FD */ |
| |
| putreg32(regval, priv->base + STM32_FDCAN_DBTP_OFFSET); |
| |
| /* Operation Configuration */ |
| |
| #ifdef CONFIG_STM32H7_FDCAN_LOOPBACK |
| /* Enable External Loopback Mode (Rx pin disconnected) (RM0433 pg 2494) */ |
| |
| modifyreg32(priv->base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_TEST); |
| modifyreg32(priv->base + STM32_FDCAN_TEST_OFFSET, 0, FDCAN_TEST_LBCK); |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN_LOOPBACK_INTERNAL |
| /* Enable Bus Monitoring / Restricted Op Mode (RM0433 pg 2492, 2494) */ |
| |
| modifyreg32(priv->base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_MON); |
| #endif |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| /* Enable CAN-FD frames, including bitrate switching if needed */ |
| |
| modifyreg32(priv->base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_FDOE); |
| if (priv->arbi_timing.bitrate != priv->data_timing.bitrate) |
| { |
| modifyreg32(priv->base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_BRSE); |
| } |
| #else |
| /* Disable CAN-FD communications ("classic" CAN only) */ |
| |
| modifyreg32(priv->base + STM32_FDCAN_CCCR_OFFSET, FDCAN_CCCR_FDOE, 0); |
| #endif |
| |
| #if 0 |
| /* Disable Automatic Retransmission of frames upon error |
| * NOTE: This will even disable automatic retry due to lost arbitration!! |
| */ |
| |
| modifyreg32(priv->base + STM32_FDCAN_CCCR_OFFSET, 0, FDCAN_CCCR_DAR); |
| #endif |
| |
| /* Configure Interrupts */ |
| |
| /* Clear all interrupt flags |
| * Note: A flag is cleared by writing a 1 to the corresponding bit position |
| */ |
| |
| putreg32(FDCAN_IR_MASK, priv->base + STM32_FDCAN_IR_OFFSET); |
| |
| /* Enable relevant interrupts */ |
| |
| regval = FDCAN_IE_TCE /* Transmit Complete */ |
| | FDCAN_IE_RF0NE /* Rx FIFO 0 new message */ |
| | FDCAN_IE_RF0FE /* Rx FIFO 0 FIFO full */ |
| | FDCAN_IE_RF1NE /* Rx FIFO 1 new message */ |
| | FDCAN_IE_RF1FE; /* Rx FIFO 1 FIFO full */ |
| putreg32(regval, priv->base + STM32_FDCAN_IE_OFFSET); |
| |
| #ifdef CONFIG_NET_CAN_ERRORS |
| fdcan_errint(priv, true); |
| #endif |
| |
| /* Keep Rx interrupts on Line 0; move Tx to Line 1 |
| * TC (Tx Complete) interrupt on line 1 |
| */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_ILS_OFFSET); |
| regval |= FDCAN_ILS_TCL; |
| putreg32(FDCAN_ILS_TCL, priv->base + STM32_FDCAN_ILS_OFFSET); |
| |
| /* Enable Tx buffer transmission interrupts |
| * Note: Still need fdcan_enable_interrupts() to set ILE (IR line enable) |
| */ |
| |
| putreg32(FDCAN_TXBTIE_TIE, priv->base + STM32_FDCAN_TXBTIE_OFFSET); |
| |
| /* Configure Message RAM |
| * |
| * The available 2560 words (10 kiB) of RAM are shared between both FDCAN |
| * interfaces. It is up to us to ensure each interface has its own non- |
| * overlapping region of RAM assigned to it by properly assigning the start |
| * and end addresses for all regions of RAM. |
| * |
| * We will give each interface half of the available RAM. |
| * |
| * Rx buffers are only used in conjunction with acceptance filters; we |
| * don't have any specific need for this, so we will only use Rx FIFOs. |
| * |
| * Each FIFO can hold up to 64 elements, where each element (for a classic |
| * CAN 2.0B frame) is up to 4 words long (8 bytes data + header bits) |
| * |
| * Let's make use of the full 64 FIFO elements for FIFO0. We have no need |
| * to separate messages between FIFO0 and FIFO1, so ignore FIFO1 for |
| * simplicity. |
| * |
| * Note that the start addresses given to FDCAN are in terms of _words_, |
| * not bytes, so when we go to read/write to/from the message RAM, there |
| * will be a factor of 4 necessary in the address relative to the SA |
| * register values. |
| */ |
| |
| /* Location of this interface's message RAM - address in CPU memory address |
| * and relative address (in words) used for configuration |
| */ |
| |
| const uint32_t iface_ram_base = (2560 / 2) * priv->iface_idx; |
| const uint32_t gl_ram_base = STM32_CANRAM_BASE; |
| uint32_t ram_offset = iface_ram_base; |
| |
| /* Standard ID Filters: Allow space for 128 filters (128 words) */ |
| |
| const uint8_t n_stdid = 128; |
| priv->message_ram.filt_stdid_addr = gl_ram_base + ram_offset * WORD_LENGTH; |
| |
| regval = (n_stdid << FDCAN_SIDFC_LSS_SHIFT) & FDCAN_SIDFC_LSS_MASK; |
| regval |= ram_offset << FDCAN_SIDFC_FLSSA_SHIFT; |
| putreg32(regval, priv->base + STM32_FDCAN_SIDFC_OFFSET); |
| ram_offset += n_stdid; |
| |
| /* Extended ID Filters: Allow space for 64 filters (64 words) |
| * The register definition of FDCAN_XIDFC:LSE - List size extended, in the |
| * reference manual (RM0433 Rev 8) is defined as 8 bits and claims that a |
| * value of 0-128 (Number of extended ID filter elements) are possible, but |
| * in the text in section 56.4.22 and in STM32CubeH7's HAL it's only 64. |
| * |
| * HAL source: |
| * https://github.com/STMicroelectronics/STM32CubeH7/blob/ |
| * 43c9e552ba1c038577c48723d96ca8c825b11987/Drivers/STM32H7xx_HAL_Driver/ |
| * Inc/stm32h7xx_hal_fdcan.h#L112-L113 |
| * |
| * Discussion: |
| * https://community.st.com/s/question/0D73W000001nzqFSAQ |
| */ |
| |
| const uint8_t n_extid = 64; |
| priv->message_ram.filt_extid_addr = gl_ram_base + ram_offset * WORD_LENGTH; |
| |
| regval = (n_extid << FDCAN_XIDFC_LSE_SHIFT) & FDCAN_XIDFC_LSE_MASK; |
| regval |= ram_offset << FDCAN_XIDFC_FLESA_SHIFT; |
| putreg32(regval, priv->base + STM32_FDCAN_XIDFC_OFFSET); |
| ram_offset += n_extid; |
| |
| /* Set size of each element in the Rx/Tx buffers and FIFOs */ |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| /* Set full 64 byte space for every Rx/Tx FIFO element */ |
| |
| modifyreg32(priv->base + STM32_FDCAN_RXESC_OFFSET, 0, FDCAN_RXESC_RBDS); /* Rx Buffer */ |
| modifyreg32(priv->base + STM32_FDCAN_RXESC_OFFSET, 0, FDCAN_RXESC_F0DS); /* Rx FIFO 0 */ |
| modifyreg32(priv->base + STM32_FDCAN_RXESC_OFFSET, 0, FDCAN_RXESC_F1DS); /* Rx FIFO 1 */ |
| modifyreg32(priv->base + STM32_FDCAN_TXESC_OFFSET, 0, FDCAN_TXESC_TBDS); /* Tx Buffer */ |
| #else |
| putreg32(0, priv->base + STM32_FDCAN_RXESC_OFFSET); /* 8 byte space for every element (Rx buffer, FIFO1, FIFO0) */ |
| putreg32(0, priv->base + STM32_FDCAN_TXESC_OFFSET); /* 8 byte space for every element (Tx buffer) */ |
| #endif |
| |
| priv->message_ram.n_rxfifo0 = NUM_RX_FIFO0; |
| priv->message_ram.n_rxfifo1 = NUM_RX_FIFO1; |
| priv->message_ram.n_txfifo = NUM_TX_FIFO; |
| |
| /* Assign Rx Mailbox pointer in the driver structure */ |
| |
| priv->message_ram.rxfifo0_addr = gl_ram_base + ram_offset * WORD_LENGTH; |
| priv->rx = (struct rx_fifo_s *)(priv->message_ram.rxfifo0_addr); |
| |
| /* Set Rx FIFO0 size (64 elements max) */ |
| |
| regval = (ram_offset << FDCAN_RXF0C_F0SA_SHIFT) & FDCAN_RXF0C_F0SA_MASK; |
| regval |= (NUM_RX_FIFO0 << FDCAN_RXF0C_F0S_SHIFT) & FDCAN_RXF0C_F0S_MASK; |
| putreg32(regval, priv->base + STM32_FDCAN_RXF0C_OFFSET); |
| ram_offset += NUM_RX_FIFO0 * FIFO_ELEMENT_SIZE; |
| |
| /* Not using Rx FIFO1 */ |
| |
| /* Assign Tx Mailbox pointer in the driver structure */ |
| |
| priv->message_ram.txfifo_addr = gl_ram_base + ram_offset * WORD_LENGTH; |
| priv->tx = (struct tx_fifo_s *)(priv->message_ram.txfifo_addr); |
| |
| /* Set Tx FIFO size (32 elements max) */ |
| |
| regval = (NUM_TX_FIFO << FDCAN_TXBC_TFQS_SHIFT) & FDCAN_TXBC_TFQS_MASK; |
| regval &= ~FDCAN_TXBC_TFQM; /* Use FIFO */ |
| regval |= (ram_offset << FDCAN_TXBC_TBSA_SHIFT) & FDCAN_TXBC_TBSA_MASK; |
| putreg32(regval, priv->base + STM32_FDCAN_TXBC_OFFSET); |
| |
| /* Default filter configuration - Accept all messages into Rx FIFO0 */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_GFC_OFFSET); |
| regval &= ~FDCAN_GFC_ANFS; /* Accept non-matching stdid frames into FIFO0 */ |
| regval &= ~FDCAN_GFC_ANFE; /* Accept non-matching extid frames into FIFO0 */ |
| putreg32(regval, priv->base + STM32_FDCAN_GFC_OFFSET); |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| fdcan_dumpregs(priv); |
| #endif |
| |
| /* Exit Initialization mode */ |
| |
| fdcan_setinit(priv->base, 0); |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| fdcan_dumpregs(priv); |
| #endif |
| |
| leave_critical_section(flags); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: fdcan_reset |
| * |
| * Description: |
| * Put the device in the non-operational, reset state |
| * |
| * Input Parameters: |
| * priv - Pointer to the private FDCAN driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * The device has previously been initialized, including message RAM |
| ****************************************************************************/ |
| |
| static void fdcan_reset(struct fdcan_driver_s *priv) |
| { |
| /* Request Init Mode */ |
| |
| irqstate_t flags = enter_critical_section(); |
| |
| fdcan_setenable(priv->base, 1); |
| fdcan_setinit(priv->base, 1); |
| |
| /* Enable Configuration Change Mode */ |
| |
| fdcan_setconfig(priv->base, 1); |
| |
| /* Disable interrupts and clear all interrupt flags */ |
| |
| fdcan_disable_interrupts(priv); |
| |
| putreg32(FDCAN_IR_MASK, priv->base + STM32_FDCAN_IR_OFFSET); |
| |
| /* Clear all message RAM mailboxes if initialized */ |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| const uint8_t n_data_words = 16; |
| #else |
| const uint8_t n_data_words = 2; |
| #endif |
| |
| if (priv->rx) |
| { |
| for (uint32_t i = 0; i < NUM_RX_FIFO0; i++) |
| { |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| ninfo("[fdcan] MB RX %i %p\r\n", i, &priv->rx[i]); |
| #endif |
| priv->rx[i].header.w1 = 0x0; |
| priv->rx[i].header.w0 = 0x0; |
| for (uint8_t j = 0; j < n_data_words; j++) |
| { |
| priv->rx[i].data[j].word = 0x0; |
| } |
| } |
| } |
| |
| if (priv->tx) |
| { |
| for (uint32_t i = 0; i < NUM_TX_FIFO; i++) |
| { |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| ninfo("[fdcan] MB TX %i %p\r\n", i, &priv->tx[i]); |
| #endif |
| priv->tx[i].header.w1 = 0x0; |
| priv->tx[i].header.w0 = 0x0; |
| for (uint8_t j = 0; j < n_data_words; j++) |
| { |
| priv->tx[i].data[j].word = 0x0; |
| } |
| } |
| } |
| |
| /* Power off the device -- See RM0433 pg 2493 */ |
| |
| fdcan_setinit(priv->base, 0); |
| fdcan_setenable(priv->base, 0); |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Function: stm32_fdcansockinitialize |
| * |
| * Description: |
| * Initialize the selected CAN peripheral and network (socket) interface |
| * |
| * Input Parameters: |
| * intf - In the case where there are multiple interfaces, this value |
| * identifies which interface is to be initialized. |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| int stm32_fdcansockinitialize(int intf) |
| { |
| struct fdcan_driver_s *priv; |
| |
| switch (intf) |
| { |
| #ifdef CONFIG_STM32H7_FDCAN1 |
| case 0: |
| priv = &g_fdcan0; |
| memset(priv, 0, sizeof(struct fdcan_driver_s)); |
| priv->base = STM32_FDCAN1_BASE; |
| priv->iface_idx = 0; |
| priv->config = &stm32_fdcan0_config; |
| |
| /* Default bitrate configuration */ |
| |
| # ifdef CONFIG_NET_CAN_CANFD |
| priv->arbi_timing.bitrate = CONFIG_FDCAN1_ARBI_BITRATE; |
| priv->data_timing.bitrate = CONFIG_FDCAN1_DATA_BITRATE; |
| # else |
| priv->arbi_timing.bitrate = CONFIG_FDCAN1_BITRATE; |
| # endif |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN2 |
| case 1: |
| priv = &g_fdcan1; |
| memset(priv, 0, sizeof(struct fdcan_driver_s)); |
| priv->base = STM32_FDCAN2_BASE; |
| priv->iface_idx = 1; |
| priv->config = &stm32_fdcan1_config; |
| |
| /* Default bitrate configuration */ |
| |
| # ifdef CONFIG_NET_CAN_CANFD |
| priv->arbi_timing.bitrate = CONFIG_FDCAN2_ARBI_BITRATE; |
| priv->data_timing.bitrate = CONFIG_FDCAN2_DATA_BITRATE; |
| # else |
| priv->arbi_timing.bitrate = CONFIG_FDCAN2_BITRATE; |
| # endif |
| break; |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN3 |
| case 2: |
| priv = &g_fdcan2 |
| memset(priv, 0, sizeof(struct fdcan_driver_s)); |
| priv->base = STM32_FDCAN3_BASE; |
| priv->iface_idx = 2; |
| priv->config = &stm32_fdcan2_config; |
| |
| /* Default bitrate configuration */ |
| |
| # ifdef CONFIG_NET_CAN_CANFD |
| priv->arbi_timing.bitrate = CONFIG_FDCAN3_ARBI_BITRATE; |
| priv->data_timing.bitrate = CONFIG_FDCAN3_DATA_BITRATE; |
| # else |
| priv->arbi_timing.bitrate = CONFIG_FDCAN3_BITRATE; |
| # endif |
| break; |
| #endif |
| |
| default: |
| return -ENODEV; |
| } |
| |
| if (fdcan_bittiming(&priv->arbi_timing) != OK) |
| { |
| printf("ERROR: Invalid CAN timings\n"); |
| return -1; |
| } |
| |
| #ifdef CONFIG_NET_CAN_CANFD |
| if (fdcan_bittiming(&priv->data_timing) != OK) |
| { |
| printf("ERROR: Invalid CAN data phase timings\n"); |
| return -1; |
| } |
| #endif |
| |
| /* Configure the pins we're using to interface to the controller */ |
| |
| stm32_configgpio(priv->config->tx_pin); |
| stm32_configgpio(priv->config->rx_pin); |
| |
| /* Attach the fdcan interrupt handlers */ |
| |
| if (irq_attach(priv->config->mb_irq[0], fdcan_interrupt, priv)) |
| { |
| /* We could not attach the ISR to the interrupt */ |
| |
| printf("ERROR: Failed to attach CAN RX IRQ\n"); |
| return -EAGAIN; |
| } |
| |
| if (irq_attach(priv->config->mb_irq[1], fdcan_interrupt, priv)) |
| { |
| /* We could not attach the ISR to the interrupt */ |
| |
| printf("ERROR: Failed to attach CAN TX IRQ\n"); |
| return -EAGAIN; |
| } |
| |
| /* Initialize the driver structure */ |
| |
| priv->dev.d_ifup = fdcan_ifup; /* I/F up callback */ |
| priv->dev.d_ifdown = fdcan_ifdown; /* I/F down callback */ |
| priv->dev.d_txavail = fdcan_txavail; /* New TX data callback */ |
| #ifdef CONFIG_NETDEV_IOCTL |
| priv->dev.d_ioctl = fdcan_netdev_ioctl; /* Support CAN ioctl() calls */ |
| #endif |
| priv->dev.d_private = (void *)priv; /* Used to recover private state from dev */ |
| |
| priv->dev.d_buf = priv->tx_pool; |
| |
| priv->rx = NULL; |
| priv->tx = NULL; |
| |
| /* Put the interface in the down state (disable interrupts, power off) */ |
| |
| fdcan_ifdown(&priv->dev); |
| |
| /* Register the device with the OS so that socket IOCTLs can be performed */ |
| |
| netdev_register(&priv->dev, NET_LL_CAN); |
| |
| #ifdef CONFIG_STM32H7_FDCAN_REGDEBUG |
| fdcan_dumpregs(priv); |
| #endif |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: arm_netinitialize |
| * |
| * Description: |
| * Initialize the CAN device interfaces. If there is more than one device |
| * interface in the chip, then board-specific logic will have to provide |
| * this function to determine which, if any, CAN interfaces should be |
| * initialized. |
| * |
| ****************************************************************************/ |
| |
| #if !defined(CONFIG_NETDEV_LATEINIT) |
| void arm_netinitialize(void) |
| { |
| #ifdef CONFIG_STM32H7_FDCAN1 |
| stm32_fdcansockinitialize(0); |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN2 |
| stm32_fdcansockinitialize(1); |
| #endif |
| |
| #ifdef CONFIG_STM32H7_FDCAN3 |
| stm32_fdcansockinitialize(2); |
| #endif |
| } |
| #endif |
| |
| #ifdef CONFIG_NET_CAN_ERRORS |
| /**************************************************************************** |
| * Name: fdcan_error_work |
| ****************************************************************************/ |
| |
| static void fdcan_error_work(void *arg) |
| { |
| struct fdcan_driver_s *priv = (struct fdcan_driver_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 = getreg32(priv->base + STM32_FDCAN_IR_OFFSET); |
| ie = getreg32(priv->base + STM32_FDCAN_IE_OFFSET); |
| psr = getreg32(priv->base + STM32_FDCAN_PSR_OFFSET); |
| |
| pending = (ir); |
| |
| /* 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 |
| */ |
| |
| if ((psr & FDCAN_PSR_LEC_MASK) != 0) |
| { |
| ie &= ~(FDCAN_IE_PEAE | FDCAN_IE_PEDE); |
| putreg32(ie, priv->base + STM32_FDCAN_IE_OFFSET); |
| } |
| |
| /* Clear the error indications */ |
| |
| putreg32(FDCAN_CMNERR_INTS, priv->base + STM32_FDCAN_IR_OFFSET); |
| } |
| |
| /* 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 */ |
| |
| putreg32(FDCAN_TXERR_INTS, priv->base + STM32_FDCAN_IR_OFFSET); |
| } |
| |
| /* 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); |
| putreg32(ie, priv->base + STM32_FDCAN_IE_OFFSET); |
| |
| /* Clear the error indications */ |
| |
| putreg32(FDCAN_RXERR_INTS, priv->base + STM32_FDCAN_IR_OFFSET); |
| } |
| |
| /* Report errors */ |
| |
| net_lock(); |
| fdcan_error(priv, pending & FDCAN_ANYERR_INTS, psr); |
| net_unlock(); |
| |
| /* 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 |
| * psr - psr register content |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_error(struct fdcan_driver_s *priv, uint32_t status, |
| uint32_t psr) |
| { |
| struct can_frame *frame = (struct can_frame *)priv->rx_pool; |
| uint32_t errbits = 0; |
| uint8_t data[CAN_ERR_DLC]; |
| uint32_t regval; |
| |
| 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. |
| */ |
| |
| errbits |= CAN_ERR_CNT; |
| regval = getreg32(priv->base + STM32_FDCAN_ECR_OFFSET); |
| data[6] = (uint8_t)((regval & FDCAN_ECR_TEC_MASK) >> FDCAN_ECR_TEC_SHIFT); |
| data[7] = (uint8_t)((regval & FDCAN_ECR_TREC_MASK) >> |
| FDCAN_ECR_TREC_SHIFT); |
| |
| if ((psr & FDCAN_PSR_EP) != 0) |
| { |
| if ((regval & FDCAN_ECR_RP) != 0) |
| { |
| data[1] |= (CAN_ERR_CRTL_RX_PASSIVE); |
| } |
| |
| if (data[6] >= CAN_ERROR_PASSIVE_THRESHOLD) |
| { |
| data[1] |= (CAN_ERR_CRTL_TX_PASSIVE); |
| } |
| } |
| |
| if ((psr & FDCAN_PSR_EW) != 0) |
| { |
| if ((data[6] >= CAN_ERROR_WARNING_THRESHOLD)) |
| { |
| data[1] |= CAN_ERR_CRTL_TX_WARNING; |
| } |
| |
| if ((data[7] >= CAN_ERROR_WARNING_THRESHOLD)) |
| { |
| data[1] |= CAN_ERR_CRTL_RX_WARNING; |
| } |
| } |
| |
| if ((status & (FDCAN_IR_EP | FDCAN_IR_EW)) != 0) |
| { |
| /* "Error Passive" or "Error Warning" status changed */ |
| |
| errbits |= CAN_ERR_CRTL; |
| } |
| |
| if ((status & FDCAN_IR_PEA) != 0) |
| { |
| /* Protocol Error in Arbitration Phase */ |
| |
| if ((psr & FDCAN_PSR_ACT) == FDCAN_PSR_ACT) |
| { |
| /* transmit error */ |
| |
| data[2] |= CAN_ERR_PROT_TX; |
| } |
| |
| switch (psr & FDCAN_PSR_LEC_MASK) |
| { |
| case FDCAN_PSR_LEC(FDCAN_PSR_EC_STUFF_ERROR): |
| |
| /* Stuff Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_STUFF; |
| break; |
| |
| case FDCAN_PSR_LEC(FDCAN_PSR_EC_FORM_ERROR): |
| |
| /* Format Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_FORM; |
| break; |
| |
| case FDCAN_PSR_LEC(FDCAN_PSR_EC_ACK_ERROR): |
| |
| /* Acknowledge Error */ |
| |
| errbits |= (CAN_ERR_PROT | CAN_ERR_ACK); |
| break; |
| |
| case FDCAN_PSR_LEC(FDCAN_PSR_EC_BIT0_ERROR): |
| |
| /* Bit0 Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_BIT0; |
| break; |
| |
| case FDCAN_PSR_LEC(FDCAN_PSR_EC_BIT1_ERROR): |
| |
| /* Bit1 Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_BIT1; |
| break; |
| |
| case FDCAN_PSR_LEC(FDCAN_PSR_EC_CRC_ERROR): |
| |
| /* Receive CRC Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[3] |= (CAN_ERR_PROT_LOC_CRC_SEQ | CAN_ERR_PROT_LOC_CRC_DEL); |
| break; |
| |
| case FDCAN_PSR_LEC(FDCAN_PSR_EC_NO_CHANGE): |
| |
| /* No change (nothing has cleared the error) */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_UNSPEC; |
| break; |
| |
| default: |
| |
| /* no error */ |
| |
| break; |
| } |
| } |
| |
| if ((status & FDCAN_IR_PED) != 0) |
| { |
| /* Protocol Error in Data Phase */ |
| |
| if ((psr & FDCAN_PSR_ACT) == FDCAN_PSR_ACT) |
| { |
| /* transmit error */ |
| |
| data[2] |= CAN_ERR_PROT_TX; |
| } |
| |
| switch (psr & FDCAN_PSR_DLEC_MASK) |
| { |
| case FDCAN_PSR_DLEC(FDCAN_PSR_EC_STUFF_ERROR): |
| |
| /* Stuff Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_STUFF; |
| break; |
| |
| case FDCAN_PSR_DLEC(FDCAN_PSR_EC_FORM_ERROR): |
| |
| /* Format Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_FORM; |
| break; |
| |
| case FDCAN_PSR_DLEC(FDCAN_PSR_EC_ACK_ERROR): |
| |
| /* Acknowledge Error */ |
| |
| errbits |= (CAN_ERR_ACK | CAN_ERR_PROT); |
| break; |
| |
| case FDCAN_PSR_DLEC(FDCAN_PSR_EC_BIT0_ERROR): |
| |
| /* Bit0 Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_BIT0; |
| break; |
| |
| case FDCAN_PSR_DLEC(FDCAN_PSR_EC_BIT1_ERROR): |
| |
| /* Bit1 Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_BIT1; |
| break; |
| |
| case FDCAN_PSR_DLEC(FDCAN_PSR_EC_CRC_ERROR): |
| |
| /* Receive CRC Error */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[3] |= (CAN_ERR_PROT_LOC_CRC_SEQ | CAN_ERR_PROT_LOC_CRC_DEL); |
| break; |
| |
| case FDCAN_PSR_DLEC(FDCAN_PSR_EC_NO_CHANGE): |
| |
| /* No change (nothing has cleared the error) */ |
| |
| errbits |= CAN_ERR_PROT; |
| data[2] |= CAN_ERR_PROT_UNSPEC; |
| break; |
| |
| default: |
| |
| /* no error */ |
| |
| break; |
| } |
| } |
| |
| if ((status & FDCAN_IR_BO) != 0) |
| { |
| /* Bus_Off Status changed */ |
| |
| if ((psr & FDCAN_PSR_BO) != 0) |
| { |
| errbits |= CAN_ERR_BUSOFF; |
| } |
| else |
| { |
| errbits |= CAN_ERR_RESTARTED; |
| } |
| } |
| |
| if ((status & (FDCAN_IR_RF0L | FDCAN_IR_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_IR_TEFL) != 0) |
| { |
| /* Tx Event FIFO Element Lost */ |
| |
| errbits |= CAN_ERR_CRTL; |
| data[1] |= CAN_ERR_CRTL_TX_OVERFLOW; |
| } |
| |
| if ((status & FDCAN_IR_TOO) != 0) |
| { |
| /* Timeout Occurred */ |
| |
| errbits |= CAN_ERR_TX_TIMEOUT; |
| } |
| |
| if ((status & (FDCAN_IR_MRAF | FDCAN_IR_ELO)) != 0) |
| { |
| /* Message RAM Access Failure |
| * Error Logging Overflow |
| */ |
| |
| errbits |= CAN_ERR_CRTL; |
| data[1] |= CAN_ERR_CRTL_UNSPEC; |
| } |
| |
| errbits |= CAN_ERR_FLAG; |
| |
| if (errbits != 0) |
| { |
| nerr("ERROR: errbits = %lx" 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->tx_pool; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: fdcan_errint |
| * |
| * Description: |
| * Call to enable or disable CAN error interrupts. |
| * |
| * Input Parameters: |
| * priv - reference to the private CAN driver state structure |
| * enable - enable or disable |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void fdcan_errint(struct fdcan_driver_s *priv, bool enable) |
| { |
| const struct fdcan_config_s *config = NULL; |
| uint32_t regval = 0; |
| |
| DEBUGASSERT(priv); |
| config = priv->config; |
| DEBUGASSERT(config); |
| |
| /* Enable/disable the transmit mailbox interrupt */ |
| |
| regval = getreg32(priv->base + STM32_FDCAN_IE_OFFSET); |
| if (enable) |
| { |
| regval |= FDCAN_ANYERR_INTS; |
| } |
| else |
| { |
| regval &= ~FDCAN_ANYERR_INTS; |
| } |
| |
| putreg32(regval, priv->base + STM32_FDCAN_IE_OFFSET); |
| } |
| #endif |
| |