I've attached a patch that has this working well for me. This consists of a new module, ir-kbd-nebula, and a patch against a few existing bttv files to support it. I could not see how to sensibly glue this code into ir-kbd-gpio as it is very different from the method most cards use. The basic hooks in bttv are to add an `anyirq' flag, which forwards any interrupt, not just GPINT, to the IR sub-driver if it has any any_irq function. This is needed because somehow the nebula card generates an interrupt without setting any bits in INT_STAT. I believe it is triggering it off GPIO pin 5 somehow, but the nebula windows driver doesn't even look in the stat bits -- it just tests GPIO pin 4. If anyone can demystify me on that I'd be grateful, I just couldn't figure out how this is possible from the bt878a datasheet. I also set the has_remote flag & gpiomask (to a value obtained with the DScaler regspy utility, I'm not sure if this is actually required for it to work though). The rest of the code (ir-kbd-nebula.c) is just some timers and bit twiddling. I think the timing values might need to be adjusted a bit for best 'feel', module parameters are: key_timeout = 200ms. We don't get a key up event so I use a timer to release the key. repeat_period = 33ms. Standard input layer repeat period. repeat_delay = 500ms. 2xStandard input layer repeat delay. Remote buttons aren't as bouncy as keyboards, might be a bit long. remote_gap = 885us. This is the inter-pulse distance, apparently there is another remote that uses 600us. I don't have one so I don't know if adjusting that value will work (depends on if it is RC5). I'd be grateful for any test results &| code reviews. I'm not a kernel programmer by trade so this would be helpful. Thanks, Mark p.s. if required, Signed-Off-By: Mark Weaver <mark@xxxxxxxxxx> -------------- next part -------------- Index: Makefile =================================================================== RCS file: /cvs/video4linux/video4linux/Makefile,v retrieving revision 1.49 diff -u -r1.49 Makefile --- Makefile 30 Sep 2005 04:21:04 -0000 1.49 +++ Makefile 1 Oct 2005 01:53:03 -0000 @@ -54,7 +54,7 @@ ifeq ($(VERSION).$(PATCHLEVEL),2.6) ifeq ($(CONFIG_VIDEO_BTTV),m) bttv-objs += bttv-gpio.o - obj-$(CONFIG_VIDEO_IR) += ir-kbd-gpio.o ir-kbd-i2c.o + obj-$(CONFIG_VIDEO_IR) += ir-kbd-gpio.o ir-kbd-i2c.o ir-kbd-nebula.o endif ifeq ($(CONFIG_VIDEO_SAA7134),m) obj-$(CONFIG_VIDEO_IR) += ir-kbd-i2c.o @@ -136,7 +136,7 @@ inst_video := btcx-risc.ko bttv.ko tda9887.ko tuner.ko tvaudio.ko tveeprom.ko saa6588.ko inst_video += tvmixer.ko v4l1-compat.ko v4l2-common.ko inst_video += video-buf.ko video-buf-dvb.ko -inst_video += ir-kbd-gpio.ko ir-kbd-i2c.ko msp3400.ko +inst_video += ir-kbd-gpio.ko ir-kbd-i2c.ko msp3400.ko ir-kbd-nebula.o inst_cx88 := cx8800.ko cx8802.ko cx88-alsa.ko inst_cx88 += cx88-blackbird.ko cx88xx.ko cx88-dvb.ko inst_saa7134 := saa6752hs.ko saa7134.ko saa7134-empress.ko saa7134-dvb.ko Index: bttv-cards.c =================================================================== RCS file: /cvs/video4linux/video4linux/bttv-cards.c,v retrieving revision 1.85 diff -u -r1.85 bttv-cards.c --- bttv-cards.c 30 Sep 2005 00:06:36 -0000 1.85 +++ bttv-cards.c 1 Oct 2005 01:53:07 -0000 @@ -2042,7 +2042,10 @@ .tuner_type = -1, .tuner_addr = ADDR_UNSET, .has_dvb = 1, + .has_remote = 1, + .gpiomask = 0x1b, .no_gpioirq = 1, + .anyirq = 1, }, [BTTV_BOARD_PV143] = { /* Jorge Boncompte - DTI2 <jorge@xxxxxxxx> */ @@ -3195,6 +3198,8 @@ btv->has_remote=1; if (!bttv_tvcards[btv->c.type].no_gpioirq) btv->gpioirq=1; + if (bttv_tvcards[btv->c.type].anyirq) + btv->anyirq = 1; if (bttv_tvcards[btv->c.type].audio_hook) btv->audio_hook=bttv_tvcards[btv->c.type].audio_hook; Index: bttv-driver.c =================================================================== RCS file: /cvs/video4linux/video4linux/bttv-driver.c,v retrieving revision 1.60 diff -u -r1.60 bttv-driver.c --- bttv-driver.c 29 Sep 2005 20:09:24 -0000 1.60 +++ bttv-driver.c 1 Oct 2005 01:53:10 -0000 @@ -3684,6 +3684,12 @@ int handled = 0; btv=(struct bttv *)dev_id; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + if (btv->anyirq) + handled = bttv_any_irq(&btv->c); +#endif + count=0; while (1) { /* get/clear interrupt status bits */ Index: bttv-gpio.c =================================================================== RCS file: /cvs/video4linux/video4linux/bttv-gpio.c,v retrieving revision 1.7 diff -u -r1.7 bttv-gpio.c --- bttv-gpio.c 16 Feb 2005 12:14:10 -0000 1.7 +++ bttv-gpio.c 1 Oct 2005 01:53:10 -0000 @@ -114,6 +114,24 @@ } } +int bttv_any_irq(struct bttv_core *core) +{ + struct bttv_sub_driver *drv; + struct bttv_sub_device *dev; + struct list_head *item; + int handled = 0; + + list_for_each(item,&core->subs) { + dev = list_entry(item,struct bttv_sub_device,list); + drv = to_bttv_sub_drv(dev->dev.driver); + if (drv && drv->any_irq) { + if (drv->any_irq(dev)) + handled = 1; + } + } + return handled; +} + /* ----------------------------------------------------------------------- */ /* external: sub-driver register/unregister */ Index: bttv.h =================================================================== RCS file: /cvs/video4linux/video4linux/bttv.h,v retrieving revision 1.26 diff -u -r1.26 bttv.h --- bttv.h 29 Sep 2005 20:09:24 -0000 1.26 +++ bttv.h 1 Oct 2005 01:53:11 -0000 @@ -233,6 +233,7 @@ unsigned int has_dvb:1; unsigned int has_remote:1; unsigned int no_gpioirq:1; + unsigned int anyirq:1; /* other settings */ unsigned int pll; @@ -338,6 +339,7 @@ struct device_driver drv; char wanted[BUS_ID_SIZE]; void (*gpio_irq)(struct bttv_sub_device *sub); + int (*any_irq)(struct bttv_sub_device *sub); }; #define to_bttv_sub_drv(x) container_of((x), struct bttv_sub_driver, drv) Index: bttvp.h =================================================================== RCS file: /cvs/video4linux/video4linux/bttvp.h,v retrieving revision 1.22 diff -u -r1.22 bttvp.h --- bttvp.h 28 Sep 2005 19:07:59 -0000 1.22 +++ bttvp.h 1 Oct 2005 01:53:11 -0000 @@ -221,6 +221,7 @@ int bttv_sub_add_device(struct bttv_core *core, char *name); int bttv_sub_del_devices(struct bttv_core *core); void bttv_gpio_irq(struct bttv_core *core); +int bttv_any_irq(struct bttv_core *core); #endif @@ -290,6 +291,7 @@ struct bttv_pll_info pll; int triton1; int gpioirq; + int anyirq; int use_i2c_hw; /* old gpio interface */ -------------- next part -------------- /* * $Id:$ * * Copyright (c) 2005 Mark Weaver * Copyright (c) 2003 Gerd Knorr * Copyright (c) 2003 Pavel Machek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/input.h> #include <linux/pci.h> #include <media/ir-common.h> #include "bttv.h" /* RC5 macros */ #define RC5_START(x) (((x)>>12)&3) #define RC5_TOGGLE(x) (((x)>>11)&1) #define RC5_ADDR(x) (((x)>>6)&31) #define RC5_INSTR(x) ((x)&63) /* ---------------------------------------------------------------------- */ static IR_KEYTAB_TYPE ir_codes_nebula[IR_KEYTAB_SIZE] = { [ 0x00 ] = KEY_KP0, [ 0x01 ] = KEY_KP1, [ 0x02 ] = KEY_KP2, [ 0x03 ] = KEY_KP3, [ 0x04 ] = KEY_KP4, [ 0x05 ] = KEY_KP5, [ 0x06 ] = KEY_KP6, [ 0x07 ] = KEY_KP7, [ 0x08 ] = KEY_KP8, [ 0x09 ] = KEY_KP9, [ 0x0a ] = KEY_TV, [ 0x0b ] = KEY_AUX, [ 0x0c ] = KEY_DVD, [ 0x0d ] = KEY_POWER, [ 0x0e ] = KEY_MHP, /* labelled 'Picture' */ [ 0x0f ] = KEY_AUDIO, [ 0x10 ] = KEY_INFO, [ 0x11 ] = KEY_F13, /* 16:9 */ [ 0x12 ] = KEY_F14, /* 14:9 */ [ 0x13 ] = KEY_EPG, [ 0x14 ] = KEY_EXIT, [ 0x15 ] = KEY_MENU, [ 0x16 ] = KEY_UP, [ 0x17 ] = KEY_DOWN, [ 0x18 ] = KEY_LEFT, [ 0x19 ] = KEY_RIGHT, [ 0x1a ] = KEY_ENTER, [ 0x1b ] = KEY_CHANNELUP, [ 0x1c ] = KEY_CHANNELDOWN, [ 0x1d ] = KEY_VOLUMEUP, [ 0x1e ] = KEY_VOLUMEDOWN, [ 0x1f ] = KEY_RED, [ 0x20 ] = KEY_GREEN, [ 0x21 ] = KEY_YELLOW, [ 0x22 ] = KEY_BLUE, [ 0x23 ] = KEY_SUBTITLE, [ 0x24 ] = KEY_F15, /* AD */ [ 0x25 ] = KEY_TEXT, [ 0x26 ] = KEY_MUTE, [ 0x27 ] = KEY_REWIND, [ 0x28 ] = KEY_STOP, [ 0x29 ] = KEY_PLAY, [ 0x2a ] = KEY_FASTFORWARD, [ 0x2b ] = KEY_F16, /* chapter */ [ 0x2c ] = KEY_PAUSE, [ 0x2d ] = KEY_PLAY, [ 0x2e ] = KEY_RECORD, [ 0x2f ] = KEY_F17, /* picture in picture */ [ 0x30 ] = KEY_KPPLUS, /* zoom in */ [ 0x31 ] = KEY_KPMINUS, /* zoom out */ [ 0x32 ] = KEY_F18, /* capture */ [ 0x33 ] = KEY_F19, /* web */ [ 0x34 ] = KEY_EMAIL, [ 0x35 ] = KEY_PHONE, [ 0x36 ] = KEY_PC }; struct IR { struct bttv_sub_device *sub; struct input_dev input; struct ir_input_state ir; char name[32]; char phys[32]; /* timer_end for code completion */ struct timer_list timer_end; /* timer_end for key release */ struct timer_list timer_keyup; /* last good rc5 code */ u32 last_rc5; /* last raw bit seen */ u32 last_bit; /* raw code under construction */ u32 code; /* time of last seen code */ struct timeval base_time; /* remote gap */ u32 remote_gap; /* key up timer length */ u32 key_timeout; /* building raw code */ int active; }; static int debug; module_param(debug, int, 0644); /* debug level (0,1,2) */ static int remote_gap = 885; module_param(remote_gap, int, 0644); static int repeat_delay = 500; module_param(repeat_delay, int, 0644); static int repeat_period = 33; module_param(repeat_period, int, 0644); static int key_timeout = 200; module_param(key_timeout, int, 0644); #define DEVNAME "ir-kbd-gpio" #define dprintk(fmt, arg...) if (debug) \ printk(KERN_DEBUG DEVNAME ": " fmt , ## arg) static int ir_irq(struct bttv_sub_device *sub); static void ir_timer_end(unsigned long data); static void ir_timer_keyup(unsigned long data); static int ir_probe(struct device *dev); static int ir_remove(struct device *dev); static struct bttv_sub_driver driver = { .drv = { .name = DEVNAME, .probe = ir_probe, .remove = ir_remove, }, .any_irq = ir_irq, }; /* ---------------------------------------------------------------------- */ /* decode raw bit pattern to RC5 code */ static u32 rc5_decode(unsigned int code) { unsigned int org_code = code; unsigned int pair; unsigned int rc5 = 0; int i; code = (code << 1) | 1; for (i = 0; i < 14; ++i) { pair = code & 0x3; code >>= 2; rc5 <<= 1; switch (pair) { case 0: case 2: break; case 1: rc5 |= 1; break; case 3: dprintk("bad code: %x\n", org_code); return 0; } } dprintk("code=%x, rc5=%x, start=%x, toggle=%x, address=%x, " "instr=%x\n", rc5, org_code, RC5_START(rc5), RC5_TOGGLE(rc5), RC5_ADDR(rc5), RC5_INSTR(rc5)); return rc5; } static int ir_irq(struct bttv_sub_device *sub) { struct IR *ir = dev_get_drvdata(&sub->dev); struct timeval tv; u32 gpio; u32 gap; unsigned long current_jiffies, timeout; /* read gpio port */ gpio = bttv_gpio_read(ir->sub->core); /* remote IRQ? */ if (!(gpio & 0x20)) return 0; /* get time of bit */ current_jiffies = jiffies; do_gettimeofday(&tv); /* avoid overflow with gap >1s */ if (tv.tv_sec - ir->base_time.tv_sec > 1) { gap = 200000; } else { gap = 1000000 * (tv.tv_sec - ir->base_time.tv_sec) + tv.tv_usec - ir->base_time.tv_usec; } /* active code => add bit */ if (ir->active) { /* only if in the code (otherwise spurious IRQ or timer late) */ if (ir->last_bit < 28) { ir->last_bit = (gap - ir->remote_gap / 2) / ir->remote_gap; ir->code |= 1 << ir->last_bit; } /* starting new code */ } else { ir->active = 1; ir->code = 0; ir->base_time = tv; ir->last_bit = 0; timeout = current_jiffies + (500 + 30 * HZ) / 1000; mod_timer(&ir->timer_end, timeout); } /* toggle GPIO pin 4 to reset the irq */ bttv_gpio_write(ir->sub->core, gpio & ~(1 << 4)); bttv_gpio_write(ir->sub->core, gpio | (1 << 4)); return 1; } static void ir_timer_end(unsigned long data) { struct IR *ir = (struct IR*)data; struct timeval tv; unsigned long current_jiffies, timeout; u32 gap; /* get time */ current_jiffies = jiffies; do_gettimeofday(&tv); /* avoid overflow with gap >1s */ if (tv.tv_sec - ir->base_time.tv_sec > 1) { gap = 200000; } else { gap = 1000000 * (tv.tv_sec - ir->base_time.tv_sec) + tv.tv_usec - ir->base_time.tv_usec; } /* Allow some timmer jitter (RC5 is ~24ms anyway so this is ok) */ if (gap < 28000) { dprintk("spurious timer_end\n"); return; } ir->active = 0; if (ir->last_bit < 20) { /* ignore spurious codes (caused by light/other remotes) */ dprintk("short code: %x\n", ir->code); } else { u32 rc5 = rc5_decode(ir->code); /* two start bits? */ if (RC5_START(rc5) != 3) { dprintk("rc5 start bits invalid: %u\n", RC5_START(rc5)); /* right address? */ } else if (RC5_ADDR(rc5) == 0x0) { u32 toggle = RC5_TOGGLE(rc5); u32 instr = RC5_INSTR(rc5); /* Good code, decide if repeat/repress */ if (toggle != RC5_TOGGLE(ir->last_rc5) || instr != RC5_INSTR(ir->last_rc5)) { dprintk("instruction %x, toggle %x\n", instr, toggle); ir_input_nokey(&ir->input, &ir->ir); ir_input_keydown(&ir->input, &ir->ir, instr, instr); } /* Set/reset key-up timer */ timeout = current_jiffies + (500 + ir->key_timeout * HZ) / 1000; mod_timer(&ir->timer_keyup, timeout); /* Save code for repeat test */ ir->last_rc5 = rc5; } } } static void ir_timer_keyup(unsigned long data) { struct IR *ir = (struct IR*)data; dprintk("key released\n"); ir_input_nokey(&ir->input,&ir->ir); } /* ---------------------------------------------------------------------- */ static int ir_probe(struct device *dev) { struct bttv_sub_device *sub = to_bttv_sub_dev(dev); struct IR *ir; IR_KEYTAB_TYPE *ir_codes = NULL; int ir_type = IR_TYPE_OTHER; u32 gpio; ir = kmalloc(sizeof(*ir),GFP_KERNEL); if (NULL == ir) return -ENOMEM; memset(ir,0,sizeof(*ir)); ir->remote_gap = remote_gap; ir->key_timeout = key_timeout; /* detect & configure */ switch (sub->core->type) { case BTTV_BOARD_NEBULA_DIGITV: ir_codes = ir_codes_nebula; break; } if (NULL == ir_codes) { kfree(ir); return -ENODEV; } /* enable remote irq */ bttv_gpio_inout(sub->core, (1<<4), 1<<4); gpio = bttv_gpio_read(sub->core); bttv_gpio_write(sub->core, gpio & ~(1 << 4)); bttv_gpio_write(sub->core, gpio | (1 << 4)); ir->sub = sub; /* init input device */ snprintf(ir->name, sizeof(ir->name), "bttv IR (card=%d)", sub->core->type); snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", pci_name(sub->core->pci)); ir_input_init(&ir->input, &ir->ir, ir_type, ir_codes); ir->input.name = ir->name; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) ir->input.phys = ir->phys; ir->input.id.bustype = BUS_PCI; ir->input.id.version = 1; if (sub->core->pci->subsystem_vendor) { ir->input.id.vendor = sub->core->pci->subsystem_vendor; ir->input.id.product = sub->core->pci->subsystem_device; } else { ir->input.id.vendor = sub->core->pci->vendor; ir->input.id.product = sub->core->pci->device; } ir->input.dev = &sub->core->pci->dev; #endif /* set timer_end for code completion */ init_timer(&ir->timer_end); ir->timer_end.function = ir_timer_end; ir->timer_end.data = (unsigned long)ir; init_timer(&ir->timer_keyup); ir->timer_keyup.function = ir_timer_keyup; ir->timer_keyup.data = (unsigned long)ir; /* all done */ dev_set_drvdata(dev,ir); input_register_device(&ir->input); /* the remote isn't as bouncy as a keyboard */ ir->input.rep[REP_DELAY] = repeat_delay; ir->input.rep[REP_PERIOD] = repeat_period; printk(DEVNAME ": %s detected at %s\n",ir->input.name,ir->input.phys); return 0; } static int ir_remove(struct device *dev) { struct IR *ir = dev_get_drvdata(dev); u32 gpio; del_timer(&ir->timer_end); flush_scheduled_work(); /* Disable IRQ */ gpio = bttv_gpio_read(ir->sub->core); bttv_gpio_write(ir->sub->core, gpio & ~(1 << 4)); input_unregister_device(&ir->input); kfree(ir); return 0; } /* ---------------------------------------------------------------------- */ MODULE_AUTHOR("Gerd Knorr, Pavel Machek, Mark Weaver"); MODULE_DESCRIPTION("input driver for nebula bt8x8 gpio IR remote controls"); MODULE_LICENSE("GPL"); static int ir_init(void) { return bttv_sub_register(&driver, "remote"); } static void ir_fini(void) { bttv_sub_unregister(&driver); } module_init(ir_init); module_exit(ir_fini); /* * Local variables: * c-basic-offset: 8 * End: */