288 lines
6.7 KiB
C
288 lines
6.7 KiB
C
/*
|
|
* Copyright (c) 2019 Alexander Wachter
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <drivers/can.h>
|
|
#include <kernel.h>
|
|
#include <sys/util.h>
|
|
|
|
#define LOG_LEVEL CONFIG_CAN_LOG_LEVEL
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(can_driver);
|
|
|
|
#define CAN_SYNC_SEG 1
|
|
|
|
#define WORK_BUF_COUNT_IS_POWER_OF_2 !(CONFIG_CAN_WORKQ_FRAMES_BUF_CNT & \
|
|
(CONFIG_CAN_WORKQ_FRAMES_BUF_CNT - 1))
|
|
|
|
#define WORK_BUF_MOD_MASK (CONFIG_CAN_WORKQ_FRAMES_BUF_CNT - 1)
|
|
|
|
#if WORK_BUF_COUNT_IS_POWER_OF_2
|
|
#define WORK_BUF_MOD_SIZE(x) ((x) & WORK_BUF_MOD_MASK)
|
|
#else
|
|
#define WORK_BUF_MOD_SIZE(x) ((x) % CONFIG_CAN_WORKQ_FRAMES_BUF_CNT)
|
|
#endif
|
|
|
|
#define WORK_BUF_FULL 0xFFFF
|
|
|
|
static void can_msgq_put(struct zcan_frame *frame, void *arg)
|
|
{
|
|
struct k_msgq *msgq = (struct k_msgq *)arg;
|
|
int ret;
|
|
|
|
__ASSERT_NO_MSG(msgq);
|
|
|
|
ret = k_msgq_put(msgq, frame, K_NO_WAIT);
|
|
if (ret) {
|
|
LOG_ERR("Msgq %p overflowed. Frame ID: 0x%x", arg, frame->id);
|
|
}
|
|
}
|
|
|
|
int z_impl_can_attach_msgq(const struct device *dev, struct k_msgq *msg_q,
|
|
const struct zcan_filter *filter)
|
|
{
|
|
const struct can_driver_api *api = dev->api;
|
|
|
|
return api->attach_isr(dev, can_msgq_put, msg_q, filter);
|
|
}
|
|
|
|
static inline void can_work_buffer_init(struct can_frame_buffer *buffer)
|
|
{
|
|
buffer->head = 0;
|
|
buffer->tail = 0;
|
|
}
|
|
|
|
static inline int can_work_buffer_put(struct zcan_frame *frame,
|
|
struct can_frame_buffer *buffer)
|
|
{
|
|
uint16_t next_head = WORK_BUF_MOD_SIZE(buffer->head + 1);
|
|
|
|
if (buffer->head == WORK_BUF_FULL) {
|
|
return -1;
|
|
}
|
|
|
|
buffer->buf[buffer->head] = *frame;
|
|
|
|
/* Buffer is almost full */
|
|
if (next_head == buffer->tail) {
|
|
buffer->head = WORK_BUF_FULL;
|
|
} else {
|
|
buffer->head = next_head;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline
|
|
struct zcan_frame *can_work_buffer_get_next(struct can_frame_buffer *buffer)
|
|
{
|
|
/* Buffer empty */
|
|
if (buffer->head == buffer->tail) {
|
|
return NULL;
|
|
} else {
|
|
return &buffer->buf[buffer->tail];
|
|
}
|
|
}
|
|
|
|
static inline void can_work_buffer_free_next(struct can_frame_buffer *buffer)
|
|
{
|
|
uint16_t next_tail = WORK_BUF_MOD_SIZE(buffer->tail + 1);
|
|
|
|
if (buffer->head == buffer->tail) {
|
|
return;
|
|
}
|
|
|
|
if (buffer->head == WORK_BUF_FULL) {
|
|
buffer->head = buffer->tail;
|
|
}
|
|
|
|
buffer->tail = next_tail;
|
|
}
|
|
|
|
static void can_work_handler(struct k_work *work)
|
|
{
|
|
struct zcan_work *can_work = CONTAINER_OF(work, struct zcan_work,
|
|
work_item);
|
|
struct zcan_frame *frame;
|
|
|
|
while ((frame = can_work_buffer_get_next(&can_work->buf))) {
|
|
can_work->cb(frame, can_work->cb_arg);
|
|
can_work_buffer_free_next(&can_work->buf);
|
|
}
|
|
}
|
|
|
|
static void can_work_isr_put(struct zcan_frame *frame, void *arg)
|
|
{
|
|
struct zcan_work *work = (struct zcan_work *)arg;
|
|
int ret;
|
|
|
|
ret = can_work_buffer_put(frame, &work->buf);
|
|
if (ret) {
|
|
LOG_ERR("Workq buffer overflow. Msg ID: 0x%x", frame->id);
|
|
return;
|
|
}
|
|
|
|
k_work_submit_to_queue(work->work_queue, &work->work_item);
|
|
}
|
|
|
|
int can_attach_workq(const struct device *dev, struct k_work_q *work_q,
|
|
struct zcan_work *work,
|
|
can_rx_callback_t callback, void *callback_arg,
|
|
const struct zcan_filter *filter)
|
|
{
|
|
const struct can_driver_api *api = dev->api;
|
|
|
|
k_work_init(&work->work_item, can_work_handler);
|
|
work->work_queue = work_q;
|
|
work->cb = callback;
|
|
work->cb_arg = callback_arg;
|
|
can_work_buffer_init(&work->buf);
|
|
|
|
return api->attach_isr(dev, can_work_isr_put, work, 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 (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 can_calc_timing(const struct device *dev, struct can_timing *res,
|
|
uint32_t bitrate, uint16_t sample_pnt)
|
|
{
|
|
const struct can_driver_api *api = dev->api;
|
|
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, &api->timing_min,
|
|
&api->timing_max, bitrate, sample_pnt);
|
|
}
|
|
|
|
#ifdef CONFIG_CAN_FD_MODE
|
|
int can_calc_timing_data(const struct device *dev, struct can_timing *res,
|
|
uint32_t bitrate, uint16_t sample_pnt)
|
|
{
|
|
const struct can_driver_api *api = dev->api;
|
|
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, &api->timing_min_data,
|
|
&api->timing_max_data, bitrate, sample_pnt);
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
}
|