nuttx/arch/arm/src/stm32h7/stm32_fdcan_sock.c
Julian Oes 4f30c298bf stm32h7: reduce the extended filter size to 64
When I checked how this register was set I discovered that 128 was not
accepted by the H7 but 64 was ok. Looking at the STM32Cube's HAL it
seems to be only 64 words long, however, the reference manual claims
otherwise.

I have opened a discussion on the ST community forum
https://community.st.com/s/question/0D73W000001nzqFSAQ
but unfortunately not received an answer yet.

In the meantime, I think, we should update this to what I found to be
working though.

Signed-off-by: Julian Oes <julian@oes.ch>
2023-02-15 19:06:54 +08:00

3048 lines
83 KiB
C

/****************************************************************************
* 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 = %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);
#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 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);
#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