1c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/*
2c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * lirc_sasem.c - USB remote support for LIRC
3c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Version 0.5
4c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *
5c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Copyright (C) 2004-2005 Oliver Stabel <oliver.stabel@gmx.de>
6c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *			 Tim Davies <tim@opensystems.net.au>
7c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *
8c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * This driver was derived from:
9c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *   Venky Raju <dev@venky.ws>
10c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *      "lirc_imon - "LIRC/VFD driver for Ahanix/Soundgraph IMON IR/VFD"
11c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *   Paul Miller <pmiller9@users.sourceforge.net>'s 2003-2004
12c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *      "lirc_atiusb - USB remote support for LIRC"
13c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *   Culver Consulting Services <henry@culcon.com>'s 2003
14c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *      "Sasem OnAir VFD/IR USB driver"
15c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *
16c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *
17c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * NOTE - The LCDproc iMon driver should work with this module.  More info at
18c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *	http://www.frogstorm.info/sasem
19c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
20c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
21c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/*
22c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  This program is free software; you can redistribute it and/or modify
23c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  it under the terms of the GNU General Public License as published by
24c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  the Free Software Foundation; either version 2 of the License, or
25c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  (at your option) any later version.
26c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *
27c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  This program is distributed in the hope that it will be useful,
28c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  but WITHOUT ANY WARRANTY; without even the implied warranty of
29c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  GNU General Public License for more details.
31c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *
32c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  You should have received a copy of the GNU General Public License
33c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  along with this program; if not, write to the Free Software
34c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
35c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
36c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
37c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <linux/errno.h>
38c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <linux/init.h>
39c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <linux/kernel.h>
40c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <linux/module.h>
41c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <linux/slab.h>
42c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <linux/uaccess.h>
43c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <linux/usb.h>
44c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
45c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <media/lirc.h>
46c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#include <media/lirc_dev.h>
47c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
48c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
49c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define MOD_AUTHOR	"Oliver Stabel <oliver.stabel@gmx.de>, " \
50c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			"Tim Davies <tim@opensystems.net.au>"
51c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define MOD_DESC	"USB Driver for Sasem Remote Controller V1.1"
52c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define MOD_NAME	"lirc_sasem"
53c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define MOD_VERSION	"0.5"
54c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
55c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define VFD_MINOR_BASE	144	/* Same as LCD */
56c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define DEVICE_NAME	"lcd%d"
57c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
58c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define BUF_CHUNK_SIZE	8
59c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define BUF_SIZE	128
60c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
61c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define IOCTL_LCD_CONTRAST 1
62c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
63c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/*** P R O T O T Y P E S ***/
64c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
65c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* USB Callback prototypes */
66c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int sasem_probe(struct usb_interface *interface,
67c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			const struct usb_device_id *id);
68c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void sasem_disconnect(struct usb_interface *interface);
69c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void usb_rx_callback(struct urb *urb);
70c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void usb_tx_callback(struct urb *urb);
71c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
72c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* VFD file_operations function prototypes */
73c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int vfd_open(struct inode *inode, struct file *file);
74c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg);
75c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int vfd_close(struct inode *inode, struct file *file);
76c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic ssize_t vfd_write(struct file *file, const char *buf,
77c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				size_t n_bytes, loff_t *pos);
78c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
79c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* LIRC driver function prototypes */
80c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int ir_open(void *data);
81c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void ir_close(void *data);
82c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
83c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* Driver init/exit prototypes */
84c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int __init sasem_init(void);
85c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void __exit sasem_exit(void);
86c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
87c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/*** G L O B A L S ***/
88c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson#define SASEM_DATA_BUF_SZ	32
89c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
90c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstruct sasem_context {
91c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
92c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_device *dev;
933be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller	int vfd_isopen;			/* VFD port has been opened */
943be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller	unsigned int vfd_contrast;	/* VFD contrast */
953be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller	int ir_isopen;			/* IR port has been opened */
963be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller	int dev_present;		/* USB device presence */
973be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller	struct mutex ctx_lock;		/* to lock this object */
98c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	wait_queue_head_t remove_ok;	/* For unexpected USB disconnects */
99c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
100c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct lirc_driver *driver;
101c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_endpoint_descriptor *rx_endpoint;
102c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_endpoint_descriptor *tx_endpoint;
103c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct urb *rx_urb;
104c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct urb *tx_urb;
105c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	unsigned char usb_rx_buf[8];
106c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	unsigned char usb_tx_buf[8];
107c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
108c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct tx_t {
1093be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller		unsigned char data_buf[SASEM_DATA_BUF_SZ]; /* user data
1103be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller							    * buffer */
111c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		struct completion finished;  /* wait for write to finish  */
1123be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller		atomic_t busy;		     /* write in progress */
1133be11133bf733d67c3031a08772f4ab0c1a0fc01Andrew Miller		int status;		     /* status of tx completion */
114c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	} tx;
115c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
116c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* for dealing with repeat codes (wish there was a toggle bit!) */
117c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct timeval presstime;
118c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	char lastcode[8];
119c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int codesaved;
120c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson};
121c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
122c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* VFD file operations */
1230f9313ad068af4156109661fb8e94ee7fcb79001Mauro Carvalho Chehabstatic const struct file_operations vfd_fops = {
124c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.owner		= THIS_MODULE,
125c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.open		= &vfd_open,
126c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.write		= &vfd_write,
127c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.unlocked_ioctl	= &vfd_ioctl,
128c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.release	= &vfd_close,
1296038f373a3dc1f1c26496e60b6c40b164716f07eArnd Bergmann	.llseek		= noop_llseek,
130c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson};
131c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
132c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* USB Device ID for Sasem USB Control Board */
133c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic struct usb_device_id sasem_usb_id_table[] = {
134c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Sasem USB Control Board */
135c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	{ USB_DEVICE(0x11ba, 0x0101) },
136c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Terminating entry */
137c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	{}
138c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson};
139c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
140c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* USB Device data */
141c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic struct usb_driver sasem_driver = {
142c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.name		= MOD_NAME,
143c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.probe		= sasem_probe,
144c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.disconnect	= sasem_disconnect,
145c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.id_table	= sasem_usb_id_table,
146c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson};
147c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
148c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic struct usb_class_driver sasem_class = {
149c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.name		= DEVICE_NAME,
150c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.fops		= &vfd_fops,
151c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	.minor_base	= VFD_MINOR_BASE,
152c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson};
153c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
154c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/* to prevent races between open() and disconnect() */
155c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic DEFINE_MUTEX(disconnect_lock);
156c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
157c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int debug;
158c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
159c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
160c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/*** M O D U L E   C O D E ***/
161c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
162c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod WilsonMODULE_AUTHOR(MOD_AUTHOR);
163c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod WilsonMODULE_DESCRIPTION(MOD_DESC);
164c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod WilsonMODULE_LICENSE("GPL");
165c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonmodule_param(debug, int, S_IRUGO | S_IWUSR);
166c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod WilsonMODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes (default: no)");
167c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
168c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void delete_context(struct sasem_context *context)
169c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
170c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_free_urb(context->tx_urb);  /* VFD */
171c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_free_urb(context->rx_urb);  /* IR */
172c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	lirc_buffer_free(context->driver->rbuf);
173c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	kfree(context->driver->rbuf);
174c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	kfree(context->driver);
175c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	kfree(context);
176c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
177c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (debug)
178c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "%s: context deleted\n", __func__);
179c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
180c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
181c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void deregister_from_lirc(struct sasem_context *context)
182c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
183c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int retval;
184c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int minor = context->driver->minor;
185c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
186c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	retval = lirc_unregister_driver(minor);
187c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (retval)
188c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: unable to deregister from lirc (%d)",
189c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			__func__, retval);
190c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	else
191c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "Deregistered Sasem driver (minor:%d)\n",
192c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		       minor);
193c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
194c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
195c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
196c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
197c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Called when the VFD device (e.g. /dev/usb/lcd)
198c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * is opened by the application.
199c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
200c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int vfd_open(struct inode *inode, struct file *file)
201c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
202c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_interface *interface;
203c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context = NULL;
204c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int subminor;
205c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int retval = 0;
206c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
207c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* prevent races with disconnect */
208c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&disconnect_lock);
209c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
210c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	subminor = iminor(inode);
211c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	interface = usb_find_interface(&sasem_driver, subminor);
212c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!interface) {
213c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: could not find interface for minor %d",
214c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		    __func__, subminor);
215c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -ENODEV;
216c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto exit;
217c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
218c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = usb_get_intfdata(interface);
219c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
220c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context) {
221c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: no context found for minor %d",
222c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson					__func__, subminor);
223c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -ENODEV;
224c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto exit;
225c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
226c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
227c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
228c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
229c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (context->vfd_isopen) {
230c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: VFD port is already open", __func__);
231c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -EBUSY;
232c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	} else {
233c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->vfd_isopen = 1;
234c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		file->private_data = context;
235c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "VFD port opened\n");
236c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
237c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
238c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
239c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
240c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonexit:
241c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&disconnect_lock);
242c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return retval;
243c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
244c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
245c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
246c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Called when the VFD device (e.g. /dev/usb/lcd)
247c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * is closed by the application.
248c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
249c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg)
250c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
251c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context = NULL;
252c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
253c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = (struct sasem_context *) file->private_data;
254c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
255c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context) {
256c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: no context for device", __func__);
257c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return -ENODEV;
258c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
259c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
260c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
261c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
262c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	switch (cmd) {
263c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	case IOCTL_LCD_CONTRAST:
264c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (arg > 1000)
265c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			arg = 1000;
266c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->vfd_contrast = (unsigned int)arg;
267c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		break;
268c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	default:
269c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "Unknown IOCTL command\n");
270c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		mutex_unlock(&context->ctx_lock);
271c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return -ENOIOCTLCMD;  /* not supported */
272c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
273c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
274c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
275c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return 0;
276c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
277c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
278c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
279c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Called when the VFD device (e.g. /dev/usb/lcd)
280c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * is closed by the application.
281c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
282c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int vfd_close(struct inode *inode, struct file *file)
283c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
284c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context = NULL;
285c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int retval = 0;
286c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
287c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = (struct sasem_context *) file->private_data;
288c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
289c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context) {
290c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: no context for device", __func__);
291c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return -ENODEV;
292c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
293c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
294c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
295c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
296c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context->vfd_isopen) {
297c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: VFD is not open", __func__);
298c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -EIO;
299c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	} else {
300c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->vfd_isopen = 0;
301c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "VFD port closed\n");
302c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (!context->dev_present && !context->ir_isopen) {
303c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
304c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			/* Device disconnected before close and IR port is
305c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			 * not open. If IR port is open, context will be
306c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			 * deleted by ir_close. */
307c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			mutex_unlock(&context->ctx_lock);
308c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			delete_context(context);
309c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			return retval;
310c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		}
311c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
312c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
313c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
314c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return retval;
315c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
316c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
317c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
318c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Sends a packet to the VFD.
319c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
320c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int send_packet(struct sasem_context *context)
321c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
322c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	unsigned int pipe;
323c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int interval = 0;
324c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int retval = 0;
325c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
326c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	pipe = usb_sndintpipe(context->dev,
327c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			context->tx_endpoint->bEndpointAddress);
328c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	interval = context->tx_endpoint->bInterval;
329c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
330c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_fill_int_urb(context->tx_urb, context->dev, pipe,
331c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->usb_tx_buf, sizeof(context->usb_tx_buf),
332c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		usb_tx_callback, context, interval);
333c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
334c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->tx_urb->actual_length = 0;
335c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
336c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	init_completion(&context->tx.finished);
337c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	atomic_set(&(context->tx.busy), 1);
338c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
339c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	retval =  usb_submit_urb(context->tx_urb, GFP_KERNEL);
340c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (retval) {
341c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		atomic_set(&(context->tx.busy), 0);
342c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: error submitting urb (%d)", __func__, retval);
343c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	} else {
344c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		/* Wait for transmission to complete (or abort) */
345c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		mutex_unlock(&context->ctx_lock);
346c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		wait_for_completion(&context->tx.finished);
347c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		mutex_lock(&context->ctx_lock);
348c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
349c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = context->tx.status;
350c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (retval)
351c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			err("%s: packet tx failed (%d)", __func__, retval);
352c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
353c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
354c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return retval;
355c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
356c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
357c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
358c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Writes data to the VFD.  The Sasem VFD is 2x16 characters
359c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * and requires data in 9 consecutive USB interrupt packets,
360c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * each packet carrying 8 bytes.
361c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
362c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic ssize_t vfd_write(struct file *file, const char *buf,
363c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				size_t n_bytes, loff_t *pos)
364c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
365c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int i;
366c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int retval = 0;
367c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context;
36855734785cdaff596be9a5238af54fc0f4ace2e63Jarod Wilson	int *data_buf = NULL;
369c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
370c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = (struct sasem_context *) file->private_data;
371c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context) {
372c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: no context for device", __func__);
373c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return -ENODEV;
374c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
375c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
376c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
377c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
378c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context->dev_present) {
379c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: no Sasem device present", __func__);
380c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -ENODEV;
381c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto exit;
382c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
383c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
384c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (n_bytes <= 0 || n_bytes > SASEM_DATA_BUF_SZ) {
385c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: invalid payload size", __func__);
386c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -EINVAL;
387c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto exit;
388c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
389c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
390c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	data_buf = memdup_user(buf, n_bytes);
39179e7c561aeea958479194e24d711e94615ea2823Dan Carpenter	if (IS_ERR(data_buf)) {
392ff7d368ed98b27405197a1d3e76d8032ecbe6194Jiri Slaby		retval = PTR_ERR(data_buf);
393ff7d368ed98b27405197a1d3e76d8032ecbe6194Jiri Slaby		goto exit;
394ff7d368ed98b27405197a1d3e76d8032ecbe6194Jiri Slaby	}
395c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
396c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	memcpy(context->tx.data_buf, data_buf, n_bytes);
397c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
398c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Pad with spaces */
399c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	for (i = n_bytes; i < SASEM_DATA_BUF_SZ; ++i)
400c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->tx.data_buf[i] = ' ';
401c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
402c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Nine 8 byte packets to be sent */
403c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* NOTE: "\x07\x01\0\0\0\0\0\0" or "\x0c\0\0\0\0\0\0\0"
404c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 *       will clear the VFD */
405c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	for (i = 0; i < 9; i++) {
406c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		switch (i) {
407c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 0:
408c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf, "\x07\0\0\0\0\0\0\0", 8);
409c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			context->usb_tx_buf[1] = (context->vfd_contrast) ?
410c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				(0x2B - (context->vfd_contrast - 1) / 250)
411c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				: 0x2B;
412c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
413c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 1:
414c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8);
415c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
416c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 2:
417c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf, "\x0b\x01\0\0\0\0\0\0", 8);
418c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
419c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 3:
420c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf, context->tx.data_buf, 8);
421c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
422c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 4:
423c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf,
424c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			       context->tx.data_buf + 8, 8);
425c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
426c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 5:
427c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8);
428c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
429c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 6:
430c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf, "\x0b\x02\0\0\0\0\0\0", 8);
431c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
432c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 7:
433c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf,
434c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			       context->tx.data_buf + 16, 8);
435c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
436c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		case 8:
437c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(context->usb_tx_buf,
438c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			       context->tx.data_buf + 24, 8);
439c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			break;
440c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		}
441c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = send_packet(context);
442c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (retval) {
443c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
444c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			err("%s: send packet failed for packet #%d",
445c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson					__func__, i);
446c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			goto exit;
447c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		}
448c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
449c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonexit:
450c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
451c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
45288914bdf8c677ebd7e797adac05e47303fd6ac77Jarod Wilson	kfree(data_buf);
453c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
454c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return (!retval) ? n_bytes : retval;
455c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
456c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
457c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
458c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Callback function for USB core API: transmit data
459c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
460c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void usb_tx_callback(struct urb *urb)
461c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
462c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context;
463c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
464c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!urb)
465c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return;
466c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = (struct sasem_context *) urb->context;
467c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context)
468c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return;
469c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
470c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->tx.status = urb->status;
471c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
472c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* notify waiters that write has finished */
473c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	atomic_set(&context->tx.busy, 0);
474c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	complete(&context->tx.finished);
475c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
476c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return;
477c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
478c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
479c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
480c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Called by lirc_dev when the application opens /dev/lirc
481c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
482c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int ir_open(void *data)
483c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
484c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int retval = 0;
485c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context;
486c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
487c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* prevent races with disconnect */
488c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&disconnect_lock);
489c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
490c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = (struct sasem_context *) data;
491c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
492c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
493c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
494c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (context->ir_isopen) {
495c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: IR port is already open", __func__);
496c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -EBUSY;
497c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto exit;
498c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
499c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
500c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_fill_int_urb(context->rx_urb, context->dev,
501c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		usb_rcvintpipe(context->dev,
502c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				context->rx_endpoint->bEndpointAddress),
503c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->usb_rx_buf, sizeof(context->usb_rx_buf),
504c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		usb_rx_callback, context, context->rx_endpoint->bInterval);
505c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
506c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	retval = usb_submit_urb(context->rx_urb, GFP_KERNEL);
507c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
508c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (retval)
509c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: usb_submit_urb failed for ir_open (%d)",
510c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		    __func__, retval);
511c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	else {
512c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->ir_isopen = 1;
513c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "IR port opened\n");
514c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
515c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
516c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonexit:
517c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
518c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
519c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&disconnect_lock);
5204d9db977f9ac9b15f916888978026025c6cf9563Julia Lawall	return retval;
521c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
522c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
523c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
524c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Called by lirc_dev when the application closes /dev/lirc
525c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
526c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void ir_close(void *data)
527c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
528c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context;
529c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
530c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = (struct sasem_context *)data;
531c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context) {
532c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: no context for device", __func__);
533c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return;
534c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
535c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
536c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
537c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
538c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_kill_urb(context->rx_urb);
539c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->ir_isopen = 0;
540c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	printk(KERN_INFO "IR port closed\n");
541c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
542c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context->dev_present) {
543c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
544c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		/*
545c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * Device disconnected while IR port was
546c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * still open. Driver was not deregistered
547c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * at disconnect time, so do it now.
548c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 */
549c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		deregister_from_lirc(context);
550c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
551c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (!context->vfd_isopen) {
552c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
553c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			mutex_unlock(&context->ctx_lock);
554c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			delete_context(context);
555c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			return;
556c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		}
557c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		/* If VFD port is open, context will be deleted by vfd_close */
558c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
559c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
560c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
561c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return;
562c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
563c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
564c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
565c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Process the incoming packet
566c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
567c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void incoming_packet(struct sasem_context *context,
568c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				   struct urb *urb)
569c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
570c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int len = urb->actual_length;
571c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	unsigned char *buf = urb->transfer_buffer;
572c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	long ms;
573c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct timeval tv;
574990528ebe7b7f07f67e29e66700297b10557a706Jarod Wilson	int i;
575c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
576c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (len != 8) {
577c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_WARNING "%s: invalid incoming packet size (%d)\n",
578c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		     __func__, len);
579c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return;
580c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
581c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
582990528ebe7b7f07f67e29e66700297b10557a706Jarod Wilson	if (debug) {
583990528ebe7b7f07f67e29e66700297b10557a706Jarod Wilson		printk(KERN_INFO "Incoming data: ");
584990528ebe7b7f07f67e29e66700297b10557a706Jarod Wilson		for (i = 0; i < 8; ++i)
585990528ebe7b7f07f67e29e66700297b10557a706Jarod Wilson			printk(KERN_CONT "%02x ", buf[i]);
586990528ebe7b7f07f67e29e66700297b10557a706Jarod Wilson		printk(KERN_CONT "\n");
587990528ebe7b7f07f67e29e66700297b10557a706Jarod Wilson	}
588c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
589c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/*
590c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 * Lirc could deal with the repeat code, but we really need to block it
591c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 * if it arrives too late.  Otherwise we could repeat the wrong code.
592c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 */
593c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
594c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* get the time since the last button press */
595c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	do_gettimeofday(&tv);
596c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	ms = (tv.tv_sec - context->presstime.tv_sec) * 1000 +
597c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	     (tv.tv_usec - context->presstime.tv_usec) / 1000;
598c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
599c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (memcmp(buf, "\x08\0\0\0\0\0\0\0", 8) == 0) {
600c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		/*
601c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * the repeat code is being sent, so we copy
602c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * the old code to LIRC
603c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 */
604c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
605c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		/*
606c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * NOTE: Only if the last code was less than 250ms ago
607c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * - no one should be able to push another (undetected) button
608c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 *   in that time and then get a false repeat of the previous
609c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 *   press but it is long enough for a genuine repeat
610c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 */
611c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if ((ms < 250) && (context->codesaved != 0)) {
612c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			memcpy(buf, &context->lastcode, 8);
613c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			context->presstime.tv_sec = tv.tv_sec;
614c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			context->presstime.tv_usec = tv.tv_usec;
615c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		}
616c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	} else {
617c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		/* save the current valid code for repeats */
618c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		memcpy(&context->lastcode, buf, 8);
619c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		/*
620c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * set flag to signal a valid code was save;
621c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 * just for safety reasons
622c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		 */
623c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->codesaved = 1;
624c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->presstime.tv_sec = tv.tv_sec;
625c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->presstime.tv_usec = tv.tv_usec;
626c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
627c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
628c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	lirc_buffer_write(context->driver->rbuf, buf);
629c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	wake_up(&context->driver->rbuf->wait_poll);
630c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
631c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
632c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
633c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Callback function for USB core API: receive data
634c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
635c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void usb_rx_callback(struct urb *urb)
636c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
637c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context;
638c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
639c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!urb)
640c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return;
641c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = (struct sasem_context *) urb->context;
642c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context)
643c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return;
644c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
645c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	switch (urb->status) {
646c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
647c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	case -ENOENT:		/* usbcore unlink successful! */
648c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		return;
649c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
650c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	case 0:
651c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (context->ir_isopen)
652c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			incoming_packet(context, urb);
653c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		break;
654c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
655c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	default:
656c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_WARNING "%s: status (%d): ignored",
657c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			 __func__, urb->status);
658c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		break;
659c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
660c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
661c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_submit_urb(context->rx_urb, GFP_ATOMIC);
662c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return;
663c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
664c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
665c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
666c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
667c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
668c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Callback function for USB core API: Probe
669c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
670c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic int sasem_probe(struct usb_interface *interface,
671c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			const struct usb_device_id *id)
672c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
673c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_device *dev = NULL;
674c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_host_interface *iface_desc = NULL;
675c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_endpoint_descriptor *rx_endpoint = NULL;
676c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct usb_endpoint_descriptor *tx_endpoint = NULL;
677c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct urb *rx_urb = NULL;
678c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct urb *tx_urb = NULL;
679c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct lirc_driver *driver = NULL;
680c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct lirc_buffer *rbuf = NULL;
681c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int lirc_minor = 0;
682c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int num_endpoints;
683c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int retval = 0;
684c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int vfd_ep_found;
685c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int ir_ep_found;
686c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int alloc_status;
687c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context = NULL;
688c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	int i;
689c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
690c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	printk(KERN_INFO "%s: found Sasem device\n", __func__);
691c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
692c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
693c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	dev = usb_get_dev(interface_to_usbdev(interface));
694c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	iface_desc = interface->cur_altsetting;
695c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	num_endpoints = iface_desc->desc.bNumEndpoints;
696c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
697c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/*
698c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 * Scan the endpoint list and set:
699c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 *	first input endpoint = IR endpoint
700c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 *	first output endpoint = VFD endpoint
701c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	 */
702c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
703c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	ir_ep_found = 0;
704c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	vfd_ep_found = 0;
705c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
706c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	for (i = 0; i < num_endpoints && !(ir_ep_found && vfd_ep_found); ++i) {
707c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
708c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		struct usb_endpoint_descriptor *ep;
709c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		int ep_dir;
710c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		int ep_type;
711c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		ep = &iface_desc->endpoint [i].desc;
712c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
713c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
714c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
715c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (!ir_ep_found &&
716c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			ep_dir == USB_DIR_IN &&
717c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			ep_type == USB_ENDPOINT_XFER_INT) {
718c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
719c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			rx_endpoint = ep;
720c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			ir_ep_found = 1;
721c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			if (debug)
722c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				printk(KERN_INFO "%s: found IR endpoint\n",
723c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				       __func__);
724c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
725c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		} else if (!vfd_ep_found &&
726c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			ep_dir == USB_DIR_OUT &&
727c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			ep_type == USB_ENDPOINT_XFER_INT) {
728c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
729c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			tx_endpoint = ep;
730c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			vfd_ep_found = 1;
731c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			if (debug)
732c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				printk(KERN_INFO "%s: found VFD endpoint\n",
733c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson				       __func__);
734c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		}
735c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
736c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
737c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Input endpoint is mandatory */
738c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!ir_ep_found) {
739c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
740c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: no valid input (IR) endpoint found.", __func__);
741c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		retval = -ENODEV;
742c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto exit;
743c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
744c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
745c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!vfd_ep_found)
746c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "%s: no valid output (VFD) endpoint found.\n",
747c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		       __func__);
748c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
749c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
750c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Allocate memory */
751c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	alloc_status = 0;
752c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
753c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = kzalloc(sizeof(struct sasem_context), GFP_KERNEL);
754c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context) {
755c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: kzalloc failed for context", __func__);
756c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		alloc_status = 1;
757c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto alloc_status_switch;
758c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
759c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
760c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!driver) {
761c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: kzalloc failed for lirc_driver", __func__);
762c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		alloc_status = 2;
763c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto alloc_status_switch;
764c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
765c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
766c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!rbuf) {
767c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: kmalloc failed for lirc_buffer", __func__);
768c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		alloc_status = 3;
769c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto alloc_status_switch;
770c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
771c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) {
772c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: lirc_buffer_init failed", __func__);
773c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		alloc_status = 4;
774c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto alloc_status_switch;
775c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
776c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	rx_urb = usb_alloc_urb(0, GFP_KERNEL);
777c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!rx_urb) {
778c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: usb_alloc_urb failed for IR urb", __func__);
779c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		alloc_status = 5;
780c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		goto alloc_status_switch;
781c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
782c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (vfd_ep_found) {
783c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		tx_urb = usb_alloc_urb(0, GFP_KERNEL);
784c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (!tx_urb) {
785c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			err("%s: usb_alloc_urb failed for VFD urb",
786c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			    __func__);
787c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			alloc_status = 6;
788c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			goto alloc_status_switch;
789c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		}
790c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
791c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
792c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_init(&context->ctx_lock);
793c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
794c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	strcpy(driver->name, MOD_NAME);
795c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->minor = -1;
796c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->code_length = 64;
797c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->sample_rate = 0;
798c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->features = LIRC_CAN_REC_LIRCCODE;
799c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->data = context;
800c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->rbuf = rbuf;
801c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->set_use_inc = ir_open;
802c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->set_use_dec = ir_close;
803c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->dev   = &interface->dev;
804c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->owner = THIS_MODULE;
805c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
806c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
807c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
808c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	lirc_minor = lirc_register_driver(driver);
809c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (lirc_minor < 0) {
810c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		err("%s: lirc_register_driver failed", __func__);
811c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		alloc_status = 7;
812ff7d368ed98b27405197a1d3e76d8032ecbe6194Jiri Slaby		retval = lirc_minor;
813ff7d368ed98b27405197a1d3e76d8032ecbe6194Jiri Slaby		goto unlock;
814c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	} else
815c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		printk(KERN_INFO "%s: Registered Sasem driver (minor:%d)\n",
816c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			__func__, lirc_minor);
817c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
818c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Needed while unregistering! */
819c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	driver->minor = lirc_minor;
820c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
821c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->dev = dev;
822c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->dev_present = 1;
823c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->rx_endpoint = rx_endpoint;
824c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->rx_urb = rx_urb;
825c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (vfd_ep_found) {
826c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->tx_endpoint = tx_endpoint;
827c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->tx_urb = tx_urb;
828c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		context->vfd_contrast = 1000;   /* range 0 - 1000 */
829c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
830c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->driver = driver;
831c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
832c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_set_intfdata(interface, context);
833c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
834c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (vfd_ep_found) {
835c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
836c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (debug)
837c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			printk(KERN_INFO "Registering VFD with sysfs\n");
838c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		if (usb_register_dev(interface, &sasem_class))
839c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			/* Not a fatal error, so ignore */
840c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			printk(KERN_INFO "%s: could not get a minor number "
841c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			       "for VFD\n", __func__);
842c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
843c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
844c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	printk(KERN_INFO "%s: Sasem device on usb<%d:%d> initialized\n",
845c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson			__func__, dev->bus->busnum, dev->devnum);
846ff7d368ed98b27405197a1d3e76d8032ecbe6194Jiri Slabyunlock:
847c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
84806b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov
84906b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilovalloc_status_switch:
85006b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	switch (alloc_status) {
85106b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov
85206b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	case 7:
85306b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		if (vfd_ep_found)
85406b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov			usb_free_urb(tx_urb);
85506b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	case 6:
85606b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		usb_free_urb(rx_urb);
85706b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	case 5:
85806b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		lirc_buffer_free(rbuf);
85906b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	case 4:
86006b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		kfree(rbuf);
86106b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	case 3:
86206b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		kfree(driver);
86306b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	case 2:
86406b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		kfree(context);
86506b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		context = NULL;
86606b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	case 1:
86706b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov		if (retval == 0)
86806b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov			retval = -ENOMEM;
86906b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov	}
87006b3f44a9784c48c64dfedf5f012deb93049a3aaAlexey Khoroshilov
871c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonexit:
872c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	return retval;
873c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
874c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
875c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson/**
876c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson * Callback function for USB core API: disonnect
877c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson */
878c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilsonstatic void sasem_disconnect(struct usb_interface *interface)
879c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson{
880c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	struct sasem_context *context;
881c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
882c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* prevent races with ir_open()/vfd_open() */
883c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&disconnect_lock);
884c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
885c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context = usb_get_intfdata(interface);
886c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_lock(&context->ctx_lock);
887c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
888c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	printk(KERN_INFO "%s: Sasem device disconnected\n", __func__);
889c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
890c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_set_intfdata(interface, NULL);
891c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	context->dev_present = 0;
892c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
893c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Stop reception */
894c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_kill_urb(context->rx_urb);
895c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
896c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* Abort ongoing write */
897c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (atomic_read(&context->tx.busy)) {
898c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
899c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		usb_kill_urb(context->tx_urb);
900c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		wait_for_completion(&context->tx.finished);
901c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	}
902c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
903c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	/* De-register from lirc_dev if IR port is not open */
904c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context->ir_isopen)
905c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		deregister_from_lirc(context);
906c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
907c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	usb_deregister_dev(interface, &sasem_class);
908c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
909c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&context->ctx_lock);
910c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
911c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	if (!context->ir_isopen && !context->vfd_isopen)
912c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson		delete_context(context);
913c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
914c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson	mutex_unlock(&disconnect_lock);
915c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson}
916c5ac4571171cb4db94581e9e7a03b9dc08a7df19Jarod Wilson
917bac2c126e452eb00f91305ba2c04a8b2bd95acf0Greg Kroah-Hartmanmodule_usb_driver(sasem_driver);
918