Contributors: 55
Author Tokens Token Proportion Commits Commit Proportion
Mauro Carvalho Chehab 988 24.49% 35 25.74%
Jean Delvare 353 8.75% 7 5.15%
Andrew Morton 321 7.96% 3 2.21%
Ricardo Cerqueira 295 7.31% 3 2.21%
Kyle Strickland 214 5.30% 1 0.74%
Lukas Karas 208 5.15% 1 0.74%
David Härdeman 184 4.56% 9 6.62%
Andrey J. Melnikoff (TEMHOTA) 146 3.62% 1 0.74%
Brian Rogers 136 3.37% 1 0.74%
Vadim Solomin 113 2.80% 1 0.74%
Gerd Knorr 99 2.45% 3 2.21%
Sean Young 99 2.45% 9 6.62%
Dmitry Torokhov 99 2.45% 5 3.68%
Dmitry Belimov 88 2.18% 7 5.15%
Pojar George 84 2.08% 1 0.74%
Peter Missel 69 1.71% 2 1.47%
Hermann Pitton 41 1.02% 3 2.21%
Nickolay V. Shmyrev 37 0.92% 4 2.94%
Pedro 31 0.77% 1 0.74%
Devin Heitmueller 28 0.69% 1 0.74%
Darek Zielski 25 0.62% 1 0.74%
Herton Ronaldo Krzesinski 24 0.59% 2 1.47%
remi schwartz 24 0.59% 1 0.74%
Juan Pablo Sormani 23 0.57% 1 0.74%
Petr Baudis 22 0.55% 2 1.47%
Kees Cook 21 0.52% 1 0.74%
Sylvain Pasche 21 0.52% 1 0.74%
Michal Majchrowicz 20 0.50% 1 0.74%
Tim Farrington 20 0.50% 1 0.74%
Tony Wan 20 0.50% 1 0.74%
James R. Webb 20 0.50% 1 0.74%
Adrian Pardini 20 0.50% 1 0.74%
Pavel Mihaylov 19 0.47% 1 0.74%
Michael Obst 19 0.47% 1 0.74%
Ramiro Morales 15 0.37% 1 0.74%
Giampiero Giancipoli 15 0.37% 1 0.74%
Igor M. Liplianin 15 0.37% 1 0.74%
Ondrej Zary 10 0.25% 1 0.74%
Dmitry Eremin-Solenikov 6 0.15% 1 0.74%
Michael Ira Krufky 4 0.10% 2 1.47%
Andi Shyti 3 0.07% 1 0.74%
Rudo Thomas 3 0.07% 1 0.74%
Mikhail Fedotov 3 0.07% 1 0.74%
Albert Graham 3 0.07% 1 0.74%
Arnaud Patard 3 0.07% 1 0.74%
Ed Vipas 3 0.07% 1 0.74%
Andy Shevchenko 3 0.07% 1 0.74%
Eugene Yudin 3 0.07% 1 0.74%
Vasiliy Temnikov 3 0.07% 1 0.74%
James Le Cuirot 3 0.07% 1 0.74%
Nam Phạm Thành 3 0.07% 1 0.74%
Tejun Heo 2 0.05% 1 0.74%
Thomas Gleixner 2 0.05% 1 0.74%
Samuel Rakitnican 1 0.02% 1 0.74%
Linus Torvalds 1 0.02% 1 0.74%
Total 4035 136


// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 * handle saa7134 IR remotes via linux kernel input layer.
 */

#include "saa7134.h"
#include "saa7134-reg.h"

#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/slab.h>

#define MODULE_NAME "saa7134"

static unsigned int disable_ir;
module_param(disable_ir, int, 0444);
MODULE_PARM_DESC(disable_ir,"disable infrared remote support");

static unsigned int ir_debug;
module_param(ir_debug, int, 0644);
MODULE_PARM_DESC(ir_debug,"enable debug messages [IR]");

static int pinnacle_remote;
module_param(pinnacle_remote, int, 0644);    /* Choose Pinnacle PCTV remote */
MODULE_PARM_DESC(pinnacle_remote, "Specify Pinnacle PCTV remote: 0=coloured, 1=grey (defaults to 0)");

#define input_dbg(fmt, arg...) do { \
	if (ir_debug) \
		printk(KERN_DEBUG pr_fmt("input: " fmt), ## arg); \
	} while (0)
#define ir_dbg(ir, fmt, arg...) do { \
	if (ir_debug) \
		printk(KERN_DEBUG pr_fmt("ir %s: " fmt), ir->rc->device_name, \
		       ## arg); \
	} while (0)

/* Helper function for raw decoding at GPIO16 or GPIO18 */
static int saa7134_raw_decode_irq(struct saa7134_dev *dev);

/* -------------------- GPIO generic keycode builder -------------------- */

static int build_key(struct saa7134_dev *dev)
{
	struct saa7134_card_ir *ir = dev->remote;
	u32 gpio, data;

	/* here comes the additional handshake steps for some cards */
	switch (dev->board) {
	case SAA7134_BOARD_GOTVIEW_7135:
		saa_setb(SAA7134_GPIO_GPSTATUS1, 0x80);
		saa_clearb(SAA7134_GPIO_GPSTATUS1, 0x80);
		break;
	}
	/* rising SAA7134_GPIO_GPRESCAN reads the status */
	saa_clearb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN);
	saa_setb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN);

	gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2);
	if (ir->polling) {
		if (ir->last_gpio == gpio)
			return 0;
		ir->last_gpio = gpio;
	}

	data = ir_extract_bits(gpio, ir->mask_keycode);
	input_dbg("build_key gpio=0x%x mask=0x%x data=%d\n",
		gpio, ir->mask_keycode, data);

	switch (dev->board) {
	case SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG:
		if (data == ir->mask_keycode)
			rc_keyup(ir->dev);
		else
			rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data,
					     0);
		return 0;
	}

	if (ir->polling) {
		if ((ir->mask_keydown  &&  (0 != (gpio & ir->mask_keydown))) ||
		    (ir->mask_keyup    &&  (0 == (gpio & ir->mask_keyup)))) {
			rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data,
					     0);
		} else {
			rc_keyup(ir->dev);
		}
	}
	else {	/* IRQ driven mode - handle key press and release in one go */
		if ((ir->mask_keydown  &&  (0 != (gpio & ir->mask_keydown))) ||
		    (ir->mask_keyup    &&  (0 == (gpio & ir->mask_keyup)))) {
			rc_keydown_notimeout(ir->dev, RC_PROTO_UNKNOWN, data,
					     0);
			rc_keyup(ir->dev);
		}
	}

	return 0;
}

/* --------------------- Chip specific I2C key builders ----------------- */

static int get_key_flydvb_trio(struct IR_i2c *ir, enum rc_proto *protocol,
			       u32 *scancode, u8 *toggle)
{
	int gpio, rc;
	int attempt = 0;
	unsigned char b;

	/* We need this to access GPI Used by the saa_readl macro. */
	struct saa7134_dev *dev = ir->c->adapter->algo_data;

	if (dev == NULL) {
		ir_dbg(ir, "get_key_flydvb_trio: ir->c->adapter->algo_data is NULL!\n");
		return -EIO;
	}

	/* rising SAA7134_GPIGPRESCAN reads the status */
	saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
	saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);

	gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2);

	if (0x40000 & ~gpio)
		return 0; /* No button press */

	/* poll IR chip */
	/* weak up the IR chip */
	b = 0;

	while (1 != i2c_master_send(ir->c, &b, 1)) {
		if ((attempt++) < 10) {
			/*
			 * wait a bit for next attempt -
			 * I don't know how make it better
			 */
			msleep(10);
			continue;
		}
		ir_dbg(ir, "send wake up byte to pic16C505 (IR chip)failed %dx\n",
		       attempt);
		return -EIO;
	}
	rc = i2c_master_recv(ir->c, &b, 1);
	if (rc != 1) {
		ir_dbg(ir, "read error\n");
		if (rc < 0)
			return rc;
		return -EIO;
	}

	*protocol = RC_PROTO_UNKNOWN;
	*scancode = b;
	*toggle = 0;
	return 1;
}

static int get_key_msi_tvanywhere_plus(struct IR_i2c *ir,
				       enum rc_proto *protocol,
				       u32 *scancode, u8 *toggle)
{
	unsigned char b;
	int gpio, rc;

	/* <dev> is needed to access GPIO. Used by the saa_readl macro. */
	struct saa7134_dev *dev = ir->c->adapter->algo_data;
	if (dev == NULL) {
		ir_dbg(ir, "get_key_msi_tvanywhere_plus: ir->c->adapter->algo_data is NULL!\n");
		return -EIO;
	}

	/* rising SAA7134_GPIO_GPRESCAN reads the status */

	saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
	saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);

	gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2);

	/* GPIO&0x40 is pulsed low when a button is pressed. Don't do
	   I2C receive if gpio&0x40 is not low. */

	if (gpio & 0x40)
		return 0;       /* No button press */

	/* GPIO says there is a button press. Get it. */

	rc = i2c_master_recv(ir->c, &b, 1);
	if (rc != 1) {
		ir_dbg(ir, "read error\n");
		if (rc < 0)
			return rc;
		return -EIO;
	}

	/* No button press */

	if (b == 0xff)
		return 0;

	/* Button pressed */

	input_dbg("get_key_msi_tvanywhere_plus: Key = 0x%02X\n", b);
	*protocol = RC_PROTO_UNKNOWN;
	*scancode = b;
	*toggle = 0;
	return 1;
}

/* copied and modified from get_key_msi_tvanywhere_plus() */
static int get_key_kworld_pc150u(struct IR_i2c *ir, enum rc_proto *protocol,
				 u32 *scancode, u8 *toggle)
{
	unsigned char b;
	unsigned int gpio;
	int rc;

	/* <dev> is needed to access GPIO. Used by the saa_readl macro. */
	struct saa7134_dev *dev = ir->c->adapter->algo_data;
	if (dev == NULL) {
		ir_dbg(ir, "get_key_kworld_pc150u: ir->c->adapter->algo_data is NULL!\n");
		return -EIO;
	}

	/* rising SAA7134_GPIO_GPRESCAN reads the status */

	saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
	saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);

	gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2);

	/* GPIO&0x100 is pulsed low when a button is pressed. Don't do
	   I2C receive if gpio&0x100 is not low. */

	if (gpio & 0x100)
		return 0;       /* No button press */

	/* GPIO says there is a button press. Get it. */

	rc = i2c_master_recv(ir->c, &b, 1);
	if (rc != 1) {
		ir_dbg(ir, "read error\n");
		if (rc < 0)
			return rc;
		return -EIO;
	}

	/* No button press */

	if (b == 0xff)
		return 0;

	/* Button pressed */

	input_dbg("get_key_kworld_pc150u: Key = 0x%02X\n", b);
	*protocol = RC_PROTO_UNKNOWN;
	*scancode = b;
	*toggle = 0;
	return 1;
}

static int get_key_purpletv(struct IR_i2c *ir, enum rc_proto *protocol,
			    u32 *scancode, u8 *toggle)
{
	int rc;
	unsigned char b;

	/* poll IR chip */
	rc = i2c_master_recv(ir->c, &b, 1);
	if (rc != 1) {
		ir_dbg(ir, "read error\n");
		if (rc < 0)
			return rc;
		return -EIO;
	}

	/* no button press */
	if (b==0)
		return 0;

	/* repeating */
	if (b & 0x80)
		return 1;

	*protocol = RC_PROTO_UNKNOWN;
	*scancode = b;
	*toggle = 0;
	return 1;
}

static int get_key_beholdm6xx(struct IR_i2c *ir, enum rc_proto *protocol,
			      u32 *scancode, u8 *toggle)
{
	int rc;
	unsigned char data[12];
	u32 gpio;

	struct saa7134_dev *dev = ir->c->adapter->algo_data;

	/* rising SAA7134_GPIO_GPRESCAN reads the status */
	saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
	saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);

	gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2);

	if (0x400000 & ~gpio)
		return 0; /* No button press */

	ir->c->addr = 0x5a >> 1;

	rc = i2c_master_recv(ir->c, data, 12);
	if (rc != 12) {
		ir_dbg(ir, "read error\n");
		if (rc < 0)
			return rc;
		return -EIO;
	}

	if (data[9] != (unsigned char)(~data[8]))
		return 0;

	*protocol = RC_PROTO_NECX;
	*scancode = RC_SCANCODE_NECX(data[11] << 8 | data[10], data[9]);
	*toggle = 0;
	return 1;
}

/* Common (grey or coloured) pinnacle PCTV remote handling
 *
 */
static int get_key_pinnacle(struct IR_i2c *ir, enum rc_proto *protocol,
			    u32 *scancode, u8 *toggle, int parity_offset,
			    int marker, int code_modulo)
{
	int rc;
	unsigned char b[4];
	unsigned int start = 0,parity = 0,code = 0;

	/* poll IR chip */
	rc = i2c_master_recv(ir->c, b, 4);
	if (rc != 4) {
		ir_dbg(ir, "read error\n");
		if (rc < 0)
			return rc;
		return -EIO;
	}

	for (start = 0; start < ARRAY_SIZE(b); start++) {
		if (b[start] == marker) {
			code=b[(start+parity_offset + 1) % 4];
			parity=b[(start+parity_offset) % 4];
		}
	}

	/* Empty Request */
	if (parity == 0)
		return 0;

	/* Repeating... */
	if (ir->old == parity)
		return 0;

	ir->old = parity;

	/* drop special codes when a key is held down a long time for the grey controller
	   In this case, the second bit of the code is asserted */
	if (marker == 0xfe && (code & 0x40))
		return 0;

	code %= code_modulo;

	*protocol = RC_PROTO_UNKNOWN;
	*scancode = code;
	*toggle = 0;

	ir_dbg(ir, "Pinnacle PCTV key %02x\n", code);
	return 1;
}

/* The grey pinnacle PCTV remote
 *
 *  There are one issue with this remote:
 *   - I2c packet does not change when the same key is pressed quickly. The workaround
 *     is to hold down each key for about half a second, so that another code is generated
 *     in the i2c packet, and the function can distinguish key presses.
 *
 * Sylvain Pasche <sylvain.pasche@gmail.com>
 */
static int get_key_pinnacle_grey(struct IR_i2c *ir, enum rc_proto *protocol,
				 u32 *scancode, u8 *toggle)
{

	return get_key_pinnacle(ir, protocol, scancode, toggle, 1, 0xfe, 0xff);
}


/* The new pinnacle PCTV remote (with the colored buttons)
 *
 * Ricardo Cerqueira <v4l@cerqueira.org>
 */
static int get_key_pinnacle_color(struct IR_i2c *ir, enum rc_proto *protocol,
				  u32 *scancode, u8 *toggle)
{
	/* code_modulo parameter (0x88) is used to reduce code value to fit inside IR_KEYTAB_SIZE
	 *
	 * this is the only value that results in 42 unique
	 * codes < 128
	 */

	return get_key_pinnacle(ir, protocol, scancode, toggle, 2, 0x80, 0x88);
}

void saa7134_input_irq(struct saa7134_dev *dev)
{
	struct saa7134_card_ir *ir;

	if (!dev || !dev->remote)
		return;

	ir = dev->remote;
	if (!ir->running)
		return;

	if (!ir->polling && !ir->raw_decode) {
		build_key(dev);
	} else if (ir->raw_decode) {
		saa7134_raw_decode_irq(dev);
	}
}

static void saa7134_input_timer(struct timer_list *t)
{
	struct saa7134_card_ir *ir = from_timer(ir, t, timer);
	struct saa7134_dev *dev = ir->dev->priv;

	build_key(dev);
	mod_timer(&ir->timer, jiffies + msecs_to_jiffies(ir->polling));
}

int saa7134_ir_open(struct rc_dev *rc)
{
	struct saa7134_dev *dev = rc->priv;
	struct saa7134_card_ir *ir = dev->remote;

	/* Moved here from saa7134_input_init1() because the latter
	 * is not called on device resume */
	switch (dev->board) {
	case SAA7134_BOARD_MD2819:
	case SAA7134_BOARD_KWORLD_VSTREAM_XPERT:
	case SAA7134_BOARD_AVERMEDIA_305:
	case SAA7134_BOARD_AVERMEDIA_307:
	case SAA7134_BOARD_AVERMEDIA_505:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_305:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_505:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_307:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_507:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_507UA:
	case SAA7134_BOARD_AVERMEDIA_GO_007_FM:
	case SAA7134_BOARD_AVERMEDIA_M102:
	case SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS:
		/* Without this we won't receive key up events */
		saa_setb(SAA7134_GPIO_GPMODE0, 0x4);
		saa_setb(SAA7134_GPIO_GPSTATUS0, 0x4);
		break;
	case SAA7134_BOARD_AVERMEDIA_777:
	case SAA7134_BOARD_AVERMEDIA_A16AR:
		/* Without this we won't receive key up events */
		saa_setb(SAA7134_GPIO_GPMODE1, 0x1);
		saa_setb(SAA7134_GPIO_GPSTATUS1, 0x1);
		break;
	case SAA7134_BOARD_AVERMEDIA_A16D:
		/* Without this we won't receive key up events */
		saa_setb(SAA7134_GPIO_GPMODE1, 0x1);
		saa_setb(SAA7134_GPIO_GPSTATUS1, 0x1);
		break;
	case SAA7134_BOARD_GOTVIEW_7135:
		saa_setb(SAA7134_GPIO_GPMODE1, 0x80);
		break;
	}

	ir->running = true;

	if (ir->polling) {
		timer_setup(&ir->timer, saa7134_input_timer, 0);
		ir->timer.expires = jiffies + HZ;
		add_timer(&ir->timer);
	}

	return 0;
}

void saa7134_ir_close(struct rc_dev *rc)
{
	struct saa7134_dev *dev = rc->priv;
	struct saa7134_card_ir *ir = dev->remote;

	if (ir->polling)
		del_timer_sync(&ir->timer);

	ir->running = false;
}

int saa7134_input_init1(struct saa7134_dev *dev)
{
	struct saa7134_card_ir *ir;
	struct rc_dev *rc;
	char *ir_codes = NULL;
	u32 mask_keycode = 0;
	u32 mask_keydown = 0;
	u32 mask_keyup   = 0;
	unsigned polling = 0;
	bool raw_decode  = false;
	int err;

	if (dev->has_remote != SAA7134_REMOTE_GPIO)
		return -ENODEV;
	if (disable_ir)
		return -ENODEV;

	/* detect & configure */
	switch (dev->board) {
	case SAA7134_BOARD_FLYVIDEO2000:
	case SAA7134_BOARD_FLYVIDEO3000:
	case SAA7134_BOARD_FLYTVPLATINUM_FM:
	case SAA7134_BOARD_FLYTVPLATINUM_MINI2:
	case SAA7134_BOARD_ROVERMEDIA_LINK_PRO_FM:
		ir_codes     = RC_MAP_FLYVIDEO;
		mask_keycode = 0xEC00000;
		mask_keydown = 0x0040000;
		break;
	case SAA7134_BOARD_CINERGY400:
	case SAA7134_BOARD_CINERGY600:
	case SAA7134_BOARD_CINERGY600_MK3:
		ir_codes     = RC_MAP_CINERGY;
		mask_keycode = 0x00003f;
		mask_keyup   = 0x040000;
		break;
	case SAA7134_BOARD_ECS_TVP3XP:
	case SAA7134_BOARD_ECS_TVP3XP_4CB5:
		ir_codes     = RC_MAP_EZTV;
		mask_keycode = 0x00017c;
		mask_keyup   = 0x000002;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_KWORLD_XPERT:
	case SAA7134_BOARD_AVACSSMARTTV:
		ir_codes     = RC_MAP_PIXELVIEW;
		mask_keycode = 0x00001F;
		mask_keyup   = 0x000020;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_MD2819:
	case SAA7134_BOARD_KWORLD_VSTREAM_XPERT:
	case SAA7134_BOARD_AVERMEDIA_305:
	case SAA7134_BOARD_AVERMEDIA_307:
	case SAA7134_BOARD_AVERMEDIA_505:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_305:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_505:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_307:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_507:
	case SAA7134_BOARD_AVERMEDIA_STUDIO_507UA:
	case SAA7134_BOARD_AVERMEDIA_GO_007_FM:
	case SAA7134_BOARD_AVERMEDIA_M102:
	case SAA7134_BOARD_AVERMEDIA_GO_007_FM_PLUS:
		ir_codes     = RC_MAP_AVERMEDIA;
		mask_keycode = 0x0007C8;
		mask_keydown = 0x000010;
		polling      = 50; // ms
		/* GPIO stuff moved to saa7134_ir_open() */
		break;
	case SAA7134_BOARD_AVERMEDIA_M135A:
		ir_codes     = RC_MAP_AVERMEDIA_M135A;
		mask_keydown = 0x0040000;	/* Enable GPIO18 line on both edges */
		mask_keyup   = 0x0040000;
		mask_keycode = 0xffff;
		raw_decode   = true;
		break;
	case SAA7134_BOARD_AVERMEDIA_M733A:
		ir_codes     = RC_MAP_AVERMEDIA_M733A_RM_K6;
		mask_keydown = 0x0040000;
		mask_keyup   = 0x0040000;
		mask_keycode = 0xffff;
		raw_decode   = true;
		break;
	case SAA7134_BOARD_AVERMEDIA_777:
	case SAA7134_BOARD_AVERMEDIA_A16AR:
		ir_codes     = RC_MAP_AVERMEDIA;
		mask_keycode = 0x02F200;
		mask_keydown = 0x000400;
		polling      = 50; // ms
		/* GPIO stuff moved to saa7134_ir_open() */
		break;
	case SAA7134_BOARD_AVERMEDIA_A16D:
		ir_codes     = RC_MAP_AVERMEDIA_A16D;
		mask_keycode = 0x02F200;
		mask_keydown = 0x000400;
		polling      = 50; /* ms */
		/* GPIO stuff moved to saa7134_ir_open() */
		break;
	case SAA7134_BOARD_KWORLD_TERMINATOR:
		ir_codes     = RC_MAP_PIXELVIEW;
		mask_keycode = 0x00001f;
		mask_keyup   = 0x000060;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_MANLI_MTV001:
	case SAA7134_BOARD_MANLI_MTV002:
		ir_codes     = RC_MAP_MANLI;
		mask_keycode = 0x001f00;
		mask_keyup   = 0x004000;
		polling      = 50; /* ms */
		break;
	case SAA7134_BOARD_BEHOLD_409FM:
	case SAA7134_BOARD_BEHOLD_401:
	case SAA7134_BOARD_BEHOLD_403:
	case SAA7134_BOARD_BEHOLD_403FM:
	case SAA7134_BOARD_BEHOLD_405:
	case SAA7134_BOARD_BEHOLD_405FM:
	case SAA7134_BOARD_BEHOLD_407:
	case SAA7134_BOARD_BEHOLD_407FM:
	case SAA7134_BOARD_BEHOLD_409:
	case SAA7134_BOARD_BEHOLD_505FM:
	case SAA7134_BOARD_BEHOLD_505RDS_MK5:
	case SAA7134_BOARD_BEHOLD_505RDS_MK3:
	case SAA7134_BOARD_BEHOLD_507_9FM:
	case SAA7134_BOARD_BEHOLD_507RDS_MK3:
	case SAA7134_BOARD_BEHOLD_507RDS_MK5:
		ir_codes     = RC_MAP_MANLI;
		mask_keycode = 0x003f00;
		mask_keyup   = 0x004000;
		polling      = 50; /* ms */
		break;
	case SAA7134_BOARD_BEHOLD_COLUMBUS_TVFM:
		ir_codes     = RC_MAP_BEHOLD_COLUMBUS;
		mask_keycode = 0x003f00;
		mask_keyup   = 0x004000;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_SEDNA_PC_TV_CARDBUS:
		ir_codes     = RC_MAP_PCTV_SEDNA;
		mask_keycode = 0x001f00;
		mask_keyup   = 0x004000;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_GOTVIEW_7135:
		ir_codes     = RC_MAP_GOTVIEW7135;
		mask_keycode = 0x0003CC;
		mask_keydown = 0x000010;
		polling	     = 5; /* ms */
		/* GPIO stuff moved to saa7134_ir_open() */
		break;
	case SAA7134_BOARD_VIDEOMATE_TV_PVR:
	case SAA7134_BOARD_VIDEOMATE_GOLD_PLUS:
	case SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUSII:
		ir_codes     = RC_MAP_VIDEOMATE_TV_PVR;
		mask_keycode = 0x00003F;
		mask_keyup   = 0x400000;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_PROTEUS_2309:
		ir_codes     = RC_MAP_PROTEUS_2309;
		mask_keycode = 0x00007F;
		mask_keyup   = 0x000080;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_VIDEOMATE_DVBT_300:
	case SAA7134_BOARD_VIDEOMATE_DVBT_200:
		ir_codes     = RC_MAP_VIDEOMATE_TV_PVR;
		mask_keycode = 0x003F00;
		mask_keyup   = 0x040000;
		break;
	case SAA7134_BOARD_FLYDVBS_LR300:
	case SAA7134_BOARD_FLYDVBT_LR301:
	case SAA7134_BOARD_FLYDVBTDUO:
		ir_codes     = RC_MAP_FLYDVB;
		mask_keycode = 0x0001F00;
		mask_keydown = 0x0040000;
		break;
	case SAA7134_BOARD_ASUSTeK_P7131_DUAL:
	case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA:
	case SAA7134_BOARD_ASUSTeK_P7131_ANALOG:
		ir_codes     = RC_MAP_ASUS_PC39;
		mask_keydown = 0x0040000;	/* Enable GPIO18 line on both edges */
		mask_keyup   = 0x0040000;
		mask_keycode = 0xffff;
		raw_decode   = true;
		break;
	case SAA7134_BOARD_ASUSTeK_PS3_100:
		ir_codes     = RC_MAP_ASUS_PS3_100;
		mask_keydown = 0x0040000;
		mask_keyup   = 0x0040000;
		mask_keycode = 0xffff;
		raw_decode   = true;
		break;
	case SAA7134_BOARD_ENCORE_ENLTV:
	case SAA7134_BOARD_ENCORE_ENLTV_FM:
		ir_codes     = RC_MAP_ENCORE_ENLTV;
		mask_keycode = 0x00007f;
		mask_keyup   = 0x040000;
		polling      = 50; // ms
		break;
	case SAA7134_BOARD_ENCORE_ENLTV_FM53:
	case SAA7134_BOARD_ENCORE_ENLTV_FM3:
		ir_codes     = RC_MAP_ENCORE_ENLTV_FM53;
		mask_keydown = 0x0040000;	/* Enable GPIO18 line on both edges */
		mask_keyup   = 0x0040000;
		mask_keycode = 0xffff;
		raw_decode   = true;
		break;
	case SAA7134_BOARD_10MOONSTVMASTER3:
		ir_codes     = RC_MAP_ENCORE_ENLTV;
		mask_keycode = 0x5f80000;
		mask_keyup   = 0x8000000;
		polling      = 50; //ms
		break;
	case SAA7134_BOARD_GENIUS_TVGO_A11MCE:
		ir_codes     = RC_MAP_GENIUS_TVGO_A11MCE;
		mask_keycode = 0xff;
		mask_keydown = 0xf00000;
		polling = 50; /* ms */
		break;
	case SAA7134_BOARD_REAL_ANGEL_220:
		ir_codes     = RC_MAP_REAL_AUDIO_220_32_KEYS;
		mask_keycode = 0x3f00;
		mask_keyup   = 0x4000;
		polling = 50; /* ms */
		break;
	case SAA7134_BOARD_KWORLD_PLUS_TV_ANALOG:
		ir_codes     = RC_MAP_KWORLD_PLUS_TV_ANALOG;
		mask_keycode = 0x7f;
		polling = 40; /* ms */
		break;
	case SAA7134_BOARD_VIDEOMATE_S350:
		ir_codes     = RC_MAP_VIDEOMATE_S350;
		mask_keycode = 0x003f00;
		mask_keydown = 0x040000;
		break;
	case SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S:
		ir_codes     = RC_MAP_WINFAST;
		mask_keycode = 0x5f00;
		mask_keyup   = 0x020000;
		polling      = 50; /* ms */
		break;
	case SAA7134_BOARD_VIDEOMATE_M1F:
		ir_codes     = RC_MAP_VIDEOMATE_K100;
		mask_keycode = 0x0ff00;
		mask_keyup   = 0x040000;
		break;
	case SAA7134_BOARD_HAUPPAUGE_HVR1150:
	case SAA7134_BOARD_HAUPPAUGE_HVR1120:
		ir_codes     = RC_MAP_HAUPPAUGE;
		mask_keydown = 0x0040000;	/* Enable GPIO18 line on both edges */
		mask_keyup   = 0x0040000;
		mask_keycode = 0xffff;
		raw_decode   = true;
		break;
	case SAA7134_BOARD_LEADTEK_WINFAST_TV2100_FM:
		ir_codes     = RC_MAP_LEADTEK_Y04G0051;
		mask_keydown = 0x0040000;	/* Enable GPIO18 line on both edges */
		mask_keyup   = 0x0040000;
		mask_keycode = 0xffff;
		raw_decode   = true;
		break;
	}
	if (NULL == ir_codes) {
		pr_err("Oops: IR config error [card=%d]\n", dev->board);
		return -ENODEV;
	}

	ir = kzalloc(sizeof(*ir), GFP_KERNEL);
	rc = rc_allocate_device(RC_DRIVER_SCANCODE);
	if (!ir || !rc) {
		err = -ENOMEM;
		goto err_out_free;
	}

	ir->dev = rc;
	dev->remote = ir;

	/* init hardware-specific stuff */
	ir->mask_keycode = mask_keycode;
	ir->mask_keydown = mask_keydown;
	ir->mask_keyup   = mask_keyup;
	ir->polling      = polling;
	ir->raw_decode	 = raw_decode;

	/* init input device */
	snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0",
		 pci_name(dev->pci));

	rc->priv = dev;
	rc->open = saa7134_ir_open;
	rc->close = saa7134_ir_close;
	if (raw_decode) {
		rc->driver_type = RC_DRIVER_IR_RAW;
		rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
	}

	rc->device_name = saa7134_boards[dev->board].name;
	rc->input_phys = ir->phys;
	rc->input_id.bustype = BUS_PCI;
	rc->input_id.version = 1;
	if (dev->pci->subsystem_vendor) {
		rc->input_id.vendor  = dev->pci->subsystem_vendor;
		rc->input_id.product = dev->pci->subsystem_device;
	} else {
		rc->input_id.vendor  = dev->pci->vendor;
		rc->input_id.product = dev->pci->device;
	}
	rc->dev.parent = &dev->pci->dev;
	rc->map_name = ir_codes;
	rc->driver_name = MODULE_NAME;
	rc->min_timeout = 1;
	rc->timeout = IR_DEFAULT_TIMEOUT;
	rc->max_timeout = 10 * IR_DEFAULT_TIMEOUT;

	err = rc_register_device(rc);
	if (err)
		goto err_out_free;

	return 0;

err_out_free:
	rc_free_device(rc);
	dev->remote = NULL;
	kfree(ir);
	return err;
}

void saa7134_input_fini(struct saa7134_dev *dev)
{
	if (NULL == dev->remote)
		return;

	rc_unregister_device(dev->remote->dev);
	kfree(dev->remote);
	dev->remote = NULL;
}

void saa7134_probe_i2c_ir(struct saa7134_dev *dev)
{
	struct i2c_board_info info;
	struct i2c_msg msg_msi = {
		.addr = 0x50,
		.flags = I2C_M_RD,
		.len = 0,
		.buf = NULL,
	};
	int rc;

	if (disable_ir) {
		input_dbg("IR has been disabled, not probing for i2c remote\n");
		return;
	}

	memset(&info, 0, sizeof(struct i2c_board_info));
	memset(&dev->init_data, 0, sizeof(dev->init_data));
	strscpy(info.type, "ir_video", I2C_NAME_SIZE);

	switch (dev->board) {
	case SAA7134_BOARD_PINNACLE_PCTV_110i:
	case SAA7134_BOARD_PINNACLE_PCTV_310i:
		dev->init_data.name = "Pinnacle PCTV";
		if (pinnacle_remote == 0) {
			dev->init_data.get_key = get_key_pinnacle_color;
			dev->init_data.ir_codes = RC_MAP_PINNACLE_COLOR;
			info.addr = 0x47;
		} else {
			dev->init_data.get_key = get_key_pinnacle_grey;
			dev->init_data.ir_codes = RC_MAP_PINNACLE_GREY;
			info.addr = 0x47;
		}
		break;
	case SAA7134_BOARD_UPMOST_PURPLE_TV:
		dev->init_data.name = "Purple TV";
		dev->init_data.get_key = get_key_purpletv;
		dev->init_data.ir_codes = RC_MAP_PURPLETV;
		info.addr = 0x7a;
		break;
	case SAA7134_BOARD_MSI_TVATANYWHERE_PLUS:
		dev->init_data.name = "MSI TV@nywhere Plus";
		dev->init_data.get_key = get_key_msi_tvanywhere_plus;
		dev->init_data.ir_codes = RC_MAP_MSI_TVANYWHERE_PLUS;
		/*
		 * MSI TV@nyware Plus requires more frequent polling
		 * otherwise it will miss some keypresses
		 */
		dev->init_data.polling_interval = 50;
		info.addr = 0x30;
		/* MSI TV@nywhere Plus controller doesn't seem to
		   respond to probes unless we read something from
		   an existing device. Weird...
		   REVISIT: might no longer be needed */
		rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1);
		input_dbg("probe 0x%02x @ %s: %s\n",
			msg_msi.addr, dev->i2c_adap.name,
			(1 == rc) ? "yes" : "no");
		break;
	case SAA7134_BOARD_SNAZIO_TVPVR_PRO:
		dev->init_data.name = "SnaZio* TVPVR PRO";
		dev->init_data.get_key = get_key_msi_tvanywhere_plus;
		dev->init_data.ir_codes = RC_MAP_MSI_TVANYWHERE_PLUS;
		/*
		 * MSI TV@nyware Plus requires more frequent polling
		 * otherwise it will miss some keypresses
		 */
		dev->init_data.polling_interval = 50;
		info.addr = 0x30;
		/*
		 * MSI TV@nywhere Plus controller doesn't seem to
		 *  respond to probes unless we read something from
		 *  an existing device. Weird...
		 * REVISIT: might no longer be needed
		 */
		rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1);
		input_dbg("probe 0x%02x @ %s: %s\n",
			msg_msi.addr, dev->i2c_adap.name,
			(rc == 1) ? "yes" : "no");
		break;
	case SAA7134_BOARD_KWORLD_PC150U:
		/* copied and modified from MSI TV@nywhere Plus */
		dev->init_data.name = "Kworld PC150-U";
		dev->init_data.get_key = get_key_kworld_pc150u;
		dev->init_data.ir_codes = RC_MAP_KWORLD_PC150U;
		info.addr = 0x30;
		/* MSI TV@nywhere Plus controller doesn't seem to
		   respond to probes unless we read something from
		   an existing device. Weird...
		   REVISIT: might no longer be needed */
		rc = i2c_transfer(&dev->i2c_adap, &msg_msi, 1);
		input_dbg("probe 0x%02x @ %s: %s\n",
			msg_msi.addr, dev->i2c_adap.name,
			(1 == rc) ? "yes" : "no");
		break;
	case SAA7134_BOARD_HAUPPAUGE_HVR1110:
		dev->init_data.name = saa7134_boards[dev->board].name;
		dev->init_data.ir_codes = RC_MAP_HAUPPAUGE;
		dev->init_data.type = RC_PROTO_BIT_RC5 |
				RC_PROTO_BIT_RC6_MCE | RC_PROTO_BIT_RC6_6A_32;
		dev->init_data.internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR;
		info.addr = 0x71;
		break;
	case SAA7134_BOARD_BEHOLD_607FM_MK3:
	case SAA7134_BOARD_BEHOLD_607FM_MK5:
	case SAA7134_BOARD_BEHOLD_609FM_MK3:
	case SAA7134_BOARD_BEHOLD_609FM_MK5:
	case SAA7134_BOARD_BEHOLD_607RDS_MK3:
	case SAA7134_BOARD_BEHOLD_607RDS_MK5:
	case SAA7134_BOARD_BEHOLD_609RDS_MK3:
	case SAA7134_BOARD_BEHOLD_609RDS_MK5:
	case SAA7134_BOARD_BEHOLD_M6:
	case SAA7134_BOARD_BEHOLD_M63:
	case SAA7134_BOARD_BEHOLD_M6_EXTRA:
	case SAA7134_BOARD_BEHOLD_H6:
	case SAA7134_BOARD_BEHOLD_X7:
	case SAA7134_BOARD_BEHOLD_H7:
	case SAA7134_BOARD_BEHOLD_A7:
		dev->init_data.name = "BeholdTV";
		dev->init_data.get_key = get_key_beholdm6xx;
		dev->init_data.ir_codes = RC_MAP_BEHOLD;
		dev->init_data.type = RC_PROTO_BIT_NECX;
		info.addr = 0x2d;
		break;
	case SAA7134_BOARD_AVERMEDIA_CARDBUS_501:
	case SAA7134_BOARD_AVERMEDIA_CARDBUS_506:
		info.addr = 0x40;
		break;
	case SAA7134_BOARD_AVERMEDIA_A706:
		info.addr = 0x41;
		break;
	case SAA7134_BOARD_FLYDVB_TRIO:
		dev->init_data.name = "FlyDVB Trio";
		dev->init_data.get_key = get_key_flydvb_trio;
		dev->init_data.ir_codes = RC_MAP_FLYDVB;
		info.addr = 0x0b;
		break;
	default:
		input_dbg("No I2C IR support for board %x\n", dev->board);
		return;
	}

	if (dev->init_data.name)
		info.platform_data = &dev->init_data;
	i2c_new_device(&dev->i2c_adap, &info);
}

static int saa7134_raw_decode_irq(struct saa7134_dev *dev)
{
	struct saa7134_card_ir *ir = dev->remote;
	int space;

	/* Generate initial event */
	saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
	saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
	space = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & ir->mask_keydown;
	ir_raw_event_store_edge(dev->remote->dev, !space);

	return 1;
}