From: "krumboeck@universalnet.at" <krumboeck@universalnet.at>
To: linux-can@vger.kernel.org
Cc: info@gerhard-bertelsmann.de, gediminas@8devices.com
Subject: [PATCH] usb2can: Add support for USB2CAN interface from 8 devices
Date: Sun, 02 Dec 2012 10:25:34 +0100 [thread overview]
Message-ID: <50BB1E8E.10809@universalnet.at> (raw)
Add device driver for USB2CAN interface from "8 devices" (http://www.8devices.com).
Signed-off-by: Bernd Krumboeck <krumboeck@universalnet.at>
---
drivers/net/can/usb/Kconfig | 6 +
drivers/net/can/usb/Makefile | 1 +
drivers/net/can/usb/usb2can.c | 1323 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 1330 insertions(+)
create mode 100644 drivers/net/can/usb/usb2can.c
diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
index a4e4bee..2068c99 100644
--- a/drivers/net/can/usb/Kconfig
+++ b/drivers/net/can/usb/Kconfig
@@ -48,4 +48,10 @@ config CAN_PEAK_USB
This driver supports the PCAN-USB and PCAN-USB Pro adapters
from PEAK-System Technik (http://www.peak-system.com).
+config CAN_USB2CAN
+ tristate "8 devices USB2CAN interface"
+ ---help---
+ This driver supports the USB2CAN interface
+ from 8 devices (http://www.8devices.com).
+
endmenu
diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
index 80a2ee4..3c0a378 100644
--- a/drivers/net/can/usb/Makefile
+++ b/drivers/net/can/usb/Makefile
@@ -6,5 +6,6 @@ obj-$(CONFIG_CAN_EMS_USB) += ems_usb.o
obj-$(CONFIG_CAN_ESD_USB2) += esd_usb2.o
obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb.o
obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
+obj-$(CONFIG_CAN_USB2CAN) += usb2can.o
ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG
diff --git a/drivers/net/can/usb/usb2can.c b/drivers/net/can/usb/usb2can.c
new file mode 100644
index 0000000..5a09141
--- /dev/null
+++ b/drivers/net/can/usb/usb2can.c
@@ -0,0 +1,1323 @@
+/*
+ * CAN driver for UAB "8 devices" USB2CAN converter
+ *
+ * Copyright (C) 2012 Bernd Krumboeck (krumboeck@universalnet.at)
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * This driver is based on the 3.2.0 version of drivers/net/can/usb/ems_usb.c
+ * and drivers/net/can/usb/esd_usb2.c
+ *
+ * Many thanks to Gerhard Bertelsmann (info@gerhard-bertelsmann.de)
+ * for testing and fixing this driver. Also many thanks to "8 devices",
+ * who were very cooperative and answered my questions.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/usb.h>
+
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+
+
+/* driver constants */
+#define MAX_RX_URBS 10
+#define MAX_TX_URBS 10
+#define RX_BUFFER_SIZE 64
+
+/* vendor and product id */
+#define USB2CAN_VENDOR_ID 0x0483
+#define USB2CAN_PRODUCT_ID 0x1234
+
+/* bittiming constants */
+#define USB2CAN_ABP_CLOCK 32000000
+#define USB2CAN_BAUD_MANUAL 0x09
+#define USB2CAN_TSEG1_MIN 1
+#define USB2CAN_TSEG1_MAX 16
+#define USB2CAN_TSEG2_MIN 1
+#define USB2CAN_TSEG2_MAX 8
+#define USB2CAN_SJW_MAX 4
+#define USB2CAN_BRP_MIN 1
+#define USB2CAN_BRP_MAX 1024
+#define USB2CAN_BRP_INC 1
+
+/* setup flags */
+#define USB2CAN_SILENT 0x00000001
+#define USB2CAN_LOOPBACK 0x00000002
+#define USB2CAN_DISABLE_AUTO_RESTRANS 0x00000004
+#define USB2CAN_STATUS_FRAME 0x00000008
+
+/* commands */
+#define USB2CAN_RESET 1
+#define USB2CAN_OPEN 2
+#define USB2CAN_CLOSE 3
+#define USB2CAN_SET_SPEED 4
+#define USB2CAN_SET_MASK_FILTER 5
+#define USB2CAN_GET_STATUS 6
+#define USB2CAN_GET_STATISTICS 7
+#define USB2CAN_GET_SERIAL 8
+#define USB2CAN_GET_SOFTW_VER 9
+#define USB2CAN_GET_HARDW_VER 10
+#define USB2CAN_RESET_TIMESTAMP 11
+#define USB2CAN_GET_SOFTW_HARDW_VER 12
+
+#define USB2CAN_CMD_START 0x11
+#define USB2CAN_CMD_END 0x22
+
+#define USB2CAN_CMD_SUCCESS 0
+#define USB2CAN_CMD_ERROR 255
+
+/* statistics */
+#define USB2CAN_STAT_RX_FRAMES 0
+#define USB2CAN_STAT_RX_BYTES 1
+#define USB2CAN_STAT_TX_FRAMES 2
+#define USB2CAN_STAT_TX_BYTES 3
+#define USB2CAN_STAT_OVERRUNS 4
+#define USB2CAN_STAT_WARNINGS 5
+#define USB2CAN_STAT_BUS_OFF 6
+#define USB2CAN_STAT_RESET_STAT 7
+
+/* frames */
+#define USB2CAN_DATA_START 0x55
+#define USB2CAN_DATA_END 0xAA
+
+#define USB2CAN_TYPE_CAN_FRAME 0
+#define USB2CAN_TYPE_ERROR_FRAME 3
+
+#define USB2CAN_EXTID 0x01
+#define USB2CAN_RTR 0x02
+#define USB2CAN_ERR_FLAG 0x04
+
+/* status */
+#define USB2CAN_STATUSMSG_OK 0x00 /* Normal condition. */
+#define USB2CAN_STATUSMSG_OVERRUN 0x01 /* Overrun occured when sending */
+#define USB2CAN_STATUSMSG_BUSLIGHT 0x02 /* Error counter has reached 96 */
+#define USB2CAN_STATUSMSG_BUSHEAVY 0x03 /* Error count. has reached 128 */
+#define USB2CAN_STATUSMSG_BUSOFF 0x04 /* Device is in BUSOFF */
+#define USB2CAN_STATUSMSG_STUFF 0x20 /* Stuff Error */
+#define USB2CAN_STATUSMSG_FORM 0x21 /* Form Error */
+#define USB2CAN_STATUSMSG_ACK 0x23 /* Ack Error */
+#define USB2CAN_STATUSMSG_BIT0 0x24 /* Bit1 Error */
+#define USB2CAN_STATUSMSG_BIT1 0x25 /* Bit0 Error */
+#define USB2CAN_STATUSMSG_CRC 0x26 /* CRC Error */
+
+#define USB2CAN_RP_MASK 0x7F /* Mask for Receive Error Bit */
+
+
+/* table of devices that work with this driver */
+static struct usb_device_id usb2can_table[] = {
+ { USB_DEVICE(USB2CAN_VENDOR_ID, USB2CAN_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, usb2can_table);
+
+struct usb2can_tx_urb_context {
+ struct usb2can *dev;
+
+ u32 echo_index;
+ u8 dlc;
+};
+
+/* Structure to hold all of our device specific stuff */
+struct usb2can {
+ struct can_priv can; /* must be the first member */
+
+ struct sk_buff *echo_skb[MAX_TX_URBS];
+
+ struct usb_device *udev;
+ struct net_device *netdev;
+
+ atomic_t active_tx_urbs;
+ struct usb_anchor tx_submitted;
+ struct usb2can_tx_urb_context tx_contexts[MAX_TX_URBS];
+
+ struct usb_anchor rx_submitted;
+
+ u8 *cmd_msg_buffer;
+
+ unsigned int free_slots; /* remember number of available slots */
+
+ unsigned int dar; /* disable automatic restransmission */
+
+ struct mutex usb2can_cmd_lock;
+};
+
+/* tx frame */
+struct __packed usb2can_tx_msg {
+ u8 begin;
+ u8 flags; /* RTR and EXT_ID flag */
+ __be32 id; /* upper 3 bits not used */
+ u8 dlc; /* data length code 0-8 bytes */
+ u8 data[8]; /* 64-bit data */
+ u8 end;
+};
+
+/* rx frame */
+struct __packed usb2can_rx_msg {
+ u8 begin;
+ u8 type; /* frame type */
+ u8 flags; /* RTR and EXT_ID flag */
+ __be32 id; /* upper 3 bits not used */
+ u8 dlc; /* data length code 0-8 bytes */
+ u8 data[8]; /* 64-bit data */
+ __be32 timestamp; /* 32-bit timestamp */
+ u8 end;
+};
+
+/* command frame */
+struct __packed usb2can_cmd_msg {
+ u8 begin;
+ u8 channel; /* unkown - always 0 */
+ u8 command; /* command to execute */
+ u8 opt1; /* optional parameter / return value */
+ u8 opt2; /* optional parameter 2 */
+ u8 data[10]; /* optional parameter and data */
+ u8 end;
+};
+
+static struct usb_driver usb2can_driver;
+
+static int usb2can_send_cmd_msg(struct usb2can *dev, u8 *msg, int size)
+{
+ int actual_length;
+
+ return usb_bulk_msg(dev->udev,
+ usb_sndbulkpipe(dev->udev, 4),
+ msg,
+ size,
+ &actual_length,
+ 1000);
+}
+
+static int usb2can_wait_cmd_msg(struct usb2can *dev, u8 *msg, int size,
+ int *actual_length)
+{
+ return usb_bulk_msg(dev->udev,
+ usb_rcvbulkpipe(dev->udev, 3),
+ msg,
+ size,
+ actual_length,
+ 1000);
+}
+
+/* Send command to device and receive result.
+ * Command was successful When opt1 = 0.
+ */
+static int usb2can_send_cmd(struct usb2can *dev, struct usb2can_cmd_msg *out,
+ struct usb2can_cmd_msg *in)
+{
+ int err;
+ int nBytesRead;
+ struct net_device *netdev;
+
+ netdev = dev->netdev;
+
+ out->begin = USB2CAN_CMD_START;
+ out->end = USB2CAN_CMD_END;
+
+ memcpy(&dev->cmd_msg_buffer[0], out,
+ sizeof(struct usb2can_cmd_msg));
+
+ mutex_lock(&dev->usb2can_cmd_lock);
+
+ err = usb2can_send_cmd_msg(dev, &dev->cmd_msg_buffer[0],
+ sizeof(struct usb2can_cmd_msg));
+ if (err < 0) {
+ dev_err(netdev->dev.parent, "sending command message failed\n");
+ return err;
+ }
+
+ err = usb2can_wait_cmd_msg(dev, &dev->cmd_msg_buffer[0],
+ sizeof(struct usb2can_cmd_msg), &nBytesRead);
+ if (err < 0) {
+ dev_err(netdev->dev.parent, "no command message answer\n");
+ return err;
+ }
+
+ mutex_unlock(&dev->usb2can_cmd_lock);
+
+ memcpy(in, &dev->cmd_msg_buffer[0],
+ sizeof(struct usb2can_cmd_msg));
+
+ if (in->begin != USB2CAN_CMD_START || in->end != USB2CAN_CMD_END ||
+ nBytesRead != 16 || in->opt1 != 0)
+ return -EPROTO;
+
+ return 0;
+}
+
+/* Send open command to device */
+static int usb2can_cmd_open(struct usb2can *dev)
+{
+ struct can_bittiming *bt = &dev->can.bittiming;
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+ u32 flags = 0x00000000;
+ u32 beflags;
+ u16 bebrp;
+ u32 ctrlmode = dev->can.ctrlmode;
+
+ if (ctrlmode & CAN_CTRLMODE_LOOPBACK)
+ flags |= USB2CAN_LOOPBACK;
+ if (ctrlmode & CAN_CTRLMODE_LISTENONLY)
+ flags |= USB2CAN_SILENT;
+ if (dev->dar == 1)
+ flags |= USB2CAN_DISABLE_AUTO_RESTRANS;
+
+ flags |= USB2CAN_STATUS_FRAME;
+
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_OPEN;
+ outmsg.opt1 = USB2CAN_BAUD_MANUAL;
+ outmsg.data[0] = (u8) (bt->prop_seg + bt->phase_seg1);
+ outmsg.data[1] = (u8) bt->phase_seg2;
+ outmsg.data[2] = (u8) bt->sjw;
+
+ /* BRP */
+ bebrp = cpu_to_be16((u16) bt->brp);
+ memcpy(&outmsg.data[3], &bebrp, sizeof(bebrp));
+
+ /* flags */
+ beflags = cpu_to_be32(flags);
+ memcpy(&outmsg.data[5], &beflags, sizeof(beflags));
+
+ return usb2can_send_cmd(dev, &outmsg, &inmsg);
+}
+
+/* Send close command to device */
+static int usb2can_cmd_close(struct usb2can *dev)
+{
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_CLOSE;
+
+ return usb2can_send_cmd(dev, &outmsg, &inmsg);
+}
+
+/* Get firmware and hardware version */
+static int usb2can_cmd_version(struct usb2can *dev, u32 *res)
+{
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+ int err = 0;
+ u32 *value;
+
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_GET_SOFTW_HARDW_VER;
+
+ err = usb2can_send_cmd(dev, &outmsg, &inmsg);
+ if (err)
+ return err;
+
+ value = (u32 *) inmsg.data;
+ *res = be32_to_cpu(*value);
+
+ return err;
+}
+
+/* Get firmware version */
+static ssize_t show_firmware(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+ int err = 0;
+ u16 *value;
+ u16 result;
+ struct usb_interface *intf = to_usb_interface(d);
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_GET_SOFTW_VER;
+
+ err = usb2can_send_cmd(dev, &outmsg, &inmsg);
+ if (err)
+ return -EIO;
+
+ value = (u16 *) inmsg.data;
+ result = be16_to_cpu(*value);
+
+ return sprintf(buf, "%d.%d\n", (u8)(result>>8), (u8)result);
+}
+
+/* Get hardware version */
+static ssize_t show_hardware(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+ int err = 0;
+ u16 *value;
+ u16 result;
+ struct usb_interface *intf = to_usb_interface(d);
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_GET_HARDW_VER;
+
+ err = usb2can_send_cmd(dev, &outmsg, &inmsg);
+ if (err)
+ return -EIO;
+
+ value = (u16 *) inmsg.data;
+ result = be16_to_cpu(*value);
+
+ return sprintf(buf, "%d.%d\n", (u8)(result>>8), (u8)result);
+}
+
+/* Get status
+ *
+ * Returns:
+ * STATUS_NONE 0x00000000
+ * STATUS_BUS_OFF 0x80000000
+ * STATUS_PASSIVE 0x40000000
+ * STATUS_BUS_WARN 0x20000000
+ * STATUS_ACTIVE 0x10000000
+ * STATUS_PHY_FAULT 0x08000000
+ * STATUS_PHY_H 0x04000000
+ * STATUS_PHY_L 0x02000000
+ * STATUS_SLEEPING 0x01000000
+ * STATUS_STOPPED 0x00800000
+ */
+static ssize_t show_status(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+ int err = 0;
+ u32 *value;
+ u32 result;
+ struct usb_interface *intf = to_usb_interface(d);
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_GET_STATUS;
+
+ err = usb2can_send_cmd(dev, &outmsg, &inmsg);
+ if (err)
+ return -EIO;
+
+ value = (u32 *) inmsg.data;
+ result = be32_to_cpu(*value);
+
+ return sprintf(buf, "0x%08x\n", result);
+}
+
+/* Get statistic values */
+static ssize_t show_statistics(struct device *d, struct device_attribute *attr,
+ u8 statistic, char *buf)
+{
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+ int err = 0;
+ u32 *value;
+ u32 result;
+ struct usb_interface *intf = to_usb_interface(d);
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_GET_STATISTICS;
+ outmsg.opt1 = statistic;
+
+ err = usb2can_send_cmd(dev, &outmsg, &inmsg);
+ if (err)
+ return -EIO;
+
+ value = (u32 *) inmsg.data;
+ result = be32_to_cpu(*value);
+
+ return sprintf(buf, "%d\n", result);
+}
+
+static ssize_t show_rx_frames(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return show_statistics(d, attr, USB2CAN_STAT_RX_FRAMES, buf);
+}
+
+static ssize_t show_rx_bytes(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return show_statistics(d, attr, USB2CAN_STAT_RX_BYTES, buf);
+}
+
+static ssize_t show_tx_frames(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return show_statistics(d, attr, USB2CAN_STAT_TX_FRAMES, buf);
+}
+
+static ssize_t show_tx_bytes(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return show_statistics(d, attr, USB2CAN_STAT_RX_BYTES, buf);
+}
+
+static ssize_t show_overruns(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return show_statistics(d, attr, USB2CAN_STAT_OVERRUNS, buf);
+}
+
+static ssize_t show_warnings(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return show_statistics(d, attr, USB2CAN_STAT_WARNINGS, buf);
+}
+
+static ssize_t show_bus_off(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ return show_statistics(d, attr, USB2CAN_STAT_BUS_OFF, buf);
+}
+
+/* Reset statistics */
+static ssize_t reset_statistics(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb2can_cmd_msg outmsg;
+ struct usb2can_cmd_msg inmsg;
+ int err = 0;
+ struct usb_interface *intf = to_usb_interface(d);
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ if (buf[0] == '1') {
+ memset(&outmsg, 0, sizeof(struct usb2can_cmd_msg));
+ outmsg.command = USB2CAN_GET_STATISTICS;
+ outmsg.opt1 = USB2CAN_STAT_RESET_STAT;
+
+ err = usb2can_send_cmd(dev, &outmsg, &inmsg);
+ if (err)
+ return -EIO;
+ }
+
+ return count;
+}
+
+/* Get "disable automatic retransmission" flag */
+static ssize_t show_dar(struct device *d, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(d);
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ return sprintf(buf, "%d\n", dev->dar);
+}
+
+/* Set "disable automatic retransmission" flag */
+static ssize_t set_dar(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(d);
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ if (dev->can.state != CAN_STATE_STOPPED) {
+ dev_err(&intf->dev,
+ "DAR flag can only be set when device is stopped\n");
+ return -EIO;
+ }
+
+ if (buf[0] == '0')
+ dev->dar = 0;
+ else if (buf[0] == '1')
+ dev->dar = 1;
+ else
+ return -EIO;
+
+ return count;
+}
+
+static DEVICE_ATTR(firmware, S_IRUGO, show_firmware, NULL);
+static DEVICE_ATTR(hardware, S_IRUGO, show_hardware, NULL);
+static DEVICE_ATTR(can_state, S_IRUGO, show_status, NULL);
+static DEVICE_ATTR(can_rx_frames, S_IRUGO, show_rx_frames, NULL);
+static DEVICE_ATTR(can_rx_bytes, S_IRUGO, show_rx_bytes, NULL);
+static DEVICE_ATTR(can_tx_frames, S_IRUGO, show_tx_frames, NULL);
+static DEVICE_ATTR(can_tx_bytes, S_IRUGO, show_tx_bytes, NULL);
+static DEVICE_ATTR(can_overruns, S_IRUGO, show_overruns, NULL);
+static DEVICE_ATTR(can_warnings, S_IRUGO, show_warnings, NULL);
+static DEVICE_ATTR(can_bus_off_counter, S_IRUGO, show_bus_off, NULL);
+
+static DEVICE_ATTR(can_reset_statistics, S_IWUSR, NULL, reset_statistics);
+
+static DEVICE_ATTR(can_disable_automatic_retransmission, S_IRUGO | S_IWUSR,
+ show_dar, set_dar);
+
+/* Set network device mode
+ *
+ * Maybe we should leave this function empty, because the device
+ * set mode variable with open command.
+ */
+static int usb2can_set_mode(struct net_device *netdev, enum can_mode mode)
+{
+ struct usb2can *dev = netdev_priv(netdev);
+ int err = 0;
+
+ switch (mode) {
+ case CAN_MODE_START:
+ err = usb2can_cmd_open(dev);
+ if (err)
+ dev_warn(netdev->dev.parent, "couldn't start device");
+
+ if (netif_queue_stopped(netdev))
+ netif_wake_queue(netdev);
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+/* Empty function: We set bittiming when we start the interface.
+ * This is a firmware limitation.
+ */
+static int usb2can_set_bittiming(struct net_device *netdev)
+{
+ return 0;
+}
+
+/* Read data and status frames */
+static void usb2can_rx_can_msg(struct usb2can *dev, struct usb2can_rx_msg *msg)
+{
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ int i;
+ struct net_device_stats *stats = &dev->netdev->stats;
+
+ skb = alloc_can_skb(dev->netdev, &cf);
+ if (skb == NULL)
+ return;
+
+ if (msg->type == USB2CAN_TYPE_CAN_FRAME) {
+ cf->can_id = be32_to_cpu(msg->id);
+ cf->can_dlc = get_can_dlc(msg->dlc & 0xF);
+
+ if (msg->flags & USB2CAN_EXTID)
+ cf->can_id |= CAN_EFF_FLAG;
+
+ if (msg->flags & USB2CAN_RTR)
+ cf->can_id |= CAN_RTR_FLAG;
+ else
+ for (i = 0; i < cf->can_dlc; i++)
+ cf->data[i] = msg->data[i];
+ } else if (msg->type == USB2CAN_TYPE_ERROR_FRAME &&
+ msg->flags == USB2CAN_ERR_FLAG) {
+
+ /* Error message:
+ byte 0: Status
+ byte 1: bit 7: Receive Passive
+ byte 1: bit 0-6: Receive Error Counter
+ byte 2: Transmit Error Counter
+ byte 3: Always 0 (maybe reserved for future use)
+ */
+
+ u8 state = msg->data[0];
+ u8 rxerr = msg->data[1] & USB2CAN_RP_MASK;
+ u8 txerr = msg->data[2];
+ int rx_errors = 0;
+ int tx_errors = 0;
+
+ dev->can.can_stats.bus_error++;
+
+ cf->can_id = CAN_ERR_FLAG;
+ cf->can_dlc = CAN_ERR_DLC;
+
+ switch (state) {
+ case USB2CAN_STATUSMSG_OK:
+ dev->can.state = CAN_STATE_ERROR_ACTIVE;
+ cf->can_id |= CAN_ERR_PROT;
+ cf->data[2] = CAN_ERR_PROT_ACTIVE;
+ break;
+ case USB2CAN_STATUSMSG_BUSOFF:
+ dev->can.state = CAN_STATE_BUS_OFF;
+ cf->can_id |= CAN_ERR_BUSOFF;
+ can_bus_off(dev->netdev);
+ break;
+ default:
+ dev->can.state = CAN_STATE_ERROR_WARNING;
+ cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+ break;
+ }
+
+ switch (state) {
+ case USB2CAN_STATUSMSG_ACK:
+ cf->can_id |= CAN_ERR_ACK;
+ tx_errors = 1;
+ break;
+ case USB2CAN_STATUSMSG_CRC:
+ cf->data[2] |= CAN_ERR_PROT_BIT;
+ rx_errors = 1;
+ break;
+ case USB2CAN_STATUSMSG_BIT0:
+ cf->data[2] |= CAN_ERR_PROT_BIT0;
+ tx_errors = 1;
+ break;
+ case USB2CAN_STATUSMSG_BIT1:
+ cf->data[2] |= CAN_ERR_PROT_BIT1;
+ tx_errors = 1;
+ break;
+ case USB2CAN_STATUSMSG_FORM:
+ cf->data[2] |= CAN_ERR_PROT_FORM;
+ rx_errors = 1;
+ break;
+ case USB2CAN_STATUSMSG_STUFF:
+ cf->data[2] |= CAN_ERR_PROT_STUFF;
+ rx_errors = 1;
+ break;
+ case USB2CAN_STATUSMSG_OVERRUN:
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = (txerr > rxerr) ?
+ CAN_ERR_CRTL_TX_OVERFLOW :
+ CAN_ERR_CRTL_RX_OVERFLOW;
+ cf->data[2] |= CAN_ERR_PROT_OVERLOAD;
+ stats->rx_over_errors++;
+ break;
+ case USB2CAN_STATUSMSG_BUSLIGHT:
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = (txerr > rxerr) ?
+ CAN_ERR_CRTL_TX_WARNING :
+ CAN_ERR_CRTL_RX_WARNING;
+ dev->can.can_stats.error_warning++;
+ break;
+ case USB2CAN_STATUSMSG_BUSHEAVY:
+ cf->can_id |= CAN_ERR_CRTL;
+ cf->data[1] = (txerr > rxerr) ?
+ CAN_ERR_CRTL_TX_PASSIVE :
+ CAN_ERR_CRTL_RX_PASSIVE;
+ dev->can.can_stats.error_passive++;
+ break;
+ default:
+ cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+ break;
+ }
+
+ if (tx_errors) {
+ cf->data[2] |= CAN_ERR_PROT_TX;
+ stats->tx_errors++;
+ }
+
+ if (rx_errors)
+ stats->rx_errors++;
+
+ cf->data[6] = txerr;
+ cf->data[7] = rxerr;
+
+ } else {
+ dev_warn(dev->udev->dev.parent, "frame type %d unknown",
+ msg->type);
+ }
+
+ netif_rx(skb);
+
+ stats->rx_packets++;
+ stats->rx_bytes += cf->can_dlc;
+}
+
+/* Callback for reading data from device
+ *
+ * Check urb status, call read function and resubmit urb read operation.
+ */
+static void usb2can_read_bulk_callback(struct urb *urb)
+{
+ struct usb2can *dev = urb->context;
+ struct net_device *netdev;
+ int retval;
+ int pos = 0;
+
+ netdev = dev->netdev;
+
+ if (!netif_device_present(netdev))
+ return;
+
+ switch (urb->status) {
+ case 0: /* success */
+ break;
+
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+
+ default:
+ dev_info(netdev->dev.parent, "Rx URB aborted (%d)\n",
+ urb->status);
+ goto resubmit_urb;
+ }
+
+ while (pos < urb->actual_length) {
+ struct usb2can_rx_msg *msg;
+
+ msg = (struct usb2can_rx_msg *)(urb->transfer_buffer + pos);
+
+ usb2can_rx_can_msg(dev, msg);
+
+ pos += sizeof(struct usb2can_rx_msg);
+
+ if (pos > urb->actual_length) {
+ dev_err(dev->udev->dev.parent, "format error\n");
+ break;
+ }
+ }
+
+resubmit_urb:
+ usb_fill_bulk_urb(urb, dev->udev, usb_rcvbulkpipe(dev->udev, 1),
+ urb->transfer_buffer, RX_BUFFER_SIZE,
+ usb2can_read_bulk_callback, dev);
+
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+
+ if (retval == -ENODEV)
+ netif_device_detach(netdev);
+ else if (retval)
+ dev_err(netdev->dev.parent,
+ "failed resubmitting read bulk urb: %d\n", retval);
+}
+
+/* Callback handler for write operations
+ *
+ * Free allocated buffers, check transmit status and
+ * calculate statistic.
+ */
+static void usb2can_write_bulk_callback(struct urb *urb)
+{
+ struct usb2can_tx_urb_context *context = urb->context;
+ struct usb2can *dev;
+ struct net_device *netdev;
+
+ BUG_ON(!context);
+
+ dev = context->dev;
+ netdev = dev->netdev;
+
+ /* free up our allocated buffer */
+ usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+
+ atomic_dec(&dev->active_tx_urbs);
+
+ if (!netif_device_present(netdev))
+ return;
+
+ if (urb->status)
+ dev_info(netdev->dev.parent, "Tx URB aborted (%d)\n",
+ urb->status);
+
+ netdev->trans_start = jiffies;
+
+ netdev->stats.tx_packets++;
+ netdev->stats.tx_bytes += context->dlc;
+
+ can_get_echo_skb(netdev, context->echo_index);
+
+ /* Release context */
+ context->echo_index = MAX_TX_URBS;
+
+ if (netif_queue_stopped(netdev))
+ netif_wake_queue(netdev);
+}
+
+/* Send data to device */
+static netdev_tx_t usb2can_start_xmit(struct sk_buff *skb,
+ struct net_device *netdev)
+{
+ struct usb2can *dev = netdev_priv(netdev);
+ struct net_device_stats *stats = &netdev->stats;
+ struct can_frame *cf = (struct can_frame *)skb->data;
+ struct usb2can_tx_msg *msg;
+ struct urb *urb;
+ struct usb2can_tx_urb_context *context = NULL;
+ u8 *buf;
+ int i, err;
+ size_t size = sizeof(struct usb2can_tx_msg);
+
+ 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) {
+ dev_err(netdev->dev.parent, "No memory left for URBs\n");
+ goto nomem;
+ }
+
+ buf = usb_alloc_coherent(dev->udev, size, GFP_ATOMIC,
+ &urb->transfer_dma);
+ if (!buf) {
+ dev_err(netdev->dev.parent, "No memory left for USB buffer\n");
+ usb_free_urb(urb);
+ goto nomem;
+ }
+
+ memset(buf, 0, size);
+
+ msg = (struct usb2can_tx_msg *)buf;
+ msg->begin = USB2CAN_DATA_START;
+ msg->flags = 0x00;
+
+ if (cf->can_id & CAN_RTR_FLAG)
+ msg->flags |= USB2CAN_RTR;
+
+ if (cf->can_id & CAN_EFF_FLAG)
+ msg->flags |= USB2CAN_EXTID;
+
+ msg->id = cpu_to_be32(cf->can_id & CAN_ERR_MASK);
+
+ msg->dlc = cf->can_dlc;
+
+ for (i = 0; i < cf->can_dlc; i++)
+ msg->data[i] = cf->data[i];
+
+ msg->end = USB2CAN_DATA_END;
+
+
+ for (i = 0; i < MAX_TX_URBS; i++) {
+ if (dev->tx_contexts[i].echo_index == MAX_TX_URBS) {
+ context = &dev->tx_contexts[i];
+ break;
+ }
+ }
+
+ /* May never happen! When this happens we'd more URBs in flight as
+ * allowed (MAX_TX_URBS).
+ */
+ if (!context) {
+ usb_unanchor_urb(urb);
+ usb_free_coherent(dev->udev, size, buf, urb->transfer_dma);
+
+ dev_warn(netdev->dev.parent, "couldn't find free context");
+
+ return NETDEV_TX_BUSY;
+ }
+
+ context->dev = dev;
+ context->echo_index = i;
+ context->dlc = cf->can_dlc;
+
+ usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, 2), buf,
+ size, usb2can_write_bulk_callback, context);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(urb, &dev->tx_submitted);
+
+ can_put_echo_skb(skb, netdev, context->echo_index);
+
+ atomic_inc(&dev->active_tx_urbs);
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (unlikely(err)) {
+ can_free_echo_skb(netdev, context->echo_index);
+
+ usb_unanchor_urb(urb);
+ usb_free_coherent(dev->udev, size, buf, urb->transfer_dma);
+ dev_kfree_skb(skb);
+
+ atomic_dec(&dev->active_tx_urbs);
+
+ if (err == -ENODEV) {
+ netif_device_detach(netdev);
+ } else {
+ dev_warn(netdev->dev.parent, "failed tx_urb %d\n", err);
+
+ stats->tx_dropped++;
+ }
+ } else {
+ netdev->trans_start = jiffies;
+
+ /* Slow down tx path */
+ if (atomic_read(&dev->active_tx_urbs) >= MAX_TX_URBS ||
+ dev->free_slots < 5) {
+ netif_stop_queue(netdev);
+ }
+ }
+
+ /* Release our reference to this URB, the USB core will eventually free
+ * it entirely.
+ */
+ usb_free_urb(urb);
+
+ return NETDEV_TX_OK;
+
+nomem:
+ dev_kfree_skb(skb);
+ stats->tx_dropped++;
+
+ return NETDEV_TX_OK;
+}
+
+
+/* Start USB device */
+static int usb2can_start(struct usb2can *dev)
+{
+ struct net_device *netdev = dev->netdev;
+ int err, i;
+
+ dev->free_slots = 15; /* initial size */
+
+ for (i = 0; i < MAX_RX_URBS; i++) {
+ struct urb *urb = NULL;
+ u8 *buf = NULL;
+
+ /* create a URB, and a buffer for it */
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ dev_err(netdev->dev.parent,
+ "No memory left for URBs\n");
+ return -ENOMEM;
+ }
+
+ buf = usb_alloc_coherent(dev->udev, RX_BUFFER_SIZE, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!buf) {
+ dev_err(netdev->dev.parent,
+ "No memory left for USB buffer\n");
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+
+ usb_fill_bulk_urb(urb, dev->udev, usb_rcvbulkpipe(dev->udev, 1),
+ buf, RX_BUFFER_SIZE,
+ usb2can_read_bulk_callback, dev);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(urb, &dev->rx_submitted);
+
+ err = usb_submit_urb(urb, GFP_KERNEL);
+ if (err) {
+ if (err == -ENODEV)
+ netif_device_detach(dev->netdev);
+
+ usb_unanchor_urb(urb);
+ usb_free_coherent(dev->udev, RX_BUFFER_SIZE, buf,
+ urb->transfer_dma);
+ break;
+ }
+
+ /* Drop reference, USB core will take care of freeing it */
+ usb_free_urb(urb);
+ }
+
+ /* Did we submit any URBs */
+ if (i == 0) {
+ dev_warn(netdev->dev.parent, "couldn't setup read URBs\n");
+ return err;
+ }
+
+ /* Warn if we've couldn't transmit all the URBs */
+ if (i < MAX_RX_URBS)
+ dev_warn(netdev->dev.parent, "rx performance may be slow\n");
+
+ err = usb2can_cmd_open(dev);
+ if (err)
+ goto failed;
+
+ dev->can.state = CAN_STATE_ERROR_ACTIVE;
+
+ return 0;
+
+failed:
+ if (err == -ENODEV)
+ netif_device_detach(dev->netdev);
+
+ dev_warn(netdev->dev.parent, "couldn't submit control: %d\n", err);
+
+ return err;
+}
+
+/* Open USB device */
+static int usb2can_open(struct net_device *netdev)
+{
+ struct usb2can *dev = netdev_priv(netdev);
+ int err;
+
+ /* common open */
+ err = open_candev(netdev);
+ if (err)
+ return err;
+
+ /* finally start device */
+ err = usb2can_start(dev);
+ if (err) {
+ if (err == -ENODEV)
+ netif_device_detach(dev->netdev);
+
+ dev_warn(netdev->dev.parent, "couldn't start device: %d\n",
+ err);
+
+ close_candev(netdev);
+
+ return err;
+ }
+
+ netif_start_queue(netdev);
+
+ return 0;
+}
+
+static void unlink_all_urbs(struct usb2can *dev)
+{
+ int i;
+
+ usb_kill_anchored_urbs(&dev->rx_submitted);
+
+ usb_kill_anchored_urbs(&dev->tx_submitted);
+ atomic_set(&dev->active_tx_urbs, 0);
+
+ for (i = 0; i < MAX_TX_URBS; i++)
+ dev->tx_contexts[i].echo_index = MAX_TX_URBS;
+}
+
+/* Close USB device */
+static int usb2can_close(struct net_device *netdev)
+{
+ struct usb2can *dev = netdev_priv(netdev);
+ int err = 0;
+
+ /* Send CLOSE command to CAN controller */
+ err = usb2can_cmd_close(dev);
+ if (err)
+ dev_warn(netdev->dev.parent, "couldn't stop device");
+
+ dev->can.state = CAN_STATE_STOPPED;
+
+ /* Stop polling */
+ unlink_all_urbs(dev);
+
+ netif_stop_queue(netdev);
+
+ close_candev(netdev);
+
+ return err;
+}
+
+static const struct net_device_ops usb2can_netdev_ops = {
+ .ndo_open = usb2can_open,
+ .ndo_stop = usb2can_close,
+ .ndo_start_xmit = usb2can_start_xmit,
+};
+
+static struct can_bittiming_const usb2can_bittiming_const = {
+ .name = "usb2can",
+ .tseg1_min = USB2CAN_TSEG1_MIN,
+ .tseg1_max = USB2CAN_TSEG1_MAX,
+ .tseg2_min = USB2CAN_TSEG2_MIN,
+ .tseg2_max = USB2CAN_TSEG2_MAX,
+ .sjw_max = USB2CAN_SJW_MAX,
+ .brp_min = USB2CAN_BRP_MIN,
+ .brp_max = USB2CAN_BRP_MAX,
+ .brp_inc = USB2CAN_BRP_INC,
+};
+
+/* Probe USB device
+ *
+ * Check device and firmware.
+ * Set supported modes and bittiming constants.
+ * Allocate some memory.
+ */
+static int usb2can_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct net_device *netdev;
+ struct usb2can *dev;
+ int i, err = -ENOMEM;
+ u32 version;
+ char buf[18];
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+
+ /* product id looks strange, better we also check iProdukt string */
+ if (usb_string(usbdev, usbdev->descriptor.iProduct, buf,
+ sizeof(buf)) > 0 && strcmp(buf, "USB2CAN converter")) {
+ dev_info(&usbdev->dev, "ignoring: not an USB2CAN converter\n");
+ return -ENODEV;
+ }
+
+ netdev = alloc_candev(sizeof(struct usb2can), MAX_TX_URBS);
+ if (!netdev) {
+ dev_err(&intf->dev, "Couldn't alloc candev\n");
+ return -ENOMEM;
+ }
+
+ dev = netdev_priv(netdev);
+
+ dev->udev = usbdev;
+ dev->netdev = netdev;
+
+ dev->dar = 0;
+
+ dev->can.state = CAN_STATE_STOPPED;
+ dev->can.clock.freq = USB2CAN_ABP_CLOCK;
+ dev->can.bittiming_const = &usb2can_bittiming_const;
+ dev->can.do_set_bittiming = usb2can_set_bittiming;
+ dev->can.do_set_mode = usb2can_set_mode;
+ dev->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK |
+ CAN_CTRLMODE_LISTENONLY;
+
+ netdev->netdev_ops = &usb2can_netdev_ops;
+
+ netdev->flags |= IFF_ECHO; /* we support local echo */
+
+ init_usb_anchor(&dev->rx_submitted);
+
+ init_usb_anchor(&dev->tx_submitted);
+ atomic_set(&dev->active_tx_urbs, 0);
+
+ for (i = 0; i < MAX_TX_URBS; i++)
+ dev->tx_contexts[i].echo_index = MAX_TX_URBS;
+
+ dev->cmd_msg_buffer = kzalloc(sizeof(struct usb2can_cmd_msg),
+ GFP_KERNEL);
+ if (!dev->cmd_msg_buffer) {
+ dev_err(&intf->dev, "Couldn't alloc Tx buffer\n");
+ goto cleanup_candev;
+ }
+
+ usb_set_intfdata(intf, dev);
+
+ SET_NETDEV_DEV(netdev, &intf->dev);
+
+ mutex_init(&dev->usb2can_cmd_lock);
+
+ err = usb2can_cmd_version(dev, &version);
+ if (err) {
+ dev_err(netdev->dev.parent, "can't get firmware version\n");
+ goto cleanup_cmd_msg_buffer;
+ } else {
+ dev_info(netdev->dev.parent,
+ "firmware: %d.%d, hardware: %d.%d\n",
+ (u8)(version>>24), (u8)(version>>16),
+ (u8)(version>>8), (u8)version);
+ }
+
+ err = register_candev(netdev);
+ if (err) {
+ dev_err(netdev->dev.parent,
+ "couldn't register CAN device: %d\n", err);
+ goto cleanup_cmd_msg_buffer;
+ }
+
+ if (device_create_file(&intf->dev, &dev_attr_firmware))
+ dev_err(&intf->dev,
+ "Couldn't create device file for firmware\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_hardware))
+ dev_err(&intf->dev,
+ "Couldn't create device file for hardware\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_state))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_state\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_rx_frames))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_rx_frames\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_rx_bytes))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_rx_bytes\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_tx_frames))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_tx_frames\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_tx_bytes))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_tx_bytes\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_overruns))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_overruns\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_warnings))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_warnings\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_bus_off_counter))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_bus_off_counter\n");
+
+ if (device_create_file(&intf->dev, &dev_attr_can_reset_statistics))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_reset_statistics\n");
+
+ if (device_create_file(&intf->dev,
+ &dev_attr_can_disable_automatic_retransmission))
+ dev_err(&intf->dev,
+ "Couldn't create device file for can_disable_automatic_retransmission\n");
+
+ /* let the user know what node this device is now attached to */
+ dev_info(netdev->dev.parent, "device registered as %s\n", netdev->name);
+ return 0;
+
+cleanup_cmd_msg_buffer:
+ kfree(dev->cmd_msg_buffer);
+
+cleanup_candev:
+ free_candev(netdev);
+
+ return err;
+
+}
+
+/* Called by the usb core when driver is unloaded or device is removed */
+static void usb2can_disconnect(struct usb_interface *intf)
+{
+ struct usb2can *dev = usb_get_intfdata(intf);
+
+ device_remove_file(&intf->dev, &dev_attr_firmware);
+ device_remove_file(&intf->dev, &dev_attr_hardware);
+ device_remove_file(&intf->dev, &dev_attr_can_state);
+ device_remove_file(&intf->dev, &dev_attr_can_rx_frames);
+ device_remove_file(&intf->dev, &dev_attr_can_rx_bytes);
+ device_remove_file(&intf->dev, &dev_attr_can_tx_frames);
+ device_remove_file(&intf->dev, &dev_attr_can_tx_bytes);
+ device_remove_file(&intf->dev, &dev_attr_can_overruns);
+ device_remove_file(&intf->dev, &dev_attr_can_warnings);
+ device_remove_file(&intf->dev, &dev_attr_can_bus_off_counter);
+ device_remove_file(&intf->dev, &dev_attr_can_reset_statistics);
+ device_remove_file(&intf->dev,
+ &dev_attr_can_disable_automatic_retransmission);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (dev) {
+ dev_info(&intf->dev, "disconnect %s\n", dev->netdev->name);
+
+ unregister_netdev(dev->netdev);
+ free_candev(dev->netdev);
+
+ unlink_all_urbs(dev);
+ }
+
+}
+
+static struct usb_driver usb2can_driver = {
+ .name = "usb2can",
+ .probe = usb2can_probe,
+ .disconnect = usb2can_disconnect,
+ .id_table = usb2can_table,
+};
+
+module_usb_driver(usb2can_driver);
+
+MODULE_AUTHOR("Bernd Krumboeck <krumboeck@universalnet.at>");
+MODULE_DESCRIPTION("CAN driver for UAB 8 devices USB2CAN interfaces");
+MODULE_LICENSE("GPL v2");
+
--
1.7.10.4
next reply other threads:[~2012-12-02 8:26 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-12-02 9:25 krumboeck [this message]
2012-12-02 10:36 ` [PATCH] usb2can: Add support for USB2CAN interface from 8 devices Oliver Hartkopp
2012-12-02 11:45 ` Kurt Van Dijck
2012-12-02 13:35 ` Wolfgang Grandegger
2012-12-03 0:43 ` krumboeck
2012-12-03 7:26 ` Wolfgang Grandegger
[not found] ` <50BCF810.6060108@universalnet.at>
2012-12-03 20:12 ` Wolfgang Grandegger
2012-12-03 20:41 ` krumboeck
2012-12-03 8:15 ` Wolfgang Grandegger
-- strict thread matches above, loose matches on Subject: below --
2012-12-13 7:44 "Bernd Krumböck"
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=50BB1E8E.10809@universalnet.at \
--to=krumboeck@universalnet.at \
--cc=gediminas@8devices.com \
--cc=info@gerhard-bertelsmann.de \
--cc=linux-can@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.