| /* |
| * Copyright (c) 2019 Vestas Wind Systems A/S |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nxp_kinetis_flexcan |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/atomic.h> |
| #include <zephyr/drivers/can.h> |
| #include <zephyr/drivers/can/transceiver.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/device.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <fsl_flexcan.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| |
| #ifdef CONFIG_PINCTRL |
| #include <zephyr/drivers/pinctrl.h> |
| #endif |
| |
| LOG_MODULE_REGISTER(can_mcux_flexcan, CONFIG_CAN_LOG_LEVEL); |
| |
| #define SP_IS_SET(inst) DT_INST_NODE_HAS_PROP(inst, sample_point) || |
| |
| /* Macro to exclude the sample point algorithm from compilation if not used |
| * Without the macro, the algorithm would always waste ROM |
| */ |
| #define USE_SP_ALGO (DT_INST_FOREACH_STATUS_OKAY(SP_IS_SET) 0) |
| |
| #define SP_AND_TIMING_NOT_SET(inst) \ |
| (!DT_INST_NODE_HAS_PROP(inst, sample_point) && \ |
| !(DT_INST_NODE_HAS_PROP(inst, prop_seg) && \ |
| DT_INST_NODE_HAS_PROP(inst, phase_seg1) && \ |
| DT_INST_NODE_HAS_PROP(inst, phase_seg2))) || |
| |
| #if DT_INST_FOREACH_STATUS_OKAY(SP_AND_TIMING_NOT_SET) 0 |
| #error You must either set a sampling-point or timings (phase-seg* and prop-seg) |
| #endif |
| |
| #if ((defined(FSL_FEATURE_FLEXCAN_HAS_ERRATA_5641) && FSL_FEATURE_FLEXCAN_HAS_ERRATA_5641) || \ |
| (defined(FSL_FEATURE_FLEXCAN_HAS_ERRATA_5829) && FSL_FEATURE_FLEXCAN_HAS_ERRATA_5829)) |
| /* the first valid MB should be occupied by ERRATA 5461 or 5829. */ |
| #define RX_START_IDX 1 |
| #else |
| #define RX_START_IDX 0 |
| #endif |
| |
| /* |
| * RX message buffers (filters) will take up the first N message |
| * buffers. The rest are available for TX use. |
| */ |
| #define MCUX_FLEXCAN_MAX_RX (CONFIG_CAN_MAX_FILTER + RX_START_IDX) |
| #define MCUX_FLEXCAN_MAX_TX \ |
| (FSL_FEATURE_FLEXCAN_HAS_MESSAGE_BUFFER_MAX_NUMBERn(0) \ |
| - MCUX_FLEXCAN_MAX_RX) |
| |
| /* |
| * Convert from RX message buffer index to allocated filter ID and |
| * vice versa. |
| */ |
| #define RX_MBIDX_TO_ALLOC_IDX(x) (x) |
| #define ALLOC_IDX_TO_RXMB_IDX(x) (x) |
| |
| /* |
| * Convert from TX message buffer index to allocated TX ID and vice |
| * versa. |
| */ |
| #define TX_MBIDX_TO_ALLOC_IDX(x) (x - MCUX_FLEXCAN_MAX_RX) |
| #define ALLOC_IDX_TO_TXMB_IDX(x) (x + MCUX_FLEXCAN_MAX_RX) |
| |
| /* Convert from back from FLEXCAN IDs to Zephyr CAN IDs. */ |
| #define FLEXCAN_ID_TO_CAN_ID_STD(id) \ |
| ((uint32_t)((((uint32_t)(id)) & CAN_ID_STD_MASK) >> CAN_ID_STD_SHIFT)) |
| #define FLEXCAN_ID_TO_CAN_ID_EXT(id) \ |
| ((uint32_t)((((uint32_t)(id)) & (CAN_ID_STD_MASK | CAN_ID_EXT_MASK)) \ |
| >> CAN_ID_EXT_SHIFT)) |
| |
| struct mcux_flexcan_config { |
| CAN_Type *base; |
| const struct device *clock_dev; |
| clock_control_subsys_t clock_subsys; |
| int clk_source; |
| uint32_t bitrate; |
| uint32_t sample_point; |
| uint32_t sjw; |
| uint32_t prop_seg; |
| uint32_t phase_seg1; |
| uint32_t phase_seg2; |
| void (*irq_config_func)(const struct device *dev); |
| const struct device *phy; |
| uint32_t max_bitrate; |
| #ifdef CONFIG_PINCTRL |
| const struct pinctrl_dev_config *pincfg; |
| #endif |
| }; |
| |
| struct mcux_flexcan_rx_callback { |
| flexcan_rx_mb_config_t mb_config; |
| flexcan_frame_t frame; |
| can_rx_callback_t function; |
| void *arg; |
| }; |
| |
| struct mcux_flexcan_tx_callback { |
| flexcan_frame_t frame; |
| can_tx_callback_t function; |
| void *arg; |
| }; |
| |
| struct mcux_flexcan_data { |
| const struct device *dev; |
| flexcan_handle_t handle; |
| |
| ATOMIC_DEFINE(rx_allocs, MCUX_FLEXCAN_MAX_RX); |
| struct k_mutex rx_mutex; |
| struct mcux_flexcan_rx_callback rx_cbs[MCUX_FLEXCAN_MAX_RX]; |
| |
| ATOMIC_DEFINE(tx_allocs, MCUX_FLEXCAN_MAX_TX); |
| struct k_sem tx_allocs_sem; |
| struct mcux_flexcan_tx_callback tx_cbs[MCUX_FLEXCAN_MAX_TX]; |
| enum can_state state; |
| can_state_change_callback_t state_change_cb; |
| void *state_change_cb_data; |
| struct can_timing timing; |
| bool started; |
| }; |
| |
| static int mcux_flexcan_get_core_clock(const struct device *dev, uint32_t *rate) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| |
| return clock_control_get_rate(config->clock_dev, config->clock_subsys, rate); |
| } |
| |
| static int mcux_flexcan_get_max_filters(const struct device *dev, bool ide) |
| { |
| ARG_UNUSED(ide); |
| |
| return CONFIG_CAN_MAX_FILTER; |
| } |
| |
| static int mcux_flexcan_get_max_bitrate(const struct device *dev, uint32_t *max_bitrate) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| |
| *max_bitrate = config->max_bitrate; |
| |
| return 0; |
| } |
| |
| static int mcux_flexcan_set_timing(const struct device *dev, |
| const struct can_timing *timing) |
| { |
| struct mcux_flexcan_data *data = dev->data; |
| uint8_t sjw_backup = data->timing.sjw; |
| |
| if (!timing) { |
| return -EINVAL; |
| } |
| |
| if (data->started) { |
| return -EBUSY; |
| } |
| |
| data->timing = *timing; |
| if (timing->sjw == CAN_SJW_NO_CHANGE) { |
| data->timing.sjw = sjw_backup; |
| } |
| |
| return 0; |
| } |
| |
| static int mcux_flexcan_get_capabilities(const struct device *dev, can_mode_t *cap) |
| { |
| ARG_UNUSED(dev); |
| |
| *cap = CAN_MODE_NORMAL | CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY | CAN_MODE_3_SAMPLES; |
| |
| return 0; |
| } |
| |
| static int mcux_flexcan_start(const struct device *dev) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| flexcan_timing_config_t timing; |
| int err; |
| |
| if (data->started) { |
| return -EALREADY; |
| } |
| |
| if (config->phy != NULL) { |
| err = can_transceiver_enable(config->phy); |
| if (err != 0) { |
| LOG_ERR("failed to enable CAN transceiver (err %d)", err); |
| return err; |
| } |
| } |
| |
| /* Clear error counters */ |
| config->base->ECR &= ~(CAN_ECR_TXERRCNT_MASK | CAN_ECR_RXERRCNT_MASK); |
| |
| /* Delay this until start since setting the timing automatically exits freeze mode */ |
| timing.preDivider = data->timing.prescaler - 1U; |
| timing.rJumpwidth = data->timing.sjw - 1U; |
| timing.phaseSeg1 = data->timing.phase_seg1 - 1U; |
| timing.phaseSeg2 = data->timing.phase_seg2 - 1U; |
| timing.propSeg = data->timing.prop_seg - 1U; |
| FLEXCAN_SetTimingConfig(config->base, &timing); |
| |
| data->started = true; |
| |
| return 0; |
| } |
| |
| static int mcux_flexcan_stop(const struct device *dev) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| can_tx_callback_t function; |
| void *arg; |
| int alloc; |
| int err; |
| |
| if (!data->started) { |
| return -EALREADY; |
| } |
| |
| data->started = false; |
| |
| /* Abort any pending TX frames before entering freeze mode */ |
| for (alloc = 0; alloc < MCUX_FLEXCAN_MAX_TX; alloc++) { |
| function = data->tx_cbs[alloc].function; |
| arg = data->tx_cbs[alloc].arg; |
| |
| if (atomic_test_and_clear_bit(data->tx_allocs, alloc)) { |
| FLEXCAN_TransferAbortSend(config->base, &data->handle, |
| ALLOC_IDX_TO_TXMB_IDX(alloc)); |
| |
| function(dev, -ENETDOWN, arg); |
| k_sem_give(&data->tx_allocs_sem); |
| } |
| } |
| |
| FLEXCAN_EnterFreezeMode(config->base); |
| |
| if (config->phy != NULL) { |
| err = can_transceiver_disable(config->phy); |
| if (err != 0) { |
| LOG_ERR("failed to disable CAN transceiver (err %d)", err); |
| return err; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int mcux_flexcan_set_mode(const struct device *dev, can_mode_t mode) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| uint32_t ctrl1; |
| uint32_t mcr; |
| |
| if (data->started) { |
| return -EBUSY; |
| } |
| |
| if ((mode & ~(CAN_MODE_LOOPBACK | CAN_MODE_LISTENONLY | CAN_MODE_3_SAMPLES)) != 0) { |
| LOG_ERR("unsupported mode: 0x%08x", mode); |
| return -ENOTSUP; |
| } |
| |
| ctrl1 = config->base->CTRL1; |
| mcr = config->base->MCR; |
| |
| if ((mode & CAN_MODE_LOOPBACK) != 0) { |
| /* Enable loopback and self-reception */ |
| ctrl1 |= CAN_CTRL1_LPB_MASK; |
| mcr &= ~(CAN_MCR_SRXDIS_MASK); |
| } else { |
| /* Disable loopback and self-reception */ |
| ctrl1 &= ~(CAN_CTRL1_LPB_MASK); |
| mcr |= CAN_MCR_SRXDIS_MASK; |
| } |
| |
| if ((mode & CAN_MODE_LISTENONLY) != 0) { |
| /* Enable listen-only mode */ |
| ctrl1 |= CAN_CTRL1_LOM_MASK; |
| } else { |
| /* Disable listen-only mode */ |
| ctrl1 &= ~(CAN_CTRL1_LOM_MASK); |
| } |
| |
| if ((mode & CAN_MODE_3_SAMPLES) != 0) { |
| /* Enable triple sampling mode */ |
| ctrl1 |= CAN_CTRL1_SMP_MASK; |
| } else { |
| /* Disable triple sampling mode */ |
| ctrl1 &= ~(CAN_CTRL1_SMP_MASK); |
| } |
| |
| config->base->CTRL1 = ctrl1; |
| config->base->MCR = mcr; |
| |
| return 0; |
| } |
| |
| static void mcux_flexcan_from_can_frame(const struct can_frame *src, |
| flexcan_frame_t *dest) |
| { |
| memset(dest, 0, sizeof(*dest)); |
| |
| if ((src->flags & CAN_FRAME_IDE) != 0) { |
| dest->format = kFLEXCAN_FrameFormatExtend; |
| dest->id = FLEXCAN_ID_EXT(src->id); |
| } else { |
| dest->format = kFLEXCAN_FrameFormatStandard; |
| dest->id = FLEXCAN_ID_STD(src->id); |
| } |
| |
| if ((src->flags & CAN_FRAME_RTR) != 0) { |
| dest->type = kFLEXCAN_FrameTypeRemote; |
| } else { |
| dest->type = kFLEXCAN_FrameTypeData; |
| } |
| |
| dest->length = src->dlc; |
| dest->dataWord0 = sys_cpu_to_be32(src->data_32[0]); |
| dest->dataWord1 = sys_cpu_to_be32(src->data_32[1]); |
| } |
| |
| static void mcux_flexcan_to_can_frame(const flexcan_frame_t *src, |
| struct can_frame *dest) |
| { |
| memset(dest, 0, sizeof(*dest)); |
| |
| if (src->format == kFLEXCAN_FrameFormatStandard) { |
| dest->id = FLEXCAN_ID_TO_CAN_ID_STD(src->id); |
| } else { |
| dest->flags |= CAN_FRAME_IDE; |
| dest->id = FLEXCAN_ID_TO_CAN_ID_EXT(src->id); |
| } |
| |
| if (src->type == kFLEXCAN_FrameTypeRemote) { |
| dest->flags |= CAN_FRAME_RTR; |
| } |
| |
| dest->dlc = src->length; |
| dest->data_32[0] = sys_be32_to_cpu(src->dataWord0); |
| dest->data_32[1] = sys_be32_to_cpu(src->dataWord1); |
| #ifdef CONFIG_CAN_RX_TIMESTAMP |
| dest->timestamp = src->timestamp; |
| #endif /* CAN_RX_TIMESTAMP */ |
| } |
| |
| static void mcux_flexcan_can_filter_to_mbconfig(const struct can_filter *src, |
| flexcan_rx_mb_config_t *dest, |
| uint32_t *mask) |
| { |
| static const uint32_t ide_mask = 1U; |
| uint32_t rtr_mask = (src->flags & (CAN_FILTER_DATA | CAN_FILTER_RTR)) != |
| (CAN_FILTER_DATA | CAN_FILTER_RTR) ? 1U : 0U; |
| |
| if ((src->flags & CAN_FILTER_IDE) != 0) { |
| dest->format = kFLEXCAN_FrameFormatExtend; |
| dest->id = FLEXCAN_ID_EXT(src->id); |
| *mask = FLEXCAN_RX_MB_EXT_MASK(src->mask, rtr_mask, ide_mask); |
| } else { |
| dest->format = kFLEXCAN_FrameFormatStandard; |
| dest->id = FLEXCAN_ID_STD(src->id); |
| *mask = FLEXCAN_RX_MB_STD_MASK(src->mask, rtr_mask, ide_mask); |
| } |
| |
| if ((src->flags & CAN_FILTER_RTR) != 0) { |
| dest->type = kFLEXCAN_FrameTypeRemote; |
| } else { |
| dest->type = kFLEXCAN_FrameTypeData; |
| } |
| } |
| |
| static int mcux_flexcan_get_state(const struct device *dev, enum can_state *state, |
| struct can_bus_err_cnt *err_cnt) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| uint64_t status_flags; |
| |
| if (state != NULL) { |
| if (!data->started) { |
| *state = CAN_STATE_STOPPED; |
| } else { |
| status_flags = FLEXCAN_GetStatusFlags(config->base); |
| |
| if ((status_flags & CAN_ESR1_FLTCONF(2)) != 0U) { |
| *state = CAN_STATE_BUS_OFF; |
| } else if ((status_flags & CAN_ESR1_FLTCONF(1)) != 0U) { |
| *state = CAN_STATE_ERROR_PASSIVE; |
| } else if ((status_flags & |
| (kFLEXCAN_TxErrorWarningFlag | kFLEXCAN_RxErrorWarningFlag)) != 0) { |
| *state = CAN_STATE_ERROR_WARNING; |
| } else { |
| *state = CAN_STATE_ERROR_ACTIVE; |
| } |
| } |
| } |
| |
| if (err_cnt != NULL) { |
| FLEXCAN_GetBusErrCount(config->base, &err_cnt->tx_err_cnt, |
| &err_cnt->rx_err_cnt); |
| } |
| |
| return 0; |
| } |
| |
| static int mcux_flexcan_send(const struct device *dev, |
| const struct can_frame *frame, |
| k_timeout_t timeout, |
| can_tx_callback_t callback, void *user_data) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| flexcan_mb_transfer_t xfer; |
| enum can_state state; |
| status_t status; |
| int alloc; |
| |
| __ASSERT_NO_MSG(callback != NULL); |
| |
| if (frame->dlc > CAN_MAX_DLC) { |
| LOG_ERR("DLC of %d exceeds maximum (%d)", frame->dlc, CAN_MAX_DLC); |
| return -EINVAL; |
| } |
| |
| if ((frame->flags & ~(CAN_FRAME_IDE | CAN_FRAME_RTR)) != 0) { |
| LOG_ERR("unsupported CAN frame flags 0x%02x", frame->flags); |
| return -ENOTSUP; |
| } |
| |
| if (!data->started) { |
| return -ENETDOWN; |
| } |
| |
| (void)mcux_flexcan_get_state(dev, &state, NULL); |
| if (state == CAN_STATE_BUS_OFF) { |
| LOG_DBG("Transmit failed, bus-off"); |
| return -ENETUNREACH; |
| } |
| |
| if (k_sem_take(&data->tx_allocs_sem, timeout) != 0) { |
| return -EAGAIN; |
| } |
| |
| for (alloc = 0; alloc < MCUX_FLEXCAN_MAX_TX; alloc++) { |
| if (!atomic_test_and_set_bit(data->tx_allocs, alloc)) { |
| break; |
| } |
| } |
| |
| mcux_flexcan_from_can_frame(frame, &data->tx_cbs[alloc].frame); |
| data->tx_cbs[alloc].function = callback; |
| data->tx_cbs[alloc].arg = user_data; |
| xfer.frame = &data->tx_cbs[alloc].frame; |
| xfer.mbIdx = ALLOC_IDX_TO_TXMB_IDX(alloc); |
| FLEXCAN_SetTxMbConfig(config->base, xfer.mbIdx, true); |
| status = FLEXCAN_TransferSendNonBlocking(config->base, &data->handle, |
| &xfer); |
| if (status != kStatus_Success) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int mcux_flexcan_add_rx_filter(const struct device *dev, |
| can_rx_callback_t callback, |
| void *user_data, |
| const struct can_filter *filter) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| flexcan_mb_transfer_t xfer; |
| status_t status; |
| uint32_t mask; |
| int alloc = -ENOSPC; |
| int i; |
| |
| __ASSERT_NO_MSG(callback); |
| |
| if ((filter->flags & ~(CAN_FILTER_IDE | CAN_FILTER_DATA | CAN_FILTER_RTR)) != 0) { |
| LOG_ERR("unsupported CAN filter flags 0x%02x", filter->flags); |
| return -ENOTSUP; |
| } |
| |
| k_mutex_lock(&data->rx_mutex, K_FOREVER); |
| |
| /* Find and allocate RX message buffer */ |
| for (i = RX_START_IDX; i < MCUX_FLEXCAN_MAX_RX; i++) { |
| if (!atomic_test_and_set_bit(data->rx_allocs, i)) { |
| alloc = i; |
| break; |
| } |
| } |
| |
| if (alloc == -ENOSPC) { |
| return alloc; |
| } |
| |
| mcux_flexcan_can_filter_to_mbconfig(filter, &data->rx_cbs[alloc].mb_config, |
| &mask); |
| |
| data->rx_cbs[alloc].arg = user_data; |
| data->rx_cbs[alloc].function = callback; |
| |
| /* The indidual RX mask registers can only be written in freeze mode */ |
| FLEXCAN_EnterFreezeMode(config->base); |
| config->base->RXIMR[ALLOC_IDX_TO_RXMB_IDX(alloc)] = mask; |
| if (data->started) { |
| FLEXCAN_ExitFreezeMode(config->base); |
| } |
| |
| FLEXCAN_SetRxMbConfig(config->base, ALLOC_IDX_TO_RXMB_IDX(alloc), |
| &data->rx_cbs[alloc].mb_config, true); |
| |
| xfer.frame = &data->rx_cbs[alloc].frame; |
| xfer.mbIdx = ALLOC_IDX_TO_RXMB_IDX(alloc); |
| status = FLEXCAN_TransferReceiveNonBlocking(config->base, &data->handle, |
| &xfer); |
| if (status != kStatus_Success) { |
| LOG_ERR("Failed to start rx for filter id %d (err = %d)", |
| alloc, status); |
| alloc = -ENOSPC; |
| } |
| |
| k_mutex_unlock(&data->rx_mutex); |
| |
| return alloc; |
| } |
| |
| static void mcux_flexcan_set_state_change_callback(const struct device *dev, |
| can_state_change_callback_t callback, |
| void *user_data) |
| { |
| struct mcux_flexcan_data *data = dev->data; |
| |
| data->state_change_cb = callback; |
| data->state_change_cb_data = user_data; |
| } |
| |
| #ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY |
| static int mcux_flexcan_recover(const struct device *dev, k_timeout_t timeout) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| enum can_state state; |
| uint64_t start_time; |
| int ret = 0; |
| |
| if (!data->started) { |
| return -ENETDOWN; |
| } |
| |
| (void)mcux_flexcan_get_state(dev, &state, NULL); |
| if (state != CAN_STATE_BUS_OFF) { |
| return 0; |
| } |
| |
| start_time = k_uptime_ticks(); |
| config->base->CTRL1 &= ~CAN_CTRL1_BOFFREC_MASK; |
| |
| if (!K_TIMEOUT_EQ(timeout, K_NO_WAIT)) { |
| (void)mcux_flexcan_get_state(dev, &state, NULL); |
| |
| while (state == CAN_STATE_BUS_OFF) { |
| if (!K_TIMEOUT_EQ(timeout, K_FOREVER) && |
| k_uptime_ticks() - start_time >= timeout.ticks) { |
| ret = -EAGAIN; |
| } |
| |
| (void)mcux_flexcan_get_state(dev, &state, NULL); |
| } |
| } |
| |
| config->base->CTRL1 |= CAN_CTRL1_BOFFREC_MASK; |
| |
| return ret; |
| } |
| #endif /* CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */ |
| |
| static void mcux_flexcan_remove_rx_filter(const struct device *dev, int filter_id) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| |
| if (filter_id >= MCUX_FLEXCAN_MAX_RX) { |
| LOG_ERR("Detach: Filter id >= MAX_RX (%d >= %d)", filter_id, |
| MCUX_FLEXCAN_MAX_RX); |
| return; |
| } |
| |
| k_mutex_lock(&data->rx_mutex, K_FOREVER); |
| |
| if (atomic_test_and_clear_bit(data->rx_allocs, filter_id)) { |
| FLEXCAN_TransferAbortReceive(config->base, &data->handle, |
| ALLOC_IDX_TO_RXMB_IDX(filter_id)); |
| FLEXCAN_SetRxMbConfig(config->base, |
| ALLOC_IDX_TO_RXMB_IDX(filter_id), NULL, |
| false); |
| data->rx_cbs[filter_id].function = NULL; |
| data->rx_cbs[filter_id].arg = NULL; |
| } else { |
| LOG_WRN("Filter ID %d already detached", filter_id); |
| } |
| |
| k_mutex_unlock(&data->rx_mutex); |
| } |
| |
| static inline void mcux_flexcan_transfer_error_status(const struct device *dev, |
| uint64_t error) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| const can_state_change_callback_t cb = data->state_change_cb; |
| void *cb_data = data->state_change_cb_data; |
| can_tx_callback_t function; |
| void *arg; |
| int alloc; |
| enum can_state state; |
| struct can_bus_err_cnt err_cnt; |
| |
| if ((error & kFLEXCAN_Bit0Error) != 0U) { |
| CAN_STATS_BIT0_ERROR_INC(dev); |
| } |
| |
| if ((error & kFLEXCAN_Bit1Error) != 0U) { |
| CAN_STATS_BIT1_ERROR_INC(dev); |
| } |
| |
| if ((error & kFLEXCAN_AckError) != 0U) { |
| CAN_STATS_ACK_ERROR_INC(dev); |
| } |
| |
| if ((error & kFLEXCAN_StuffingError) != 0U) { |
| CAN_STATS_STUFF_ERROR_INC(dev); |
| } |
| |
| if ((error & kFLEXCAN_FormError) != 0U) { |
| CAN_STATS_FORM_ERROR_INC(dev); |
| } |
| |
| if ((error & kFLEXCAN_CrcError) != 0U) { |
| CAN_STATS_CRC_ERROR_INC(dev); |
| } |
| |
| (void)mcux_flexcan_get_state(dev, &state, &err_cnt); |
| if (data->state != state) { |
| data->state = state; |
| |
| if (cb != NULL) { |
| cb(dev, state, err_cnt, cb_data); |
| } |
| } |
| |
| if (state == CAN_STATE_BUS_OFF) { |
| /* Abort any pending TX frames in case of bus-off */ |
| for (alloc = 0; alloc < MCUX_FLEXCAN_MAX_TX; alloc++) { |
| /* Copy callback function and argument before clearing bit */ |
| function = data->tx_cbs[alloc].function; |
| arg = data->tx_cbs[alloc].arg; |
| |
| if (atomic_test_and_clear_bit(data->tx_allocs, alloc)) { |
| FLEXCAN_TransferAbortSend(config->base, &data->handle, |
| ALLOC_IDX_TO_TXMB_IDX(alloc)); |
| |
| function(dev, -ENETUNREACH, arg); |
| k_sem_give(&data->tx_allocs_sem); |
| } |
| } |
| } |
| } |
| |
| static inline void mcux_flexcan_transfer_tx_idle(const struct device *dev, |
| uint32_t mb) |
| { |
| struct mcux_flexcan_data *data = dev->data; |
| can_tx_callback_t function; |
| void *arg; |
| int alloc; |
| |
| alloc = TX_MBIDX_TO_ALLOC_IDX(mb); |
| |
| /* Copy callback function and argument before clearing bit */ |
| function = data->tx_cbs[alloc].function; |
| arg = data->tx_cbs[alloc].arg; |
| |
| if (atomic_test_and_clear_bit(data->tx_allocs, alloc)) { |
| function(dev, 0, arg); |
| k_sem_give(&data->tx_allocs_sem); |
| } |
| } |
| |
| static inline void mcux_flexcan_transfer_rx_idle(const struct device *dev, |
| uint32_t mb) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| can_rx_callback_t function; |
| flexcan_mb_transfer_t xfer; |
| struct can_frame frame; |
| status_t status; |
| void *arg; |
| int alloc; |
| |
| alloc = RX_MBIDX_TO_ALLOC_IDX(mb); |
| function = data->rx_cbs[alloc].function; |
| arg = data->rx_cbs[alloc].arg; |
| |
| if (atomic_test_bit(data->rx_allocs, alloc)) { |
| mcux_flexcan_to_can_frame(&data->rx_cbs[alloc].frame, &frame); |
| function(dev, &frame, arg); |
| |
| /* Setup RX message buffer to receive next message */ |
| FLEXCAN_SetRxMbConfig(config->base, mb, |
| &data->rx_cbs[alloc].mb_config, true); |
| xfer.frame = &data->rx_cbs[alloc].frame; |
| xfer.mbIdx = mb; |
| status = FLEXCAN_TransferReceiveNonBlocking(config->base, |
| &data->handle, |
| &xfer); |
| if (status != kStatus_Success) { |
| LOG_ERR("Failed to restart rx for filter id %d " |
| "(err = %d)", alloc, status); |
| } |
| } |
| } |
| |
| static FLEXCAN_CALLBACK(mcux_flexcan_transfer_callback) |
| { |
| struct mcux_flexcan_data *data = (struct mcux_flexcan_data *)userData; |
| const struct mcux_flexcan_config *config = data->dev->config; |
| /* |
| * The result field can either be a MB index (which is limited to 32 bit |
| * value) or a status flags value, which is 32 bit on some platforms but |
| * 64 on others. To decouple the remaining functions from this, the |
| * result field is always promoted to uint64_t. |
| */ |
| uint32_t mb = (uint32_t)result; |
| uint64_t status_flags = result; |
| |
| ARG_UNUSED(base); |
| |
| switch (status) { |
| case kStatus_FLEXCAN_UnHandled: |
| /* Not all fault confinement state changes are handled by the HAL */ |
| __fallthrough; |
| case kStatus_FLEXCAN_ErrorStatus: |
| mcux_flexcan_transfer_error_status(data->dev, status_flags); |
| break; |
| case kStatus_FLEXCAN_TxSwitchToRx: |
| FLEXCAN_TransferAbortReceive(config->base, &data->handle, mb); |
| __fallthrough; |
| case kStatus_FLEXCAN_TxIdle: |
| mcux_flexcan_transfer_tx_idle(data->dev, mb); |
| break; |
| case kStatus_FLEXCAN_RxOverflow: |
| __fallthrough; |
| case kStatus_FLEXCAN_RxIdle: |
| mcux_flexcan_transfer_rx_idle(data->dev, mb); |
| break; |
| default: |
| LOG_WRN("Unhandled status 0x%08x (result = 0x%016llx)", |
| status, status_flags); |
| } |
| } |
| |
| static void mcux_flexcan_isr(const struct device *dev) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| |
| FLEXCAN_TransferHandleIRQ(config->base, &data->handle); |
| } |
| |
| static int mcux_flexcan_init(const struct device *dev) |
| { |
| const struct mcux_flexcan_config *config = dev->config; |
| struct mcux_flexcan_data *data = dev->data; |
| flexcan_config_t flexcan_config; |
| uint32_t clock_freq; |
| int err; |
| |
| if (config->phy != NULL) { |
| if (!device_is_ready(config->phy)) { |
| LOG_ERR("CAN transceiver not ready"); |
| return -ENODEV; |
| } |
| } |
| |
| if (!device_is_ready(config->clock_dev)) { |
| LOG_ERR("clock device not ready"); |
| return -ENODEV; |
| } |
| |
| k_mutex_init(&data->rx_mutex); |
| k_sem_init(&data->tx_allocs_sem, MCUX_FLEXCAN_MAX_TX, |
| MCUX_FLEXCAN_MAX_TX); |
| |
| data->timing.sjw = config->sjw; |
| if (config->sample_point && USE_SP_ALGO) { |
| err = can_calc_timing(dev, &data->timing, config->bitrate, |
| config->sample_point); |
| if (err == -EINVAL) { |
| LOG_ERR("Can't find timing for given param"); |
| return -EIO; |
| } |
| LOG_DBG("Presc: %d, Seg1S1: %d, Seg2: %d", |
| data->timing.prescaler, data->timing.phase_seg1, |
| data->timing.phase_seg2); |
| LOG_DBG("Sample-point err : %d", err); |
| } else { |
| data->timing.prop_seg = config->prop_seg; |
| data->timing.phase_seg1 = config->phase_seg1; |
| data->timing.phase_seg2 = config->phase_seg2; |
| err = can_calc_prescaler(dev, &data->timing, config->bitrate); |
| if (err) { |
| LOG_WRN("Bitrate error: %d", err); |
| } |
| } |
| |
| #ifdef CONFIG_PINCTRL |
| err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); |
| if (err != 0) { |
| return err; |
| } |
| #endif |
| |
| err = mcux_flexcan_get_core_clock(dev, &clock_freq); |
| if (err != 0) { |
| return -EIO; |
| } |
| |
| data->dev = dev; |
| |
| FLEXCAN_GetDefaultConfig(&flexcan_config); |
| flexcan_config.maxMbNum = FSL_FEATURE_FLEXCAN_HAS_MESSAGE_BUFFER_MAX_NUMBERn(0); |
| flexcan_config.clkSrc = config->clk_source; |
| flexcan_config.baudRate = clock_freq / |
| (1U + data->timing.prop_seg + data->timing.phase_seg1 + |
| data->timing.phase_seg2) / data->timing.prescaler; |
| flexcan_config.enableIndividMask = true; |
| flexcan_config.enableLoopBack = false; |
| flexcan_config.disableSelfReception = true; |
| flexcan_config.enableListenOnlyMode = true; |
| |
| flexcan_config.timingConfig.rJumpwidth = data->timing.sjw - 1U; |
| flexcan_config.timingConfig.propSeg = data->timing.prop_seg - 1U; |
| flexcan_config.timingConfig.phaseSeg1 = data->timing.phase_seg1 - 1U; |
| flexcan_config.timingConfig.phaseSeg2 = data->timing.phase_seg2 - 1U; |
| |
| /* Initialize in listen-only mode since FLEXCAN_Init() exits freeze mode */ |
| FLEXCAN_Init(config->base, &flexcan_config, clock_freq); |
| FLEXCAN_TransferCreateHandle(config->base, &data->handle, |
| mcux_flexcan_transfer_callback, data); |
| |
| /* Manually enter freeze mode, set normal mode, and clear error counters */ |
| FLEXCAN_EnterFreezeMode(config->base); |
| (void)mcux_flexcan_set_mode(dev, CAN_MODE_NORMAL); |
| config->base->ECR &= ~(CAN_ECR_TXERRCNT_MASK | CAN_ECR_RXERRCNT_MASK); |
| |
| config->irq_config_func(dev); |
| |
| #ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY |
| config->base->CTRL1 |= CAN_CTRL1_BOFFREC_MASK; |
| #endif /* CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */ |
| |
| (void)mcux_flexcan_get_state(dev, &data->state, NULL); |
| |
| return 0; |
| } |
| |
| static const struct can_driver_api mcux_flexcan_driver_api = { |
| .get_capabilities = mcux_flexcan_get_capabilities, |
| .start = mcux_flexcan_start, |
| .stop = mcux_flexcan_stop, |
| .set_mode = mcux_flexcan_set_mode, |
| .set_timing = mcux_flexcan_set_timing, |
| .send = mcux_flexcan_send, |
| .add_rx_filter = mcux_flexcan_add_rx_filter, |
| .remove_rx_filter = mcux_flexcan_remove_rx_filter, |
| .get_state = mcux_flexcan_get_state, |
| #ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY |
| .recover = mcux_flexcan_recover, |
| #endif |
| .set_state_change_callback = mcux_flexcan_set_state_change_callback, |
| .get_core_clock = mcux_flexcan_get_core_clock, |
| .get_max_filters = mcux_flexcan_get_max_filters, |
| .get_max_bitrate = mcux_flexcan_get_max_bitrate, |
| /* |
| * FlexCAN timing limits are specified in the "FLEXCANx_CTRL1 field |
| * descriptions" table in the SoC reference manual. |
| * |
| * Note that the values here are the "physical" timing limits, whereas |
| * the register field limits are physical values minus 1 (which is |
| * handled by the flexcan_config_t field assignments elsewhere in this |
| * driver). |
| */ |
| .timing_min = { |
| .sjw = 0x01, |
| .prop_seg = 0x01, |
| .phase_seg1 = 0x01, |
| .phase_seg2 = 0x02, |
| .prescaler = 0x01 |
| }, |
| .timing_max = { |
| .sjw = 0x04, |
| .prop_seg = 0x08, |
| .phase_seg1 = 0x08, |
| .phase_seg2 = 0x08, |
| .prescaler = 0x100 |
| } |
| }; |
| |
| #define FLEXCAN_IRQ_CODE(id, name) \ |
| do { \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_NAME(id, name, irq), \ |
| DT_INST_IRQ_BY_NAME(id, name, priority), \ |
| mcux_flexcan_isr, \ |
| DEVICE_DT_INST_GET(id), 0); \ |
| irq_enable(DT_INST_IRQ_BY_NAME(id, name, irq)); \ |
| } while (false) |
| |
| #define FLEXCAN_IRQ(id, name) \ |
| COND_CODE_1(DT_INST_IRQ_HAS_NAME(id, name), \ |
| (FLEXCAN_IRQ_CODE(id, name)), ()) |
| |
| #ifdef CONFIG_PINCTRL |
| #define FLEXCAN_PINCTRL_DEFINE(id) PINCTRL_DT_INST_DEFINE(id); |
| #define FLEXCAN_PINCTRL_INIT(id) .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(id), |
| #else |
| #define FLEXCAN_PINCTRL_DEFINE(id) |
| #define FLEXCAN_PINCTRL_INIT(id) |
| #endif /* CONFIG_PINCTRL */ |
| |
| |
| #define FLEXCAN_DEVICE_INIT_MCUX(id) \ |
| FLEXCAN_PINCTRL_DEFINE(id) \ |
| \ |
| static void mcux_flexcan_irq_config_##id(const struct device *dev); \ |
| \ |
| static const struct mcux_flexcan_config mcux_flexcan_config_##id = { \ |
| .base = (CAN_Type *)DT_INST_REG_ADDR(id), \ |
| .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(id)), \ |
| .clock_subsys = (clock_control_subsys_t) \ |
| DT_INST_CLOCKS_CELL(id, name), \ |
| .clk_source = DT_INST_PROP(id, clk_source), \ |
| .bitrate = DT_INST_PROP(id, bus_speed), \ |
| .sjw = DT_INST_PROP(id, sjw), \ |
| .prop_seg = DT_INST_PROP_OR(id, prop_seg, 0), \ |
| .phase_seg1 = DT_INST_PROP_OR(id, phase_seg1, 0), \ |
| .phase_seg2 = DT_INST_PROP_OR(id, phase_seg2, 0), \ |
| .sample_point = DT_INST_PROP_OR(id, sample_point, 0), \ |
| .irq_config_func = mcux_flexcan_irq_config_##id, \ |
| .phy = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(id, phys)),\ |
| .max_bitrate = DT_INST_CAN_TRANSCEIVER_MAX_BITRATE(id, 1000000), \ |
| FLEXCAN_PINCTRL_INIT(id) \ |
| }; \ |
| \ |
| static struct mcux_flexcan_data mcux_flexcan_data_##id; \ |
| \ |
| CAN_DEVICE_DT_INST_DEFINE(id, mcux_flexcan_init, \ |
| NULL, &mcux_flexcan_data_##id, \ |
| &mcux_flexcan_config_##id, \ |
| POST_KERNEL, CONFIG_CAN_INIT_PRIORITY,\ |
| &mcux_flexcan_driver_api); \ |
| \ |
| static void mcux_flexcan_irq_config_##id(const struct device *dev) \ |
| { \ |
| FLEXCAN_IRQ(id, rx_warning); \ |
| FLEXCAN_IRQ(id, tx_warning); \ |
| FLEXCAN_IRQ(id, bus_off); \ |
| FLEXCAN_IRQ(id, warning); \ |
| FLEXCAN_IRQ(id, error); \ |
| FLEXCAN_IRQ(id, wake_up); \ |
| FLEXCAN_IRQ(id, mb_0_15); \ |
| FLEXCAN_IRQ(id, common); \ |
| } |
| |
| DT_INST_FOREACH_STATUS_OKAY(FLEXCAN_DEVICE_INIT_MCUX) |