drivers: can: Implement Bosch M_CAN driver

Implementation of the Bosch M_CAN IP driver.
This driver is just the base for a specific SoC implementation.

Signed-off-by: Alexander Wachter <alexander@wachter.cloud>
This commit is contained in:
Alexander Wachter 2021-02-11 20:07:04 +01:00 committed by Kumar Gala
commit 1c50ef6c43
12 changed files with 2846 additions and 2 deletions

View file

@ -4,6 +4,8 @@ zephyr_sources_ifdef(CONFIG_CAN can_common.c)
zephyr_sources_ifdef(CONFIG_CAN_LOOPBACK can_loopback.c)
zephyr_sources_ifdef(CONFIG_CAN_MCP2515 can_mcp2515.c)
zephyr_sources_ifdef(CONFIG_CAN_STM32 can_stm32.c)
zephyr_sources_ifdef(CONFIG_CAN_STM32FD can_stm32fd.c)
zephyr_sources_ifdef(CONFIG_CAN_MCAN can_mcan.c)
zephyr_sources_ifdef(CONFIG_CAN_MCUX_FLEXCAN can_mcux_flexcan.c)
zephyr_sources_ifdef(CONFIG_USERSPACE can_handlers.c)

View file

@ -24,8 +24,15 @@ config CAN_SHELL
help
Enable CAN Shell for testing.
config CAN_FD_MODE
config CAN_HAS_CANFD
bool
help
driver supports CAN-FD
config CAN_FD_MODE
bool "CAN-FD"
default y
depends on CAN_HAS_CANFD
help
Enable CAN-FD compatible API
@ -59,7 +66,7 @@ config CAN_WORKQ_FRAMES_BUF_CNT
config CAN_RX_TIMESTAMP
bool "Enable receiving timestamps"
depends on CAN_STM32 || CAN_MCUX_FLEXCAN
depends on CAN_STM32 || CAN_MCUX_FLEXCAN || CAN_STM32FD
help
This option enables a timestamp value of the CAN free running timer.
The value is incremented every bit time and starts when the controller
@ -75,8 +82,10 @@ config CAN_AUTO_BUS_OFF_RECOVERY
available.
source "drivers/can/Kconfig.stm32"
source "drivers/can/Kconfig.stm32fd"
source "drivers/can/Kconfig.mcux"
source "drivers/can/Kconfig.mcp2515"
source "drivers/can/Kconfig.mcan"
source "drivers/can/Kconfig.loopback"
source "drivers/can/Kconfig.net"

22
drivers/can/Kconfig.mcan Normal file
View file

@ -0,0 +1,22 @@
# Bosch m_can configuration options
# Copyright (c) 2020 Alexander Wachter
# SPDX-License-Identifier: Apache-2.0
config CAN_MCAN
bool
select CAN_HAS_CANFD
help
Enable Bosch m_can driver.
This driver supports the Bosch m_can IP. This IP is built into the
STM32G4, STM32G0 and the Microchip SAM controllers with CAN-FD.
if CAN_MCAN
config CAN_DELAY_COMP
bool "Enable transceiver delay compensation"
default y
help
Enable the automatic transceiver delay compensation.
endif #CAN_MCAN

View file

@ -0,0 +1,35 @@
# STM32 CAN configuration options
if CAN_MCAN && SOC_SERIES_STM32G4X
config CAN_STM32FD
bool
default y
config CAN_MAX_STD_ID_FILTER
int "Maximum number of std ID filters"
default 28
range 0 28
help
Defines the maximum number of filters with standard ID (11-bit)
that can be attached.
config CAN_MAX_EXT_ID_FILTER
int "Maximum number of ext ID filters"
default 8
range 0 8
help
Defines the maximum number of filters with extended ID (29-bit)
that can be attached.
config CAN_CKDIV
int "CKDIV register value"
range 0 15
default 0
help
This value is written to the CKDIV register.
The APB clock is divided according to this value before it is feed to
CAN core. Note that the the divider affects all CAN controllers.
The values of the register are multiplied by two and zero corresponds
to one. The value six, for example results in a clock divided by 12.
endif #CAN_MCAN

875
drivers/can/can_mcan.c Normal file
View file

@ -0,0 +1,875 @@
/*
* Copyright (c) 2020 Alexander Wachter
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/util.h>
#include <string.h>
#include <kernel.h>
#include <drivers/can.h>
#include "can_mcan.h"
#include "can_mcan_int.h"
#include <logging/log.h>
LOG_MODULE_DECLARE(can_driver, CONFIG_CAN_LOG_LEVEL);
#define CAN_INIT_TIMEOUT (100)
#define CAN_DIV_CEIL(val, div) (((val) + (div) - 1) / (div))
#ifdef CONFIG_CAN_FD_MODE
#define MCAN_MAX_DLC CANFD_MAX_DLC
#else
#define MCAN_MAX_DLC CAN_MAX_DLC
#endif
static int can_exit_sleep_mode(struct can_mcan_reg *can)
{
uint32_t start_time;
can->cccr &= ~CAN_MCAN_CCCR_CSR;
start_time = k_cycle_get_32();
while ((can->cccr & CAN_MCAN_CCCR_CSA) == CAN_MCAN_CCCR_CSA) {
if (k_cycle_get_32() - start_time >
k_ms_to_cyc_ceil32(CAN_INIT_TIMEOUT)) {
can->cccr |= CAN_MCAN_CCCR_CSR;
return CAN_TIMEOUT;
}
}
return 0;
}
static int can_enter_init_mode(struct can_mcan_reg *can, k_timeout_t timeout)
{
int64_t start_time;
can->cccr |= CAN_MCAN_CCCR_INIT;
start_time = k_uptime_ticks();
while ((can->cccr & CAN_MCAN_CCCR_INIT) == 0U) {
if (k_uptime_ticks() - start_time > timeout.ticks) {
can->cccr &= ~CAN_MCAN_CCCR_INIT;
return CAN_TIMEOUT;
}
}
return 0;
}
static int can_leave_init_mode(struct can_mcan_reg *can, k_timeout_t timeout)
{
int64_t start_time;
can->cccr &= ~CAN_MCAN_CCCR_INIT;
start_time = k_uptime_ticks();
while ((can->cccr & CAN_MCAN_CCCR_INIT) != 0U) {
if (k_uptime_ticks() - start_time > timeout.ticks) {
return CAN_TIMEOUT;
}
}
return 0;
}
void can_mcan_configure_timing(struct can_mcan_reg *can,
const struct can_timing *timing,
const struct can_timing *timing_data)
{
if (timing) {
__ASSERT_NO_MSG(timing->prop_seg == 0);
__ASSERT_NO_MSG(timing->phase_seg1 <= 0x100 &&
timing->phase_seg1 > 0);
__ASSERT_NO_MSG(timing->phase_seg2 <= 0x80 &&
timing->phase_seg2 > 0);
__ASSERT_NO_MSG(timing->prescaler <= 0x200 &&
timing->prescaler > 0);
can->nbtp = (((uint32_t)timing->phase_seg1 - 1UL) & 0xFF) <<
CAN_MCAN_NBTP_NTSEG1_POS |
(((uint32_t)timing->phase_seg2 - 1UL) & 0x7F) <<
CAN_MCAN_NBTP_NTSEG2_POS |
(((uint32_t)timing->sjw - 1UL) & 0x7F) <<
CAN_MCAN_NBTP_NSJW_POS |
(((uint32_t)timing->prescaler - 1UL) & 0x1FF) <<
CAN_MCAN_NBTP_NBRP_POS;
}
#ifdef CONFIG_CAN_FD_MODE
if (timing_data) {
__ASSERT_NO_MSG(timing_data->prop_seg == 0);
__ASSERT_NO_MSG(timing_data->phase_seg1 <= 0x20 &&
timing_data->phase_seg1 > 0);
__ASSERT_NO_MSG(timing_data->phase_seg2 <= 0x10 &&
timing_data->phase_seg2 > 0);
__ASSERT_NO_MSG(timing_data->prescaler <= 20 &&
timing_data->prescaler > 0);
can->dbtp = (((uint32_t)timing_data->phase_seg1 - 1UL) & 0x1F) <<
CAN_MCAN_DBTP_DTSEG1_POS |
(((uint32_t)timing_data->phase_seg2 - 1UL) & 0x0F) <<
CAN_MCAN_DBTP_DTSEG2_POS |
(((uint32_t)timing_data->sjw - 1UL) & 0x0F) <<
CAN_MCAN_DBTP_DSJW_POS |
(((uint32_t)timing_data->prescaler - 1UL) & 0x1F) <<
CAN_MCAN_DBTP_DBRP_POS;
}
#endif
}
int can_mcan_set_timing(const struct can_mcan_config *cfg,
const struct can_timing *timing,
const struct can_timing *timing_data)
{
struct can_mcan_reg *can = cfg->can;
int ret;
ret = can_enter_init_mode(can, K_MSEC(CAN_INIT_TIMEOUT));
if (ret) {
LOG_ERR("Failed to enter init mode");
return -EIO;
}
can_mcan_configure_timing(can, timing, timing_data);
ret = can_leave_init_mode(can, K_MSEC(CAN_INIT_TIMEOUT));
if (ret) {
LOG_ERR("Failed to leave init mode");
return -EIO;
}
return 0;
}
int can_mcan_set_mode(const struct can_mcan_config *cfg, enum can_mode mode)
{
struct can_mcan_reg *can = cfg->can;
int ret;
ret = can_enter_init_mode(can, K_MSEC(CAN_INIT_TIMEOUT));
if (ret) {
LOG_ERR("Failed to enter init mode");
return -EIO;
}
/* Configuration Change Enable */
can->cccr |= CAN_MCAN_CCCR_CCE;
switch (mode) {
case CAN_NORMAL_MODE:
LOG_DBG("Config normal mode");
can->cccr &= ~(CAN_MCAN_CCCR_TEST | CAN_MCAN_CCCR_MON);
break;
case CAN_SILENT_MODE:
LOG_DBG("Config silent mode");
can->cccr &= ~CAN_MCAN_CCCR_TEST;
can->cccr |= CAN_MCAN_CCCR_MON;
break;
case CAN_LOOPBACK_MODE:
LOG_DBG("Config loopback mode");
can->cccr &= ~CAN_MCAN_CCCR_MON;
can->cccr |= CAN_MCAN_CCCR_TEST;
can->test |= CAN_MCAN_TEST_LBCK;
break;
case CAN_SILENT_LOOPBACK_MODE:
LOG_DBG("Config silent loopback mode");
can->cccr |= (CAN_MCAN_CCCR_TEST | CAN_MCAN_CCCR_MON);
can->test |= CAN_MCAN_TEST_LBCK;
break;
default:
break;
}
ret = can_leave_init_mode(can, K_MSEC(CAN_INIT_TIMEOUT));
if (ret) {
LOG_ERR("Failed to leave init mode");
}
return 0;
}
int can_mcan_init(const struct device *dev, const struct can_mcan_config *cfg,
struct can_mcan_msg_sram *msg_ram,
struct can_mcan_data *data)
{
struct can_mcan_reg *can = cfg->can;
struct can_timing timing;
#ifdef CONFIG_CAN_FD_MODE
struct can_timing timing_data;
#endif
int ret;
k_mutex_init(&data->inst_mutex);
k_mutex_init(&data->tx_mtx);
k_sem_init(&data->tx_sem, NUM_TX_BUF_ELEMENTS, NUM_TX_BUF_ELEMENTS);
for (int i = 0; i < ARRAY_SIZE(data->tx_fin_sem); ++i) {
k_sem_init(&data->tx_fin_sem[i], 0, 1);
}
ret = can_exit_sleep_mode(can);
if (ret) {
LOG_ERR("Failed to exit sleep mode");
return -EIO;
}
ret = can_enter_init_mode(can, K_MSEC(CAN_INIT_TIMEOUT));
if (ret) {
LOG_ERR("Failed to enter init mode");
return -EIO;
}
/* Configuration Change Enable */
can->cccr |= CAN_MCAN_CCCR_CCE;
LOG_DBG("IP rel: %lu.%lu.%lu %02lu.%lu.%lu",
(can->crel & CAN_MCAN_CREL_REL) >> CAN_MCAN_CREL_REL_POS,
(can->crel & CAN_MCAN_CREL_STEP) >> CAN_MCAN_CREL_STEP_POS,
(can->crel & CAN_MCAN_CREL_SUBSTEP) >>
CAN_MCAN_CREL_SUBSTEP_POS,
(can->crel & CAN_MCAN_CREL_YEAR) >> CAN_MCAN_CREL_YEAR_POS,
(can->crel & CAN_MCAN_CREL_MON) >> CAN_MCAN_CREL_MON_POS,
(can->crel & CAN_MCAN_CREL_DAY) >> CAN_MCAN_CREL_DAY_POS);
#ifndef CONFIG_CAN_STM32FD
can->sidfc = ((uint32_t)msg_ram->std_filt & CAN_MCAN_SIDFC_FLSSA_MSK) |
(ARRAY_SIZE(msg_ram->std_filt) << CAN_MCAN_SIDFC_LSS_POS);
can->xidfc = ((uint32_t)msg_ram->ext_filt & CAN_MCAN_XIDFC_FLESA_MSK) |
(ARRAY_SIZE(msg_ram->ext_filt) << CAN_MCAN_XIDFC_LSS_POS);
can->rxf0c = ((uint32_t)msg_ram->rx_fifo0 & CAN_MCAN_RXF0C_F0SA) |
(ARRAY_SIZE(msg_ram->rx_fifo0) << CAN_MCAN_RXF0C_F0S_POS);
can->rxf1c = ((uint32_t)msg_ram->rx_fifo1 & CAN_MCAN_RXF1C_F1SA) |
(ARRAY_SIZE(msg_ram->rx_fifo1) << CAN_MCAN_RXF1C_F1S_POS);
can->rxbc = ((uint32_t)msg_ram->rx_buffer & CAN_MCAN_RXBC_RBSA);
can->txefc = ((uint32_t)msg_ram->tx_event_fifo & CAN_MCAN_TXEFC_EFSA_MSK) |
(ARRAY_SIZE(msg_ram->tx_event_fifo) <<
CAN_MCAN_TXEFC_EFS_POS);
can->txbc = ((uint32_t)msg_ram->tx_fifo & CAN_MCAN_TXBC_TBSA) |
(ARRAY_SIZE(msg_ram->tx_fifo) << CAN_MCAN_TXBC_TFQS_POS);
if (sizeof(msg_ram->tx_fifo[0].data) <= 24) {
can->txesc = (sizeof(msg_ram->tx_fifo[0].data) - 8) / 4;
} else {
can->txesc = (sizeof(msg_ram->tx_fifo[0].data) - 32) / 16 + 5;
}
if (sizeof(msg_ram->rx_fifo0[0].data) <= 24) {
can->rxesc = (((sizeof(msg_ram->rx_fifo0[0].data) - 8) / 4) <<
CAN_MCAN_RXESC_F0DS_POS) |
(((sizeof(msg_ram->rx_fifo1[0].data) - 8) / 4) <<
CAN_MCAN_RXESC_F1DS_POS) |
(((sizeof(msg_ram->rx_buffer[0].data) - 8) / 4) <<
CAN_MCAN_RXESC_RBDS_POS);
} else {
can->rxesc = (((sizeof(msg_ram->rx_fifo0[0].data) - 32)
/ 16 + 5) << CAN_MCAN_RXESC_F0DS_POS) |
(((sizeof(msg_ram->rx_fifo1[0].data) - 32)
/ 16 + 5) << CAN_MCAN_RXESC_F1DS_POS) |
(((sizeof(msg_ram->rx_buffer[0].data) - 32)
/ 16 + 5) << CAN_MCAN_RXESC_RBDS_POS);
}
#endif
#ifdef CONFIG_CAN_FD_MODE
can->cccr |= CAN_MCAN_CCCR_FDOE | CAN_MCAN_CCCR_BRSE;
#else
can->cccr &= ~(CAN_MCAN_CCCR_FDOE | CAN_MCAN_CCCR_BRSE);
#endif
can->cccr &= ~(CAN_MCAN_CCCR_TEST | CAN_MCAN_CCCR_MON |
CAN_MCAN_CCCR_ASM);
can->test &= ~(CAN_MCAN_TEST_LBCK);
#if defined(CONFIG_CAN_DELAY_COMP) && defined(CONFIG_CAN_FD_MODE)
can->dbtp |= CAN_MCAN_DBTP_TDC;
can->tdcr |= cfg->tx_delay_comp_offset << CAN_MCAN_TDCR_TDCO_POS;
#endif
#ifdef CONFIG_CAN_STM32FD
can->rxgfc |= (CONFIG_CAN_MAX_STD_ID_FILTER << CAN_MCAN_RXGFC_LSS_POS) |
(CONFIG_CAN_MAX_EXT_ID_FILTER << CAN_MCAN_RXGFC_LSE_POS) |
(0x2 << CAN_MCAN_RXGFC_ANFS_POS) |
(0x2 << CAN_MCAN_RXGFC_ANFE_POS);
#else
can->gfc |= (0x2 << CAN_MCAN_GFC_ANFE_POS) |
(0x2 << CAN_MCAN_GFC_ANFS_POS);
#endif /* CONFIG_CAN_STM32FD */
if (cfg->sample_point) {
ret = can_calc_timing(dev, &timing, cfg->bus_speed,
cfg->sample_point);
if (ret == -EINVAL) {
LOG_ERR("Can't find timing for given param");
return -EIO;
}
LOG_DBG("Presc: %d, TS1: %d, TS2: %d",
timing.prescaler, timing.phase_seg1, timing.phase_seg2);
LOG_DBG("Sample-point err : %d", ret);
} else if (cfg->prop_ts1) {
timing.prop_seg = 0;
timing.phase_seg1 = cfg->prop_ts1;
timing.phase_seg2 = cfg->ts2;
ret = can_calc_prescaler(dev, &timing, cfg->bus_speed);
if (ret) {
LOG_WRN("Bitrate error: %d", ret);
}
}
#ifdef CONFIG_CAN_FD_MODE
if (cfg->sample_point_data) {
ret = can_calc_timing_data(dev, &timing_data,
cfg->bus_speed_data,
cfg->sample_point_data);
if (ret == -EINVAL) {
LOG_ERR("Can't find timing for given dataphase param");
return -EIO;
}
LOG_DBG("Sample-point err data phase: %d", ret);
} else if (cfg->prop_ts1_data) {
timing_data.prop_seg = 0;
timing_data.phase_seg1 = cfg->prop_ts1_data;
timing_data.phase_seg2 = cfg->ts2_data;
ret = can_calc_prescaler(dev, &timing_data,
cfg->bus_speed_data);
if (ret) {
LOG_WRN("Dataphase bitrate error: %d", ret);
}
}
#endif
#ifdef CONFIG_CAN_FD_MODE
timing_data.sjw = cfg->sjw_data;
can_mcan_configure_timing(can, &timing, &timing_data);
#else
timing.sjw = cfg->sjw;
can_mcan_configure_timing(can, &timing, NULL);
#endif
can->ie = CAN_MCAN_IE_BO | CAN_MCAN_IE_EW | CAN_MCAN_IE_EP |
CAN_MCAN_IE_MRAF | CAN_MCAN_IE_TEFL | CAN_MCAN_IE_TEFN |
CAN_MCAN_IE_RF0N | CAN_MCAN_IE_RF1N | CAN_MCAN_IE_RF0L |
CAN_MCAN_IE_RF1L;
#ifdef CONFIG_CAN_STM32FD
can->ils = CAN_MCAN_ILS_RXFIFO0 | CAN_MCAN_ILS_RXFIFO1;
#else
can->ils = CAN_MCAN_ILS_RF0N | CAN_MCAN_ILS_RF1N;
#endif
can->ile = CAN_MCAN_ILE_EINT0 | CAN_MCAN_ILE_EINT1;
/* Interrupt on every TX fifo element*/
can->txbtie = CAN_MCAN_TXBTIE_TIE;
ret = can_leave_init_mode(can, K_MSEC(CAN_INIT_TIMEOUT));
if (ret) {
LOG_ERR("Failed to leave init mode");
return -EIO;
}
/* No memset because only aligned ptr are allowed */
for (uint32_t *ptr = (uint32_t *)msg_ram;
ptr < (uint32_t *)msg_ram +
sizeof(struct can_mcan_msg_sram) / sizeof(uint32_t);
ptr++) {
*ptr = 0;
}
return 0;
}
static void can_mcan_state_change_handler(const struct can_mcan_config *cfg,
struct can_mcan_data *data)
{
enum can_state state;
struct can_bus_err_cnt err_cnt;
state = can_mcan_get_state(cfg, &err_cnt);
if (data->state_change_isr) {
data->state_change_isr(state, err_cnt);
}
}
static void can_mcan_tc_event_handler(struct can_mcan_reg *can,
struct can_mcan_msg_sram *msg_ram,
struct can_mcan_data *data)
{
volatile struct can_mcan_tx_event_fifo *tx_event;
can_tx_callback_t tx_cb;
uint32_t event_idx, tx_idx;
while (can->txefs & CAN_MCAN_TXEFS_EFFL) {
event_idx = (can->txefs & CAN_MCAN_TXEFS_EFGI) >>
CAN_MCAN_TXEFS_EFGI_POS;
tx_event = &msg_ram->tx_event_fifo[event_idx];
tx_idx = tx_event->mm.idx;
/* Acknowledge TX event */
can->txefa = event_idx;
k_sem_give(&data->tx_sem);
tx_cb = data->tx_fin_cb[tx_idx];
if (tx_cb == NULL) {
k_sem_give(&data->tx_fin_sem[tx_idx]);
} else {
tx_cb(CAN_TX_OK, data->tx_fin_cb_arg[tx_idx]);
}
}
}
void can_mcan_line_0_isr(const struct can_mcan_config *cfg,
struct can_mcan_msg_sram *msg_ram,
struct can_mcan_data *data)
{
struct can_mcan_reg *can = cfg->can;
do {
if (can->ir & (CAN_MCAN_IR_BO | CAN_MCAN_IR_EP |
CAN_MCAN_IR_EW)) {
can->ir = CAN_MCAN_IR_BO | CAN_MCAN_IR_EP |
CAN_MCAN_IR_EW;
can_mcan_state_change_handler(cfg, data);
}
/* TX event FIFO new entry */
if (can->ir & CAN_MCAN_IR_TEFN) {
can->ir = CAN_MCAN_IR_TEFN;
can_mcan_tc_event_handler(can, msg_ram, data);
}
if (can->ir & CAN_MCAN_IR_TEFL) {
can->ir = CAN_MCAN_IR_TEFL;
LOG_ERR("TX FIFO element lost");
k_sem_give(&data->tx_sem);
}
if (can->ir & CAN_MCAN_IR_ARA) {
can->ir = CAN_MCAN_IR_ARA;
LOG_ERR("Access to reserved address");
}
if (can->ir & CAN_MCAN_IR_MRAF) {
can->ir = CAN_MCAN_IR_MRAF;
LOG_ERR("Message RAM access failure");
}
} while (can->ir & (CAN_MCAN_IR_BO | CAN_MCAN_IR_EW | CAN_MCAN_IR_EP |
CAN_MCAN_IR_TEFL | CAN_MCAN_IR_TEFN));
}
static void can_mcan_get_message(struct can_mcan_data *data,
volatile struct can_mcan_rx_fifo *fifo,
volatile uint32_t *fifo_status_reg,
volatile uint32_t *fifo_ack_reg)
{
uint32_t get_idx, filt_idx;
struct zcan_frame frame;
can_rx_callback_t cb;
volatile uint32_t *src, *dst, *end;
int data_length;
void *cb_arg;
struct can_mcan_rx_fifo_hdr hdr;
while ((*fifo_status_reg & CAN_MCAN_RXF0S_F0FL)) {
get_idx = (*fifo_status_reg & CAN_MCAN_RXF0S_F0GI) >>
CAN_MCAN_RXF0S_F0GI_POS;
hdr = fifo[get_idx].hdr;
if (hdr.xtd) {
frame.id = hdr.ext_id;
} else {
frame.id = hdr.std_id;
}
frame.fd = hdr.fdf;
frame.rtr = hdr.rtr ? CAN_REMOTEREQUEST :
CAN_DATAFRAME;
frame.id_type = hdr.xtd ? CAN_EXTENDED_IDENTIFIER :
CAN_STANDARD_IDENTIFIER;
frame.dlc = hdr.dlc;
frame.brs = hdr.brs;
#if defined(CONFIG_CAN_RX_TIMESTAMP)
frame.timestamp = hdr.rxts;
#endif
filt_idx = hdr.fidx;
/* Check if RTR must match */
if ((hdr.xtd && data->ext_filt_rtr_mask & (1U << filt_idx) &&
((data->ext_filt_rtr >> filt_idx) & 1U) != frame.rtr) ||
(data->std_filt_rtr_mask & (1U << filt_idx) &&
((data->std_filt_rtr >> filt_idx) & 1U) != frame.rtr)) {
continue;
}
data_length = can_dlc_to_bytes(frame.dlc);
if (data_length <= sizeof(frame.data)) {
/* data needs to be written in 32 bit blocks!*/
for (src = fifo[get_idx].data_32,
dst = frame.data_32,
end = dst + CAN_DIV_CEIL(data_length, sizeof(uint32_t));
dst < end;
src++, dst++) {
*dst = *src;
}
if (frame.id_type == CAN_STANDARD_IDENTIFIER) {
LOG_DBG("Frame on filter %d, ID: 0x%x",
filt_idx, frame.id);
cb = data->rx_cb_std[filt_idx];
cb_arg = data->cb_arg_std[filt_idx];
} else {
LOG_DBG("Frame on filter %d, ID: 0x%x",
filt_idx + NUM_STD_FILTER_DATA,
frame.id);
cb = data->rx_cb_ext[filt_idx];
cb_arg = data->cb_arg_ext[filt_idx];
}
if (cb) {
cb(&frame, cb_arg);
} else {
LOG_DBG("cb missing");
}
} else {
LOG_ERR("Frame is too big");
}
*fifo_ack_reg = get_idx;
}
}
void can_mcan_line_1_isr(const struct can_mcan_config *cfg,
struct can_mcan_msg_sram *msg_ram,
struct can_mcan_data *data)
{
struct can_mcan_reg *can = cfg->can;
do {
if (can->ir & CAN_MCAN_IR_RF0N) {
can->ir = CAN_MCAN_IR_RF0N;
LOG_DBG("RX FIFO0 INT");
can_mcan_get_message(data, msg_ram->rx_fifo0,
&can->rxf0s, &can->rxf0a);
}
if (can->ir & CAN_MCAN_IR_RF1N) {
can->ir = CAN_MCAN_IR_RF1N;
LOG_DBG("RX FIFO1 INT");
can_mcan_get_message(data, msg_ram->rx_fifo1,
&can->rxf1s, &can->rxf1a);
}
if (can->ir & CAN_MCAN_IR_RF0L) {
can->ir = CAN_MCAN_IR_RF0L;
LOG_ERR("Message lost on FIFO0");
}
if (can->ir & CAN_MCAN_IR_RF1L) {
can->ir = CAN_MCAN_IR_RF1L;
LOG_ERR("Message lost on FIFO1");
}
} while (can->ir & (CAN_MCAN_IR_RF0N | CAN_MCAN_IR_RF1N |
CAN_MCAN_IR_RF0L | CAN_MCAN_IR_RF1L));
}
enum can_state can_mcan_get_state(const struct can_mcan_config *cfg,
struct can_bus_err_cnt *err_cnt)
{
struct can_mcan_reg *can = cfg->can;
err_cnt->rx_err_cnt = (can->ecr & CAN_MCAN_ECR_TEC_MSK) <<
CAN_MCAN_ECR_TEC_POS;
err_cnt->tx_err_cnt = (can->ecr & CAN_MCAN_ECR_REC_MSK) <<
CAN_MCAN_ECR_REC_POS;
if (can->psr & CAN_MCAN_PSR_BO) {
return CAN_BUS_OFF;
}
if (can->psr & CAN_MCAN_PSR_EP) {
return CAN_ERROR_PASSIVE;
}
return CAN_ERROR_ACTIVE;
}
#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY
int can_mcan_recover(struct can_mcan_reg *can, k_timeout_t timeout)
{
return can_leave_init_mode(can, timeout);
}
#endif /* CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */
int can_mcan_send(const struct can_mcan_config *cfg,
struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram,
const struct zcan_frame *frame,
k_timeout_t timeout,
can_tx_callback_t callback, void *callback_arg)
{
struct can_mcan_reg *can = cfg->can;
size_t data_length = can_dlc_to_bytes(frame->dlc);
struct can_mcan_tx_fifo_hdr tx_hdr = {
.rtr = frame->rtr == CAN_REMOTEREQUEST,
.xtd = frame->id_type == CAN_EXTENDED_IDENTIFIER,
.esi = 0,
.dlc = frame->dlc,
#ifdef CONFIG_CAN_FD_MODE
.brs = frame->brs == true,
#endif
.fdf = frame->fd,
.efc = 1,
};
uint32_t put_idx;
int ret;
struct can_mcan_mm mm;
volatile uint32_t *dst, *end;
const uint32_t *src;
LOG_DBG("Sending %d bytes. Id: 0x%x, ID type: %s %s %s %s",
data_length, frame->id,
frame->id_type == CAN_STANDARD_IDENTIFIER ?
"standard" : "extended",
frame->rtr == CAN_DATAFRAME ? "" : "RTR",
frame->fd == CAN_DATAFRAME ? "" : "FD frame",
frame->brs == CAN_DATAFRAME ? "" : "BRS");
if (data_length > sizeof(frame->data)) {
LOG_ERR("data length (%zu) > max frame data length (%zu)",
data_length, sizeof(frame->data));
return CAN_TX_EINVAL;
}
if (frame->fd != 1 && frame->dlc > MCAN_MAX_DLC) {
LOG_ERR("DLC of %d without fd flag set.", frame->dlc);
return CAN_TX_EINVAL;
}
if (can->psr & CAN_MCAN_PSR_BO) {
return CAN_TX_BUS_OFF;
}
ret = k_sem_take(&data->tx_sem, timeout);
if (ret != 0) {
return CAN_TIMEOUT;
}
__ASSERT_NO_MSG((can->txfqs & CAN_MCAN_TXFQS_TFQF) !=
CAN_MCAN_TXFQS_TFQF);
k_mutex_lock(&data->tx_mtx, K_FOREVER);
put_idx = ((can->txfqs & CAN_MCAN_TXFQS_TFQPI) >>
CAN_MCAN_TXFQS_TFQPI_POS);
mm.idx = put_idx;
mm.cnt = data->mm.cnt++;
tx_hdr.mm = mm;
if (frame->id_type == CAN_STANDARD_IDENTIFIER) {
tx_hdr.std_id = frame->id & CAN_STD_ID_MASK;
} else {
tx_hdr.ext_id = frame->id;
}
msg_ram->tx_fifo[put_idx].hdr = tx_hdr;
for (src = frame->data_32,
dst = msg_ram->tx_fifo[put_idx].data_32,
end = dst + CAN_DIV_CEIL(data_length, sizeof(uint32_t));
dst < end;
src++, dst++) {
*dst = *src;
}
data->tx_fin_cb[put_idx] = callback;
data->tx_fin_cb_arg[put_idx] = callback_arg;
can->txbar = (1U << put_idx);
k_mutex_unlock(&data->tx_mtx);
if (callback == NULL) {
LOG_DBG("Waiting for TX complete");
k_sem_take(&data->tx_fin_sem[put_idx], K_FOREVER);
}
return CAN_TX_OK;
}
static int can_mcan_get_free_std(volatile struct can_mcan_std_filter *filters)
{
for (int i = 0; i < NUM_STD_FILTER_DATA; ++i) {
if (filters[i].sfce == CAN_MCAN_FCE_DISABLE) {
return i;
}
}
return CAN_NO_FREE_FILTER;
}
/* Use masked configuration only for simplicity. If someone needs more than
* 28 standard filters, dual mode needs to be implemented.
* Dual mode gets tricky, because we can only activate both filters.
* If one of the IDs is not used anymore, we would need to mark it as unused.
*/
int can_mcan_attach_std(struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram,
can_rx_callback_t isr, void *cb_arg,
const struct zcan_filter *filter)
{
struct can_mcan_std_filter filter_element = {
.id1 = filter->id,
.id2 = filter->id_mask,
.sft = CAN_MCAN_SFT_MASKED
};
int filter_nr;
k_mutex_lock(&data->inst_mutex, K_FOREVER);
filter_nr = can_mcan_get_free_std(msg_ram->std_filt);
if (filter_nr == CAN_NO_FREE_FILTER) {
LOG_INF("No free standard id filter left");
return CAN_NO_FREE_FILTER;
}
/* TODO propper fifo balancing */
filter_element.sfce = filter_nr & 0x01 ? CAN_MCAN_FCE_FIFO1 :
CAN_MCAN_FCE_FIFO0;
msg_ram->std_filt[filter_nr] = filter_element;
k_mutex_unlock(&data->inst_mutex);
LOG_DBG("Attached std filter at %d", filter_nr);
if (filter->rtr) {
data->std_filt_rtr |= (1U << filter_nr);
} else {
data->std_filt_rtr &= ~(1U << filter_nr);
}
if (filter->rtr_mask) {
data->std_filt_rtr_mask |= (1U << filter_nr);
} else {
data->std_filt_rtr_mask &= ~(1U << filter_nr);
}
data->rx_cb_std[filter_nr] = isr;
data->cb_arg_std[filter_nr] = cb_arg;
return filter_nr;
}
static int can_mcan_get_free_ext(volatile struct can_mcan_ext_filter *filters)
{
for (int i = 0; i < NUM_EXT_FILTER_DATA; ++i) {
if (filters[i].efce == CAN_MCAN_FCE_DISABLE) {
return i;
}
}
return CAN_NO_FREE_FILTER;
}
static int can_mcan_attach_ext(struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram,
can_rx_callback_t isr, void *cb_arg,
const struct zcan_filter *filter)
{
struct can_mcan_ext_filter filter_element = {
.id2 = filter->id_mask,
.id1 = filter->id,
.eft = CAN_MCAN_EFT_MASKED
};
int filter_nr;
k_mutex_lock(&data->inst_mutex, K_FOREVER);
filter_nr = can_mcan_get_free_ext(msg_ram->ext_filt);
if (filter_nr == CAN_NO_FREE_FILTER) {
LOG_INF("No free extender id filter left");
return CAN_NO_FREE_FILTER;
}
/* TODO propper fifo balancing */
filter_element.efce = filter_nr & 0x01 ? CAN_MCAN_FCE_FIFO1 :
CAN_MCAN_FCE_FIFO0;
msg_ram->ext_filt[filter_nr] = filter_element;
k_mutex_unlock(&data->inst_mutex);
LOG_DBG("Attached ext filter at %d", filter_nr);
if (filter->rtr) {
data->ext_filt_rtr |= (1U << filter_nr);
} else {
data->ext_filt_rtr &= ~(1U << filter_nr);
}
if (filter->rtr_mask) {
data->ext_filt_rtr_mask |= (1U << filter_nr);
} else {
data->ext_filt_rtr_mask &= ~(1U << filter_nr);
}
data->rx_cb_ext[filter_nr] = isr;
data->cb_arg_ext[filter_nr] = cb_arg;
return filter_nr;
}
int can_mcan_attach_isr(struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram,
can_rx_callback_t isr, void *cb_arg,
const struct zcan_filter *filter)
{
int filter_nr;
if (!isr) {
return -EINVAL;
}
if (filter->id_type == CAN_STANDARD_IDENTIFIER) {
filter_nr = can_mcan_attach_std(data, msg_ram, isr, cb_arg,
filter);
} else {
filter_nr = can_mcan_attach_ext(data, msg_ram, isr, cb_arg,
filter);
filter_nr += NUM_STD_FILTER_DATA;
}
if (filter_nr == CAN_NO_FREE_FILTER) {
LOG_INF("No free filter left");
}
return filter_nr;
}
void can_mcan_detach(struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram, int filter_nr)
{
const struct can_mcan_ext_filter ext_filter = {0};
const struct can_mcan_std_filter std_filter = {0};
k_mutex_lock(&data->inst_mutex, K_FOREVER);
if (filter_nr >= NUM_STD_FILTER_DATA) {
filter_nr -= NUM_STD_FILTER_DATA;
if (filter_nr >= NUM_STD_FILTER_DATA) {
LOG_ERR("Wrong filter id");
return;
}
msg_ram->ext_filt[filter_nr] = ext_filter;
data->rx_cb_ext[filter_nr] = NULL;
} else {
msg_ram->std_filt[filter_nr] = std_filter;
data->rx_cb_std[filter_nr] = NULL;
}
k_mutex_unlock(&data->inst_mutex);
}

233
drivers/can/can_mcan.h Normal file
View file

@ -0,0 +1,233 @@
/*
* Copyright (c) 2020 Alexander Wachter
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#ifndef ZEPHYR_DRIVERS_CAN_MCAN_H_
#define ZEPHYR_DRIVERS_CAN_MCAN_H_
#define NUM_STD_FILTER_ELEMENTS DT_PROP(DT_PATH(soc, can), std_filter_elements)
#define NUM_EXT_FILTER_ELEMENTS DT_PROP(DT_PATH(soc, can), ext_filter_elements)
#define NUM_RX_FIFO0_ELEMENTS DT_PROP(DT_PATH(soc, can), rx_fifo0_elements)
#define NUM_RX_FIFO1_ELEMENTS DT_PROP(DT_PATH(soc, can), rx_fifo0_elements)
#define NUM_RX_BUF_ELEMENTS DT_PROP(DT_PATH(soc, can), rx_buffer_elements)
#define NUM_TX_EVENT_FIFO_ELEMENTS \
DT_PROP(DT_PATH(soc, can), tx_event_fifo_elements)
#define NUM_TX_BUF_ELEMENTS DT_PROP(DT_PATH(soc, can), tx_buffer_elements)
#ifdef CONFIG_CAN_STM32FD
#define NUM_STD_FILTER_DATA CONFIG_CAN_MAX_STD_ID_FILTER
#define NUM_EXT_FILTER_DATA CONFIG_CAN_MAX_EXT_ID_FILTER
#else
#define NUM_STD_FILTER_DATA NUM_STD_FILTER_ELEMENTS
#define NUM_EXT_FILTER_DATA NUM_EXT_FILTER_ELEMENTS
#endif
struct can_mcan_rx_fifo_hdr {
union {
struct {
volatile uint32_t ext_id : 29; /* Extended Identifier */
volatile uint32_t rtr : 1; /* Retmote Transmission Request*/
volatile uint32_t xtd : 1; /* Extended identifier */
volatile uint32_t esi : 1; /* Error state indicator */
};
struct {
volatile uint32_t pad1 : 18;
volatile uint32_t std_id : 11; /* Standard Identifier */
volatile uint32_t pad2 : 3;
};
};
volatile uint32_t rxts : 16; /* Rx timestamp */
volatile uint32_t dlc : 4; /* Data Length Code */
volatile uint32_t brs : 1; /* Bit Rate Switch */
volatile uint32_t fdf : 1; /* FD Format */
volatile uint32_t res : 2; /* Reserved */
volatile uint32_t fidx : 7; /* Filter Index */
volatile uint32_t anmf : 1; /* Accepted non-matching frame */
} __packed;
struct can_mcan_rx_fifo {
struct can_mcan_rx_fifo_hdr hdr;
union {
volatile uint8_t data[64];
volatile uint32_t data_32[16];
};
} __packed;
struct can_mcan_mm {
volatile uint8_t idx : 5;
volatile uint8_t cnt : 3;
} __packed;
struct can_mcan_tx_fifo_hdr {
union {
struct {
volatile uint32_t ext_id : 29; /* Identifier */
volatile uint32_t rtr : 1; /* Retmote Transmission Request*/
volatile uint32_t xtd : 1; /* Extended identifier */
volatile uint32_t esi : 1; /* Error state indicator */
};
struct {
volatile uint32_t pad1 : 18;
volatile uint32_t std_id : 11; /* Identifier */
volatile uint32_t pad2 : 3;
};
};
volatile uint16_t res1; /* Reserved */
volatile uint8_t dlc : 4; /* Data Length Code */
volatile uint8_t brs : 1; /* Bit Rate Switch */
volatile uint8_t fdf : 1; /* FD Format */
volatile uint8_t res2 : 1; /* Reserved */
volatile uint8_t efc : 1; /* Event FIFO control (Store Tx events) */
struct can_mcan_mm mm; /* Message marker */
} __packed;
struct can_mcan_tx_fifo {
struct can_mcan_tx_fifo_hdr hdr;
union {
volatile uint8_t data[64];
volatile uint32_t data_32[16];
};
} __packed;
#define CAN_MCAN_TE_TX 0x1 /* TX event */
#define CAN_MCAN_TE_TXC 0x2 /* TX event in spite of cancellation */
struct can_mcan_tx_event_fifo {
volatile uint32_t id : 29; /* Identifier */
volatile uint32_t rtr : 1; /* Retmote Transmission Request*/
volatile uint32_t xtd : 1; /* Extended identifier */
volatile uint32_t esi : 1; /* Error state indicator */
volatile uint16_t txts; /* TX Timestamp */
volatile uint8_t dlc : 4; /* Data Length Code */
volatile uint8_t brs : 1; /* Bit Rate Switch */
volatile uint8_t fdf : 1; /* FD Format */
volatile uint8_t et : 2; /* Event type */
struct can_mcan_mm mm; /* Message marker */
} __packed;
#define CAN_MCAN_FCE_DISABLE 0x0
#define CAN_MCAN_FCE_FIFO0 0x1
#define CAN_MCAN_FCE_FIFO1 0x2
#define CAN_MCAN_FCE_REJECT 0x3
#define CAN_MCAN_FCE_PRIO 0x4
#define CAN_MCAN_FCE_PRIO_FIFO0 0x5
#define CAN_MCAN_FCE_PRIO_FIFO1 0x7
#define CAN_MCAN_SFT_RANGE 0x0
#define CAN_MCAN_SFT_DUAL 0x1
#define CAN_MCAN_SFT_MASKED 0x2
#define CAN_MCAN_SFT_DISABLED 0x3
struct can_mcan_std_filter {
volatile uint32_t id2 : 11; /* ID2 for dual or range, mask otherwise */
volatile uint32_t res : 5;
volatile uint32_t id1 : 11;
volatile uint32_t sfce : 3; /* Filter config */
volatile uint32_t sft : 2; /* Filter type */
} __packed;
#define CAN_MCAN_EFT_RANGE_XIDAM 0x0
#define CAN_MCAN_EFT_DUAL 0x1
#define CAN_MCAN_EFT_MASKED 0x2
#define CAN_MCAN_EFT_RANGE 0x3
struct can_mcan_ext_filter {
volatile uint32_t id1 : 29;
volatile uint32_t efce : 3; /* Filter config */
volatile uint32_t id2 : 29; /* ID2 for dual or range, mask otherwise */
volatile uint32_t res : 1;
volatile uint32_t eft : 2; /* Filter type */
} __packed;
struct can_mcan_msg_sram {
volatile struct can_mcan_std_filter std_filt[NUM_STD_FILTER_ELEMENTS];
volatile struct can_mcan_ext_filter ext_filt[NUM_EXT_FILTER_ELEMENTS];
volatile struct can_mcan_rx_fifo rx_fifo0[NUM_RX_FIFO0_ELEMENTS];
volatile struct can_mcan_rx_fifo rx_fifo1[NUM_RX_FIFO1_ELEMENTS];
volatile struct can_mcan_rx_fifo rx_buffer[NUM_RX_BUF_ELEMENTS];
volatile struct can_mcan_tx_event_fifo tx_event_fifo[NUM_TX_BUF_ELEMENTS];
volatile struct can_mcan_tx_fifo tx_fifo[NUM_TX_BUF_ELEMENTS];
} __packed;
struct can_mcan_data {
struct k_mutex inst_mutex;
struct k_sem tx_sem;
struct k_mutex tx_mtx;
struct k_sem tx_fin_sem[NUM_TX_BUF_ELEMENTS];
can_tx_callback_t tx_fin_cb[NUM_TX_BUF_ELEMENTS];
void *tx_fin_cb_arg[NUM_TX_BUF_ELEMENTS];
can_rx_callback_t rx_cb_std[NUM_STD_FILTER_DATA];
can_rx_callback_t rx_cb_ext[NUM_EXT_FILTER_DATA];
void *cb_arg_std[NUM_STD_FILTER_DATA];
void *cb_arg_ext[NUM_EXT_FILTER_DATA];
can_state_change_isr_t state_change_isr;
uint32_t std_filt_rtr;
uint32_t std_filt_rtr_mask;
uint8_t ext_filt_rtr;
uint8_t ext_filt_rtr_mask;
struct can_mcan_mm mm;
} __aligned(4);
struct can_mcan_config {
struct can_mcan_reg *can; /*!< CAN Registers*/
uint32_t bus_speed;
uint32_t bus_speed_data;
uint16_t sjw;
uint16_t sample_point;
uint16_t prop_ts1;
uint16_t ts2;
#ifdef CONFIG_CAN_FD_MODE
uint16_t sample_point_data;
uint8_t sjw_data;
uint8_t prop_ts1_data;
uint8_t ts2_data;
uint8_t tx_delay_comp_offset;
#endif
};
struct can_mcan_reg;
int can_mcan_set_mode(const struct can_mcan_config *cfg, enum can_mode mode);
int can_mcan_set_timing(const struct can_mcan_config *cfg,
const struct can_timing *timing,
const struct can_timing *timing_data);
int can_mcan_init(const struct device *dev, const struct can_mcan_config *cfg,
struct can_mcan_msg_sram *msg_ram,
struct can_mcan_data *data);
void can_mcan_line_0_isr(const struct can_mcan_config *cfg,
struct can_mcan_msg_sram *msg_ram,
struct can_mcan_data *data);
void can_mcan_line_1_isr(const struct can_mcan_config *cfg,
struct can_mcan_msg_sram *msg_ram,
struct can_mcan_data *data);
int can_mcan_recover(struct can_mcan_reg *can, k_timeout_t timeout);
int can_mcan_send(const struct can_mcan_config *cfg, struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram,
const struct zcan_frame *frame,
k_timeout_t timeout, can_tx_callback_t callback,
void *callback_arg);
int can_mcan_attach_isr(struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram,
can_rx_callback_t isr, void *cb_arg,
const struct zcan_filter *filter);
void can_mcan_detach(struct can_mcan_data *data,
struct can_mcan_msg_sram *msg_ram, int filter_nr);
enum can_state can_mcan_get_state(const struct can_mcan_config *cfg,
struct can_bus_err_cnt *err_cnt);
#endif /* ZEPHYR_DRIVERS_CAN_MCAN_H_ */

1560
drivers/can/can_mcan_int.h Normal file

File diff suppressed because it is too large Load diff

24
drivers/can/can_stm32fd.c Normal file
View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2020 Alexander Wachter
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <drivers/can.h>
u32_t can_mcan_get_core_clock(struct device *dev)
{
u32_t core_clock = LL_RCC_GetFDCANClockFreq(LL_RCC_FDCAN_CLKSOURCE);
u32_t dbrp, nbrp;
#if CONFIG_CAN_CKDIV != 0
core_clock /= CONFIG_CAN_CKDIV * 2;
#endif
__weak void can_mcan_clock_enable()
{
LL_RCC_SetFDCANClockSource(LL_RCC_FDCAN_CLKSOURCE_PCLK1);
__HAL_RCC_FDCAN_CLK_ENABLE();
FDCAN_CONFIG->CKDIV = CONFIG_CAN_CKDIV;
}

View file

@ -0,0 +1,28 @@
description: Bosch m_can CAN-FD controller base
compatible: "bosch,m-can-base"
properties:
std-filter-elements:
type: int
required: true
ext-filter-elements:
type: int
required: true
rx-fifo0-elements:
type: int
required: true
rx-fifo1-elements:
type: int
required: true
rx-buffer-elements:
type: int
required: true
tx-buffer-elements:
type: int
required: true

View file

@ -0,0 +1,12 @@
description: Bosch m_can CAN-FD controller
compatible: "bosch,m-can"
include: can-fd-controller.yaml
properties:
reg:
required: true
interrupts:
required: true

View file

@ -30,3 +30,7 @@ properties:
Sample point in permille for the data phase.
This param is required if segments are not given.
If the sample point is given, the segments are ignored.
tx-delay-comp-offset:
type: int
required: false
default: 0

View file

@ -23,6 +23,7 @@
#include <zephyr/types.h>
#include <device.h>
#include <string.h>
#include <sys/util.h>
#ifdef __cplusplus
extern "C" {
@ -403,6 +404,45 @@ __subsystem struct can_driver_api {
#endif
};
/**
* @brief Convert the DLC to the number of bytes
*
* This function converts a the Data Length Code to the number of bytes.
*
* @param dlc The Data Length Code
*
* @retval Number of bytes
*/
static inline uint8_t can_dlc_to_bytes(uint8_t dlc)
{
static const uint8_t dlc_table[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12,
16, 20, 24, 32, 48, 64};
return dlc > 0x0F ? 64 : dlc_table[dlc];
}
/**
* @brief Convert a number of bytes to the DLC
*
* This function converts a number of bytes to the Data Length Code
*
* @param num_bytes The number of bytes
*
* @retval The DLC
*/
static inline uint8_t can_bytes_to_dlc(uint8_t num_bytes)
{
return num_bytes <= 8 ? num_bytes :
num_bytes <= 12 ? 9 :
num_bytes <= 16 ? 10 :
num_bytes <= 20 ? 11 :
num_bytes <= 24 ? 12 :
num_bytes <= 32 ? 13 :
num_bytes <= 48 ? 14 :
15;
}
/**
* @brief Perform data transfer to CAN bus.
*