Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19770693 authored by Jarod Wilson's avatar Jarod Wilson Committed by Mauro Carvalho Chehab
Browse files

V4L/DVB: staging/lirc: add lirc_streamzap driver

parent 404f3e95
Loading
Loading
Loading
Loading
+821 −0
Original line number Original line Diff line number Diff line
/*
 * Streamzap Remote Control driver
 *
 * Copyright (c) 2005 Christoph Bartelmus <lirc@bartelmus.de>
 *
 * This driver was based on the work of Greg Wickham and Adrian
 * Dewhurst. It was substantially rewritten to support correct signal
 * gaps and now maintains a delay buffer, which is used to present
 * consistent timing behaviour to user space applications. Without the
 * delay buffer an ugly hack would be required in lircd, which can
 * cause sluggish signal decoding in certain situations.
 *
 * This driver is based on the USB skeleton driver packaged with the
 * kernel; copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.com)
 *
 *  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.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/smp_lock.h>
#include <linux/completion.h>
#include <linux/uaccess.h>
#include <linux/usb.h>

#include <media/lirc.h>
#include <media/lirc_dev.h>

#define DRIVER_VERSION	"1.28"
#define DRIVER_NAME	"lirc_streamzap"
#define DRIVER_DESC	"Streamzap Remote Control driver"

static int debug;

#define USB_STREAMZAP_VENDOR_ID		0x0e9c
#define USB_STREAMZAP_PRODUCT_ID	0x0000

/* Use our own dbg macro */
#define dprintk(fmt, args...)					\
	do {							\
		if (debug)					\
			printk(KERN_DEBUG DRIVER_NAME "[%d]: "	\
			       fmt "\n", ## args);		\
	} while (0)

/* table of devices that work with this driver */
static struct usb_device_id streamzap_table[] = {
	/* Streamzap Remote Control */
	{ USB_DEVICE(USB_STREAMZAP_VENDOR_ID, USB_STREAMZAP_PRODUCT_ID) },
	/* Terminating entry */
	{ }
};

MODULE_DEVICE_TABLE(usb, streamzap_table);

#define STREAMZAP_PULSE_MASK 0xf0
#define STREAMZAP_SPACE_MASK 0x0f
#define STREAMZAP_TIMEOUT    0xff
#define STREAMZAP_RESOLUTION 256

/* number of samples buffered */
#define STREAMZAP_BUF_LEN 128

enum StreamzapDecoderState {
	PulseSpace,
	FullPulse,
	FullSpace,
	IgnorePulse
};

/* Structure to hold all of our device specific stuff
 *
 * some remarks regarding locking:
 * theoretically this struct can be accessed from three threads:
 *
 * - from lirc_dev through set_use_inc/set_use_dec
 *
 * - from the USB layer throuh probe/disconnect/irq
 *
 *   Careful placement of lirc_register_driver/lirc_unregister_driver
 *   calls will prevent conflicts. lirc_dev makes sure that
 *   set_use_inc/set_use_dec are not being executed and will not be
 *   called after lirc_unregister_driver returns.
 *
 * - by the timer callback
 *
 *   The timer is only running when the device is connected and the
 *   LIRC device is open. Making sure the timer is deleted by
 *   set_use_dec will make conflicts impossible.
 */
struct usb_streamzap {

	/* usb */
	/* save off the usb device pointer */
	struct usb_device	*udev;
	/* the interface for this device */
	struct usb_interface	*interface;

	/* buffer & dma */
	unsigned char		*buf_in;
	dma_addr_t		dma_in;
	unsigned int		buf_in_len;

	struct usb_endpoint_descriptor *endpoint;

	/* IRQ */
	struct urb		*urb_in;

	/* lirc */
	struct lirc_driver	*driver;
	struct lirc_buffer	*delay_buf;

	/* timer used to support delay buffering */
	struct timer_list	delay_timer;
	int			timer_running;
	spinlock_t		timer_lock;

	/* tracks whether we are currently receiving some signal */
	int			idle;
	/* sum of signal lengths received since signal start */
	unsigned long		sum;
	/* start time of signal; necessary for gap tracking */
	struct timeval		signal_last;
	struct timeval		signal_start;
	enum StreamzapDecoderState decoder_state;
	struct timer_list	flush_timer;
	int			flush;
	int			in_use;
	int			timeout_enabled;
};


/* local function prototypes */
static int streamzap_probe(struct usb_interface *interface,
			   const struct usb_device_id *id);
static void streamzap_disconnect(struct usb_interface *interface);
static void usb_streamzap_irq(struct urb *urb);
static int streamzap_use_inc(void *data);
static void streamzap_use_dec(void *data);
static long streamzap_ioctl(struct file *filep, unsigned int cmd,
			    unsigned long arg);
static int streamzap_suspend(struct usb_interface *intf, pm_message_t message);
static int streamzap_resume(struct usb_interface *intf);

/* usb specific object needed to register this driver with the usb subsystem */

static struct usb_driver streamzap_driver = {
	.name =		DRIVER_NAME,
	.probe =	streamzap_probe,
	.disconnect =	streamzap_disconnect,
	.suspend =	streamzap_suspend,
	.resume =	streamzap_resume,
	.id_table =	streamzap_table,
};

static void stop_timer(struct usb_streamzap *sz)
{
	unsigned long flags;

	spin_lock_irqsave(&sz->timer_lock, flags);
	if (sz->timer_running) {
		sz->timer_running = 0;
		spin_unlock_irqrestore(&sz->timer_lock, flags);
		del_timer_sync(&sz->delay_timer);
	} else {
		spin_unlock_irqrestore(&sz->timer_lock, flags);
	}
}

static void flush_timeout(unsigned long arg)
{
	struct usb_streamzap *sz = (struct usb_streamzap *) arg;

	/* finally start accepting data */
	sz->flush = 0;
}
static void delay_timeout(unsigned long arg)
{
	unsigned long flags;
	/* deliver data every 10 ms */
	static unsigned long timer_inc =
		(10000/(1000000/HZ)) == 0 ? 1 : (10000/(1000000/HZ));
	struct usb_streamzap *sz = (struct usb_streamzap *) arg;
	int data;

	spin_lock_irqsave(&sz->timer_lock, flags);

	if (!lirc_buffer_empty(sz->delay_buf) &&
	    !lirc_buffer_full(sz->driver->rbuf)) {
		lirc_buffer_read(sz->delay_buf, (unsigned char *) &data);
		lirc_buffer_write(sz->driver->rbuf, (unsigned char *) &data);
	}
	if (!lirc_buffer_empty(sz->delay_buf)) {
		while (lirc_buffer_available(sz->delay_buf) <
		       STREAMZAP_BUF_LEN / 2 &&
		       !lirc_buffer_full(sz->driver->rbuf)) {
			lirc_buffer_read(sz->delay_buf,
					   (unsigned char *) &data);
			lirc_buffer_write(sz->driver->rbuf,
					    (unsigned char *) &data);
		}
		if (sz->timer_running) {
			sz->delay_timer.expires = jiffies + timer_inc;
			add_timer(&sz->delay_timer);
		}
	} else {
		sz->timer_running = 0;
	}

	if (!lirc_buffer_empty(sz->driver->rbuf))
		wake_up(&sz->driver->rbuf->wait_poll);

	spin_unlock_irqrestore(&sz->timer_lock, flags);
}

static void flush_delay_buffer(struct usb_streamzap *sz)
{
	int data;
	int empty = 1;

	while (!lirc_buffer_empty(sz->delay_buf)) {
		empty = 0;
		lirc_buffer_read(sz->delay_buf, (unsigned char *) &data);
		if (!lirc_buffer_full(sz->driver->rbuf)) {
			lirc_buffer_write(sz->driver->rbuf,
					    (unsigned char *) &data);
		} else {
			dprintk("buffer overflow", sz->driver->minor);
		}
	}
	if (!empty)
		wake_up(&sz->driver->rbuf->wait_poll);
}

static void push(struct usb_streamzap *sz, unsigned char *data)
{
	unsigned long flags;

	spin_lock_irqsave(&sz->timer_lock, flags);
	if (lirc_buffer_full(sz->delay_buf)) {
		int read_data;

		lirc_buffer_read(sz->delay_buf,
				   (unsigned char *) &read_data);
		if (!lirc_buffer_full(sz->driver->rbuf)) {
			lirc_buffer_write(sz->driver->rbuf,
					    (unsigned char *) &read_data);
		} else {
			dprintk("buffer overflow", sz->driver->minor);
		}
	}

	lirc_buffer_write(sz->delay_buf, data);

	if (!sz->timer_running) {
		sz->delay_timer.expires = jiffies + HZ/10;
		add_timer(&sz->delay_timer);
		sz->timer_running = 1;
	}

	spin_unlock_irqrestore(&sz->timer_lock, flags);
}

static void push_full_pulse(struct usb_streamzap *sz,
			    unsigned char value)
{
	int pulse;

	if (sz->idle) {
		long deltv;
		int tmp;

		sz->signal_last = sz->signal_start;
		do_gettimeofday(&sz->signal_start);

		deltv = sz->signal_start.tv_sec-sz->signal_last.tv_sec;
		if (deltv > 15) {
			/* really long time */
			tmp = LIRC_SPACE(LIRC_VALUE_MASK);
		} else {
			tmp = (int) (deltv*1000000+
					sz->signal_start.tv_usec -
					sz->signal_last.tv_usec);
			tmp -= sz->sum;
			tmp = LIRC_SPACE(tmp);
		}
		dprintk("ls %u", sz->driver->minor, tmp);
		push(sz, (char *)&tmp);

		sz->idle = 0;
		sz->sum = 0;
	}

	pulse = ((int) value) * STREAMZAP_RESOLUTION;
	pulse += STREAMZAP_RESOLUTION / 2;
	sz->sum += pulse;
	pulse = LIRC_PULSE(pulse);

	dprintk("p %u", sz->driver->minor, pulse & PULSE_MASK);
	push(sz, (char *)&pulse);
}

static void push_half_pulse(struct usb_streamzap *sz,
			    unsigned char value)
{
	push_full_pulse(sz, (value & STREAMZAP_PULSE_MASK)>>4);
}

static void push_full_space(struct usb_streamzap *sz,
			    unsigned char value)
{
	int space;

	space = ((int) value)*STREAMZAP_RESOLUTION;
	space += STREAMZAP_RESOLUTION/2;
	sz->sum += space;
	space = LIRC_SPACE(space);
	dprintk("s %u", sz->driver->minor, space);
	push(sz, (char *)&space);
}

static void push_half_space(struct usb_streamzap *sz,
			    unsigned char value)
{
	push_full_space(sz, value & STREAMZAP_SPACE_MASK);
}

/**
 * usb_streamzap_irq - IRQ handler
 *
 * This procedure is invoked on reception of data from
 * the usb remote.
 */
static void usb_streamzap_irq(struct urb *urb)
{
	struct usb_streamzap *sz;
	int		len;
	unsigned int	i = 0;

	if (!urb)
		return;

	sz = urb->context;
	len = urb->actual_length;

	switch (urb->status) {
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
		/*
		 * this urb is terminated, clean up.
		 * sz might already be invalid at this point
		 */
		dprintk("urb status: %d", -1, urb->status);
		return;
	default:
		break;
	}

	dprintk("received %d", sz->driver->minor, urb->actual_length);
	if (!sz->flush) {
		for (i = 0; i < urb->actual_length; i++) {
			dprintk("%d: %x", sz->driver->minor,
				i, (unsigned char) sz->buf_in[i]);
			switch (sz->decoder_state) {
			case PulseSpace:
				if ((sz->buf_in[i]&STREAMZAP_PULSE_MASK) ==
				    STREAMZAP_PULSE_MASK) {
					sz->decoder_state = FullPulse;
					continue;
				} else if ((sz->buf_in[i]&STREAMZAP_SPACE_MASK)
					   == STREAMZAP_SPACE_MASK) {
					push_half_pulse(sz, sz->buf_in[i]);
					sz->decoder_state = FullSpace;
					continue;
				} else {
					push_half_pulse(sz, sz->buf_in[i]);
					push_half_space(sz, sz->buf_in[i]);
				}
				break;
			case FullPulse:
				push_full_pulse(sz, sz->buf_in[i]);
				sz->decoder_state = IgnorePulse;
				break;
			case FullSpace:
				if (sz->buf_in[i] == STREAMZAP_TIMEOUT) {
					sz->idle = 1;
					stop_timer(sz);
					if (sz->timeout_enabled) {
						int timeout =
							LIRC_TIMEOUT
							(STREAMZAP_TIMEOUT *
							STREAMZAP_RESOLUTION);
						push(sz, (char *)&timeout);
					}
					flush_delay_buffer(sz);
				} else
					push_full_space(sz, sz->buf_in[i]);
				sz->decoder_state = PulseSpace;
				break;
			case IgnorePulse:
				if ((sz->buf_in[i]&STREAMZAP_SPACE_MASK) ==
				    STREAMZAP_SPACE_MASK) {
					sz->decoder_state = FullSpace;
					continue;
				}
				push_half_space(sz, sz->buf_in[i]);
				sz->decoder_state = PulseSpace;
				break;
			}
		}
	}

	usb_submit_urb(urb, GFP_ATOMIC);

	return;
}

static struct file_operations streamzap_fops = {
	.owner		= THIS_MODULE,
	.unlocked_ioctl	= streamzap_ioctl,
	.read		= lirc_dev_fop_read,
	.write		= lirc_dev_fop_write,
	.poll		= lirc_dev_fop_poll,
	.open		= lirc_dev_fop_open,
	.release	= lirc_dev_fop_close,
};


/**
 *	streamzap_probe
 *
 *	Called by usb-core to associated with a candidate device
 *	On any failure the return value is the ERROR
 *	On success return 0
 */
static int streamzap_probe(struct usb_interface *interface,
			   const struct usb_device_id *id)
{
	struct usb_device *udev = interface_to_usbdev(interface);
	struct usb_host_interface *iface_host;
	struct usb_streamzap *sz;
	struct lirc_driver *driver;
	struct lirc_buffer *lirc_buf;
	struct lirc_buffer *delay_buf;
	char buf[63], name[128] = "";
	int retval = -ENOMEM;
	int minor = 0;

	/* Allocate space for device driver specific data */
	sz = kzalloc(sizeof(struct usb_streamzap), GFP_KERNEL);
	if (sz == NULL)
		return -ENOMEM;

	sz->udev = udev;
	sz->interface = interface;

	/* Check to ensure endpoint information matches requirements */
	iface_host = interface->cur_altsetting;

	if (iface_host->desc.bNumEndpoints != 1) {
		err("%s: Unexpected desc.bNumEndpoints (%d)", __func__,
		    iface_host->desc.bNumEndpoints);
		retval = -ENODEV;
		goto free_sz;
	}

	sz->endpoint = &(iface_host->endpoint[0].desc);
	if ((sz->endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
	    != USB_DIR_IN) {
		err("%s: endpoint doesn't match input device 02%02x",
		    __func__, sz->endpoint->bEndpointAddress);
		retval = -ENODEV;
		goto free_sz;
	}

	if ((sz->endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
	    != USB_ENDPOINT_XFER_INT) {
		err("%s: endpoint attributes don't match xfer 02%02x",
		    __func__, sz->endpoint->bmAttributes);
		retval = -ENODEV;
		goto free_sz;
	}

	if (sz->endpoint->wMaxPacketSize == 0) {
		err("%s: endpoint message size==0? ", __func__);
		retval = -ENODEV;
		goto free_sz;
	}

	/* Allocate the USB buffer and IRQ URB */

	sz->buf_in_len = sz->endpoint->wMaxPacketSize;
	sz->buf_in = usb_alloc_coherent(sz->udev, sz->buf_in_len,
				      GFP_ATOMIC, &sz->dma_in);
	if (sz->buf_in == NULL)
		goto free_sz;

	sz->urb_in = usb_alloc_urb(0, GFP_KERNEL);
	if (sz->urb_in == NULL)
		goto free_sz;

	/* Connect this device to the LIRC sub-system */
	driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
	if (!driver)
		goto free_sz;

	lirc_buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
	if (!lirc_buf)
		goto free_driver;
	if (lirc_buffer_init(lirc_buf, sizeof(int), STREAMZAP_BUF_LEN))
		goto kfree_lirc_buf;

	delay_buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
	if (!delay_buf)
		goto free_lirc_buf;
	if (lirc_buffer_init(delay_buf, sizeof(int), STREAMZAP_BUF_LEN))
		goto kfree_delay_buf;

	sz->driver = driver;
	strcpy(sz->driver->name, DRIVER_NAME);
	sz->driver->minor = -1;
	sz->driver->sample_rate = 0;
	sz->driver->code_length = sizeof(int) * 8;
	sz->driver->features = LIRC_CAN_REC_MODE2 |
		LIRC_CAN_GET_REC_RESOLUTION |
		LIRC_CAN_SET_REC_TIMEOUT;
	sz->driver->data = sz;
	sz->driver->min_timeout = STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION;
	sz->driver->max_timeout = STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION;
	sz->driver->rbuf = lirc_buf;
	sz->delay_buf = delay_buf;
	sz->driver->set_use_inc = &streamzap_use_inc;
	sz->driver->set_use_dec = &streamzap_use_dec;
	sz->driver->fops = &streamzap_fops;
	sz->driver->dev = &interface->dev;
	sz->driver->owner = THIS_MODULE;

	sz->idle = 1;
	sz->decoder_state = PulseSpace;
	init_timer(&sz->delay_timer);
	sz->delay_timer.function = delay_timeout;
	sz->delay_timer.data = (unsigned long) sz;
	sz->timer_running = 0;
	spin_lock_init(&sz->timer_lock);

	init_timer(&sz->flush_timer);
	sz->flush_timer.function = flush_timeout;
	sz->flush_timer.data = (unsigned long) sz;
	/* Complete final initialisations */

	usb_fill_int_urb(sz->urb_in, udev,
		usb_rcvintpipe(udev, sz->endpoint->bEndpointAddress),
		sz->buf_in, sz->buf_in_len, usb_streamzap_irq, sz,
		sz->endpoint->bInterval);
	sz->urb_in->transfer_dma = sz->dma_in;
	sz->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	if (udev->descriptor.iManufacturer
	    && usb_string(udev, udev->descriptor.iManufacturer,
			  buf, sizeof(buf)) > 0)
		strlcpy(name, buf, sizeof(name));

	if (udev->descriptor.iProduct
	    && usb_string(udev, udev->descriptor.iProduct,
			  buf, sizeof(buf)) > 0)
		snprintf(name + strlen(name), sizeof(name) - strlen(name),
			 " %s", buf);

	minor = lirc_register_driver(driver);

	if (minor < 0)
		goto free_delay_buf;

	sz->driver->minor = minor;

	usb_set_intfdata(interface, sz);

	printk(KERN_INFO DRIVER_NAME "[%d]: %s on usb%d:%d attached\n",
	       sz->driver->minor, name,
	       udev->bus->busnum, sz->udev->devnum);

	return 0;

free_delay_buf:
	lirc_buffer_free(sz->delay_buf);
kfree_delay_buf:
	kfree(delay_buf);
free_lirc_buf:
	lirc_buffer_free(sz->driver->rbuf);
kfree_lirc_buf:
	kfree(lirc_buf);
free_driver:
	kfree(driver);
free_sz:
	if (retval == -ENOMEM)
		err("Out of memory");

	if (sz) {
		usb_free_urb(sz->urb_in);
		usb_free_coherent(udev, sz->buf_in_len, sz->buf_in, sz->dma_in);
		kfree(sz);
	}

	return retval;
}

static int streamzap_use_inc(void *data)
{
	struct usb_streamzap *sz = data;

	if (!sz) {
		dprintk("%s called with no context", -1, __func__);
		return -EINVAL;
	}
	dprintk("set use inc", sz->driver->minor);

	lirc_buffer_clear(sz->driver->rbuf);
	lirc_buffer_clear(sz->delay_buf);

	sz->flush_timer.expires = jiffies + HZ;
	sz->flush = 1;
	add_timer(&sz->flush_timer);

	sz->urb_in->dev = sz->udev;
	if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) {
		dprintk("open result = -EIO error submitting urb",
			sz->driver->minor);
		return -EIO;
	}
	sz->in_use++;

	return 0;
}

static void streamzap_use_dec(void *data)
{
	struct usb_streamzap *sz = data;

	if (!sz) {
		dprintk("%s called with no context", -1, __func__);
		return;
	}
	dprintk("set use dec", sz->driver->minor);

	if (sz->flush) {
		sz->flush = 0;
		del_timer_sync(&sz->flush_timer);
	}

	usb_kill_urb(sz->urb_in);

	stop_timer(sz);

	sz->in_use--;
}

static long streamzap_ioctl(struct file *filep, unsigned int cmd,
			    unsigned long arg)
{
	int result = 0;
	int val;
	struct usb_streamzap *sz = lirc_get_pdata(filep);

	switch (cmd) {
	case LIRC_GET_REC_RESOLUTION:
		result = put_user(STREAMZAP_RESOLUTION, (unsigned int *) arg);
		break;
	case LIRC_SET_REC_TIMEOUT:
		result = get_user(val, (int *)arg);
		if (result == 0) {
			if (val == STREAMZAP_TIMEOUT * STREAMZAP_RESOLUTION)
				sz->timeout_enabled = 1;
			else if (val == 0)
				sz->timeout_enabled = 0;
			else
				result = -EINVAL;
		}
		break;
	default:
		return lirc_dev_fop_ioctl(filep, cmd, arg);
	}
	return result;
}

/**
 * streamzap_disconnect
 *
 * Called by the usb core when the device is removed from the system.
 *
 * This routine guarantees that the driver will not submit any more urbs
 * by clearing dev->udev.  It is also supposed to terminate any currently
 * active urbs.  Unfortunately, usb_bulk_msg(), used in streamzap_read(),
 * does not provide any way to do this.
 */
static void streamzap_disconnect(struct usb_interface *interface)
{
	struct usb_streamzap *sz;
	int errnum;
	int minor;

	sz = usb_get_intfdata(interface);

	/* unregister from the LIRC sub-system */

	errnum = lirc_unregister_driver(sz->driver->minor);
	if (errnum != 0)
		dprintk("error in lirc_unregister: (returned %d)",
			sz->driver->minor, errnum);

	lirc_buffer_free(sz->delay_buf);
	lirc_buffer_free(sz->driver->rbuf);

	/* unregister from the USB sub-system */

	usb_free_urb(sz->urb_in);

	usb_free_coherent(sz->udev, sz->buf_in_len, sz->buf_in, sz->dma_in);

	minor = sz->driver->minor;
	kfree(sz->driver->rbuf);
	kfree(sz->driver);
	kfree(sz->delay_buf);
	kfree(sz);

	printk(KERN_INFO DRIVER_NAME "[%d]: disconnected\n", minor);
}

static int streamzap_suspend(struct usb_interface *intf, pm_message_t message)
{
	struct usb_streamzap *sz = usb_get_intfdata(intf);

	printk(KERN_INFO DRIVER_NAME "[%d]: suspend\n", sz->driver->minor);
	if (sz->in_use) {
		if (sz->flush) {
			sz->flush = 0;
			del_timer_sync(&sz->flush_timer);
		}

		stop_timer(sz);

		usb_kill_urb(sz->urb_in);
	}
	return 0;
}

static int streamzap_resume(struct usb_interface *intf)
{
	struct usb_streamzap *sz = usb_get_intfdata(intf);

	lirc_buffer_clear(sz->driver->rbuf);
	lirc_buffer_clear(sz->delay_buf);

	if (sz->in_use) {
		sz->flush_timer.expires = jiffies + HZ;
		sz->flush = 1;
		add_timer(&sz->flush_timer);

		sz->urb_in->dev = sz->udev;
		if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) {
			dprintk("open result = -EIO error submitting urb",
				sz->driver->minor);
			return -EIO;
		}
	}
	return 0;
}

/**
 *	usb_streamzap_init
 */
static int __init usb_streamzap_init(void)
{
	int result;

	/* register this driver with the USB subsystem */
	result = usb_register(&streamzap_driver);

	if (result) {
		err("usb_register failed. Error number %d",
		    result);
		return result;
	}

	printk(KERN_INFO DRIVER_NAME " " DRIVER_VERSION " registered\n");
	return 0;
}

/**
 *	usb_streamzap_exit
 */
static void __exit usb_streamzap_exit(void)
{
	usb_deregister(&streamzap_driver);
}


module_init(usb_streamzap_init);
module_exit(usb_streamzap_exit);

MODULE_AUTHOR("Christoph Bartelmus, Greg Wickham, Adrian Dewhurst");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Enable debugging messages");