diff --git a/drivers/can/can_common.c b/drivers/can/can_common.c index 57bc6b9f52e..d106d13dabb 100644 --- a/drivers/can/can_common.c +++ b/drivers/can/can_common.c @@ -11,6 +11,9 @@ LOG_MODULE_REGISTER(can_common, CONFIG_CAN_LOG_LEVEL); +/* Maximum acceptable deviation in sample point location (permille) */ +#define SAMPLE_POINT_MARGIN 50 + /* CAN sync segment is always one time quantum */ #define CAN_SYNC_SEG 1 @@ -180,6 +183,30 @@ int can_calc_prescaler(const struct device *dev, struct can_timing *timing, return core_clock % (ts * timing->prescaler); } +/** + * @brief Get the sample point location for a given bitrate + * + * @param bitrate The bitrate in bits/second. + * @return The sample point in permille. + */ +uint16_t sample_point_for_bitrate(uint32_t bitrate) +{ + uint16_t sample_pnt; + + if (bitrate > 800000) { + /* 75.0% */ + sample_pnt = 750; + } else if (bitrate > 500000) { + /* 80.0% */ + sample_pnt = 800; + } else { + /* 87.5% */ + sample_pnt = 875; + } + + return sample_pnt; +} + int can_set_bitrate(const struct device *dev, uint32_t bitrate, uint32_t bitrate_data) { struct can_timing timing; @@ -187,6 +214,7 @@ int can_set_bitrate(const struct device *dev, uint32_t bitrate, uint32_t bitrate struct can_timing timing_data; #endif /* CONFIG_CAN_FD_MODE */ uint32_t max_bitrate; + uint16_t sample_pnt; int ret; ret = can_get_max_bitrate(dev, &max_bitrate); @@ -201,11 +229,16 @@ int can_set_bitrate(const struct device *dev, uint32_t bitrate, uint32_t bitrate return -ENOTSUP; } - ret = can_calc_timing(dev, &timing, bitrate, 875); + sample_pnt = sample_point_for_bitrate(bitrate); + ret = can_calc_timing(dev, &timing, bitrate, sample_pnt); if (ret < 0) { return -EINVAL; } + if (ret > SAMPLE_POINT_MARGIN) { + return -EINVAL; + } + timing.sjw = CAN_SJW_NO_CHANGE; #ifdef CONFIG_CAN_FD_MODE @@ -213,11 +246,16 @@ int can_set_bitrate(const struct device *dev, uint32_t bitrate, uint32_t bitrate return -ENOTSUP; } - ret = can_calc_timing_data(dev, &timing_data, bitrate_data, 875); + sample_pnt = sample_point_for_bitrate(bitrate_data); + ret = can_calc_timing_data(dev, &timing_data, bitrate_data, sample_pnt); if (ret < 0) { return -EINVAL; } + if (ret > SAMPLE_POINT_MARGIN) { + return -EINVAL; + } + timing_data.sjw = CAN_SJW_NO_CHANGE; return can_set_timing(dev, &timing, &timing_data); diff --git a/include/zephyr/drivers/can.h b/include/zephyr/drivers/can.h index d116ee75939..f9ebb841435 100644 --- a/include/zephyr/drivers/can.h +++ b/include/zephyr/drivers/can.h @@ -845,7 +845,14 @@ static inline int z_impl_can_set_mode(const struct device *dev, enum can_mode mo /** * @brief Set the bitrate of the CAN controller * - * The sample point is set to the CiA DS 301 recommended value of 87.5%. + * CAN in Automation (CiA) 301 v4.2.0 recommends a sample point location of + * 87.5% percent for all bitrates. However, some CAN controllers have + * difficulties meeting this for higher bitrates. + * + * This function defaults to using a sample point of 75.0% for bitrates over 800 + * kbit/s, 80.0% for bitrates over 500 kbit/s, and 87.5% for all other + * bitrates. This is in line with the sample point locations used by the Linux + * kernel. * * @note The parameter ``bitrate_data`` is only relevant for CAN-FD. If the * controller does not support CAN-FD or if @kconfig{CONFIG_CAN_FD_MODE} is not @@ -857,7 +864,7 @@ static inline int z_impl_can_set_mode(const struct device *dev, enum can_mode mo * * @retval 0 If successful. * @retval -ENOTSUP bitrate not supported by CAN controller/transceiver combination - * @retval -EINVAL bitrate cannot be met. + * @retval -EINVAL bitrate/sample point cannot be met. * @retval -EIO General input/output error, failed to set bitrate. */ int can_set_bitrate(const struct device *dev, uint32_t bitrate, uint32_t bitrate_data);