162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Driver for the Conexant CX23885/7/8 PCIe bridge
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Infrared remote control input device
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  Most of this file is
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci *  Copyright (C) 2009  Andy Walls <awalls@md.metrocast.net>
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci *  However, the cx23885_input_{init,fini} functions contained herein are
1262306a36Sopenharmony_ci *  derived from Linux kernel files linux/media/video/.../...-input.c marked as:
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci *  Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
1562306a36Sopenharmony_ci *  Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
1662306a36Sopenharmony_ci *		       Markus Rechberger <mrechberger@gmail.com>
1762306a36Sopenharmony_ci *		       Mauro Carvalho Chehab <mchehab@kernel.org>
1862306a36Sopenharmony_ci *		       Sascha Sommer <saschasommer@freenet.de>
1962306a36Sopenharmony_ci *  Copyright (C) 2004, 2005 Chris Pascoe
2062306a36Sopenharmony_ci *  Copyright (C) 2003, 2004 Gerd Knorr
2162306a36Sopenharmony_ci *  Copyright (C) 2003 Pavel Machek
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include "cx23885.h"
2562306a36Sopenharmony_ci#include "cx23885-input.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include <linux/slab.h>
2862306a36Sopenharmony_ci#include <media/rc-core.h>
2962306a36Sopenharmony_ci#include <media/v4l2-subdev.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define MODULE_NAME "cx23885"
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic void cx23885_input_process_measurements(struct cx23885_dev *dev,
3462306a36Sopenharmony_ci					       bool overrun)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	struct cx23885_kernel_ir *kernel_ir = dev->kernel_ir;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	ssize_t num;
3962306a36Sopenharmony_ci	int count, i;
4062306a36Sopenharmony_ci	bool handle = false;
4162306a36Sopenharmony_ci	struct ir_raw_event ir_core_event[64];
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	do {
4462306a36Sopenharmony_ci		num = 0;
4562306a36Sopenharmony_ci		v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) ir_core_event,
4662306a36Sopenharmony_ci				 sizeof(ir_core_event), &num);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci		count = num / sizeof(struct ir_raw_event);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci		for (i = 0; i < count; i++) {
5162306a36Sopenharmony_ci			ir_raw_event_store(kernel_ir->rc,
5262306a36Sopenharmony_ci					   &ir_core_event[i]);
5362306a36Sopenharmony_ci			handle = true;
5462306a36Sopenharmony_ci		}
5562306a36Sopenharmony_ci	} while (num != 0);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (overrun)
5862306a36Sopenharmony_ci		ir_raw_event_overflow(kernel_ir->rc);
5962306a36Sopenharmony_ci	else if (handle)
6062306a36Sopenharmony_ci		ir_raw_event_handle(kernel_ir->rc);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_civoid cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	struct v4l2_subdev_ir_parameters params;
6662306a36Sopenharmony_ci	int overrun, data_available;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (dev->sd_ir == NULL || events == 0)
6962306a36Sopenharmony_ci		return;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	switch (dev->board) {
7262306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1270:
7362306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1850:
7462306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1290:
7562306a36Sopenharmony_ci	case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
7662306a36Sopenharmony_ci	case CX23885_BOARD_TEVII_S470:
7762306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1250:
7862306a36Sopenharmony_ci	case CX23885_BOARD_MYGICA_X8507:
7962306a36Sopenharmony_ci	case CX23885_BOARD_TBS_6980:
8062306a36Sopenharmony_ci	case CX23885_BOARD_TBS_6981:
8162306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T9580:
8262306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T980C:
8362306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S950C:
8462306a36Sopenharmony_ci	case CX23885_BOARD_TT_CT2_4500_CI:
8562306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S950:
8662306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S952:
8762306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T982:
8862306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1265_K4:
8962306a36Sopenharmony_ci		/*
9062306a36Sopenharmony_ci		 * The only boards we handle right now.  However other boards
9162306a36Sopenharmony_ci		 * using the CX2388x integrated IR controller should be similar
9262306a36Sopenharmony_ci		 */
9362306a36Sopenharmony_ci		break;
9462306a36Sopenharmony_ci	default:
9562306a36Sopenharmony_ci		return;
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	overrun = events & (V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN |
9962306a36Sopenharmony_ci			    V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	data_available = events & (V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED |
10262306a36Sopenharmony_ci				   V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (overrun) {
10562306a36Sopenharmony_ci		/* If there was a FIFO overrun, stop the device */
10662306a36Sopenharmony_ci		v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
10762306a36Sopenharmony_ci		params.enable = false;
10862306a36Sopenharmony_ci		/* Mitigate race with cx23885_input_ir_stop() */
10962306a36Sopenharmony_ci		params.shutdown = atomic_read(&dev->ir_input_stopping);
11062306a36Sopenharmony_ci		v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (data_available)
11462306a36Sopenharmony_ci		cx23885_input_process_measurements(dev, overrun);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (overrun) {
11762306a36Sopenharmony_ci		/* If there was a FIFO overrun, clear & restart the device */
11862306a36Sopenharmony_ci		params.enable = true;
11962306a36Sopenharmony_ci		/* Mitigate race with cx23885_input_ir_stop() */
12062306a36Sopenharmony_ci		params.shutdown = atomic_read(&dev->ir_input_stopping);
12162306a36Sopenharmony_ci		v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic int cx23885_input_ir_start(struct cx23885_dev *dev)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	struct v4l2_subdev_ir_parameters params;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	if (dev->sd_ir == NULL)
13062306a36Sopenharmony_ci		return -ENODEV;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	atomic_set(&dev->ir_input_stopping, 0);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
13562306a36Sopenharmony_ci	switch (dev->board) {
13662306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1270:
13762306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1850:
13862306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1290:
13962306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1250:
14062306a36Sopenharmony_ci	case CX23885_BOARD_MYGICA_X8507:
14162306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T9580:
14262306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T980C:
14362306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S950C:
14462306a36Sopenharmony_ci	case CX23885_BOARD_TT_CT2_4500_CI:
14562306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S950:
14662306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S952:
14762306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T982:
14862306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1265_K4:
14962306a36Sopenharmony_ci		/*
15062306a36Sopenharmony_ci		 * The IR controller on this board only returns pulse widths.
15162306a36Sopenharmony_ci		 * Any other mode setting will fail to set up the device.
15262306a36Sopenharmony_ci		*/
15362306a36Sopenharmony_ci		params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
15462306a36Sopenharmony_ci		params.enable = true;
15562306a36Sopenharmony_ci		params.interrupt_enable = true;
15662306a36Sopenharmony_ci		params.shutdown = false;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		/* Setup for baseband compatible with both RC-5 and RC-6A */
15962306a36Sopenharmony_ci		params.modulation = false;
16062306a36Sopenharmony_ci		/* RC-5:  2,222,222 ns = 1/36 kHz * 32 cycles * 2 marks * 1.25*/
16162306a36Sopenharmony_ci		/* RC-6A: 3,333,333 ns = 1/36 kHz * 16 cycles * 6 marks * 1.25*/
16262306a36Sopenharmony_ci		params.max_pulse_width = 3333333; /* ns */
16362306a36Sopenharmony_ci		/* RC-5:    666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */
16462306a36Sopenharmony_ci		/* RC-6A:   333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */
16562306a36Sopenharmony_ci		params.noise_filter_min_width = 333333; /* ns */
16662306a36Sopenharmony_ci		/*
16762306a36Sopenharmony_ci		 * This board has inverted receive sense:
16862306a36Sopenharmony_ci		 * mark is received as low logic level;
16962306a36Sopenharmony_ci		 * falling edges are detected as rising edges; etc.
17062306a36Sopenharmony_ci		 */
17162306a36Sopenharmony_ci		params.invert_level = true;
17262306a36Sopenharmony_ci		break;
17362306a36Sopenharmony_ci	case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
17462306a36Sopenharmony_ci	case CX23885_BOARD_TEVII_S470:
17562306a36Sopenharmony_ci	case CX23885_BOARD_TBS_6980:
17662306a36Sopenharmony_ci	case CX23885_BOARD_TBS_6981:
17762306a36Sopenharmony_ci		/*
17862306a36Sopenharmony_ci		 * The IR controller on this board only returns pulse widths.
17962306a36Sopenharmony_ci		 * Any other mode setting will fail to set up the device.
18062306a36Sopenharmony_ci		 */
18162306a36Sopenharmony_ci		params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
18262306a36Sopenharmony_ci		params.enable = true;
18362306a36Sopenharmony_ci		params.interrupt_enable = true;
18462306a36Sopenharmony_ci		params.shutdown = false;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci		/* Setup for a standard NEC protocol */
18762306a36Sopenharmony_ci		params.carrier_freq = 37917; /* Hz, 455 kHz/12 for NEC */
18862306a36Sopenharmony_ci		params.carrier_range_lower = 33000; /* Hz */
18962306a36Sopenharmony_ci		params.carrier_range_upper = 43000; /* Hz */
19062306a36Sopenharmony_ci		params.duty_cycle = 33; /* percent, 33 percent for NEC */
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		/*
19362306a36Sopenharmony_ci		 * NEC max pulse width: (64/3)/(455 kHz/12) * 16 nec_units
19462306a36Sopenharmony_ci		 * (64/3)/(455 kHz/12) * 16 nec_units * 1.375 = 12378022 ns
19562306a36Sopenharmony_ci		 */
19662306a36Sopenharmony_ci		params.max_pulse_width = 12378022; /* ns */
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		/*
19962306a36Sopenharmony_ci		 * NEC noise filter min width: (64/3)/(455 kHz/12) * 1 nec_unit
20062306a36Sopenharmony_ci		 * (64/3)/(455 kHz/12) * 1 nec_units * 0.625 = 351648 ns
20162306a36Sopenharmony_ci		 */
20262306a36Sopenharmony_ci		params.noise_filter_min_width = 351648; /* ns */
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		params.modulation = false;
20562306a36Sopenharmony_ci		params.invert_level = true;
20662306a36Sopenharmony_ci		break;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci	v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
20962306a36Sopenharmony_ci	return 0;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic int cx23885_input_ir_open(struct rc_dev *rc)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	struct cx23885_kernel_ir *kernel_ir = rc->priv;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if (kernel_ir->cx == NULL)
21762306a36Sopenharmony_ci		return -ENODEV;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	return cx23885_input_ir_start(kernel_ir->cx);
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic void cx23885_input_ir_stop(struct cx23885_dev *dev)
22362306a36Sopenharmony_ci{
22462306a36Sopenharmony_ci	struct v4l2_subdev_ir_parameters params;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	if (dev->sd_ir == NULL)
22762306a36Sopenharmony_ci		return;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	/*
23062306a36Sopenharmony_ci	 * Stop the sd_ir subdevice from generating notifications and
23162306a36Sopenharmony_ci	 * scheduling work.
23262306a36Sopenharmony_ci	 * It is shutdown this way in order to mitigate a race with
23362306a36Sopenharmony_ci	 * cx23885_input_rx_work_handler() in the overrun case, which could
23462306a36Sopenharmony_ci	 * re-enable the subdevice.
23562306a36Sopenharmony_ci	 */
23662306a36Sopenharmony_ci	atomic_set(&dev->ir_input_stopping, 1);
23762306a36Sopenharmony_ci	v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
23862306a36Sopenharmony_ci	while (params.shutdown == false) {
23962306a36Sopenharmony_ci		params.enable = false;
24062306a36Sopenharmony_ci		params.interrupt_enable = false;
24162306a36Sopenharmony_ci		params.shutdown = true;
24262306a36Sopenharmony_ci		v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
24362306a36Sopenharmony_ci		v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
24462306a36Sopenharmony_ci	}
24562306a36Sopenharmony_ci	flush_work(&dev->cx25840_work);
24662306a36Sopenharmony_ci	flush_work(&dev->ir_rx_work);
24762306a36Sopenharmony_ci	flush_work(&dev->ir_tx_work);
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic void cx23885_input_ir_close(struct rc_dev *rc)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct cx23885_kernel_ir *kernel_ir = rc->priv;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	if (kernel_ir->cx != NULL)
25562306a36Sopenharmony_ci		cx23885_input_ir_stop(kernel_ir->cx);
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ciint cx23885_input_init(struct cx23885_dev *dev)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct cx23885_kernel_ir *kernel_ir;
26162306a36Sopenharmony_ci	struct rc_dev *rc;
26262306a36Sopenharmony_ci	char *rc_map;
26362306a36Sopenharmony_ci	u64 allowed_protos;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	int ret;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	/*
26862306a36Sopenharmony_ci	 * If the IR device (hardware registers, chip, GPIO lines, etc.) isn't
26962306a36Sopenharmony_ci	 * encapsulated in a v4l2_subdev, then I'm not going to deal with it.
27062306a36Sopenharmony_ci	 */
27162306a36Sopenharmony_ci	if (dev->sd_ir == NULL)
27262306a36Sopenharmony_ci		return -ENODEV;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	switch (dev->board) {
27562306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1270:
27662306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1850:
27762306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1290:
27862306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1250:
27962306a36Sopenharmony_ci	case CX23885_BOARD_HAUPPAUGE_HVR1265_K4:
28062306a36Sopenharmony_ci		/* Integrated CX2388[58] IR controller */
28162306a36Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
28262306a36Sopenharmony_ci		/* The grey Hauppauge RC-5 remote */
28362306a36Sopenharmony_ci		rc_map = RC_MAP_HAUPPAUGE;
28462306a36Sopenharmony_ci		break;
28562306a36Sopenharmony_ci	case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
28662306a36Sopenharmony_ci		/* Integrated CX23885 IR controller */
28762306a36Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
28862306a36Sopenharmony_ci		/* The grey Terratec remote with orange buttons */
28962306a36Sopenharmony_ci		rc_map = RC_MAP_NEC_TERRATEC_CINERGY_XS;
29062306a36Sopenharmony_ci		break;
29162306a36Sopenharmony_ci	case CX23885_BOARD_TEVII_S470:
29262306a36Sopenharmony_ci		/* Integrated CX23885 IR controller */
29362306a36Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
29462306a36Sopenharmony_ci		/* A guess at the remote */
29562306a36Sopenharmony_ci		rc_map = RC_MAP_TEVII_NEC;
29662306a36Sopenharmony_ci		break;
29762306a36Sopenharmony_ci	case CX23885_BOARD_MYGICA_X8507:
29862306a36Sopenharmony_ci		/* Integrated CX23885 IR controller */
29962306a36Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
30062306a36Sopenharmony_ci		/* A guess at the remote */
30162306a36Sopenharmony_ci		rc_map = RC_MAP_TOTAL_MEDIA_IN_HAND_02;
30262306a36Sopenharmony_ci		break;
30362306a36Sopenharmony_ci	case CX23885_BOARD_TBS_6980:
30462306a36Sopenharmony_ci	case CX23885_BOARD_TBS_6981:
30562306a36Sopenharmony_ci		/* Integrated CX23885 IR controller */
30662306a36Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
30762306a36Sopenharmony_ci		/* A guess at the remote */
30862306a36Sopenharmony_ci		rc_map = RC_MAP_TBS_NEC;
30962306a36Sopenharmony_ci		break;
31062306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T9580:
31162306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T980C:
31262306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S950C:
31362306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S950:
31462306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_S952:
31562306a36Sopenharmony_ci	case CX23885_BOARD_DVBSKY_T982:
31662306a36Sopenharmony_ci		/* Integrated CX23885 IR controller */
31762306a36Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
31862306a36Sopenharmony_ci		rc_map = RC_MAP_DVBSKY;
31962306a36Sopenharmony_ci		break;
32062306a36Sopenharmony_ci	case CX23885_BOARD_TT_CT2_4500_CI:
32162306a36Sopenharmony_ci		/* Integrated CX23885 IR controller */
32262306a36Sopenharmony_ci		allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
32362306a36Sopenharmony_ci		rc_map = RC_MAP_TT_1500;
32462306a36Sopenharmony_ci		break;
32562306a36Sopenharmony_ci	default:
32662306a36Sopenharmony_ci		return -ENODEV;
32762306a36Sopenharmony_ci	}
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	/* cx23885 board instance kernel IR state */
33062306a36Sopenharmony_ci	kernel_ir = kzalloc(sizeof(struct cx23885_kernel_ir), GFP_KERNEL);
33162306a36Sopenharmony_ci	if (kernel_ir == NULL)
33262306a36Sopenharmony_ci		return -ENOMEM;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	kernel_ir->cx = dev;
33562306a36Sopenharmony_ci	kernel_ir->name = kasprintf(GFP_KERNEL, "cx23885 IR (%s)",
33662306a36Sopenharmony_ci				    cx23885_boards[dev->board].name);
33762306a36Sopenharmony_ci	if (!kernel_ir->name) {
33862306a36Sopenharmony_ci		ret = -ENOMEM;
33962306a36Sopenharmony_ci		goto err_out_free;
34062306a36Sopenharmony_ci	}
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	kernel_ir->phys = kasprintf(GFP_KERNEL, "pci-%s/ir0",
34362306a36Sopenharmony_ci				    pci_name(dev->pci));
34462306a36Sopenharmony_ci	if (!kernel_ir->phys) {
34562306a36Sopenharmony_ci		ret = -ENOMEM;
34662306a36Sopenharmony_ci		goto err_out_free_name;
34762306a36Sopenharmony_ci	}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	/* input device */
35062306a36Sopenharmony_ci	rc = rc_allocate_device(RC_DRIVER_IR_RAW);
35162306a36Sopenharmony_ci	if (!rc) {
35262306a36Sopenharmony_ci		ret = -ENOMEM;
35362306a36Sopenharmony_ci		goto err_out_free_phys;
35462306a36Sopenharmony_ci	}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	kernel_ir->rc = rc;
35762306a36Sopenharmony_ci	rc->device_name = kernel_ir->name;
35862306a36Sopenharmony_ci	rc->input_phys = kernel_ir->phys;
35962306a36Sopenharmony_ci	rc->input_id.bustype = BUS_PCI;
36062306a36Sopenharmony_ci	rc->input_id.version = 1;
36162306a36Sopenharmony_ci	if (dev->pci->subsystem_vendor) {
36262306a36Sopenharmony_ci		rc->input_id.vendor  = dev->pci->subsystem_vendor;
36362306a36Sopenharmony_ci		rc->input_id.product = dev->pci->subsystem_device;
36462306a36Sopenharmony_ci	} else {
36562306a36Sopenharmony_ci		rc->input_id.vendor  = dev->pci->vendor;
36662306a36Sopenharmony_ci		rc->input_id.product = dev->pci->device;
36762306a36Sopenharmony_ci	}
36862306a36Sopenharmony_ci	rc->dev.parent = &dev->pci->dev;
36962306a36Sopenharmony_ci	rc->allowed_protocols = allowed_protos;
37062306a36Sopenharmony_ci	rc->priv = kernel_ir;
37162306a36Sopenharmony_ci	rc->open = cx23885_input_ir_open;
37262306a36Sopenharmony_ci	rc->close = cx23885_input_ir_close;
37362306a36Sopenharmony_ci	rc->map_name = rc_map;
37462306a36Sopenharmony_ci	rc->driver_name = MODULE_NAME;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	/* Go */
37762306a36Sopenharmony_ci	dev->kernel_ir = kernel_ir;
37862306a36Sopenharmony_ci	ret = rc_register_device(rc);
37962306a36Sopenharmony_ci	if (ret)
38062306a36Sopenharmony_ci		goto err_out_stop;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	return 0;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cierr_out_stop:
38562306a36Sopenharmony_ci	cx23885_input_ir_stop(dev);
38662306a36Sopenharmony_ci	dev->kernel_ir = NULL;
38762306a36Sopenharmony_ci	rc_free_device(rc);
38862306a36Sopenharmony_cierr_out_free_phys:
38962306a36Sopenharmony_ci	kfree(kernel_ir->phys);
39062306a36Sopenharmony_cierr_out_free_name:
39162306a36Sopenharmony_ci	kfree(kernel_ir->name);
39262306a36Sopenharmony_cierr_out_free:
39362306a36Sopenharmony_ci	kfree(kernel_ir);
39462306a36Sopenharmony_ci	return ret;
39562306a36Sopenharmony_ci}
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_civoid cx23885_input_fini(struct cx23885_dev *dev)
39862306a36Sopenharmony_ci{
39962306a36Sopenharmony_ci	/* Always stop the IR hardware from generating interrupts */
40062306a36Sopenharmony_ci	cx23885_input_ir_stop(dev);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	if (dev->kernel_ir == NULL)
40362306a36Sopenharmony_ci		return;
40462306a36Sopenharmony_ci	rc_unregister_device(dev->kernel_ir->rc);
40562306a36Sopenharmony_ci	kfree(dev->kernel_ir->phys);
40662306a36Sopenharmony_ci	kfree(dev->kernel_ir->name);
40762306a36Sopenharmony_ci	kfree(dev->kernel_ir);
40862306a36Sopenharmony_ci	dev->kernel_ir = NULL;
40962306a36Sopenharmony_ci}
410