]> rtime.felk.cvut.cz Git - socketcan-devel.git/blobdiff - kernel/2.6/drivers/net/can/usb/ctu_usbcan.c
Getting a bit timing constants from device has been done.
[socketcan-devel.git] / kernel / 2.6 / drivers / net / can / usb / ctu_usbcan.c
index 78f672e0897bdbec1ca4fda024ce8ecd13f9a8a2..0543bc6440510b99da27e0bbc50a840c82e3213b 100644 (file)
@@ -7,11 +7,43 @@
 #include <linux/uaccess.h>
 #include <linux/usb.h>
 #include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/version.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
 
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+
+// lpc17xx debug
+#define USBCAN_VENDOR_SET_CANBTR       (9)
+#define USBCAN_VENDOR_GET_BITTIMING_CONST      (10)
+// lpc17xx debug - end
 
 #define CTU_USBCAN_VENDOR_ID   0x1669
 #define CTU_USBCAN_PRODUCT_ID  0x1011
 
+#define USBCAN_TOT_RX_URBS     8
+#define USBCAN_TOT_TX_URBS     8
+
+#define USBCAN_TRANSFER_SIZE   16
+#define USBCAN_BITTIMING_SIZE  16
+#define USBCAN_BITTIMING_CONST_SIZE    36
+
+#define CAN_MSG_LENGTH 8
+
+#define MSG_RTR          (1<<0)
+#define MSG_OVR   (1<<1)
+#define MSG_EXT   (1<<2)
+#define MSG_LOCAL (1<<3)
+
+#define USBCAN_DATA_OK (1)
+#define USBCAN_TX_PENDING      (2)
+#define USBCAN_BITTIMING_CONST_SET     (3)
+
+
+MODULE_LICENSE("GPL");
 
 /* table of devices that work with this driver */
 static struct usb_device_id ctu_usbcan_table [] = {
@@ -22,22 +54,704 @@ MODULE_DEVICE_TABLE(usb, ctu_usbcan_table);
 
 
 static struct usb_driver ctu_usbcan_driver;
+struct ctu_usbcan_usb;
+
+
+struct usbcan_message {
+       struct urb      *u;
+       struct ctu_usbcan_usb *dev;
+       u8      msg[USBCAN_TRANSFER_SIZE];
+       u32 echo_index;
+       u8      dlc;
+       struct list_head list_node;
+};
+/*
+Structure of byte array msg in struct usbcan_message that represents CAN message (little endian):
+       msg[0] - reserved (1 byte)
+       msg[1] - length (1 byte)
+       msg[2:3] - flags (2 bytes)
+       msg[4:7] - id (4 bytes)
+       msg[8:15] - data (8 bytes)
+
+*/
+
+/* Structure to hold all of our device specific stuff */
+struct ctu_usbcan_usb {
+
+       struct can_priv can; /* must be the first member */
+
+       struct can_bittiming_const cbc;
+       u32 can_clock;
+
+       struct usb_device *udev;                        /* the usb device for this device */
+       struct net_device *netdev;
+
+       u8 bulk_in_endpointAddr;        /* the address of the bulk in endpoint */
+       u8 bulk_out_endpointAddr;       /* the address of the bulk out endpoint */
+
+       struct mutex            io_mutex;               /* synchronize I/O with disconnect */
+
+       struct list_head        rx_pend_list;           /* URBs waiting for data receive */
+       struct list_head        rx_ready_list;          /* URBs with valid received data */
+       struct list_head        tx_idle_list;           /* URBs prepared to hold Tx messages */
+       struct list_head        tx_pend_list;           /* URBs holding Tx messages in progress */
+       struct list_head        tx_ready_list;          /* URBs with yet confirmed Tx messages */
+
+       spinlock_t              list_lock;                      /* list lock */
+       struct task_struct *comthread;                  /* usb communication kernel thread  */
+       wait_queue_head_t queue;
+
+       volatile long flags;
+
+};
+
+
+static void usbcan_usb_message_move_list(struct ctu_usbcan_usb *dev,
+                       struct usbcan_message *m, struct list_head *head)
+{
+       unsigned long flags;    
+       spin_lock_irqsave(&dev->list_lock, flags);
+       list_del(&m->list_node);
+       list_add_tail(&m->list_node, head);
+       spin_unlock_irqrestore(&dev->list_lock, flags);
+}
+
+
+static int get_bittiming_constants(struct ctu_usbcan_usb *dev)
+{
+
+       int retval;
+       u8 *usbbuf;
+       u32 * ptr;
+
+       usbbuf = kzalloc(sizeof(u8)*USBCAN_BITTIMING_CONST_SIZE, GFP_KERNEL);
+
+       if(!usbbuf){
+               err("Error allocating receive buffer for bittiming constants\n");
+               return 1;
+       }
+       
+       retval = usb_control_msg(dev->udev,
+               usb_rcvctrlpipe(dev->udev, 0),
+               USBCAN_VENDOR_GET_BITTIMING_CONST,
+               USB_TYPE_VENDOR,
+               cpu_to_le16(0), cpu_to_le16(0),
+               usbbuf, USBCAN_BITTIMING_CONST_SIZE,
+               1000);
+
+       if(retval<0)
+               goto exit;
+
+       ptr = (u32*) usbbuf;
+
+       dev->can_clock = le32_to_cpu(*(ptr++));
+       dev->cbc.tseg1_min = le32_to_cpu(*(ptr++));
+       dev->cbc.tseg1_max = le32_to_cpu(*(ptr++));
+       dev->cbc.tseg2_min = le32_to_cpu(*(ptr++));
+       dev->cbc.tseg2_max = le32_to_cpu(*(ptr++));
+       dev->cbc.sjw_max = le32_to_cpu(*(ptr++));
+       dev->cbc.brp_min = le32_to_cpu(*(ptr++));
+       dev->cbc.brp_max = le32_to_cpu(*(ptr++));
+       dev->cbc.brp_inc = le32_to_cpu(*(ptr));
+
+       set_bit(USBCAN_BITTIMING_CONST_SET,&dev->flags);
+
+
+exit:
+
+       kfree(usbbuf);
+       if(retval<0)
+               return retval;
+
+       return 0;
+
+}
+
+static int ctu_usbcan_set_mode(struct net_device *netdev, enum can_mode mode)
+{
+       
+       printk("SET MODE\n");
+
+       return 0;
+}
+
+static int ctu_usbcan_set_bittiming(struct net_device *netdev)
+{
+       struct ctu_usbcan_usb *dev = netdev_priv(netdev);
+       struct can_bittiming *bt = &dev->can.bittiming;
+       u8 *usbbuf;     
+       u32 * ptr;
+       int retval;
+       
+
+       if (!dev)
+               return -ENODEV;
+
+
+       usbbuf = kzalloc(sizeof(u8)*USBCAN_BITTIMING_SIZE, GFP_KERNEL);
+
+       if(!usbbuf){
+               err("Error allocating transmit buffer for set bittiming\n");
+               return 1;
+       }       
+       
+       ptr = (u32*) usbbuf;
+
+       *(ptr++)=cpu_to_le32(bt->brp);          // baudrate prescaler
+       *(ptr++)=cpu_to_le32(bt->sjw);  // sjw
+       *(ptr++)=cpu_to_le32(bt->prop_seg + bt->phase_seg1);    // TSEG1
+       *(ptr)=cpu_to_le32(bt->phase_seg2);     // TSEG2
+
+
+       retval = usb_control_msg(dev->udev,
+                               usb_sndctrlpipe(dev->udev, 0),
+                               USBCAN_VENDOR_SET_CANBTR,
+                               USB_TYPE_VENDOR,
+                               cpu_to_le16(0), cpu_to_le16(0),
+                               usbbuf, USBCAN_BITTIMING_SIZE,
+                               1000);
+
+       kfree(usbbuf);
+
+       if(retval){
+               printk("BITRATE %d, BRP: %d, SJW: %d, TSEG1: %d, TSEG2: %d \n", bt->bitrate, bt->brp, bt->sjw,\
+                               (bt->prop_seg + bt->phase_seg1), bt->phase_seg2);       
+               return 0;
+       }
+       else{
+               err("Could not set bittiming\n");
+               return retval;
+       }
+
+}
+
+
+static void ctu_usbcan_tx_callback(struct urb *urb){
+
+       struct usbcan_message *m = urb->context;
+       struct net_device_stats *stats = &m->dev->netdev->stats;
+
+       if (!netif_device_present(m->dev->netdev))
+               return;
+
+       if (urb->status != 0){
+               err("TX callback: error");
+               return;
+       }
+
+       /* success */
+
+       printk("TX callback: URB successfully transmitted\n");
+
+       stats->tx_packets++;
+       stats->tx_bytes += m->dlc;
+
+       
+       can_get_echo_skb(m->dev->netdev, m->echo_index);
+
+       set_bit(USBCAN_DATA_OK,&m->dev->flags);
+       usbcan_usb_message_move_list(m->dev, m, &m->dev->tx_ready_list);
+       wake_up_process(m->dev->comthread);
+
+}
+
+static void ctu_usbcan_rx_callback(struct urb *urb)
+{
+       
+       struct usbcan_message *m = urb->context;
+       
+       if (!netif_device_present(m->dev->netdev))
+               return;
+
+
+       if(urb->status != 0){
+               err("RX callback: error");
+               return;
+       }
+
+       /* success */
+       
+       printk("RX callback: URB successfully received\n");
+
+       set_bit(USBCAN_DATA_OK,&m->dev->flags);
+       usbcan_usb_message_move_list(m->dev, m, &m->dev->rx_ready_list);
+       wake_up_process(m->dev->comthread);
+
+}
+
+static netdev_tx_t ctu_usbcan_start_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+       struct ctu_usbcan_usb *dev = netdev_priv(netdev);
+       struct can_frame *cf = (struct can_frame *)skb->data;
+       struct usbcan_message *m;
+       int i, retval, len;
+       u8 *ptr;
+       u16 flags = 0;
+       
+       if(list_empty(&dev->tx_idle_list)) 
+               goto exit;
+
+       m = list_first_entry(&dev->tx_idle_list, typeof(*m), list_node);
+
+       /* naplneni     */
+
+       len = cf->can_dlc;
+       if(len > CAN_MSG_LENGTH)
+               len = CAN_MSG_LENGTH;
+
+       if (cf->can_id & CAN_RTR_FLAG)
+               flags |= MSG_RTR;
+
+       if (cf->can_id & CAN_EFF_FLAG)
+               flags |= MSG_EXT;
+
+       *(u8 *)(m->msg)=0;
+       *(u8 *)(m->msg+1)=len & 0xFF;
+       *(u16 *)(m->msg+2)=cpu_to_le16(flags);
+       *(u32 *)(m->msg+4)=cpu_to_le32(cf->can_id & CAN_ERR_MASK);
+
+       for(ptr=m->msg+8, i=0; i < len; ptr++,i++)
+               *ptr= cf->data[i] & 0xFF;
+               
+       for(; i < 8; ptr++,i++)
+               *ptr=0;
+
+
+       m->dlc = (u8) len;
+
+       usbcan_usb_message_move_list(dev, m, &dev->tx_pend_list);
+
+       can_put_echo_skb(skb, netdev, m->echo_index);
+
+       /* odeslani     */ 
+       retval = usb_submit_urb(m->u, GFP_ATOMIC);
+       if (retval){
+               err("Error submitting URB: %d", retval);
+               usbcan_usb_message_move_list(dev, m, &dev->tx_idle_list);
+               goto exit;
+       }
+
+
+exit:
+
+       return NETDEV_TX_OK;
+
+}
+
+static void usbcan_kthread_free_urbs(struct ctu_usbcan_usb *dev)
+{
+       while(!list_empty(&dev->rx_pend_list)) {
+               struct usbcan_message *m;
+               m = list_first_entry(&dev->rx_pend_list, typeof(*m), list_node);
+               usb_kill_urb(m->u);
+               usbcan_usb_message_move_list(dev, m, &dev->rx_ready_list);
+       }
+
+       while(!list_empty(&dev->tx_pend_list)) {
+               struct usbcan_message *m;
+               m = list_first_entry(&dev->tx_pend_list, typeof(*m), list_node);
+               usb_kill_urb(m->u);
+               usbcan_usb_message_move_list(dev, m, &dev->tx_idle_list);
+       }
+
+       while(!list_empty(&dev->rx_ready_list)) {
+               struct usbcan_message *m;
+               m = list_first_entry(&dev->rx_ready_list, typeof(*m), list_node);
+               list_del(&m->list_node);
+               usb_free_urb(m->u);
+               kfree(m);
+       }
+
+       while(!list_empty(&dev->tx_ready_list)) {
+               struct usbcan_message *m;
+               m = list_first_entry(&dev->tx_ready_list, typeof(*m), list_node);
+               list_del(&m->list_node);
+               usb_free_urb(m->u);
+               kfree(m);
+       }
 
+       while(!list_empty(&dev->tx_idle_list)) {
+               struct usbcan_message *m;
+               m = list_first_entry(&dev->tx_idle_list, typeof(*m), list_node);
+               list_del(&m->list_node);
+               usb_free_urb(m->u);
+               kfree(m);
+       }
 
-static int ctu_usbcan_probe(struct usb_interface *interface, const struct usb_device_id *id)
+}
+
+void usbcan_kthread_write_handler(struct ctu_usbcan_usb *dev, struct usbcan_message *m)
+{
+
+       usbcan_usb_message_move_list(dev, m, &dev->tx_idle_list);
+
+}
+
+void usbcan_kthread_read_handler(struct ctu_usbcan_usb *dev, struct usbcan_message *m)
+{
+
+       struct can_frame *cf;
+       struct sk_buff *skb;
+       u8 *ptr;
+       int i, len, retval;
+       u16 flags = 0;
+       struct net_device_stats *stats = &m->dev->netdev->stats;
+
+/*
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)
+       skb = alloc_can_skb(m->dev->netdev, &cf);
+#else
+       skb = netdev_alloc_skb(m->dev->netdev, sizeof(struct can_frame)); 
+       skb->protocol = htons(ETH_P_CAN);
+       cf = (struct can_frame *)skb_put(skb, sizeof(struct can_frame));
+#endif
+*/
+       skb = alloc_can_skb(m->dev->netdev, &cf);
+
+       if (skb == NULL){
+               err("RX: error alloc skb\n");
+               return;
+       }
+
+       
+       printk("RX: URB successfully received\n");
+
+       len=*(u8 *)(m->msg+1);
+       if(len > CAN_MSG_LENGTH) len = CAN_MSG_LENGTH;
+
+       flags = le16_to_cpu(*(u16 *)(m->msg+2));
+       cf->can_id = le32_to_cpu((*(u32 *)(m->msg+4)));
+
+       if (flags & MSG_RTR)
+               cf->can_id |= CAN_RTR_FLAG;
+
+       if (flags & MSG_EXT)
+               cf->can_id |= CAN_EFF_FLAG;
+
+       cf->can_dlc = len;
+
+       for(ptr=m->msg+8,i=0; i < len; ptr++,i++) {
+               cf->data[i]=*ptr;
+       }
+
+
+       netif_rx(skb);
+
+       stats->rx_packets++;
+       stats->rx_bytes += cf->can_dlc;
+
+       /* Renewing RX urb */
+
+       usbcan_usb_message_move_list(dev, m, &dev->rx_pend_list);
+       retval = usb_submit_urb (m->u, GFP_KERNEL);
+       if (retval<0) {
+               err("URB error %d\n", retval);
+       }
+
+
+}
+
+
+static int usbcan_sleep_thread(struct ctu_usbcan_usb *dev)
+{
+       int     rc = 0;
+
+       /* Wait until a signal arrives or we are woken up */
+       for (;;) {
+               try_to_freeze();
+               set_current_state(TASK_INTERRUPTIBLE);
+               if (signal_pending(current)) {
+                       rc = -EINTR;
+                       break;
+               }
+               if (
+                       kthread_should_stop() ||
+                       test_bit(USBCAN_DATA_OK,&dev->flags)
+               )
+                       break;
+               schedule();
+       }
+       __set_current_state(TASK_RUNNING);
+       return rc;
+}
+
+int usbcan_kthread(void *data)
+{
+
+       struct ctu_usbcan_usb *dev=(struct ctu_usbcan_usb *) data;
+
+       int i, retval;
+
+       struct usbcan_message *m;
+       struct urb *u;
+
+       printk("CTU USBCAN: kthread running\n");
+
+       INIT_LIST_HEAD(&dev->rx_pend_list);
+       INIT_LIST_HEAD(&dev->rx_ready_list);
+       INIT_LIST_HEAD(&dev->tx_idle_list);
+       INIT_LIST_HEAD(&dev->tx_pend_list);
+       INIT_LIST_HEAD(&dev->tx_ready_list);
+
+       /* Prepare receive urbs  */
+       for (i=0;i<USBCAN_TOT_RX_URBS;i++){
+               
+               u = usb_alloc_urb(0, GFP_KERNEL);               
+               m = kzalloc(sizeof(struct usbcan_message), GFP_KERNEL);
+               
+               if (!u){
+                       err("Error allocating usb receive urb");
+                       goto exit;
+               }
+
+               if(!m) {
+                       usb_free_urb(u);
+                       err("Error allocating receive usbcan_message");
+                       goto exit;
+               }
+               m->u = u;
+               u->dev = dev->udev;
+               m->dev = dev;
+               usb_fill_bulk_urb(u, dev->udev,
+                       usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
+                       m->msg, USBCAN_TRANSFER_SIZE, ctu_usbcan_rx_callback, m);
+
+
+               list_add_tail(&m->list_node, &dev->rx_ready_list);
+       }
+
+       /* Prepare transmit urbs  */
+       for (i=0;i<USBCAN_TOT_TX_URBS;i++){
+
+               u = usb_alloc_urb(0, GFP_ATOMIC);
+               m = kzalloc(sizeof(struct usbcan_message), GFP_ATOMIC);
+
+               if (!u){
+                       err("Error allocating usb transmit urb");
+                       goto exit;
+               }
+       
+               if(!m) {
+                       usb_free_urb(u);
+                       err("Error allocating transmit usbcan_message");
+                       goto exit;
+               }
+               m->u = u;
+               u->dev = dev->udev;
+               m->dev = dev;
+
+               m->echo_index = i;
+
+               usb_fill_bulk_urb(u, dev->udev,
+                       usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
+                       m->msg, USBCAN_TRANSFER_SIZE, ctu_usbcan_tx_callback, m);
+
+               list_add_tail(&m->list_node, &dev->tx_idle_list);
+       }
+
+
+       for (i=0;i<USBCAN_TOT_RX_URBS;i++){
+               
+               m = list_first_entry(&dev->rx_ready_list, typeof(*m), list_node);
+               usbcan_usb_message_move_list(dev, m, &dev->rx_pend_list);
+
+               retval=usb_submit_urb(m->u, GFP_KERNEL);
+               if (retval){
+                       err("Error submitting URB: %d", retval);
+                       goto exit;
+               }
+       }
+
+
+       /* an endless loop in which we are doing our work */
+       for(;;)
+       {
+
+
+               /* We need to do a memory barrier here to be sure that
+               the flags are visible on all CPUs. */
+               mb();
+
+               /* fall asleep */
+               if (!kthread_should_stop() && (usbcan_sleep_thread(dev)<0)){
+                       break;
+               }
+               /* We need to do a memory barrier here to be sure that the flags are visible on all CPUs. */
+               mb();
+
+               if (kthread_should_stop()){
+                       break;
+               }
+
+               mb();
+
+               clear_bit(USBCAN_DATA_OK,&dev->flags);
+
+               
+               while(!list_empty(&dev->rx_ready_list)) {
+                       struct usbcan_message *m;
+                       m = list_first_entry(&dev->rx_ready_list, typeof(*m), list_node);
+                       usbcan_kthread_read_handler(dev, m);
+               }
+
+               while(!list_empty(&dev->tx_ready_list)) {
+                       struct usbcan_message *m;
+                       m = list_first_entry(&dev->tx_ready_list, typeof(*m), list_node);
+                       usbcan_kthread_write_handler(dev, m);
+               }
+
+       }
+
+
+exit:
+       usbcan_kthread_free_urbs(dev);
+       printk("CTU USBCAN: usbcan thread finished\n");
+       
+       return 0;
+
+}
+
+static int ctu_usbcan_open(struct net_device *netdev)
 {
+       int err;
+
+       struct ctu_usbcan_usb *dev = netdev_priv(netdev);
+
+       /* common open */       
+       err = open_candev(netdev);
+       if (err)
+               return err;     
+
+       /* start kernel thread */
+       dev->comthread = kthread_run(usbcan_kthread, (void *)dev, "usbcan_1");
+
+       return 0;
+}
+
+static int ctu_usbcan_close(struct net_device *netdev)
+{
+
+       struct ctu_usbcan_usb *dev = netdev_priv(netdev);
+       kthread_stop(dev->comthread);
+
+       close_candev(netdev);
+       
+       return 0;
+}
+
+static const struct net_device_ops ctu_usbcan_netdev_ops = {
+       .ndo_open = ctu_usbcan_open,
+       .ndo_stop = ctu_usbcan_close,
+       .ndo_start_xmit = ctu_usbcan_start_xmit,
+};
+
+
+static int ctu_usbcan_probe(struct usb_interface *intf,
+                                                       const struct usb_device_id *id)
+{
+       struct net_device *netdev;
+       struct ctu_usbcan_usb *dev;
+       struct usb_host_interface *iface_desc;
+       struct usb_endpoint_descriptor *endpoint;
+       int i;
+       int err = -ENOMEM;
 
        printk(KERN_INFO "CTU USBCAN device now attached\n");
 
+/*
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)
+       netdev = alloc_candev(sizeof(struct ctu_usbcan_usb), USBCAN_TOT_TX_URBS);
+#else
+       netdev = alloc_candev(sizeof(struct ctu_usbcan_usb));
+#endif
+*/
+       netdev = alloc_candev(sizeof(struct ctu_usbcan_usb), USBCAN_TOT_TX_URBS);
+
+       if (!netdev) {
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,33)
+               dev_err(&intf->dev, "Couldn't alloc candev\n");
+#else
+               dev_err(netdev->dev.parent, "Couldn't alloc candev\n");
+#endif
+               return -ENOMEM;
+       }
+
+       dev = netdev_priv(netdev);
+
+       dev->udev = interface_to_usbdev(intf);
+       dev->netdev = netdev;
+
+
+       netdev->netdev_ops = &ctu_usbcan_netdev_ops;
+       netdev->flags |= IFF_ECHO;
+
+       /* set up the endpoint information */
+       /* use only the first bulk-in and bulk-out endpoints */
+       iface_desc = intf->cur_altsetting;
+       for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+               endpoint = &iface_desc->endpoint[i].desc;
+
+               if (!dev->bulk_in_endpointAddr &&
+                   usb_endpoint_is_bulk_in(endpoint)) {
+                       /* we found a bulk in endpoint */
+                       dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
+               }
+
+               if (!dev->bulk_out_endpointAddr &&
+                   usb_endpoint_is_bulk_out(endpoint)) {
+                       /* we found a bulk out endpoint */
+                       dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
+               }
+       }
+       if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
+               err("Could not find both bulk-in and bulk-out endpoints");
+               goto exit;
+       }
+
+
+       usb_set_intfdata(intf, dev);
+       SET_NETDEV_DEV(netdev, &intf->dev);
+
+       if (get_bittiming_constants(dev)){
+               err("Could not get bittiming constants\n");
+               goto exit;
+       }
+
+       dev->can.clock.freq = dev->can_clock;
+       dev->can.bittiming_const = &dev->cbc;
+       dev->can.do_set_bittiming = ctu_usbcan_set_bittiming;
+       dev->can.do_set_mode = ctu_usbcan_set_mode;
+
+
+       err = register_candev(netdev);
+       if (err) {
+               dev_err(netdev->dev.parent,
+                       "couldn't register CAN device: %d\n", err);
+       }
+
+exit:
+
        return 0;
 }
 
 /* called by the usb core when the device is removed from the system */
-static void ctu_usbcan_disconnect(struct usb_interface *interface)
+static void ctu_usbcan_disconnect(struct usb_interface *intf)
 {
 
+       struct ctu_usbcan_usb *dev = usb_get_intfdata(intf);
+
        printk(KERN_INFO "CTU USBCAN device now disconnected\n");
 
+       usb_set_intfdata(intf, NULL);
+
+       if (dev) {
+               if(test_bit(USBCAN_BITTIMING_CONST_SET,&dev->flags))            
+                       unregister_netdev(dev->netdev);
+
+               free_candev(dev->netdev);
+               clear_bit(USBCAN_BITTIMING_CONST_SET,&dev->flags);
+       }
+
 }
 
 /* usb specific object needed to register this driver with the usb subsystem */
@@ -73,4 +787,3 @@ static void __exit ctu_usbcan_exit(void)
 module_init(ctu_usbcan_init);
 module_exit(ctu_usbcan_exit);
 
-MODULE_LICENSE("GPL");