/* $Id: lirc_sasem.c,v 1.18 2007/02/13 07:28:39 lirc Exp $ */ /* lirc_sasem.c - USB remote support for LIRC * Version 0.5 * * Copyright (C) 2004-2005 Oliver Stabel * Tim Davies * * This driver was derived from: * Venky Raju * "lirc_imon - "LIRC plugin/VFD driver for Ahanix/Soundgraph IMON IR/VFD" * Paul Miller 's 2003-2004 * "lirc_atiusb - USB remote support for LIRC" * Culver Consulting Services 's 2003 * "Sasem OnAir VFD/IR USB driver" * * * 2004/06/13 - 0.1 * initial version * * 2004/06/28 - 0.2 * added file system support to write data to VFD device (used * in conjunction with LCDProc) * * 2004/11/22 - 0.3 * Ported to 2.6 kernel - Tim Davies * * 2005/03/29 - 0.4 * A few tidyups and keypress timings - Tim Davies * * 2005/06/23 - 0.5 * A complete rewrite (shamelessly) based on lirc_imon.c * Tim Davies * * NOTE - The LCDproc iMon driver should work with this module. More info at * http://www.frogstorm.info/sasem */ /* * 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 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,22) #error "*** Sorry, this driver requires kernel version 2.4.22 or higher" #endif #include #include #include #include #include #include #include #include #include "drivers/kcompat.h" #include "drivers/lirc.h" #include "drivers/lirc_dev/lirc_dev.h" #define MOD_AUTHOR "Oliver Stabel , Tim Davies " #define MOD_DESC "USB Driver for Sasem Remote Controller V1.1" #define MOD_NAME "lirc_sasem" #define MOD_VERSION "0.5" #define VFD_MINOR_BASE 144 /* Same as LCD */ #define DEVFS_MODE S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH #define DEVFS_NAME LIRC_DEVFS_PREFIX "lcd%d" #define BUF_CHUNK_SIZE 8 #define BUF_SIZE 128 #define SUCCESS 0 #define TRUE 1 #define FALSE 0 #define IOCTL_LCD_CONTRAST 1 /* ------------------------------------------------------------ * P R O T O T Y P E S * ------------------------------------------------------------ */ /* USB Callback prototypes */ #ifdef KERNEL_2_5 static int sasem_probe (struct usb_interface *interface, const struct usb_device_id *id); static void sasem_disconnect (struct usb_interface *interface); static void usb_rx_callback (struct urb *urb, struct pt_regs *regs); static void usb_tx_callback (struct urb *urb, struct pt_regs *regs); #else static void * sasem_probe (struct usb_device * dev, unsigned int intf, const struct usb_device_id *id); static void sasem_disconnect (struct usb_device *dev, void *data); static void usb_rx_callback (struct urb *urb); static void usb_tx_callback (struct urb *urb); #endif /* VFD file_operations function prototypes */ static int vfd_open (struct inode *inode, struct file *file); static int vfd_ioctl (struct inode *inode, struct file *file, unsigned cmd, unsigned long arg); static int vfd_close (struct inode *inode, struct file *file); static ssize_t vfd_write (struct file *file, const char *buf, size_t n_bytes, loff_t *pos); /* LIRC plugin function prototypes */ static int ir_open (void *data); static void ir_close (void *data); /* Driver init/exit prototypes */ static int __init sasem_init (void); static void __exit sasem_exit (void); /* ------------------------------------------------------------ * G L O B A L S * ------------------------------------------------------------ */ struct sasem_context { struct usb_device *dev; int vfd_isopen; /* VFD port has been opened */ unsigned int vfd_contrast; /* VFD contrast */ #if !defined (KERNEL_2_5) int subminor; /* index into minor_table */ devfs_handle_t devfs; #endif int ir_isopen; /* IR port has been opened */ int dev_present; /* USB device presence */ struct semaphore sem; /* to lock this object */ wait_queue_head_t remove_ok; /* For unexpected USB disconnects */ struct lirc_plugin *plugin; struct usb_endpoint_descriptor *rx_endpoint; struct usb_endpoint_descriptor *tx_endpoint; struct urb *rx_urb; struct urb *tx_urb; unsigned char usb_rx_buf [8]; unsigned char usb_tx_buf [8]; struct tx_t { unsigned char data_buf [32]; /* user data buffer */ struct completion finished; /* wait for write to finish */ atomic_t busy; /* write in progress */ int status; /* status of tx completion */ } tx; /* for dealing with repeat codes (wish there was a toggle bit!) */ struct timeval presstime; char lastcode[8]; int codesaved; }; #define LOCK_CONTEXT down (&context ->sem) #define UNLOCK_CONTEXT up (&context ->sem) /* VFD file operations */ static struct file_operations vfd_fops = { .owner = THIS_MODULE, .open = &vfd_open, .write = &vfd_write, .ioctl = &vfd_ioctl, .release = &vfd_close }; /* USB Device ID for Sasem USB Control Board */ static struct usb_device_id sasem_usb_id_table [] = { { USB_DEVICE(0x11ba, 0x0101) }, /* Sasem */ {} }; /* USB Device data */ static struct usb_driver sasem_driver = { LIRC_THIS_MODULE(.owner = THIS_MODULE) .name = MOD_NAME, .probe = sasem_probe, .disconnect = sasem_disconnect, .id_table = sasem_usb_id_table, #if !defined(KERNEL_2_5) .fops = &vfd_fops, .minor = VFD_MINOR_BASE, #endif }; #ifdef KERNEL_2_5 static struct usb_class_driver sasem_class = { .name = DEVFS_NAME, .fops = &vfd_fops, #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 15) .mode = DEVFS_MODE, #endif .minor_base = VFD_MINOR_BASE, }; #endif /* to prevent races between open() and disconnect() */ static DECLARE_MUTEX (disconnect_sem); static int debug = 0; #if !defined(KERNEL_2_5) #define MAX_DEVICES 4 /* In case there's more than one Sasem device */ static struct sasem_context * minor_table [MAX_DEVICES]; /* the global usb devfs handle */ extern devfs_handle_t usb_devfs_handle; #endif /* ------------------------------------------------------------ * M O D U L E C O D E * ------------------------------------------------------------ */ MODULE_AUTHOR (MOD_AUTHOR); MODULE_DESCRIPTION (MOD_DESC); MODULE_LICENSE ("GPL"); module_param (debug, int, 0); MODULE_PARM_DESC (debug, "Debug messages: 0=no, 1=yes (default: no)"); static inline void delete_context (struct sasem_context *context) { usb_free_urb (context ->tx_urb); /* VFD */ usb_free_urb (context ->rx_urb); /* IR */ lirc_buffer_free (context ->plugin ->rbuf); kfree (context ->plugin ->rbuf); kfree (context ->plugin); kfree (context); if (debug) info ("%s: context deleted", __FUNCTION__); } static inline void deregister_from_lirc (struct sasem_context *context) { int retval; int minor = context ->plugin ->minor; if ((retval = lirc_unregister_plugin (minor))) { err ("%s: unable to deregister from lirc (%d)", __FUNCTION__, retval); } else info ("Deregistered Sasem plugin (minor:%d)", minor); } /** * Called when the VFD device (e.g. /dev/usb/lcd) * is opened by the application. */ static int vfd_open (struct inode *inode, struct file *file) { #ifdef KERNEL_2_5 struct usb_interface *interface; #endif struct sasem_context *context = NULL; int subminor; int retval = SUCCESS; /* prevent races with disconnect */ down (&disconnect_sem); #ifdef KERNEL_2_5 subminor = iminor (inode); interface = usb_find_interface (&sasem_driver, subminor); if (!interface) { err ("%s: could not find interface for minor %d", __FUNCTION__, subminor); retval = -ENODEV; goto exit; } context = usb_get_intfdata (interface); #else subminor = MINOR (inode ->i_rdev) - VFD_MINOR_BASE; if (subminor < 0 || subminor >= MAX_DEVICES) { err ("%s: no record of minor %d", __FUNCTION__, subminor); retval = -ENODEV; goto exit; } context = minor_table [subminor]; #endif if (!context) { err ("%s: no context found for minor %d", __FUNCTION__, subminor); retval = -ENODEV; goto exit; } LOCK_CONTEXT; if (context ->vfd_isopen) { err ("%s: VFD port is already open", __FUNCTION__); retval = -EBUSY; } else { MOD_INC_USE_COUNT; context ->vfd_isopen = TRUE; file ->private_data = context; info ("VFD port opened"); } UNLOCK_CONTEXT; exit: up (&disconnect_sem); return retval; } /** * Called when the VFD device (e.g. /dev/usb/lcd) * is closed by the application. */ static int vfd_ioctl (struct inode *inode, struct file *file, unsigned cmd, unsigned long arg) { struct sasem_context *context = NULL; context = (struct sasem_context *) file ->private_data; if (!context) { err ("%s: no context for device", __FUNCTION__); return -ENODEV; } LOCK_CONTEXT; switch (cmd) { case IOCTL_LCD_CONTRAST: if (arg > 1000) arg = 1000; if (arg < 0) arg = 0; context ->vfd_contrast = (unsigned int)arg; break; default: info ("Unknown IOCTL command"); UNLOCK_CONTEXT; return -ENOIOCTLCMD; /* not supported */ } UNLOCK_CONTEXT; return 0; } /** * Called when the VFD device (e.g. /dev/usb/lcd) * is closed by the application. */ static int vfd_close (struct inode *inode, struct file *file) { struct sasem_context *context = NULL; int retval = SUCCESS; context = (struct sasem_context *) file ->private_data; if (!context) { err ("%s: no context for device", __FUNCTION__); return -ENODEV; } LOCK_CONTEXT; if (!context ->vfd_isopen) { err ("%s: VFD is not open", __FUNCTION__); retval = -EIO; } else { context ->vfd_isopen = FALSE; MOD_DEC_USE_COUNT; info ("VFD port closed"); if (!context ->dev_present && !context ->ir_isopen) { /* Device disconnected before close and IR port is not open. */ /* If IR port is open, context will be deleted by ir_close. */ UNLOCK_CONTEXT; delete_context (context); return retval; } } UNLOCK_CONTEXT; return retval; } /** * Sends a packet to the VFD. */ static inline int send_packet (struct sasem_context *context) { unsigned int pipe; int interval = 0; int retval = SUCCESS; pipe = usb_sndintpipe (context ->dev, context-> tx_endpoint ->bEndpointAddress); #ifdef KERNEL_2_5 interval = context ->tx_endpoint ->bInterval; #endif /* Use 0 for 2.4 kernels */ usb_fill_int_urb (context ->tx_urb, context ->dev, pipe, context ->usb_tx_buf, sizeof (context ->usb_tx_buf), usb_tx_callback, context, interval); context ->tx_urb ->actual_length = 0; init_completion (&context ->tx.finished); atomic_set (&(context ->tx.busy), 1); #ifdef KERNEL_2_5 retval = usb_submit_urb (context ->tx_urb, GFP_KERNEL); #else retval = usb_submit_urb (context ->tx_urb); #endif if (retval != SUCCESS) { atomic_set (&(context ->tx.busy), 0); err ("%s: error submitting urb (%d)", __FUNCTION__, retval); } else { /* Wait for tranmission to complete (or abort) */ UNLOCK_CONTEXT; wait_for_completion (&context ->tx.finished); LOCK_CONTEXT; retval = context ->tx.status; if (retval != SUCCESS) err ("%s: packet tx failed (%d)", __FUNCTION__, retval); } return retval; } /** * Writes data to the VFD. The Sasem VFD is 2x16 characters * and requires data in 9 consecutive USB interrupt packets, * each packet carrying 8 bytes. */ static ssize_t vfd_write (struct file *file, const char *buf, size_t n_bytes, loff_t *pos) { int i; int retval = SUCCESS; struct sasem_context *context; context = (struct sasem_context *) file ->private_data; if (!context) { err ("%s: no context for device", __FUNCTION__); return -ENODEV; } LOCK_CONTEXT; if (!context ->dev_present) { err ("%s: no Sasem device present", __FUNCTION__); retval = -ENODEV; goto exit; } if (n_bytes <= 0 || n_bytes > 32) { err ("%s: invalid payload size", __FUNCTION__); retval = -EINVAL; goto exit; } copy_from_user (context ->tx.data_buf, buf, n_bytes); /* Pad with spaces */ for (i=n_bytes; i < 32; ++i) context ->tx.data_buf [i] = ' '; /* Nine 8 byte packets to be sent */ /* NOTE: "\x07\x01\0\0\0\0\0\0" or "\x0c\0\0\0\0\0\0\0" will clear the VFD */ for (i = 0; i < 9; i++) { switch (i) { case 0: memcpy (context ->usb_tx_buf, "\x07\0\0\0\0\0\0\0", 8); context ->usb_tx_buf[1] = (context ->vfd_contrast)?(0x2B - (context ->vfd_contrast-1) / 250):0x2B; break; case 1: memcpy (context ->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8); break; case 2: memcpy (context ->usb_tx_buf, "\x0b\x01\0\0\0\0\0\0", 8); break; case 3: memcpy (context ->usb_tx_buf, context ->tx.data_buf, 8); break; case 4: memcpy (context ->usb_tx_buf, context ->tx.data_buf + 8, 8); break; case 5: memcpy (context ->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8); break; case 6: memcpy (context ->usb_tx_buf, "\x0b\x02\0\0\0\0\0\0", 8); break; case 7: memcpy (context ->usb_tx_buf, context ->tx.data_buf + 16, 8); break; case 8: memcpy (context ->usb_tx_buf, context ->tx.data_buf + 24, 8); break; } if ((retval = send_packet (context)) != SUCCESS) { err ("%s: send packet failed for packet #%d", __FUNCTION__, i); goto exit; } } exit: UNLOCK_CONTEXT; return (retval == SUCCESS) ? n_bytes : retval; } /** * Callback function for USB core API: transmit data */ #ifdef KERNEL_2_5 static void usb_tx_callback (struct urb *urb, struct pt_regs *regs) #else static void usb_tx_callback (struct urb *urb) #endif { struct sasem_context *context; if (!urb || !(context = (struct sasem_context *) urb->context)) return; context ->tx.status = urb ->status; /* notify waiters that write has finished */ atomic_set (&context ->tx.busy, 0); complete (&context ->tx.finished); return; } /** * Called by lirc_dev when the application opens /dev/lirc */ static int ir_open (void *data) { int retval = SUCCESS; struct sasem_context *context; /* prevent races with disconnect */ down (&disconnect_sem); context = (struct sasem_context *) data; LOCK_CONTEXT; if (context ->ir_isopen) { err ("%s: IR port is already open", __FUNCTION__); retval = -EBUSY; goto exit; } usb_fill_int_urb (context ->rx_urb, context ->dev, usb_rcvintpipe (context ->dev, context ->rx_endpoint-> bEndpointAddress), context ->usb_rx_buf, sizeof (context ->usb_rx_buf), usb_rx_callback, context, context ->rx_endpoint ->bInterval); #ifdef KERNEL_2_5 retval = usb_submit_urb (context ->rx_urb, GFP_KERNEL); #else retval = usb_submit_urb (context ->rx_urb); #endif if (retval) { err ("%s: usb_submit_urb failed for ir_open (%d)", __FUNCTION__, retval); } else { MOD_INC_USE_COUNT; context ->ir_isopen = TRUE; info ("IR port opened"); } exit: UNLOCK_CONTEXT; up (&disconnect_sem); return SUCCESS; } /** * Called by lirc_dev when the application closes /dev/lirc */ static void ir_close (void *data) { struct sasem_context *context; context = (struct sasem_context *)data; if (!context) { err ("%s: no context for device", __FUNCTION__); return; } LOCK_CONTEXT; usb_kill_urb(context->rx_urb); context ->ir_isopen = FALSE; MOD_DEC_USE_COUNT; info ("IR port closed"); if (!context ->dev_present) { /* * Device disconnected while IR port was * still open. Plugin was not deregistered * at disconnect time, so do it now. */ deregister_from_lirc (context); if (!context ->vfd_isopen) { UNLOCK_CONTEXT; delete_context (context); return; } /* If VFD port is open, context will be deleted by vfd_close */ } UNLOCK_CONTEXT; return; } /** * Process the incoming packet */ static inline void incoming_packet (struct sasem_context *context, struct urb *urb) { int len = urb ->actual_length; unsigned char *buf = urb ->transfer_buffer; long ms; struct timeval tv; if (len != 8) { warn ("%s: invalid incoming packet size (%d)", __FUNCTION__, len); return; } #ifdef DEBUG int i; for (i=0; i < 8; ++i) printk ("%02x ", buf [i]); printk ("\n"); #endif // Lirc could deal with the repeat code, but we really need to block it // if it arrives too late. Otherwise we could repeat the wrong code. // get the time since the last button press do_gettimeofday(&tv); ms = (tv.tv_sec - context->presstime.tv_sec) * 1000 + (tv.tv_usec - context->presstime.tv_usec) / 1000; if (memcmp(buf, "\x08\0\0\0\0\0\0\0", 8) == 0) { // the repeat code is being sent, so we copy // the old code to LIRC // NOTE: Only if the last code was less than 250ms ago // - no one should be able to push another (undetected) button // in that time and then get a false repeat of the previous press // - but it is long enough for a genuine repeat if ((ms < 250) && (context->codesaved != 0)) { memcpy(buf, &context->lastcode, 8); context->presstime.tv_sec = tv.tv_sec; context->presstime.tv_usec = tv.tv_usec; } // there was no old code else { // Do Nothing! } } else { // save the current valid code for repeats memcpy(&context->lastcode, buf, 8); // set flag to signal a valid code was save; // just for safety reasons context->codesaved = 1; context->presstime.tv_sec = tv.tv_sec; context->presstime.tv_usec = tv.tv_usec; } lirc_buffer_write_1 (context ->plugin ->rbuf, buf); wake_up (&context ->plugin ->rbuf ->wait_poll); } /** * Callback function for USB core API: receive data */ #ifdef KERNEL_2_5 static void usb_rx_callback (struct urb *urb, struct pt_regs *regs) #else static void usb_rx_callback (struct urb *urb) #endif { struct sasem_context *context; if (!urb || !(context = (struct sasem_context *) urb->context)) return; switch (urb ->status) { case -ENOENT: /* usbcore unlink successful! */ return; case SUCCESS: if (context ->ir_isopen) incoming_packet (context, urb); break; default : warn ("%s: status (%d): ignored", __FUNCTION__, urb ->status); break; } #ifdef KERNEL_2_5 usb_submit_urb (context ->rx_urb, GFP_ATOMIC); #endif return; } /** * Callback function for USB core API: Probe */ #ifdef KERNEL_2_5 static int sasem_probe (struct usb_interface *interface, const struct usb_device_id *id) #else static void * sasem_probe (struct usb_device * dev, unsigned int intf, const struct usb_device_id *id) #endif { #ifdef KERNEL_2_5 struct usb_device *dev = NULL; struct usb_host_interface *iface_desc = NULL; #else struct usb_interface *interface = NULL; struct usb_interface_descriptor *iface_desc = NULL; char name [10]; int subminor = 0; #endif struct usb_endpoint_descriptor *rx_endpoint = NULL; struct usb_endpoint_descriptor *tx_endpoint = NULL; struct urb *rx_urb = NULL; struct urb *tx_urb = NULL; struct lirc_plugin *plugin = NULL; struct lirc_buffer *rbuf = NULL; int lirc_minor = 0; int num_endpoints; int retval = SUCCESS; int vfd_ep_found; int ir_ep_found; int alloc_status; struct sasem_context *context = NULL; int i; info ("%s: found Sasem device", __FUNCTION__); #if !defined(KERNEL_2_5) for (subminor = 0; subminor < MAX_DEVICES; ++subminor) { if (minor_table [subminor] == NULL) break; } if (subminor == MAX_DEVICES) { err ("%s: allowed number of devices already present", __FUNCTION__); retval = -ENOMEM; goto exit; } #endif #ifdef KERNEL_2_5 dev = usb_get_dev (interface_to_usbdev (interface)); iface_desc = interface ->cur_altsetting; num_endpoints = iface_desc ->desc.bNumEndpoints; #else interface = &dev ->actconfig ->interface [intf]; iface_desc = &interface ->altsetting [interface ->act_altsetting]; num_endpoints = iface_desc ->bNumEndpoints; #endif /* * Scan the endpoint list and set: * first input endpoint = IR endpoint * first output endpoint = VFD endpoint */ ir_ep_found = vfd_ep_found = FALSE; for (i=0; i < num_endpoints && !(ir_ep_found && vfd_ep_found); ++i) { struct usb_endpoint_descriptor *ep; int ep_dir; int ep_type; #ifdef KERNEL_2_5 ep = &iface_desc ->endpoint [i].desc; #else ep = &iface_desc ->endpoint [i]; #endif ep_dir = ep ->bEndpointAddress & USB_ENDPOINT_DIR_MASK; ep_type = ep ->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; if (!ir_ep_found && ep_dir == USB_DIR_IN && ep_type == USB_ENDPOINT_XFER_INT) { rx_endpoint = ep; ir_ep_found = TRUE; if (debug) info ("%s: found IR endpoint", __FUNCTION__); } else if (!vfd_ep_found && ep_dir == USB_DIR_OUT && ep_type == USB_ENDPOINT_XFER_INT) { tx_endpoint = ep; vfd_ep_found = TRUE; if (debug) info ("%s: found VFD endpoint", __FUNCTION__); } } /* Input endpoint is mandatory */ if (!ir_ep_found) { err ("%s: no valid input (IR) endpoint found.", __FUNCTION__); retval = -ENODEV; goto exit; } /* Warning if no VFD endpoint */ if (!vfd_ep_found) info ("%s: no valid output (VFD) endpoint found.", __FUNCTION__); /* Allocate memory */ alloc_status = SUCCESS; if (!(context = kmalloc (sizeof(struct sasem_context), GFP_KERNEL))) { err ("%s: kmalloc failed for context", __FUNCTION__); alloc_status = 1; } else if (!(plugin = kmalloc (sizeof(struct lirc_plugin), GFP_KERNEL))) { err ("%s: kmalloc failed for lirc_plugin", __FUNCTION__); alloc_status = 2; } else if (!(rbuf = kmalloc (sizeof(struct lirc_buffer), GFP_KERNEL))) { err ("%s: kmalloc failed for lirc_buffer", __FUNCTION__); alloc_status = 3; } else if (lirc_buffer_init (rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) { err ("%s: lirc_buffer_init failed", __FUNCTION__); alloc_status = 4; } #ifdef KERNEL_2_5 else if (!(rx_urb = usb_alloc_urb (0, GFP_KERNEL))) { #else else if (!(rx_urb = usb_alloc_urb (0))) { #endif err ("%s: usb_alloc_urb failed for IR urb", __FUNCTION__); alloc_status = 5; } #ifdef KERNEL_2_5 else if (vfd_ep_found && !(tx_urb = usb_alloc_urb (0, GFP_KERNEL))) { #else else if (vfd_ep_found && !(tx_urb = usb_alloc_urb (0))) { #endif err ("%s: usb_alloc_urb failed for VFD urb", __FUNCTION__); alloc_status = 6; } else { /* clear all members of sasem_context and lirc_plugin */ memset (context, 0, sizeof (struct sasem_context)); init_MUTEX (&context ->sem); memset (plugin, 0, sizeof (struct lirc_plugin)); strcpy (plugin ->name, MOD_NAME); plugin ->minor = -1; plugin ->code_length = 64; plugin ->sample_rate = 0; plugin ->features = LIRC_CAN_REC_LIRCCODE; plugin ->data = context; plugin ->rbuf = rbuf; plugin ->set_use_inc = ir_open; plugin ->set_use_dec = ir_close; #ifdef LIRC_HAVE_SYSFS plugin->dev = &dev->dev; #endif plugin->owner = THIS_MODULE; LOCK_CONTEXT; if ((lirc_minor = lirc_register_plugin (plugin)) < 0) { err ("%s: lirc_register_plugin failed", __FUNCTION__); alloc_status = 7; UNLOCK_CONTEXT; } else info ("%s: Registered Sasem plugin (minor:%d)", __FUNCTION__, lirc_minor); } switch (alloc_status) { case 7: if (vfd_ep_found) usb_free_urb (tx_urb); case 6: usb_free_urb (rx_urb); case 5: lirc_buffer_free (rbuf); case 4: kfree (rbuf); case 3: kfree (plugin); case 2: kfree (context); context = NULL; case 1: retval = -ENOMEM; goto exit; } // Needed while unregistering! plugin ->minor = lirc_minor; context ->dev = dev; context ->dev_present = TRUE; context ->rx_endpoint = rx_endpoint; context ->rx_urb = rx_urb; if (vfd_ep_found) { context ->tx_endpoint = tx_endpoint; context ->tx_urb = tx_urb; context ->vfd_contrast = 1000; /* range 0 - 1000 */ } context ->plugin = plugin; #ifdef KERNEL_2_5 usb_set_intfdata (interface, context); #else minor_table [subminor] = context; context ->subminor = subminor; #endif if (vfd_ep_found) { if (debug) info ("Registering VFD with devfs"); #ifdef KERNEL_2_5 if (usb_register_dev (interface, &sasem_class)) { // Not a fatal error, so ignore info ("%s: could not get a minor number for VFD", __FUNCTION__); } #else sprintf (name, DEVFS_NAME, subminor); if (!(context ->devfs = devfs_register (usb_devfs_handle, name, DEVFS_FL_DEFAULT, USB_MAJOR, VFD_MINOR_BASE + subminor, DEVFS_MODE, &vfd_fops, NULL))) { // not a fatal error so ignore info ("%s: devfs register failed for VFD", __FUNCTION__); } #endif } info ("%s: Sasem device on usb<%d:%d> initialized", __FUNCTION__, dev ->bus ->busnum, dev ->devnum); UNLOCK_CONTEXT; exit: #ifdef KERNEL_2_5 return retval; #else return (retval == SUCCESS) ? context : NULL; #endif } /** * Callback function for USB core API: disonnect */ #ifdef KERNEL_2_5 static void sasem_disconnect (struct usb_interface *interface) #else static void sasem_disconnect (struct usb_device *dev, void *data) #endif { struct sasem_context *context; /* prevent races with ir_open()/vfd_open() */ down (&disconnect_sem); #ifdef KERNEL_2_5 context = usb_get_intfdata (interface); #else context = (struct sasem_context *)data; #endif LOCK_CONTEXT; info ("%s: Sasem device disconnected", __FUNCTION__); #ifdef KERNEL_2_5 usb_set_intfdata (interface, NULL); #else minor_table [context ->subminor] = NULL; #endif context ->dev_present = FALSE; /* Stop reception */ usb_kill_urb(context->rx_urb); /* Abort ongoing write */ if (atomic_read (&context ->tx.busy)) { usb_kill_urb(context->tx_urb); wait_for_completion (&context ->tx.finished); } /* De-register from lirc_dev if IR port is not open */ if (!context ->ir_isopen) deregister_from_lirc (context); #ifdef KERNEL_2_5 usb_deregister_dev (interface, &sasem_class); #else if (context ->devfs) devfs_unregister (context ->devfs); #endif UNLOCK_CONTEXT; if (!context ->ir_isopen && !context ->vfd_isopen) delete_context (context); up (&disconnect_sem); } static int __init sasem_init (void) { int rc; info (MOD_DESC ", v" MOD_VERSION); info (MOD_AUTHOR); if ((rc = usb_register (&sasem_driver)) < 0) { err ("%s: usb register failed (%d)", __FUNCTION__, rc); return -ENODEV; } return SUCCESS; } static void __exit sasem_exit (void) { usb_deregister (&sasem_driver); info ("module removed. Goodbye!"); } module_init (sasem_init); module_exit (sasem_exit); #if !defined(KERNEL_2_5) EXPORT_NO_SYMBOLS; #endif