patch-2.4.20 linux-2.4.20/drivers/usb/tiglusb.c

Next file: linux-2.4.20/drivers/usb/tiglusb.h
Previous file: linux-2.4.20/drivers/usb/stv680.h
Back to the patch index
Back to the overall index

diff -urN linux-2.4.19/drivers/usb/tiglusb.c linux-2.4.20/drivers/usb/tiglusb.c
@@ -0,0 +1,514 @@
+/* Hey EMACS -*- linux-c -*-
+ *
+ * tiglusb -- Texas Instruments' USB GraphLink (aka SilverLink) driver.
+ * Target: Texas Instruments graphing calculators (http://lpg.ticalc.org).
+ *
+ * Copyright (C) 2001-2002:
+ *   Romain Lievin <roms@lpg.ticalc.org>
+ *   Julien BLACHE <jb@technologeek.org>
+ * under the terms of the GNU General Public License.
+ *
+ * Based on dabusb.c, printer.c & scanner.c
+ *
+ * Please see the file: linux/Documentation/usb/SilverLink.txt
+ * and the website at:  http://lpg.ticalc.org/prj_usb/
+ * for more info.
+ *
+ * History :
+ *  16/07/2002 : v1.04 -- Julien BLACHE <jb@jblache.org>
+ *    + removed useless usblp_cleanup()
+ *    + removed {un,}lock_kernel() as suggested on lkml
+ *    + inlined clear_pipes() (used once)
+ *    + inlined clear_device() (small, used twice)
+ *    + removed tiglusb_find_struct() (used once, simple code)
+ *    + replaced down() with down_interruptible() wherever possible
+ *    + fixed double unregistering wrt devfs, causing devfs
+ *      to force an oops when the device is deconnected
+ *    + removed unused fields from struct tiglusb_t
+ */
+
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <linux/delay.h>
+#include <linux/usb.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <linux/ticable.h>
+#include "tiglusb.h"
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "1.04"
+#define DRIVER_AUTHOR  "Romain Lievin <roms@lpg.ticalc.org> & Julien Blache <jb@jblache.org>"
+#define DRIVER_DESC    "TI-GRAPH LINK USB (aka SilverLink) driver"
+#define DRIVER_LICENSE "GPL"
+
+/* ----- global variables --------------------------------------------- */
+
+static tiglusb_t tiglusb[MAXTIGL];
+static int timeout = TIMAXTIME;	/* timeout in tenth of seconds     */
+
+static devfs_handle_t devfs_handle;
+
+/*---------- misc functions ------------------------------------------- */
+
+/*
+ * Re-initialize device
+ */
+static inline int
+clear_device (struct usb_device *dev)
+{
+	if (usb_set_configuration (dev, dev->config[0].bConfigurationValue) < 0) {
+		err ("clear_device failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* 
+ * Clear input & output pipes (endpoints)
+ */
+static inline int
+clear_pipes (struct usb_device *dev)
+{
+	unsigned int pipe;
+
+	pipe = usb_sndbulkpipe (dev, 1);
+	if (usb_clear_halt (dev, usb_pipeendpoint (pipe))) {
+		err ("clear_pipe (r), request failed");
+		return -1;
+	}
+
+	pipe = usb_sndbulkpipe (dev, 2);
+	if (usb_clear_halt (dev, usb_pipeendpoint (pipe))) {
+		err ("clear_pipe (w), request failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* ----- file operations functions--------------------------------------- */
+
+static int
+tiglusb_open (struct inode *inode, struct file *filp)
+{
+	int devnum = minor (inode->i_rdev);
+	ptiglusb_t s;
+
+	if (devnum < TIUSB_MINOR || devnum >= (TIUSB_MINOR + MAXTIGL))
+		return -EIO;
+
+	s = &tiglusb[devnum - TIUSB_MINOR];
+
+	if (down_interruptible (&s->mutex)) {
+		return -ERESTARTSYS;
+	}
+
+	while (!s->dev || s->opened) {
+		up (&s->mutex);
+
+		if (filp->f_flags & O_NONBLOCK) {
+			return -EBUSY;
+		}
+
+		schedule_timeout (HZ / 2);
+
+		if (signal_pending (current)) {
+			return -EAGAIN;
+		}
+
+		if (down_interruptible (&s->mutex)) {
+			return -ERESTARTSYS;
+		}
+	}
+
+	s->opened = 1;
+	up (&s->mutex);
+
+	filp->f_pos = 0;
+	filp->private_data = s;
+
+	return 0;
+}
+
+static int
+tiglusb_release (struct inode *inode, struct file *filp)
+{
+	ptiglusb_t s = (ptiglusb_t) filp->private_data;
+
+	if (down_interruptible (&s->mutex)) {
+		return -ERESTARTSYS;
+	}
+
+	s->state = _stopped;
+	up (&s->mutex);
+
+	if (!s->remove_pending)
+		clear_device (s->dev);
+	else
+		wake_up (&s->remove_ok);
+
+	s->opened = 0;
+
+	return 0;
+}
+
+static ssize_t
+tiglusb_read (struct file *filp, char *buf, size_t count, loff_t * f_pos)
+{
+	ptiglusb_t s = (ptiglusb_t) filp->private_data;
+	ssize_t ret = 0;
+	int bytes_to_read = 0;
+	int bytes_read = 0;
+	int result = 0;
+	char buffer[BULK_RCV_MAX];
+	unsigned int pipe;
+
+	if (*f_pos)
+		return -ESPIPE;
+
+	if (s->remove_pending)
+		return -EIO;
+
+	if (!s->dev)
+		return -EIO;
+
+	bytes_to_read = (count >= BULK_RCV_MAX) ? BULK_RCV_MAX : count;
+
+	pipe = usb_rcvbulkpipe (s->dev, 1);
+	result = usb_bulk_msg (s->dev, pipe, buffer, bytes_to_read,
+			       &bytes_read, HZ / (timeout / 10));
+	if (result == -ETIMEDOUT) {	/* NAK */
+		ret = result;
+		if (!bytes_read) {
+			dbg ("quirk !");
+		}
+		warn ("tiglusb_read, NAK received.");
+		goto out;
+	} else if (result == -EPIPE) {	/* STALL -- shouldn't happen */
+		warn ("clear_halt request to remove STALL condition.");
+		if (usb_clear_halt (s->dev, usb_pipeendpoint (pipe)))
+			err ("clear_halt, request failed");
+		clear_device (s->dev);
+		ret = result;
+		goto out;
+	} else if (result < 0) {	/* We should not get any I/O errors */
+		err ("funky result: %d. Please notify maintainer.", result);
+		ret = -EIO;
+		goto out;
+	}
+
+	if (copy_to_user (buf, buffer, bytes_read)) {
+		ret = -EFAULT;
+	}
+
+      out:
+	return ret ? ret : bytes_read;
+}
+
+static ssize_t
+tiglusb_write (struct file *filp, const char *buf, size_t count, loff_t * f_pos)
+{
+	ptiglusb_t s = (ptiglusb_t) filp->private_data;
+	ssize_t ret = 0;
+	int bytes_to_write = 0;
+	int bytes_written = 0;
+	int result = 0;
+	char buffer[BULK_SND_MAX];
+	unsigned int pipe;
+
+	if (*f_pos)
+		return -ESPIPE;
+
+	if (s->remove_pending)
+		return -EIO;
+
+	if (!s->dev)
+		return -EIO;
+
+	bytes_to_write = (count >= BULK_SND_MAX) ? BULK_SND_MAX : count;
+	if (copy_from_user (buffer, buf, bytes_to_write)) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	pipe = usb_sndbulkpipe (s->dev, 2);
+	result = usb_bulk_msg (s->dev, pipe, buffer, bytes_to_write,
+			       &bytes_written, HZ / (timeout / 10));
+
+	if (result == -ETIMEDOUT) {	/* NAK */
+		warn ("tiglusb_write, NAK received.");
+		ret = result;
+		goto out;
+	} else if (result == -EPIPE) {	/* STALL -- shouldn't happen */
+		warn ("clear_halt request to remove STALL condition.");
+		if (usb_clear_halt (s->dev, usb_pipeendpoint (pipe)))
+			err ("clear_halt, request failed");
+		clear_device (s->dev);
+		ret = result;
+		goto out;
+	} else if (result < 0) {	/* We should not get any I/O errors */
+		warn ("funky result: %d. Please notify maintainer.", result);
+		ret = -EIO;
+		goto out;
+	}
+
+	if (bytes_written != bytes_to_write) {
+		ret = -EIO;
+	}
+
+      out:
+	return ret ? ret : bytes_written;
+}
+
+static int
+tiglusb_ioctl (struct inode *inode, struct file *filp,
+	       unsigned int cmd, unsigned long arg)
+{
+	ptiglusb_t s = (ptiglusb_t) filp->private_data;
+	int ret = 0;
+
+	if (s->remove_pending)
+		return -EIO;
+
+	if (down_interruptible (&s->mutex)) {
+		return -ERESTARTSYS;
+	}
+
+	if (!s->dev) {
+		up (&s->mutex);
+		return -EIO;
+	}
+
+	switch (cmd) {
+	case IOCTL_TIUSB_TIMEOUT:
+		timeout = arg;	// timeout value in tenth of seconds
+		break;
+	case IOCTL_TIUSB_RESET_DEVICE:
+		dbg ("IOCTL_TIGLUSB_RESET_DEVICE");
+		if (clear_device (s->dev))
+			ret = -EIO;
+		break;
+	case IOCTL_TIUSB_RESET_PIPES:
+		dbg ("IOCTL_TIGLUSB_RESET_PIPES");
+		if (clear_pipes (s->dev))
+			ret = -EIO;
+		break;
+	default:
+		ret = -ENOTTY;
+		break;
+	}
+
+	up (&s->mutex);
+
+	return ret;
+}
+
+/* ----- kernel module registering ------------------------------------ */
+
+static struct file_operations tiglusb_fops = {
+	.llseek =	no_llseek,
+	.read =		tiglusb_read,
+	.write =	tiglusb_write,
+	.ioctl =	tiglusb_ioctl,
+	.open =		tiglusb_open,
+	.release =	tiglusb_release,
+};
+
+/* --- initialisation code ------------------------------------- */
+
+static void *
+tiglusb_probe (struct usb_device *dev, unsigned int ifnum,
+	       const struct usb_device_id *id)
+{
+	int minor = -1;
+	int i;
+	ptiglusb_t s;
+	char name[8];
+
+	dbg ("probing vendor id 0x%x, device id 0x%x ifnum:%d",
+	     dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum);
+
+	/*
+	 * We don't handle multiple configurations. As of version 0x0103 of
+	 * the TIGL hardware, there's only 1 configuration.
+	 */
+
+	if (dev->descriptor.bNumConfigurations != 1)
+		return NULL;
+
+	if ((dev->descriptor.idProduct != 0xe001)
+	    && (dev->descriptor.idVendor != 0x451))
+		return NULL;
+
+	if (usb_set_configuration (dev, dev->config[0].bConfigurationValue) < 0) {
+		err ("tiglusb_probe: set_configuration failed");
+		return NULL;
+	}
+
+	/*
+	 * Find a tiglusb struct
+	 */
+	for (i = 0; i < MAXTIGL; i++) {
+		ptiglusb_t s = &tiglusb[i];
+		if (!s->dev) {
+			minor = i;
+			break;
+		}
+	}
+
+	if (minor == -1)
+		return NULL;
+
+	s = &tiglusb[minor];
+
+	down (&s->mutex);
+	s->remove_pending = 0;
+	s->dev = dev;
+	up (&s->mutex);
+	dbg ("bound to interface: %d", ifnum);
+
+	sprintf (name, "%d", s->minor);
+	dbg ("registering to devfs : major = %d, minor = %d, node = %s",
+	     TIUSB_MAJOR, (TIUSB_MINOR + s->minor), name);
+	s->devfs =
+	    devfs_register (devfs_handle, name, DEVFS_FL_DEFAULT, TIUSB_MAJOR,
+			    TIUSB_MINOR + s->minor, S_IFCHR | S_IRUGO | S_IWUGO,
+			    &tiglusb_fops, NULL);
+
+	/* Display firmware version */
+	info ("link cable version %i.%02x",
+		dev->descriptor.bcdDevice >> 8,
+		dev->descriptor.bcdDevice & 0xff);
+
+	return s;
+}
+
+static void
+tiglusb_disconnect (struct usb_device *dev, void *drv_context)
+{
+	ptiglusb_t s = (ptiglusb_t) drv_context;
+
+	if (!s || !s->dev)
+		info ("bogus disconnect");
+
+	s->remove_pending = 1;
+	wake_up (&s->wait);
+	if (s->state == _started)
+		sleep_on (&s->remove_ok);
+	down (&s->mutex);
+	s->dev = NULL;
+	s->opened = 0;
+
+	devfs_unregister (s->devfs);
+	s->devfs = NULL;
+
+	info ("device %d removed", s->minor);
+
+	up (&s->mutex);
+}
+
+static struct usb_device_id tiglusb_ids[] = {
+	{USB_DEVICE (0x0451, 0xe001)},
+	{}
+};
+
+MODULE_DEVICE_TABLE (usb, tiglusb_ids);
+
+static struct usb_driver tiglusb_driver = {
+	.name =		"tiglusb",
+	.probe =	tiglusb_probe,
+	.disconnect =	tiglusb_disconnect,
+	.id_table =	tiglusb_ids,
+};
+
+/* --- initialisation code ------------------------------------- */
+
+#ifndef MODULE
+/*
+ * You can use 'tiusb=timeout'
+ */
+static int __init
+tiglusb_setup (char *str)
+{
+	int ints[2];
+
+	str = get_options (str, ARRAY_SIZE (ints), ints);
+
+	if (ints[0] > 0) {
+		timeout = ints[1];
+	}
+
+	return 1;
+}
+#endif
+
+static int __init
+tiglusb_init (void)
+{
+	unsigned u;
+	int result;
+
+	/* initialize struct */
+	for (u = 0; u < MAXTIGL; u++) {
+		ptiglusb_t s = &tiglusb[u];
+		memset (s, 0, sizeof (tiglusb_t));
+		init_MUTEX (&s->mutex);
+		s->dev = NULL;
+		s->minor = u;
+		s->opened = 0;
+		init_waitqueue_head (&s->wait);
+		init_waitqueue_head (&s->remove_ok);
+	}
+
+	/* register device */
+	if (devfs_register_chrdev (TIUSB_MAJOR, "tiglusb", &tiglusb_fops)) {
+		err ("unable to get major %d", TIUSB_MAJOR);
+		return -EIO;
+	}
+
+	/* Use devfs, tree: /dev/ticables/usb/[0..3] */
+	devfs_handle = devfs_mk_dir (NULL, "ticables/usb", NULL);
+
+	/* register USB module */
+	result = usb_register (&tiglusb_driver);
+	if (result < 0) {
+		devfs_unregister_chrdev (TIUSB_MAJOR, "tiglusb");
+		return -1;
+	}
+
+	info (DRIVER_DESC ", " DRIVER_VERSION);
+
+	return 0;
+}
+
+static void __exit
+tiglusb_cleanup (void)
+{
+	usb_deregister (&tiglusb_driver);
+	devfs_unregister (devfs_handle);
+	devfs_unregister_chrdev (TIUSB_MAJOR, "tiglusb");
+}
+
+/* --------------------------------------------------------------------- */
+
+__setup ("tiusb=", tiglusb_setup);
+module_init (tiglusb_init);
+module_exit (tiglusb_cleanup);
+
+MODULE_AUTHOR (DRIVER_AUTHOR);
+MODULE_DESCRIPTION (DRIVER_DESC);
+MODULE_LICENSE (DRIVER_LICENSE);
+
+MODULE_PARM (timeout, "i");
+MODULE_PARM_DESC (timeout, "Timeout (default=1.5 seconds)");
+
+/* --------------------------------------------------------------------- */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)