zephyr/drivers/can/can_common.c
Henrik Brix Andersen 7e3814b262 drivers: can: expand return values reported by the CAN timing functions
Expand the error return values reported by the CAN timing calculation
functions to be able to distinguish between an an out of range
bitrate/sample point, an unsupported bitrate, and a resulting sample point
outside the guard limit.

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
2022-08-18 21:39:16 +00:00

283 lines
6.4 KiB
C

/*
* Copyright (c) 2019 Alexander Wachter
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/can.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/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 can_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 can_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) {
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 ? -ENOTSUP : (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;
if (bitrate > 1000000) {
return -EINVAL;
}
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_data_min(dev);
const struct can_timing *max = can_get_timing_data_max(dev);
uint32_t core_clock;
int ret;
if (bitrate > 8000000) {
return -EINVAL;
}
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 z_impl_can_set_bitrate(const struct device *dev, uint32_t bitrate)
{
struct can_timing timing;
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 ret;
}
if (ret > SAMPLE_POINT_MARGIN) {
return -ERANGE;
}
timing.sjw = CAN_SJW_NO_CHANGE;
return can_set_timing(dev, &timing);
}
#ifdef CONFIG_CAN_FD_MODE
int z_impl_can_set_bitrate_data(const struct device *dev, uint32_t bitrate_data)
{
struct can_timing timing_data;
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_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 ret;
}
if (ret > SAMPLE_POINT_MARGIN) {
return -ERANGE;
}
timing_data.sjw = CAN_SJW_NO_CHANGE;
return can_set_timing_data(dev, &timing_data);
}
#endif /* CONFIG_CAN_FD_MODE */