1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/can/device.c

589 lines
19 KiB
C

/*
* Copyright (C) 2016 OTA keys S.A.
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup sys_can_dll
* @{
* @file
* @brief CAN device interface
*
* @author Toon Stegen <toon.stegen@altran.com>
* @author Vincent Dupont <vincent@otakeys.com>
* @author Aurelien Gonce <aurelien.gonce@altran.com>
* @}
*/
#include <assert.h>
#include <errno.h>
#include "thread.h"
#include "ztimer.h"
#include "can/device.h"
#include "can/common.h"
#include "can/pkt.h"
#include "can/dll.h"
#ifdef MODULE_CAN_TRX
#include "can/can_trx.h"
#endif
#define ENABLE_DEBUG 0
#include "debug.h"
#ifndef CAN_DEVICE_MSG_QUEUE_SIZE
#define CAN_DEVICE_MSG_QUEUE_SIZE 64
#endif
#ifdef MODULE_CAN_PM
#define CAN_DEVICE_PM_DEFAULT_RX_TIMEOUT (10 * US_PER_SEC)
#define CAN_DEVICE_PM_DEFAULT_TX_TIMEOUT (2 * US_PER_SEC)
static void pm_cb(void *arg);
static void pm_reset(candev_dev_t *candev_dev, uint32_t value);
#endif
static int power_up(candev_dev_t *candev_dev);
static int power_down(candev_dev_t *candev_dev);
static void _can_event(candev_t *dev, candev_event_t event, void *arg)
{
msg_t msg;
struct can_frame *frame;
can_pkt_t *pkt;
candev_dev_t *candev_dev = dev->isr_arg;
DEBUG("_can_event: dev=%p, params=%p\n", (void*)dev, (void*)candev_dev);
DEBUG("_can_event: params->ifnum=%d, params->pid=%" PRIkernel_pid ", params->dev=%p\n",
candev_dev->ifnum, candev_dev->pid, (void*)candev_dev->dev);
switch (event) {
case CANDEV_EVENT_ISR:
DEBUG("_can_event: CANDEV_EVENT_ISR\n");
msg.type = CAN_MSG_EVENT;
if (msg_send(&msg, candev_dev->pid) <= 0) {
DEBUG("can device: isr lost\n");
}
break;
case CANDEV_EVENT_WAKE_UP:
DEBUG("_can_event: CANDEV_EVENT_WAKE_UP\n");
power_up(candev_dev);
#ifdef MODULE_CAN_PM
pm_reset(candev_dev, candev_dev->rx_inactivity_timeout);
#endif
break;
case CANDEV_EVENT_TX_CONFIRMATION:
DEBUG("_can_event: CANDEV_EVENT_TX_CONFIRMATION\n");
/* frame pointer in arg */
pkt = container_of((struct can_frame *)arg, can_pkt_t, frame);
can_dll_dispatch_tx_conf(pkt);
break;
case CANDEV_EVENT_TX_ERROR:
DEBUG("_can_event: CANDEV_EVENT_TX_ERROR\n");
/* frame pointer in arg */
pkt = container_of((struct can_frame *)arg, can_pkt_t, frame);
can_dll_dispatch_tx_error(pkt);
break;
case CANDEV_EVENT_RX_INDICATION:
DEBUG("_can_event: CANDEV_EVENT_RX_INDICATION\n");
#ifdef MODULE_CAN_PM
pm_reset(candev_dev, candev_dev->rx_inactivity_timeout);
#endif
/* received frame in arg */
frame = (struct can_frame *) arg;
can_dll_dispatch_rx_frame(frame, candev_dev->pid);
break;
case CANDEV_EVENT_RX_ERROR:
DEBUG("_can_event: CANDEV_EVENT_RX_ERROR\n");
break;
case CANDEV_EVENT_BUS_OFF:
dev->state = CAN_STATE_BUS_OFF;
break;
case CANDEV_EVENT_ERROR_PASSIVE:
dev->state = CAN_STATE_ERROR_PASSIVE;
break;
case CANDEV_EVENT_ERROR_WARNING:
dev->state = CAN_STATE_ERROR_WARNING;
break;
default:
DEBUG("_can_event: unknown event\n");
break;
}
}
static int power_up(candev_dev_t *candev_dev)
{
candev_t *dev = candev_dev->dev;
DEBUG("candev: power up\n");
#ifdef MODULE_CAN_TRX
can_trx_set_mode(candev_dev->trx, TRX_NORMAL_MODE);
#endif
canopt_state_t state = CANOPT_STATE_ON;
int res = dev->driver->set(dev, CANOPT_STATE, &state, sizeof(state));
dev->state = CAN_STATE_ERROR_ACTIVE;
return res;
}
static int power_down(candev_dev_t *candev_dev)
{
candev_t *dev = candev_dev->dev;
DEBUG("candev: power down\n");
#ifdef MODULE_CAN_TRX
can_trx_set_mode(candev_dev->trx, TRX_SLEEP_MODE);
#endif
canopt_state_t state = CANOPT_STATE_SLEEP;
int res = dev->driver->set(dev, CANOPT_STATE, &state, sizeof(state));
if (dev->state != CAN_STATE_BUS_OFF) {
dev->state = CAN_STATE_SLEEPING;
}
#ifdef MODULE_CAN_PM
ztimer_remove(ZTIMER_USEC, &candev_dev->pm_timer);
candev_dev->last_pm_update = 0;
#endif
return res;
}
#ifdef MODULE_CAN_PM
static void pm_cb(void *arg)
{
candev_dev_t *candev_dev = (candev_dev_t *)arg;
msg_t msg;
msg.type = CAN_MSG_PM;
msg_send(&msg, candev_dev->pid);
}
static void pm_reset(candev_dev_t *candev_dev, uint32_t value)
{
DEBUG("pm_reset: dev=%p, value=%" PRIu32 ", last_pm_value=%" PRIu32
", last_pm_update=%" PRIu32 "\n", (void *)candev_dev, value,
candev_dev->last_pm_value, candev_dev->last_pm_update);
if (value == 0) {
candev_dev->last_pm_value = 0;
ztimer_remove(ZTIMER_USEC, &candev_dev->pm_timer);
return;
}
if (candev_dev->last_pm_update == 0 ||
value > (candev_dev->last_pm_value - (ztimer_now(ZTIMER_USEC) - candev_dev->last_pm_update))) {
candev_dev->last_pm_value = value;
candev_dev->last_pm_update = ztimer_now(ZTIMER_USEC);
ztimer_set(ZTIMER_USEC, &candev_dev->pm_timer, value);
}
}
#endif
static void wake_up(candev_dev_t *candev_dev)
{
candev_t *dev = candev_dev->dev;
if (dev->state == CAN_STATE_BUS_OFF || dev->state == CAN_STATE_SLEEPING) {
DEBUG("can device: waking up driver\n");
power_up(candev_dev);
}
#ifdef MODULE_CAN_PM
pm_reset(candev_dev, candev_dev->tx_wakeup_timeout);
#endif
}
static void *_can_device_thread(void *args)
{
candev_dev_t *candev_dev = (candev_dev_t *) args;
candev_t *dev = candev_dev->dev;
DEBUG("_can_device_thread: starting thread for ifnum=%d, pid=%" PRIkernel_pid "\n",
candev_dev->ifnum, thread_getpid());
DEBUG("_cand_device_thread: dev=%p, params=%p\n", (void*)dev, (void*)candev_dev);
candev_dev->pid = thread_getpid();
#ifdef MODULE_CAN_TRX
can_trx_init(candev_dev->trx);
#endif
#ifdef MODULE_CAN_PM
if (candev_dev->rx_inactivity_timeout == 0) {
candev_dev->rx_inactivity_timeout = CAN_DEVICE_PM_DEFAULT_RX_TIMEOUT;
}
if (candev_dev->tx_wakeup_timeout == 0) {
candev_dev->tx_wakeup_timeout = CAN_DEVICE_PM_DEFAULT_TX_TIMEOUT;
}
candev_dev->pm_timer.callback = pm_cb;
candev_dev->pm_timer.arg = candev_dev;
pm_reset(candev_dev, candev_dev->rx_inactivity_timeout);
#endif
int res;
can_pkt_t *pkt;
can_opt_t *opt;
msg_t msg, reply, msg_queue[CAN_DEVICE_MSG_QUEUE_SIZE];
/* setup the device layers message queue */
msg_init_queue(msg_queue, CAN_DEVICE_MSG_QUEUE_SIZE);
dev->event_callback = _can_event;
dev->isr_arg = candev_dev;
candev_dev->ifnum = can_dll_register_candev(candev_dev);
dev->driver->init(dev);
power_up(candev_dev);
while (1) {
msg_receive(&msg);
switch (msg.type) {
case CAN_MSG_EVENT:
DEBUG("can device: CAN_MSG_EVENT received\n");
dev->driver->isr(dev);
break;
case CAN_MSG_ABORT_FRAME:
DEBUG("can device: CAN_MSG_ABORT_FRAME received\n");
pkt = (can_pkt_t *) msg.content.ptr;
dev->driver->abort(dev, &pkt->frame);
reply.type = CAN_MSG_ACK;
reply.content.value = 0;
msg_reply(&msg, &reply);
break;
case CAN_MSG_SEND_FRAME:
DEBUG("can device: CAN_MSG_SEND_FRAME received\n");
wake_up(candev_dev);
/* read incoming pkt */
pkt = (can_pkt_t *) msg.content.ptr;
dev->driver->send(dev, &pkt->frame);
break;
case CAN_MSG_SET:
DEBUG("can device: CAN_MSG_SET received\n");
/* read incoming options */
opt = (can_opt_t *)msg.content.ptr;
/* set option for device driver */
res = dev->driver->set(dev, opt->opt, opt->data, opt->data_len);
/* send reply to calling thread */
reply.type = CAN_MSG_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
case CAN_MSG_GET:
DEBUG("can device: CAN_MSG_GET received\n");
/* read incoming options */
opt = (can_opt_t *)msg.content.ptr;
/* get option for device driver */
res = dev->driver->get(dev, opt->opt, opt->data, opt->data_len);
/* send reply to calling thread */
reply.type = CAN_MSG_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
case CAN_MSG_SET_FILTER:
DEBUG("can device: CAN_MSG_SET_FILTER received\n");
wake_up(candev_dev);
/* set filter for device driver */
res = dev->driver->set_filter(dev, msg.content.ptr);
/* send reply to calling thread */
reply.type = CAN_MSG_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
case CAN_MSG_REMOVE_FILTER:
DEBUG("can device: CAN_MSG_REMOVE_FILTER received\n");
wake_up(candev_dev);
/* set filter for device driver */
res = dev->driver->remove_filter(dev, msg.content.ptr);
/* send reply to calling thread */
reply.type = CAN_MSG_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
case CAN_MSG_POWER_UP:
DEBUG("can device: CAN_MSG_POWER_UP received\n");
res = power_up(candev_dev);
#ifdef MODULE_CAN_PM
pm_reset(candev_dev, candev_dev->tx_wakeup_timeout);
#endif
/* send reply to calling thread */
reply.type = CAN_MSG_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
case CAN_MSG_POWER_DOWN:
DEBUG("can device: CAN_MSG_POWER_DOWN received\n");
res = power_down(candev_dev);
#ifdef MODULE_CAN_PM
pm_reset(candev_dev, 0);
#endif
/* send reply to calling thread */
reply.type = CAN_MSG_ACK;
reply.content.value = (uint32_t)res;
msg_reply(&msg, &reply);
break;
#ifdef MODULE_CAN_TRX
case CAN_MSG_SET_TRX:
DEBUG("can device: CAN_MSG_SET_TRX received\n");
reply.type = CAN_MSG_ACK;
if (dev->state != CAN_STATE_SLEEPING) {
reply.content.value = -EBUSY;
}
else {
candev_dev->trx = msg.content.ptr;
reply.content.value = 0;
}
msg_reply(&msg, &reply);
break;
#endif
#ifdef MODULE_CAN_PM
case CAN_MSG_PM:
DEBUG("can device: pm power down\n");
power_down(candev_dev);
break;
#endif
default:
break;
}
}
return NULL;
}
kernel_pid_t can_device_init(char *stack, int stacksize, char priority,
const char *name, candev_dev_t *params)
{
kernel_pid_t res;
/* check if given device is defined and the driver is set */
if (params == NULL || params->dev == NULL || params->dev->driver == NULL) {
return -ENODEV;
}
/* create new can device thread */
res = thread_create(stack, stacksize, priority, 0,
_can_device_thread, (void *)params, name);
if (res <= 0) {
return -EINVAL;
}
return res;
}
#define SJW 2
#define CAN_SYNC_SEG 1
static inline uint32_t min(uint32_t x, uint32_t y)
{
return x < y ? x : y;
}
static inline uint32_t max(uint32_t x, uint32_t y)
{
return x > y ? x : y;
}
static inline uint32_t clamp(uint32_t val, uint32_t lo, uint32_t hi)
{
return min(max(val, lo), hi);
}
/**
* @brief Compute tseg1 and tseg2 and returns the sample point
*
* tseg1 and tseg2 are calculated from the nominal sample point and tseg
*
* @param[in] btc the bittiming const
* @param[in] spt_nominal the nominal sample point
* @param[in] tseg number of tq in the nbt minus the SYNC_SEG
* @param[out] p_tseg1 number of tq in tseg1 (PHASE_SEG_1 + PROP_SEG)
* @param[out] p_tseg2 number of tq in tseg2 (PHASE_SEG_2)
* @param[out] p_spt_error (optional) the sample point difference between @p spt_nominal
* and computed sample point from @p tseg1 and @p tseg2
*
* @return the computed sample point from @p tseg1 and @p tseg2
*/
static uint32_t update_sample_point(const struct can_bittiming_const *btc, uint32_t spt_nominal,
uint32_t tseg, uint32_t *p_tseg1, uint32_t *p_tseg2, uint32_t *p_spt_error)
{
uint32_t best_spt = 0;
uint32_t min_spt_error = UINT32_MAX;
for (int i = 0; i <= 1; i++) {
uint32_t tseg1;
uint32_t tseg2;
uint32_t spt;
uint32_t spt_error;
tseg2 = tseg + CAN_SYNC_SEG - (spt_nominal * (tseg + CAN_SYNC_SEG)) / 1000 - i;
tseg2 = clamp(tseg2, btc->tseg2_min, btc->tseg2_max);
tseg1 = tseg - tseg2;
if (tseg1 > btc->tseg1_max) {
tseg1 = btc->tseg1_max;
tseg2 = tseg - tseg1;
}
spt = 1000 * (tseg1 + CAN_SYNC_SEG) / (tseg + CAN_SYNC_SEG);
spt_error = max(spt, spt_nominal) - min(spt, spt_nominal);
if (spt <= spt_nominal && spt_error < min_spt_error) {
best_spt = spt;
min_spt_error = spt_error;
*p_tseg1 = tseg1;
*p_tseg2 = tseg2;
}
if (p_spt_error) {
*p_spt_error = min_spt_error;
}
DEBUG("tseg1=%" PRIu32 ", tseg2=%" PRIu32 ", spt_error=%" PRIu32 "\n",
tseg1, tseg2, spt_error);
}
return best_spt;
}
/*
* Nominal bit time (nbt) composed of 8 time quantum (tq)
* |<------------------------------------------------------------------------------------->|
* | |
* +----------+----------+-------------------------------------------+---------------------+
* | SYNC_SEG | PROP_SEG | PHASE_SEG_1 | PHASE_SEG_2 |
* +----------+----------+-------------------------------------------+---------------------+
* | ^ |
* | Sample point | at 75% |
* |----------|----------|----------|----------|----------|----------|----------|----------|
* | Time quanta 6 | 2 |
*
* Synchronization segment = always 1 tq
* SYNC_SEG + PROP_SEG + PHASE_SEG1
* Sample point = --------------------------------
* nbt
*
* tseg1 = PROP_SEG + PHASE_SEG_1
* tseg2 = PHASE_SEG_2
* tseg = tseg1 + tseg2
* nbt = tseg + SYNC_SEG
*
*/
int can_device_calc_bittiming(uint32_t clock, const struct can_bittiming_const *timing_const,
struct can_bittiming *timing)
{
uint32_t spt; /* nominal sample point, in one-tenth of a percent */
uint32_t spt_error;
uint32_t min_spt_error = UINT32_MAX;
uint32_t best_brp = 0;
uint32_t tseg = 0;
uint32_t tseg1 = 0;
uint32_t tseg2 = 0;
uint32_t best_tseg = 0;
uint32_t rate_error;
uint32_t min_rate_error;
assert((timing != NULL) && (timing->bitrate != 0));
assert(timing_const != NULL);
if (timing->sample_point) {
spt = timing->sample_point;
}
else {
/* Use recommended sample points */
/* See CiA 301 (https://www.can-cia.org/standardization/technical-documents/) */
/* 87.5% is recommended from 10kbit/s to 1Mbit/s */
spt = 875;
}
rate_error = min_rate_error = timing->bitrate;
DEBUG("init_bittiming: rate=%" PRIu32 ", clock=%" PRIu32 ", spt=%" PRIu32 "\n",
timing->bitrate, clock, timing->sample_point);
/* Starting from higher tq per nbt */
for (tseg = timing_const->tseg1_max + timing_const->tseg2_max;
tseg >= timing_const->tseg1_min + timing_const->tseg2_min; tseg--) {
uint32_t nbt = tseg + CAN_SYNC_SEG;
/* theoretical brp */
uint32_t brp = clock / (timing->bitrate * nbt);
/* brp according to brp_inc */
brp = (brp / timing_const->brp_inc) * timing_const->brp_inc;
DEBUG("tsegall=%" PRIu32 ", brp=%" PRIu32 "\n", nbt, brp);
if (brp < timing_const->brp_min || brp > timing_const->brp_max) {
/* Invalid brp */
DEBUG("invalid brp\n");
continue;
}
/* current bitrate */
uint32_t rate = clock / (brp * nbt);
rate_error = max(timing->bitrate, rate) - min(timing->bitrate, rate);
if (rate_error > min_rate_error) {
DEBUG("timing->rate=%" PRIu32 ", rate=%" PRIu32 ", rate_error=%" PRIu32 " > min_rate_error=%" PRIu32 ", continuing\n",
timing->bitrate, rate, rate_error, min_rate_error);
continue;
}
if (rate_error < min_rate_error) {
min_spt_error = UINT32_MAX;
}
update_sample_point(timing_const, spt, tseg, &tseg1, &tseg2, &spt_error);
if (spt_error > min_spt_error) {
DEBUG("spt_error=%" PRIu32 " > min_spt_error=%" PRIu32 ", continuing\n",
spt_error, min_spt_error);
continue;
}
min_spt_error = spt_error;
min_rate_error = rate_error;
best_tseg = tseg;
best_brp = brp;
DEBUG("rate_error=%" PRIu32 ", spt_error=%" PRIu32 "\n", rate_error, spt_error);
if (rate_error == 0 && spt_error == 0) {
break;
}
}
DEBUG("computed values: min_rate_error=%" PRIu32 ", min_spt_error=%" PRIu32 "\n", min_rate_error, min_spt_error);
if (min_rate_error) {
rate_error = min_rate_error * 1000 / timing->bitrate;
if (rate_error > CAN_MAX_RATE_ERROR) {
return -1;
}
}
timing->sample_point = update_sample_point(timing_const, spt,
best_tseg, &tseg1, &tseg2, NULL);
timing->prop_seg = tseg1 / 2;
timing->phase_seg1 = tseg1 - timing->prop_seg;
timing->phase_seg2 = tseg2;
if (!timing->sjw || !timing_const->sjw_max) {
timing->sjw = SJW;
}
else {
if (timing->sjw > timing_const->sjw_max) {
timing->sjw = timing_const->sjw_max;
}
if (timing->sjw > tseg2) {
timing->sjw = tseg2;
}
}
timing->brp = best_brp;
timing->bitrate = clock / (timing->brp * (CAN_SYNC_SEG + tseg1 + tseg2));
DEBUG("bitrate=%" PRIu32 ", sample_point=%" PRIu32 ", brp=%" PRIu32 ", prop_seg=%" PRIu32
", phase_seg1=%" PRIu32 ", phase_seg2=%" PRIu32 "\n", timing->bitrate, timing->sample_point,
timing->brp, timing->prop_seg, timing->phase_seg1, timing->phase_seg2);
return 0;
}