894 lines
23 KiB
C
894 lines
23 KiB
C
/*
|
|
* 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) {
|
|
uint32_t nbtp_sjw = can->nbtp & CAN_MCAN_NBTP_NSJW_MSK;
|
|
|
|
__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);
|
|
__ASSERT_NO_MSG(timing->sjw <= 0x80 && timing->sjw > 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->prescaler - 1UL) & 0x1FF) <<
|
|
CAN_MCAN_NBTP_NBRP_POS;
|
|
|
|
if (timing->sjw == CAN_SJW_NO_CHANGE) {
|
|
can->nbtp |= nbtp_sjw;
|
|
} else {
|
|
can->nbtp |= (((uint32_t)timing->sjw - 1UL) & 0x7F) <<
|
|
CAN_MCAN_NBTP_NSJW_POS;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_CAN_FD_MODE
|
|
if (timing_data) {
|
|
uint32_t dbtp_sjw = can->dbtp & CAN_MCAN_DBTP_DSJW_MSK;
|
|
|
|
__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);
|
|
__ASSERT_NO_MSG(timing_data->sjw <= 0x80 &&
|
|
timing_data->sjw > 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->prescaler - 1UL) & 0x1F) <<
|
|
CAN_MCAN_DBTP_DBRP_POS;
|
|
|
|
if (timing_data->sjw == CAN_SJW_NO_CHANGE) {
|
|
can->dbtp |= dbtp_sjw;
|
|
} else {
|
|
can->dbtp |= (((uint32_t)timing_data->sjw - 1UL) & 0x0F) <<
|
|
CAN_MCAN_DBTP_DSJW_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_buffer & CAN_MCAN_TXBC_TBSA) |
|
|
(ARRAY_SIZE(msg_ram->tx_buffer) << CAN_MCAN_TXBC_TFQS_POS);
|
|
if (sizeof(msg_ram->tx_buffer[0].data) <= 24) {
|
|
can->txesc = (sizeof(msg_ram->tx_buffer[0].data) - 8) / 4;
|
|
} else {
|
|
can->txesc = (sizeof(msg_ram->tx_buffer[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
|
|
|
|
timing.sjw = cfg->sjw;
|
|
#ifdef CONFIG_CAN_FD_MODE
|
|
timing_data.sjw = cfg->sjw_data;
|
|
can_mcan_configure_timing(can, &timing, &timing_data);
|
|
#else
|
|
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_buffer_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_buffer[put_idx].hdr = tx_hdr;
|
|
|
|
for (src = frame->data_32,
|
|
dst = msg_ram->tx_buffer[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);
|
|
}
|