Commit 88d10a9d authored by Stephane Grosjean's avatar Stephane Grosjean Committed by Jan Kiszka

RT-socket-CAN: Add support of PEAK PCAN-PCIe FD cards family

This patch includes the driver that supports the CANFD interfaces of
the PCAN-PCIe FD family. This driver is largely inspired by the peak_pciefd
driver included in the kernel since version 4.12. Except for the
differences related to the RTDM model, this driver differs from the
socket-CAN driver by the following points:

. CAN 2.0 a/b support only (CAN-FD not available in rt-socket-can)
. all interrupts are required (or released) when loading (or unloading)
  the module, not when activating (or deactivating) the CAN interface as in
  the socket-CAN model. Only hardware interruptions are enabled (or
  disabled) when the rtcan interface is set up (or down).
. This way of operating allows the usage of the MSI mode: if CONFIG_PCI_MSI
  is defined while CONFIG_XENO_OPT_SHIRQ is not, then the driver activates
  the MSI mode by default. The legacy INTA mode is used otherwise. In case
  CONFIG_PCI_MSI is defined, the parameter module "usemsi" allows to change
  this default behaviour: usemsi=0 selects INTA mode while usemsi=1 selects
  MSI mode.
. the Tx flow is managed more easily (no echo management in rt-socket-can)
Signed-off-by: default avatarStephane Grosjean <s.grosjean@peak-system.com>
Tested-by: default avatarAndy Tannenbaum <trb@bioniklabs.com>
[Jan: added to CI]
Signed-off-by: Jan Kiszka's avatarJan Kiszka <jan.kiszka@siemens.com>
parent a249d3d2
......@@ -103,6 +103,7 @@ variables:
- ./scripts/config -e XENO_DRIVERS_CAN_SJA1000_EMS_PCI
- ./scripts/config -e XENO_DRIVERS_CAN_SJA1000_ESD_PCI
- ./scripts/config -e XENO_DRIVERS_CAN_SJA1000_PEAK_DNG
- ./scripts/config -e XENO_DRIVERS_CAN_PEAK_CANFD
- ./scripts/config -m XENO_DRIVERS_NET
- ./scripts/config -e XENO_DRIVERS_RTNET_CHECKED
- ./scripts/config -e XENO_DRIVERS_NET_ETH_P_ALL
......
......@@ -86,6 +86,7 @@ config XENO_DRIVERS_CAN_FLEXCAN
Say Y here if you want to support for Freescale FlexCAN.
source "drivers/xenomai/can/mscan/Kconfig"
source "drivers/xenomai/can/peak_canfd/Kconfig"
source "drivers/xenomai/can/sja1000/Kconfig"
endmenu
ccflags-y += -I$(srctree)/drivers/xenomai/can
obj-$(CONFIG_XENO_DRIVERS_CAN) += xeno_can.o mscan/ sja1000/
obj-$(CONFIG_XENO_DRIVERS_CAN) += xeno_can.o mscan/ sja1000/ peak_canfd/
obj-$(CONFIG_XENO_DRIVERS_CAN_FLEXCAN) += xeno_can_flexcan.o
obj-$(CONFIG_XENO_DRIVERS_CAN_VIRT) += xeno_can_virt.o
......
config XENO_DRIVERS_CAN_PEAK_CANFD
depends on XENO_DRIVERS_CAN && PCI && !XENO_DRIVERS_CAN_CALC_BITTIME_OLD
tristate "PEAK driver for PCAN-PCIe FD family"
help
This driver supports the PCAN-PCIe FD boards family from PEAK-System.
#
# Makefile for the PEAK-System CAN-FD IP module drivers
#
ccflags-y += -Idrivers/xenomai/can
obj-$(CONFIG_XENO_DRIVERS_CAN_PEAK_CANFD) += xeno_can_peak_pciefd.o
xeno_can_peak_pciefd-y := rtcan_peak_pciefd.o rtcan_peak_canfd.o
// 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;