1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Driver for the remote control of SAA7146 based AV7110 cards
4  *
5  * Copyright (C) 1999-2003 Holger Waechtler <holger@convergence.de>
6  * Copyright (C) 2003-2007 Oliver Endriss <o.endriss@gmx.de>
7  * Copyright (C) 2019 Sean Young <sean@mess.org>
8  */
9 
10 #include <linux/kernel.h>
11 #include <media/rc-core.h>
12 
13 #include "av7110.h"
14 #include "av7110_hw.h"
15 
16 #define IR_RC5		0
17 #define IR_RCMM		1
18 #define IR_RC5_EXT	2 /* internal only */
19 
20 /* interrupt handler */
av7110_ir_handler(struct av7110 * av7110,u32 ircom)21 void av7110_ir_handler(struct av7110 *av7110, u32 ircom)
22 {
23 	struct rc_dev *rcdev = av7110->ir.rcdev;
24 	enum rc_proto proto;
25 	u32 command, addr, scancode;
26 	u32 toggle;
27 
28 	dprintk(4, "ir command = %08x\n", ircom);
29 
30 	if (rcdev) {
31 		switch (av7110->ir.ir_config) {
32 		case IR_RC5: /* RC5: 5 bits device address, 6 bits command */
33 			command = ircom & 0x3f;
34 			addr = (ircom >> 6) & 0x1f;
35 			scancode = RC_SCANCODE_RC5(addr, command);
36 			toggle = ircom & 0x0800;
37 			proto = RC_PROTO_RC5;
38 			break;
39 
40 		case IR_RCMM: /* RCMM: 32 bits scancode */
41 			scancode = ircom & ~0x8000;
42 			toggle = ircom & 0x8000;
43 			proto = RC_PROTO_RCMM32;
44 			break;
45 
46 		case IR_RC5_EXT:
47 			/*
48 			 * extended RC5: 5 bits device address, 7 bits command
49 			 *
50 			 * Extended RC5 uses only one start bit. The second
51 			 * start bit is re-assigned bit 6 of the command bit.
52 			 */
53 			command = ircom & 0x3f;
54 			addr = (ircom >> 6) & 0x1f;
55 			if (!(ircom & 0x1000))
56 				command |= 0x40;
57 			scancode = RC_SCANCODE_RC5(addr, command);
58 			toggle = ircom & 0x0800;
59 			proto = RC_PROTO_RC5;
60 			break;
61 		default:
62 			dprintk(2, "unknown ir config %d\n",
63 				av7110->ir.ir_config);
64 			return;
65 		}
66 
67 		rc_keydown(rcdev, proto, scancode, toggle != 0);
68 	}
69 }
70 
av7110_set_ir_config(struct av7110 * av7110)71 int av7110_set_ir_config(struct av7110 *av7110)
72 {
73 	dprintk(4, "ir config = %08x\n", av7110->ir.ir_config);
74 
75 	return av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1,
76 			     av7110->ir.ir_config);
77 }
78 
change_protocol(struct rc_dev * rcdev,u64 * rc_type)79 static int change_protocol(struct rc_dev *rcdev, u64 *rc_type)
80 {
81 	struct av7110 *av7110 = rcdev->priv;
82 	u32 ir_config;
83 
84 	if (*rc_type & RC_PROTO_BIT_RCMM32) {
85 		ir_config = IR_RCMM;
86 		*rc_type = RC_PROTO_BIT_RCMM32;
87 	} else if (*rc_type & RC_PROTO_BIT_RC5) {
88 		if (FW_VERSION(av7110->arm_app) >= 0x2620)
89 			ir_config = IR_RC5_EXT;
90 		else
91 			ir_config = IR_RC5;
92 		*rc_type = RC_PROTO_BIT_RC5;
93 	} else {
94 		return -EINVAL;
95 	}
96 
97 	if (ir_config == av7110->ir.ir_config)
98 		return 0;
99 
100 	av7110->ir.ir_config = ir_config;
101 
102 	return av7110_set_ir_config(av7110);
103 }
104 
av7110_ir_init(struct av7110 * av7110)105 int av7110_ir_init(struct av7110 *av7110)
106 {
107 	struct rc_dev *rcdev;
108 	struct pci_dev *pci;
109 	int ret;
110 
111 	rcdev = rc_allocate_device(RC_DRIVER_SCANCODE);
112 	if (!rcdev)
113 		return -ENOMEM;
114 
115 	pci = av7110->dev->pci;
116 
117 	snprintf(av7110->ir.input_phys, sizeof(av7110->ir.input_phys),
118 		 "pci-%s/ir0", pci_name(pci));
119 
120 	rcdev->device_name = av7110->card_name;
121 	rcdev->driver_name = KBUILD_MODNAME;
122 	rcdev->input_phys = av7110->ir.input_phys;
123 	rcdev->input_id.bustype = BUS_PCI;
124 	rcdev->input_id.version = 2;
125 	if (pci->subsystem_vendor) {
126 		rcdev->input_id.vendor	= pci->subsystem_vendor;
127 		rcdev->input_id.product = pci->subsystem_device;
128 	} else {
129 		rcdev->input_id.vendor	= pci->vendor;
130 		rcdev->input_id.product = pci->device;
131 	}
132 
133 	rcdev->dev.parent = &pci->dev;
134 	rcdev->allowed_protocols = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RCMM32;
135 	rcdev->change_protocol = change_protocol;
136 	rcdev->map_name = RC_MAP_HAUPPAUGE;
137 	rcdev->priv = av7110;
138 
139 	av7110->ir.rcdev = rcdev;
140 	av7110->ir.ir_config = IR_RC5;
141 	av7110_set_ir_config(av7110);
142 
143 	ret = rc_register_device(rcdev);
144 	if (ret) {
145 		av7110->ir.rcdev = NULL;
146 		rc_free_device(rcdev);
147 	}
148 
149 	return ret;
150 }
151 
av7110_ir_exit(struct av7110 * av7110)152 void av7110_ir_exit(struct av7110 *av7110)
153 {
154 	rc_unregister_device(av7110->ir.rcdev);
155 }
156 
157 //MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>");
158 //MODULE_LICENSE("GPL");
159