[PATCH 3/4] can: usb: ixxat_usb: add CL2/V2 protocol layer with CAN

Stéphane Grosjean <[email protected]> Thu, 23 Jul 2026 09:29:38 +0200
Newsgroups org.kernel.vger.linux-can
Message-ID <[email protected]>
From: Stéphane Grosjean <[email protected]>

Add ixxat_usb_cl2.c, which implements the CL2 transfer protocol
(V2 communication layer) used by all CAN FD capable IXXAT adapters and
by USB-to-CAN V2 classic-only devices whose firmware version is >= 1.7.

Three adapter descriptors are exported:

  usb2can_fd   – CAN FD adapters (USB-to-CAN FD Compact/Pro/Automotive/
                 MiniPCIe, USB-to-CAN/FD Pro/Standard and variants,
                 USB-to-CAR).  Supports ISO and non-ISO CAN FD with
                 independent arbitration and data-phase bittiming.

  usb2can_v2   – USB-to-CAN V2 (classic CAN 2.0b only via CL2).

  can_fd_idm   – CAN IDM101/IDM200 modules with dedicated USB endpoints
                 per channel and a wider bittiming range.

The init command carries two sets of bittiming parameters (nominal and
data-phase) and flags for listen-only, triple-sampling, loopback,
bus-error reporting, CAN FD (ISO) and non-ISO CAN FD.

Supported control modes (FD adapters): listen-only, loopback,
triple-sampling, bus-error reporting, CAN FD, non-ISO CAN FD.

Signed-off-by: Stéphane Grosjean <[email protected]>
---
 drivers/net/can/usb/ixxat_usb/ixxat_usb_cl2.c | 347 ++++++++++++++++++
 1 file changed, 347 insertions(+)
 create mode 100644 drivers/net/can/usb/ixxat_usb/ixxat_usb_cl2.c

diff --git a/drivers/net/can/usb/ixxat_usb/ixxat_usb_cl2.c b/drivers/net/can/usb/ixxat_usb/ixxat_usb_cl2.c
new file mode 100644
index 000000000000..6a56a2bbdbbf
--- /dev/null
+++ b/drivers/net/can/usb/ixxat_usb/ixxat_usb_cl2.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/* CAN driver adapter for IXXAT USB-to-CAN CL2
+ *
+ * Copyright (C) 2018-2024 HMS Industrial Networks <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+#include <linux/can/dev.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+
+#include "ixxat_usb_core.h"
+
+#define IXXAT_USB_CLOCK			80000000
+#define IXXAT_USBV2_CLOCK		36000000
+
+#define IXXAT_USBFD_BUFFER_SIZE_RX	512
+#define IXXAT_USBFD_BUFFER_SIZE_TX	512
+
+#define IXXAT_USBV2_BUFFER_SIZE_RX	512
+#define IXXAT_USBV2_BUFFER_SIZE_TX	512
+
+#define IXXAT_USBIDM_BUFFER_SIZE_RX	512
+#define IXXAT_USBIDM_BUFFER_SIZE_TX	512
+
+#define IXXAT_USB_MODES_FD		(CAN_CTRLMODE_LISTENONLY | \
+					 CAN_CTRLMODE_3_SAMPLES | \
+					 CAN_CTRLMODE_LOOPBACK | \
+					 CAN_CTRLMODE_BERR_REPORTING | \
+					 CAN_CTRLMODE_FD | \
+					 CAN_CTRLMODE_FD_NON_ISO)
+
+#define IXXAT_USB_MODES			(CAN_CTRLMODE_LISTENONLY | \
+					 CAN_CTRLMODE_3_SAMPLES | \
+					 CAN_CTRLMODE_LOOPBACK | \
+					 CAN_CTRLMODE_BERR_REPORTING)
+
+/* bittiming parameters USB-to-CAN FD */
+#define IXXAT_USB2CANFD_TSEG1_MIN	1
+#define IXXAT_USB2CANFD_TSEG1_MAX	256
+#define IXXAT_USB2CANFD_TSEG2_MIN	1
+#define IXXAT_USB2CANFD_TSEG2_MAX	256
+#define IXXAT_USB2CANFD_SJW_MAX		128
+#define IXXAT_USB2CANFD_BRP_MIN		2
+#define IXXAT_USB2CANFD_BRP_MAX		513
+#define IXXAT_USB2CANFD_BRP_INC		1
+
+#define IXXAT_USB2CANFD_TSEG1_MIN_DATA	1
+#define IXXAT_USB2CANFD_TSEG1_MAX_DATA	256
+#define IXXAT_USB2CANFD_TSEG2_MIN_DATA	1
+#define IXXAT_USB2CANFD_TSEG2_MAX_DATA	256
+#define IXXAT_USB2CANFD_SJW_MAX_DATA	128
+#define IXXAT_USB2CANFD_BRP_MIN_DATA	2
+#define IXXAT_USB2CANFD_BRP_MAX_DATA	513
+#define IXXAT_USB2CANFD_BRP_INC_DATA	1
+
+/* bittiming parameters USB-to-CAN V2 */
+#define IXXAT_USB2CANV2_TSEG1_MIN	1
+#define IXXAT_USB2CANV2_TSEG1_MAX	16
+#define IXXAT_USB2CANV2_TSEG2_MIN	1
+#define IXXAT_USB2CANV2_TSEG2_MAX	8
+#define IXXAT_USB2CANV2_SJW_MAX		4
+#define IXXAT_USB2CANV2_BRP_MIN		1
+#define IXXAT_USB2CANV2_BRP_MAX		1024
+#define IXXAT_USB2CANV2_BRP_INC		1
+
+/* bittiming parameters CAN IDM */
+#define IXXAT_CANIDM_TSEG1_MIN		1
+#define IXXAT_CANIDM_TSEG1_MAX		256
+#define IXXAT_CANIDM_TSEG2_MIN		1
+#define IXXAT_CANIDM_TSEG2_MAX		128
+#define IXXAT_CANIDM_SJW_MAX		128
+#define IXXAT_CANIDM_BRP_MIN		1
+#define IXXAT_CANIDM_BRP_MAX		512
+#define IXXAT_CANIDM_BRP_INC		1
+
+#define IXXAT_CANIDM_TSEG1_MIN_DATA	1
+#define IXXAT_CANIDM_TSEG1_MAX_DATA	32
+#define IXXAT_CANIDM_TSEG2_MIN_DATA	1
+#define IXXAT_CANIDM_TSEG2_MAX_DATA	16
+#define IXXAT_CANIDM_SJW_MAX_DATA	8
+#define IXXAT_CANIDM_BRP_MIN_DATA	1
+#define IXXAT_CANIDM_BRP_MAX_DATA	32
+#define IXXAT_CANIDM_BRP_INC_DATA	1
+
+/* USB endpoint mapping for USB-to-CAN V2 */
+#define IXXAT_USB2CANV2_EP1_IN		(1 | USB_DIR_IN)
+#define IXXAT_USB2CANV2_EP2_IN		(2 | USB_DIR_IN)
+#define IXXAT_USB2CANV2_EP3_IN		(3 | USB_DIR_IN)
+#define IXXAT_USB2CANV2_EP4_IN		(4 | USB_DIR_IN)
+#define IXXAT_USB2CANV2_EP5_IN		(5 | USB_DIR_IN)
+
+#define IXXAT_USB2CANV2_EP1_OUT		(1 | USB_DIR_OUT)
+#define IXXAT_USB2CANV2_EP2_OUT		(2 | USB_DIR_OUT)
+#define IXXAT_USB2CANV2_EP3_OUT		(3 | USB_DIR_OUT)
+#define IXXAT_USB2CANV2_EP4_OUT		(4 | USB_DIR_OUT)
+#define IXXAT_USB2CANV2_EP5_OUT		(5 | USB_DIR_OUT)
+
+/* USB endpoint mapping for USB-to-CAN FD */
+#define IXXAT_USB2CANFD_EP1_IN		(1 | USB_DIR_IN)
+#define IXXAT_USB2CANFD_EP2_IN		(2 | USB_DIR_IN)
+#define IXXAT_USB2CANFD_EP3_IN		(3 | USB_DIR_IN)
+#define IXXAT_USB2CANFD_EP4_IN		(4 | USB_DIR_IN)
+#define IXXAT_USB2CANFD_EP5_IN		(5 | USB_DIR_IN)
+
+#define IXXAT_USB2CANFD_EP1_OUT		(1 | USB_DIR_OUT)
+#define IXXAT_USB2CANFD_EP2_OUT		(2 | USB_DIR_OUT)
+#define IXXAT_USB2CANFD_EP3_OUT		(3 | USB_DIR_OUT)
+#define IXXAT_USB2CANFD_EP4_OUT		(4 | USB_DIR_OUT)
+#define IXXAT_USB2CANFD_EP5_OUT		(5 | USB_DIR_OUT)
+
+/* USB endpoint mapping for CAN IDM */
+#define IXXAT_CANIDM_EP1_IN		(2 | USB_DIR_IN)
+#define IXXAT_CANIDM_EP2_IN		(4 | USB_DIR_IN)
+#define IXXAT_CANIDM_EP3_IN		(6 | USB_DIR_IN)
+#define IXXAT_CANIDM_EP4_IN		(8 | USB_DIR_IN)
+#define IXXAT_CANIDM_EP5_IN		(10 | USB_DIR_IN)
+
+#define IXXAT_CANIDM_EP1_OUT		(1 | USB_DIR_OUT)
+#define IXXAT_CANIDM_EP2_OUT		(3 | USB_DIR_OUT)
+#define IXXAT_CANIDM_EP3_OUT		(5 | USB_DIR_OUT)
+#define IXXAT_CANIDM_EP4_OUT		(7 | USB_DIR_OUT)
+#define IXXAT_CANIDM_EP5_OUT		(9 | USB_DIR_OUT)
+
+#define IXXAT_USB_CAN_CMD_GETCAPS2	0x335
+#define IXXAT_USB_CAN_CMD_INIT2		0x337
+
+static const struct can_bittiming_const usb2can_v2_bt = {
+	.name = KBUILD_MODNAME,
+	.tseg1_min = IXXAT_USB2CANV2_TSEG1_MIN,
+	.tseg1_max = IXXAT_USB2CANV2_TSEG1_MAX,
+	.tseg2_min = IXXAT_USB2CANV2_TSEG2_MIN,
+	.tseg2_max = IXXAT_USB2CANV2_TSEG2_MAX,
+	.sjw_max = IXXAT_USB2CANV2_SJW_MAX,
+	.brp_min = IXXAT_USB2CANV2_BRP_MIN,
+	.brp_max = IXXAT_USB2CANV2_BRP_MAX,
+	.brp_inc = IXXAT_USB2CANV2_BRP_INC,
+};
+
+static const struct can_bittiming_const usb2can_fd_bt = {
+	.name = KBUILD_MODNAME,
+	.tseg1_min = IXXAT_USB2CANFD_TSEG1_MIN,
+	.tseg1_max = IXXAT_USB2CANFD_TSEG1_MAX,
+	.tseg2_min = IXXAT_USB2CANFD_TSEG2_MIN,
+	.tseg2_max = IXXAT_USB2CANFD_TSEG2_MAX,
+	.sjw_max = IXXAT_USB2CANFD_SJW_MAX,
+	.brp_min = IXXAT_USB2CANFD_BRP_MIN,
+	.brp_max = IXXAT_USB2CANFD_BRP_MAX,
+	.brp_inc = IXXAT_USB2CANFD_BRP_INC,
+};
+
+static const struct can_bittiming_const usb2can_fd_btd = {
+	.name = KBUILD_MODNAME,
+	.tseg1_min = IXXAT_USB2CANFD_TSEG1_MIN_DATA,
+	.tseg1_max = IXXAT_USB2CANFD_TSEG1_MAX_DATA,
+	.tseg2_min = IXXAT_USB2CANFD_TSEG2_MIN_DATA,
+	.tseg2_max = IXXAT_USB2CANFD_TSEG2_MAX_DATA,
+	.sjw_max = IXXAT_USB2CANFD_SJW_MAX_DATA,
+	.brp_min = IXXAT_USB2CANFD_BRP_MIN_DATA,
+	.brp_max = IXXAT_USB2CANFD_BRP_MAX_DATA,
+	.brp_inc = IXXAT_USB2CANFD_BRP_INC_DATA,
+};
+
+static const struct can_bittiming_const canidm_bt = {
+	.name = KBUILD_MODNAME,
+	.tseg1_min = IXXAT_CANIDM_TSEG1_MIN,
+	.tseg1_max = IXXAT_CANIDM_TSEG1_MAX,
+	.tseg2_min = IXXAT_CANIDM_TSEG2_MIN,
+	.tseg2_max = IXXAT_CANIDM_TSEG2_MAX,
+	.sjw_max = IXXAT_CANIDM_SJW_MAX,
+	.brp_min = IXXAT_CANIDM_BRP_MIN,
+	.brp_max = IXXAT_CANIDM_BRP_MAX,
+	.brp_inc = IXXAT_CANIDM_BRP_INC
+};
+
+static const struct can_bittiming_const canidm_btd = {
+	.name = KBUILD_MODNAME,
+	.tseg1_min = IXXAT_CANIDM_TSEG1_MIN_DATA,
+	.tseg1_max = IXXAT_CANIDM_TSEG1_MAX_DATA,
+	.tseg2_min = IXXAT_CANIDM_TSEG2_MIN_DATA,
+	.tseg2_max = IXXAT_CANIDM_TSEG2_MAX_DATA,
+	.sjw_max = IXXAT_CANIDM_SJW_MAX_DATA,
+	.brp_min = IXXAT_CANIDM_BRP_MIN_DATA,
+	.brp_max = IXXAT_CANIDM_BRP_MAX_DATA,
+	.brp_inc = IXXAT_CANIDM_BRP_INC_DATA
+};
+
+static int ixxat_usb_get_ctrl_caps(struct ixxat_usb_candevice *dev,
+				   struct ixxat_cancaps2 *caps)
+{
+	const u16 port = dev->ctrl_index;
+	struct ixxat_usb_getcaps_cl2_cmd cmd = { 0 };
+	const u32 cmd_size = sizeof(cmd);
+	const u32 req_size = sizeof(cmd.req);
+	const u32 rcv_size = cmd_size - req_size;
+	const u32 snd_size = req_size + sizeof(cmd.res);
+	int err;
+
+	ixxat_usb_setup_cmd(&cmd.req, &cmd.res);
+	cmd.req.code = cpu_to_le32(IXXAT_USB_CAN_CMD_GETCAPS2);
+	cmd.req.port = cpu_to_le16(port);
+	cmd.res.res_size = cpu_to_le32(rcv_size);
+
+	err = ixxat_usb_send_cmd(dev, port, &cmd, snd_size, &cmd.res,
+				 rcv_size, IXXAT_USB_CMD_TIMEOUT);
+	if (!err && caps)
+		memcpy(caps, &cmd.caps, sizeof(*caps));
+
+	return err;
+}
+
+static int ixxat_usb_init_ctrl(struct ixxat_usb_candevice *dev)
+{
+	const struct can_bittiming *bt = &dev->can.bittiming;
+	const struct can_bittiming *btd = &dev->can.fd.data_bittiming;
+	const u16 port = dev->ctrl_index;
+	struct ixxat_usb_init_cl2_cmd cmd = { 0 };
+	const u32 rcv_size = sizeof(cmd.res);
+	const u32 snd_size = sizeof(cmd);
+	u32 btmode = IXXAT_USB_BTMODE_NAT;
+	u8 opmode = IXXAT_USB_OPMODE_EXTENDED | IXXAT_USB_OPMODE_STANDARD;
+	u8 exmode = 0;
+
+	dev->loopback = ((dev->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) > 0);
+
+	if (dev->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES)
+		btmode = IXXAT_USB_BTMODE_TSM;
+	if (dev->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING)
+		opmode |= IXXAT_USB_OPMODE_ERRFRAME;
+	if (dev->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+		opmode |= IXXAT_USB_OPMODE_LISTONLY;
+	if (dev->can.ctrlmode & CAN_CTRLMODE_FD) {
+		exmode |= IXXAT_USB_EXMODE_EXTDATA | IXXAT_USB_EXMODE_FASTDATA;
+
+		if (!(dev->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO))
+			exmode |= IXXAT_USB_EXMODE_ISOFD;
+	}
+
+	ixxat_usb_setup_cmd(&cmd.req, &cmd.res);
+	cmd.req.size = cpu_to_le32(snd_size - rcv_size);
+	cmd.req.code = cpu_to_le32(IXXAT_USB_CAN_CMD_INIT2);
+	cmd.req.port = cpu_to_le16(port);
+	cmd.opmode = opmode;
+	cmd.exmode = exmode;
+	cmd.sdr.mode = cpu_to_le32(btmode);
+	cmd.sdr.bps = cpu_to_le32(bt->brp);
+	cmd.sdr.ts1 = cpu_to_le16(bt->prop_seg + bt->phase_seg1);
+	cmd.sdr.ts2 = cpu_to_le16(bt->phase_seg2);
+	cmd.sdr.sjw = cpu_to_le16(bt->sjw);
+	cmd.sdr.tdo = 0;
+
+	if (exmode) {
+		u16 tdo = btd->brp * (btd->phase_seg1 + 1 + btd->prop_seg);
+
+		cmd.fdr.mode = cpu_to_le32(btmode);
+		cmd.fdr.bps = cpu_to_le32(btd->brp);
+		cmd.fdr.ts1 = cpu_to_le16(btd->prop_seg + btd->phase_seg1);
+		cmd.fdr.ts2 = cpu_to_le16(btd->phase_seg2);
+		cmd.fdr.sjw = cpu_to_le16(btd->sjw);
+		cmd.fdr.tdo = cpu_to_le16(tdo);
+	}
+
+	return ixxat_usb_send_cmd(dev, port, &cmd, snd_size, &cmd.res,
+				  rcv_size, IXXAT_USB_CMD_TIMEOUT);
+}
+
+const struct ixxat_usb_adapter usb2can_fd = {
+	.clock = IXXAT_USB_CLOCK,
+	.bt = &usb2can_fd_bt,
+	.btd = &usb2can_fd_btd,
+	.modes = IXXAT_USB_MODES_FD,
+	.buffer_size_rx = IXXAT_USBFD_BUFFER_SIZE_RX,
+	.buffer_size_tx = IXXAT_USBFD_BUFFER_SIZE_TX,
+	.ep_msg_in = {
+		IXXAT_USB2CANFD_EP1_IN,
+		IXXAT_USB2CANFD_EP2_IN,
+		IXXAT_USB2CANFD_EP3_IN,
+		IXXAT_USB2CANFD_EP4_IN,
+		IXXAT_USB2CANFD_EP5_IN
+	},
+	.ep_msg_out = {
+		IXXAT_USB2CANFD_EP1_OUT,
+		IXXAT_USB2CANFD_EP2_OUT,
+		IXXAT_USB2CANFD_EP3_OUT,
+		IXXAT_USB2CANFD_EP4_OUT,
+		IXXAT_USB2CANFD_EP5_OUT
+	},
+	.ep_offs = 1,
+	.get_ctrl_caps = ixxat_usb_get_ctrl_caps,
+	.init_ctrl = ixxat_usb_init_ctrl
+};
+
+const struct ixxat_usb_adapter usb2can_v2 = {
+	.clock = IXXAT_USBV2_CLOCK,
+	.bt = &usb2can_v2_bt,
+	.modes = IXXAT_USB_MODES,
+	.buffer_size_rx = IXXAT_USBV2_BUFFER_SIZE_RX,
+	.buffer_size_tx = IXXAT_USBV2_BUFFER_SIZE_TX,
+	.ep_msg_in = {
+		IXXAT_USB2CANV2_EP1_IN,
+		IXXAT_USB2CANV2_EP2_IN,
+		IXXAT_USB2CANV2_EP3_IN,
+		IXXAT_USB2CANV2_EP4_IN,
+		IXXAT_USB2CANV2_EP5_IN
+	},
+	.ep_msg_out = {
+		IXXAT_USB2CANV2_EP1_OUT,
+		IXXAT_USB2CANV2_EP2_OUT,
+		IXXAT_USB2CANV2_EP3_OUT,
+		IXXAT_USB2CANV2_EP4_OUT,
+		IXXAT_USB2CANV2_EP5_OUT
+	},
+	.get_ctrl_caps = ixxat_usb_get_ctrl_caps,
+	.init_ctrl = ixxat_usb_init_ctrl
+};
+
+const struct ixxat_usb_adapter can_fd_idm = {
+	.clock = IXXAT_USB_CLOCK,
+	.bt = &canidm_bt,
+	.btd = &canidm_btd,
+	.modes = IXXAT_USB_MODES_FD,
+	.buffer_size_rx = IXXAT_USBIDM_BUFFER_SIZE_RX,
+	.buffer_size_tx = IXXAT_USBIDM_BUFFER_SIZE_TX,
+	.ep_msg_in = {
+		IXXAT_CANIDM_EP1_IN,
+		IXXAT_CANIDM_EP2_IN,
+		IXXAT_CANIDM_EP3_IN,
+		IXXAT_CANIDM_EP4_IN,
+		IXXAT_CANIDM_EP5_IN
+	},
+	.ep_msg_out = {
+		IXXAT_CANIDM_EP1_OUT,
+		IXXAT_CANIDM_EP2_OUT,
+		IXXAT_CANIDM_EP3_OUT,
+		IXXAT_CANIDM_EP4_OUT,
+		IXXAT_CANIDM_EP5_OUT
+	},
+	.get_ctrl_caps = ixxat_usb_get_ctrl_caps,
+	.init_ctrl = ixxat_usb_init_ctrl
+};
-- 
2.53.0