Messages in this thread | | | Date | Sun, 31 Mar 2002 12:39:21 +0200 | From | Romain Liévin <> | Subject | Subject: kernel 2.5.7, tiusb: SilverLink cable driver for TI graphing calculators |
| |
Hi,
this is a new driver for handling a TI-GRAPH LINK USB (aka SilverLink) cable designed to connect a Texas Instruments graphing calculators to a computer/workstation through USB.
This driver has an official device number.
==============================[ tiglusb.c ]========================== /* 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. * */
#include <linux/module.h> #include <linux/socket.h> #include <linux/miscdevice.h> #include <linux/list.h> #include <linux/vmalloc.h> #include <linux/slab.h> #include <linux/init.h> #include <asm/uaccess.h> #include <asm/atomic.h> #include <linux/delay.h> #include <linux/usb.h> #include <linux/smp_lock.h> #include <linux/devfs_fs_kernel.h>
#include "ticable.h" #include "tiglusb.h"
/* * Version Information */ #define DRIVER_VERSION "1.01" #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 -----------------------------------------------*/
/* Unregister device */ static void usblp_cleanup (tiglusb_t *s) { devfs_unregister (s->devfs); //memset(tiglusb[s->minor], 0, sizeof(tiglusb_t)); info ("tiglusb%d removed", s->minor); }
/* Re-initialize device */ static int clear_device(struct usb_device *dev) { if (usb_set_configuration (dev, dev->config[0].bConfigurationValue) < 0) { printk("tiglusb: clear_device failed\n"); return -1; } return 0; }
/* Clear input & output pipes (endpoints) */ static int clear_pipes(struct usb_device *dev) { unsigned int pipe; pipe = usb_sndbulkpipe (dev, 1); if(usb_clear_halt(dev, usb_pipeendpoint(pipe))) { printk("tiglusb: clear_pipe (r), request failed\n"); return -1; } pipe = usb_sndbulkpipe (dev, 2); if(usb_clear_halt(dev, usb_pipeendpoint(pipe))) { printk("tiglusb: clear_pipe (w), request failed\n"); return -1; } return 0; }
/* ----- kernel module functions--------------------------------------- */
// ok static int tiglusb_open(struct inode *inode, struct file *file) { int devnum = MINOR(inode->i_rdev); ptiglusb_t s;
if (devnum < TIUSB_MINOR || devnum >= (TIUSB_MINOR + MAXTIGL)) return -EIO; s = &tiglusb[devnum - TIUSB_MINOR];
down(&s->mutex);
while (!s->dev || s->opened) { up (&s->mutex); if (file->f_flags & O_NONBLOCK) { return -EBUSY; } schedule_timeout (HZ / 2); if (signal_pending (current)) { return -EAGAIN; } down (&s->mutex); }
s->opened = 1; up(&s->mutex); file->f_pos = 0; file->private_data = s;
return 0; }
//ok static int tiglusb_release(struct inode *inode, struct file *file) { ptiglusb_t s = (ptiglusb_t) file->private_data; lock_kernel(); down (&s->mutex); s->state = _stopped; up (&s->mutex);
if (!s->remove_pending) clear_device(s->dev); else wake_up (&s->remove_ok);
s->opened = 0; unlock_kernel();
return 0; }
// static ssize_t tiglusb_read(struct file *file, char *buf, size_t count, loff_t * ppos) { ptiglusb_t s = (ptiglusb_t) file->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 (*ppos) 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)); //printk("hello: %i %i %i %i\n", // ETIMEDOUT, USB_ST_TIMEOUT, EPIPE, USB_ST_STALL); if (result == USB_ST_TIMEOUT) { /* NAK */ ret = -ETIMEDOUT; if(!bytes_read) { printk("quirk !\n"); } warn("tiglusb_read, NAK received."); goto out; } else if (result == USB_ST_STALL) { /* STALL -- shouldn't happen */ warn("CLEAR_FEATURE request to remove STALL condition.\n"); if(usb_clear_halt(s->dev, usb_pipeendpoint(pipe))) warn("send_packet, request failed\n"); //clear_device(s->dev); ret = -EPIPE; 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 (copy_to_user(buf, buffer, bytes_read)) { ret = -EFAULT; goto out; } out: return ret ? ret : bytes_read; }
// static ssize_t tiglusb_write(struct file *file, const char *buf, size_t count, loff_t * ppos) { ptiglusb_t s = (ptiglusb_t) file->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 (*ppos) 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 == USB_ST_TIMEOUT) { /* NAK */ warn("tiglusb_write, NAK received."); ret = -ETIMEDOUT; goto out; } else if (result == USB_ST_STALL) { /* STALL -- shouldn't happen */ warn("CLEAR_FEATURE request to remove STALL condition.\n"); if(usb_clear_halt(s->dev, usb_pipeendpoint(pipe))) warn("send_packet, request failed\n"); //clear_device(s->dev); ret = -EPIPE; 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; goto out; } out: return ret ? ret : bytes_written; }
//ok static int tiglusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { ptiglusb_t s = (ptiglusb_t) file->private_data; int ret = 0;
if (s->remove_pending) return -EIO; down (&s->mutex);
if (!s->dev) { up (&s->mutex); return -EIO; } switch (cmd) { case IOCTL_TIUSB_TIMEOUT: timeout = arg ; // timeout value is passed in tenth of seconds break; case IOCTL_TIUSB_RESET_DEVICE: printk(KERN_DEBUG "IOCTL_TIGLUSB_RESET_DEVICE\n"); if(clear_device(s->dev)) ret = -EIO; break; case IOCTL_TIUSB_RESET_PIPES: printk(KERN_DEBUG "IOCTL_TIGLUSB_RESET_PIPES\n"); 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 = { owner: THIS_MODULE, llseek: no_llseek, read: tiglusb_read, write: tiglusb_write, ioctl: tiglusb_ioctl, open: tiglusb_open, release: tiglusb_release, };
//ok static int tiglusb_find_struct (void) { int u; for (u=0; u<MAXTIGL; u++) { ptiglusb_t s = &tiglusb[u]; if (!s->dev) return u; } return -1; }
/* --- initialisation code ------------------------------------- */
//ok static void *tiglusb_probe (struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) { int minor; ptiglusb_t s; char name[8]; printk("tiglusb: probing vendor id 0x%x, device id 0x%x ifnum:%d\n", 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) { printk("tiglusb_probe: set_configuration failed\n"); return NULL; } minor = tiglusb_find_struct (); 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); printk("tiglusb: registering to devfs : major = %d, minor = %d, node = %s\n", 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 */ printk("tiglusb: link cable version %i.%02x\n", dev->descriptor.bcdDevice >> 8, dev->descriptor.bcdDevice & 0xff); return s; }
//ok static void tiglusb_disconnect (struct usb_device *dev, void *drv_context) { ptiglusb_t s = (ptiglusb_t) drv_context; if (!s || !s->dev) printk("bogus disconnect"); s->remove_pending = 1; wake_up (&s->wait); if (s->state == _started) sleep_on (&s->remove_ok); s->dev = NULL; s->opened = 0; /* cleanup now or later, on close*/ if (!s->opened) usblp_cleanup (s); else up (&s->mutex); /* unregister device */ devfs_unregister(s->devfs); s->devfs = NULL; printk("tiglusb: device disconnected\n"); }
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, fops: &tiglusb_fops, minor: TIUSB_MINOR, id_table: tiglusb_ids, };
/* --- initialisation code ------------------------------------- */
#ifndef MODULE /* You must set these - there is no sane way to probe for this cable. * You can use 'tipar=timeout,delay' to set these now. */ 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)) { printk("tiglusb: unable to get major %d\n", 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("tipar=", tiglusb_setup); module_init(tiglusb_init); module_exit(tiglusb_cleanup);
MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE(DRIVER_LICENSE);
EXPORT_NO_SYMBOLS;
MODULE_PARM(timeout, "i"); MODULE_PARM_DESC(timeout, "Timeout (default=1.5 seconds)");
/* --------------------------------------------------------------------- */
==============================[ tiglusb.h ]======================== /* Hey EMACS -*- linux-c -*- * * tiglusb - low level driver for SilverLink cable * * Copyright (C) 2000-2002, Romain Lievin <roms@lpg.ticalc.org> * under the terms of the GNU General Public License. * * Redistribution of this file is permitted under the terms of the GNU * Public License (GPL) */
#ifndef _TIGLUSB_H #define _TIGLUSB_H
/* * Max. number of devices supported */ #define MAXTIGL 16
/* * Max. packetsize for IN and OUT pipes */ #define BULK_RCV_MAX 32 #define BULK_SND_MAX 32
/* * The driver context... */
typedef enum { _stopped=0, _started } driver_state_t;
typedef struct { struct usb_device *dev; /* USB device handle */ struct semaphore mutex; /* locks this struct */ struct semaphore sem; wait_queue_head_t wait; /* for timed waits */ wait_queue_head_t remove_ok; int minor; /* which minor dev #? */ devfs_handle_t devfs; /* devfs device */
driver_state_t state; /* started/stopped */ int opened; /* tru if open */ int remove_pending;
struct urb readurb, writeurb; /* The urbs */ int wcomplete, rcomplete; /* R/W is complete */
char rd_buf[BULK_RCV_MAX]; /* read buffer */ char wr_buf[BULK_SND_MAX]; /* write buffer */ } tiglusb_t, *ptiglusb_t;
extern devfs_handle_t usb_devfs_handle; /* /dev/usb dir. */
#endif
==============================[ ticable.h ]========================== /* Hey EMACS -*- linux-c -*- * * tipar/tiser/tiusb - low level driver for handling link cables * designed for Texas Instruments graphing calculators. * * Copyright (C) 2000-2002, Romain Lievin <roms@lpg.ticalc.org> * * Redistribution of this file is permitted under the terms of the GNU * Public License (GPL) */
#ifndef _TICABLE_H #define _TICABLE_H 1
/* Internal default constants for the kernel module */ #define TIMAXTIME 15 /* 1.5 seconds */ #define IO_DELAY 10 /* 10 micro-seconds */
/* Major & minor number for character devices */ #define TIPAR_MAJOR 115 /* 0 to 7 */ #define TIPAR_MINOR 0
#define TISER_MAJOR 115 /* 8 to 15 */ #define TISER_MINOR 8
#define TIUSB_MAJOR 115 /* 16 to 31 */ #define TIUSB_MINOR 16
/* * Request values for the 'ioctl' function. */ #define IOCTL_TIPAR_DELAY _IOW('p', 0xa8, int) /* set delay */ #define IOCTL_TIPAR_TIMEOUT _IOW('p', 0xa9, int) /* set timeout */
#define IOCTL_TISER_DELAY _IOW('p', 0xa0, int) /* set delay */ #define IOCTL_TISER_TIMEOUT _IOW('p', 0xa1, int) /* set timeout */
#define IOCTL_TIUSB_TIMEOUT _IOW('N', 0x20, int) /* set timeout */ #define IOCTL_TIUSB_RESET_DEVICE _IOW('N', 0x21, int) /* reset device */ #define IOCTL_TIUSB_RESET_PIPES _IOW('N', 0x22, int) /* reset both pipes*/
#endif /* TICABLE_H */
--- Romain LIEVIN aka Roms, mail: roms@lpg.ticalc.org web: http://lpg.ticalc.org/prj_tilp (on www.ticalc.org) ICQ: 43585029 (GtkIcq0.60/Linux) - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
| |