diff --git a/drivers/can/Kconfig.mcan b/drivers/can/Kconfig.mcan index db09fc800c11..96c5f22446f6 100644 --- a/drivers/can/Kconfig.mcan +++ b/drivers/can/Kconfig.mcan @@ -7,3 +7,18 @@ config CAN_MCAN 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 + necessary to poll TXBCF register to detect when a transmission failed if + DAR is enabled. + +endif # CAN_MCAN diff --git a/drivers/can/can_mcan.c b/drivers/can/can_mcan.c index 3f7d771e5f4a..e82cac4f19ad 100644 --- a/drivers/can/can_mcan.c +++ b/drivers/can/can_mcan.c @@ -10,12 +10,14 @@ #include #include #include +#include #include #include LOG_MODULE_REGISTER(can_mcan, CONFIG_CAN_LOG_LEVEL); #define CAN_INIT_TIMEOUT_MS 100 +#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) { @@ -275,7 +277,7 @@ int can_mcan_get_capabilities(const struct device *dev, can_mode_t *cap) { 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; @@ -321,11 +323,99 @@ int can_mcan_start(const struct device *dev) return err; } + uint32_t cccr; + + 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 + * necessary to poll TXBCF register to detect when a transmission failed if + * DAR is enabled. + */ + k_timer_start(&data->txbcf_timer, TXBCF_TIMER_TIMEOUT, TXBCF_TIMER_TIMEOUT); + } + data->common.started = true; + pm_device_busy_set(dev); return err; } +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; + 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, -EIO, user_data); + } + + 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; @@ -339,6 +429,8 @@ int can_mcan_stop(const struct device *dev) return -EALREADY; } + k_timer_stop(&data->txbcf_timer); + /* CAN transmissions are automatically stopped when entering init mode */ err = can_mcan_enter_init_mode(dev, K_MSEC(CAN_INIT_TIMEOUT_MS)); if (err != 0) { @@ -368,12 +460,18 @@ int can_mcan_stop(const struct device *dev) } } + 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; @@ -431,6 +529,13 @@ int can_mcan_set_mode(const struct device *dev, can_mode_t mode) } #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; @@ -1024,6 +1129,21 @@ int can_mcan_send(const struct device *dev, const struct can_frame *frame, k_tim } } + uint32_t cccr; + + err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); + if (err != 0) { + return err; + } + + if (cccr & CAN_MCAN_CCCR_DAR) { + /* + * TXBCR is cleared after TXBAR is set. Stop timer to ensure + * TXBCR is not read before TXBAR has been set. + */ + k_timer_stop(&data->txbcf_timer); + } + __ASSERT_NO_MSG(put_idx < cbs->num_tx); cbs->tx[put_idx].function = callback; cbs->tx[put_idx].user_data = user_data; @@ -1034,10 +1154,18 @@ int can_mcan_send(const struct device *dev, const struct can_frame *frame, k_tim goto err_unlock; } + if (cccr & CAN_MCAN_CCCR_DAR) { + k_timer_start(&data->txbcf_timer, TXBCF_TIMER_TIMEOUT, TXBCF_TIMER_TIMEOUT); + } + k_mutex_unlock(&data->tx_mtx); return 0; err_unlock: + if (cccr & CAN_MCAN_CCCR_DAR) { + k_timer_start(&data->txbcf_timer, TXBCF_TIMER_TIMEOUT, TXBCF_TIMER_TIMEOUT); + } + k_mutex_unlock(&data->tx_mtx); k_sem_give(&data->tx_sem); @@ -1184,6 +1312,8 @@ int can_mcan_add_rx_filter(const struct device *dev, can_rx_callback_t callback, filter_id = can_mcan_add_rx_filter_std(dev, callback, user_data, filter); } + pm_device_busy_set(dev); + return filter_id; } @@ -1230,6 +1360,10 @@ void can_mcan_remove_rx_filter(const struct device *dev, int filter_id) } } + if (!can_mcan_rx_filters_exist(dev) && !data->common.started) { + pm_device_busy_clear(dev); + } + k_mutex_unlock(&data->lock); } @@ -1389,6 +1523,8 @@ int can_mcan_init(const struct device *dev) k_mutex_init(&data->lock); k_mutex_init(&data->tx_mtx); k_sem_init(&data->tx_sem, cbs->num_tx, cbs->num_tx); + 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"); @@ -1530,5 +1666,14 @@ int can_mcan_init(const struct device *dev) 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); } diff --git a/drivers/can/can_tcan4x5x.c b/drivers/can/can_tcan4x5x.c index 2abd0467a5ee..31f61b7dd72d 100644 --- a/drivers/can/can_tcan4x5x.c +++ b/drivers/can/can_tcan4x5x.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL); @@ -100,6 +102,10 @@ LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL); #define CAN_TCAN4X5X_MODE_CONFIG_SWE_DIS BIT(1) #define CAN_TCAN4X5X_MODE_CONFIG_TEST_MODE_CONFIG BIT(0) +#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP 0 +#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY 1 +#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL 2 + /* Timestamp Prescaler register */ #define CAN_TCAN4X5X_TIMESTAMP_PRESCALER 0x0804 #define CAN_TCAN4X5X_TIMESTAMP_PRESCALER_MASK GENMASK(7, 0) @@ -201,6 +207,9 @@ LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL); /* TCAN4x5x timing requirements */ #define CAN_TCAN4X5X_T_MODE_STBY_NOM_US 70 +#define CAN_TCAN4X5X_T_MODE_NOM_SLP_US 200 +#define CAN_TCAN4X5X_T_MODE_NOM_STBY_US 200 +#define CAN_TCAN4X5X_T_MODE_SLP_STBY_US 200 #define CAN_TCAN4X5X_T_WAKE_US 50 #define CAN_TCAN4X5X_T_PULSE_WIDTH_US 30 #define CAN_TCAN4X5X_T_RESET_US 1000 @@ -551,6 +560,154 @@ static int tcan4x5x_reset(const struct device *dev) return 0; } +static int tcan4x5x_set_config_mode_sel(const struct device *dev, uint8_t mode, uint32_t *reg) +{ + int err; + uint8_t current_mode; + + switch (mode) { + case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP: + case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY: + case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL: + break; + default: + LOG_ERR("invalid mode %u", mode); + return -EINVAL; + } + + err = tcan4x5x_read_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg); + if (err != 0) { + LOG_ERR("failed to read configuration register (err %d)", err); + return -EIO; + } + + current_mode = FIELD_GET(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, *reg); + LOG_DBG("current mode %u, new mode %u", current_mode, mode); + + *reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL); + *reg |= FIELD_PREP(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, mode); + + err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, *reg); + if (err != 0) { + LOG_ERR("failed to write configuration register (err %d)", err); + return -EIO; + } + + if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY && + mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL) { + /* Wait for standby to normal mode switch */ + k_busy_wait(CAN_TCAN4X5X_T_MODE_STBY_NOM_US); + } else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL && + mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP) { + /* Wait for normal to sleep mode switch */ + k_busy_wait(CAN_TCAN4X5X_T_MODE_NOM_SLP_US); + } else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL && + mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY) { + /* Wait for normal to standby mode switch */ + k_busy_wait(CAN_TCAN4X5X_T_MODE_NOM_STBY_US); + } else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP && + mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY) { + /* Wait for sleep to standby mode switch */ + k_busy_wait(CAN_TCAN4X5X_T_MODE_SLP_STBY_US); + } + + return 0; +} + +static int tcan4x5x_init_normal_mode(const struct device *dev) +{ + const struct can_mcan_config *mcan_config = dev->config; + const struct tcan4x5x_config *tcan_config = mcan_config->custom; + int err = 0; + uint32_t reg; + + /* Set TCAN4x5x mode normal */ + err = tcan4x5x_set_config_mode_sel(dev, CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL, ®); + if (err != 0) { + return -ENODEV; + } + + /* Configure the frequency reference */ + if (tcan_config->clk_freq == MHZ(20)) { + /* 20 MHz frequency reference */ + reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_CLK_REF); + } else { + /* 40 MHz frequency reference */ + reg |= CAN_TCAN4X5X_MODE_CONFIG_CLK_REF; + } + + /* Set nWKRQ voltage to VIO */ + reg |= CAN_TCAN4X5X_MODE_CONFIG_NWKRQ_VOLTAGE; + + /* Write remaining configuration to the device */ + err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg); + if (err != 0) { + LOG_ERR("failed to write configuration register (err %d)", err); + return -EIO; + } + + /* Configure Message RAM */ + err = can_mcan_configure_mram(dev, CAN_TCAN4X5X_MRAM_BASE, CAN_TCAN4X5X_MRAM_BASE); + if (err != 0) { + return -EIO; + } + + /* Initialize M_CAN */ + err = can_mcan_init(dev); + if (err != 0) { + LOG_ERR("failed to initialize mcan (err %d)", err); + return err; + } + + return err; +} + +#ifdef CONFIG_PM_DEVICE +static int tcan4x5x_pm_control(const struct device *dev, enum pm_device_action action) +{ + int err = 0; + uint32_t reg; + + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + if (pm_device_is_busy(dev)) { + LOG_DBG("Cannot suspend while device is busy"); + return -EBUSY; + } + + /* + * Enter sleep mode. + * NOTE: All RX filters are cleared when entering sleep mode. + * User must remove and re-add filters at the application layer. + */ + err = tcan4x5x_set_config_mode_sel(dev, CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP, + ®); + return err; + case PM_DEVICE_ACTION_RESUME: + /* Wake up the device */ +#if TCAN4X5X_WAKE_GPIO_SUPPORT + LOG_DBG("Waking up TCAN4x5x via WAKE GPIO"); + err = tcan4x5x_wake(dev); + if (err != 0) { + return err; + } +#else + LOG_DBG("Waking up TCAN4x5x via reset"); + err = tcan4x5x_reset(dev); + if (err != 0) { + return err; + } +#endif + /* Enter normal mode */ + return tcan4x5x_init_normal_mode(dev); + default: + break; + } + + return -ENOTSUP; +} +#endif /* CONFIG_PM_DEVICE */ + static int tcan4x5x_init(const struct device *dev) { const struct can_mcan_config *mcan_config = dev->config; @@ -558,7 +715,6 @@ static int tcan4x5x_init(const struct device *dev) struct can_mcan_data *mcan_data = dev->data; struct tcan4x5x_data *tcan_data = mcan_data->custom; k_tid_t tid; - uint32_t reg; int err; /* Initialize int_sem to 1 to ensure any pending IRQ is serviced */ @@ -671,48 +827,7 @@ static int tcan4x5x_init(const struct device *dev) FIELD_GET(GENMASK(15, 8), info[2]), FIELD_GET(GENMASK(7, 0), info[2])); #endif /* CONFIG_CAN_LOG_LEVEL >= LOG_LEVEL_DBG */ - /* Set TCAN4x5x mode normal */ - err = tcan4x5x_read_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, ®); - if (err != 0) { - LOG_ERR("failed to read configuration register (err %d)", err); - return -ENODEV; - } - - reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL); - reg |= FIELD_PREP(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, 0x02); - reg |= CAN_TCAN4X5X_MODE_CONFIG_WAKE_CONFIG; - - if (tcan_config->clk_freq == MHZ(20)) { - /* 20 MHz frequency reference */ - reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_CLK_REF); - } else { - /* 40 MHz frequency reference */ - reg |= CAN_TCAN4X5X_MODE_CONFIG_CLK_REF; - } - - err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg); - if (err != 0) { - LOG_ERR("failed to write configuration register (err %d)", err); - return -ENODEV; - } - - /* Wait for standby to normal mode switch */ - k_busy_wait(CAN_TCAN4X5X_T_MODE_STBY_NOM_US); - - /* Configure Message RAM */ - err = can_mcan_configure_mram(dev, CAN_TCAN4X5X_MRAM_BASE, CAN_TCAN4X5X_MRAM_BASE); - if (err != 0) { - return -EIO; - } - - /* Initialize M_CAN */ - err = can_mcan_init(dev); - if (err != 0) { - LOG_ERR("failed to initialize mcan (err %d)", err); - return err; - } - - return 0; + return tcan4x5x_init_normal_mode(dev); } static DEVICE_API(can, tcan4x5x_driver_api) = { @@ -794,8 +909,9 @@ static const struct can_mcan_ops tcan4x5x_ops = { static struct can_mcan_data can_mcan_data_##inst = \ CAN_MCAN_DATA_INITIALIZER(&tcan4x5x_data_##inst); \ \ - CAN_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_init, NULL, &can_mcan_data_##inst, \ - &can_mcan_config_##inst, POST_KERNEL, CONFIG_CAN_INIT_PRIORITY, \ - &tcan4x5x_driver_api); + PM_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_pm_control); \ + CAN_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_init, PM_DEVICE_DT_INST_GET(inst), \ + &can_mcan_data_##inst, &can_mcan_config_##inst, POST_KERNEL, \ + CONFIG_CAN_INIT_PRIORITY, &tcan4x5x_driver_api); DT_INST_FOREACH_STATUS_OKAY(TCAN4X5X_INIT) diff --git a/include/zephyr/drivers/can/can_mcan.h b/include/zephyr/drivers/can/can_mcan.h index 25eeb437efba..28aa899371c8 100644 --- a/include/zephyr/drivers/can/can_mcan.h +++ b/include/zephyr/drivers/can/can_mcan.h @@ -1065,6 +1065,7 @@ struct can_mcan_data { struct k_mutex lock; struct k_sem tx_sem; struct k_mutex tx_mtx; + struct k_timer txbcf_timer; void *custom; } __aligned(4);