[PATCH 2/4] can: usb: ixxat_usb: add CL1 legacy protocol layer
Stéphane Grosjean <[email protected]> Thu, 23 Jul 2026 09:29:37 +0200
| Newsgroups | org.kernel.vger.linux-can |
|---|---|
| Message-ID | <[email protected]> |
From: Stéphane Grosjean <[email protected]> Add ixxat_usb_cl1.c, which implements the CL1 communication protocol used by the early firmware versions for IXXAT USB-to-CAN adapters. CL1 devices expose classic CAN 2.0b frames only (no CAN FD). The init command carries a single set of bittiming parameters and optional mode flags for listen-only, triple-sampling, loopback and bus-error reporting. The exported usb2can_cl1 adapter descriptor references the CL1 open/close/start/stop callbacks and the CL1 bittiming constants. Supported control modes: listen-only, loopback, triple-sampling, bus-error reporting. Signed-off-by: Stéphane Grosjean <[email protected]> --- drivers/net/can/usb/ixxat_usb/ixxat_usb_cl1.c | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 drivers/net/can/usb/ixxat_usb/ixxat_usb_cl1.c diff --git a/drivers/net/can/usb/ixxat_usb/ixxat_usb_cl1.c b/drivers/net/can/usb/ixxat_usb/ixxat_usb_cl1.c new file mode 100644 index 000000000000..c1e337fc3b91 --- /dev/null +++ b/drivers/net/can/usb/ixxat_usb/ixxat_usb_cl1.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +/* CAN driver adapter for IXXAT USB-to-CAN CL1 + * + * 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 8000000 + +#define IXXAT_USB_BUFFER_SIZE_RX 512 +#define IXXAT_USB_BUFFER_SIZE_TX 256 + +#define IXXAT_USB_MODES (CAN_CTRLMODE_BERR_REPORTING | \ + CAN_CTRLMODE_3_SAMPLES | \ + CAN_CTRLMODE_LOOPBACK | \ + CAN_CTRLMODE_LISTENONLY) + +#define IXXAT_USB_BTMODE_TSM_CL1 0x80 + +/* bittiming parameters */ +#define IXXAT_USB2CAN_TSEG1_MIN 1 +#define IXXAT_USB2CAN_TSEG1_MAX 16 +#define IXXAT_USB2CAN_TSEG2_MIN 1 +#define IXXAT_USB2CAN_TSEG2_MAX 8 +#define IXXAT_USB2CAN_SJW_MAX 4 +#define IXXAT_USB2CAN_BRP_MIN 1 +#define IXXAT_USB2CAN_BRP_MAX 64 +#define IXXAT_USB2CAN_BRP_INC 1 + +/* USB endpoint mapping for CL1 */ +#define IXXAT_USB2CAN_EP1_IN (1 | USB_DIR_IN) +#define IXXAT_USB2CAN_EP2_IN (2 | USB_DIR_IN) +#define IXXAT_USB2CAN_EP3_IN (3 | USB_DIR_IN) +#define IXXAT_USB2CAN_EP4_IN (4 | USB_DIR_IN) +#define IXXAT_USB2CAN_EP5_IN (5 | USB_DIR_IN) + +#define IXXAT_USB2CAN_EP1_OUT (1 | USB_DIR_OUT) +#define IXXAT_USB2CAN_EP2_OUT (2 | USB_DIR_OUT) +#define IXXAT_USB2CAN_EP3_OUT (3 | USB_DIR_OUT) +#define IXXAT_USB2CAN_EP4_OUT (4 | USB_DIR_OUT) +#define IXXAT_USB2CAN_EP5_OUT (5 | USB_DIR_OUT) + +#define IXXAT_USB_CAN_CMD_GETCAPS 0x320 +#define IXXAT_USB_CAN_CMD_INIT 0x325 + +static const struct can_bittiming_const usb2can_bt = { + .name = KBUILD_MODNAME, + .tseg1_min = IXXAT_USB2CAN_TSEG1_MIN, + .tseg1_max = IXXAT_USB2CAN_TSEG1_MAX, + .tseg2_min = IXXAT_USB2CAN_TSEG2_MIN, + .tseg2_max = IXXAT_USB2CAN_TSEG2_MAX, + .sjw_max = IXXAT_USB2CAN_SJW_MAX, + .brp_min = IXXAT_USB2CAN_BRP_MIN, + .brp_max = IXXAT_USB2CAN_BRP_MAX, + .brp_inc = IXXAT_USB2CAN_BRP_INC, +}; + +/* ixxat_usb_get_ctrl_caps - get controller capabilities + * @dev: pointer to the IXXAT USB CAN device + * @caps: pointer to the structure to store capabilities (can be NULL) + * This function retrieves the capabilities of the IXXAT USB CAN controller. + * + * Returns 0 on success, negative error code on failure. + */ +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_cl1_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_GETCAPS); + cmd.req.port = cpu_to_le16(port); + cmd.res.res_size = cpu_to_le32(rcv_size); + memset(&cmd.caps, 0, sizeof(cmd.caps)); + + err = ixxat_usb_send_cmd(dev, port, &cmd, snd_size, &cmd.res, + rcv_size, IXXAT_USB_CMD_TIMEOUT); + if (!err && caps) { + memset(caps, 0, sizeof(*caps)); + + caps->ctrltype = cmd.caps.ctrltype; + caps->buscoupling = cmd.caps.buscoupling; + caps->features = cmd.caps.features; + caps->can_clock_freq = cmd.caps.can_clock_freq; + + /* these are not available in CL1 + * caps->sdr_range_min + * caps->sdr_range_max + * caps->fdr_range_min + * caps->fdr_range_max + */ + caps->ts_clock_freq = cmd.caps.can_clock_freq; + caps->ts_clock_divisor = cmd.caps.ts_clock_divisor; + + caps->cms_clock_freq = cmd.caps.can_clock_freq; + caps->cms_clock_divisor = cmd.caps.cms_clock_divisor; + caps->cms_max_ticks = cmd.caps.cms_max_ticks; + + caps->dtx_clock_freq = cmd.caps.can_clock_freq; + caps->dtx_clock_divisor = cmd.caps.dtx_clock_divisor; + caps->dtx_max_ticks = cmd.caps.dtx_max_ticks; + } + + return err; +} + +/* ixxat_usb_init_ctrl - initialize the controller + * @dev: pointer to the IXXAT USB CAN device + * This function initializes the IXXAT USB CAN controller with the specified + * bittiming parameters and control modes. + * + * Returns 0 on success, negative error code on failure. + */ +static int ixxat_usb_init_ctrl(struct ixxat_usb_candevice *dev) +{ + const struct can_bittiming *bt = &dev->can.bittiming; + const u16 port = dev->ctrl_index; + struct ixxat_usb_init_cl1_cmd cmd = { 0 }; + const u32 rcv_size = sizeof(cmd.res); + const u32 snd_size = sizeof(cmd); + u8 opmode = IXXAT_USB_OPMODE_EXTENDED | IXXAT_USB_OPMODE_STANDARD; + u8 btr0 = ((bt->brp - 1) & 0x3f) | (((bt->sjw - 1) & 0x3) << 6); + u8 btr1 = ((bt->prop_seg + bt->phase_seg1 - 1) & 0xf) | + (((bt->phase_seg2 - 1) & 0x7) << 4); + + dev->loopback = ((dev->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) > 0); + + if (dev->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) + btr1 |= IXXAT_USB_BTMODE_TSM_CL1; + 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; + + 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_INIT); + cmd.req.port = cpu_to_le16(port); + cmd.mode = opmode; + cmd.btr0 = btr0; + cmd.btr1 = btr1; + + return ixxat_usb_send_cmd(dev, port, &cmd, snd_size, &cmd.res, + rcv_size, IXXAT_USB_CMD_TIMEOUT); +} + +const struct ixxat_usb_adapter usb2can_cl1 = { + .clock = IXXAT_USB_CLOCK, + .bt = &usb2can_bt, + .modes = IXXAT_USB_MODES, + .buffer_size_rx = IXXAT_USB_BUFFER_SIZE_RX, + .buffer_size_tx = IXXAT_USB_BUFFER_SIZE_TX, + .ep_msg_in = { + IXXAT_USB2CAN_EP1_IN, + IXXAT_USB2CAN_EP2_IN, + IXXAT_USB2CAN_EP3_IN, + IXXAT_USB2CAN_EP4_IN, + IXXAT_USB2CAN_EP5_IN + }, + .ep_msg_out = { + IXXAT_USB2CAN_EP1_OUT, + IXXAT_USB2CAN_EP2_OUT, + IXXAT_USB2CAN_EP3_OUT, + IXXAT_USB2CAN_EP4_OUT, + IXXAT_USB2CAN_EP5_OUT + }, + .get_ctrl_caps = ixxat_usb_get_ctrl_caps, + .init_ctrl = ixxat_usb_init_ctrl +}; -- 2.53.0