From acc6373a1d34a8973f5a405dde4744ce68a889af Mon Sep 17 00:00:00 2001
From: micki <micki@ntrig.com>
Date: Sun, 3 Oct 2010 08:42:13 +0200
Subject: [PATCH] USB: misc: Add N-trig firmware driver

Add N-trig Firmware upgrade capability.

Signed-off-by: Micki Balanga <micki@n-trig.com>
---
 drivers/usb/misc/Makefile    |    1 +
 drivers/usb/misc/ntrig_dfu.c |  519 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 520 insertions(+), 0 deletions(-)
 create mode 100755 drivers/usb/misc/ntrig_dfu.c

diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 717703e..50c42a3 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_USB_TEST)		+= usbtest.o
 obj-$(CONFIG_USB_TRANCEVIBRATOR)	+= trancevibrator.o
 obj-$(CONFIG_USB_USS720)	+= uss720.o
 obj-$(CONFIG_USB_SEVSEG)	+= usbsevseg.o
+obj-$(CONFIG_USB_DFU_NTRIG)	+= ntrig_dfu.o
 
 obj-$(CONFIG_USB_SISUSBVGA)	+= sisusbvga/
 
diff --git a/drivers/usb/misc/ntrig_dfu.c b/drivers/usb/misc/ntrig_dfu.c
new file mode 100755
index 0000000..4e0c1e8
--- /dev/null
+++ b/drivers/usb/misc/ntrig_dfu.c
@@ -0,0 +1,519 @@
+/*
+ *  DFU driver interface for firmware upgrade
+ *
+ *  Copyright (c) 2010 N-TRIG
+ *
+ * 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; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+
+#define DRIVER_VERSION "N-TRIG Bulk Driver Version 1.00"
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define USB_NTRIG_MINOR_BASE		0
+#else
+/* FIXME 186 - is another driver's minor - apply for that */
+#define USB_NTRIG_MINOR_BASE		186
+#endif
+
+#define N_TRIG_BOOT_LOADER_ID		0x02
+#define BULK_IN_MAX_MSG_LEN		1024
+#define IOCTL_GET_HARD_VERSION	1
+#define IOCTL_GET_DRV_VERSION	2
+/**
+ * Ctrl Endpoint header declaration
+ */
+#define REQUEST_CMD			0
+#define BULK_GROUP_ID			1
+#define BULK_CODE			2
+#define BULK_LEN_LSB			3
+#define BULK_LEN_MSB			4
+#define BULK_DATA			5
+
+#define CMD_WRITE_MESSAGE		0x0F
+#define CMD_GO2_BOOT_LOADER		0x0B
+
+#define CTL_REQUEST_TYPE		0x40
+#define NTRIG_INT_PACKET_LENGTH		10
+#define MAX_TRANSMIT_FRAGMENT_SIZE	64
+static struct usb_device_id id_table[] = {
+	{ .idVendor = 0x1b96, .match_flags = USB_DEVICE_ID_MATCH_VENDOR, },
+	{ },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static DEFINE_MUTEX(open_disc_mutex);
+
+
+struct usb_ntrig {
+	struct usb_device	*udev;
+	struct usb_interface	*interface;
+	unsigned char		*bulk_in_buffer;
+	size_t		bulk_in_size;
+	__u8		bulk_in_endpointAddr;
+	__u8		bulk_out_endpointAddr;
+	__u8		product_id;
+	struct kref	kref;
+	struct usb_anchor	submitted;
+};
+#define to_ntrig_dev(d) container_of(d, struct usb_ntrig, kref)
+
+
+
+static struct usb_driver ntrig_driver;
+
+
+static void ntrig_delete(struct kref *kref)
+{
+	struct usb_ntrig *dev = to_ntrig_dev(kref);
+	usb_put_dev(dev->udev);
+	/* We don't want to enable Autosuspend while */
+	/* Firmware upgrade procedure */
+	kfree(dev->bulk_in_buffer);
+	kfree(dev);
+}
+
+
+static int ntrig_open(struct inode *inode, struct file *file)
+{
+	struct usb_ntrig *dev;
+	struct usb_interface *interface;
+	int subminor, r;
+	subminor = iminor(inode);
+	interface = usb_find_interface(&ntrig_driver, subminor);
+	if (!interface) {
+		err("N-trig bulk: %s - error, can't find device for minor %d",
+		     __func__, subminor);
+		return -ENODEV;
+	}
+	mutex_lock(&open_disc_mutex);
+	dev = usb_get_intfdata(interface);
+	if (!dev) {
+		mutex_unlock(&open_disc_mutex);
+		return -ENODEV;
+	}
+	/* increment our usage count for the device */
+	kref_get(&dev->kref);
+	mutex_unlock(&open_disc_mutex);
+	/* grab a power reference */
+	r = usb_autopm_get_interface(interface);
+	if (r < 0) {
+		kref_put(&dev->kref, ntrig_delete);
+		return r;
+	}
+	/* save our object in the file's private structure */
+	file->private_data = dev;
+	return 0;
+}
+
+static int ntrig_release(struct inode *inode, struct file *file)
+{
+	struct usb_ntrig *dev;
+	dev = (struct usb_ntrig *)file->private_data;
+	if (dev == NULL)
+		return -ENODEV;
+	/*decrement the count on our device*/
+	/*We Can't But there is still ntrig-dfu communicate with him*/
+	/*We don't want to enable autosusppend*/
+	/*becuase we don't when we finish work we driver we take it out*/
+	kref_put(&dev->kref, ntrig_delete);
+	return 0;
+}
+
+static ssize_t ntrig_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
+{
+	struct usb_ntrig *dev;
+	int retval = 0;
+	int bytes_read;
+	dev = (struct usb_ntrig *)file->private_data;
+	/* do a blocking bulk read to get data from the device */
+	retval = usb_bulk_msg(dev->udev,
+		      usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
+		      dev->bulk_in_buffer,
+		      min(dev->bulk_in_size, count),
+		      &bytes_read, 10000);
+	/* if the read was successful, copy the data to userspace */
+	if (!retval) {
+		if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
+			retval = -EFAULT;
+		else
+			retval = bytes_read;
+	}
+	return retval;
+}
+
+static long ntrig_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct usb_ntrig *dev;
+	u16 bcdDevice;
+	char buf[30];
+	dev = (struct usb_ntrig *)file->private_data;
+	if (dev == NULL)
+		return -ENODEV;
+	switch (cmd) {
+	case IOCTL_GET_HARD_VERSION:
+		lock_kernel();
+		bcdDevice = le16_to_cpu((dev->udev)->descriptor.bcdDevice);
+		sprintf(buf, "%1d%1d.%1d%1d",
+			(bcdDevice & 0xF000)>>12,
+			(bcdDevice & 0xF00)>>8,
+			(bcdDevice & 0xF0)>>4,
+			(bcdDevice & 0xF));
+		unlock_kernel();
+		if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0)
+			return -EFAULT;
+		break;
+	case IOCTL_GET_DRV_VERSION:
+		sprintf(buf, DRIVER_VERSION);
+		if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0)
+			return -EFAULT;
+		break;
+	default:
+		return -ENOTTY;
+		break;
+	}
+	return 0;
+}
+
+static void ntrig_write_bulk_callback(struct urb *urb)
+{
+	struct usb_ntrig *dev;
+	int status = urb->status;
+	dev = urb->context;
+	/* sync/async unlink faults aren't errors */
+	if (status &&
+	    !(status == -ENOENT ||
+	      status == -ECONNRESET ||
+	      status == -ESHUTDOWN)) {
+		dbg("N-trig bulk: %s - nonzero write bulk status received: %d",
+		    __func__, status);
+	}
+	/* free up our allocated buffer */
+	usb_buffer_free(urb->dev, urb->transfer_buffer_length,
+			urb->transfer_buffer, urb->transfer_dma);
+}
+
+/**
+ * ctl_probe_timeout - Timeout Wait for control message to be sent
+ */
+static void ctl_probe_timeout(unsigned long arg)
+{
+	struct completion *cop = (struct completion *) arg;
+	complete(cop);
+	err("%s() ERR timeout occured for control message", __func__);
+}
+
+/**
+ * Support Sensor without bulk out endpoint
+ * Information need to be send via control enpoint
+ */
+static int ntrig_ctrl_write(struct file *file, const char __user * user_buffer, size_t count, loff_t *ppos)
+{
+	struct usb_ntrig *dev;
+	struct usb_ctrlrequest *ctl_write;
+	struct urb *urb = NULL;
+	struct completion compl;
+	struct timer_list timer;
+	int retval = 0, nLength = 0, nNumOfFragments = 0,
+	  nFragmentNum = 0, nBytesLeft = 0, nFragmentSize = 0;
+	unsigned char *pData = NULL;
+	/*
+	 * completion stuct - for synchronization mechanism
+	 * Wait for ctrl message to be send
+	 */
+	init_completion(&compl);
+	dev = (struct usb_ntrig *)file->private_data;
+	/* verify that we actually have some data to write */
+	if (0 == count)
+		return 0;
+	ctl_write = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+	if (!ctl_write) {
+		err("%s() Memory Allocation Failed usb_ctrlrequest", __func__);
+		return -ENOMEM;
+	}
+	urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!urb) {
+		err("%s() URB Memory Allocation Failed", __func__);
+		goto urb_alloc_failed;
+	}
+	/*Worst Case scenario we allocate 4 bytes*/
+	pData = kmalloc(count, GFP_KERNEL);
+	if (copy_from_user(pData, user_buffer, count)) {
+		err("%s() Write User Buffer Failed", __func__);
+		goto mem_alloc_failed;
+	}
+	/* Update Setup Field - According To Buffer Received from User */
+	ctl_write->bRequestType  = CTL_REQUEST_TYPE;
+	ctl_write->bRequest      = (CMD_WRITE_MESSAGE == pData[REQUEST_CMD]) ? CMD_WRITE_MESSAGE : CMD_GO2_BOOT_LOADER;
+	/*GroupID + Code */
+	ctl_write->wValue        = (pData[BULK_GROUP_ID] * 0x100) + pData[BULK_CODE];
+	nLength = (pData[BULK_LEN_LSB] * 0x100) + pData[BULK_LEN_MSB];
+	if ((nLength != 0) && (0 == (nLength % 64)))
+	    nNumOfFragments = nLength / MAX_TRANSMIT_FRAGMENT_SIZE;
+	else
+	    nNumOfFragments = nLength / MAX_TRANSMIT_FRAGMENT_SIZE + 1;
+	if (nNumOfFragments > 0xFF)
+		goto mem_alloc_failed;
+	pData += BULK_DATA; /*Point To Data Field*/
+	nBytesLeft = nLength;
+	while (nBytesLeft >= 0) {
+		if (nBytesLeft > MAX_TRANSMIT_FRAGMENT_SIZE)
+		    nFragmentSize = MAX_TRANSMIT_FRAGMENT_SIZE;
+		else
+		  nFragmentSize = nBytesLeft;
+		ctl_write->wIndex  = nNumOfFragments*0x100 /*MSB*/+ nFragmentNum /*LSB*/;
+		ctl_write->wLength = cpu_to_le16(nFragmentSize);
+		usb_fill_control_urb(urb, dev->udev, usb_sndctrlpipe(dev->udev, 0/*Endpoint Address*/),
+		(unsigned char *)ctl_write, &pData[nFragmentNum * MAX_TRANSMIT_FRAGMENT_SIZE],
+		nFragmentSize, ntrig_write_bulk_callback, &compl);
+		retval = usb_submit_urb(urb, GFP_KERNEL);
+		if (retval < 0) {
+			err("N-trig bulk: %s() - failed submitting write urb, error %d",
+			    __func__, retval);
+			goto mem_alloc_failed;
+		}
+		/* Initialize timer wait for control message completions */
+		init_timer(&timer);
+		timer.function = ctl_probe_timeout;
+		timer.data = (unsigned long) &compl;
+		timer.expires = jiffies + 500 /*0.5 sec*/;
+		add_timer(&timer);
+		wait_for_completion(&compl);
+		del_timer_sync(&timer);
+		nFragmentNum++;
+		nBytesLeft -= MAX_TRANSMIT_FRAGMENT_SIZE;
+	}
+	return count;
+mem_alloc_failed:
+	usb_free_urb(urb);
+	kfree(pData);
+	kfree(urb);
+urb_alloc_failed:
+	kfree(ctl_write);
+	return -ENOMEM;
+
+}
+static ssize_t ntrig_write(struct file *file, const char __user * user_buffer, size_t count, loff_t *ppos)
+{
+	struct usb_ntrig *dev;
+	int retval = 0;
+	struct urb *urb = NULL;
+	char *buf = NULL;
+	dev = (struct usb_ntrig *)file->private_data;
+	/* verify that we actually have some data to write */
+	if (count == 0)
+		goto exit;
+	if (N_TRIG_BOOT_LOADER_ID != dev->product_id) {
+	    count = ntrig_ctrl_write(file, user_buffer, count, ppos);
+	    goto exit;
+	}
+	/* create a urb, and a buffer for it, and copy the data to the urb */
+	urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!urb) {
+		retval = -ENOMEM;
+		goto err_no_buf;
+	}
+	buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
+	if (!buf) {
+		retval = -ENOMEM;
+		goto error;
+	}
+	if (copy_from_user(buf, user_buffer, count)) {
+		retval = -EFAULT;
+		goto error;
+	}
+	/* initialize the urb properly */
+	usb_fill_bulk_urb(urb, dev->udev,
+			  usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
+			  buf, count, ntrig_write_bulk_callback, dev);
+	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	usb_anchor_urb(urb, &dev->submitted);
+	/* send the data out the bulk port */
+	retval = usb_submit_urb(urb, GFP_KERNEL);
+	if (retval) {
+		err("N-trig bulk: %s() - failed submitting write urb, error %d",
+		    __func__, retval);
+		goto error_unanchor;
+	}
+	/*Release our reference to this urb*/
+	/*USB core will eventually free it entirely */
+	usb_free_urb(urb);
+exit:
+	return count;
+error_unanchor:
+	usb_unanchor_urb(urb);
+error:
+	usb_buffer_free(dev->udev, count, buf, urb->transfer_dma);
+	usb_free_urb(urb);
+err_no_buf:
+	return retval;
+}
+
+static const struct file_operations ntrig_fops = {
+	.owner =        THIS_MODULE,
+	.read =         ntrig_read,
+	.write =        ntrig_write,
+	.open =         ntrig_open,
+	.unlocked_ioctl = ntrig_ioctl,
+	.release =      ntrig_release,
+};
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver ntrig_class = {
+	.name =         "ntrig%d",
+	.fops =         &ntrig_fops,
+	.minor_base =   USB_NTRIG_MINOR_BASE,
+};
+
+static int ntrig_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+	struct usb_ntrig *dev = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+	int i;
+	int retval = -ENOMEM;
+	/* allocate memory for our device state and initialize it */
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL) {
+		err("%s() Out of memory", __func__);
+		goto error;
+	}
+	kref_init(&dev->kref);
+	init_usb_anchor(&dev->submitted);
+	dev->udev = usb_get_dev(interface_to_usbdev(interface));
+	dev->interface = interface;
+	dev->product_id = le16_to_cpu(dev->udev->descriptor.idProduct);
+	/* set up the endpoint information */
+	/* use only the first bulk-in and bulk-out endpoints */
+	iface_desc = interface->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*/
+			/*We ignore buffer size from descriptor*/
+			dev->bulk_in_size = BULK_IN_MAX_MSG_LEN;
+			dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
+			dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
+			if (!dev->bulk_in_buffer) {
+				err("%s() Could not allocate bulk_in_buffer",
+				    __func__);
+				goto error;
+			}
+		}
+		if (!dev->bulk_out_endpointAddr &&
+		    usb_endpoint_is_bulk_out(endpoint)) {
+			/* we found a bulk out endpoint */
+			dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
+		}
+	}
+	/* save our data pointer in this interface device */
+	usb_set_intfdata(interface, dev);
+	/* we can register the device now, as it is ready */
+	retval = usb_register_dev(interface, &ntrig_class);
+	if (retval) {
+		/* something prevented us from registering this driver */
+		err("%s() Not able to get a minor for this device.", __func__);
+		usb_set_intfdata(interface, NULL);
+		goto error;
+	}
+	i = le16_to_cpu(dev->udev->descriptor.bcdDevice);
+	dev_info(&interface->dev, "N-trig bulk Version %1d%1d.%1d%1d found "
+		 "at address %d\n", (i & 0xF000)>>12, (i & 0xF00)>>8,
+		 (i & 0xF0)>>4, (i & 0xF), dev->udev->devnum);
+	/* let the user know what node this device is now attached to */
+	dev_info(&interface->dev, "USB N-trig device now attached to N-trig bulk-%d\n",
+		 interface->minor);
+	return 0;
+error:
+	if (dev)
+		kref_put(&dev->kref, ntrig_delete);
+	return retval;
+}
+
+static void ntrig_draw_down(struct usb_ntrig *dev)
+{
+	int time;
+	time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
+	if (!time)
+		usb_kill_anchored_urbs(&dev->submitted);
+}
+
+static int ntrig_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct usb_ntrig *dev = usb_get_intfdata(intf);
+	if (!dev)
+		return 0;
+	ntrig_draw_down(dev);
+	return 0;
+}
+
+static int ntrig_resume(struct usb_interface *intf)
+{
+	return 0;
+}
+
+static void ntrig_disconnect(struct usb_interface *interface)
+{
+	struct usb_ntrig *dev;
+	int minor = interface->minor;
+	mutex_lock(&open_disc_mutex);
+	dev = usb_get_intfdata(interface);
+	usb_set_intfdata(interface, NULL);
+	mutex_unlock(&open_disc_mutex);
+	/* give back our minor */
+	usb_deregister_dev(interface, &ntrig_class);
+	/* decrement our usage count */
+	kref_put(&dev->kref, ntrig_delete);
+	dev_info(&interface->dev, "USB N-trig #%d now disconnected\n", minor);
+}
+
+static struct usb_driver ntrig_driver = {
+	.name =		"usbntrig",
+	.probe =	ntrig_probe,
+	.disconnect =	ntrig_disconnect,
+	.suspend =	ntrig_suspend,
+	.resume =	ntrig_resume,
+	.id_table =	id_table,
+	.supports_autosuspend = 1,
+};
+
+static int __init usb_ntrig_init(void)
+{
+	int result;
+	result = usb_register(&ntrig_driver);
+	if (result)
+		err("%s() usb_register failed. Error number %d",
+		    __func__, result);
+	return result;
+}
+
+
+static void __exit usb_ntrig_exit(void)
+{
+	usb_deregister(&ntrig_driver);
+}
+
+module_init(usb_ntrig_init);
+module_exit(usb_ntrig_exit);
+
+MODULE_AUTHOR("Micki Balanga <micki@n-trig.com>");
+MODULE_DESCRIPTION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
-- 
1.6.3.3

