Hi Frank, Thank you for your patch. Here is my first batch of comments. Some comments also apply to the existing code. So you may want to address those in separate clean-up patches first. On Fri. 5 May 2023 at 01:16, Frank Jungclaus <frank.jungclaus@xxxxxx> wrote: > Add support for esd CAN-USB/3 and CAN FD to esd_usb. > > Signed-off-by: Frank Jungclaus <frank.jungclaus@xxxxxx> > --- > drivers/net/can/usb/esd_usb.c | 282 ++++++++++++++++++++++++++++++---- > 1 file changed, 249 insertions(+), 33 deletions(-) > > diff --git a/drivers/net/can/usb/esd_usb.c b/drivers/net/can/usb/esd_usb.c > index e24fa48b9b42..48cf5e88d216 100644 > --- a/drivers/net/can/usb/esd_usb.c > +++ b/drivers/net/can/usb/esd_usb.c > @@ -1,6 +1,6 @@ > // SPDX-License-Identifier: GPL-2.0-only > /* > - * CAN driver for esd electronics gmbh CAN-USB/2 and CAN-USB/Micro > + * CAN driver for esd electronics gmbh CAN-USB/2, CAN-USB/3 and CAN-USB/Micro > * > * Copyright (C) 2010-2012 esd electronic system design gmbh, Matthias Fuchs <socketcan@xxxxxx> > * Copyright (C) 2022-2023 esd electronics gmbh, Frank Jungclaus <frank.jungclaus@xxxxxx> > @@ -18,17 +18,19 @@ > > MODULE_AUTHOR("Matthias Fuchs <socketcan@xxxxxx>"); > MODULE_AUTHOR("Frank Jungclaus <frank.jungclaus@xxxxxx>"); > -MODULE_DESCRIPTION("CAN driver for esd electronics gmbh CAN-USB/2 and CAN-USB/Micro interfaces"); > +MODULE_DESCRIPTION("CAN driver for esd electronics gmbh CAN-USB/2, CAN-USB/3 and CAN-USB/Micro interfaces"); > MODULE_LICENSE("GPL v2"); > > /* USB vendor and product ID */ > #define USB_ESDGMBH_VENDOR_ID 0x0ab4 > #define USB_CANUSB2_PRODUCT_ID 0x0010 > #define USB_CANUSBM_PRODUCT_ID 0x0011 > +#define USB_CANUSB3_PRODUCT_ID 0x0014 > > /* CAN controller clock frequencies */ > #define ESD_USB2_CAN_CLOCK 60000000 > #define ESD_USBM_CAN_CLOCK 36000000 > +#define ESD_USB3_CAN_CLOCK 80000000 Nitpick: consider using the unit suffixes from linux/units.h: #define ESD_USB3_CAN_CLOCK (80 * MEGA) > /* Maximum number of CAN nets */ > #define ESD_USB_MAX_NETS 2 > @@ -43,6 +45,9 @@ MODULE_LICENSE("GPL v2"); > > /* esd CAN message flags - dlc field */ > #define ESD_DLC_RTR 0x10 > +#define ESD_DLC_NO_BRS 0x10 > +#define ESD_DLC_ESI 0x20 > +#define ESD_DLC_FD 0x80 Use the BIT() macro: #define ESD_DLC_NO_BRS BIT(4) #define ESD_DLC_ESI BIT(5) #define ESD_DLC_FD BIT(7) Also, if this is specific to the ESD_USB3 then add it in the prefix. > /* esd CAN message flags - id field */ > #define ESD_EXTID 0x20000000 > @@ -72,6 +77,28 @@ MODULE_LICENSE("GPL v2"); > #define ESD_USB2_BRP_INC 1 > #define ESD_USB2_3_SAMPLES 0x00800000 > > +/* Bit timing CAN-USB/3 */ > +#define ESD_USB3_TSEG1_MIN 1 > +#define ESD_USB3_TSEG1_MAX 256 > +#define ESD_USB3_TSEG2_MIN 1 > +#define ESD_USB3_TSEG2_MAX 128 > +#define ESD_USB3_SJW_MAX 128 > +#define ESD_USB3_BRP_MIN 1 > +#define ESD_USB3_BRP_MAX 1024 > +#define ESD_USB3_BRP_INC 1 > +/* Bit timing CAN-USB/3, data phase */ > +#define ESD_USB3_DATA_TSEG1_MIN 1 > +#define ESD_USB3_DATA_TSEG1_MAX 32 > +#define ESD_USB3_DATA_TSEG2_MIN 1 > +#define ESD_USB3_DATA_TSEG2_MAX 16 > +#define ESD_USB3_DATA_SJW_MAX 8 > +#define ESD_USB3_DATA_BRP_MIN 1 > +#define ESD_USB3_DATA_BRP_MAX 32 > +#define ESD_USB3_DATA_BRP_INC 1 There is no clear benefit to define macros for some initializers of a const struct. Doing as below has zero ambiguity: static const struct can_bittiming_const esd_usb3_bittiming_const = { .name = "esd_usb3", .tseg1_min = 1, .tseg1_max = 256, .tseg2_min = 1, .tseg2_max = 128, .sjw_max = 128, .brp_min = 1, .brp_max = 1024, .brp_inc = 1, }; > +/* Transmitter Delay Compensation */ > +#define ESD_TDC_MODE_AUTO 0 > + > /* esd IDADD message */ > #define ESD_ID_ENABLE 0x80 > #define ESD_MAX_ID_SEGMENT 64 > @@ -95,6 +122,21 @@ MODULE_LICENSE("GPL v2"); > #define MAX_RX_URBS 4 > #define MAX_TX_URBS 16 /* must be power of 2 */ > > +/* Modes for NTCAN_BAUDRATE_X */ > +#define ESD_BAUDRATE_MODE_DISABLE 0 /* remove from bus */ > +#define ESD_BAUDRATE_MODE_INDEX 1 /* ESD (CiA) bit rate idx */ > +#define ESD_BAUDRATE_MODE_BTR_CTRL 2 /* BTR values (Controller)*/ > +#define ESD_BAUDRATE_MODE_BTR_CANONICAL 3 /* BTR values (Canonical) */ > +#define ESD_BAUDRATE_MODE_NUM 4 /* numerical bit rate */ > +#define ESD_BAUDRATE_MODE_AUTOBAUD 5 /* autobaud */ > + > +/* Flags for NTCAN_BAUDRATE_X */ > +#define ESD_BAUDRATE_FLAG_FD 0x0001 /* enable CAN FD Mode */ > +#define ESD_BAUDRATE_FLAG_LOM 0x0002 /* enable Listen Only mode */ > +#define ESD_BAUDRATE_FLAG_STM 0x0004 /* enable Self test mode */ > +#define ESD_BAUDRATE_FLAG_TRS 0x0008 /* enable Triple Sampling */ > +#define ESD_BAUDRATE_FLAG_TXP 0x0010 /* enable Transmit Pause */ > + > struct header_msg { > u8 len; /* len is always the total message length in 32bit words */ > u8 cmd; > @@ -129,6 +171,7 @@ struct rx_msg { > __le32 id; /* upper 3 bits contain flags */ > union { > u8 data[8]; > + u8 data_fd[64]; > struct { > u8 status; /* CAN Controller Status */ > u8 ecc; /* Error Capture Register */ > @@ -144,8 +187,11 @@ struct tx_msg { > u8 net; > u8 dlc; > u32 hnd; /* opaque handle, not used by device */ > - __le32 id; /* upper 3 bits contain flags */ > - u8 data[8]; > + __le32 id; /* upper 3 bits contain flags */ > + union { > + u8 data[8]; > + u8 data_fd[64]; Nitpick, use the macro: u8 data[CAN_MAX_DLEN]; u8 data_fd[CANFD_MAX_DLEN]; > + }; > }; > > struct tx_done_msg { > @@ -165,12 +211,37 @@ struct id_filter_msg { > __le32 mask[ESD_MAX_ID_SEGMENT + 1]; > }; > > +struct baudrate_x_cfg { > + __le16 brp; /* bit rate pre-scaler */ > + __le16 tseg1; /* TSEG1 register */ > + __le16 tseg2; /* TSEG2 register */ > + __le16 sjw; /* SJW register */ > +}; > + > +struct tdc_cfg { Please prefix all the structures with the device name. e.g. struct esd_usb3_tdc_cfg { > + u8 tdc_mode; /* transmitter Delay Compensation mode */ > + u8 ssp_offset; /* secondary Sample Point offset in mtq */ > + s8 ssp_shift; /* secondary Sample Point shift in mtq */ > + u8 tdc_filter; /* Transmitter Delay Compensation */ > +}; > + > +struct baudrate_x { The x in baudrate_x and baudrate_x_cfg is confusing me. Is it meant to signify that the structure applies to both nominal and data baudrate? In that case, just put a comment and remove the x from the name. > + __le16 mode; /* mode word */ > + __le16 flags; /* control flags */ > + struct tdc_cfg tdc; /* TDC configuration */ > + struct baudrate_x_cfg arb; /* bit rate during arbitration phase */ /* nominal bit rate */ The comment is incorrect. CAN-FD may use the nominal bitrate for the data phase if the bit rate switch (BRS) is not set. > + struct baudrate_x_cfg data; /* bit rate during data phase */ /* data bit rate */ Please adjust the field names accordingly. > +}; > + > struct set_baudrate_msg { > u8 len; > u8 cmd; > u8 net; > u8 rsvd; > - __le32 baud; > + union { > + __le32 baud; > + struct baudrate_x baud_x; > + }; > }; > > /* Main message type used between library and application */ > @@ -188,6 +259,7 @@ union __packed esd_usb_msg { > static struct usb_device_id esd_usb_table[] = { > {USB_DEVICE(USB_ESDGMBH_VENDOR_ID, USB_CANUSB2_PRODUCT_ID)}, > {USB_DEVICE(USB_ESDGMBH_VENDOR_ID, USB_CANUSBM_PRODUCT_ID)}, > + {USB_DEVICE(USB_ESDGMBH_VENDOR_ID, USB_CANUSB3_PRODUCT_ID)}, > {} > }; > MODULE_DEVICE_TABLE(usb, esd_usb_table); > @@ -326,11 +398,13 @@ static void esd_usb_rx_event(struct esd_usb_net_priv *priv, > static void esd_usb_rx_can_msg(struct esd_usb_net_priv *priv, > union esd_usb_msg *msg) > { > + bool is_canfd = msg->rx.dlc & ESD_DLC_FD ? true : false; This is redundant. Just this is enough: bool is_canfd = msg->rx.dlc & ESD_DLC_FD; This variable being used only twice, you may want to consider not declaring it and simply doing directly: if (msg->rx.dlc & ESD_DLC_FD) > struct net_device_stats *stats = &priv->netdev->stats; > struct can_frame *cf; > + struct canfd_frame *cfd; > struct sk_buff *skb; > - int i; > u32 id; > + u8 len; > > if (!netif_device_present(priv->netdev)) > return; > @@ -340,27 +414,42 @@ static void esd_usb_rx_can_msg(struct esd_usb_net_priv *priv, > if (id & ESD_EVENT) { > esd_usb_rx_event(priv, msg); > } else { > - skb = alloc_can_skb(priv->netdev, &cf); > + if (is_canfd) { > + skb = alloc_canfd_skb(priv->netdev, &cfd); > + } else { > + skb = alloc_can_skb(priv->netdev, &cf); > + cfd = (struct canfd_frame *)cf; > + } > + > if (skb == NULL) { > stats->rx_dropped++; > return; > } > > - cf->can_id = id & ESD_IDMASK; > - can_frame_set_cc_len(cf, msg->rx.dlc & ~ESD_DLC_RTR, > - priv->can.ctrlmode); > - > - if (id & ESD_EXTID) > - cf->can_id |= CAN_EFF_FLAG; > + cfd->can_id = id & ESD_IDMASK; > > - if (msg->rx.dlc & ESD_DLC_RTR) { > - cf->can_id |= CAN_RTR_FLAG; > + if (is_canfd) { > + /* masking by 0x0F is already done within can_fd_dlc2len() */ > + cfd->len = can_fd_dlc2len(msg->rx.dlc); > + len = cfd->len; > + if ((msg->rx.dlc & ESD_DLC_NO_BRS) == 0) > + cfd->flags |= CANFD_BRS; > + if (msg->rx.dlc & ESD_DLC_ESI) > + cfd->flags |= CANFD_ESI; > } else { > - for (i = 0; i < cf->len; i++) > - cf->data[i] = msg->rx.data[i]; > - > - stats->rx_bytes += cf->len; > + can_frame_set_cc_len(cf, msg->rx.dlc & ~ESD_DLC_RTR, priv->can.ctrlmode); > + len = cf->len; > + if (msg->rx.dlc & ESD_DLC_RTR) { > + cf->can_id |= CAN_RTR_FLAG; > + len = 0; > + } > } > + > + if (id & ESD_EXTID) > + cfd->can_id |= CAN_EFF_FLAG; > + > + memcpy(cfd->data, msg->rx.data_fd, len); > + stats->rx_bytes += len; > stats->rx_packets++; > > netif_rx(skb); > @@ -735,7 +824,7 @@ static netdev_tx_t esd_usb_start_xmit(struct sk_buff *skb, > struct esd_usb *dev = priv->usb; > struct esd_tx_urb_context *context = NULL; > struct net_device_stats *stats = &netdev->stats; > - struct can_frame *cf = (struct can_frame *)skb->data; > + struct canfd_frame *cfd = (struct canfd_frame *)skb->data; > union esd_usb_msg *msg; > struct urb *urb; > u8 *buf; > @@ -768,19 +857,28 @@ static netdev_tx_t esd_usb_start_xmit(struct sk_buff *skb, > msg->hdr.len = 3; /* minimal length */ > msg->hdr.cmd = CMD_CAN_TX; > msg->tx.net = priv->index; > - msg->tx.dlc = can_get_cc_dlc(cf, priv->can.ctrlmode); > - msg->tx.id = cpu_to_le32(cf->can_id & CAN_ERR_MASK); > > - if (cf->can_id & CAN_RTR_FLAG) > - msg->tx.dlc |= ESD_DLC_RTR; > + if (can_is_canfd_skb(skb)) { > + msg->tx.dlc = can_fd_len2dlc(cfd->len); > + msg->tx.dlc |= ESD_DLC_FD; > + > + if ((cfd->flags & CANFD_BRS) == 0) > + msg->tx.dlc |= ESD_DLC_NO_BRS; > + } else { > + msg->tx.dlc = can_get_cc_dlc((struct can_frame *)cfd, priv->can.ctrlmode); > + > + if (cfd->can_id & CAN_RTR_FLAG) > + msg->tx.dlc |= ESD_DLC_RTR; > + } > > - if (cf->can_id & CAN_EFF_FLAG) > + msg->tx.id = cpu_to_le32(cfd->can_id & CAN_ERR_MASK); > + > + if (cfd->can_id & CAN_EFF_FLAG) > msg->tx.id |= cpu_to_le32(ESD_EXTID); > > - for (i = 0; i < cf->len; i++) > - msg->tx.data[i] = cf->data[i]; > + memcpy(msg->tx.data_fd, cfd->data, cfd->len); > > - msg->hdr.len += (cf->len + 3) >> 2; > + msg->hdr.len += (cfd->len + 3) >> 2; I do not get the logic. Assuming cfd->len is 8. Then hdr.len += (8 + 3) >> 2 hdr.len += 2 And because hdr.len is initially 3, hdr.len becomes 5. Right? Shouldn't it be 8? > for (i = 0; i < MAX_TX_URBS; i++) { > if (priv->tx_contexts[i].echo_index == MAX_TX_URBS) { > @@ -966,6 +1064,108 @@ static int esd_usb2_set_bittiming(struct net_device *netdev) > return err; > } > > +static const struct can_bittiming_const esd_usb3_bittiming_const = { > + .name = "esd_usb3", > + .tseg1_min = ESD_USB3_TSEG1_MIN, > + .tseg1_max = ESD_USB3_TSEG1_MAX, > + .tseg2_min = ESD_USB3_TSEG2_MIN, > + .tseg2_max = ESD_USB3_TSEG2_MAX, > + .sjw_max = ESD_USB3_SJW_MAX, > + .brp_min = ESD_USB3_BRP_MIN, > + .brp_max = ESD_USB3_BRP_MAX, > + .brp_inc = ESD_USB3_BRP_INC, > +}; > + > +static const struct can_bittiming_const esd_usb3_data_bittiming_const = { > + .name = "esd_usb3", > + .tseg1_min = ESD_USB3_DATA_TSEG1_MIN, > + .tseg1_max = ESD_USB3_DATA_TSEG1_MAX, > + .tseg2_min = ESD_USB3_DATA_TSEG2_MIN, > + .tseg2_max = ESD_USB3_DATA_TSEG2_MAX, > + .sjw_max = ESD_USB3_DATA_SJW_MAX, > + .brp_min = ESD_USB3_DATA_BRP_MIN, > + .brp_max = ESD_USB3_DATA_BRP_MAX, > + .brp_inc = ESD_USB3_DATA_BRP_INC, > +}; > + > +static int esd_usb3_set_bittiming(struct net_device *netdev) > +{ > + struct esd_usb_net_priv *priv = netdev_priv(netdev); > + struct can_bittiming *bt = &priv->can.bittiming; > + struct can_bittiming *d_bt = &priv->can.data_bittiming; > + union esd_usb_msg *msg; > + int err; > + u16 mode; > + u16 flags = 0; > + u16 brp, tseg1, tseg2, sjw; > + u16 d_brp, d_tseg1, d_tseg2, d_sjw; > + > + msg = kmalloc(sizeof(*msg), GFP_KERNEL); > + if (!msg) > + return -ENOMEM; > + > + /* Canonical is the most reasonable mode for SocketCAN on CAN-USB/3 ... */ > + mode = ESD_BAUDRATE_MODE_BTR_CANONICAL; > + > + if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) > + flags |= ESD_BAUDRATE_FLAG_LOM; > + > + if (priv->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES) > + flags |= ESD_BAUDRATE_FLAG_TRS; > + > + brp = bt->brp & (ESD_USB3_BRP_MAX - 1); > + sjw = bt->sjw & (ESD_USB3_SJW_MAX - 1); > + tseg1 = (bt->prop_seg + bt->phase_seg1) & (ESD_USB3_TSEG1_MAX - 1); > + tseg2 = bt->phase_seg2 & (ESD_USB3_TSEG2_MAX - 1); I am not convinced by the use of these intermediate variables brp, sjw, tseg1 and tseg2. I think you can directly assign them to baud_x. > + msg->setbaud.baud_x.arb.brp = cpu_to_le16(brp); > + msg->setbaud.baud_x.arb.sjw = cpu_to_le16(sjw); > + msg->setbaud.baud_x.arb.tseg1 = cpu_to_le16(tseg1); > + msg->setbaud.baud_x.arb.tseg2 = cpu_to_le16(tseg2); You may want to declare a local variable struct baudrate_x *baud_x = &msg->setbaud.baud_x; so that you do not have to do msg->setbaud.baud_x each time. > + if (priv->can.ctrlmode & CAN_CTRLMODE_FD) { > + d_brp = d_bt->brp & (ESD_USB3_DATA_BRP_MAX - 1); > + d_sjw = d_bt->sjw & (ESD_USB3_DATA_SJW_MAX - 1); > + d_tseg1 = (d_bt->prop_seg + d_bt->phase_seg1) & (ESD_USB3_DATA_TSEG1_MAX - 1); > + d_tseg2 = d_bt->phase_seg2 & (ESD_USB3_DATA_TSEG2_MAX - 1); > + flags |= ESD_BAUDRATE_FLAG_FD; > + } else { > + d_brp = 0; > + d_sjw = 0; > + d_tseg1 = 0; > + d_tseg2 = 0; > + } > + > + msg->setbaud.baud_x.data.brp = cpu_to_le16(d_brp); > + msg->setbaud.baud_x.data.sjw = cpu_to_le16(d_sjw); > + msg->setbaud.baud_x.data.tseg1 = cpu_to_le16(d_tseg1); > + msg->setbaud.baud_x.data.tseg2 = cpu_to_le16(d_tseg2); > + msg->setbaud.baud_x.mode = cpu_to_le16(mode); > + msg->setbaud.baud_x.flags = cpu_to_le16(flags); > + msg->setbaud.baud_x.tdc.tdc_mode = ESD_TDC_MODE_AUTO; > + msg->setbaud.baud_x.tdc.ssp_offset = 0; > + msg->setbaud.baud_x.tdc.ssp_shift = 0; > + msg->setbaud.baud_x.tdc.tdc_filter = 0; It seems that your device supports TDC. What is the reason to not configure it? Please have a look at struct can_tdc: https://elixir.bootlin.com/linux/v6.2/source/include/linux/can/bittiming.h#L21 Please refer to this patch if you want an example of how to use TDC: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1010a8fa9608 > + msg->hdr.len = 7; What is this magic number? If possible, replace it with a sizeof(). It seems that this relates to the size of struct set_baudrate_msg, but that structure is 8 bytes. Is the last byte of struct set_baudrate_msg really used? If not, reflect this in the declaration of the structure. > + msg->hdr.cmd = CMD_SETBAUD; > + > + msg->setbaud.net = priv->index; > + msg->setbaud.rsvd = 0; > + > + netdev_info(netdev, > + "ctrlmode=%#x/%#x, esd-net=%u, esd-mode=%#x, esd-flg=%#x, arb: brp=%u, ts1=%u, ts2=%u, sjw=%u, data: dbrp=%u, dts1=%u, dts2=%u dsjw=%u\n", > + priv->can.ctrlmode, priv->can.ctrlmode_supported, > + priv->index, mode, flags, > + brp, tseg1, tseg2, sjw, > + d_brp, d_tseg1, d_tseg2, d_sjw); Remove this debug message. The bittiming information can be retrieved with the ip tool. ip --details link show canX > + err = esd_usb_send_msg(priv->usb, msg); > + > + kfree(msg); esd_usb_send_msg() uses usb_bulk_msg() which does a synchronous call. It would be great to go asynchronous and use usb_submit_urb() so that you minimize the time spent in the driver. I know that esd_usb2_set_bittiming() also uses the synchronous call, so I am fine to have it as-is for this patch but for the future, it would be great to consider refactoring this. > + return err; > +} > + > static int esd_usb_get_berr_counter(const struct net_device *netdev, > struct can_berr_counter *bec) > { > @@ -1023,16 +1223,32 @@ static int esd_usb_probe_one_net(struct usb_interface *intf, int index) > CAN_CTRLMODE_CC_LEN8_DLC | > CAN_CTRLMODE_BERR_REPORTING; > > - if (le16_to_cpu(dev->udev->descriptor.idProduct) == > - USB_CANUSBM_PRODUCT_ID) > + switch (le16_to_cpu(dev->udev->descriptor.idProduct)) { Instead of doing a switch on idProduct, you can use the driver_info field from struct usb_device_id to store the device quirks. You can pass either a pointer or some flags into driver_info. Examples: https://elixir.bootlin.com/linux/v6.2/source/drivers/net/can/usb/peak_usb/pcan_usb_core.c#L30 https://elixir.bootlin.com/linux/v6.2/source/drivers/net/can/usb/etas_es58x/es58x_core.c#L37 > + case USB_CANUSB3_PRODUCT_ID: > + priv->can.clock.freq = ESD_USB3_CAN_CLOCK; > + priv->can.ctrlmode_supported |= CAN_CTRLMODE_3_SAMPLES; > + priv->can.ctrlmode_supported |= CAN_CTRLMODE_FD; > + priv->can.bittiming_const = &esd_usb3_bittiming_const; > + priv->can.data_bittiming_const = &esd_usb3_data_bittiming_const; > + priv->can.do_set_bittiming = esd_usb3_set_bittiming; > + priv->can.do_set_data_bittiming = esd_usb3_set_bittiming; > + break; > + > + case USB_CANUSBM_PRODUCT_ID: > priv->can.clock.freq = ESD_USBM_CAN_CLOCK; > - else { > + priv->can.bittiming_const = &esd_usb2_bittiming_const; > + priv->can.do_set_bittiming = esd_usb2_set_bittiming; > + break; > + > + case USB_CANUSB2_PRODUCT_ID: > + default: > priv->can.clock.freq = ESD_USB2_CAN_CLOCK; > priv->can.ctrlmode_supported |= CAN_CTRLMODE_3_SAMPLES; > + priv->can.bittiming_const = &esd_usb2_bittiming_const; > + priv->can.do_set_bittiming = esd_usb2_set_bittiming; > + break; > } > > - priv->can.bittiming_const = &esd_usb2_bittiming_const; > - priv->can.do_set_bittiming = esd_usb2_set_bittiming; > priv->can.do_set_mode = esd_usb_set_mode; > priv->can.do_get_berr_counter = esd_usb_get_berr_counter; > > -- > 2.25.1 >