[BACK]Return to tiglusb.c CVS log [TXT][DIR] Up to [Development] / linux-2.6-xfs / drivers / usb / misc

File: [Development] / linux-2.6-xfs / drivers / usb / misc / Attic / tiglusb.c (download)

Revision 1.7, Wed Jan 5 14:17:31 2005 UTC (12 years, 9 months ago) by nathans.longdrop.melbourne.sgi.com
Branch: MAIN
Changes since 1.6: +43 -17 lines

Merge up to 2.6.10.
Merge of 2.6.x-xfs-melb:linux:21010a by kenmcd.

/* Hey EMACS -*- linux-c -*-
 *
 * tiglusb -- Texas Instruments' USB GraphLink (aka SilverLink) driver.
 * Target: Texas Instruments graphing calculators (http://lpg.ticalc.org).
 *
 * Copyright (C) 2001-2004:
 *   Romain Lievin <roms@tilp.info>
 *   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: Documentation/usb/silverlink.txt
 * and the website at:  http://lpg.ticalc.org/prj_usb/
 * for more info.
 *
 * History:
 *   1.0x, Romain & Julien: initial submit.
 *   1.03, Greg Kroah: modifications.
 *   1.04, Julien: clean-up & fixes; Romain: 2.4 backport.
 *   1.05, Randy Dunlap: bug fix with the timeout parameter (divide-by-zero).
 *   1.06, Romain: synched with 2.5, version/firmware changed (confusing).
 *   1.07, Romain: fixed bad use of usb_clear_halt (invalid argument);
 *          timeout argument checked in ioctl + clean-up.
 *   1.08, Romain: added support of USB port embedded on some TI's handhelds.
 */

#include <linux/module.h>
#include <linux/socket.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/device.h>

#include <linux/ticable.h>
#include "tiglusb.h"

/*
 * Version Information
 */
#define DRIVER_VERSION "1.08"
#define DRIVER_AUTHOR  "Romain Lievin <roms@tilp.info> & 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 struct class_simple *tiglusb_class;

/*---------- misc functions ------------------------------------------- */

/*
 * Re-initialize device
 */
static inline int
clear_device (struct usb_device *dev)
{
	if (usb_reset_configuration (dev) < 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, 2);
	if (usb_clear_halt (dev, pipe)) {
		err ("clear_pipe (w), request failed");
		return -1;
	}

	pipe = usb_rcvbulkpipe (dev, 1);
	if (usb_clear_halt (dev, pipe)) {
		err ("clear_pipe (r), request failed");
		return -1;
	}

	return 0;
}

/* ----- file operations functions--------------------------------------- */

static int
tiglusb_open (struct inode *inode, struct file *filp)
{
	int devnum = iminor(inode);
	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;
		}

		msleep_interruptible(500);

		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 nonseekable_open(inode, filp);
}

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 __user *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;
	unsigned int pipe;

	if (*f_pos)
		return -ESPIPE;

	if (s->remove_pending)
		return -EIO;

	if (!s->dev)
		return -EIO;

	buffer = kmalloc (s->max_ps, GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	bytes_to_read = (count >= s->max_ps) ? s->max_ps : 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 */
		if (!bytes_read)
			dbg ("quirk !");
		warn ("tiglusb_read, 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, 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:
	kfree(buffer);
	return ret ? ret : bytes_read;
}

static ssize_t
tiglusb_write (struct file *filp, const char __user *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;
	unsigned int pipe;

	if (*f_pos)
		return -ESPIPE;

	if (s->remove_pending)
		return -EIO;

	if (!s->dev)
		return -EIO;

	buffer = kmalloc (s->max_ps, GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	bytes_to_write = (count >= s->max_ps) ? s->max_ps : 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, 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:
	kfree(buffer);
	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:
		if (arg > 0)
			timeout = arg;
		else
			ret = -EINVAL;
		break;
	case IOCTL_TIUSB_RESET_DEVICE:
		if (clear_device (s->dev))
			ret = -EIO;
		break;
	case IOCTL_TIUSB_RESET_PIPES:
		if (clear_pipes (s->dev))
			ret = -EIO;
		break;
	case IOCTL_TIUSB_GET_MAXPS:
                if (copy_to_user((int __user *) arg, &s->max_ps, sizeof(int)))
                        return -EFAULT;
                break;
        case IOCTL_TIUSB_GET_DEVID:
                if (copy_to_user((int __user *) arg, &s->dev->descriptor.idProduct,
                                 sizeof(int)))
                        return -EFAULT;
                break;
	default:
		ret = -ENOTTY;
		break;
	}

	up (&s->mutex);

	return ret;
}

/* ----- kernel module registering ------------------------------------ */

static struct file_operations tiglusb_fops = {
	.owner =	THIS_MODULE,
	.llseek =	no_llseek,
	.read =		tiglusb_read,
	.write =	tiglusb_write,
	.ioctl =	tiglusb_ioctl,
	.open =		tiglusb_open,
	.release =	tiglusb_release,
};

/* --- initialisation code ------------------------------------- */

static int
tiglusb_probe (struct usb_interface *intf,
	       const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	int minor = -1;
	int i, err = 0;
	ptiglusb_t s;
	struct usb_host_config *conf;
        struct usb_host_interface *ifdata = NULL;
        int max_ps;

	dbg ("probing vendor id 0x%x, device id 0x%x",
	     dev->descriptor.idVendor, dev->descriptor.idProduct);

	/*
	 * We don't handle multiple configurations. As of version 0x0103 of
	 * the TIGL hardware, there's only 1 configuration.
	 */

	if (dev->descriptor.bNumConfigurations != 1) {
		err = -ENODEV;
		goto out;
	}

	if (dev->descriptor.idVendor != 0x451) {
		err = -ENODEV;
		goto out;
	}

	if ((dev->descriptor.idProduct != 0xe001) &&
            (dev->descriptor.idProduct != 0xe004) &&
            (dev->descriptor.idProduct != 0xe008)) {
                err = -ENODEV;
                goto out;
        }

	/*
         * TI introduced some new handhelds with embedded USB port.
         * Port advertises same config as SilverLink cable but with a 
	 * different maximum packet size (64 rather than 32).
         */

        conf = dev->actconfig;
        ifdata = conf->interface[0]->cur_altsetting;
        max_ps = ifdata->endpoint[0].desc.wMaxPacketSize;

        info("max packet size of %d/%d bytes\n",
             ifdata->endpoint[0].desc.wMaxPacketSize,
             ifdata->endpoint[1].desc.wMaxPacketSize);

	/*
	 * Find a tiglusb struct
	 */
	for (i = 0; i < MAXTIGL; i++) {
		ptiglusb_t s = &tiglusb[i];
		if (!s->dev) {
			minor = i;
			break;
		}
	}

	if (minor == -1) {
		err = -ENODEV;
		goto out;
	}

	s = &tiglusb[minor];

	down (&s->mutex);
	s->remove_pending = 0;
	s->dev = dev;
	s->max_ps = max_ps;
	up (&s->mutex);
	dbg ("bound to interface");

	class_simple_device_add(tiglusb_class, MKDEV(TIUSB_MAJOR, TIUSB_MINOR + s->minor),
			NULL, "usb%d", s->minor);
	err = devfs_mk_cdev(MKDEV(TIUSB_MAJOR, TIUSB_MINOR) + s->minor,
			S_IFCHR | S_IRUGO | S_IWUGO,
			"ticables/usb/%d", s->minor);

	if (err)
		goto out_class;

	/* Display firmware version */
	info ("firmware revision %i.%02x",
		dev->descriptor.bcdDevice >> 8,
		dev->descriptor.bcdDevice & 0xff);

	usb_set_intfdata (intf, s);
	err = 0;
	goto out;

out_class:
	class_simple_device_remove(MKDEV(TIUSB_MAJOR, TIUSB_MINOR + s->minor));
out:
	return err;
}

static void
tiglusb_disconnect (struct usb_interface *intf)
{
	wait_queue_t __wait;
	ptiglusb_t s = usb_get_intfdata (intf);
	
	init_waitqueue_entry(&__wait, current);
	

	usb_set_intfdata (intf, NULL);
	if (!s || !s->dev) {
		info ("bogus disconnect");
		return;
	}

	s->remove_pending = 1;
	wake_up (&s->wait);
	add_wait_queue(&s->wait, &__wait);
	set_current_state(TASK_UNINTERRUPTIBLE);
	if (s->state == _started)
		schedule();
	current->state = TASK_RUNNING;
	remove_wait_queue(&s->wait, &__wait);
	down (&s->mutex);
	s->dev = NULL;
	s->opened = 0;

	class_simple_device_remove(MKDEV(TIUSB_MAJOR, TIUSB_MINOR + s->minor));
	devfs_remove("ticables/usb/%d", s->minor);

	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 = {
	.owner =	THIS_MODULE,
	.name =		"tiglusb",
	.probe =	tiglusb_probe,
	.disconnect =	tiglusb_disconnect,
	.id_table =	tiglusb_ids,
};

/* --- initialisation code ------------------------------------- */

#ifndef MODULE
/*
 * You can use 'tiusb=timeout' to set timeout.
 */
static int __init
tiglusb_setup (char *str)
{
	int ints[2];

	str = get_options (str, ARRAY_SIZE (ints), ints);

	if (ints[0] > 0) {
		if (ints[1] > 0)
			timeout = ints[1];
		else
			info ("tiglusb: wrong timeout value (0), using default value.");
	}

	return 1;
}
#endif

static int __init
tiglusb_init (void)
{
	unsigned u;
	int result, err = 0;

	/* 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 (register_chrdev (TIUSB_MAJOR, "tiglusb", &tiglusb_fops)) {
		err ("unable to get major %d", TIUSB_MAJOR);
		err = -EIO;
		goto out;
	}

	/* Use devfs, tree: /dev/ticables/usb/[0..3] */
	devfs_mk_dir ("ticables/usb");

	tiglusb_class = class_simple_create(THIS_MODULE, "tiglusb");
	if (IS_ERR(tiglusb_class)) {
		err = PTR_ERR(tiglusb_class);
		goto out_chrdev;
	}
	/* register USB module */
	result = usb_register (&tiglusb_driver);
	if (result < 0) {
		err = -1;
		goto out_chrdev;
	}

	info (DRIVER_DESC ", version " DRIVER_VERSION);

	err = 0;
	goto out;

out_chrdev:
	unregister_chrdev (TIUSB_MAJOR, "tiglusb");
out:
	return err;
}

static void __exit
tiglusb_cleanup (void)
{
	usb_deregister (&tiglusb_driver);
	class_simple_destroy(tiglusb_class);
	devfs_remove("ticables/usb");
	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_param(timeout, int, 0);
MODULE_PARM_DESC (timeout, "Timeout in tenths of seconds (default=1.5 seconds)");

/* --------------------------------------------------------------------- */