[linux-dvb] PATCH: Nebula DigiTv PCI Remote Control support

Mark Weaver mark-clist at npsl.co.uk
Sat Oct 1 05:12:49 CEST 2005


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 at npsl.co.uk>

-------------- 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 at dti2.net> */
@@ -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:
 */


More information about the linux-dvb mailing list