Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions drivers/can/Kconfig.mcan
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,18 @@
bool
help
Enable the Bosch M_CAN CAN IP module driver backend.

if CAN_MCAN

config CAN_MCAN_TXBCF_POLL_INTERVAL_MS
int "Polling interval in milliseconds of TXBCF register if DAR is enabled"
default 75
help
When DAR (Disable Automatic Retransmission), used for CAN_MODE_ONE_SHOT,
is enabled, and a transmission fails, a bug in the MCAN IP prevents the
TCF (Transmission Cancellation Finalized) interrupt from triggering,
despite the correct bit being set in the TXBCF register. It is thus
neccesary to poll TXBCF register to detect when a transmission failed if

Check warning on line 21 in drivers/can/Kconfig.mcan

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

TYPO_SPELLING

drivers/can/Kconfig.mcan:21 'neccesary' may be misspelled - perhaps 'necessary'?
DAR is enabled.

endif # CAN_MCAN
190 changes: 161 additions & 29 deletions drivers/can/can_mcan.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
#include <zephyr/drivers/can/transceiver.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/sys/util.h>

LOG_MODULE_REGISTER(can_mcan, CONFIG_CAN_LOG_LEVEL);

#define CAN_INIT_TIMEOUT_MS 100
#define TX_ABORTED_TIMEOUT_MS 100
#define TXBCF_TIMER_TIMEOUT K_MSEC(CONFIG_CAN_MCAN_TXBCF_POLL_INTERVAL_MS)

Check notice on line 22 in drivers/can/can_mcan.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

drivers/can/can_mcan.c:22 -#define TXBCF_TIMER_TIMEOUT K_MSEC(CONFIG_CAN_MCAN_TXBCF_POLL_INTERVAL_MS) +#define TXBCF_TIMER_TIMEOUT K_MSEC(CONFIG_CAN_MCAN_TXBCF_POLL_INTERVAL_MS)
int can_mcan_read_reg(const struct device *dev, uint16_t reg, uint32_t *val)
{
const struct can_mcan_config *config = dev->config;
Expand Down Expand Up @@ -275,7 +278,7 @@
{
ARG_UNUSED(dev);

*cap = CAN_MODE_NORMAL | CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY;
*cap = CAN_MODE_NORMAL | CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY | CAN_MODE_ONE_SHOT;

if (IS_ENABLED(CONFIG_CAN_MANUAL_RECOVERY_MODE)) {
*cap |= CAN_MODE_MANUAL_RECOVERY;
Expand All @@ -293,6 +296,7 @@
const struct can_mcan_config *config = dev->config;
struct can_mcan_data *data = dev->data;
int err = 0;
uint32_t cccr;

if (data->common.started) {
return -EALREADY;
Expand Down Expand Up @@ -321,25 +325,128 @@
return err;
}

err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr);
if (err != 0) {
return err;
}

if (cccr & CAN_MCAN_CCCR_DAR) {
/*
* When DAR (Disable Automatic Retransmission), used for CAN_MODE_ONE_SHOT,
* is enabled, and a transmission fails, a bug in the MCAN IP prevents the
* TCF (Transmission Cancellation Finalized) interrupt from triggering,
* despite the correct bit being set in the TXBCF register. It is thus
* neccesary to poll TXBCF register to detect when a transmission failed if

Check warning on line 339 in drivers/can/can_mcan.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

TYPO_SPELLING

drivers/can/can_mcan.c:339 'neccesary' may be misspelled - perhaps 'necessary'?
* DAR is enabled.
*/

Check warning on line 341 in drivers/can/can_mcan.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

BLOCK_COMMENT_STYLE

drivers/can/can_mcan.c:341 Block comments should align the * on each line
k_timer_start(&data->txbcf_timer, TXBCF_TIMER_TIMEOUT, TXBCF_TIMER_TIMEOUT);

Check notice on line 342 in drivers/can/can_mcan.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

drivers/can/can_mcan.c:342 - */ + */
}

data->common.started = true;
pm_device_busy_set(dev);

return err;
}

int can_mcan_stop(const struct device *dev)
static int can_mcan_read_txbcf(const struct device *dev)
{
const struct can_mcan_config *config = dev->config;
const struct can_mcan_callbacks *cbs = config->callbacks;
struct can_mcan_data *data = dev->data;
uint32_t txbcfs;
int err;
can_tx_callback_t tx_cb;
uint32_t tx_idx;
void *user_data;

err = can_mcan_read_reg(dev, CAN_MCAN_TXBCF, &txbcfs);
if (err != 0) {
LOG_ERR("failed to read tx cancellation finished (err %d)", err);
return err;
}

if (txbcfs == 0) {
return 0;
}

for (size_t tx_idx = 0; tx_idx < cbs->num_tx; tx_idx++) {
if ((txbcfs & BIT(tx_idx)) == 0) {
continue;
}

if (cbs->tx[tx_idx].function == NULL) {
continue;
}

tx_cb = cbs->tx[tx_idx].function;
user_data = cbs->tx[tx_idx].user_data;
cbs->tx[tx_idx].function = NULL;
LOG_DBG("tx buffer cancellation finished (idx %u)", tx_idx);
k_sem_give(&data->tx_sem);
tx_cb(dev, -ENETDOWN, user_data);
}

if (k_sem_count_get(&data->tx_sem) == cbs->num_tx) {
k_sem_give(&data->txbcr_sem);
}

return 0;
}

static void can_mcan_txbcf_timer_handler(struct k_timer *timer_id)
{
const struct device *dev = k_timer_user_data_get(timer_id);

can_mcan_read_txbcf(dev);
}

static bool can_mcan_rx_filters_exist(const struct device *dev)
{
const struct can_mcan_config *config = dev->config;
const struct can_mcan_callbacks *cbs = config->callbacks;
int i;

for (i = 0; i < cbs->num_std; i++) {
if (cbs->std[i].function != NULL) {
return true;
}
}

for (i = 0; i < cbs->num_ext; i++) {
if (cbs->ext[i].function != NULL) {
return true;
}
}

return false;
}

int can_mcan_stop(const struct device *dev)
{
const struct can_mcan_config *config = dev->config;
struct can_mcan_data *data = dev->data;
int err;

if (!data->common.started) {
return -EALREADY;
}

/* CAN transmissions are automatically stopped when entering init mode */

Check notice on line 433 in drivers/can/can_mcan.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

drivers/can/can_mcan.c:433 -
/* Request all TX buffers to be cancelled */
k_sem_reset(&data->txbcr_sem);
err = can_mcan_write_reg(dev, CAN_MCAN_TXBCR, CAN_MCAN_TXBCR_CR);
if (err != 0) {
return err;
}

/* Wait for all TX buffers to be cancelled */
err = k_sem_take(&data->txbcr_sem, K_MSEC(TX_ABORTED_TIMEOUT_MS));
if (err != 0) {
LOG_ERR("Timed out waiting for all TX buffers to be cancelled");
return err;
}

k_timer_stop(&data->txbcf_timer);

err = can_mcan_enter_init_mode(dev, K_MSEC(CAN_INIT_TIMEOUT_MS));
if (err != 0) {
LOG_ERR("Failed to enter init mode");
Expand All @@ -358,22 +465,18 @@

data->common.started = false;

for (tx_idx = 0U; tx_idx < cbs->num_tx; tx_idx++) {
tx_cb = cbs->tx[tx_idx].function;

if (tx_cb != NULL) {
cbs->tx[tx_idx].function = NULL;
tx_cb(dev, -ENETDOWN, cbs->tx[tx_idx].user_data);
k_sem_give(&data->tx_sem);
}
k_mutex_lock(&data->lock, K_FOREVER);
if (!can_mcan_rx_filters_exist(dev)) {
pm_device_busy_clear(dev);
}
k_mutex_unlock(&data->lock);

return 0;
}

int can_mcan_set_mode(const struct device *dev, can_mode_t mode)
{
can_mode_t supported = CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY;
can_mode_t supported = CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY | CAN_MODE_ONE_SHOT;
struct can_mcan_data *data = dev->data;
uint32_t cccr;
uint32_t test;
Expand Down Expand Up @@ -431,6 +534,13 @@
}
#endif /* CONFIG_CAN_FD_MODE */

if ((mode & CAN_MODE_ONE_SHOT) != 0) {
/* Disable Automatic Retransmission */
cccr |= CAN_MCAN_CCCR_DAR;
} else {
cccr &= ~CAN_MCAN_CCCR_DAR;
}

err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr);
if (err != 0) {
goto unlock;
Expand All @@ -451,12 +561,9 @@

static void can_mcan_state_change_handler(const struct device *dev)
{
const struct can_mcan_config *config = dev->config;
struct can_mcan_data *data = dev->data;
const can_state_change_callback_t state_cb = data->common.state_change_cb;
void *state_cb_data = data->common.state_change_cb_user_data;
const struct can_mcan_callbacks *cbs = config->callbacks;
can_tx_callback_t tx_cb;
struct can_bus_err_cnt err_cnt;
enum can_state state;
uint32_t cccr;
Expand All @@ -478,17 +585,6 @@
return;
}

/* Call all TX queue callbacks with -ENETUNREACH */
for (uint32_t tx_idx = 0U; tx_idx < cbs->num_tx; tx_idx++) {
tx_cb = cbs->tx[tx_idx].function;

if (tx_cb != NULL) {
cbs->tx[tx_idx].function = NULL;
tx_cb(dev, -ENETUNREACH, cbs->tx[tx_idx].user_data);
k_sem_give(&data->tx_sem);
}
}

if (!IS_ENABLED(CONFIG_CAN_MANUAL_RECOVERY_MODE) ||
(data->common.mode & CAN_MODE_MANUAL_RECOVERY) == 0U) {
/*
Expand Down Expand Up @@ -624,7 +720,8 @@
{
const uint32_t events = CAN_MCAN_IR_BO | CAN_MCAN_IR_EP | CAN_MCAN_IR_EW |
CAN_MCAN_IR_TEFN | CAN_MCAN_IR_TEFL | CAN_MCAN_IR_ARA |
CAN_MCAN_IR_MRAF | CAN_MCAN_IR_PEA | CAN_MCAN_IR_PED;
CAN_MCAN_IR_MRAF | CAN_MCAN_IR_PEA | CAN_MCAN_IR_PED |
CAN_MCAN_IR_TCF;
struct can_mcan_data *data = dev->data;
uint32_t ir;
int err;
Expand Down Expand Up @@ -665,11 +762,16 @@
#ifdef CONFIG_CAN_STATS
if ((ir & (CAN_MCAN_IR_PEA | CAN_MCAN_IR_PED)) != 0U) {
uint32_t reg;

/* This function automatically updates protocol error stats */
can_mcan_read_psr(dev, &reg);
}
#endif

if ((ir & CAN_MCAN_IR_TCF) != 0U) {
can_mcan_read_txbcf(dev);
}

err = can_mcan_read_reg(dev, CAN_MCAN_IR, &ir);
if (err != 0) {
return;
Expand Down Expand Up @@ -981,6 +1083,18 @@
return -ENETUNREACH;
}

err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &reg);
if (err != 0) {
return err;
}

if (reg & CAN_MCAN_CCCR_DAR) {
err = can_mcan_read_txbcf(dev);
if (err != 0) {
return err;
}
}

err = k_sem_take(&data->tx_sem, timeout);
if (err != 0) {
return -EAGAIN;
Expand Down Expand Up @@ -1184,6 +1298,8 @@
filter_id = can_mcan_add_rx_filter_std(dev, callback, user_data, filter);
}

pm_device_busy_set(dev);

return filter_id;
}

Expand Down Expand Up @@ -1230,6 +1346,10 @@
}
}

if (!can_mcan_rx_filters_exist(dev) && !data->common.started) {
pm_device_busy_clear(dev);
}

k_mutex_unlock(&data->lock);
}

Expand Down Expand Up @@ -1389,6 +1509,9 @@
k_mutex_init(&data->lock);
k_mutex_init(&data->tx_mtx);
k_sem_init(&data->tx_sem, cbs->num_tx, cbs->num_tx);
k_sem_init(&data->txbcr_sem, 0, 1);
k_timer_init(&data->txbcf_timer, can_mcan_txbcf_timer_handler, NULL);
k_timer_user_data_set(&data->txbcf_timer, (void *)dev);

if (config->common.phy != NULL && !device_is_ready(config->common.phy)) {
LOG_ERR("CAN transceiver not ready");
Expand Down Expand Up @@ -1498,7 +1621,7 @@

reg = CAN_MCAN_IE_BOE | CAN_MCAN_IE_EWE | CAN_MCAN_IE_EPE | CAN_MCAN_IE_MRAFE |
CAN_MCAN_IE_TEFLE | CAN_MCAN_IE_TEFNE | CAN_MCAN_IE_RF0NE | CAN_MCAN_IE_RF1NE |
CAN_MCAN_IE_RF0LE | CAN_MCAN_IE_RF1LE;
CAN_MCAN_IE_RF0LE | CAN_MCAN_IE_RF1LE | CAN_MCAN_IE_TCFE;
#ifdef CONFIG_CAN_STATS
/* These ISRs are only enabled/used for statistics, they are otherwise
* disabled as they may produce a significant amount of frequent ISRs.
Expand Down Expand Up @@ -1530,5 +1653,14 @@
return err;
}

/*
* Interrupt on every TX buffer cancellation finished event.
*/
reg = CAN_MCAN_TXBCIE_CFIE;
err = can_mcan_write_reg(dev, CAN_MCAN_TXBCIE, reg);
if (err != 0) {
return err;
}

return can_mcan_clear_mram(dev, 0, config->mram_size);
}
Loading
Loading