/**************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_NET_CAN_RAW_TX_DEADLINE) || defined(CONFIG_NET_TIMESTAMP) #include #endif #include #include "arm_internal.h" #include "chip.h" #include "stm32.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* 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; 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); /**************************************************************************** * 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 = %d\n", regval & FDCAN_PSR_LEC); } } #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); 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); /* 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) { switch (irq) { #ifdef CONFIG_STM32H7_FDCAN1 case STM32_IRQ_FDCAN1_0: fdcan_receive(&g_fdcan0); break; case STM32_IRQ_FDCAN1_1: fdcan_txdone(&g_fdcan0); break; #endif #ifdef CONFIG_STM32H7_FDCAN2 case STM32_IRQ_FDCAN2_0: fdcan_receive(&g_fdcan1); break; case STM32_IRQ_FDCAN2_1: fdcan_txdone(&g_fdcan1); break; #endif #ifdef CONFIG_STM32H7_FDCAN3 case STM32_IRQ_FDCAN3_0: fdcan_receive(&g_fdcan2); break; case STM32_IRQ_FDCAN3_1: fdcan_txdone(&g_fdcan2); break; #endif default: 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 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 const fdcan_bitseg *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); /* 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 128 filters (128 words) */ const uint8_t n_extid = 128; 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, NULL)) { /* 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, NULL)) { /* 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