Subject: kernel 2.5.7, tiusb: SilverLink cable driver for TI graphing calculators

From: Romain Liévin (rlievin@free.fr)
Date: Sun Mar 31 2002 - 05:39:21 EST


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/



This archive was generated by hypermail 2b29 : Sun Mar 31 2002 - 22:00:20 EST