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. Change can_set_bitrate() to use 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. Regard a sample point error of more than +/- 5.0% as an error in setting the bitrate. Previously, any sample rate error was accepted without providing any feedback to the caller. This is in line with the CAN sample point calculation criteria used by the Linux kernel. Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
266 lines
6.2 KiB
C
266 lines
6.2 KiB
C
/*
|
|
* Copyright (c) 2019 Alexander Wachter
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <drivers/can.h>
|
|
#include <kernel.h>
|
|
#include <sys/util.h>
|
|
#include <logging/log.h>
|
|
|
|
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
|
|
|
|
static void can_msgq_put(const struct device *dev, struct zcan_frame *frame, void *user_data)
|
|
{
|
|
struct k_msgq *msgq = (struct k_msgq *)user_data;
|
|
int ret;
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
__ASSERT_NO_MSG(msgq);
|
|
|
|
ret = k_msgq_put(msgq, frame, K_NO_WAIT);
|
|
if (ret) {
|
|
LOG_ERR("Msgq %p overflowed. Frame ID: 0x%x", msgq, frame->id);
|
|
}
|
|
}
|
|
|
|
int z_impl_can_add_rx_filter_msgq(const struct device *dev, struct k_msgq *msgq,
|
|
const struct zcan_filter *filter)
|
|
{
|
|
const struct can_driver_api *api = dev->api;
|
|
|
|
return api->add_rx_filter(dev, can_msgq_put, msgq, filter);
|
|
}
|
|
|
|
static int update_sampling_pnt(uint32_t ts, uint32_t sp, struct can_timing *res,
|
|
const struct can_timing *max,
|
|
const struct can_timing *min)
|
|
{
|
|
uint16_t ts1_max = max->phase_seg1 + max->prop_seg;
|
|
uint16_t ts1_min = min->phase_seg1 + min->prop_seg;
|
|
uint32_t sp_calc;
|
|
uint16_t ts1, ts2;
|
|
|
|
ts2 = ts - (ts * sp) / 1000;
|
|
ts2 = CLAMP(ts2, min->phase_seg2, max->phase_seg2);
|
|
ts1 = ts - CAN_SYNC_SEG - ts2;
|
|
|
|
if (ts1 > ts1_max) {
|
|
ts1 = ts1_max;
|
|
ts2 = ts - CAN_SYNC_SEG - ts1;
|
|
if (ts2 > max->phase_seg2) {
|
|
return -1;
|
|
}
|
|
} else if (ts1 < ts1_min) {
|
|
ts1 = ts1_min;
|
|
ts2 = ts - ts1;
|
|
if (ts2 < min->phase_seg2) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
res->prop_seg = CLAMP(ts1 / 2, min->prop_seg, max->prop_seg);
|
|
res->phase_seg1 = ts1 - res->prop_seg;
|
|
res->phase_seg2 = ts2;
|
|
|
|
sp_calc = (CAN_SYNC_SEG + ts1) * 1000 / ts;
|
|
|
|
return sp_calc > sp ? sp_calc - sp : sp - sp_calc;
|
|
}
|
|
|
|
/* Internal function to do the actual calculation */
|
|
static int can_calc_timing_int(uint32_t core_clock, struct can_timing *res,
|
|
const struct can_timing *min,
|
|
const struct can_timing *max,
|
|
uint32_t bitrate, uint16_t sp)
|
|
{
|
|
uint32_t ts = max->prop_seg + max->phase_seg1 + max->phase_seg2 +
|
|
CAN_SYNC_SEG;
|
|
uint16_t sp_err_min = UINT16_MAX;
|
|
int sp_err;
|
|
struct can_timing tmp_res;
|
|
|
|
if (bitrate == 0 || sp >= 1000 ||
|
|
(!IS_ENABLED(CONFIG_CAN_FD_MODE) && bitrate > 1000000) ||
|
|
(IS_ENABLED(CONFIG_CAN_FD_MODE) && bitrate > 8000000)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (int prescaler = MAX(core_clock / (ts * bitrate), 1);
|
|
prescaler <= max->prescaler; ++prescaler) {
|
|
if (core_clock % (prescaler * bitrate)) {
|
|
/* No integer ts */
|
|
continue;
|
|
}
|
|
|
|
ts = core_clock / (prescaler * bitrate);
|
|
|
|
sp_err = update_sampling_pnt(ts, sp, &tmp_res,
|
|
max, min);
|
|
if (sp_err < 0) {
|
|
/* No prop_seg, seg1, seg2 combination possible */
|
|
continue;
|
|
}
|
|
|
|
if (sp_err < sp_err_min) {
|
|
sp_err_min = sp_err;
|
|
res->prop_seg = tmp_res.prop_seg;
|
|
res->phase_seg1 = tmp_res.phase_seg1;
|
|
res->phase_seg2 = tmp_res.phase_seg2;
|
|
res->prescaler = (uint16_t)prescaler;
|
|
if (sp_err == 0) {
|
|
/* No better result than a perfect match*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sp_err_min) {
|
|
LOG_DBG("SP error: %d 1/1000", sp_err_min);
|
|
}
|
|
|
|
return sp_err_min == UINT16_MAX ? -EINVAL : (int)sp_err_min;
|
|
}
|
|
|
|
|
|
int z_impl_can_calc_timing(const struct device *dev, struct can_timing *res,
|
|
uint32_t bitrate, uint16_t sample_pnt)
|
|
{
|
|
const struct can_timing *min = can_get_timing_min(dev);
|
|
const struct can_timing *max = can_get_timing_max(dev);
|
|
uint32_t core_clock;
|
|
int ret;
|
|
|
|
ret = can_get_core_clock(dev, &core_clock);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
return can_calc_timing_int(core_clock, res, min, max, bitrate, sample_pnt);
|
|
}
|
|
|
|
#ifdef CONFIG_CAN_FD_MODE
|
|
int z_impl_can_calc_timing_data(const struct device *dev, struct can_timing *res,
|
|
uint32_t bitrate, uint16_t sample_pnt)
|
|
{
|
|
const struct can_timing *min = can_get_timing_min_data(dev);
|
|
const struct can_timing *max = can_get_timing_max_data(dev);
|
|
uint32_t core_clock;
|
|
int ret;
|
|
|
|
ret = can_get_core_clock(dev, &core_clock);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
return can_calc_timing_int(core_clock, res, min, max, bitrate, sample_pnt);
|
|
}
|
|
#endif /* CONFIG_CAN_FD_MODE */
|
|
|
|
int can_calc_prescaler(const struct device *dev, struct can_timing *timing,
|
|
uint32_t bitrate)
|
|
{
|
|
uint32_t ts = timing->prop_seg + timing->phase_seg1 + timing->phase_seg2 +
|
|
CAN_SYNC_SEG;
|
|
uint32_t core_clock;
|
|
int ret;
|
|
|
|
ret = can_get_core_clock(dev, &core_clock);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
timing->prescaler = core_clock / (bitrate * ts);
|
|
|
|
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;
|
|
#ifdef CONFIG_CAN_FD_MODE
|
|
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);
|
|
if (ret == -ENOSYS) {
|
|
/* Maximum bitrate unknown */
|
|
max_bitrate = 0;
|
|
} else if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if ((max_bitrate > 0) && (bitrate > max_bitrate)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
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
|
|
if ((max_bitrate > 0) && (bitrate_data > max_bitrate)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
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);
|
|
#else /* CONFIG_CAN_FD_MODE */
|
|
return can_set_timing(dev, &timing, NULL);
|
|
#endif /* !CONFIG_CAN_FD_MODE */
|
|
}
|