Commit ee820a64 authored by Oliver Endriss's avatar Oliver Endriss Committed by Mauro Carvalho Chehab

V4L/DVB (5334): Dvb-ttpci: Infrared remote control refactoring

Infrared remote control support rewritten.
Now each device provides its own event device, keymap, protocol,
inversion and address setting.
EVIOCGKEYCODE and EVIOCSKEYCODE ioctls are supported to read/modify
a keymap. Keymaps may be loaded using
- input tools (keyb etc.)
- av7110_loadkeys (obsolete, for backward compatibility)
New command line parameters:
- ir_protocol:    select infrared protocol: 0 RC5, 1 RCMM (default)
- ir_inversion:   signal inversion: 0 not inverted (default), 1 inverted
- ir_device_mask: bitmask of infrared devices (default: accept all)
Those parameters may be set anytime.
Signed-off-by: default avatarOliver Endriss <o.endriss@gmx.de>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@infradead.org>
parent db483679
...@@ -219,7 +219,10 @@ static void recover_arm(struct av7110 *av7110) ...@@ -219,7 +219,10 @@ static void recover_arm(struct av7110 *av7110)
av7110->recover(av7110); av7110->recover(av7110);
restart_feeds(av7110); restart_feeds(av7110);
av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1, av7110->ir_config);
#if defined(CONFIG_INPUT_EVDEV) || defined(CONFIG_INPUT_EVDEV_MODULE)
av7110_check_ir_config(av7110, true);
#endif
} }
static void av7110_arm_sync(struct av7110 *av7110) static void av7110_arm_sync(struct av7110 *av7110)
...@@ -250,6 +253,10 @@ static int arm_thread(void *data) ...@@ -250,6 +253,10 @@ static int arm_thread(void *data)
if (!av7110->arm_ready) if (!av7110->arm_ready)
continue; continue;
#if defined(CONFIG_INPUT_EVDEV) || defined(CONFIG_INPUT_EVDEV_MODULE)
av7110_check_ir_config(av7110, false);
#endif
if (mutex_lock_interruptible(&av7110->dcomlock)) if (mutex_lock_interruptible(&av7110->dcomlock))
break; break;
newloops = rdebi(av7110, DEBINOSWAP, STATUS_LOOPS, 0, 2); newloops = rdebi(av7110, DEBINOSWAP, STATUS_LOOPS, 0, 2);
...@@ -667,8 +674,8 @@ static void gpioirq(unsigned long data) ...@@ -667,8 +674,8 @@ static void gpioirq(unsigned long data)
return; return;
case DATA_IRCOMMAND: case DATA_IRCOMMAND:
if (av7110->ir_handler) if (av7110->ir.ir_handler)
av7110->ir_handler(av7110, av7110->ir.ir_handler(av7110,
swahw32(irdebi(av7110, DEBINOSWAP, Reserved, 0, 4))); swahw32(irdebi(av7110, DEBINOSWAP, Reserved, 0, 4)));
iwdebi(av7110, DEBINOSWAP, RX_BUFF, 0, 2); iwdebi(av7110, DEBINOSWAP, RX_BUFF, 0, 2);
break; break;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include <linux/socket.h> #include <linux/socket.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/input.h>
#include <linux/dvb/video.h> #include <linux/dvb/video.h>
#include <linux/dvb/audio.h> #include <linux/dvb/audio.h>
...@@ -66,6 +67,27 @@ struct dvb_video_events { ...@@ -66,6 +67,27 @@ struct dvb_video_events {
}; };
struct av7110;
/* infrared remote control */
struct infrared {
u16 key_map[256];
struct input_dev *input_dev;
char input_phys[32];
struct timer_list keyup_timer;
struct tasklet_struct ir_tasklet;
void (*ir_handler)(struct av7110 *av7110, u32 ircom);
u32 ir_command;
u32 ir_config;
u32 device_mask;
u8 protocol;
u8 inversion;
u16 last_key;
u16 last_toggle;
u8 delay_timer_finished;
};
/* place to store all the necessary device information */ /* place to store all the necessary device information */
struct av7110 { struct av7110 {
...@@ -227,10 +249,7 @@ struct av7110 { ...@@ -227,10 +249,7 @@ struct av7110 {
u16 wssMode; u16 wssMode;
u16 wssData; u16 wssData;
u32 ir_config; struct infrared ir;
u32 ir_command;
void (*ir_handler)(struct av7110 *av7110, u32 ircom);
struct tasklet_struct ir_tasklet;
/* firmware stuff */ /* firmware stuff */
unsigned char *bin_fw; unsigned char *bin_fw;
...@@ -268,6 +287,7 @@ struct av7110 { ...@@ -268,6 +287,7 @@ struct av7110 {
extern int ChangePIDs(struct av7110 *av7110, u16 vpid, u16 apid, u16 ttpid, extern int ChangePIDs(struct av7110 *av7110, u16 vpid, u16 apid, u16 ttpid,
u16 subpid, u16 pcrpid); u16 subpid, u16 pcrpid);
extern int av7110_check_ir_config(struct av7110 *av7110, int force);
extern int av7110_ir_init(struct av7110 *av7110); extern int av7110_ir_init(struct av7110 *av7110);
extern void av7110_ir_exit(struct av7110 *av7110); extern void av7110_ir_exit(struct av7110 *av7110);
......
/*
* Driver for the remote control of SAA7146 based AV7110 cards
*
* Copyright (C) 1999-2003 Holger Waechtler <holger@convergence.de>
* Copyright (C) 2003-2007 Oliver Endriss <o.endriss@gmx.de>
*
* 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.
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
*
*/
#include <linux/types.h> #include <linux/types.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/input.h>
#include <linux/proc_fs.h> #include <linux/proc_fs.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <asm/bitops.h> #include <asm/bitops.h>
...@@ -10,18 +33,37 @@ ...@@ -10,18 +33,37 @@
#include "av7110.h" #include "av7110.h"
#include "av7110_hw.h" #include "av7110_hw.h"
#define AV_CNT 4
#define IR_RC5 0
#define IR_RCMM 1
#define IR_RC5_EXT 2 /* internal only */
#define IR_ALL 0xffffffff
#define UP_TIMEOUT (HZ*7/25) #define UP_TIMEOUT (HZ*7/25)
/* enable ir debugging by or'ing debug with 16 */
static int av_cnt; /* Note: enable ir debugging by or'ing debug with 16 */
static struct av7110 *av_list[4];
static struct input_dev *input_dev; static int ir_protocol[AV_CNT] = { IR_RCMM, IR_RCMM, IR_RCMM, IR_RCMM};
static char input_phys[32]; module_param_array(ir_protocol, int, NULL, 0644);
MODULE_PARM_DESC(ir_protocol, "Infrared protocol: 0 RC5, 1 RCMM (default)");
static int ir_inversion[AV_CNT];
module_param_array(ir_inversion, int, NULL, 0644);
MODULE_PARM_DESC(ir_inversion, "Inversion of infrared signal: 0 not inverted (default), 1 inverted");
static uint ir_device_mask[AV_CNT] = { IR_ALL, IR_ALL, IR_ALL, IR_ALL };
module_param_array(ir_device_mask, uint, NULL, 0644);
MODULE_PARM_DESC(ir_device_mask, "Bitmask of infrared devices: bit 0..31 = device 0..31 (default: all)");
static u8 delay_timer_finished;
static u16 key_map [256] = { static int av_cnt;
static struct av7110 *av_list[AV_CNT];
static u16 default_key_map [256] = {
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7,
KEY_8, KEY_9, KEY_BACK, 0, KEY_POWER, KEY_MUTE, 0, KEY_INFO, KEY_8, KEY_9, KEY_BACK, 0, KEY_POWER, KEY_MUTE, 0, KEY_INFO,
KEY_VOLUMEUP, KEY_VOLUMEDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_VOLUMEUP, KEY_VOLUMEDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
...@@ -45,141 +87,187 @@ static u16 key_map [256] = { ...@@ -45,141 +87,187 @@ static u16 key_map [256] = {
}; };
static void av7110_emit_keyup(unsigned long data) /* key-up timer */
static void av7110_emit_keyup(unsigned long parm)
{ {
if (!data || !test_bit(data, input_dev->key)) struct infrared *ir = (struct infrared *) parm;
if (!ir || !test_bit(ir->last_key, ir->input_dev->key))
return; return;
input_report_key(input_dev, data, 0); input_report_key(ir->input_dev, ir->last_key, 0);
input_sync(input_dev); input_sync(ir->input_dev);
} }
static struct timer_list keyup_timer = { .function = av7110_emit_keyup }; /* tasklet */
static void av7110_emit_key(unsigned long parm) static void av7110_emit_key(unsigned long parm)
{ {
struct av7110 *av7110 = (struct av7110 *) parm; struct infrared *ir = (struct infrared *) parm;
u32 ir_config = av7110->ir_config; u32 ircom = ir->ir_command;
u32 ircom = av7110->ir_command;
u8 data; u8 data;
u8 addr; u8 addr;
static u16 old_toggle = 0; u16 toggle;
u16 new_toggle;
u16 keycode; u16 keycode;
/* extract device address and data */ /* extract device address and data */
switch (ir_config & 0x0003) { switch (ir->protocol) {
case 0: /* RC5: 5 bits device address, 6 bits data */ case IR_RC5: /* RC5: 5 bits device address, 6 bits data */
data = ircom & 0x3f; data = ircom & 0x3f;
addr = (ircom >> 6) & 0x1f; addr = (ircom >> 6) & 0x1f;
toggle = ircom & 0x0800;
break; break;
case 1: /* RCMM: 8(?) bits device address, 8(?) bits data */ case IR_RCMM: /* RCMM: ? bits device address, ? bits data */
data = ircom & 0xff; data = ircom & 0xff;
addr = (ircom >> 8) & 0xff; addr = (ircom >> 8) & 0x1f;
toggle = ircom & 0x8000;
break; break;
case 2: /* extended RC5: 5 bits device address, 7 bits data */ case IR_RC5_EXT: /* extended RC5: 5 bits device address, 7 bits data */
data = ircom & 0x3f; data = ircom & 0x3f;
addr = (ircom >> 6) & 0x1f; addr = (ircom >> 6) & 0x1f;
/* invert 7th data bit for backward compatibility with RC5 keymaps */ /* invert 7th data bit for backward compatibility with RC5 keymaps */
if (!(ircom & 0x1000)) if (!(ircom & 0x1000))
data |= 0x40; data |= 0x40;
toggle = ircom & 0x0800;
break; break;
default: default:
printk("invalid ir_config %x\n", ir_config); printk("%s invalid protocol %x\n", __FUNCTION__, ir->protocol);
return; return;
} }
keycode = key_map[data]; keycode = ir->key_map[data];
dprintk(16, "code %08x -> addr %i data 0x%02x -> keycode %i\n", dprintk(16, "%s: code %08x -> addr %i data 0x%02x -> keycode %i\n",
ircom, addr, data, keycode); __FUNCTION__, ircom, addr, data, keycode);
/* check device address (if selected) */ /* check device address */
if (ir_config & 0x4000) if (!(ir->device_mask & (1 << addr)))
if (addr != ((ir_config >> 16) & 0xff))
return; return;
if (!keycode) { if (!keycode) {
printk ("%s: unknown key 0x%02x!!\n", __FUNCTION__, data); printk ("%s: code %08x -> addr %i data 0x%02x -> unknown key!\n",
__FUNCTION__, ircom, addr, data);
return; return;
} }
if ((ir_config & 0x0003) == 1) if (timer_pending(&ir->keyup_timer)) {
new_toggle = 0; /* RCMM */ del_timer(&ir->keyup_timer);
else if (ir->last_key != keycode || toggle != ir->last_toggle) {
new_toggle = (ircom & 0x800); /* RC5, extended RC5 */ ir->delay_timer_finished = 0;
input_event(ir->input_dev, EV_KEY, ir->last_key, 0);
if (timer_pending(&keyup_timer)) { input_event(ir->input_dev, EV_KEY, keycode, 1);
del_timer(&keyup_timer); input_sync(ir->input_dev);
if (keyup_timer.data != keycode || new_toggle != old_toggle) { } else if (ir->delay_timer_finished) {
delay_timer_finished = 0; input_event(ir->input_dev, EV_KEY, keycode, 2);
input_event(input_dev, EV_KEY, keyup_timer.data, 0); input_sync(ir->input_dev);
input_event(input_dev, EV_KEY, keycode, 1);
input_sync(input_dev);
} else if (delay_timer_finished) {
input_event(input_dev, EV_KEY, keycode, 2);
input_sync(input_dev);
} }
} else { } else {
delay_timer_finished = 0; ir->delay_timer_finished = 0;
input_event(input_dev, EV_KEY, keycode, 1); input_event(ir->input_dev, EV_KEY, keycode, 1);
input_sync(input_dev); input_sync(ir->input_dev);
} }
keyup_timer.expires = jiffies + UP_TIMEOUT; ir->last_key = keycode;
keyup_timer.data = keycode; ir->last_toggle = toggle;
add_timer(&keyup_timer); ir->keyup_timer.expires = jiffies + UP_TIMEOUT;
add_timer(&ir->keyup_timer);
old_toggle = new_toggle;
} }
static void input_register_keys(void)
/* register with input layer */
static void input_register_keys(struct infrared *ir)
{ {
int i; int i;
memset(input_dev->keybit, 0, sizeof(input_dev->keybit)); set_bit(EV_KEY, ir->input_dev->evbit);
set_bit(EV_REP, ir->input_dev->evbit);
for (i = 0; i < ARRAY_SIZE(key_map); i++) { memset(ir->input_dev->keybit, 0, sizeof(ir->input_dev->keybit));
if (key_map[i] > KEY_MAX)
key_map[i] = 0; for (i = 0; i < ARRAY_SIZE(ir->key_map); i++) {
else if (key_map[i] > KEY_RESERVED) if (ir->key_map[i] > KEY_MAX)
set_bit(key_map[i], input_dev->keybit); ir->key_map[i] = 0;
else if (ir->key_map[i] > KEY_RESERVED)
set_bit(ir->key_map[i], ir->input_dev->keybit);
} }
ir->input_dev->keycode = ir->key_map;
ir->input_dev->keycodesize = sizeof(ir->key_map[0]);
ir->input_dev->keycodemax = ARRAY_SIZE(ir->key_map);
} }
static void input_repeat_key(unsigned long data) /* called by the input driver after rep[REP_DELAY] ms */
static void input_repeat_key(unsigned long parm)
{ {
/* called by the input driver after rep[REP_DELAY] ms */ struct infrared *ir = (struct infrared *) parm;
delay_timer_finished = 1;
ir->delay_timer_finished = 1;
} }
static int av7110_setup_irc_config(struct av7110 *av7110, u32 ir_config) /* check for configuration changes */
int av7110_check_ir_config(struct av7110 *av7110, int force)
{ {
int ret = 0; int i;
int modified = force;
int ret = -ENODEV;
for (i = 0; i < av_cnt; i++)
if (av7110 == av_list[i])
break;
dprintk(4, "%p\n", av7110); if (i < av_cnt && av7110) {
if (av7110) { if ((av7110->ir.protocol & 1) != ir_protocol[i] ||
ret = av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1, ir_config); av7110->ir.inversion != ir_inversion[i])
av7110->ir_config = ir_config; modified = true;
if (modified) {
/* protocol */
if (ir_protocol[i]) {
ir_protocol[i] = 1;
av7110->ir.protocol = IR_RCMM;
av7110->ir.ir_config = 0x0001;
} else if (FW_VERSION(av7110->arm_app) >= 0x2620) {
av7110->ir.protocol = IR_RC5_EXT;
av7110->ir.ir_config = 0x0002;
} else {
av7110->ir.protocol = IR_RC5;
av7110->ir.ir_config = 0x0000;
}
/* inversion */
if (ir_inversion[i]) {
ir_inversion[i] = 1;
av7110->ir.ir_config |= 0x8000;
}
av7110->ir.inversion = ir_inversion[i];
/* update ARM */
ret = av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1,
av7110->ir.ir_config);
} else
ret = 0;
/* address */
if (av7110->ir.device_mask != ir_device_mask[i])
av7110->ir.device_mask = ir_device_mask[i];
} }
return ret; return ret;
} }
/* /proc/av7110_ir interface */
static int av7110_ir_write_proc(struct file *file, const char __user *buffer, static int av7110_ir_write_proc(struct file *file, const char __user *buffer,
unsigned long count, void *data) unsigned long count, void *data)
{ {
char *page; char *page;
int size = 4 + 256 * sizeof(u16);
u32 ir_config; u32 ir_config;
int size = sizeof ir_config + sizeof av_list[0]->ir.key_map;
int i; int i;
if (count < size) if (count < size)
...@@ -194,53 +282,66 @@ static int av7110_ir_write_proc(struct file *file, const char __user *buffer, ...@@ -194,53 +282,66 @@ static int av7110_ir_write_proc(struct file *file, const char __user *buffer,
return -EFAULT; return -EFAULT;
} }
memcpy(&ir_config, page, 4); memcpy(&ir_config, page, sizeof ir_config);
memcpy(&key_map, page + 4, 256 * sizeof(u16));
for (i = 0; i < av_cnt; i++) {
/* keymap */
memcpy(av_list[i]->ir.key_map, page + sizeof ir_config,
sizeof(av_list[i]->ir.key_map));
/* protocol, inversion, address */
ir_protocol[i] = ir_config & 0x0001;
ir_inversion[i] = ir_config & 0x8000 ? 1 : 0;
if (ir_config & 0x4000)
ir_device_mask[i] = 1 << ((ir_config >> 16) & 0x1f);
else
ir_device_mask[i] = IR_ALL;
/* update configuration */
av7110_check_ir_config(av_list[i], false);
input_register_keys(&av_list[i]->ir);
}
vfree(page); vfree(page);
if (FW_VERSION(av_list[0]->arm_app) >= 0x2620 && !(ir_config & 0x0001))
ir_config |= 0x0002; /* enable extended RC5 */
for (i = 0; i < av_cnt; i++)
av7110_setup_irc_config(av_list[i], ir_config);
input_register_keys();
return count; return count;
} }
/* interrupt handler */
static void ir_handler(struct av7110 *av7110, u32 ircom) static void ir_handler(struct av7110 *av7110, u32 ircom)
{ {
dprintk(4, "ircommand = %08x\n", ircom); dprintk(4, "ir command = %08x\n", ircom);
av7110->ir_command = ircom; av7110->ir.ir_command = ircom;
tasklet_schedule(&av7110->ir_tasklet); tasklet_schedule(&av7110->ir.ir_tasklet);
} }
int __devinit av7110_ir_init(struct av7110 *av7110) int __devinit av7110_ir_init(struct av7110 *av7110)
{ {
struct input_dev *input_dev;
static struct proc_dir_entry *e; static struct proc_dir_entry *e;
int err; int err;
if (av_cnt >= ARRAY_SIZE(av_list)) if (av_cnt >= ARRAY_SIZE(av_list))
return -ENOSPC; return -ENOSPC;
av7110_setup_irc_config(av7110, 0x0001); av7110_check_ir_config(av7110, true);
av_list[av_cnt++] = av7110; av_list[av_cnt++] = av7110;
if (av_cnt == 1) { init_timer(&av7110->ir.keyup_timer);
init_timer(&keyup_timer); av7110->ir.keyup_timer.function = av7110_emit_keyup;
keyup_timer.data = 0; av7110->ir.keyup_timer.data = (unsigned long) &av7110->ir;
input_dev = input_allocate_device(); input_dev = input_allocate_device();
if (!input_dev) if (!input_dev)
return -ENOMEM; return -ENOMEM;
snprintf(input_phys, sizeof(input_phys), av7110->ir.input_dev = input_dev;
snprintf(av7110->ir.input_phys, sizeof(av7110->ir.input_phys),
"pci-%s/ir0", pci_name(av7110->dev->pci)); "pci-%s/ir0", pci_name(av7110->dev->pci));
input_dev->name = "DVB on-card IR receiver"; input_dev->name = "DVB on-card IR receiver";
input_dev->phys = input_phys; input_dev->phys = av7110->ir.input_phys;
input_dev->id.bustype = BUS_PCI; input_dev->id.bustype = BUS_PCI;
input_dev->id.version = 1; input_dev->id.version = 2;
if (av7110->dev->pci->subsystem_vendor) { if (av7110->dev->pci->subsystem_vendor) {
input_dev->id.vendor = av7110->dev->pci->subsystem_vendor; input_dev->id.vendor = av7110->dev->pci->subsystem_vendor;
input_dev->id.product = av7110->dev->pci->subsystem_device; input_dev->id.product = av7110->dev->pci->subsystem_device;
...@@ -249,16 +350,18 @@ int __devinit av7110_ir_init(struct av7110 *av7110) ...@@ -249,16 +350,18 @@ int __devinit av7110_ir_init(struct av7110 *av7110)
input_dev->id.product = av7110->dev->pci->device; input_dev->id.product = av7110->dev->pci->device;
} }
input_dev->cdev.dev = &av7110->dev->pci->dev; input_dev->cdev.dev = &av7110->dev->pci->dev;
set_bit(EV_KEY, input_dev->evbit); /* initial keymap */
set_bit(EV_REP, input_dev->evbit); memcpy(av7110->ir.key_map, default_key_map, sizeof av7110->ir.key_map);
input_register_keys(); input_register_keys(&av7110->ir);
err = input_register_device(input_dev); err = input_register_device(input_dev);
if (err) { if (err) {
input_free_device(input_dev); input_free_device(input_dev);
return err; return err;
} }
input_dev->timer.function = input_repeat_key; input_dev->timer.function = input_repeat_key;
input_dev->timer.data = (unsigned long) &av7110->ir;
if (av_cnt == 1) {
e = create_proc_entry("av7110_ir", S_IFREG | S_IRUGO | S_IWUSR, NULL); e = create_proc_entry("av7110_ir", S_IFREG | S_IRUGO | S_IWUSR, NULL);
if (e) { if (e) {
e->write_proc = av7110_ir_write_proc; e->write_proc = av7110_ir_write_proc;
...@@ -266,8 +369,8 @@ int __devinit av7110_ir_init(struct av7110 *av7110) ...@@ -266,8 +369,8 @@ int __devinit av7110_ir_init(struct av7110 *av7110)
} }
} }
tasklet_init(&av7110->ir_tasklet, av7110_emit_key, (unsigned long) av7110); tasklet_init(&av7110->ir.ir_tasklet, av7110_emit_key, (unsigned long) &av7110->ir);
av7110->ir_handler = ir_handler; av7110->ir.ir_handler = ir_handler;
return 0; return 0;
} }
...@@ -280,8 +383,10 @@ void __devexit av7110_ir_exit(struct av7110 *av7110) ...@@ -280,8 +383,10 @@ void __devexit av7110_ir_exit(struct av7110 *av7110)
if (av_cnt == 0) if (av_cnt == 0)
return; return;
av7110->ir_handler = NULL; del_timer_sync(&av7110->ir.keyup_timer);
tasklet_kill(&av7110->ir_tasklet); av7110->ir.ir_handler = NULL;
tasklet_kill(&av7110->ir.ir_tasklet);
for (i = 0; i < av_cnt; i++) for (i = 0; i < av_cnt; i++)
if (av_list[i] == av7110) { if (av_list[i] == av7110) {
av_list[i] = av_list[av_cnt-1]; av_list[i] = av_list[av_cnt-1];
...@@ -289,14 +394,13 @@ void __devexit av7110_ir_exit(struct av7110 *av7110) ...@@ -289,14 +394,13 @@ void __devexit av7110_ir_exit(struct av7110 *av7110)
break; break;
} }
if (av_cnt == 1) { if (av_cnt == 1)
del_timer_sync(&keyup_timer);
remove_proc_entry("av7110_ir", NULL); remove_proc_entry("av7110_ir", NULL);
input_unregister_device(input_dev);
} input_unregister_device(av7110->ir.input_dev);
av_cnt--; av_cnt--;
} }
//MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>"); //MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>");
//MODULE_LICENSE("GPL"); //MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment