summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Muellner <christoph.muellner@theobroma-systems.com>2019-04-02 19:12:02 +0200
committerChristoph Muellner <christoph.muellner@theobroma-systems.com>2019-04-30 20:02:06 +0200
commit25cb47bd1a6bd96b360637d125e9a13d04804bd6 (patch)
tree1ce5f89190411ef06794b676c444b08ec7f6693e
parent3866fc44de7afcc5241c79f06c6e87446e97adb2 (diff)
ucan: Add old driver to support legacy (pre v3.0) devices.
A previous commit updated the ucan driver to the one from mainline Linux. Unfortunately the updated driver does not support the old v2.0 devices, which breaks environments where such devices are deployed and cannot be updated. Therefore this patch adds the old driver back as ucan_legacy. Besides the USB PID/VID combination it will match for device versions from 0x0000 to 0x02ff (the new ucan driver will test for 0x0300+). Signed-off-by: Christoph Muellner <christoph.muellner@theobroma-systems.com>
-rw-r--r--drivers/net/can/usb/Makefile2
-rw-r--r--drivers/net/can/usb/ucan_legacy.c1303
2 files changed, 1304 insertions, 1 deletions
diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
index 3afde989139b..aeed7dac969d 100644
--- a/drivers/net/can/usb/Makefile
+++ b/drivers/net/can/usb/Makefile
@@ -8,4 +8,4 @@ obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb.o
obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
obj-$(CONFIG_CAN_8DEV_USB) += usb_8dev.o
-obj-$(CONFIG_CAN_UCAN) += ucan.o
+obj-$(CONFIG_CAN_UCAN) += ucan.o ucan_legacy.o
diff --git a/drivers/net/can/usb/ucan_legacy.c b/drivers/net/can/usb/ucan_legacy.c
new file mode 100644
index 000000000000..5376e506cdc8
--- /dev/null
+++ b/drivers/net/can/usb/ucan_legacy.c
@@ -0,0 +1,1303 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Driver for Theobroma Systems UCAN devices
+ *
+ * Copyright (C) 2015 Theobroma Systems Design und Consulting GmbH
+ *
+ * This driver is inspired by the 4.0.0 version of drivers/net/can/usb/ems_usb.c
+ *
+ *
+ * General Description:
+ *
+ * The USB Device uses three Endpoints:
+ *
+ * Interrupt Endpoint: Once the device is started the device sends
+ * its TX FIFO status (space left) on this endpoint. The driver uses
+ * this information for flow control.
+ *
+ * IN Enpoint: The device sends CAN Frame Messages and Device
+ * Information using the IN endpoint.
+ *
+ * OUT Endpoint: The driver sends configuration requests, and CAN
+ * Frames on the out endpoint.
+ *
+ * Error Handling: If error reporting is turned on the device
+ * encodes error into CAN error frames (see uapi/linux/can/error.h)
+ * and sends it using the IN Endpoint. The driver updates statistics
+ * and forward it.
+ */
+
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/signal.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Martin Elshuber, Theobroma Systems Design und Consulting GmbH <martin.elshuber@theobroma-systems.com>");
+MODULE_DESCRIPTION("Driver for Theobroma Systems legacy UCAN devices");
+
+#define MAX_TX_URBS 8
+#define MAX_RX_URBS 8
+#define TX_QUEUE_STOP_THRESHOLD 1
+
+static struct usb_device_id ucan_table[] = {
+ {
+ /* Mule (soldered onto compute modules) */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+ USB_DEVICE_ID_MATCH_INT_NUMBER,
+ .idVendor = 0x2294,
+ .idProduct = 0x425a,
+ .bInterfaceNumber = 0,
+ .bcdDevice_lo = 0x0000,
+ .bcdDevice_hi = 0x02ff
+ },
+ {
+ /* Seal (standalone USB stick) */
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+ USB_DEVICE_ID_MATCH_INT_NUMBER,
+ .idVendor = 0x2294,
+ .idProduct = 0x425b,
+ .bInterfaceNumber = 0,
+ .bcdDevice_lo = 0x0000,
+ .bcdDevice_hi = 0x02ff
+ },
+ {} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, ucan_table);
+
+/* UCAN Message Definitions --------------------------------------------
+ *
+ * ucan_message_out_t and ucan_message_in_t define the messages
+ * transmitted on the OUT and IN endpoint.
+ *
+ * Multibyte fields are transmitted with little endianness
+ *
+ * INTR Endpoint: a single uint32_t storing the current space in the fifo
+ *
+ * OUT Enpoint: single message of type ucan_message_out_t is
+ * transmitted on the out endpoint
+ *
+ * IN Endpoint: multiple messages ucan_message_in_t concateted in
+ * the following way:
+ *
+ * m[n].len <=> the length if message n(including the header in bytes)
+ * m[n] is is aligned to a 4 byte boundary, hence
+ * offset(m[0]) := 0;
+ * offset(m[n+1]) := offset(m[n]) + (m[n].len + 3) & 3
+ *
+ * this implies that
+ * offset(m[n]) % 4 <=> 0
+ */
+
+/* Device Global Commands */
+enum {
+ UCAN_DEVICE_GET_FW_STRING = 0,
+};
+
+/* UCAN Commands */
+enum {
+ /* start the can transceiver - val defines the operation mode */
+ UCAN_COMMAND_START = 0,
+ /* cancel pending transmissions and stop the can transceiver */
+ UCAN_COMMAND_STOP = 1,
+ /* send can transceiver into low-power sleep mode */
+ UCAN_COMMAND_SLEEP = 2,
+ /* wake up can transceiver from low-power sleep mode */
+ UCAN_COMMAND_WAKEUP = 3,
+ /* reset the can transceiver */
+ UCAN_COMMAND_RESET = 4,
+ /* get piece of info from the can transceiver - subcmd defines what
+ * piece
+ */
+ UCAN_COMMAND_GET = 5,
+ /* clear or disable hardware filter - subcmd defines which of the two */
+ UCAN_COMMAND_FILTER = 6,
+ /* Setup bittiming */
+ UCAN_COMMAND_SET_BITTIMING = 7,
+};
+
+/* UCAN_COMMAND_START and UCAN_COMMAND_GET_INFO operation modes (bitmap).
+ * Undefined bits must be set to 0.
+ */
+enum {
+ UCAN_MODE_LOOPBACK = (1 << 0),
+ UCAN_MODE_SILENT = (1 << 1),
+ UCAN_MODE_3_SAMPLES = (1 << 2),
+ UCAN_MODE_ONE_SHOT = (1 << 3),
+ UCAN_MODE_BERR_REPORT = (1 << 4),
+};
+
+/* UCAN_COMMAND_GET subcommands */
+enum {
+ UCAN_COMMAND_GET_INFO = 0,
+ UCAN_COMMAND_GET_PROTOCOL_VERSION = 1,
+};
+
+/* UCAN_COMMAND_FILTER subcommands */
+enum {
+ UCAN_FILTER_CLEAR = 0,
+ UCAN_FILTER_DISABLE = 1,
+ UCAN_FILTER_ENABLE = 2,
+};
+
+/* OUT endpoint message types */
+enum {
+ UCAN_OUT_TX = 2, /* transmit a CAN frame */
+};
+
+/* IN endpoint message types */
+enum {
+ UCAN_IN_RX = 2,
+};
+
+typedef union {
+ /***************************************************
+ * Setup Bittiming
+ * bmRequest == UCAN_COMMAND_START
+ ***************************************************/
+ struct {
+ u16 mode;
+ } __packed cmd_start;
+ /***************************************************
+ * Setup Bittiming
+ * bmRequest == UCAN_COMMAND_SET_BITTIMING
+ ***************************************************/
+ struct {
+ u32 tq; /* Time quanta (TQ) in nanoseconds */
+ u16 brp; /* TQ Prescaler */
+ u16 sample_point; /* Samplepoint on tenth percent */
+ u8 prop_seg; /* Propagation segment in TQs */
+ u8 phase_seg1; /* Phase buffer segment 1 in TQs */
+ u8 phase_seg2; /* Phase buffer segment 2 in TQs */
+ u8 sjw; /* Synchronisation jump width in TQs */
+ } __packed cmd_set_bittiming;
+ /***************************************************
+ * Setup HW Filter Modes
+ * bmRequest == UCAN_ENA_FILTER
+ ***************************************************/
+ struct {
+ u32 id;
+ u32 mask;
+ u16 mbox;
+ } __packed cmd_ena_filter;
+ /***************************************************
+ * Get Device Information
+ * bmRequest == UCAN_COMMAND_GET; wValue = UCAN_COMMAND_GET_INFO
+ ***************************************************/
+ struct {
+ u32 freq; /* Clock Frequency for tq generation */
+ u8 tx_fifo; /* Size of the transmission fifo */
+ u8 sjw_max; /* can_bittiming fields... */
+ u8 tseg1_min;
+ u8 tseg1_max;
+ u8 tseg2_min;
+ u8 tseg2_max;
+ u16 brp_inc;
+ u32 brp_min;
+ u32 brp_max; /* ...can_bittiming fields */
+ u16 ctrlmodes; /* supported control modes */
+ u16 hwfilter; /* Number of HW filter banks */
+ u16 rxmboxes; /* Number of receive Mailboxes */
+ } __packed device_info;
+ struct {
+ u32 version;
+ } __packed protocol_version;
+ struct {
+ u8 d[128];
+ } raw;
+} __packed ucan_ctl_payload_t;
+
+
+/* OUT Enpoint, outbound messages */
+struct ucan_message_out {
+ u16 len; /* Length of the content include header */
+ u16 type; /* UCAN_OUT_COMMAND and friends */
+ union {
+ /***************************************************
+ * Transmit CAN frame
+ * (type == UCAN_TX) && ((msg.can_msg.id & CAN_RTR_FLAG) == 0)
+ ***************************************************/
+ struct {
+ /* note DLC is computed by
+ * msg.len - sizeof (msg.len)
+ * - sizeof (msg.type)
+ * - sizeof (msg.can_msg.id)
+ */
+ u32 id;
+ u8 data[8];
+ /* ensure data alignment to 4
+ * by moving dlc after data
+ */
+ } __packed can_msg;
+
+ /***************************************************
+ * Transmit RTR CAN frame
+ * (type == UCAN_TX) && ((msg.can_msg.id & CAN_RTR_FLAG) != 0)
+ ***************************************************/
+ struct {
+ u32 id;
+ u8 dlc;
+ } __packed can_rtr_msg;
+ } msg;
+} __packed __aligned(0x4);
+
+/* IN Enpoint, inbound messages */
+struct ucan_message_in {
+ u16 len; /* Length of the content include header */
+ u16 type; /* UCAN_IN_DEVICE_INFO and friends */
+
+ union {
+ /***************************************************
+ * CAN Frame received
+ * (type == UCAN_IN_RX)
+ * && ((msg.can_msg.id & CAN_RTR_FLAG) == 0)
+ ***************************************************/
+ struct {
+ /* note DLC is computed by
+ * msg.len - sizeof (msg.len)
+ * - sizeof (msg.type)
+ * - sizeof (msg.can_msg.id)
+ */
+ u32 id;
+ u8 data[8];
+ /* ensure data alignment to 4
+ * by moving dlc after data
+ */
+ } __packed can_msg;
+
+ /***************************************************
+ * CAN RTR Frame received
+ * (type == UCAN_IN_RX)
+ * && ((msg.can_msg.id & CAN_RTR_FLAG) != 0)
+ ***************************************************/
+ struct {
+ u32 id;
+ u8 dlc;
+ } __packed can_rtr_msg;
+
+ } __aligned(0x4) msg;
+} __packed;
+
+/* Macros to calculate message lengths */
+#define UCAN_OUT_HDR_SIZE offsetof(struct ucan_message_out, msg)
+#define UCAN_OUT_LEN(member) (UCAN_OUT_HDR_SIZE + sizeof(member))
+
+#define UCAN_IN_HDR_SIZE offsetof(struct ucan_message_in, msg)
+#define UCAN_IN_LEN(member) (UCAN_OUT_HDR_SIZE + sizeof(member))
+
+struct ucan;
+
+/* Context Information for transmission URBs */
+struct ucan_urb_context {
+ struct ucan *up;
+ u32 echo_index;
+ u8 dlc;
+};
+
+/* Information reported by the USB device */
+struct ucan_device_info {
+ struct can_bittiming_const bittiming_const;
+ int tx_fifo;
+};
+
+/* Driver private data */
+struct ucan {
+ struct can_priv can; /* must be the first member */
+
+ u8 intf_index;
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ struct net_device *netdev;
+
+ struct usb_endpoint_descriptor *out_ep;
+ struct usb_endpoint_descriptor *in_ep;
+ struct usb_endpoint_descriptor *irq_ep;
+
+ struct usb_anchor rx_urbs;
+ struct usb_anchor tx_urbs;
+ struct urb *irq_urb;
+ u32 *irq_data;
+
+ ucan_ctl_payload_t *ctl_msg_buffer;
+ struct ucan_device_info device_info;
+
+ atomic_t active_tx_urbs;
+ atomic_t free_slots;
+ struct ucan_urb_context tx_urb_contexts[MAX_TX_URBS];
+};
+
+static void ucan_tx_wake_queue_if_possible(struct ucan *up)
+{
+ if ( (netif_queue_stopped(up->netdev)) &&
+ (atomic_read(&up->active_tx_urbs) < MAX_TX_URBS) &&
+ (atomic_read(&up->free_slots) >= TX_QUEUE_STOP_THRESHOLD) ) {
+ netif_wake_queue(up->netdev);
+ }
+}
+
+static int ucan_ctrl_command_out(struct ucan *up, u8 cmd, u16 subcmd, size_t datalen)
+{
+ if (datalen > sizeof(ucan_ctl_payload_t))
+ return -ENOMEM;
+
+ return usb_control_msg(up->udev,
+ usb_sndctrlpipe(up->udev, 0),
+ cmd,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+ cpu_to_le16(subcmd),
+ up->intf_index,
+ up->ctl_msg_buffer,
+ datalen,
+ 500);
+}
+
+static int ucan_ctrl_command_in(struct ucan *up, u8 cmd, u16 subcmd, size_t datalen)
+{
+ if (datalen > sizeof(ucan_ctl_payload_t))
+ return -ENOMEM;
+
+ return usb_control_msg(up->udev,
+ usb_rcvctrlpipe(up->udev, 0),
+ cmd,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+ cpu_to_le16(subcmd),
+ up->intf_index,
+ up->ctl_msg_buffer,
+ datalen,
+ 500);
+}
+
+static int ucan_device_request_in(struct ucan *up, u8 cmd, u16 subcmd, size_t datalen)
+{
+ if (datalen > sizeof(ucan_ctl_payload_t))
+ return -ENOMEM;
+
+ return usb_control_msg(up->udev,
+ usb_rcvctrlpipe(up->udev, 0),
+ cmd,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ cpu_to_le16(subcmd),
+ 0,
+ up->ctl_msg_buffer,
+ datalen,
+ 500);
+}
+
+/* Request the device Information */
+static int ucan_get_device_info(struct ucan *up)
+{
+ int ret;
+ struct can_bittiming_const *bittiming =
+ &up->device_info.bittiming_const;
+
+ ret = ucan_ctrl_command_in(up, UCAN_COMMAND_GET, UCAN_COMMAND_GET_INFO,
+ sizeof(up->ctl_msg_buffer->device_info));
+ if (ret < 0)
+ return ret;
+ if (ret < sizeof(up->ctl_msg_buffer->device_info))
+ return -EINVAL;
+
+ /* store the data */
+ up->can.clock.freq = le32_to_cpu(up->ctl_msg_buffer->device_info.freq);
+ up->device_info.tx_fifo = up->ctl_msg_buffer->device_info.tx_fifo;
+ strcpy(bittiming->name, "ucan");
+ bittiming->tseg1_min = up->ctl_msg_buffer->device_info.tseg1_min;
+ bittiming->tseg1_max = up->ctl_msg_buffer->device_info.tseg1_max;
+ bittiming->tseg2_min = up->ctl_msg_buffer->device_info.tseg2_min;
+ bittiming->tseg2_max = up->ctl_msg_buffer->device_info.tseg2_max;
+ bittiming->sjw_max = up->ctl_msg_buffer->device_info.sjw_max;
+ bittiming->brp_min = le32_to_cpu(up->ctl_msg_buffer->device_info.brp_min);
+ bittiming->brp_max = le32_to_cpu(up->ctl_msg_buffer->device_info.brp_max);
+ bittiming->brp_inc = le16_to_cpu(up->ctl_msg_buffer->device_info.brp_inc);
+
+ up->can.ctrlmode_supported = 0;
+
+ if (le16_to_cpu(up->ctl_msg_buffer->device_info.ctrlmodes) & UCAN_MODE_LOOPBACK)
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_LOOPBACK;
+ if (le16_to_cpu(up->ctl_msg_buffer->device_info.ctrlmodes) & UCAN_MODE_SILENT)
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_LISTENONLY;
+ if (le16_to_cpu(up->ctl_msg_buffer->device_info.ctrlmodes) & UCAN_MODE_3_SAMPLES)
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_3_SAMPLES;
+ if (le16_to_cpu(up->ctl_msg_buffer->device_info.ctrlmodes) & UCAN_MODE_ONE_SHOT)
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_ONE_SHOT;
+ if (le16_to_cpu(up->ctl_msg_buffer->device_info.ctrlmodes) & UCAN_MODE_BERR_REPORT)
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_BERR_REPORTING;
+
+ return 0;
+}
+
+/* Callback when the device sends the IRQ sate *
+ *
+ * This function simply stores the current TX fifo state for flow
+ * control
+ */
+static void ucan_read_irq_callback(struct urb *urb)
+{
+ int ret;
+ struct ucan *up = urb->context;
+ struct net_device *netdev = up->netdev;
+
+ switch (urb->status) {
+ case 0:
+ atomic_set(&up->free_slots, le32_to_cpu(*up->irq_data));
+ ucan_tx_wake_queue_if_possible(up);
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ETIME:
+ case -EPROTO:
+ dev_dbg(&up->udev->dev, "%s ENOENT|ESHUTDOWN|ETIME\n",
+ __func__);
+ return;
+ default:
+ dev_warn(&up->udev->dev, "%s error (%d)\n", __func__,
+ urb->status);
+ return;
+ }
+
+ usb_fill_int_urb(urb, up->udev,
+ usb_rcvintpipe(up->udev, up->irq_ep->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK),
+ urb->transfer_buffer, up->irq_ep->wMaxPacketSize,
+ ucan_read_irq_callback, up, up->irq_ep->bInterval);
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+
+ if (ret == -ENODEV)
+ netif_device_detach(netdev);
+ else if (ret)
+ dev_err(&up->udev->dev, "failed resubmitting urb: %d\n", ret);
+}
+
+/* Handle a CAN error frame that we have received from the device */
+static void ucan_handle_error_frame(struct ucan *up, struct ucan_message_in *m, u32 canid) {
+ enum can_state new_state = CAN_STATE_ERROR_ACTIVE;
+ struct net_device_stats *net_stats = &up->netdev->stats;
+ struct can_device_stats *can_stats = &up->can.can_stats;
+
+ if (canid & CAN_ERR_LOSTARB)
+ can_stats->arbitration_lost++;
+
+ if (canid & CAN_ERR_BUSERROR)
+ can_stats->bus_error++;
+
+ if (canid & CAN_ERR_ACK)
+ net_stats->tx_errors++;
+
+ if (canid & CAN_ERR_BUSOFF)
+ new_state = CAN_STATE_BUS_OFF;
+
+ /* controller problems, details in data[1] */
+ if (canid & CAN_ERR_CRTL) {
+ u8 d1 = m->msg.can_msg.data[1];
+
+ if (d1 & (CAN_ERR_CRTL_RX_PASSIVE | CAN_ERR_CRTL_TX_PASSIVE))
+ new_state = max(new_state, (enum can_state)CAN_STATE_ERROR_PASSIVE);
+
+ if (d1 & (CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING))
+ new_state = max(new_state, (enum can_state)CAN_STATE_ERROR_WARNING);
+
+ if (d1 & CAN_ERR_CRTL_RX_OVERFLOW)
+ net_stats->rx_over_errors++;
+ }
+
+ /* protocol error, details in data[2] */
+ if (canid & CAN_ERR_PROT) {
+ u8 d2 = m->msg.can_msg.data[2];
+
+ if (d2 & CAN_ERR_PROT_TX)
+ net_stats->tx_errors++;
+ else
+ net_stats->rx_errors++;
+ }
+
+ /* we switched into a better state */
+ if (up->can.state >= new_state) {
+ up->can.state = new_state;
+ return;
+ }
+
+ /* we switched into a worse state */
+ dev_dbg(&up->udev->dev, "%s: switching from state %d to %d\n", __func__, up->can.state, new_state);
+ up->can.state = new_state;
+ switch(new_state) {
+ case CAN_STATE_BUS_OFF:
+ can_stats->bus_off++;
+ can_bus_off(up->netdev);
+ netdev_info(up->netdev, "link has gone into BUS-OFF state\n");
+ break;
+ case CAN_STATE_ERROR_PASSIVE:
+ can_stats->error_passive++;
+ break;
+ case CAN_STATE_ERROR_WARNING:
+ can_stats->error_warning++;
+ break;
+ default:
+ break;
+ }
+}
+
+/* Callback on reception of a can frame via the IN endpoint
+ *
+ * This function allocates an skb and transferres it to the Linux
+ * network stack
+ */
+static void ucan_rx_can_msg(struct ucan *up, struct ucan_message_in *m)
+{
+ int len;
+ u32 canid;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ struct net_device_stats *stats = &up->netdev->stats;
+
+ /* get the contents of the length field */
+ len = le16_to_cpu(m->len);
+
+ /* check sanity */
+ if (len < UCAN_IN_HDR_SIZE + sizeof(m->msg.can_msg.id)) {
+ dev_warn(&up->udev->dev, "invalid input message len\n");
+ return;
+ }
+
+ /* handle error frames */
+ canid = le32_to_cpu(m->msg.can_msg.id);
+ if (canid & CAN_ERR_FLAG) {
+ ucan_handle_error_frame(up, m, canid);
+ /* drop frame if berr-reporting is off */
+ if (!(up->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING))
+ return;
+ }
+
+ /* allocate skb */
+ skb = alloc_can_skb(up->netdev, &cf);
+ if (!skb)
+ return;
+
+ /* fill the can frame */
+ cf->can_id = le32_to_cpu(m->msg.can_msg.id);
+ cf->can_dlc = len - (UCAN_IN_HDR_SIZE + sizeof(m->msg.can_msg.id));
+
+ if (cf->can_dlc > sizeof(m->msg.can_msg.data))
+ goto err_freeskb;
+
+ if (cf->can_dlc < 0)
+ goto err_freeskb;
+
+ if (cf->can_id & CAN_EFF_FLAG)
+ cf->can_id &=
+ (CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_ERR_FLAG);
+ else
+ cf->can_id &= (CAN_SFF_MASK | CAN_RTR_FLAG | CAN_ERR_FLAG);
+
+ if (cf->can_id & CAN_RTR_FLAG) {
+ cf->can_id |= CAN_RTR_FLAG;
+ cf->can_dlc = m->msg.can_rtr_msg.dlc;
+ } else {
+ memcpy(cf->data, m->msg.can_msg.data, cf->can_dlc);
+ }
+
+ /* pass it to Linux */
+ netif_receive_skb(skb);
+
+ /* don't count error frames as real packets */
+ if (!(canid & CAN_ERR_FLAG)) {
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+ }
+
+ return;
+err_freeskb:
+ kfree_skb(skb);
+}
+
+/* callback on reception of a USB message */
+static void ucan_read_bulk_callback(struct urb *urb)
+{
+ int ret;
+ int len;
+ int pos;
+ struct ucan *up = urb->context;
+ struct net_device *netdev = up->netdev;
+ struct ucan_message_in m;
+
+ /* check URB status */
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -EPIPE:
+ case -EPROTO:
+ case -ESHUTDOWN:
+ case -ETIME:
+ dev_dbg(&up->udev->dev, "%s ENOENT|ESHUTDOWN|ETIME\n",
+ __func__);
+ return;
+ default:
+ goto resubmit;
+ }
+
+ /* iterate over input */
+ pos = 0;
+ while (pos < urb->actual_length) {
+ /* check sanity (length of header) */
+ if ((urb->actual_length - pos) < UCAN_IN_HDR_SIZE) {
+ dev_warn(&up->udev->dev, "invalid input message %d; too short (no header)\n",
+ urb->actual_length);
+ goto resubmit;
+ }
+
+ /* copy the message header */
+ memcpy(&m, urb->transfer_buffer + pos, UCAN_IN_HDR_SIZE);
+ len = le16_to_cpu(m.len);
+
+ /* check sanity (length of content) */
+ if (urb->actual_length - pos < len) {
+ dev_warn(&up->udev->dev, "invalid input message al:%d pos:%d len:%d; too short (no data)\n",
+ urb->actual_length, pos, len);
+ print_hex_dump(KERN_WARNING, "raw data: ", DUMP_PREFIX_ADDRESS, 16, 1, urb->transfer_buffer, urb->actual_length, true);
+
+ goto resubmit;
+ }
+
+ // copy remainder of packet
+ memcpy(&m.msg, urb->transfer_buffer + UCAN_IN_HDR_SIZE + pos,
+ len - UCAN_IN_HDR_SIZE);
+
+ switch (__le16_to_cpu(m.type)) {
+ case UCAN_IN_RX:
+ ucan_rx_can_msg(up, &m);
+ break;
+ default:
+ dev_warn(&up->udev->dev,
+ "invalid input message type\n");
+ break;
+ }
+
+ /* proceed to next message */
+ pos += len;
+ /* align to 4 byte boundary */
+ pos = round_up(pos, 4);
+ }
+
+resubmit:
+ /* resubmit urb when done */
+ usb_fill_bulk_urb(urb, up->udev,
+ usb_rcvbulkpipe(up->udev, up->in_ep->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK),
+ urb->transfer_buffer, up->in_ep->wMaxPacketSize,
+ ucan_read_bulk_callback, up);
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+
+ if (ret == -ENODEV)
+ netif_device_detach(netdev);
+ else if (ret)
+ dev_err(&up->udev->dev,
+ "failed resubmitting read bulk urb: %d\n", ret);
+}
+
+/* callback after transmission of a USB message */
+static void ucan_write_bulk_callback(struct urb *urb)
+{
+ struct ucan_urb_context *context = urb->context;
+ struct ucan *up;
+
+ /* get the urb context */
+ if (WARN_ON(!context))
+ return;
+
+ up = context->up;
+
+ /* free up our allocated buffer */
+ usb_free_coherent(urb->dev, sizeof(struct ucan_message_out), urb->transfer_buffer,
+ urb->transfer_dma);
+
+ atomic_dec(&up->active_tx_urbs);
+
+ /* sanity check */
+ if (!netif_device_present(up->netdev))
+ return;
+
+ /* urb state check */
+ if (urb->status)
+ netdev_info(up->netdev, "Tx URB aborted (%d)\n", urb->status);
+
+ up->netdev->trans_start = jiffies;
+
+ /* update statistics */
+ up->netdev->stats.tx_packets++;
+ up->netdev->stats.tx_bytes += context->dlc;
+
+ /* echo can frame */
+ can_get_echo_skb(up->netdev, context->echo_index);
+
+ /* Release context */
+ context->echo_index = -1;
+
+ /* restart the queue if necessary */
+ ucan_tx_wake_queue_if_possible(up);
+}
+
+/* Open the network device */
+static int ucan_open(struct net_device *netdev)
+{
+ int i;
+ int ret;
+ void *buf;
+ u16 ctrlmode;
+ struct urb *urb;
+ struct ucan *up = netdev_priv(netdev);
+
+ /* call CAN layer open */
+ ret = open_candev(netdev);
+ if (ret)
+ goto err;
+
+ ret = -ENOMEM;
+
+ /* set the queue state as empty */
+ atomic_set(&up->free_slots, up->device_info.tx_fifo);
+
+ /* initialize IRQ endpoint */
+ up->irq_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!up->irq_urb)
+ goto err;
+
+ up->irq_data = kzalloc(up->irq_ep->wMaxPacketSize, GFP_KERNEL);
+ if (!up->irq_data) {
+ usb_free_urb(up->irq_urb);
+ goto err;
+ }
+
+ usb_fill_int_urb(up->irq_urb, up->udev,
+ usb_rcvintpipe(up->udev, up->irq_ep->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK),
+ up->irq_data, up->irq_ep->wMaxPacketSize,
+ ucan_read_irq_callback, up, up->irq_ep->bInterval);
+
+ ret = usb_submit_urb(up->irq_urb, GFP_KERNEL);
+ if (ret) {
+ kfree(up->irq_data);
+ usb_free_urb(up->irq_urb);
+ goto err;
+ }
+
+ /* initialize IN enpoint */
+ for (i = 0; i < MAX_RX_URBS; i++) {
+ ret = -ENOMEM;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ goto err;
+
+ buf = usb_alloc_coherent(up->udev, up->in_ep->wMaxPacketSize,
+ GFP_KERNEL, &urb->transfer_dma);
+ if (!buf) {
+ usb_free_urb(urb);
+ goto err;
+ }
+
+ usb_fill_bulk_urb(urb, up->udev,
+ usb_rcvbulkpipe(up->udev, up->in_ep->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK),
+ buf, up->in_ep->wMaxPacketSize, ucan_read_bulk_callback,
+ up);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(urb, &up->rx_urbs);
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+
+ if (ret) {
+ usb_unanchor_urb(urb);
+ usb_free_coherent(up->udev, up->in_ep->wMaxPacketSize,
+ buf, urb->transfer_dma);
+ usb_free_urb(urb);
+ goto err;
+ }
+
+ /* Drop reference, USB core will take care of freeing it */
+ usb_free_urb(urb);
+ }
+
+ /* check the control mode */
+ ctrlmode = 0;
+ if (up->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ ctrlmode |= UCAN_MODE_LOOPBACK;
+ if (up->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ ctrlmode |= UCAN_MODE_SILENT;
+ if (up->can.ctrlmode & CAN_CTRLMODE_3_SAMPLES)
+ ctrlmode |= UCAN_MODE_3_SAMPLES;
+ if (up->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+ ctrlmode |= UCAN_MODE_ONE_SHOT;
+
+ // Enable this in any case - filtering is down within the receive path
+ ctrlmode |= UCAN_MODE_BERR_REPORT;
+ up->ctl_msg_buffer->cmd_start.mode = cpu_to_le16(ctrlmode);
+
+ /* start the USB device */
+ ret = ucan_ctrl_command_out(up, UCAN_COMMAND_START, 0, 2);
+ if (ret < 0)
+ goto err;
+
+ up->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ /* start the network queue */
+ netif_start_queue(netdev);
+
+ return 0;
+
+err:
+ usb_kill_anchored_urbs(&up->rx_urbs);
+ return ret;
+}
+
+/* callback when Linux needs to send a can frame */
+static netdev_tx_t ucan_start_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ int i, ret, mlen;
+ struct urb *urb;
+ struct ucan_urb_context *context;
+ struct ucan *up = netdev_priv(netdev);
+ struct can_frame *cf = (struct can_frame *)skb->data;
+ struct ucan_message_out *m;
+
+ /* check skb */
+ if (can_dropped_invalid_skb(netdev, skb))
+ return NETDEV_TX_OK;
+
+ /* create a URB, and a buffer for it, and copy the data to the URB */
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb) {
+ netdev_err(netdev, "No memory left for URBs\n");
+ goto drop;
+ }
+
+ m = usb_alloc_coherent(up->udev, sizeof(struct ucan_message_out), GFP_ATOMIC, &urb->transfer_dma);
+ if (!m) {
+ netdev_err(netdev, "No memory left for USB buffer\n");
+ usb_free_urb(urb);
+ goto drop;
+ }
+
+ /* build the USB message */
+ if (cf->can_dlc > sizeof(m->msg.can_msg.data))
+ cf->can_dlc = sizeof(m->msg.can_msg.data);
+
+ m->type = cpu_to_le16(UCAN_OUT_TX);
+ m->msg.can_msg.id = cpu_to_le32(cf->can_id);
+
+ if (cf->can_id & CAN_RTR_FLAG) {
+ mlen = UCAN_OUT_LEN(m->msg.can_rtr_msg);
+ m->msg.can_rtr_msg.dlc = cf->can_dlc;
+ } else {
+ mlen = UCAN_OUT_HDR_SIZE +
+ sizeof(m->msg.can_msg.id) + cf->can_dlc;
+ memcpy(m->msg.can_msg.data, cf->data, cf->can_dlc);
+ }
+ m->len = cpu_to_le16(mlen);
+
+ /* allocate a context */
+ context = NULL;
+ for (i = 0; i < MAX_TX_URBS; i++) {
+ if (up->tx_urb_contexts[i].echo_index == -1) {
+ context = &up->tx_urb_contexts[i];
+ context->up = up;
+ context->echo_index = i;
+ context->dlc = cf->can_dlc;
+ atomic_inc(&up->active_tx_urbs);
+ break;
+ }
+ }
+
+ WARN_ON_ONCE(!context);
+ if (!context) {
+ usb_free_coherent(up->udev, sizeof(struct ucan_message_out), m, urb->transfer_dma);
+ usb_free_urb(urb);
+ goto drop;
+ }
+
+ /* build the urb */
+ usb_fill_bulk_urb(urb, up->udev,
+ usb_sndbulkpipe(up->udev, up->out_ep->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK),
+ m, mlen, ucan_write_bulk_callback, context);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(urb, &up->tx_urbs);
+ can_put_echo_skb(skb, up->netdev, context->echo_index);
+ /* transmit it */
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+
+ // cleanup urb
+ if (ret) {
+ usb_unanchor_urb(urb);
+ usb_free_coherent(up->udev, sizeof(struct ucan_message_out), m, urb->transfer_dma);
+
+ /* on error, clean up */
+ can_free_echo_skb(up->netdev, context->echo_index);
+ dev_kfree_skb(skb);
+
+ context->echo_index = -1;
+ atomic_dec(&up->active_tx_urbs);
+
+ if (ret == -ENODEV) {
+ netif_device_detach(up->netdev);
+ } else {
+ netdev_warn(up->netdev, "failed tx_urb %d\n", ret);
+ up->netdev->stats.tx_dropped++;
+ }
+ }
+ else {
+ atomic_dec(&up->free_slots);
+
+ up->netdev->trans_start = jiffies;
+
+ /* Slow down tx path, if fifo state is low */
+ if ((atomic_read(&up->active_tx_urbs) >= MAX_TX_URBS) ||
+ (atomic_read(&up->free_slots) < TX_QUEUE_STOP_THRESHOLD)) {
+ netif_stop_queue(netdev);
+ }
+ }
+
+ /* release ref, as we do not need the urb anymore */
+ usb_free_urb(urb);
+
+ return NETDEV_TX_OK;
+drop:
+ dev_kfree_skb(skb);
+ up->netdev->stats.tx_dropped++;
+
+ return NETDEV_TX_OK;
+}
+
+/* Device goes down
+ *
+ * Clean up used resources
+ */
+static int ucan_close(struct net_device *netdev)
+{
+ int ret;
+ struct ucan *up = netdev_priv(netdev);
+
+ up->can.state = CAN_STATE_STOPPED;
+
+ netif_stop_queue(netdev);
+
+ ret = ucan_ctrl_command_out( up, UCAN_COMMAND_STOP, 0, 0);
+ if (ret < 0)
+ dev_err(&up->udev->dev,
+ "Could not stop UCAN device, code: %d\n", ret);
+
+ ret = ucan_ctrl_command_out( up, UCAN_COMMAND_RESET, 0, 0);
+ if (ret < 0)
+ dev_err(&up->udev->dev,
+ "Could not reset UCAN device, code: %d\n", ret);
+
+ usb_kill_urb(up->irq_urb);
+ usb_kill_anchored_urbs(&up->rx_urbs);
+ usb_kill_anchored_urbs(&up->tx_urbs);
+ atomic_set(&up->active_tx_urbs, 0);
+ atomic_set(&up->free_slots, 0);
+
+ kfree(up->irq_data);
+
+ close_candev(up->netdev);
+ return 0;
+}
+
+/* CAN driver callbacks */
+static const struct net_device_ops ucan_netdev_ops = {
+ .ndo_open = ucan_open,
+ .ndo_stop = ucan_close,
+ .ndo_start_xmit = ucan_start_xmit,
+ .ndo_change_mtu = can_change_mtu,
+};
+
+/* Request to set bittiming
+ *
+ * This function generates an USB set bittiming message and transmits
+ * it to the device
+ */
+static int ucan_set_bittiming(struct net_device *netdev)
+{
+ int ret;
+ struct ucan *up = netdev_priv(netdev);
+
+ up->ctl_msg_buffer->cmd_set_bittiming.tq = cpu_to_le32(up->can.bittiming.tq);
+ up->ctl_msg_buffer->cmd_set_bittiming.brp = cpu_to_le16(up->can.bittiming.brp);
+ up->ctl_msg_buffer->cmd_set_bittiming.sample_point =
+ cpu_to_le32(up->can.bittiming.sample_point);
+ up->ctl_msg_buffer->cmd_set_bittiming.prop_seg = up->can.bittiming.prop_seg;
+ up->ctl_msg_buffer->cmd_set_bittiming.phase_seg1 = up->can.bittiming.phase_seg1;
+ up->ctl_msg_buffer->cmd_set_bittiming.phase_seg2 = up->can.bittiming.phase_seg2;
+ up->ctl_msg_buffer->cmd_set_bittiming.sjw = up->can.bittiming.sjw;
+
+ dev_dbg(&up->udev->dev,
+ "Setup bittiming\n"
+ " bitrate: %d\n"
+ " sample-point: %d\n"
+ " tq: %d\n"
+ " prop_seg: %d\n"
+ " phase_seg1 %d\n"
+ " phase_seg2 %d\n"
+ " sjw %d\n"
+ " brp %d\n",
+ up->can.bittiming.bitrate, up->can.bittiming.sample_point,
+ up->can.bittiming.tq, up->can.bittiming.prop_seg,
+ up->can.bittiming.phase_seg1, up->can.bittiming.phase_seg2,
+ up->can.bittiming.sjw, up->can.bittiming.brp);
+
+ ret = ucan_ctrl_command_out(up, UCAN_COMMAND_SET_BITTIMING,
+ 0, sizeof(up->ctl_msg_buffer->cmd_set_bittiming));
+ return (ret < 0) ? ret : 0;
+}
+
+/* Restart the device to get it out of BUS-OFF state.
+ * Called when the user runs "ip link set can1 type can restart".
+ */
+static int ucan_set_mode(struct net_device *netdev, enum can_mode mode)
+{
+ // XXX TODO
+ return -EOPNOTSUPP;
+}
+
+/* Probe the device, reset it and gather general device information */
+static int ucan_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret;
+ int i;
+ u32 protocol_version;
+ struct usb_device *udev;
+ struct net_device *netdev;
+ struct usb_host_interface *iface_desc;
+ struct ucan *up;
+ struct usb_endpoint_descriptor *ep;
+
+ udev = interface_to_usbdev(intf);
+
+ /* check if the interface is sane */
+ ret = -ENODEV;
+ iface_desc = intf->cur_altsetting;
+ if (!iface_desc)
+ goto err;
+
+ /* Infvalid interface Settings */
+ if (iface_desc->desc.bNumEndpoints != 3)
+ goto err;
+
+ dev_info(&udev->dev, "Found UCAN device on interface #%d\n",
+ iface_desc->desc.iInterface);
+
+ /* allocate driver resources */
+ ret = -ENOMEM;
+ netdev = alloc_candev(sizeof(struct ucan), MAX_TX_URBS);
+ if (!netdev)
+ goto err;
+
+ up = netdev_priv(netdev);
+
+ /* get interface descriptors */
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+ ep = &iface_desc->endpoint[i].desc;
+
+ if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != 0) &&
+ ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+ USB_ENDPOINT_XFER_BULK)) {
+ /* In Endpoint */
+ up->in_ep = ep;
+ } else if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ==
+ 0) &&
+ ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+ USB_ENDPOINT_XFER_BULK)) {
+ /* Out Endpoint */
+ up->out_ep = ep;
+ } else if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) !=
+ 0) &&
+ ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+ USB_ENDPOINT_XFER_INT)) {
+ /* Out Endpoint */
+ up->irq_ep = ep;
+ }
+ }
+
+ /* check if all interfaces are sane */
+ if (!up->in_ep || !up->out_ep || !up->irq_ep) {
+ dev_err(&udev->dev, "invalid endpoint configuration\n");
+ goto err_free_candev;
+ }
+ if (up->in_ep->wMaxPacketSize < sizeof(struct ucan_message_in)) {
+ dev_err(&udev->dev, "invalid in_ep MaxPacketSize\n");
+ goto err_free_candev;
+ }
+ if (up->out_ep->wMaxPacketSize < sizeof(struct ucan_message_out)) {
+ dev_err(&udev->dev, "invalid out_ep MaxPacketSize\n");
+ goto err_free_candev;
+ }
+ if (up->irq_ep->wMaxPacketSize < sizeof(u32)) {
+ dev_err(&udev->dev, "invalid irq_ep MaxPacketSize\n");
+ goto err_free_candev;
+ }
+
+ dev_dbg(&udev->dev,
+ "using EP %02x for input with max packet size 0x%x\n",
+ up->in_ep->bEndpointAddress, up->in_ep->wMaxPacketSize);
+ dev_dbg(&udev->dev,
+ "using EP %02x for output with max packet size 0x%x\n",
+ up->out_ep->bEndpointAddress, up->out_ep->wMaxPacketSize);
+ dev_dbg(&udev->dev,
+ "using EP %02x for irq input with max packet size 0x%x\n",
+ up->irq_ep->bEndpointAddress, up->irq_ep->wMaxPacketSize);
+
+ /* initialze data */
+ up->udev = udev;
+ up->intf = intf;
+ up->netdev = netdev;
+ up->intf_index = iface_desc->desc.bInterfaceNumber;
+
+ up->can.state = CAN_STATE_STOPPED;
+ up->can.bittiming_const = &up->device_info.bittiming_const;
+ up->can.do_set_bittiming = ucan_set_bittiming;
+ up->can.do_set_mode = &ucan_set_mode;
+ netdev->netdev_ops = &ucan_netdev_ops;
+
+ usb_set_intfdata(intf, up);
+ SET_NETDEV_DEV(netdev, &intf->dev);
+
+ /* allocate memory for command messages */
+ up->ctl_msg_buffer =
+ devm_kzalloc(&udev->dev, sizeof(ucan_ctl_payload_t), GFP_KERNEL);
+ if (!up->ctl_msg_buffer)
+ goto err_free_candev;
+
+ ret = ucan_ctrl_command_in(up, UCAN_COMMAND_GET, UCAN_COMMAND_GET_PROTOCOL_VERSION, sizeof(ucan_ctl_payload_t));
+
+ /* older firmware version do not support this command - those are not supported by this driver */
+ if (ret != 4) {
+ dev_err(&udev->dev,
+ "Could not read protocol version, ret=%d. The firmware on this device is too old, please update!\n",
+ ret);
+ if (ret >= 0)
+ ret = -EINVAL;
+ goto err_free_candev;
+ }
+
+ /* this driver currently supports protocol version 2 only */
+ protocol_version = le32_to_cpu(up->ctl_msg_buffer->protocol_version.version);
+ if (protocol_version != 2) {
+ dev_err(&udev->dev, "Device protocol version %d is not supported", protocol_version);
+ ret = -EINVAL;
+ goto err_free_candev;
+ }
+ dev_info(&udev->dev, "Device protocol version %d ok", protocol_version);
+
+ /* just print some device information - if available */
+ ret = ucan_device_request_in(up, UCAN_DEVICE_GET_FW_STRING, 0, sizeof(ucan_ctl_payload_t));
+ if (ret <= 0) {
+ dev_info(&udev->dev, "Device did not report firmware string\n");
+ } else {
+ /* ensure zero terminiation */
+ char str[sizeof(ucan_ctl_payload_t)+1];
+ strncpy(str, up->ctl_msg_buffer->raw.d, sizeof(ucan_ctl_payload_t));
+ dev_info(&udev->dev, "Device firmware string: %s\n", str);
+ }
+
+ /* device is compatible, reset it */
+ ret = ucan_ctrl_command_out( up, UCAN_COMMAND_RESET, 0, 0);
+ if (ret < 0)
+ goto err_free_candev;
+
+ /* gather device information */
+ ret = ucan_get_device_info(up);
+ if (ret)
+ goto err_free_candev;
+
+ dev_info(&up->udev->dev,
+ "Device Reports:\n"
+ " Frequency [Hz] : %d\n"
+ " TX Fifo [length] : %d\n"
+ " Time Segment 1 [min,max] : %d - %d\n"
+ " Time Segment 2 [min,max] : %d - %d\n"
+ " SWJ [max] : %d\n"
+ " Prescale [min-max,step] : %d - %d, %d\n"
+ " Supported modes :%s%s%s%s%s [%x]\n",
+ up->can.clock.freq, up->device_info.tx_fifo,
+ up->can.bittiming_const->tseg1_min,
+ up->can.bittiming_const->tseg1_max,
+ up->can.bittiming_const->tseg2_min,
+ up->can.bittiming_const->tseg2_max,
+ up->can.bittiming_const->sjw_max, up->can.bittiming_const->brp_min,
+ up->can.bittiming_const->brp_max, up->can.bittiming_const->brp_inc,
+ (up->can.ctrlmode_supported & CAN_CTRLMODE_LOOPBACK) ? " Loopback"
+ : "",
+ (up->can.ctrlmode_supported & CAN_CTRLMODE_LISTENONLY) ? " Silent"
+ : "",
+ (up->can.ctrlmode_supported & CAN_CTRLMODE_3_SAMPLES)
+ ? " 3-Sampling"
+ : "",
+ (up->can.ctrlmode_supported & CAN_CTRLMODE_ONE_SHOT) ? " OneShot"
+ : "",
+ (up->can.ctrlmode_supported & CAN_CTRLMODE_BERR_REPORTING)
+ ? " BusErrReport"
+ : "",
+ up->can.ctrlmode_supported);
+
+ atomic_set(&up->active_tx_urbs, 0);
+ for (i = 0; i < MAX_TX_URBS; i++)
+ up->tx_urb_contexts[i].echo_index = -1;
+
+ init_usb_anchor(&up->rx_urbs);
+ init_usb_anchor(&up->tx_urbs);
+
+ up->can.state = CAN_STATE_STOPPED;
+
+ /* register the device */
+ ret = register_candev(netdev);
+ if (ret)
+ goto err_free_candev;
+
+ /* success */
+ return 0;
+
+err_free_candev:
+ free_candev(netdev);
+err:
+ return ret;
+}
+
+/* disconnect the device */
+static void ucan_disconnect(struct usb_interface *intf)
+{
+ struct usb_device *udev;
+ struct ucan *up = usb_get_intfdata(intf);
+
+ udev = interface_to_usbdev(intf);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (up) {
+ unregister_netdev(up->netdev);
+ free_candev(up->netdev);
+ }
+}
+
+/* driver callbacks */
+static struct usb_driver ucan_driver = {
+ .name = "ucan_legacy",
+ .probe = ucan_probe,
+ .disconnect = ucan_disconnect,
+ .id_table = ucan_table,
+};
+
+module_usb_driver(ucan_driver);