| /* |
| * Copyright (c) 2022-2023 Vestas Wind Systems A/S |
| * Copyright (c) 2020 Alexander Wachter |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/can.h> |
| #include <zephyr/drivers/can/can_mcan.h> |
| #include <zephyr/drivers/can/transceiver.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.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 |
| |
| int can_mcan_read_reg(const struct device *dev, uint16_t reg, uint32_t *val) |
| { |
| const struct can_mcan_config *config = dev->config; |
| int err; |
| |
| err = config->ops->read_reg(dev, reg, val); |
| if (err != 0) { |
| LOG_ERR("failed to read reg 0x%03x (err %d)", reg, err); |
| } |
| |
| return err; |
| } |
| |
| int can_mcan_write_reg(const struct device *dev, uint16_t reg, uint32_t val) |
| { |
| const struct can_mcan_config *config = dev->config; |
| int err; |
| |
| err = config->ops->write_reg(dev, reg, val); |
| if (err != 0) { |
| LOG_ERR("failed to write reg 0x%03x (err %d)", reg, err); |
| } |
| |
| return err; |
| } |
| |
| static int can_mcan_exit_sleep_mode(const struct device *dev) |
| { |
| struct can_mcan_data *data = dev->data; |
| uint32_t start_time; |
| uint32_t cccr; |
| int err; |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| cccr &= ~CAN_MCAN_CCCR_CSR; |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| start_time = k_cycle_get_32(); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| while ((cccr & CAN_MCAN_CCCR_CSA) == CAN_MCAN_CCCR_CSA) { |
| if (k_cycle_get_32() - start_time > k_ms_to_cyc_ceil32(CAN_INIT_TIMEOUT_MS)) { |
| cccr |= CAN_MCAN_CCCR_CSR; |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| err = -EAGAIN; |
| goto unlock; |
| } |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| } |
| |
| |
| unlock: |
| k_mutex_unlock(&data->lock); |
| |
| return err; |
| } |
| |
| static int can_mcan_enter_init_mode(const struct device *dev, k_timeout_t timeout) |
| { |
| struct can_mcan_data *data = dev->data; |
| int64_t start_time; |
| uint32_t cccr; |
| int err; |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| cccr |= CAN_MCAN_CCCR_INIT; |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| start_time = k_uptime_ticks(); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| while ((cccr & CAN_MCAN_CCCR_INIT) == 0U) { |
| if (k_uptime_ticks() - start_time > timeout.ticks) { |
| cccr &= ~CAN_MCAN_CCCR_INIT; |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| err = -EAGAIN; |
| goto unlock; |
| } |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| } |
| |
| unlock: |
| k_mutex_unlock(&data->lock); |
| |
| return err; |
| } |
| |
| static int can_mcan_leave_init_mode(const struct device *dev, k_timeout_t timeout) |
| { |
| struct can_mcan_data *data = dev->data; |
| int64_t start_time; |
| uint32_t cccr; |
| int err; |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| cccr &= ~CAN_MCAN_CCCR_INIT; |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| start_time = k_uptime_ticks(); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| while ((cccr & CAN_MCAN_CCCR_INIT) != 0U) { |
| if (k_uptime_ticks() - start_time > timeout.ticks) { |
| err = -EAGAIN; |
| goto unlock; |
| } |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| } |
| |
| unlock: |
| k_mutex_unlock(&data->lock); |
| |
| return err; |
| } |
| |
| int can_mcan_set_timing(const struct device *dev, const struct can_timing *timing) |
| { |
| struct can_mcan_data *data = dev->data; |
| uint32_t nbtp = 0U; |
| int err; |
| |
| if (data->common.started) { |
| return -EBUSY; |
| } |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| nbtp |= FIELD_PREP(CAN_MCAN_NBTP_NSJW, timing->sjw - 1UL) | |
| FIELD_PREP(CAN_MCAN_NBTP_NTSEG1, timing->phase_seg1 - 1UL) | |
| FIELD_PREP(CAN_MCAN_NBTP_NTSEG2, timing->phase_seg2 - 1UL) | |
| FIELD_PREP(CAN_MCAN_NBTP_NBRP, timing->prescaler - 1UL); |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_NBTP, nbtp); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| unlock: |
| k_mutex_unlock(&data->lock); |
| |
| return err; |
| } |
| |
| #ifdef CONFIG_CAN_FD_MODE |
| int can_mcan_set_timing_data(const struct device *dev, const struct can_timing *timing_data) |
| { |
| struct can_mcan_data *data = dev->data; |
| uint32_t dbtp = 0U; |
| int err; |
| |
| if (data->common.started) { |
| return -EBUSY; |
| } |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| dbtp |= FIELD_PREP(CAN_MCAN_DBTP_DSJW, timing_data->sjw - 1UL) | |
| FIELD_PREP(CAN_MCAN_DBTP_DTSEG1, timing_data->phase_seg1 - 1UL) | |
| FIELD_PREP(CAN_MCAN_DBTP_DTSEG2, timing_data->phase_seg2 - 1UL) | |
| FIELD_PREP(CAN_MCAN_DBTP_DBRP, timing_data->prescaler - 1UL); |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_DBTP, dbtp); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| unlock: |
| k_mutex_unlock(&data->lock); |
| |
| return err; |
| } |
| #endif /* CONFIG_CAN_FD_MODE */ |
| |
| 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; |
| |
| #if CONFIG_CAN_FD_MODE |
| *cap |= CAN_MODE_FD; |
| #endif /* CONFIG_CAN_FD_MODE */ |
| |
| return 0; |
| } |
| |
| int can_mcan_start(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; |
| } |
| |
| if (config->common.phy != NULL) { |
| err = can_transceiver_enable(config->common.phy, data->common.mode); |
| if (err != 0) { |
| LOG_ERR("failed to enable CAN transceiver (err %d)", err); |
| return err; |
| } |
| } |
| |
| /* Reset statistics */ |
| CAN_STATS_RESET(dev); |
| |
| err = can_mcan_leave_init_mode(dev, K_MSEC(CAN_INIT_TIMEOUT_MS)); |
| if (err != 0) { |
| LOG_ERR("failed to leave init mode"); |
| |
| if (config->common.phy != NULL) { |
| /* Attempt to disable the CAN transceiver in case of error */ |
| (void)can_transceiver_disable(config->common.phy); |
| } |
| |
| return -EIO; |
| } |
| |
| data->common.started = true; |
| |
| return 0; |
| } |
| |
| int can_mcan_stop(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; |
| can_tx_callback_t tx_cb; |
| uint32_t tx_idx; |
| int err; |
| |
| if (!data->common.started) { |
| return -EALREADY; |
| } |
| |
| /* 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) { |
| LOG_ERR("Failed to enter init mode"); |
| return -EIO; |
| } |
| |
| if (config->common.phy != NULL) { |
| err = can_transceiver_disable(config->common.phy); |
| if (err != 0) { |
| LOG_ERR("failed to disable CAN transceiver (err %d)", err); |
| return err; |
| } |
| } |
| |
| can_mcan_enable_configuration_change(dev); |
| |
| 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); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int can_mcan_set_mode(const struct device *dev, can_mode_t mode) |
| { |
| struct can_mcan_data *data = dev->data; |
| uint32_t cccr; |
| uint32_t test; |
| int err; |
| |
| #ifdef CONFIG_CAN_FD_MODE |
| if ((mode & ~(CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY | CAN_MODE_FD)) != 0U) { |
| LOG_ERR("unsupported mode: 0x%08x", mode); |
| return -ENOTSUP; |
| } |
| #else /* CONFIG_CAN_FD_MODE */ |
| if ((mode & ~(CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY)) != 0U) { |
| LOG_ERR("unsupported mode: 0x%08x", mode); |
| return -ENOTSUP; |
| } |
| #endif /* !CONFIG_CAN_FD_MODE */ |
| |
| if (data->common.started) { |
| return -EBUSY; |
| } |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_TEST, &test); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| if ((mode & CAN_MODE_LOOPBACK) != 0) { |
| /* Loopback mode */ |
| cccr |= CAN_MCAN_CCCR_TEST; |
| test |= CAN_MCAN_TEST_LBCK; |
| } else { |
| cccr &= ~CAN_MCAN_CCCR_TEST; |
| } |
| |
| if ((mode & CAN_MODE_LISTENONLY) != 0) { |
| /* Bus monitoring mode */ |
| cccr |= CAN_MCAN_CCCR_MON; |
| } else { |
| cccr &= ~CAN_MCAN_CCCR_MON; |
| } |
| |
| #ifdef CONFIG_CAN_FD_MODE |
| if ((mode & CAN_MODE_FD) != 0) { |
| cccr |= CAN_MCAN_CCCR_FDOE | CAN_MCAN_CCCR_BRSE; |
| } else { |
| cccr &= ~(CAN_MCAN_CCCR_FDOE | CAN_MCAN_CCCR_BRSE); |
| } |
| #endif /* CONFIG_CAN_FD_MODE */ |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_TEST, test); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| data->common.mode = mode; |
| |
| unlock: |
| k_mutex_unlock(&data->lock); |
| |
| return err; |
| } |
| |
| 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; |
| uint32_t tx_idx; |
| struct can_bus_err_cnt err_cnt; |
| enum can_state state; |
| uint32_t cccr; |
| int err; |
| |
| (void)can_mcan_get_state(dev, &state, &err_cnt); |
| |
| if (state_cb != NULL) { |
| state_cb(dev, state, err_cnt, state_cb_data); |
| } |
| |
| if (state == CAN_STATE_BUS_OFF) { |
| /* Request all TX buffers to be cancelled */ |
| err = can_mcan_write_reg(dev, CAN_MCAN_TXBCR, CAN_MCAN_TXBCR_CR); |
| if (err != 0) { |
| return; |
| } |
| |
| /* Call all TX queue callbacks with -ENETUNREACH */ |
| 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, -ENETUNREACH, cbs->tx[tx_idx].user_data); |
| k_sem_give(&data->tx_sem); |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_CAN_AUTO_BUS_OFF_RECOVERY)) { |
| /* |
| * Request leaving init mode, but do not take the lock (as we are in ISR |
| * context), nor wait for the result. |
| */ |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| return; |
| } |
| |
| cccr &= ~CAN_MCAN_CCCR_INIT; |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| return; |
| } |
| } |
| } |
| } |
| |
| static void can_mcan_tx_event_handler(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; |
| struct can_mcan_tx_event_fifo tx_event; |
| can_tx_callback_t tx_cb; |
| void *user_data; |
| uint32_t event_idx; |
| uint32_t tx_idx; |
| uint32_t txefs; |
| int err; |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_TXEFS, &txefs); |
| if (err != 0) { |
| return; |
| } |
| |
| while ((txefs & CAN_MCAN_TXEFS_EFFL) != 0U) { |
| event_idx = FIELD_GET(CAN_MCAN_TXEFS_EFGI, txefs); |
| err = can_mcan_read_mram(dev, |
| config->mram_offsets[CAN_MCAN_MRAM_CFG_TX_EVENT_FIFO] + |
| event_idx * sizeof(struct can_mcan_tx_event_fifo), |
| &tx_event, |
| sizeof(struct can_mcan_tx_event_fifo)); |
| if (err != 0) { |
| LOG_ERR("failed to read tx event fifo (err %d)", err); |
| return; |
| } |
| |
| tx_idx = tx_event.mm; |
| |
| /* Acknowledge TX event */ |
| err = can_mcan_write_reg(dev, CAN_MCAN_TXEFA, event_idx); |
| if (err != 0) { |
| return; |
| } |
| |
| __ASSERT_NO_MSG(tx_idx < cbs->num_tx); |
| tx_cb = cbs->tx[tx_idx].function; |
| user_data = cbs->tx[tx_idx].user_data; |
| cbs->tx[tx_idx].function = NULL; |
| |
| k_sem_give(&data->tx_sem); |
| |
| tx_cb(dev, 0, user_data); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_TXEFS, &txefs); |
| if (err != 0) { |
| return; |
| } |
| } |
| } |
| |
| #ifdef CONFIG_CAN_STATS |
| static void can_mcan_lec_update_stats(const struct device *dev, enum can_mcan_psr_lec lec) |
| { |
| switch (lec) { |
| case CAN_MCAN_PSR_LEC_STUFF_ERROR: |
| CAN_STATS_STUFF_ERROR_INC(dev); |
| break; |
| case CAN_MCAN_PSR_LEC_FORM_ERROR: |
| CAN_STATS_FORM_ERROR_INC(dev); |
| break; |
| case CAN_MCAN_PSR_LEC_ACK_ERROR: |
| CAN_STATS_ACK_ERROR_INC(dev); |
| break; |
| case CAN_MCAN_PSR_LEC_BIT1_ERROR: |
| CAN_STATS_BIT1_ERROR_INC(dev); |
| break; |
| case CAN_MCAN_PSR_LEC_BIT0_ERROR: |
| CAN_STATS_BIT0_ERROR_INC(dev); |
| break; |
| case CAN_MCAN_PSR_LEC_CRC_ERROR: |
| CAN_STATS_CRC_ERROR_INC(dev); |
| break; |
| case CAN_MCAN_PSR_LEC_NO_ERROR: |
| case CAN_MCAN_PSR_LEC_NO_CHANGE: |
| default: |
| break; |
| } |
| } |
| #endif /* CONFIG_CAN_STATS */ |
| |
| static int can_mcan_read_psr(const struct device *dev, uint32_t *val) |
| { |
| /* Reading the lower byte of the PSR register clears the protocol last |
| * error codes (LEC). To avoid missing errors, this function should be |
| * used whenever the PSR register is read. |
| */ |
| int err = can_mcan_read_reg(dev, CAN_MCAN_PSR, val); |
| |
| if (err != 0) { |
| return err; |
| } |
| |
| #ifdef CONFIG_CAN_STATS |
| enum can_mcan_psr_lec lec; |
| |
| lec = FIELD_GET(CAN_MCAN_PSR_LEC, *val); |
| can_mcan_lec_update_stats(dev, lec); |
| #ifdef CONFIG_CAN_FD_MODE |
| lec = FIELD_GET(CAN_MCAN_PSR_DLEC, *val); |
| can_mcan_lec_update_stats(dev, lec); |
| #endif |
| #endif /* CONFIG_CAN_STATS */ |
| |
| return 0; |
| } |
| |
| void can_mcan_line_0_isr(const struct device *dev) |
| { |
| 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; |
| struct can_mcan_data *data = dev->data; |
| uint32_t ir; |
| int err; |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_IR, &ir); |
| if (err != 0) { |
| return; |
| } |
| |
| while ((ir & events) != 0U) { |
| err = can_mcan_write_reg(dev, CAN_MCAN_IR, ir & events); |
| if (err != 0) { |
| return; |
| } |
| |
| if ((ir & (CAN_MCAN_IR_BO | CAN_MCAN_IR_EP | CAN_MCAN_IR_EW)) != 0U) { |
| can_mcan_state_change_handler(dev); |
| } |
| |
| /* TX event FIFO new entry */ |
| if ((ir & CAN_MCAN_IR_TEFN) != 0U) { |
| can_mcan_tx_event_handler(dev); |
| } |
| |
| if ((ir & CAN_MCAN_IR_TEFL) != 0U) { |
| LOG_ERR("TX FIFO element lost"); |
| k_sem_give(&data->tx_sem); |
| } |
| |
| if ((ir & CAN_MCAN_IR_ARA) != 0U) { |
| LOG_ERR("Access to reserved address"); |
| } |
| |
| if ((ir & CAN_MCAN_IR_MRAF) != 0U) { |
| LOG_ERR("Message RAM access failure"); |
| } |
| |
| #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, ®); |
| } |
| #endif |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_IR, &ir); |
| if (err != 0) { |
| return; |
| } |
| } |
| } |
| |
| static void can_mcan_get_message(const struct device *dev, uint16_t fifo_offset, |
| uint16_t fifo_status_reg, uint16_t fifo_ack_reg) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const struct can_mcan_callbacks *cbs = config->callbacks; |
| struct can_mcan_rx_fifo_hdr hdr; |
| struct can_frame frame = {0}; |
| can_rx_callback_t cb; |
| void *user_data; |
| uint32_t get_idx; |
| uint32_t filt_idx; |
| int data_length; |
| uint32_t fifo_status; |
| int err; |
| |
| err = can_mcan_read_reg(dev, fifo_status_reg, &fifo_status); |
| if (err != 0) { |
| return; |
| } |
| |
| while (FIELD_GET(CAN_MCAN_RXF0S_F0FL, fifo_status) != 0U) { |
| get_idx = FIELD_GET(CAN_MCAN_RXF0S_F0GI, fifo_status); |
| |
| err = can_mcan_read_mram(dev, fifo_offset + get_idx * |
| sizeof(struct can_mcan_rx_fifo) + |
| offsetof(struct can_mcan_rx_fifo, hdr), |
| &hdr, sizeof(struct can_mcan_rx_fifo_hdr)); |
| if (err != 0) { |
| LOG_ERR("failed to read Rx FIFO header (err %d)", err); |
| return; |
| } |
| |
| frame.dlc = hdr.dlc; |
| |
| if (hdr.rtr != 0) { |
| frame.flags |= CAN_FRAME_RTR; |
| } |
| |
| if (hdr.fdf != 0) { |
| frame.flags |= CAN_FRAME_FDF; |
| } |
| |
| if (hdr.brs != 0) { |
| frame.flags |= CAN_FRAME_BRS; |
| } |
| |
| if (hdr.esi != 0) { |
| frame.flags |= CAN_FRAME_ESI; |
| } |
| |
| #ifdef CONFIG_CAN_RX_TIMESTAMP |
| frame.timestamp = hdr.rxts; |
| #endif /* CONFIG_CAN_RX_TIMESTAMP */ |
| |
| filt_idx = hdr.fidx; |
| |
| if (hdr.xtd != 0) { |
| frame.id = hdr.ext_id; |
| frame.flags |= CAN_FRAME_IDE; |
| } else { |
| frame.id = hdr.std_id; |
| } |
| |
| data_length = can_dlc_to_bytes(frame.dlc); |
| if (data_length <= sizeof(frame.data)) { |
| if ((frame.flags & CAN_FRAME_RTR) == 0U) { |
| err = can_mcan_read_mram(dev, fifo_offset + get_idx * |
| sizeof(struct can_mcan_rx_fifo) + |
| offsetof(struct can_mcan_rx_fifo, data_32), |
| &frame.data_32, |
| ROUND_UP(data_length, sizeof(uint32_t))); |
| if (err != 0) { |
| LOG_ERR("failed to read Rx FIFO data (err %d)", err); |
| return; |
| } |
| } |
| |
| if ((frame.flags & CAN_FRAME_IDE) != 0) { |
| LOG_DBG("Frame on filter %d, ID: 0x%x", |
| filt_idx + cbs->num_std, frame.id); |
| __ASSERT_NO_MSG(filt_idx < cbs->num_ext); |
| cb = cbs->ext[filt_idx].function; |
| user_data = cbs->ext[filt_idx].user_data; |
| } else { |
| LOG_DBG("Frame on filter %d, ID: 0x%x", filt_idx, frame.id); |
| __ASSERT_NO_MSG(filt_idx < cbs->num_std); |
| cb = cbs->std[filt_idx].function; |
| user_data = cbs->std[filt_idx].user_data; |
| } |
| |
| if (cb) { |
| cb(dev, &frame, user_data); |
| } else { |
| LOG_DBG("cb missing"); |
| } |
| } else { |
| LOG_ERR("Frame is too big"); |
| } |
| |
| err = can_mcan_write_reg(dev, fifo_ack_reg, get_idx); |
| if (err != 0) { |
| return; |
| } |
| |
| err = can_mcan_read_reg(dev, fifo_status_reg, &fifo_status); |
| if (err != 0) { |
| return; |
| } |
| } |
| } |
| |
| void can_mcan_line_1_isr(const struct device *dev) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const uint32_t events = |
| CAN_MCAN_IR_RF0N | CAN_MCAN_IR_RF1N | CAN_MCAN_IR_RF0L | CAN_MCAN_IR_RF1L; |
| uint32_t ir; |
| int err; |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_IR, &ir); |
| if (err != 0) { |
| return; |
| } |
| |
| while ((ir & events) != 0U) { |
| err = can_mcan_write_reg(dev, CAN_MCAN_IR, events & ir); |
| if (err != 0) { |
| return; |
| } |
| |
| if ((ir & CAN_MCAN_IR_RF0N) != 0U) { |
| LOG_DBG("RX FIFO0 INT"); |
| can_mcan_get_message(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_RX_FIFO0], |
| CAN_MCAN_RXF0S, CAN_MCAN_RXF0A); |
| } |
| |
| if ((ir & CAN_MCAN_IR_RF1N) != 0U) { |
| LOG_DBG("RX FIFO1 INT"); |
| can_mcan_get_message(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_RX_FIFO1], |
| CAN_MCAN_RXF1S, CAN_MCAN_RXF1A); |
| } |
| |
| if ((ir & CAN_MCAN_IR_RF0L) != 0U) { |
| LOG_ERR("Message lost on FIFO0"); |
| CAN_STATS_RX_OVERRUN_INC(dev); |
| } |
| |
| if ((ir & CAN_MCAN_IR_RF1L) != 0U) { |
| LOG_ERR("Message lost on FIFO1"); |
| CAN_STATS_RX_OVERRUN_INC(dev); |
| } |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_IR, &ir); |
| if (err != 0) { |
| return; |
| } |
| } |
| } |
| |
| int can_mcan_get_state(const struct device *dev, enum can_state *state, |
| struct can_bus_err_cnt *err_cnt) |
| { |
| struct can_mcan_data *data = dev->data; |
| uint32_t reg; |
| int err; |
| |
| if (state != NULL) { |
| err = can_mcan_read_psr(dev, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| if (!data->common.started) { |
| *state = CAN_STATE_STOPPED; |
| } else if ((reg & CAN_MCAN_PSR_BO) != 0U) { |
| *state = CAN_STATE_BUS_OFF; |
| } else if ((reg & CAN_MCAN_PSR_EP) != 0U) { |
| *state = CAN_STATE_ERROR_PASSIVE; |
| } else if ((reg & CAN_MCAN_PSR_EW) != 0U) { |
| *state = CAN_STATE_ERROR_WARNING; |
| } else { |
| *state = CAN_STATE_ERROR_ACTIVE; |
| } |
| } |
| |
| if (err_cnt != NULL) { |
| err = can_mcan_read_reg(dev, CAN_MCAN_ECR, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| err_cnt->tx_err_cnt = FIELD_GET(CAN_MCAN_ECR_TEC, reg); |
| err_cnt->rx_err_cnt = FIELD_GET(CAN_MCAN_ECR_REC, reg); |
| } |
| |
| return 0; |
| } |
| |
| #ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY |
| int can_mcan_recover(const struct device *dev, k_timeout_t timeout) |
| { |
| struct can_mcan_data *data = dev->data; |
| |
| if (!data->common.started) { |
| return -ENETDOWN; |
| } |
| |
| return can_mcan_leave_init_mode(dev, timeout); |
| } |
| #endif /* CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */ |
| |
| int can_mcan_send(const struct device *dev, const struct can_frame *frame, k_timeout_t timeout, |
| can_tx_callback_t callback, void *user_data) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const struct can_mcan_callbacks *cbs = config->callbacks; |
| struct can_mcan_data *data = dev->data; |
| size_t data_length = can_dlc_to_bytes(frame->dlc); |
| struct can_mcan_tx_buffer_hdr tx_hdr = { |
| .rtr = (frame->flags & CAN_FRAME_RTR) != 0U ? 1U : 0U, |
| .xtd = (frame->flags & CAN_FRAME_IDE) != 0U ? 1U : 0U, |
| .esi = 0U, |
| .dlc = frame->dlc, |
| #ifdef CONFIG_CAN_FD_MODE |
| .fdf = (frame->flags & CAN_FRAME_FDF) != 0U ? 1U : 0U, |
| .brs = (frame->flags & CAN_FRAME_BRS) != 0U ? 1U : 0U, |
| #else /* CONFIG_CAN_FD_MODE */ |
| .fdf = 0U, |
| .brs = 0U, |
| #endif /* !CONFIG_CAN_FD_MODE */ |
| .efc = 1U, |
| }; |
| uint32_t put_idx = -1; |
| uint32_t reg; |
| int err; |
| |
| LOG_DBG("Sending %zu bytes. Id: 0x%x, ID type: %s %s %s %s", data_length, frame->id, |
| (frame->flags & CAN_FRAME_IDE) != 0U ? "extended" : "standard", |
| (frame->flags & CAN_FRAME_RTR) != 0U ? "RTR" : "", |
| (frame->flags & CAN_FRAME_FDF) != 0U ? "FD frame" : "", |
| (frame->flags & CAN_FRAME_BRS) != 0U ? "BRS" : ""); |
| |
| __ASSERT_NO_MSG(callback != NULL); |
| |
| #ifdef CONFIG_CAN_FD_MODE |
| if ((frame->flags & ~(CAN_FRAME_IDE | CAN_FRAME_RTR | CAN_FRAME_FDF | CAN_FRAME_BRS)) != |
| 0) { |
| LOG_ERR("unsupported CAN frame flags 0x%02x", frame->flags); |
| return -ENOTSUP; |
| } |
| |
| if ((data->common.mode & CAN_MODE_FD) == 0U && |
| ((frame->flags & (CAN_FRAME_FDF | CAN_FRAME_BRS)) != 0U)) { |
| LOG_ERR("CAN FD format not supported in non-FD mode"); |
| return -ENOTSUP; |
| } |
| #else /* CONFIG_CAN_FD_MODE */ |
| if ((frame->flags & ~(CAN_FRAME_IDE | CAN_FRAME_RTR)) != 0U) { |
| LOG_ERR("unsupported CAN frame flags 0x%02x", frame->flags); |
| return -ENOTSUP; |
| } |
| #endif /* !CONFIG_CAN_FD_MODE */ |
| |
| if (data_length > sizeof(frame->data)) { |
| LOG_ERR("data length (%zu) > max frame data length (%zu)", data_length, |
| sizeof(frame->data)); |
| return -EINVAL; |
| } |
| |
| if ((frame->flags & CAN_FRAME_FDF) != 0U) { |
| if (frame->dlc > CANFD_MAX_DLC) { |
| LOG_ERR("DLC of %d for CAN FD format frame", frame->dlc); |
| return -EINVAL; |
| } |
| } else { |
| if (frame->dlc > CAN_MAX_DLC) { |
| LOG_ERR("DLC of %d for non-FD format frame", frame->dlc); |
| return -EINVAL; |
| } |
| } |
| |
| if (!data->common.started) { |
| return -ENETDOWN; |
| } |
| |
| err = can_mcan_read_psr(dev, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| if ((reg & CAN_MCAN_PSR_BO) != 0U) { |
| return -ENETUNREACH; |
| } |
| |
| err = k_sem_take(&data->tx_sem, timeout); |
| if (err != 0) { |
| return -EAGAIN; |
| } |
| |
| k_mutex_lock(&data->tx_mtx, K_FOREVER); |
| |
| /* Acquire a free TX buffer */ |
| for (int i = 0; i < cbs->num_tx; i++) { |
| if (cbs->tx[i].function == NULL) { |
| put_idx = i; |
| break; |
| } |
| } |
| |
| tx_hdr.mm = put_idx; |
| |
| if ((frame->flags & CAN_FRAME_IDE) != 0U) { |
| tx_hdr.ext_id = frame->id; |
| } else { |
| tx_hdr.std_id = frame->id & CAN_STD_ID_MASK; |
| } |
| |
| err = can_mcan_write_mram(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_TX_BUFFER] + put_idx * |
| sizeof(struct can_mcan_tx_buffer) + |
| offsetof(struct can_mcan_tx_buffer, hdr), |
| &tx_hdr, sizeof(struct can_mcan_tx_buffer_hdr)); |
| if (err != 0) { |
| LOG_ERR("failed to write Tx Buffer header (err %d)", err); |
| goto err_unlock; |
| } |
| |
| if ((frame->flags & CAN_FRAME_RTR) == 0U) { |
| err = can_mcan_write_mram(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_TX_BUFFER] + |
| put_idx * sizeof(struct can_mcan_tx_buffer) + |
| offsetof(struct can_mcan_tx_buffer, data_32), |
| &frame->data_32, ROUND_UP(data_length, sizeof(uint32_t))); |
| if (err != 0) { |
| LOG_ERR("failed to write Tx Buffer data (err %d)", err); |
| goto err_unlock; |
| } |
| } |
| |
| __ASSERT_NO_MSG(put_idx < cbs->num_tx); |
| cbs->tx[put_idx].function = callback; |
| cbs->tx[put_idx].user_data = user_data; |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_TXBAR, BIT(put_idx)); |
| if (err != 0) { |
| cbs->tx[put_idx].function = NULL; |
| goto err_unlock; |
| } |
| |
| k_mutex_unlock(&data->tx_mtx); |
| return 0; |
| |
| err_unlock: |
| k_mutex_unlock(&data->tx_mtx); |
| k_sem_give(&data->tx_sem); |
| |
| return err; |
| } |
| |
| int can_mcan_get_max_filters(const struct device *dev, bool ide) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const struct can_mcan_callbacks *cbs = config->callbacks; |
| |
| if (ide) { |
| return cbs->num_ext; |
| } else { |
| return cbs->num_std; |
| } |
| } |
| |
| /* 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_add_rx_filter_std(const struct device *dev, can_rx_callback_t callback, |
| void *user_data, const struct can_filter *filter) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const struct can_mcan_callbacks *cbs = config->callbacks; |
| struct can_mcan_data *data = dev->data; |
| struct can_mcan_std_filter filter_element = { |
| .sfid1 = filter->id, |
| .sfid2 = filter->mask, |
| .sft = CAN_MCAN_SFT_CLASSIC |
| }; |
| int filter_id = -ENOSPC; |
| int err; |
| int i; |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| for (i = 0; i < cbs->num_std; i++) { |
| if (cbs->std[i].function == NULL) { |
| filter_id = i; |
| break; |
| } |
| } |
| |
| if (filter_id == -ENOSPC) { |
| LOG_WRN("No free standard id filter left"); |
| k_mutex_unlock(&data->lock); |
| return -ENOSPC; |
| } |
| |
| /* TODO proper fifo balancing */ |
| filter_element.sfec = filter_id & 0x01 ? CAN_MCAN_XFEC_FIFO1 : CAN_MCAN_XFEC_FIFO0; |
| |
| err = can_mcan_write_mram(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_STD_FILTER] + |
| filter_id * sizeof(struct can_mcan_std_filter), |
| &filter_element, sizeof(filter_element)); |
| if (err != 0) { |
| LOG_ERR("failed to write std filter element (err %d)", err); |
| return err; |
| } |
| |
| k_mutex_unlock(&data->lock); |
| |
| LOG_DBG("Attached std filter at %d", filter_id); |
| |
| __ASSERT_NO_MSG(filter_id < cbs->num_std); |
| cbs->std[filter_id].function = callback; |
| cbs->std[filter_id].user_data = user_data; |
| |
| return filter_id; |
| } |
| |
| static int can_mcan_add_rx_filter_ext(const struct device *dev, can_rx_callback_t callback, |
| void *user_data, const struct can_filter *filter) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const struct can_mcan_callbacks *cbs = config->callbacks; |
| struct can_mcan_data *data = dev->data; |
| struct can_mcan_ext_filter filter_element = { |
| .efid2 = filter->mask, |
| .efid1 = filter->id, |
| .eft = CAN_MCAN_EFT_CLASSIC |
| }; |
| int filter_id = -ENOSPC; |
| int err; |
| int i; |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| for (i = 0; i < cbs->num_ext; i++) { |
| if (cbs->ext[i].function == NULL) { |
| filter_id = i; |
| break; |
| } |
| } |
| |
| if (filter_id == -ENOSPC) { |
| LOG_WRN("No free extended id filter left"); |
| k_mutex_unlock(&data->lock); |
| return -ENOSPC; |
| } |
| |
| /* TODO proper fifo balancing */ |
| filter_element.efec = filter_id & 0x01 ? CAN_MCAN_XFEC_FIFO1 : CAN_MCAN_XFEC_FIFO0; |
| |
| err = can_mcan_write_mram(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_EXT_FILTER] + |
| filter_id * sizeof(struct can_mcan_ext_filter), |
| &filter_element, sizeof(filter_element)); |
| if (err != 0) { |
| LOG_ERR("failed to write std filter element (err %d)", err); |
| return err; |
| } |
| |
| k_mutex_unlock(&data->lock); |
| |
| LOG_DBG("Attached ext filter at %d", filter_id); |
| |
| __ASSERT_NO_MSG(filter_id < cbs->num_ext); |
| cbs->ext[filter_id].function = callback; |
| cbs->ext[filter_id].user_data = user_data; |
| |
| return filter_id; |
| } |
| |
| int can_mcan_add_rx_filter(const struct device *dev, can_rx_callback_t callback, void *user_data, |
| const struct can_filter *filter) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const struct can_mcan_callbacks *cbs = config->callbacks; |
| int filter_id; |
| |
| if (callback == NULL) { |
| return -EINVAL; |
| } |
| |
| if ((filter->flags & ~(CAN_FILTER_IDE)) != 0U) { |
| LOG_ERR("unsupported CAN filter flags 0x%02x", filter->flags); |
| return -ENOTSUP; |
| } |
| |
| if ((filter->flags & CAN_FILTER_IDE) != 0U) { |
| filter_id = can_mcan_add_rx_filter_ext(dev, callback, user_data, filter); |
| if (filter_id >= 0) { |
| filter_id += cbs->num_std; |
| } |
| } else { |
| filter_id = can_mcan_add_rx_filter_std(dev, callback, user_data, filter); |
| } |
| |
| return filter_id; |
| } |
| |
| void can_mcan_remove_rx_filter(const struct device *dev, int filter_id) |
| { |
| const struct can_mcan_config *config = dev->config; |
| const struct can_mcan_callbacks *cbs = config->callbacks; |
| struct can_mcan_data *data = dev->data; |
| int err; |
| |
| if (filter_id < 0) { |
| LOG_ERR("filter ID %d out of bounds", filter_id); |
| return; |
| } |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| if (filter_id >= cbs->num_std) { |
| filter_id -= cbs->num_std; |
| if (filter_id >= cbs->num_ext) { |
| LOG_ERR("filter ID %d out of bounds", filter_id); |
| k_mutex_unlock(&data->lock); |
| return; |
| } |
| |
| cbs->ext[filter_id].function = NULL; |
| cbs->ext[filter_id].user_data = NULL; |
| |
| err = can_mcan_clear_mram(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_EXT_FILTER] + |
| filter_id * sizeof(struct can_mcan_ext_filter), |
| sizeof(struct can_mcan_ext_filter)); |
| if (err != 0) { |
| LOG_ERR("failed to clear ext filter element (err %d)", err); |
| } |
| } else { |
| cbs->std[filter_id].function = NULL; |
| cbs->std[filter_id].user_data = NULL; |
| |
| err = can_mcan_clear_mram(dev, config->mram_offsets[CAN_MCAN_MRAM_CFG_STD_FILTER] + |
| filter_id * sizeof(struct can_mcan_std_filter), |
| sizeof(struct can_mcan_std_filter)); |
| if (err != 0) { |
| LOG_ERR("failed to clear std filter element (err %d)", err); |
| } |
| } |
| |
| k_mutex_unlock(&data->lock); |
| } |
| |
| void can_mcan_set_state_change_callback(const struct device *dev, |
| can_state_change_callback_t callback, void *user_data) |
| { |
| struct can_mcan_data *data = dev->data; |
| |
| data->common.state_change_cb = callback; |
| data->common.state_change_cb_user_data = user_data; |
| } |
| |
| /* helper function allowing mcan drivers without access to private mcan |
| * definitions to set CCCR_CCE, which might be needed to disable write |
| * protection for some registers. |
| */ |
| void can_mcan_enable_configuration_change(const struct device *dev) |
| { |
| struct can_mcan_data *data = dev->data; |
| uint32_t cccr; |
| int err; |
| |
| k_mutex_lock(&data->lock, K_FOREVER); |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, &cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| cccr |= CAN_MCAN_CCCR_CCE; |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, cccr); |
| if (err != 0) { |
| goto unlock; |
| } |
| |
| unlock: |
| k_mutex_unlock(&data->lock); |
| } |
| |
| int can_mcan_configure_mram(const struct device *dev, uintptr_t mrba, uintptr_t mram) |
| { |
| const struct can_mcan_config *config = dev->config; |
| uint32_t addr; |
| uint32_t reg; |
| int err; |
| |
| err = can_mcan_exit_sleep_mode(dev); |
| if (err != 0) { |
| LOG_ERR("Failed to exit sleep mode"); |
| return -EIO; |
| } |
| |
| err = can_mcan_enter_init_mode(dev, K_MSEC(CAN_INIT_TIMEOUT_MS)); |
| if (err != 0) { |
| LOG_ERR("Failed to enter init mode"); |
| return -EIO; |
| } |
| |
| can_mcan_enable_configuration_change(dev); |
| |
| addr = mram - mrba + config->mram_offsets[CAN_MCAN_MRAM_CFG_STD_FILTER]; |
| reg = (addr & CAN_MCAN_SIDFC_FLSSA) | FIELD_PREP(CAN_MCAN_SIDFC_LSS, |
| config->mram_elements[CAN_MCAN_MRAM_CFG_STD_FILTER]); |
| err = can_mcan_write_reg(dev, CAN_MCAN_SIDFC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| addr = mram - mrba + config->mram_offsets[CAN_MCAN_MRAM_CFG_EXT_FILTER]; |
| reg = (addr & CAN_MCAN_XIDFC_FLESA) | FIELD_PREP(CAN_MCAN_XIDFC_LSS, |
| config->mram_elements[CAN_MCAN_MRAM_CFG_EXT_FILTER]); |
| err = can_mcan_write_reg(dev, CAN_MCAN_XIDFC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| addr = mram - mrba + config->mram_offsets[CAN_MCAN_MRAM_CFG_RX_FIFO0]; |
| reg = (addr & CAN_MCAN_RXF0C_F0SA) | FIELD_PREP(CAN_MCAN_RXF0C_F0S, |
| config->mram_elements[CAN_MCAN_MRAM_CFG_RX_FIFO0]); |
| err = can_mcan_write_reg(dev, CAN_MCAN_RXF0C, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| addr = mram - mrba + config->mram_offsets[CAN_MCAN_MRAM_CFG_RX_FIFO1]; |
| reg = (addr & CAN_MCAN_RXF1C_F1SA) | FIELD_PREP(CAN_MCAN_RXF1C_F1S, |
| config->mram_elements[CAN_MCAN_MRAM_CFG_RX_FIFO1]); |
| err = can_mcan_write_reg(dev, CAN_MCAN_RXF1C, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| addr = mram - mrba + config->mram_offsets[CAN_MCAN_MRAM_CFG_RX_BUFFER]; |
| reg = (addr & CAN_MCAN_RXBC_RBSA); |
| err = can_mcan_write_reg(dev, CAN_MCAN_RXBC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| addr = mram - mrba + config->mram_offsets[CAN_MCAN_MRAM_CFG_TX_EVENT_FIFO]; |
| reg = (addr & CAN_MCAN_TXEFC_EFSA) | FIELD_PREP(CAN_MCAN_TXEFC_EFS, |
| config->mram_elements[CAN_MCAN_MRAM_CFG_TX_EVENT_FIFO]); |
| err = can_mcan_write_reg(dev, CAN_MCAN_TXEFC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| addr = mram - mrba + config->mram_offsets[CAN_MCAN_MRAM_CFG_TX_BUFFER]; |
| reg = (addr & CAN_MCAN_TXBC_TBSA) | FIELD_PREP(CAN_MCAN_TXBC_TFQS, |
| config->mram_elements[CAN_MCAN_MRAM_CFG_TX_BUFFER]) | CAN_MCAN_TXBC_TFQM; |
| err = can_mcan_write_reg(dev, CAN_MCAN_TXBC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* 64 byte Tx Buffer data fields size */ |
| reg = CAN_MCAN_TXESC_TBDS; |
| err = can_mcan_write_reg(dev, CAN_MCAN_TXESC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* 64 byte Rx Buffer/FIFO1/FIFO0 data fields size */ |
| reg = CAN_MCAN_RXESC_RBDS | CAN_MCAN_RXESC_F1DS | CAN_MCAN_RXESC_F0DS; |
| err = can_mcan_write_reg(dev, CAN_MCAN_RXESC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int can_mcan_init(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; |
| struct can_timing timing = { 0 }; |
| #ifdef CONFIG_CAN_FD_MODE |
| struct can_timing timing_data = { 0 }; |
| #endif /* CONFIG_CAN_FD_MODE */ |
| uint32_t reg; |
| int err; |
| |
| __ASSERT_NO_MSG(config->ops->read_reg != NULL); |
| __ASSERT_NO_MSG(config->ops->write_reg != NULL); |
| __ASSERT_NO_MSG(config->ops->read_mram != NULL); |
| __ASSERT_NO_MSG(config->ops->write_mram != NULL); |
| __ASSERT_NO_MSG(config->ops->clear_mram != NULL); |
| __ASSERT_NO_MSG(config->callbacks != NULL); |
| |
| __ASSERT_NO_MSG(cbs->num_tx <= config->mram_elements[CAN_MCAN_MRAM_CFG_TX_BUFFER]); |
| __ASSERT_NO_MSG(cbs->num_std <= config->mram_elements[CAN_MCAN_MRAM_CFG_STD_FILTER]); |
| __ASSERT_NO_MSG(cbs->num_ext <= config->mram_elements[CAN_MCAN_MRAM_CFG_EXT_FILTER]); |
| |
| k_mutex_init(&data->lock); |
| k_mutex_init(&data->tx_mtx); |
| k_sem_init(&data->tx_sem, cbs->num_tx, cbs->num_tx); |
| |
| if (config->common.phy != NULL) { |
| if (!device_is_ready(config->common.phy)) { |
| LOG_ERR("CAN transceiver not ready"); |
| return -ENODEV; |
| } |
| } |
| |
| err = can_mcan_exit_sleep_mode(dev); |
| if (err != 0) { |
| LOG_ERR("Failed to exit sleep mode"); |
| return -EIO; |
| } |
| |
| err = can_mcan_enter_init_mode(dev, K_MSEC(CAN_INIT_TIMEOUT_MS)); |
| if (err != 0) { |
| LOG_ERR("Failed to enter init mode"); |
| return -EIO; |
| } |
| |
| can_mcan_enable_configuration_change(dev); |
| |
| #if CONFIG_CAN_LOG_LEVEL >= LOG_LEVEL_DBG |
| err = can_mcan_read_reg(dev, CAN_MCAN_CREL, ®); |
| if (err != 0) { |
| return -EIO; |
| } |
| |
| LOG_DBG("IP rel: %lu.%lu.%lu %02lu.%lu.%lu", FIELD_GET(CAN_MCAN_CREL_REL, reg), |
| FIELD_GET(CAN_MCAN_CREL_STEP, reg), FIELD_GET(CAN_MCAN_CREL_SUBSTEP, reg), |
| FIELD_GET(CAN_MCAN_CREL_YEAR, reg), FIELD_GET(CAN_MCAN_CREL_MON, reg), |
| FIELD_GET(CAN_MCAN_CREL_DAY, reg)); |
| #endif /* CONFIG_CAN_LOG_LEVEL >= LOG_LEVEL_DBG */ |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_CCCR, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| reg &= ~(CAN_MCAN_CCCR_FDOE | CAN_MCAN_CCCR_BRSE | CAN_MCAN_CCCR_TEST | CAN_MCAN_CCCR_MON | |
| CAN_MCAN_CCCR_ASM); |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_CCCR, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_TEST, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| reg &= ~(CAN_MCAN_TEST_LBCK); |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_TEST, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| #if defined(CONFIG_CAN_DELAY_COMP) && defined(CONFIG_CAN_FD_MODE) |
| err = can_mcan_read_reg(dev, CAN_MCAN_DBTP, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| reg |= CAN_MCAN_DBTP_TDC; |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_DBTP, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_TDCR, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| reg |= FIELD_PREP(CAN_MCAN_TDCR_TDCO, config->tx_delay_comp_offset); |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_TDCR, reg); |
| if (err != 0) { |
| return err; |
| } |
| #endif /* defined(CONFIG_CAN_DELAY_COMP) && defined(CONFIG_CAN_FD_MODE) */ |
| |
| err = can_mcan_read_reg(dev, CAN_MCAN_GFC, ®); |
| if (err != 0) { |
| return err; |
| } |
| |
| reg |= FIELD_PREP(CAN_MCAN_GFC_ANFE, 0x2) | FIELD_PREP(CAN_MCAN_GFC_ANFS, 0x2); |
| if (!IS_ENABLED(CONFIG_CAN_ACCEPT_RTR)) { |
| reg |= CAN_MCAN_GFC_RRFS | CAN_MCAN_GFC_RRFE; |
| } |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_GFC, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| if (config->common.sample_point) { |
| err = can_calc_timing(dev, &timing, config->common.bus_speed, |
| config->common.sample_point); |
| if (err == -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", err); |
| } else if (config->prop_ts1) { |
| timing.sjw = config->sjw; |
| timing.prop_seg = 0U; |
| timing.phase_seg1 = config->prop_ts1; |
| timing.phase_seg2 = config->ts2; |
| err = can_calc_prescaler(dev, &timing, config->common.bus_speed); |
| if (err != 0) { |
| LOG_WRN("Bitrate error: %d", err); |
| } |
| } |
| #ifdef CONFIG_CAN_FD_MODE |
| if (config->common.sample_point_data) { |
| err = can_calc_timing_data(dev, &timing_data, config->common.bus_speed_data, |
| config->common.sample_point_data); |
| if (err == -EINVAL) { |
| LOG_ERR("Can't find timing for given dataphase param"); |
| return -EIO; |
| } |
| |
| LOG_DBG("Sample-point err data phase: %d", err); |
| } else if (config->prop_ts1_data) { |
| timing_data.sjw = config->sjw_data; |
| timing_data.prop_seg = 0U; |
| timing_data.phase_seg1 = config->prop_ts1_data; |
| timing_data.phase_seg2 = config->ts2_data; |
| err = can_calc_prescaler(dev, &timing_data, config->common.bus_speed_data); |
| if (err != 0) { |
| LOG_WRN("Dataphase bitrate error: %d", err); |
| } |
| } |
| #endif /* CONFIG_CAN_FD_MODE */ |
| |
| err = can_set_timing(dev, &timing); |
| if (err != 0) { |
| LOG_ERR("failed to set timing (err %d)", err); |
| return -ENODEV; |
| } |
| |
| #ifdef CONFIG_CAN_FD_MODE |
| err = can_set_timing_data(dev, &timing_data); |
| if (err != 0) { |
| LOG_ERR("failed to set data phase timing (err %d)", err); |
| return -ENODEV; |
| } |
| #endif /* CONFIG_CAN_FD_MODE */ |
| |
| 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; |
| #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. |
| */ |
| reg |= CAN_MCAN_IE_PEAE | CAN_MCAN_IE_PEDE; |
| #endif |
| |
| err = can_mcan_write_reg(dev, CAN_MCAN_IE, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| reg = CAN_MCAN_ILS_RF0NL | CAN_MCAN_ILS_RF1NL | CAN_MCAN_ILS_RF0LL | CAN_MCAN_ILS_RF1LL; |
| err = can_mcan_write_reg(dev, CAN_MCAN_ILS, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| reg = CAN_MCAN_ILE_EINT0 | CAN_MCAN_ILE_EINT1; |
| err = can_mcan_write_reg(dev, CAN_MCAN_ILE, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Interrupt on every TX buffer transmission event */ |
| reg = CAN_MCAN_TXBTIE_TIE; |
| err = can_mcan_write_reg(dev, CAN_MCAN_TXBTIE, reg); |
| if (err != 0) { |
| return err; |
| } |
| |
| return can_mcan_clear_mram(dev, 0, config->mram_size); |
| } |