summaryrefslogtreecommitdiff
path: root/drivers/net/can/usb/ucan.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/can/usb/ucan.c')
-rw-r--r--drivers/net/can/usb/ucan.c1062
1 files changed, 1062 insertions, 0 deletions
diff --git a/drivers/net/can/usb/ucan.c b/drivers/net/can/usb/ucan.c
new file mode 100644
index 000000000000..e1fd896a29e9
--- /dev/null
+++ b/drivers/net/can/usb/ucan.c
@@ -0,0 +1,1062 @@
+/*
+ * CAN driver for uCAN
+ *
+ * Copyright (C) 2015 Theobroma Systems Design und Consulting GmbH
+ *
+ * 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.
+ *
+ * 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/signal.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/usb.h>
+#include <linux/skbuff.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+
+#include "ucan.h"
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Martin Elshuber, Theobroma Systems Design und Consulting GmbH"
+ "<martin.elshuber@theobroma-systems.com>");
+MODULE_DESCRIPTION("CAN driver for uCAN devices");
+
+#define USB_CPCUSB_VENDOR_ID 0x0483
+#define USB_CPCUSB_UCAN_PRODUCT_ID 0x5720
+
+#define MAX_TX_URBS 8
+#define MAX_RX_URBS 8
+#define TX_QUEUE_STOP_TRESHOLD 1
+
+static struct usb_device_id uCAN_table[] = {
+ {USB_DEVICE(USB_CPCUSB_VENDOR_ID, USB_CPCUSB_UCAN_PRODUCT_ID)},
+ {} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, uCAN_table);
+
+struct uCAN;
+
+/* Context Information for transmission URBs */
+struct uCAN_urb_context {
+ struct uCAN *up;
+ u32 echo_index;
+ int size;
+ 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 */
+
+ struct usb_device *udev;
+ 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;
+
+ u8 *tx_msg_buffer;
+ u8 *rx_msg_buffer;
+
+ struct uCAN_device_info device_info;
+
+ atomic_t active_tx_urbs;
+ struct uCAN_urb_context tx_urb_contexts[MAX_TX_URBS];
+ int free_slots;
+};
+
+/* Sends a command to the device */
+static int uCAN_command(struct uCAN *up, u8 cmd, u8 subcmd, u16 value)
+{
+ int len;
+ struct uCAN_message_out *m = (struct uCAN_message_out *)up->tx_msg_buffer;
+ dev_dbg(&up->udev->dev, "%s\n", __func__);
+
+ m->type = __cpu_to_le16(UCAN_OUT_COMMAND);
+ m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.command));
+ m->msg.command.cmd = cmd;
+ m->msg.command.subcmd = subcmd;
+ m->msg.command.val = __cpu_to_le16(value);
+
+ return usb_bulk_msg(up->udev,
+ usb_sndbulkpipe(up->udev, up->out_ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK),
+ m,
+ __le16_to_cpu(m->len),
+ &len, 1000);
+}
+
+/* Request the device Information */
+static int uCAN_get_device_info(struct uCAN *up)
+{
+ int ret;
+ int len;
+ struct uCAN_message_in *m = (struct uCAN_message_in *)up->rx_msg_buffer;
+ dev_dbg(&up->udev->dev, "%s\n", __func__);
+
+ // send the request command
+ ret = uCAN_command(up, UCAN_COMMAND_GET,
+ UCAN_COMMAND_GET_INFO, 0);
+ if (ret)
+ return ret;
+
+ // retrieve the information
+ ret = usb_bulk_msg(up->udev,
+ usb_rcvbulkpipe(up->udev,
+ up->in_ep->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK),
+ m,
+ up->in_ep->wMaxPacketSize,
+ &len, 1000);
+ if (ret)
+ return ret;
+
+ // check sanity
+ if (m->type!=__cpu_to_le16(UCAN_IN_DEVICE_INFO))
+ return -EINVAL;
+
+ if (__le16_to_cpu(m->len) != UCAN_IN_LEN(m->msg.device_info) )
+ return -EINVAL;
+
+ // store the data
+ strcpy(up->device_info.bittiming_const.name, "uCAN");
+ up->can.clock.freq = __le32_to_cpu(m->msg.device_info.freq);
+ up->device_info.tx_fifo = m->msg.device_info.tx_fifo;
+ up->device_info.bittiming_const.tseg1_min = m->msg.device_info.tseg1_min;
+ up->device_info.bittiming_const.tseg1_max = m->msg.device_info.tseg1_max;
+ up->device_info.bittiming_const.tseg2_min = m->msg.device_info.tseg2_min;
+ up->device_info.bittiming_const.tseg2_max = m->msg.device_info.tseg2_max;
+ up->device_info.bittiming_const.sjw_max = m->msg.device_info.sjw_max;
+ up->device_info.bittiming_const.brp_min = __le32_to_cpu(m->msg.device_info.brp_min);
+ up->device_info.bittiming_const.brp_max = __le32_to_cpu(m->msg.device_info.brp_max);
+ up->device_info.bittiming_const.brp_inc = __le16_to_cpu(m->msg.device_info.brp_inc);
+#ifdef CONFIG_CAN_HWFILTER
+ up->can.hwfilterbanks = __le16_to_cpu(m->msg.device_info.hwfilter);
+ up->can.rxmailboxes = __le16_to_cpu(m->msg.device_info.rxmboxes);
+#endif
+
+ up->can.ctrlmode_supported = 0;
+
+ if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_LOOPBACK )
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_LOOPBACK;
+ if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_SILENT )
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_LISTENONLY;
+ if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_3_SAMPLES )
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_3_SAMPLES;
+ if (__le16_to_cpu(m->msg.device_info.ctrlmodes) & UCAN_MODE_ONE_SHOT )
+ up->can.ctrlmode_supported |= CAN_CTRLMODE_ONE_SHOT;
+ if (__le16_to_cpu(m->msg.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;
+
+ dev_dbg(&up->udev->dev, "%s %d\n", __func__,urb->status );
+
+ switch (urb->status) {
+ case 0:
+ ACCESS_ONCE(up->free_slots) = __le32_to_cpu(*up->irq_data);
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ETIME:
+ 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);
+ break;
+ }
+
+ 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 read bulk urb: %d\n", ret);
+}
+
+/* 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;
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ struct net_device_stats *stats = &up->netdev->stats;
+
+ dev_dbg(&up->udev->dev, "%s\n", __func__);
+
+ // 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;
+ }
+
+ // allocate skb
+ skb = alloc_can_skb(up->netdev, &cf);
+ if (skb == NULL)
+ 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);
+ }
+
+ // handle can error frames
+ if (cf->can_id & CAN_ERR_FLAG ) {
+ if ( cf->can_id & CAN_ERR_BUSOFF ) {
+ up->can.can_stats.bus_off++;
+ can_bus_off(up->netdev);
+ }
+
+ if ( cf->data[1] & ( CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING ) ) {
+ up->can.can_stats.error_warning++;
+ }
+
+ if ( cf->data[1] & ( CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING ) ) {
+ up->can.can_stats.error_passive++;
+ }
+
+ if ( cf->data[1] & CAN_ERR_CRTL_RX_OVERFLOW )
+ stats->rx_over_errors++;
+
+ if ( cf->can_id & CAN_ERR_LOSTARB )
+ up->can.can_stats.arbitration_lost++;
+
+ if ( cf->can_id & CAN_ERR_BUSERROR )
+ up->can.can_stats.bus_error++;
+
+ if ( cf->data[2] & CAN_ERR_PROT_TX ) {
+ stats->tx_errors++;
+ }
+ else {
+ stats->rx_errors++;
+ }
+ cf->can_id |= CAN_ERR_FLAG;
+ }
+
+ // pass it to Linux
+ netif_receive_skb(skb);
+
+ 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;
+
+ dev_dbg(&up->udev->dev, "%s %p\n", __func__, up);
+
+ // check URB status
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ETIME:
+ dev_dbg(&up->udev->dev, "%s ENOENT|ESHUTDOWN|ETIME\n", __func__);
+ return;
+ default:
+ goto resubmit;
+ }
+
+ // iterate ove input
+ pos = 0;
+ while (pos < urb->actual_length) {
+
+ // check sanity
+ if ( ( urb->actual_length - pos ) < UCAN_IN_HDR_SIZE) {
+ dev_warn(&up->udev->dev, "invalid input message 1 %d\n", urb->actual_length);
+ goto resubmit;
+ }
+
+ // get the pointer to the message
+ m = (struct uCAN_message_in *)( ( (u8*)urb->transfer_buffer ) + pos );
+ len = __le16_to_cpu(m->len);
+
+ // check sanity
+ if (len > (urb->actual_length - pos) ) {
+ dev_warn(&up->udev->dev, "invalid input message 2 %d\n", len);
+ goto resubmit;
+ }
+
+ 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;
+ // allign to 4 byte boundary
+ pos = ( pos + 3 ) & ~3;
+ }
+
+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
+ BUG_ON(!context);
+ up = context->up;
+
+ // free up our allocated buffer
+ usb_free_coherent(urb->dev, context->size,
+ 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
+ if (netif_queue_stopped(up->netdev))
+ netif_wake_queue(up->netdev);
+}
+
+/* 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);
+ dev_dbg(&up->udev->dev, "%s\n", __func__);
+
+ // call CAN layer open
+ ret = open_candev(netdev);
+ if (ret)
+ goto err;
+
+ ret = -ENOMEM;
+
+ // set the queue state as empty
+ up->free_slots = up->device_info.tx_fifo;
+
+ // initiallze 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;
+ }
+
+ // intialize 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;
+ if (up->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING )
+ ctrlmode |= UCAN_MODE_BERR_REPORT;
+
+ // start the USB device
+ ret = uCAN_command(up, UCAN_COMMAND_START,0,__cpu_to_le16(ctrlmode));
+ if (ret)
+ goto err;
+
+ // 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;
+ 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 = (struct uCAN_message_out *)up->tx_msg_buffer;
+ size_t size = UCAN_OUT_HDR_SIZE+cf->can_dlc;
+
+ dev_dbg(&up->udev->dev, "%s\n", __func__);
+
+ // 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, size, 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) {
+ m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.can_rtr_msg));
+ m->msg.can_rtr_msg.dlc = cf->can_dlc;
+ }
+ else {
+ m->len = __cpu_to_le16(UCAN_OUT_HDR_SIZE+
+ sizeof(m->msg.can_msg.id)+cf->can_dlc);
+ memcpy(m->msg.can_msg.data, cf->data, cf->can_dlc);
+ }
+
+ // 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->size = size;
+ context->dlc = cf->can_dlc;
+ atomic_inc(&up->active_tx_urbs);
+ break;
+ }
+ }
+
+ WARN_ON_ONCE(!context);
+ if (!context) {
+ usb_free_coherent(up->udev, size, 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,
+ __le16_to_cpu(m->len),
+ 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);
+ if (ret) {
+ // on error, clean up
+ can_free_echo_skb(up->netdev, context->echo_index);
+
+ usb_unanchor_urb(urb);
+ usb_free_coherent(up->udev, size, m, urb->transfer_dma);
+ 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 {
+ netdev->trans_start = jiffies;
+
+ /* Slow down tx path, if fifo state is low */
+ if ( (atomic_read(&up->active_tx_urbs) >= MAX_TX_URBS)
+ || (ACCESS_ONCE(up->free_slots) < TX_QUEUE_STOP_TRESHOLD) )
+ {
+ netif_stop_queue(netdev);
+ }
+ }
+
+ // release ref, as we do not need the urb anymore
+ usb_free_urb(urb);
+
+ return 0;
+drop:
+ dev_kfree_skb(skb);
+ up->netdev->stats.tx_dropped++;
+
+ return NETDEV_TX_OK;
+}
+
+/* Device goes down
+ *
+ * Cleanup used resources */
+static int uCAN_close(struct net_device *netdev)
+{
+ int ret;
+ struct uCAN *up = netdev_priv(netdev);
+ dev_dbg(&up->udev->dev, "%s\n", __func__);
+
+ netif_stop_queue(netdev);
+
+ if ( (ret = uCAN_command(up, UCAN_COMMAND_STOP,0,0)) ) {
+ dev_warn(&up->udev->dev, "Could not stop USB CAN 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);
+
+ 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 len;
+ struct uCAN *up = netdev_priv(netdev);
+ struct uCAN_message_out *m = (struct uCAN_message_out *)up->tx_msg_buffer;
+ dev_dbg(&up->udev->dev, "%s\n", __func__);
+
+ m->type = __cpu_to_le16(UCAN_OUT_SET_BITTIMING);
+ m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.bittiming));
+ m->msg.bittiming.tq = __cpu_to_le32(up->can.bittiming.tq);
+ m->msg.bittiming.brp = __cpu_to_le16(up->can.bittiming.brp);
+ m->msg.bittiming.sample_point = __cpu_to_le32(up->can.bittiming.sample_point);
+ m->msg.bittiming.prop_seg = up->can.bittiming.prop_seg;
+ m->msg.bittiming.phase_seg1 = up->can.bittiming.phase_seg1;
+ m->msg.bittiming.phase_seg2 = up->can.bittiming.phase_seg2;
+ m->msg.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);
+
+ return usb_bulk_msg(up->udev,
+ usb_sndbulkpipe(up->udev, up->out_ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK),
+ m,
+ __le16_to_cpu(m->len),
+ &len, 1000);
+}
+
+#ifdef CONFIG_CAN_HWFILTER
+/* Request to setup hardware filtering
+ *
+ * First al current harware filters are cleard.
+ * Second e seires of messages are sent to enable specific filters
+ */
+static int uCAN_set_hwfilter(struct net_device *netdev)
+{
+ int len;
+ int ret, i;
+ struct uCAN *up = netdev_priv(netdev);
+ struct uCAN_message_out *m = (struct uCAN_message_out *)up->tx_msg_buffer;
+
+ // clear filters
+ ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_CLEAR, 0);
+ if (ret)
+ return ret;
+
+ // setup filters
+ if (up->can.filter.count) {
+ for (i = 0; i < up->can.filter.count; i++) {
+ m->type = __cpu_to_le16(UCAN_OUT_ENABLE_FILTER);
+ m->len = __cpu_to_le16(UCAN_OUT_LEN(m->msg.enable_filter));
+ m->msg.enable_filter.id = __cpu_to_le32(up->can.filter.filter[i].id);
+ m->msg.enable_filter.mask = __cpu_to_le32(up->can.filter.filter[i].mask);
+ m->msg.enable_filter.mbox = __cpu_to_le16(up->can.filter.filter[i].mbox);
+ dev_dbg(&up->udev->dev,
+ "Setup hwfilter [ (ID & %x) = ( %x & %x ) ] => #%d\n",
+ m->msg.enable_filter.mask,
+ m->msg.enable_filter.id,
+ m->msg.enable_filter.mask,
+ m->msg.enable_filter.mbox);
+ ret = usb_bulk_msg(up->udev,
+ usb_sndbulkpipe(up->udev,
+ up->out_ep->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK),
+ m,
+ __le16_to_cpu(m->len),
+ &len, 1000);
+ if (ret)
+ return ret;
+ }
+ }
+ else {
+ // disable flters if none are set
+ ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_DISABLE, 0);
+ }
+ return 0;
+}
+#endif
+
+/* Probe the device, reset it and gather general device informations */
+static int uCAN_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret;
+ int i;
+ struct usb_device *udev;
+ struct net_device *netdev;
+ struct usb_host_interface *iface_desc;
+ struct uCAN *up;
+ struct usb_endpoint_descriptor *ep;
+
+ ret = -EINVAL;
+ udev = interface_to_usbdev(intf);
+ if (!udev)
+ goto err;
+
+ dev_dbg(&udev->dev, "%s\n", __func__);
+
+ // check if the interface is sane
+ ret = -EINVAL;
+ iface_desc = intf->cur_altsetting;
+ if (!iface_desc)
+ goto err;
+
+ if (iface_desc->desc.bInterfaceClass!=UCAN_CLASS)
+ return 0;
+
+ if (iface_desc->desc.bInterfaceSubClass!=UCAN_SUBCLASS)
+ return 0;
+
+ if (iface_desc->desc.bInterfaceProtocol!=0)
+ return 0;
+
+ // Infvalid interface Settings
+ if (iface_desc->desc.bNumEndpoints!=3)
+ goto err;
+
+ dev_info(&udev->dev, "Found USB CAN Class 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
+ ret = -EINVAL;
+ 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) )
+ goto err_free_candev;
+ if (up->out_ep->wMaxPacketSize < sizeof(struct uCAN_message_out) )
+ goto err_free_candev;
+ if (up->irq_ep->wMaxPacketSize < sizeof(u32) )
+ goto err_free_candev;
+
+ dev_info(&udev->dev, " using EP %02x for input with max packet size #0x%x\n",
+ up->in_ep->bEndpointAddress, up->in_ep->wMaxPacketSize);
+ dev_info(&udev->dev, " using EP %02x for output with max packet size #0x%x\n",
+ up->out_ep->bEndpointAddress, up->out_ep->wMaxPacketSize);
+ dev_info(&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->netdev = netdev;
+
+ up->can.state = CAN_STATE_STOPPED;
+ up->can.bittiming_const = &up->device_info.bittiming_const;
+ up->can.do_set_bittiming = uCAN_set_bittiming;
+#ifdef CONFIG_CAN_HWFILTER
+ up->can.do_set_hwfilter = uCAN_set_hwfilter;
+#endif
+ netdev->netdev_ops = &uCAN_netdev_ops;
+
+ usb_set_intfdata(intf, up);
+ SET_NETDEV_DEV(netdev, &intf->dev);
+
+ // allocate memory for cammand messages
+ up->tx_msg_buffer = devm_kzalloc(&udev->dev, up->out_ep->wMaxPacketSize, GFP_KERNEL);
+ if (!up->tx_msg_buffer)
+ goto err_free_candev;
+ up->rx_msg_buffer = devm_kzalloc(&udev->dev, up->in_ep->wMaxPacketSize, GFP_KERNEL);
+ if (!up->rx_msg_buffer)
+ goto err_free_candev;
+
+ // reset the device
+ ret = uCAN_command(up, UCAN_COMMAND_RESET, 0, 0);
+ if (ret)
+ goto err_free_candev;
+
+ // gather device information
+ ret = uCAN_get_device_info(up);
+ if (ret)
+ goto err_free_candev;
+
+#ifdef CONFIG_CAN_HWFILTER
+ // disable hardware filter by default
+ ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_CLEAR, 0);
+ if (ret)
+ goto err_free_candev;
+
+ ret = uCAN_command(up, UCAN_COMMAND_FILTER, UCAN_FILTER_DISABLE, 0);
+ if (ret)
+ goto err_free_candev;
+#endif
+
+ 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"
+#ifdef CONFIG_CAN_HWFILTER
+ " Hardware filter banks : %d\n"
+ " Receive mailboxes : %d\n"
+#endif
+ " 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,
+#ifdef CONFIG_CAN_HWFILTER
+ up->can.hwfilterbanks,
+ up->can.rxmailboxes,
+#endif
+ (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);
+
+ // register the device
+ ret = register_candev(netdev);
+ if (ret)
+ goto err_free_candev;
+
+ 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);
+ dev_dbg(&udev->dev, "%s\n", __func__);
+
+ 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",
+ .probe = uCAN_probe,
+ .disconnect = uCAN_disconnect,
+ .id_table = uCAN_table,
+};
+
+module_usb_driver(uCAN_driver);
+