// SPDX-License-Identifier: GPL-2.0-only
|
/*
|
* CANFD firmware interface.
|
*
|
* Copyright (C) 2001-2021 PEAK System-Technik GmbH
|
* Copyright (C) 2019-2021 Stephane Grosjean <s.grosjean@peak-system.com>
|
*/
|
#include "rtcan_dev.h"
|
#include "rtcan_raw.h"
|
#include "rtcan_peak_canfd_user.h"
|
|
#define DRV_NAME "xeno_peak_canfd"
|
|
#define RTCAN_DEV_NAME "rtcan%d"
|
#define RTCAN_CTRLR_NAME "peak_canfd"
|
|
/* bittiming ranges of the PEAK-System PC CAN-FD interfaces */
|
static const struct can_bittiming_const peak_canfd_nominal_const = {
|
.name = RTCAN_CTRLR_NAME,
|
.tseg1_min = 1,
|
.tseg1_max = (1 << PUCAN_TSLOW_TSGEG1_BITS),
|
.tseg2_min = 1,
|
.tseg2_max = (1 << PUCAN_TSLOW_TSGEG2_BITS),
|
.sjw_max = (1 << PUCAN_TSLOW_SJW_BITS),
|
.brp_min = 1,
|
.brp_max = (1 << PUCAN_TSLOW_BRP_BITS),
|
.brp_inc = 1,
|
};
|
|
/* initialize the command area */
|
static struct peak_canfd_priv *pucan_init_cmd(struct peak_canfd_priv *priv)
|
{
|
priv->cmd_len = 0;
|
return priv;
|
}
|
|
/* add command 'cmd_op' to the command area */
|
static void *pucan_add_cmd(struct peak_canfd_priv *priv, int cmd_op)
|
{
|
struct pucan_command *cmd;
|
|
if (priv->cmd_len + sizeof(*cmd) > priv->cmd_maxlen)
|
return NULL;
|
|
cmd = priv->cmd_buffer + priv->cmd_len;
|
|
/* reset all unused bit to default */
|
memset(cmd, 0, sizeof(*cmd));
|
|
cmd->opcode_channel = pucan_cmd_opcode_channel(priv->index, cmd_op);
|
priv->cmd_len += sizeof(*cmd);
|
|
return cmd;
|
}
|
|
/* send the command(s) to the IP core through the host-device interface */
|
static int pucan_write_cmd(struct peak_canfd_priv *priv)
|
{
|
int err;
|
|
/* prepare environment before writing the command */
|
if (priv->pre_cmd) {
|
err = priv->pre_cmd(priv);
|
if (err)
|
return err;
|
}
|
|
err = priv->write_cmd(priv);
|
if (err)
|
return err;
|
|
/* update environment after writing the command */
|
if (priv->post_cmd)
|
err = priv->post_cmd(priv);
|
|
return err;
|
}
|
|
/* set the device in RESET mode */
|
static int pucan_set_reset_mode(struct peak_canfd_priv *priv)
|
{
|
int err;
|
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_RESET_MODE);
|
err = pucan_write_cmd(priv);
|
if (!err)
|
priv->rdev->state = CAN_STATE_STOPPED;
|
|
return err;
|
}
|
|
/* set the device in NORMAL mode */
|
static int pucan_set_normal_mode(struct peak_canfd_priv *priv)
|
{
|
int err;
|
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_NORMAL_MODE);
|
err = pucan_write_cmd(priv);
|
if (!err)
|
priv->rdev->state = CAN_STATE_ERROR_ACTIVE;
|
|
return err;
|
}
|
|
/* set the device in LISTEN_ONLY mode */
|
static int pucan_set_listen_only_mode(struct peak_canfd_priv *priv)
|
{
|
int err;
|
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_LISTEN_ONLY_MODE);
|
err = pucan_write_cmd(priv);
|
if (!err)
|
priv->rdev->state = CAN_STATE_ERROR_ACTIVE;
|
|
return err;
|
}
|
|
/* set acceptance filters */
|
static int pucan_set_std_filter(struct peak_canfd_priv *priv, u8 row, u32 mask)
|
{
|
struct pucan_std_filter *cmd;
|
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_SET_STD_FILTER);
|
|
/* All the 11-bit CAN ID values are represented by one bit in a
|
* 64 rows array of 32 columns: the upper 6 bit of the CAN ID select
|
* the row while the lowest 5 bit select the column in that row.
|
*
|
* bit filter
|
* 1 passed
|
* 0 discarded
|
*/
|
|
/* select the row */
|
cmd->idx = row;
|
|
/* set/unset bits in the row */
|
cmd->mask = cpu_to_le32(mask);
|
|
return pucan_write_cmd(priv);
|
}
|
|
/* request the device to stop transmission */
|
static int pucan_tx_abort(struct peak_canfd_priv *priv, u16 flags)
|
{
|
struct pucan_tx_abort *cmd;
|
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_TX_ABORT);
|
|
cmd->flags = cpu_to_le16(flags);
|
|
return pucan_write_cmd(priv);
|
}
|
|
/* request the device to clear rx/tx error counters */
|
static int pucan_clr_err_counters(struct peak_canfd_priv *priv)
|
{
|
struct pucan_wr_err_cnt *cmd;
|
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_WR_ERR_CNT);
|
|
cmd->sel_mask = cpu_to_le16(PUCAN_WRERRCNT_TE | PUCAN_WRERRCNT_RE);
|
|
/* write the counters new value */
|
cmd->tx_counter = 0;
|
cmd->rx_counter = 0;
|
|
return pucan_write_cmd(priv);
|
}
|
|
/* set options to the device */
|
static int pucan_set_options(struct peak_canfd_priv *priv, u16 opt_mask)
|
{
|
struct pucan_options *cmd;
|
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_SET_EN_OPTION);
|
|
cmd->options = cpu_to_le16(opt_mask);
|
|
return pucan_write_cmd(priv);
|
}
|
|
/* request the device to notify the driver when Tx path is ready */
|
static int pucan_setup_rx_barrier(struct peak_canfd_priv *priv)
|
{
|
pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_RX_BARRIER);
|
|
return pucan_write_cmd(priv);
|
}
|
|
/* handle the reception of one CAN frame */
|
static int pucan_handle_can_rx(struct peak_canfd_priv *priv,
|
struct pucan_rx_msg *msg)
|
{
|
struct rtcan_skb skb = { .rb_frame_size = EMPTY_RB_FRAME_SIZE, };
|
struct rtcan_rb_frame *cf = &skb.rb_frame;
|
struct rtcan_device *rdev = priv->rdev;
|
const u16 rx_msg_flags = le16_to_cpu(msg->flags);
|
|
if (rx_msg_flags & PUCAN_MSG_EXT_DATA_LEN) {
|
/* CAN-FD frames are silently discarded */
|
return 0;
|
}
|
|
cf->can_id = le32_to_cpu(msg->can_id);
|
cf->can_dlc = get_can_dlc(pucan_msg_get_dlc(msg));
|
|
if (rx_msg_flags & PUCAN_MSG_EXT_ID)
|
cf->can_id |= CAN_EFF_FLAG;
|
|
if (rx_msg_flags & PUCAN_MSG_RTR)
|
cf->can_id |= CAN_RTR_FLAG;
|
else {
|
memcpy(cf->data, msg->d, cf->can_dlc);
|
skb.rb_frame_size += cf->can_dlc;
|
}
|
|
cf->can_ifindex = rdev->ifindex;
|
|
/* Pass received frame out to the sockets */
|
rtcan_rcv(rdev, &skb);
|
|
return 0;
|
}
|
|
/* handle rx/tx error counters notification */
|
static int pucan_handle_error(struct peak_canfd_priv *priv,
|
struct pucan_error_msg *msg)
|
{
|
priv->bec.txerr = msg->tx_err_cnt;
|
priv->bec.rxerr = msg->rx_err_cnt;
|
|
return 0;
|
}
|
|
/* handle status notification */
|
static int pucan_handle_status(struct peak_canfd_priv *priv,
|
struct pucan_status_msg *msg)
|
{
|
struct rtcan_skb skb = { .rb_frame_size = EMPTY_RB_FRAME_SIZE, };
|
struct rtcan_rb_frame *cf = &skb.rb_frame;
|
struct rtcan_device *rdev = priv->rdev;
|
|
/* this STATUS is the CNF of the RX_BARRIER: Tx path can be setup */
|
if (pucan_status_is_rx_barrier(msg)) {
|
if (priv->enable_tx_path) {
|
int err = priv->enable_tx_path(priv);
|
|
if (err)
|
return err;
|
}
|
|
/* unlock senders */
|
rtdm_sem_up(&rdev->tx_sem);
|
return 0;
|
}
|
|
/* otherwise, it's a BUS status */
|
cf->can_id = CAN_ERR_FLAG;
|
cf->can_dlc = CAN_ERR_DLC;
|
|
/* test state error bits according to their priority */
|
if (pucan_status_is_busoff(msg)) {
|
rtdm_printk(DRV_NAME " CAN%u: Bus-off entry status\n",
|
priv->index+1);
|
rdev->state = CAN_STATE_BUS_OFF;
|
cf->can_id |= CAN_ERR_BUSOFF;
|
|
/* wakeup waiting senders */
|
rtdm_sem_destroy(&rdev->tx_sem);
|
|
} else if (pucan_status_is_passive(msg)) {
|
rtdm_printk(DRV_NAME " CAN%u: Error passive status\n",
|
priv->index+1);
|
rdev->state = CAN_STATE_ERROR_PASSIVE;
|
cf->can_id |= CAN_ERR_CRTL;
|
cf->data[1] = (priv->bec.txerr > priv->bec.rxerr) ?
|
CAN_ERR_CRTL_TX_PASSIVE :
|
CAN_ERR_CRTL_RX_PASSIVE;
|
cf->data[6] = priv->bec.txerr;
|
cf->data[7] = priv->bec.rxerr;
|
|
} else if (pucan_status_is_warning(msg)) {
|
rtdm_printk(DRV_NAME " CAN%u: Error warning status\n",
|
priv->index+1);
|
rdev->state = CAN_STATE_ERROR_WARNING;
|
|
cf->can_id |= CAN_ERR_CRTL;
|
cf->data[1] = (priv->bec.txerr > priv->bec.rxerr) ?
|
CAN_ERR_CRTL_TX_WARNING :
|
CAN_ERR_CRTL_RX_WARNING;
|
cf->data[6] = priv->bec.txerr;
|
cf->data[7] = priv->bec.rxerr;
|
|
} else if (rdev->state != CAN_STATE_ERROR_ACTIVE) {
|
/* back to ERROR_ACTIVE */
|
rtdm_printk(DRV_NAME " CAN%u: Error active status\n",
|
priv->index+1);
|
rdev->state = CAN_STATE_ERROR_ACTIVE;
|
}
|
|
skb.rb_frame_size += cf->can_dlc;
|
cf->can_ifindex = rdev->ifindex;
|
|
/* Pass received frame out to the sockets */
|
rtcan_rcv(rdev, &skb);
|
|
return 0;
|
}
|
|
/* handle IP core Rx overflow notification */
|
static int pucan_handle_cache_critical(struct peak_canfd_priv *priv)
|
{
|
struct rtcan_skb skb = { .rb_frame_size = EMPTY_RB_FRAME_SIZE, };
|
struct rtcan_rb_frame *cf = &skb.rb_frame;
|
struct rtcan_device *rdev = priv->rdev;
|
|
cf->can_id = CAN_ERR_FLAG | CAN_ERR_CRTL;
|
cf->can_dlc = CAN_ERR_DLC;
|
|
cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
|
|
cf->data[6] = priv->bec.txerr;
|
cf->data[7] = priv->bec.rxerr;
|
|
skb.rb_frame_size += cf->can_dlc;
|
cf->can_ifindex = rdev->ifindex;
|
|
/* Pass received frame out to the sockets */
|
rtcan_rcv(rdev, &skb);
|
|
return 0;
|
}
|
|
/* handle a single uCAN message */
|
int peak_canfd_handle_msg(struct peak_canfd_priv *priv,
|
struct pucan_rx_msg *msg)
|
{
|
u16 msg_type = le16_to_cpu(msg->type);
|
int msg_size = le16_to_cpu(msg->size);
|
int err;
|
|
if (!msg_size || !msg_type) {
|
/* null packet found: end of list */
|
goto exit;
|
}
|
|
switch (msg_type) {
|
case PUCAN_MSG_CAN_RX:
|
err = pucan_handle_can_rx(priv, (struct pucan_rx_msg *)msg);
|
break;
|
case PUCAN_MSG_ERROR:
|
err = pucan_handle_error(priv, (struct pucan_error_msg *)msg);
|
break;
|
case PUCAN_MSG_STATUS:
|
err = pucan_handle_status(priv,
|
(struct pucan_status_msg *)msg);
|
break;
|
case PUCAN_MSG_CACHE_CRITICAL:
|
err = pucan_handle_cache_critical(priv);
|
break;
|
default:
|
err = 0;
|
}
|
|
if (err < 0)
|
return err;
|
|
exit:
|
return msg_size;
|
}
|
|
/* handle a list of rx_count messages from rx_msg memory address */
|
int peak_canfd_handle_msgs_list(struct peak_canfd_priv *priv,
|
struct pucan_rx_msg *msg_list, int msg_count)
|
{
|
void *msg_ptr = msg_list;
|
int i, msg_size = 0;
|
|
for (i = 0; i < msg_count; i++) {
|
msg_size = peak_canfd_handle_msg(priv, msg_ptr);
|
|
/* a null packet can be found at the end of a list */
|
if (msg_size <= 0)
|
break;
|
|
msg_ptr += ALIGN(msg_size, 4);
|
}
|
|
if (msg_size < 0)
|
return msg_size;
|
|
return i;
|
}
|
|
/* start the device (set the IP core in NORMAL or LISTEN-ONLY mode) */
|
static int peak_canfd_start(struct rtcan_device *rdev,
|
rtdm_lockctx_t *lock_ctx)
|
{
|
struct peak_canfd_priv *priv = rdev->priv;
|
int i, err = 0;
|
|
switch (rdev->state) {
|
case CAN_STATE_BUS_OFF:
|
case CAN_STATE_STOPPED:
|
err = pucan_set_reset_mode(priv);
|
if (err)
|
break;
|
|
/* set ineeded option: get rx/tx error counters */
|
err = pucan_set_options(priv, PUCAN_OPTION_ERROR);
|
if (err)
|
break;
|
|
/* accept all standard CAN ID */
|
for (i = 0; i <= PUCAN_FLTSTD_ROW_IDX_MAX; i++)
|
pucan_set_std_filter(priv, i, 0xffffffff);
|
|
/* clear device rx/tx error counters */
|
err = pucan_clr_err_counters(priv);
|
if (err)
|
break;
|
|
/* set resquested mode */
|
if (priv->rdev->ctrl_mode & CAN_CTRLMODE_LISTENONLY)
|
err = pucan_set_listen_only_mode(priv);
|
else
|
err = pucan_set_normal_mode(priv);
|
|
rtdm_sem_init(&rdev->tx_sem, 1);
|
|
/* receiving the RB status says when Tx path is ready */
|
err = pucan_setup_rx_barrier(priv);
|
break;
|
|
default:
|
break;
|
}
|
|
return err;
|
}
|
|
/* stop the device (set the IP core in RESET mode) */
|
static int peak_canfd_stop(struct rtcan_device *rdev,
|
rtdm_lockctx_t *lock_ctx)
|
{
|
struct peak_canfd_priv *priv = rdev->priv;
|
int err = 0;
|
|
switch (rdev->state) {
|
case CAN_STATE_BUS_OFF:
|
case CAN_STATE_STOPPED:
|
break;
|
|
default:
|
/* go back to RESET mode */
|
err = pucan_set_reset_mode(priv);
|
if (err) {
|
rtdm_printk(DRV_NAME " CAN%u: reset failed\n",
|
priv->index+1);
|
break;
|
}
|
|
/* abort last Tx (MUST be done in RESET mode only!) */
|
pucan_tx_abort(priv, PUCAN_TX_ABORT_FLUSH);
|
|
rtdm_sem_destroy(&rdev->tx_sem);
|
break;
|
}
|
|
return err;
|
}
|
|
/* RT-Socket-CAN driver interface */
|
static int peak_canfd_set_mode(struct rtcan_device *rdev, can_mode_t mode,
|
rtdm_lockctx_t *lock_ctx)
|
{
|
int err = 0;
|
|
switch (mode) {
|
case CAN_MODE_STOP:
|
err = peak_canfd_stop(rdev, lock_ctx);
|
break;
|
case CAN_MODE_START:
|
err = peak_canfd_start(rdev, lock_ctx);
|
break;
|
case CAN_MODE_SLEEP:
|
/* Controller must operate, otherwise go out */
|
if (!CAN_STATE_OPERATING(rdev->state)) {
|
err = -ENETDOWN;
|
break;
|
}
|
if (rdev->state == CAN_STATE_SLEEPING)
|
break;
|
|
fallthrough;
|
default:
|
err = -EOPNOTSUPP;
|
break;
|
}
|
|
return err;
|
}
|
|
static int peak_canfd_set_bittiming(struct rtcan_device *rdev,
|
struct can_bittime *pbt,
|
rtdm_lockctx_t *lock_ctx)
|
{
|
struct peak_canfd_priv *priv = rdev->priv;
|
struct pucan_timing_slow *cmd;
|
|
/* can't support BTR0BTR1 mode with clock greater than 8 MHz */
|
if (pbt->type != CAN_BITTIME_STD) {
|
rtdm_printk(DRV_NAME
|
" CAN%u: unsupported bittiming mode %u\n",
|
priv->index+1, pbt->type);
|
return -EINVAL;
|
}
|
|
cmd = pucan_add_cmd(pucan_init_cmd(priv), PUCAN_CMD_TIMING_SLOW);
|
|
cmd->sjw_t = PUCAN_TSLOW_SJW_T(pbt->std.sjw - 1,
|
priv->rdev->ctrl_mode &
|
CAN_CTRLMODE_3_SAMPLES);
|
|
cmd->tseg1 = PUCAN_TSLOW_TSEG1(pbt->std.prop_seg +
|
pbt->std.phase_seg1 - 1);
|
cmd->tseg2 = PUCAN_TSLOW_TSEG2(pbt->std.phase_seg2 - 1);
|
cmd->brp = cpu_to_le16(PUCAN_TSLOW_BRP(pbt->std.brp - 1));
|
|
cmd->ewl = 96; /* default */
|
|
rtdm_printk(DRV_NAME ": nominal: brp=%u tseg1=%u tseg2=%u sjw=%u\n",
|
le16_to_cpu(cmd->brp), cmd->tseg1, cmd->tseg2, cmd->sjw_t);
|
|
return pucan_write_cmd(priv);
|
}
|
|
/* hard transmit callback: write the CAN frame to the device */
|
static netdev_tx_t peak_canfd_start_xmit(struct rtcan_device *rdev,
|
can_frame_t *cf)
|
{
|
struct peak_canfd_priv *priv = rdev->priv;
|
struct pucan_tx_msg *msg;
|
u16 msg_size, msg_flags;
|
int room_left;
|
const u8 dlc = (cf->can_dlc > CAN_MAX_DLC) ? CAN_MAX_DLC : cf->can_dlc;
|
|
msg_size = ALIGN(sizeof(*msg) + dlc, 4);
|
msg = priv->alloc_tx_msg(priv, msg_size, &room_left);
|
|
/* should never happen except under bus-off condition and
|
* (auto-)restart mechanism
|
*/
|
if (!msg) {
|
rtdm_printk(DRV_NAME
|
" CAN%u: skb lost (No room left in tx buffer)\n",
|
priv->index+1);
|
return 0;
|
}
|
|
msg->size = cpu_to_le16(msg_size);
|
msg->type = cpu_to_le16(PUCAN_MSG_CAN_TX);
|
msg_flags = 0;
|
if (cf->can_id & CAN_EFF_FLAG) {
|
msg_flags |= PUCAN_MSG_EXT_ID;
|
msg->can_id = cpu_to_le32(cf->can_id & CAN_EFF_MASK);
|
} else {
|
msg->can_id = cpu_to_le32(cf->can_id & CAN_SFF_MASK);
|
}
|
|
if (cf->can_id & CAN_RTR_FLAG)
|
msg_flags |= PUCAN_MSG_RTR;
|
|
/* set driver specific bit to differentiate with application
|
* loopback
|
*/
|
if (rdev->ctrl_mode & CAN_CTRLMODE_LOOPBACK)
|
msg_flags |= PUCAN_MSG_LOOPED_BACK;
|
|
msg->flags = cpu_to_le16(msg_flags);
|
msg->channel_dlc = PUCAN_MSG_CHANNEL_DLC(priv->index, dlc);
|
memcpy(msg->d, cf->data, dlc);
|
|
/* write the skb on the interface */
|
priv->write_tx_msg(priv, msg);
|
|
/* control senders flow */
|
if (room_left > (sizeof(*msg) + CAN_MAX_DLC))
|
rtdm_sem_up(&rdev->tx_sem);
|
|
return 0;
|
}
|
|
/* allocate a rtcan device for channel #index, with enough space to store
|
* private information.
|
*/
|
struct rtcan_device *alloc_peak_canfd_dev(int sizeof_priv, int index)
|
{
|
struct rtcan_device *rdev;
|
struct peak_canfd_priv *priv;
|
|
/* allocate the candev object */
|
rdev = rtcan_dev_alloc(sizeof_priv, 0);
|
if (!rdev)
|
return NULL;
|
|
/* RTCAN part initialization */
|
strncpy(rdev->name, RTCAN_DEV_NAME, IFNAMSIZ);
|
rdev->ctrl_name = RTCAN_CTRLR_NAME;
|
rdev->can_sys_clock = 80*1000*1000; /* default */
|
rdev->state = CAN_STATE_STOPPED;
|
rdev->hard_start_xmit = peak_canfd_start_xmit;
|
rdev->do_set_mode = peak_canfd_set_mode;
|
rdev->do_set_bit_time = peak_canfd_set_bittiming;
|
rdev->bittiming_const = &peak_canfd_nominal_const;
|
|
priv = rdev->priv;
|
|
/* private part initialization */
|
priv->rdev = rdev;
|
priv->index = index;
|
priv->cmd_len = 0;
|
priv->bec.txerr = 0;
|
priv->bec.rxerr = 0;
|
|
return rdev;
|
}
|