Merge tag 'linux-can-next-for-6.17-20250725' of git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can-next
Marc Kleine-Budde says: ==================== pull-request: can-next 2025-07-25 The first patch is by Khaled Elnaggar and converts the janz-ican3 driver's fwinfo_show() to sysfs_emit(). Vincent Mailhol contributes 3 patches that first fix a warning in the ti_hecc driver and then add missing COMPILE_TEST more compile coverage to the ti_hecc and tscan1 driver. Randy Dunlap's patch let's the tscan1 driver depend on PC104. A patch by Luis Felipe Hernandez fixes a kernel-doc error in the ctucanfd driver. Jimmy Assarsson contributes 10 patches for the kvaser_pciefd and 11 for the kvaser_usb driver. Both series simplify the identification of physical the CAN interfaces and add devlink support to get information about the running firmware. * tag 'linux-can-next-for-6.17-20250725' of git://git.kernel.org/pub/scm/linux/kernel/git/mkl/linux-can-next: (27 commits) Documentation: devlink: add devlink documentation for the kvaser_usb driver can: kvaser_usb: Add devlink port support can: kvaser_usb: Expose device information via devlink info_get() can: kvaser_usb: Add devlink support can: kvaser_usb: Store additional device information can: kvaser_usb: Store the different firmware version components in a struct can: kvaser_usb: Move comment regarding max_tx_urbs can: kvaser_usb: Add intermediate variables can: kvaser_usb: Assign netdev.dev_port based on device channel index can: kvaser_usb: Add support for ethtool set_phys_id() can: kvaser_usb: Add support to control CAN LEDs on device Documentation: devlink: add devlink documentation for the kvaser_pciefd driver can: kvaser_pciefd: Add devlink port support can: kvaser_pciefd: Expose device firmware version via devlink info_get() can: kvaser_pciefd: Add devlink support can: kvaser_pciefd: Split driver into C-file and header-file. can: kvaser_pciefd: Store device channel index can: kvaser_pciefd: Store the different firmware version components in a struct can: kvaser_pciefd: Add intermediate variable for device struct in probe() can: kvaser_pciefd: Add support for ethtool set_phys_id() ... ==================== Link: https://patch.msgid.link/20250725161327.4165174-1-mkl@pengutronix.de Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -85,6 +85,8 @@ parameters, info versions, and other features it supports.
|
||||
ionic
|
||||
ice
|
||||
ixgbe
|
||||
kvaser_pciefd
|
||||
kvaser_usb
|
||||
mlx4
|
||||
mlx5
|
||||
mlxsw
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=============================
|
||||
kvaser_pciefd devlink support
|
||||
=============================
|
||||
|
||||
This document describes the devlink features implemented by the
|
||||
``kvaser_pciefd`` device driver.
|
||||
|
||||
Info versions
|
||||
=============
|
||||
|
||||
The ``kvaser_pciefd`` driver reports the following versions
|
||||
|
||||
.. list-table:: devlink info versions implemented
|
||||
:widths: 5 5 90
|
||||
|
||||
* - Name
|
||||
- Type
|
||||
- Description
|
||||
* - ``fw``
|
||||
- running
|
||||
- Version of the firmware running on the device. Also available
|
||||
through ``ethtool -i`` as ``firmware-version``.
|
||||
@@ -0,0 +1,33 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
==========================
|
||||
kvaser_usb devlink support
|
||||
==========================
|
||||
|
||||
This document describes the devlink features implemented by the
|
||||
``kvaser_usb`` device driver.
|
||||
|
||||
Info versions
|
||||
=============
|
||||
|
||||
The ``kvaser_usb`` driver reports the following versions
|
||||
|
||||
.. list-table:: devlink info versions implemented
|
||||
:widths: 5 5 90
|
||||
|
||||
* - Name
|
||||
- Type
|
||||
- Description
|
||||
* - ``fw``
|
||||
- running
|
||||
- Version of the firmware running on the device. Also available
|
||||
through ``ethtool -i`` as ``firmware-version``.
|
||||
* - ``board.rev``
|
||||
- fixed
|
||||
- The device hardware revision.
|
||||
* - ``board.id``
|
||||
- fixed
|
||||
- The device EAN (product number).
|
||||
* - ``serial_number``
|
||||
- fixed
|
||||
- The device serial number.
|
||||
@@ -154,6 +154,7 @@ config CAN_JANZ_ICAN3
|
||||
config CAN_KVASER_PCIEFD
|
||||
depends on PCI
|
||||
tristate "Kvaser PCIe FD cards"
|
||||
select NET_DEVLINK
|
||||
help
|
||||
This is a driver for the Kvaser PCI Express CAN FD family.
|
||||
|
||||
@@ -201,7 +202,7 @@ config CAN_SUN4I
|
||||
be called sun4i_can.
|
||||
|
||||
config CAN_TI_HECC
|
||||
depends on ARM
|
||||
depends on ARM || COMPILE_TEST
|
||||
tristate "TI High End CAN Controller"
|
||||
select CAN_RX_OFFLOAD
|
||||
help
|
||||
|
||||
@@ -25,7 +25,7 @@ obj-$(CONFIG_CAN_FLEXCAN) += flexcan/
|
||||
obj-$(CONFIG_CAN_GRCAN) += grcan.o
|
||||
obj-$(CONFIG_CAN_IFI_CANFD) += ifi_canfd/
|
||||
obj-$(CONFIG_CAN_JANZ_ICAN3) += janz-ican3.o
|
||||
obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd.o
|
||||
obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd/
|
||||
obj-$(CONFIG_CAN_MSCAN) += mscan/
|
||||
obj-$(CONFIG_CAN_M_CAN) += m_can/
|
||||
obj-$(CONFIG_CAN_PEAK_PCIEFD) += peak_canfd/
|
||||
|
||||
@@ -506,11 +506,12 @@ static bool ctucan_is_txt_buf_writable(struct ctucan_priv *priv, u8 buf)
|
||||
* @buf: TXT Buffer index to which frame is inserted (0-based)
|
||||
* @isfdf: True - CAN FD Frame, False - CAN 2.0 Frame
|
||||
*
|
||||
* Return: True - Frame inserted successfully
|
||||
* False - Frame was not inserted due to one of:
|
||||
* 1. TXT Buffer is not writable (it is in wrong state)
|
||||
* 2. Invalid TXT buffer index
|
||||
* 3. Invalid frame length
|
||||
* Return:
|
||||
* * True - Frame inserted successfully
|
||||
* * False - Frame was not inserted due to one of:
|
||||
* 1. TXT Buffer is not writable (it is in wrong state)
|
||||
* 2. Invalid TXT buffer index
|
||||
* 3. Invalid frame length
|
||||
*/
|
||||
static bool ctucan_insert_frame(struct ctucan_priv *priv, const struct canfd_frame *cf, u8 buf,
|
||||
bool isfdf)
|
||||
|
||||
@@ -1867,7 +1867,7 @@ static ssize_t fwinfo_show(struct device *dev,
|
||||
{
|
||||
struct ican3_dev *mod = netdev_priv(to_net_dev(dev));
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", mod->fwinfo);
|
||||
return sysfs_emit(buf, "%s\n", mod->fwinfo);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(termination);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_CAN_KVASER_PCIEFD) += kvaser_pciefd.o
|
||||
kvaser_pciefd-y = kvaser_pciefd_core.o kvaser_pciefd_devlink.o
|
||||
@@ -0,0 +1,96 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
|
||||
/* kvaser_pciefd common definitions and declarations
|
||||
*
|
||||
* Copyright (C) 2025 KVASER AB, Sweden. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _KVASER_PCIEFD_H
|
||||
#define _KVASER_PCIEFD_H
|
||||
|
||||
#include <linux/can/dev.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/types.h>
|
||||
#include <net/devlink.h>
|
||||
|
||||
#define KVASER_PCIEFD_MAX_CAN_CHANNELS 8UL
|
||||
#define KVASER_PCIEFD_DMA_COUNT 2U
|
||||
#define KVASER_PCIEFD_DMA_SIZE (4U * 1024U)
|
||||
#define KVASER_PCIEFD_CAN_TX_MAX_COUNT 17U
|
||||
|
||||
struct kvaser_pciefd;
|
||||
|
||||
struct kvaser_pciefd_address_offset {
|
||||
u32 serdes;
|
||||
u32 pci_ien;
|
||||
u32 pci_irq;
|
||||
u32 sysid;
|
||||
u32 loopback;
|
||||
u32 kcan_srb_fifo;
|
||||
u32 kcan_srb;
|
||||
u32 kcan_ch0;
|
||||
u32 kcan_ch1;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_irq_mask {
|
||||
u32 kcan_rx0;
|
||||
u32 kcan_tx[KVASER_PCIEFD_MAX_CAN_CHANNELS];
|
||||
u32 all;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_dev_ops {
|
||||
void (*kvaser_pciefd_write_dma_map)(struct kvaser_pciefd *pcie,
|
||||
dma_addr_t addr, int index);
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_driver_data {
|
||||
const struct kvaser_pciefd_address_offset *address_offset;
|
||||
const struct kvaser_pciefd_irq_mask *irq_mask;
|
||||
const struct kvaser_pciefd_dev_ops *ops;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_fw_version {
|
||||
u8 major;
|
||||
u8 minor;
|
||||
u16 build;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_can {
|
||||
struct can_priv can;
|
||||
struct devlink_port devlink_port;
|
||||
struct kvaser_pciefd *kv_pcie;
|
||||
void __iomem *reg_base;
|
||||
struct can_berr_counter bec;
|
||||
u32 ioc;
|
||||
u8 cmd_seq;
|
||||
u8 tx_max_count;
|
||||
u8 tx_idx;
|
||||
u8 ack_idx;
|
||||
int err_rep_cnt;
|
||||
unsigned int completed_tx_pkts;
|
||||
unsigned int completed_tx_bytes;
|
||||
spinlock_t lock; /* Locks sensitive registers (e.g. MODE) */
|
||||
struct timer_list bec_poll_timer;
|
||||
struct completion start_comp, flush_comp;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd {
|
||||
struct pci_dev *pci;
|
||||
void __iomem *reg_base;
|
||||
struct kvaser_pciefd_can *can[KVASER_PCIEFD_MAX_CAN_CHANNELS];
|
||||
const struct kvaser_pciefd_driver_data *driver_data;
|
||||
void *dma_data[KVASER_PCIEFD_DMA_COUNT];
|
||||
u8 nr_channels;
|
||||
u32 bus_freq;
|
||||
u32 freq;
|
||||
u32 freq_to_ticks_div;
|
||||
struct kvaser_pciefd_fw_version fw_version;
|
||||
};
|
||||
|
||||
extern const struct devlink_ops kvaser_pciefd_devlink_ops;
|
||||
|
||||
int kvaser_pciefd_devlink_port_register(struct kvaser_pciefd_can *can);
|
||||
void kvaser_pciefd_devlink_port_unregister(struct kvaser_pciefd_can *can);
|
||||
#endif /* _KVASER_PCIEFD_H */
|
||||
+71
-73
@@ -5,6 +5,8 @@
|
||||
* - PEAK linux canfd driver
|
||||
*/
|
||||
|
||||
#include "kvaser_pciefd.h"
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/can/dev.h>
|
||||
#include <linux/device.h>
|
||||
@@ -27,10 +29,6 @@ MODULE_DESCRIPTION("CAN driver for Kvaser CAN/PCIe devices");
|
||||
#define KVASER_PCIEFD_WAIT_TIMEOUT msecs_to_jiffies(1000)
|
||||
#define KVASER_PCIEFD_BEC_POLL_FREQ (jiffies + msecs_to_jiffies(200))
|
||||
#define KVASER_PCIEFD_MAX_ERR_REP 256U
|
||||
#define KVASER_PCIEFD_CAN_TX_MAX_COUNT 17U
|
||||
#define KVASER_PCIEFD_MAX_CAN_CHANNELS 8UL
|
||||
#define KVASER_PCIEFD_DMA_COUNT 2U
|
||||
#define KVASER_PCIEFD_DMA_SIZE (4U * 1024U)
|
||||
|
||||
#define KVASER_PCIEFD_VENDOR 0x1a07
|
||||
|
||||
@@ -66,6 +64,7 @@ MODULE_DESCRIPTION("CAN driver for Kvaser CAN/PCIe devices");
|
||||
#define KVASER_PCIEFD_KCAN_FIFO_LAST_REG 0x180
|
||||
#define KVASER_PCIEFD_KCAN_CTRL_REG 0x2c0
|
||||
#define KVASER_PCIEFD_KCAN_CMD_REG 0x400
|
||||
#define KVASER_PCIEFD_KCAN_IOC_REG 0x404
|
||||
#define KVASER_PCIEFD_KCAN_IEN_REG 0x408
|
||||
#define KVASER_PCIEFD_KCAN_IRQ_REG 0x410
|
||||
#define KVASER_PCIEFD_KCAN_TX_NR_PACKETS_REG 0x414
|
||||
@@ -136,6 +135,9 @@ MODULE_DESCRIPTION("CAN driver for Kvaser CAN/PCIe devices");
|
||||
/* Request status packet */
|
||||
#define KVASER_PCIEFD_KCAN_CMD_SRQ BIT(0)
|
||||
|
||||
/* Control CAN LED, active low */
|
||||
#define KVASER_PCIEFD_KCAN_IOC_LED BIT(0)
|
||||
|
||||
/* Transmitter unaligned */
|
||||
#define KVASER_PCIEFD_KCAN_IRQ_TAL BIT(17)
|
||||
/* Tx FIFO empty */
|
||||
@@ -292,35 +294,6 @@ static void kvaser_pciefd_write_dma_map_sf2(struct kvaser_pciefd *pcie,
|
||||
static void kvaser_pciefd_write_dma_map_xilinx(struct kvaser_pciefd *pcie,
|
||||
dma_addr_t addr, int index);
|
||||
|
||||
struct kvaser_pciefd_address_offset {
|
||||
u32 serdes;
|
||||
u32 pci_ien;
|
||||
u32 pci_irq;
|
||||
u32 sysid;
|
||||
u32 loopback;
|
||||
u32 kcan_srb_fifo;
|
||||
u32 kcan_srb;
|
||||
u32 kcan_ch0;
|
||||
u32 kcan_ch1;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_dev_ops {
|
||||
void (*kvaser_pciefd_write_dma_map)(struct kvaser_pciefd *pcie,
|
||||
dma_addr_t addr, int index);
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_irq_mask {
|
||||
u32 kcan_rx0;
|
||||
u32 kcan_tx[KVASER_PCIEFD_MAX_CAN_CHANNELS];
|
||||
u32 all;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_driver_data {
|
||||
const struct kvaser_pciefd_address_offset *address_offset;
|
||||
const struct kvaser_pciefd_irq_mask *irq_mask;
|
||||
const struct kvaser_pciefd_dev_ops *ops;
|
||||
};
|
||||
|
||||
static const struct kvaser_pciefd_address_offset kvaser_pciefd_altera_address_offset = {
|
||||
.serdes = 0x1000,
|
||||
.pci_ien = 0x50,
|
||||
@@ -405,35 +378,6 @@ static const struct kvaser_pciefd_driver_data kvaser_pciefd_xilinx_driver_data =
|
||||
.ops = &kvaser_pciefd_xilinx_dev_ops,
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_can {
|
||||
struct can_priv can;
|
||||
struct kvaser_pciefd *kv_pcie;
|
||||
void __iomem *reg_base;
|
||||
struct can_berr_counter bec;
|
||||
u8 cmd_seq;
|
||||
u8 tx_max_count;
|
||||
u8 tx_idx;
|
||||
u8 ack_idx;
|
||||
int err_rep_cnt;
|
||||
unsigned int completed_tx_pkts;
|
||||
unsigned int completed_tx_bytes;
|
||||
spinlock_t lock; /* Locks sensitive registers (e.g. MODE) */
|
||||
struct timer_list bec_poll_timer;
|
||||
struct completion start_comp, flush_comp;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd {
|
||||
struct pci_dev *pci;
|
||||
void __iomem *reg_base;
|
||||
struct kvaser_pciefd_can *can[KVASER_PCIEFD_MAX_CAN_CHANNELS];
|
||||
const struct kvaser_pciefd_driver_data *driver_data;
|
||||
void *dma_data[KVASER_PCIEFD_DMA_COUNT];
|
||||
u8 nr_channels;
|
||||
u32 bus_freq;
|
||||
u32 freq;
|
||||
u32 freq_to_ticks_div;
|
||||
};
|
||||
|
||||
struct kvaser_pciefd_rx_packet {
|
||||
u32 header[2];
|
||||
u64 timestamp;
|
||||
@@ -528,6 +472,16 @@ static inline void kvaser_pciefd_abort_flush_reset(struct kvaser_pciefd_can *can
|
||||
kvaser_pciefd_send_kcan_cmd(can, KVASER_PCIEFD_KCAN_CMD_AT);
|
||||
}
|
||||
|
||||
static inline void kvaser_pciefd_set_led(struct kvaser_pciefd_can *can, bool on)
|
||||
{
|
||||
if (on)
|
||||
can->ioc &= ~KVASER_PCIEFD_KCAN_IOC_LED;
|
||||
else
|
||||
can->ioc |= KVASER_PCIEFD_KCAN_IOC_LED;
|
||||
|
||||
iowrite32(can->ioc, can->reg_base + KVASER_PCIEFD_KCAN_IOC_REG);
|
||||
}
|
||||
|
||||
static void kvaser_pciefd_enable_err_gen(struct kvaser_pciefd_can *can)
|
||||
{
|
||||
u32 mode;
|
||||
@@ -953,8 +907,32 @@ static const struct net_device_ops kvaser_pciefd_netdev_ops = {
|
||||
.ndo_change_mtu = can_change_mtu,
|
||||
};
|
||||
|
||||
static int kvaser_pciefd_set_phys_id(struct net_device *netdev,
|
||||
enum ethtool_phys_id_state state)
|
||||
{
|
||||
struct kvaser_pciefd_can *can = netdev_priv(netdev);
|
||||
|
||||
switch (state) {
|
||||
case ETHTOOL_ID_ACTIVE:
|
||||
return 3; /* 3 On/Off cycles per second */
|
||||
|
||||
case ETHTOOL_ID_ON:
|
||||
kvaser_pciefd_set_led(can, true);
|
||||
return 0;
|
||||
|
||||
case ETHTOOL_ID_OFF:
|
||||
case ETHTOOL_ID_INACTIVE:
|
||||
kvaser_pciefd_set_led(can, false);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct ethtool_ops kvaser_pciefd_ethtool_ops = {
|
||||
.get_ts_info = can_ethtool_op_get_ts_info_hwts,
|
||||
.set_phys_id = kvaser_pciefd_set_phys_id,
|
||||
};
|
||||
|
||||
static int kvaser_pciefd_setup_can_ctrls(struct kvaser_pciefd *pcie)
|
||||
@@ -965,6 +943,7 @@ static int kvaser_pciefd_setup_can_ctrls(struct kvaser_pciefd *pcie)
|
||||
struct net_device *netdev;
|
||||
struct kvaser_pciefd_can *can;
|
||||
u32 status, tx_nr_packets_max;
|
||||
int ret;
|
||||
|
||||
netdev = alloc_candev(sizeof(struct kvaser_pciefd_can),
|
||||
roundup_pow_of_two(KVASER_PCIEFD_CAN_TX_MAX_COUNT));
|
||||
@@ -982,6 +961,7 @@ static int kvaser_pciefd_setup_can_ctrls(struct kvaser_pciefd *pcie)
|
||||
can->completed_tx_bytes = 0;
|
||||
can->bec.txerr = 0;
|
||||
can->bec.rxerr = 0;
|
||||
can->can.dev->dev_port = i;
|
||||
|
||||
init_completion(&can->start_comp);
|
||||
init_completion(&can->flush_comp);
|
||||
@@ -990,6 +970,9 @@ static int kvaser_pciefd_setup_can_ctrls(struct kvaser_pciefd *pcie)
|
||||
/* Disable Bus load reporting */
|
||||
iowrite32(0, can->reg_base + KVASER_PCIEFD_KCAN_BUS_LOAD_REG);
|
||||
|
||||
can->ioc = ioread32(can->reg_base + KVASER_PCIEFD_KCAN_IOC_REG);
|
||||
kvaser_pciefd_set_led(can, false);
|
||||
|
||||
tx_nr_packets_max =
|
||||
FIELD_GET(KVASER_PCIEFD_KCAN_TX_NR_PACKETS_MAX_MASK,
|
||||
ioread32(can->reg_base + KVASER_PCIEFD_KCAN_TX_NR_PACKETS_REG));
|
||||
@@ -1031,6 +1014,11 @@ static int kvaser_pciefd_setup_can_ctrls(struct kvaser_pciefd *pcie)
|
||||
|
||||
pcie->can[i] = can;
|
||||
kvaser_pciefd_pwm_start(can);
|
||||
ret = kvaser_pciefd_devlink_port_register(can);
|
||||
if (ret) {
|
||||
dev_err(&pcie->pci->dev, "Failed to register devlink port\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1163,14 +1151,12 @@ static int kvaser_pciefd_setup_board(struct kvaser_pciefd *pcie)
|
||||
u32 version, srb_status, build;
|
||||
|
||||
version = ioread32(KVASER_PCIEFD_SYSID_ADDR(pcie) + KVASER_PCIEFD_SYSID_VERSION_REG);
|
||||
build = ioread32(KVASER_PCIEFD_SYSID_ADDR(pcie) + KVASER_PCIEFD_SYSID_BUILD_REG);
|
||||
pcie->nr_channels = min(KVASER_PCIEFD_MAX_CAN_CHANNELS,
|
||||
FIELD_GET(KVASER_PCIEFD_SYSID_VERSION_NR_CHAN_MASK, version));
|
||||
|
||||
build = ioread32(KVASER_PCIEFD_SYSID_ADDR(pcie) + KVASER_PCIEFD_SYSID_BUILD_REG);
|
||||
dev_dbg(&pcie->pci->dev, "Version %lu.%lu.%lu\n",
|
||||
FIELD_GET(KVASER_PCIEFD_SYSID_VERSION_MAJOR_MASK, version),
|
||||
FIELD_GET(KVASER_PCIEFD_SYSID_VERSION_MINOR_MASK, version),
|
||||
FIELD_GET(KVASER_PCIEFD_SYSID_BUILD_SEQ_MASK, build));
|
||||
pcie->fw_version.major = FIELD_GET(KVASER_PCIEFD_SYSID_VERSION_MAJOR_MASK, version);
|
||||
pcie->fw_version.minor = FIELD_GET(KVASER_PCIEFD_SYSID_VERSION_MINOR_MASK, version);
|
||||
pcie->fw_version.build = FIELD_GET(KVASER_PCIEFD_SYSID_BUILD_SEQ_MASK, build);
|
||||
|
||||
srb_status = ioread32(KVASER_PCIEFD_SRB_ADDR(pcie) + KVASER_PCIEFD_SRB_STAT_REG);
|
||||
if (!(srb_status & KVASER_PCIEFD_SRB_STAT_DMA)) {
|
||||
@@ -1752,6 +1738,7 @@ static void kvaser_pciefd_teardown_can_ctrls(struct kvaser_pciefd *pcie)
|
||||
if (can) {
|
||||
iowrite32(0, can->reg_base + KVASER_PCIEFD_KCAN_IEN_REG);
|
||||
kvaser_pciefd_pwm_stop(can);
|
||||
kvaser_pciefd_devlink_port_unregister(can);
|
||||
free_candev(can->can.dev);
|
||||
}
|
||||
}
|
||||
@@ -1771,13 +1758,16 @@ static int kvaser_pciefd_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct devlink *devlink;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct kvaser_pciefd *pcie;
|
||||
const struct kvaser_pciefd_irq_mask *irq_mask;
|
||||
|
||||
pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
|
||||
if (!pcie)
|
||||
devlink = devlink_alloc(&kvaser_pciefd_devlink_ops, sizeof(*pcie), dev);
|
||||
if (!devlink)
|
||||
return -ENOMEM;
|
||||
|
||||
pcie = devlink_priv(devlink);
|
||||
pci_set_drvdata(pdev, pcie);
|
||||
pcie->pci = pdev;
|
||||
pcie->driver_data = (const struct kvaser_pciefd_driver_data *)id->driver_data;
|
||||
@@ -1785,7 +1775,7 @@ static int kvaser_pciefd_probe(struct pci_dev *pdev,
|
||||
|
||||
ret = pci_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_free_devlink;
|
||||
|
||||
ret = pci_request_regions(pdev, KVASER_PCIEFD_DRV_NAME);
|
||||
if (ret)
|
||||
@@ -1813,7 +1803,7 @@ static int kvaser_pciefd_probe(struct pci_dev *pdev,
|
||||
|
||||
ret = pci_alloc_irq_vectors(pcie->pci, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI);
|
||||
if (ret < 0) {
|
||||
dev_err(&pcie->pci->dev, "Failed to allocate IRQ vectors.\n");
|
||||
dev_err(dev, "Failed to allocate IRQ vectors.\n");
|
||||
goto err_teardown_can_ctrls;
|
||||
}
|
||||
|
||||
@@ -1826,7 +1816,7 @@ static int kvaser_pciefd_probe(struct pci_dev *pdev,
|
||||
ret = request_irq(pcie->pci->irq, kvaser_pciefd_irq_handler,
|
||||
IRQF_SHARED, KVASER_PCIEFD_DRV_NAME, pcie);
|
||||
if (ret) {
|
||||
dev_err(&pcie->pci->dev, "Failed to request IRQ %d\n", pcie->pci->irq);
|
||||
dev_err(dev, "Failed to request IRQ %d\n", pcie->pci->irq);
|
||||
goto err_pci_free_irq_vectors;
|
||||
}
|
||||
iowrite32(KVASER_PCIEFD_SRB_IRQ_DPD0 | KVASER_PCIEFD_SRB_IRQ_DPD1,
|
||||
@@ -1849,6 +1839,8 @@ static int kvaser_pciefd_probe(struct pci_dev *pdev,
|
||||
if (ret)
|
||||
goto err_free_irq;
|
||||
|
||||
devlink_register(devlink);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_irq:
|
||||
@@ -1872,6 +1864,9 @@ err_release_regions:
|
||||
err_disable_pci:
|
||||
pci_disable_device(pdev);
|
||||
|
||||
err_free_devlink:
|
||||
devlink_free(devlink);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1886,6 +1881,7 @@ static void kvaser_pciefd_remove(struct pci_dev *pdev)
|
||||
unregister_candev(can->can.dev);
|
||||
timer_delete(&can->bec_poll_timer);
|
||||
kvaser_pciefd_pwm_stop(can);
|
||||
kvaser_pciefd_devlink_port_unregister(can);
|
||||
}
|
||||
|
||||
kvaser_pciefd_disable_irq_srcs(pcie);
|
||||
@@ -1895,9 +1891,11 @@ static void kvaser_pciefd_remove(struct pci_dev *pdev)
|
||||
for (i = 0; i < pcie->nr_channels; ++i)
|
||||
free_candev(pcie->can[i]->can.dev);
|
||||
|
||||
devlink_unregister(priv_to_devlink(pcie));
|
||||
pci_iounmap(pdev, pcie->reg_base);
|
||||
pci_release_regions(pdev);
|
||||
pci_disable_device(pdev);
|
||||
devlink_free(priv_to_devlink(pcie));
|
||||
}
|
||||
|
||||
static struct pci_driver kvaser_pciefd = {
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
|
||||
/* kvaser_pciefd devlink functions
|
||||
*
|
||||
* Copyright (C) 2025 KVASER AB, Sweden. All rights reserved.
|
||||
*/
|
||||
#include "kvaser_pciefd.h"
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <net/devlink.h>
|
||||
|
||||
static int kvaser_pciefd_devlink_info_get(struct devlink *devlink,
|
||||
struct devlink_info_req *req,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct kvaser_pciefd *pcie = devlink_priv(devlink);
|
||||
char buf[] = "xxx.xxx.xxxxx";
|
||||
int ret;
|
||||
|
||||
if (pcie->fw_version.major) {
|
||||
snprintf(buf, sizeof(buf), "%u.%u.%u",
|
||||
pcie->fw_version.major,
|
||||
pcie->fw_version.minor,
|
||||
pcie->fw_version.build);
|
||||
ret = devlink_info_version_running_put(req,
|
||||
DEVLINK_INFO_VERSION_GENERIC_FW,
|
||||
buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct devlink_ops kvaser_pciefd_devlink_ops = {
|
||||
.info_get = kvaser_pciefd_devlink_info_get,
|
||||
};
|
||||
|
||||
int kvaser_pciefd_devlink_port_register(struct kvaser_pciefd_can *can)
|
||||
{
|
||||
int ret;
|
||||
struct devlink_port_attrs attrs = {
|
||||
.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL,
|
||||
.phys.port_number = can->can.dev->dev_port,
|
||||
};
|
||||
devlink_port_attrs_set(&can->devlink_port, &attrs);
|
||||
|
||||
ret = devlink_port_register(priv_to_devlink(can->kv_pcie),
|
||||
&can->devlink_port, can->can.dev->dev_port);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
SET_NETDEV_DEVLINK_PORT(can->can.dev, &can->devlink_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void kvaser_pciefd_devlink_port_unregister(struct kvaser_pciefd_can *can)
|
||||
{
|
||||
devlink_port_unregister(&can->devlink_port);
|
||||
}
|
||||
@@ -105,7 +105,7 @@ config CAN_SJA1000_PLATFORM
|
||||
|
||||
config CAN_TSCAN1
|
||||
tristate "TS-CAN1 PC104 boards"
|
||||
depends on ISA
|
||||
depends on (ISA && PC104) || (COMPILE_TEST && HAS_IOPORT)
|
||||
help
|
||||
This driver is for Technologic Systems' TSCAN-1 PC104 boards.
|
||||
https://www.embeddedts.com/products/TS-CAN1
|
||||
|
||||
@@ -383,7 +383,7 @@ static void ti_hecc_start(struct net_device *ndev)
|
||||
* overflows instead of the hardware silently dropping the
|
||||
* messages.
|
||||
*/
|
||||
mbx_mask = ~BIT(HECC_RX_LAST_MBOX);
|
||||
mbx_mask = ~BIT_U32(HECC_RX_LAST_MBOX);
|
||||
hecc_write(priv, HECC_CANOPC, mbx_mask);
|
||||
|
||||
/* Enable interrupts */
|
||||
|
||||
@@ -66,6 +66,7 @@ config CAN_GS_USB
|
||||
|
||||
config CAN_KVASER_USB
|
||||
tristate "Kvaser CAN/USB interface"
|
||||
select NET_DEVLINK
|
||||
help
|
||||
This driver adds support for Kvaser CAN/USB devices like Kvaser
|
||||
Leaf Light, Kvaser USBcan II and Kvaser Memorator Pro 5xHS.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb.o
|
||||
kvaser_usb-y = kvaser_usb_core.o kvaser_usb_leaf.o kvaser_usb_hydra.o
|
||||
kvaser_usb-y = kvaser_usb_core.o kvaser_usb_devlink.o kvaser_usb_leaf.o kvaser_usb_hydra.o
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/usb.h>
|
||||
#include <net/devlink.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/dev.h>
|
||||
@@ -47,6 +48,10 @@
|
||||
#define KVASER_USB_CAP_EXT_CAP 0x02
|
||||
#define KVASER_USB_HYDRA_CAP_EXT_CMD 0x04
|
||||
|
||||
#define KVASER_USB_SW_VERSION_MAJOR_MASK GENMASK(31, 24)
|
||||
#define KVASER_USB_SW_VERSION_MINOR_MASK GENMASK(23, 16)
|
||||
#define KVASER_USB_SW_VERSION_BUILD_MASK GENMASK(15, 0)
|
||||
|
||||
struct kvaser_usb_dev_cfg;
|
||||
|
||||
enum kvaser_usb_leaf_family {
|
||||
@@ -54,6 +59,11 @@ enum kvaser_usb_leaf_family {
|
||||
KVASER_USBCAN,
|
||||
};
|
||||
|
||||
enum kvaser_usb_led_state {
|
||||
KVASER_USB_LED_ON = 0,
|
||||
KVASER_USB_LED_OFF = 1,
|
||||
};
|
||||
|
||||
#define KVASER_USB_HYDRA_MAX_CMD_LEN 128
|
||||
struct kvaser_usb_dev_card_data_hydra {
|
||||
u8 channel_to_he[KVASER_USB_MAX_NET_DEVICES];
|
||||
@@ -78,6 +88,12 @@ struct kvaser_usb_tx_urb_context {
|
||||
u32 echo_index;
|
||||
};
|
||||
|
||||
struct kvaser_usb_fw_version {
|
||||
u8 major;
|
||||
u8 minor;
|
||||
u16 build;
|
||||
};
|
||||
|
||||
struct kvaser_usb_busparams {
|
||||
__le32 bitrate;
|
||||
u8 tseg1;
|
||||
@@ -96,12 +112,15 @@ struct kvaser_usb {
|
||||
struct usb_endpoint_descriptor *bulk_in, *bulk_out;
|
||||
struct usb_anchor rx_submitted;
|
||||
|
||||
u32 ean[2];
|
||||
u32 serial_number;
|
||||
struct kvaser_usb_fw_version fw_version;
|
||||
u8 hw_revision;
|
||||
unsigned int nchannels;
|
||||
/* @max_tx_urbs: Firmware-reported maximum number of outstanding,
|
||||
* not yet ACKed, transmissions on this device. This value is
|
||||
* also used as a sentinel for marking free tx contexts.
|
||||
*/
|
||||
u32 fw_version;
|
||||
unsigned int nchannels;
|
||||
unsigned int max_tx_urbs;
|
||||
struct kvaser_usb_dev_card_data card_data;
|
||||
|
||||
@@ -112,6 +131,7 @@ struct kvaser_usb {
|
||||
|
||||
struct kvaser_usb_net_priv {
|
||||
struct can_priv can;
|
||||
struct devlink_port devlink_port;
|
||||
struct can_berr_counter bec;
|
||||
|
||||
/* subdriver-specific data */
|
||||
@@ -149,6 +169,7 @@ struct kvaser_usb_net_priv {
|
||||
* @dev_get_software_details: get software details
|
||||
* @dev_get_card_info: get card info
|
||||
* @dev_get_capabilities: discover device capabilities
|
||||
* @dev_set_led: turn on/off device LED
|
||||
*
|
||||
* @dev_set_opt_mode: set ctrlmod
|
||||
* @dev_start_chip: start the CAN controller
|
||||
@@ -176,6 +197,9 @@ struct kvaser_usb_dev_ops {
|
||||
int (*dev_get_software_details)(struct kvaser_usb *dev);
|
||||
int (*dev_get_card_info)(struct kvaser_usb *dev);
|
||||
int (*dev_get_capabilities)(struct kvaser_usb *dev);
|
||||
int (*dev_set_led)(struct kvaser_usb_net_priv *priv,
|
||||
enum kvaser_usb_led_state state,
|
||||
u16 duration_ms);
|
||||
int (*dev_set_opt_mode)(const struct kvaser_usb_net_priv *priv);
|
||||
int (*dev_start_chip)(struct kvaser_usb_net_priv *priv);
|
||||
int (*dev_stop_chip)(struct kvaser_usb_net_priv *priv);
|
||||
@@ -204,6 +228,11 @@ struct kvaser_usb_dev_cfg {
|
||||
extern const struct kvaser_usb_dev_ops kvaser_usb_hydra_dev_ops;
|
||||
extern const struct kvaser_usb_dev_ops kvaser_usb_leaf_dev_ops;
|
||||
|
||||
extern const struct devlink_ops kvaser_usb_devlink_ops;
|
||||
|
||||
int kvaser_usb_devlink_port_register(struct kvaser_usb_net_priv *priv);
|
||||
void kvaser_usb_devlink_port_unregister(struct kvaser_usb_net_priv *priv);
|
||||
|
||||
void kvaser_usb_unlink_tx_urbs(struct kvaser_usb_net_priv *priv);
|
||||
|
||||
int kvaser_usb_recv_cmd(const struct kvaser_usb *dev, void *cmd, int len,
|
||||
|
||||
@@ -364,10 +364,13 @@ resubmit_urb:
|
||||
err = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (err == -ENODEV) {
|
||||
for (i = 0; i < dev->nchannels; i++) {
|
||||
if (!dev->nets[i])
|
||||
struct kvaser_usb_net_priv *priv;
|
||||
|
||||
priv = dev->nets[i];
|
||||
if (!priv)
|
||||
continue;
|
||||
|
||||
netif_device_detach(dev->nets[i]->netdev);
|
||||
netif_device_detach(priv->netdev);
|
||||
}
|
||||
} else if (err) {
|
||||
dev_err(&dev->intf->dev,
|
||||
@@ -753,6 +756,31 @@ freeurb:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int kvaser_usb_set_phys_id(struct net_device *netdev,
|
||||
enum ethtool_phys_id_state state)
|
||||
{
|
||||
struct kvaser_usb_net_priv *priv = netdev_priv(netdev);
|
||||
const struct kvaser_usb_dev_ops *ops = priv->dev->driver_info->ops;
|
||||
|
||||
switch (state) {
|
||||
case ETHTOOL_ID_ACTIVE:
|
||||
return 3; /* 3 On/Off cycles per second */
|
||||
|
||||
case ETHTOOL_ID_ON:
|
||||
return ops->dev_set_led(priv, KVASER_USB_LED_ON, 1000);
|
||||
|
||||
case ETHTOOL_ID_OFF:
|
||||
return ops->dev_set_led(priv, KVASER_USB_LED_OFF, 1000);
|
||||
|
||||
case ETHTOOL_ID_INACTIVE:
|
||||
/* Turn LED off and restore standard function after 1ms */
|
||||
return ops->dev_set_led(priv, KVASER_USB_LED_OFF, 1);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct net_device_ops kvaser_usb_netdev_ops = {
|
||||
.ndo_open = kvaser_usb_open,
|
||||
.ndo_stop = kvaser_usb_close,
|
||||
@@ -763,30 +791,35 @@ static const struct net_device_ops kvaser_usb_netdev_ops = {
|
||||
|
||||
static const struct ethtool_ops kvaser_usb_ethtool_ops = {
|
||||
.get_ts_info = can_ethtool_op_get_ts_info_hwts,
|
||||
.set_phys_id = kvaser_usb_set_phys_id,
|
||||
};
|
||||
|
||||
static void kvaser_usb_remove_interfaces(struct kvaser_usb *dev)
|
||||
{
|
||||
const struct kvaser_usb_dev_ops *ops = dev->driver_info->ops;
|
||||
int i;
|
||||
struct kvaser_usb_net_priv *priv;
|
||||
|
||||
for (i = 0; i < dev->nchannels; i++) {
|
||||
if (!dev->nets[i])
|
||||
priv = dev->nets[i];
|
||||
if (!priv)
|
||||
continue;
|
||||
|
||||
unregister_candev(dev->nets[i]->netdev);
|
||||
unregister_candev(priv->netdev);
|
||||
}
|
||||
|
||||
kvaser_usb_unlink_all_urbs(dev);
|
||||
|
||||
for (i = 0; i < dev->nchannels; i++) {
|
||||
if (!dev->nets[i])
|
||||
priv = dev->nets[i];
|
||||
if (!priv)
|
||||
continue;
|
||||
|
||||
if (ops->dev_remove_channel)
|
||||
ops->dev_remove_channel(dev->nets[i]);
|
||||
ops->dev_remove_channel(priv);
|
||||
|
||||
free_candev(dev->nets[i]->netdev);
|
||||
kvaser_usb_devlink_port_unregister(priv);
|
||||
free_candev(priv->netdev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,26 +885,35 @@ static int kvaser_usb_init_one(struct kvaser_usb *dev, int channel)
|
||||
netdev->ethtool_ops = &kvaser_usb_ethtool_ops;
|
||||
SET_NETDEV_DEV(netdev, &dev->intf->dev);
|
||||
netdev->dev_id = channel;
|
||||
netdev->dev_port = channel;
|
||||
|
||||
dev->nets[channel] = priv;
|
||||
|
||||
if (ops->dev_init_channel) {
|
||||
err = ops->dev_init_channel(priv);
|
||||
if (err)
|
||||
goto err;
|
||||
goto candev_free;
|
||||
}
|
||||
|
||||
err = kvaser_usb_devlink_port_register(priv);
|
||||
if (err) {
|
||||
dev_err(&dev->intf->dev, "Failed to register devlink port\n");
|
||||
goto candev_free;
|
||||
}
|
||||
|
||||
err = register_candev(netdev);
|
||||
if (err) {
|
||||
dev_err(&dev->intf->dev, "Failed to register CAN device\n");
|
||||
goto err;
|
||||
goto unregister_devlink_port;
|
||||
}
|
||||
|
||||
netdev_dbg(netdev, "device registered\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
unregister_devlink_port:
|
||||
kvaser_usb_devlink_port_unregister(priv);
|
||||
candev_free:
|
||||
free_candev(netdev);
|
||||
dev->nets[channel] = NULL;
|
||||
return err;
|
||||
@@ -881,6 +923,7 @@ static int kvaser_usb_probe(struct usb_interface *intf,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct kvaser_usb *dev;
|
||||
struct devlink *devlink;
|
||||
int err;
|
||||
int i;
|
||||
const struct kvaser_usb_driver_info *driver_info;
|
||||
@@ -890,17 +933,20 @@ static int kvaser_usb_probe(struct usb_interface *intf,
|
||||
if (!driver_info)
|
||||
return -ENODEV;
|
||||
|
||||
dev = devm_kzalloc(&intf->dev, sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
devlink = devlink_alloc(&kvaser_usb_devlink_ops, sizeof(*dev), &intf->dev);
|
||||
if (!devlink)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = devlink_priv(devlink);
|
||||
dev->intf = intf;
|
||||
dev->driver_info = driver_info;
|
||||
ops = driver_info->ops;
|
||||
|
||||
err = ops->dev_setup_endpoints(dev);
|
||||
if (err)
|
||||
return dev_err_probe(&intf->dev, err, "Cannot get usb endpoint(s)");
|
||||
if (err) {
|
||||
dev_err_probe(&intf->dev, err, "Cannot get usb endpoint(s)");
|
||||
goto free_devlink;
|
||||
}
|
||||
|
||||
dev->udev = interface_to_usbdev(intf);
|
||||
|
||||
@@ -911,55 +957,66 @@ static int kvaser_usb_probe(struct usb_interface *intf,
|
||||
dev->card_data.ctrlmode_supported = 0;
|
||||
dev->card_data.capabilities = 0;
|
||||
err = ops->dev_init_card(dev);
|
||||
if (err)
|
||||
return dev_err_probe(&intf->dev, err,
|
||||
"Failed to initialize card\n");
|
||||
if (err) {
|
||||
dev_err_probe(&intf->dev, err,
|
||||
"Failed to initialize card\n");
|
||||
goto free_devlink;
|
||||
}
|
||||
|
||||
err = ops->dev_get_software_info(dev);
|
||||
if (err)
|
||||
return dev_err_probe(&intf->dev, err,
|
||||
"Cannot get software info\n");
|
||||
if (err) {
|
||||
dev_err_probe(&intf->dev, err,
|
||||
"Cannot get software info\n");
|
||||
goto free_devlink;
|
||||
}
|
||||
|
||||
if (ops->dev_get_software_details) {
|
||||
err = ops->dev_get_software_details(dev);
|
||||
if (err)
|
||||
return dev_err_probe(&intf->dev, err,
|
||||
"Cannot get software details\n");
|
||||
if (err) {
|
||||
dev_err_probe(&intf->dev, err,
|
||||
"Cannot get software details\n");
|
||||
goto free_devlink;
|
||||
}
|
||||
}
|
||||
|
||||
if (WARN_ON(!dev->cfg))
|
||||
return -ENODEV;
|
||||
|
||||
dev_dbg(&intf->dev, "Firmware version: %d.%d.%d\n",
|
||||
((dev->fw_version >> 24) & 0xff),
|
||||
((dev->fw_version >> 16) & 0xff),
|
||||
(dev->fw_version & 0xffff));
|
||||
if (WARN_ON(!dev->cfg)) {
|
||||
err = -ENODEV;
|
||||
goto free_devlink;
|
||||
}
|
||||
|
||||
dev_dbg(&intf->dev, "Max outstanding tx = %d URBs\n", dev->max_tx_urbs);
|
||||
|
||||
err = ops->dev_get_card_info(dev);
|
||||
if (err)
|
||||
return dev_err_probe(&intf->dev, err,
|
||||
"Cannot get card info\n");
|
||||
if (err) {
|
||||
dev_err_probe(&intf->dev, err,
|
||||
"Cannot get card info\n");
|
||||
goto free_devlink;
|
||||
}
|
||||
|
||||
if (ops->dev_get_capabilities) {
|
||||
err = ops->dev_get_capabilities(dev);
|
||||
if (err) {
|
||||
kvaser_usb_remove_interfaces(dev);
|
||||
return dev_err_probe(&intf->dev, err,
|
||||
"Cannot get capabilities\n");
|
||||
dev_err_probe(&intf->dev, err,
|
||||
"Cannot get capabilities\n");
|
||||
goto remove_interfaces;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->nchannels; i++) {
|
||||
err = kvaser_usb_init_one(dev, i);
|
||||
if (err) {
|
||||
kvaser_usb_remove_interfaces(dev);
|
||||
return err;
|
||||
}
|
||||
if (err)
|
||||
goto remove_interfaces;
|
||||
}
|
||||
devlink_register(devlink);
|
||||
|
||||
return 0;
|
||||
|
||||
remove_interfaces:
|
||||
kvaser_usb_remove_interfaces(dev);
|
||||
free_devlink:
|
||||
devlink_free(devlink);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void kvaser_usb_disconnect(struct usb_interface *intf)
|
||||
@@ -972,6 +1029,8 @@ static void kvaser_usb_disconnect(struct usb_interface *intf)
|
||||
return;
|
||||
|
||||
kvaser_usb_remove_interfaces(dev);
|
||||
devlink_unregister(priv_to_devlink(dev));
|
||||
devlink_free(priv_to_devlink(dev));
|
||||
}
|
||||
|
||||
static struct usb_driver kvaser_usb_driver = {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* kvaser_usb devlink functions
|
||||
*
|
||||
* Copyright (C) 2025 KVASER AB, Sweden. All rights reserved.
|
||||
*/
|
||||
#include "kvaser_usb.h"
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <net/devlink.h>
|
||||
|
||||
#define KVASER_USB_EAN_MSB 0x00073301
|
||||
|
||||
static int kvaser_usb_devlink_info_get(struct devlink *devlink,
|
||||
struct devlink_info_req *req,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct kvaser_usb *dev = devlink_priv(devlink);
|
||||
char buf[] = "73301XXXXXXXXXX";
|
||||
int ret;
|
||||
|
||||
if (dev->serial_number) {
|
||||
snprintf(buf, sizeof(buf), "%u", dev->serial_number);
|
||||
ret = devlink_info_serial_number_put(req, buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (dev->fw_version.major) {
|
||||
snprintf(buf, sizeof(buf), "%u.%u.%u",
|
||||
dev->fw_version.major,
|
||||
dev->fw_version.minor,
|
||||
dev->fw_version.build);
|
||||
ret = devlink_info_version_running_put(req,
|
||||
DEVLINK_INFO_VERSION_GENERIC_FW,
|
||||
buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (dev->hw_revision) {
|
||||
snprintf(buf, sizeof(buf), "%u", dev->hw_revision);
|
||||
ret = devlink_info_version_fixed_put(req,
|
||||
DEVLINK_INFO_VERSION_GENERIC_BOARD_REV,
|
||||
buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (dev->ean[1] == KVASER_USB_EAN_MSB) {
|
||||
snprintf(buf, sizeof(buf), "%x%08x", dev->ean[1], dev->ean[0]);
|
||||
ret = devlink_info_version_fixed_put(req,
|
||||
DEVLINK_INFO_VERSION_GENERIC_BOARD_ID,
|
||||
buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct devlink_ops kvaser_usb_devlink_ops = {
|
||||
.info_get = kvaser_usb_devlink_info_get,
|
||||
};
|
||||
|
||||
int kvaser_usb_devlink_port_register(struct kvaser_usb_net_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
struct devlink_port_attrs attrs = {
|
||||
.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL,
|
||||
.phys.port_number = priv->channel,
|
||||
};
|
||||
devlink_port_attrs_set(&priv->devlink_port, &attrs);
|
||||
|
||||
ret = devlink_port_register(priv_to_devlink(priv->dev),
|
||||
&priv->devlink_port, priv->channel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
SET_NETDEV_DEVLINK_PORT(priv->netdev, &priv->devlink_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void kvaser_usb_devlink_port_unregister(struct kvaser_usb_net_priv *priv)
|
||||
{
|
||||
devlink_port_unregister(&priv->devlink_port);
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
* distinguish between ERROR_WARNING and ERROR_ACTIVE.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gfp.h>
|
||||
@@ -67,6 +68,8 @@ static const struct kvaser_usb_dev_cfg kvaser_usb_hydra_dev_cfg_rt;
|
||||
#define CMD_SET_BUSPARAMS_RESP 85
|
||||
#define CMD_GET_CAPABILITIES_REQ 95
|
||||
#define CMD_GET_CAPABILITIES_RESP 96
|
||||
#define CMD_LED_ACTION_REQ 101
|
||||
#define CMD_LED_ACTION_RESP 102
|
||||
#define CMD_RX_MESSAGE 106
|
||||
#define CMD_MAP_CHANNEL_REQ 200
|
||||
#define CMD_MAP_CHANNEL_RESP 201
|
||||
@@ -111,7 +114,7 @@ struct kvaser_cmd_card_info {
|
||||
__le32 clock_res;
|
||||
__le32 mfg_date;
|
||||
__le32 ean[2];
|
||||
u8 hw_version;
|
||||
u8 hw_revision;
|
||||
u8 usb_mode;
|
||||
u8 hw_type;
|
||||
u8 reserved0;
|
||||
@@ -217,6 +220,22 @@ struct kvaser_cmd_get_busparams_res {
|
||||
u8 reserved[20];
|
||||
} __packed;
|
||||
|
||||
/* The device has two LEDs per CAN channel
|
||||
* The LSB of action field controls the state:
|
||||
* 0 = ON
|
||||
* 1 = OFF
|
||||
* The remaining bits of action field is the LED index
|
||||
*/
|
||||
#define KVASER_USB_HYDRA_LED_IDX_MASK GENMASK(31, 1)
|
||||
#define KVASER_USB_HYDRA_LED_YELLOW_CH0_IDX 3
|
||||
#define KVASER_USB_HYDRA_LEDS_PER_CHANNEL 2
|
||||
struct kvaser_cmd_led_action_req {
|
||||
u8 action;
|
||||
u8 padding;
|
||||
__le16 duration_ms;
|
||||
u8 reserved[24];
|
||||
} __packed;
|
||||
|
||||
/* Ctrl modes */
|
||||
#define KVASER_USB_HYDRA_CTRLMODE_NORMAL 0x01
|
||||
#define KVASER_USB_HYDRA_CTRLMODE_LISTEN 0x02
|
||||
@@ -299,6 +318,8 @@ struct kvaser_cmd {
|
||||
struct kvaser_cmd_get_busparams_req get_busparams_req;
|
||||
struct kvaser_cmd_get_busparams_res get_busparams_res;
|
||||
|
||||
struct kvaser_cmd_led_action_req led_action_req;
|
||||
|
||||
struct kvaser_cmd_chip_state_event chip_state_event;
|
||||
|
||||
struct kvaser_cmd_set_ctrlmode set_ctrlmode;
|
||||
@@ -1390,6 +1411,7 @@ static void kvaser_usb_hydra_handle_cmd_std(const struct kvaser_usb *dev,
|
||||
/* Ignored commands */
|
||||
case CMD_SET_BUSPARAMS_RESP:
|
||||
case CMD_SET_BUSPARAMS_FD_RESP:
|
||||
case CMD_LED_ACTION_RESP:
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1817,6 +1839,7 @@ static int kvaser_usb_hydra_get_software_details(struct kvaser_usb *dev)
|
||||
size_t cmd_len;
|
||||
int err;
|
||||
u32 flags;
|
||||
u32 fw_version;
|
||||
struct kvaser_usb_dev_card_data *card_data = &dev->card_data;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
@@ -1841,7 +1864,10 @@ static int kvaser_usb_hydra_get_software_details(struct kvaser_usb *dev)
|
||||
if (err)
|
||||
goto end;
|
||||
|
||||
dev->fw_version = le32_to_cpu(cmd->sw_detail_res.sw_version);
|
||||
fw_version = le32_to_cpu(cmd->sw_detail_res.sw_version);
|
||||
dev->fw_version.major = FIELD_GET(KVASER_USB_SW_VERSION_MAJOR_MASK, fw_version);
|
||||
dev->fw_version.minor = FIELD_GET(KVASER_USB_SW_VERSION_MINOR_MASK, fw_version);
|
||||
dev->fw_version.build = FIELD_GET(KVASER_USB_SW_VERSION_BUILD_MASK, fw_version);
|
||||
flags = le32_to_cpu(cmd->sw_detail_res.sw_flags);
|
||||
|
||||
if (flags & KVASER_USB_HYDRA_SW_FLAG_FW_BAD) {
|
||||
@@ -1892,6 +1918,10 @@ static int kvaser_usb_hydra_get_card_info(struct kvaser_usb *dev)
|
||||
err = kvaser_usb_hydra_wait_cmd(dev, CMD_GET_CARD_INFO_RESP, &cmd);
|
||||
if (err)
|
||||
return err;
|
||||
dev->ean[1] = le32_to_cpu(cmd.card_info.ean[1]);
|
||||
dev->ean[0] = le32_to_cpu(cmd.card_info.ean[0]);
|
||||
dev->serial_number = le32_to_cpu(cmd.card_info.serial_number);
|
||||
dev->hw_revision = cmd.card_info.hw_revision;
|
||||
|
||||
dev->nchannels = cmd.card_info.nchannels;
|
||||
if (dev->nchannels > KVASER_USB_MAX_NET_DEVICES)
|
||||
@@ -1946,6 +1976,36 @@ static int kvaser_usb_hydra_get_capabilities(struct kvaser_usb *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kvaser_usb_hydra_set_led(struct kvaser_usb_net_priv *priv,
|
||||
enum kvaser_usb_led_state state,
|
||||
u16 duration_ms)
|
||||
{
|
||||
struct kvaser_usb *dev = priv->dev;
|
||||
struct kvaser_cmd *cmd;
|
||||
size_t cmd_len;
|
||||
int ret;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (!cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
cmd->header.cmd_no = CMD_LED_ACTION_REQ;
|
||||
cmd_len = kvaser_usb_hydra_cmd_size(cmd);
|
||||
kvaser_usb_hydra_set_cmd_dest_he(cmd, dev->card_data.hydra.sysdbg_he);
|
||||
kvaser_usb_hydra_set_cmd_transid(cmd, kvaser_usb_hydra_get_next_transid(dev));
|
||||
|
||||
cmd->led_action_req.duration_ms = cpu_to_le16(duration_ms);
|
||||
cmd->led_action_req.action = state |
|
||||
FIELD_PREP(KVASER_USB_HYDRA_LED_IDX_MASK,
|
||||
KVASER_USB_HYDRA_LED_YELLOW_CH0_IDX +
|
||||
KVASER_USB_HYDRA_LEDS_PER_CHANNEL * priv->channel);
|
||||
|
||||
ret = kvaser_usb_send_cmd(dev, cmd, cmd_len);
|
||||
kfree(cmd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int kvaser_usb_hydra_set_opt_mode(const struct kvaser_usb_net_priv *priv)
|
||||
{
|
||||
struct kvaser_usb *dev = priv->dev;
|
||||
@@ -2149,6 +2209,7 @@ const struct kvaser_usb_dev_ops kvaser_usb_hydra_dev_ops = {
|
||||
.dev_get_software_details = kvaser_usb_hydra_get_software_details,
|
||||
.dev_get_card_info = kvaser_usb_hydra_get_card_info,
|
||||
.dev_get_capabilities = kvaser_usb_hydra_get_capabilities,
|
||||
.dev_set_led = kvaser_usb_hydra_set_led,
|
||||
.dev_set_opt_mode = kvaser_usb_hydra_set_opt_mode,
|
||||
.dev_start_chip = kvaser_usb_hydra_start_chip,
|
||||
.dev_stop_chip = kvaser_usb_hydra_stop_chip,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* Copyright (C) 2015 Valeo S.A.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gfp.h>
|
||||
@@ -81,6 +82,8 @@
|
||||
#define CMD_FLUSH_QUEUE_REPLY 68
|
||||
#define CMD_GET_CAPABILITIES_REQ 95
|
||||
#define CMD_GET_CAPABILITIES_RESP 96
|
||||
#define CMD_LED_ACTION_REQ 101
|
||||
#define CMD_LED_ACTION_RESP 102
|
||||
|
||||
#define CMD_LEAF_LOG_MESSAGE 106
|
||||
|
||||
@@ -135,7 +138,7 @@ struct kvaser_cmd_cardinfo {
|
||||
__le32 padding0;
|
||||
__le32 clock_resolution;
|
||||
__le32 mfgdate;
|
||||
u8 ean[8];
|
||||
__le32 ean[2];
|
||||
u8 hw_revision;
|
||||
union {
|
||||
struct {
|
||||
@@ -173,6 +176,21 @@ struct kvaser_cmd_busparams {
|
||||
struct kvaser_usb_busparams busparams;
|
||||
} __packed;
|
||||
|
||||
/* The device has one LED per CAN channel
|
||||
* The LSB of action field controls the state:
|
||||
* 0 = ON
|
||||
* 1 = OFF
|
||||
* The remaining bits of action field is the LED index
|
||||
*/
|
||||
#define KVASER_USB_LEAF_LED_IDX_MASK GENMASK(31, 1)
|
||||
#define KVASER_USB_LEAF_LED_YELLOW_CH0_IDX 2
|
||||
struct kvaser_cmd_led_action_req {
|
||||
u8 tid;
|
||||
u8 action;
|
||||
__le16 duration_ms;
|
||||
u8 padding[24];
|
||||
} __packed;
|
||||
|
||||
struct kvaser_cmd_tx_can {
|
||||
u8 channel;
|
||||
u8 tid;
|
||||
@@ -359,6 +377,8 @@ struct kvaser_cmd {
|
||||
struct kvaser_cmd_cardinfo cardinfo;
|
||||
struct kvaser_cmd_busparams busparams;
|
||||
|
||||
struct kvaser_cmd_led_action_req led_action_req;
|
||||
|
||||
struct kvaser_cmd_rx_can_header rx_can_header;
|
||||
struct kvaser_cmd_tx_acknowledge_header tx_acknowledge_header;
|
||||
|
||||
@@ -409,6 +429,7 @@ static const u8 kvaser_usb_leaf_cmd_sizes_leaf[] = {
|
||||
[CMD_ERROR_EVENT] = kvaser_fsize(u.leaf.error_event),
|
||||
/* ignored events: */
|
||||
[CMD_FLUSH_QUEUE_REPLY] = CMD_SIZE_ANY,
|
||||
[CMD_LED_ACTION_RESP] = CMD_SIZE_ANY,
|
||||
};
|
||||
|
||||
static const u8 kvaser_usb_leaf_cmd_sizes_usbcan[] = {
|
||||
@@ -423,6 +444,8 @@ static const u8 kvaser_usb_leaf_cmd_sizes_usbcan[] = {
|
||||
[CMD_CAN_ERROR_EVENT] = kvaser_fsize(u.usbcan.can_error_event),
|
||||
[CMD_ERROR_EVENT] = kvaser_fsize(u.usbcan.error_event),
|
||||
[CMD_USBCAN_CLOCK_OVERFLOW_EVENT] = kvaser_fsize(u.usbcan.clk_overflow_event),
|
||||
/* ignored events: */
|
||||
[CMD_LED_ACTION_RESP] = CMD_SIZE_ANY,
|
||||
};
|
||||
|
||||
/* Summary of a kvaser error event, for a unified Leaf/Usbcan error
|
||||
@@ -718,9 +741,13 @@ static int kvaser_usb_leaf_send_simple_cmd(const struct kvaser_usb *dev,
|
||||
static void kvaser_usb_leaf_get_software_info_leaf(struct kvaser_usb *dev,
|
||||
const struct leaf_cmd_softinfo *softinfo)
|
||||
{
|
||||
u32 fw_version;
|
||||
u32 sw_options = le32_to_cpu(softinfo->sw_options);
|
||||
|
||||
dev->fw_version = le32_to_cpu(softinfo->fw_version);
|
||||
fw_version = le32_to_cpu(softinfo->fw_version);
|
||||
dev->fw_version.major = FIELD_GET(KVASER_USB_SW_VERSION_MAJOR_MASK, fw_version);
|
||||
dev->fw_version.minor = FIELD_GET(KVASER_USB_SW_VERSION_MINOR_MASK, fw_version);
|
||||
dev->fw_version.build = FIELD_GET(KVASER_USB_SW_VERSION_BUILD_MASK, fw_version);
|
||||
dev->max_tx_urbs = le16_to_cpu(softinfo->max_outstanding_tx);
|
||||
|
||||
if (sw_options & KVASER_USB_LEAF_SWOPTION_EXT_CAP)
|
||||
@@ -761,6 +788,7 @@ static int kvaser_usb_leaf_get_software_info_inner(struct kvaser_usb *dev)
|
||||
{
|
||||
struct kvaser_cmd cmd;
|
||||
int err;
|
||||
u32 fw_version;
|
||||
|
||||
err = kvaser_usb_leaf_send_simple_cmd(dev, CMD_GET_SOFTWARE_INFO, 0);
|
||||
if (err)
|
||||
@@ -775,7 +803,13 @@ static int kvaser_usb_leaf_get_software_info_inner(struct kvaser_usb *dev)
|
||||
kvaser_usb_leaf_get_software_info_leaf(dev, &cmd.u.leaf.softinfo);
|
||||
break;
|
||||
case KVASER_USBCAN:
|
||||
dev->fw_version = le32_to_cpu(cmd.u.usbcan.softinfo.fw_version);
|
||||
fw_version = le32_to_cpu(cmd.u.usbcan.softinfo.fw_version);
|
||||
dev->fw_version.major = FIELD_GET(KVASER_USB_SW_VERSION_MAJOR_MASK,
|
||||
fw_version);
|
||||
dev->fw_version.minor = FIELD_GET(KVASER_USB_SW_VERSION_MINOR_MASK,
|
||||
fw_version);
|
||||
dev->fw_version.build = FIELD_GET(KVASER_USB_SW_VERSION_BUILD_MASK,
|
||||
fw_version);
|
||||
dev->max_tx_urbs =
|
||||
le16_to_cpu(cmd.u.usbcan.softinfo.max_outstanding_tx);
|
||||
dev->cfg = &kvaser_usb_leaf_usbcan_dev_cfg;
|
||||
@@ -820,6 +854,10 @@ static int kvaser_usb_leaf_get_card_info(struct kvaser_usb *dev)
|
||||
(dev->driver_info->family == KVASER_USBCAN &&
|
||||
dev->nchannels > MAX_USBCAN_NET_DEVICES))
|
||||
return -EINVAL;
|
||||
dev->ean[1] = le32_to_cpu(cmd.u.cardinfo.ean[1]);
|
||||
dev->ean[0] = le32_to_cpu(cmd.u.cardinfo.ean[0]);
|
||||
dev->serial_number = le32_to_cpu(cmd.u.cardinfo.serial_number);
|
||||
dev->hw_revision = cmd.u.cardinfo.hw_revision;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -924,6 +962,34 @@ static int kvaser_usb_leaf_get_capabilities_leaf(struct kvaser_usb *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kvaser_usb_leaf_set_led(struct kvaser_usb_net_priv *priv,
|
||||
enum kvaser_usb_led_state state,
|
||||
u16 duration_ms)
|
||||
{
|
||||
struct kvaser_usb *dev = priv->dev;
|
||||
struct kvaser_cmd *cmd;
|
||||
int ret;
|
||||
|
||||
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
|
||||
if (!cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
cmd->id = CMD_LED_ACTION_REQ;
|
||||
cmd->len = CMD_HEADER_LEN + sizeof(struct kvaser_cmd_led_action_req);
|
||||
cmd->u.led_action_req.tid = 0xff;
|
||||
|
||||
cmd->u.led_action_req.duration_ms = cpu_to_le16(duration_ms);
|
||||
cmd->u.led_action_req.action = state |
|
||||
FIELD_PREP(KVASER_USB_LEAF_LED_IDX_MASK,
|
||||
KVASER_USB_LEAF_LED_YELLOW_CH0_IDX +
|
||||
priv->channel);
|
||||
|
||||
ret = kvaser_usb_send_cmd(dev, cmd, cmd->len);
|
||||
kfree(cmd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int kvaser_usb_leaf_get_capabilities(struct kvaser_usb *dev)
|
||||
{
|
||||
int err = 0;
|
||||
@@ -1638,6 +1704,8 @@ static void kvaser_usb_leaf_handle_command(struct kvaser_usb *dev,
|
||||
if (dev->driver_info->family != KVASER_LEAF)
|
||||
goto warn;
|
||||
break;
|
||||
case CMD_LED_ACTION_RESP:
|
||||
break;
|
||||
|
||||
default:
|
||||
warn: dev_warn(&dev->intf->dev, "Unhandled command (%d)\n", cmd->id);
|
||||
@@ -1927,6 +1995,7 @@ const struct kvaser_usb_dev_ops kvaser_usb_leaf_dev_ops = {
|
||||
.dev_get_software_details = NULL,
|
||||
.dev_get_card_info = kvaser_usb_leaf_get_card_info,
|
||||
.dev_get_capabilities = kvaser_usb_leaf_get_capabilities,
|
||||
.dev_set_led = kvaser_usb_leaf_set_led,
|
||||
.dev_set_opt_mode = kvaser_usb_leaf_set_opt_mode,
|
||||
.dev_start_chip = kvaser_usb_leaf_start_chip,
|
||||
.dev_stop_chip = kvaser_usb_leaf_stop_chip,
|
||||
|
||||
Reference in New Issue
Block a user