1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for the Conexant CX23885/7/8 PCIe bridge |
4 | * |
5 | * Infrared remote control input device |
6 | * |
7 | * Most of this file is |
8 | * |
9 | * Copyright (C) 2009 Andy Walls <awalls@md.metrocast.net> |
10 | * |
11 | * However, the cx23885_input_{init,fini} functions contained herein are |
12 | * derived from Linux kernel files linux/media/video/.../...-input.c marked as: |
13 | * |
14 | * Copyright (C) 2008 <srinivasa.deevi at conexant dot com> |
15 | * Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it> |
16 | * Markus Rechberger <mrechberger@gmail.com> |
17 | * Mauro Carvalho Chehab <mchehab@kernel.org> |
18 | * Sascha Sommer <saschasommer@freenet.de> |
19 | * Copyright (C) 2004, 2005 Chris Pascoe |
20 | * Copyright (C) 2003, 2004 Gerd Knorr |
21 | * Copyright (C) 2003 Pavel Machek |
22 | */ |
23 | |
24 | #include "cx23885.h" |
25 | #include "cx23885-input.h" |
26 | |
27 | #include <linux/slab.h> |
28 | #include <media/rc-core.h> |
29 | #include <media/v4l2-subdev.h> |
30 | |
31 | #define MODULE_NAME "cx23885" |
32 | |
33 | static void cx23885_input_process_measurements(struct cx23885_dev *dev, |
34 | bool overrun) |
35 | { |
36 | struct cx23885_kernel_ir *kernel_ir = dev->kernel_ir; |
37 | |
38 | ssize_t num; |
39 | int count, i; |
40 | bool handle = false; |
41 | struct ir_raw_event ir_core_event[64]; |
42 | |
43 | do { |
44 | num = 0; |
45 | v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) ir_core_event, |
46 | sizeof(ir_core_event), &num); |
47 | |
48 | count = num / sizeof(struct ir_raw_event); |
49 | |
50 | for (i = 0; i < count; i++) { |
51 | ir_raw_event_store(dev: kernel_ir->rc, |
52 | ev: &ir_core_event[i]); |
53 | handle = true; |
54 | } |
55 | } while (num != 0); |
56 | |
57 | if (overrun) |
58 | ir_raw_event_overflow(dev: kernel_ir->rc); |
59 | else if (handle) |
60 | ir_raw_event_handle(dev: kernel_ir->rc); |
61 | } |
62 | |
63 | void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events) |
64 | { |
65 | struct v4l2_subdev_ir_parameters params; |
66 | int overrun, data_available; |
67 | |
68 | if (dev->sd_ir == NULL || events == 0) |
69 | return; |
70 | |
71 | switch (dev->board) { |
72 | case CX23885_BOARD_HAUPPAUGE_HVR1270: |
73 | case CX23885_BOARD_HAUPPAUGE_HVR1850: |
74 | case CX23885_BOARD_HAUPPAUGE_HVR1290: |
75 | case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: |
76 | case CX23885_BOARD_TEVII_S470: |
77 | case CX23885_BOARD_HAUPPAUGE_HVR1250: |
78 | case CX23885_BOARD_MYGICA_X8507: |
79 | case CX23885_BOARD_TBS_6980: |
80 | case CX23885_BOARD_TBS_6981: |
81 | case CX23885_BOARD_DVBSKY_T9580: |
82 | case CX23885_BOARD_DVBSKY_T980C: |
83 | case CX23885_BOARD_DVBSKY_S950C: |
84 | case CX23885_BOARD_TT_CT2_4500_CI: |
85 | case CX23885_BOARD_DVBSKY_S950: |
86 | case CX23885_BOARD_DVBSKY_S952: |
87 | case CX23885_BOARD_DVBSKY_T982: |
88 | case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: |
89 | /* |
90 | * The only boards we handle right now. However other boards |
91 | * using the CX2388x integrated IR controller should be similar |
92 | */ |
93 | break; |
94 | default: |
95 | return; |
96 | } |
97 | |
98 | overrun = events & (V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN | |
99 | V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN); |
100 | |
101 | data_available = events & (V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED | |
102 | V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ); |
103 | |
104 | if (overrun) { |
105 | /* If there was a FIFO overrun, stop the device */ |
106 | v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); |
107 | params.enable = false; |
108 | /* Mitigate race with cx23885_input_ir_stop() */ |
109 | params.shutdown = atomic_read(v: &dev->ir_input_stopping); |
110 | v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); |
111 | } |
112 | |
113 | if (data_available) |
114 | cx23885_input_process_measurements(dev, overrun); |
115 | |
116 | if (overrun) { |
117 | /* If there was a FIFO overrun, clear & restart the device */ |
118 | params.enable = true; |
119 | /* Mitigate race with cx23885_input_ir_stop() */ |
120 | params.shutdown = atomic_read(v: &dev->ir_input_stopping); |
121 | v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); |
122 | } |
123 | } |
124 | |
125 | static int cx23885_input_ir_start(struct cx23885_dev *dev) |
126 | { |
127 | struct v4l2_subdev_ir_parameters params; |
128 | |
129 | if (dev->sd_ir == NULL) |
130 | return -ENODEV; |
131 | |
132 | atomic_set(v: &dev->ir_input_stopping, i: 0); |
133 | |
134 | v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); |
135 | switch (dev->board) { |
136 | case CX23885_BOARD_HAUPPAUGE_HVR1270: |
137 | case CX23885_BOARD_HAUPPAUGE_HVR1850: |
138 | case CX23885_BOARD_HAUPPAUGE_HVR1290: |
139 | case CX23885_BOARD_HAUPPAUGE_HVR1250: |
140 | case CX23885_BOARD_MYGICA_X8507: |
141 | case CX23885_BOARD_DVBSKY_T9580: |
142 | case CX23885_BOARD_DVBSKY_T980C: |
143 | case CX23885_BOARD_DVBSKY_S950C: |
144 | case CX23885_BOARD_TT_CT2_4500_CI: |
145 | case CX23885_BOARD_DVBSKY_S950: |
146 | case CX23885_BOARD_DVBSKY_S952: |
147 | case CX23885_BOARD_DVBSKY_T982: |
148 | case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: |
149 | /* |
150 | * The IR controller on this board only returns pulse widths. |
151 | * Any other mode setting will fail to set up the device. |
152 | */ |
153 | params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; |
154 | params.enable = true; |
155 | params.interrupt_enable = true; |
156 | params.shutdown = false; |
157 | |
158 | /* Setup for baseband compatible with both RC-5 and RC-6A */ |
159 | params.modulation = false; |
160 | /* RC-5: 2,222,222 ns = 1/36 kHz * 32 cycles * 2 marks * 1.25*/ |
161 | /* RC-6A: 3,333,333 ns = 1/36 kHz * 16 cycles * 6 marks * 1.25*/ |
162 | params.max_pulse_width = 3333333; /* ns */ |
163 | /* RC-5: 666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */ |
164 | /* RC-6A: 333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */ |
165 | params.noise_filter_min_width = 333333; /* ns */ |
166 | /* |
167 | * This board has inverted receive sense: |
168 | * mark is received as low logic level; |
169 | * falling edges are detected as rising edges; etc. |
170 | */ |
171 | params.invert_level = true; |
172 | break; |
173 | case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: |
174 | case CX23885_BOARD_TEVII_S470: |
175 | case CX23885_BOARD_TBS_6980: |
176 | case CX23885_BOARD_TBS_6981: |
177 | /* |
178 | * The IR controller on this board only returns pulse widths. |
179 | * Any other mode setting will fail to set up the device. |
180 | */ |
181 | params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH; |
182 | params.enable = true; |
183 | params.interrupt_enable = true; |
184 | params.shutdown = false; |
185 | |
186 | /* Setup for a standard NEC protocol */ |
187 | params.carrier_freq = 37917; /* Hz, 455 kHz/12 for NEC */ |
188 | params.carrier_range_lower = 33000; /* Hz */ |
189 | params.carrier_range_upper = 43000; /* Hz */ |
190 | params.duty_cycle = 33; /* percent, 33 percent for NEC */ |
191 | |
192 | /* |
193 | * NEC max pulse width: (64/3)/(455 kHz/12) * 16 nec_units |
194 | * (64/3)/(455 kHz/12) * 16 nec_units * 1.375 = 12378022 ns |
195 | */ |
196 | params.max_pulse_width = 12378022; /* ns */ |
197 | |
198 | /* |
199 | * NEC noise filter min width: (64/3)/(455 kHz/12) * 1 nec_unit |
200 | * (64/3)/(455 kHz/12) * 1 nec_units * 0.625 = 351648 ns |
201 | */ |
202 | params.noise_filter_min_width = 351648; /* ns */ |
203 | |
204 | params.modulation = false; |
205 | params.invert_level = true; |
206 | break; |
207 | } |
208 | v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); |
209 | return 0; |
210 | } |
211 | |
212 | static int cx23885_input_ir_open(struct rc_dev *rc) |
213 | { |
214 | struct cx23885_kernel_ir *kernel_ir = rc->priv; |
215 | |
216 | if (kernel_ir->cx == NULL) |
217 | return -ENODEV; |
218 | |
219 | return cx23885_input_ir_start(dev: kernel_ir->cx); |
220 | } |
221 | |
222 | static void cx23885_input_ir_stop(struct cx23885_dev *dev) |
223 | { |
224 | struct v4l2_subdev_ir_parameters params; |
225 | |
226 | if (dev->sd_ir == NULL) |
227 | return; |
228 | |
229 | /* |
230 | * Stop the sd_ir subdevice from generating notifications and |
231 | * scheduling work. |
232 | * It is shutdown this way in order to mitigate a race with |
233 | * cx23885_input_rx_work_handler() in the overrun case, which could |
234 | * re-enable the subdevice. |
235 | */ |
236 | atomic_set(v: &dev->ir_input_stopping, i: 1); |
237 | v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); |
238 | while (params.shutdown == false) { |
239 | params.enable = false; |
240 | params.interrupt_enable = false; |
241 | params.shutdown = true; |
242 | v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, ¶ms); |
243 | v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, ¶ms); |
244 | } |
245 | flush_work(work: &dev->cx25840_work); |
246 | flush_work(work: &dev->ir_rx_work); |
247 | flush_work(work: &dev->ir_tx_work); |
248 | } |
249 | |
250 | static void cx23885_input_ir_close(struct rc_dev *rc) |
251 | { |
252 | struct cx23885_kernel_ir *kernel_ir = rc->priv; |
253 | |
254 | if (kernel_ir->cx != NULL) |
255 | cx23885_input_ir_stop(dev: kernel_ir->cx); |
256 | } |
257 | |
258 | int cx23885_input_init(struct cx23885_dev *dev) |
259 | { |
260 | struct cx23885_kernel_ir *kernel_ir; |
261 | struct rc_dev *rc; |
262 | char *rc_map; |
263 | u64 allowed_protos; |
264 | |
265 | int ret; |
266 | |
267 | /* |
268 | * If the IR device (hardware registers, chip, GPIO lines, etc.) isn't |
269 | * encapsulated in a v4l2_subdev, then I'm not going to deal with it. |
270 | */ |
271 | if (dev->sd_ir == NULL) |
272 | return -ENODEV; |
273 | |
274 | switch (dev->board) { |
275 | case CX23885_BOARD_HAUPPAUGE_HVR1270: |
276 | case CX23885_BOARD_HAUPPAUGE_HVR1850: |
277 | case CX23885_BOARD_HAUPPAUGE_HVR1290: |
278 | case CX23885_BOARD_HAUPPAUGE_HVR1250: |
279 | case CX23885_BOARD_HAUPPAUGE_HVR1265_K4: |
280 | /* Integrated CX2388[58] IR controller */ |
281 | allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; |
282 | /* The grey Hauppauge RC-5 remote */ |
283 | rc_map = RC_MAP_HAUPPAUGE; |
284 | break; |
285 | case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL: |
286 | /* Integrated CX23885 IR controller */ |
287 | allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; |
288 | /* The grey Terratec remote with orange buttons */ |
289 | rc_map = RC_MAP_NEC_TERRATEC_CINERGY_XS; |
290 | break; |
291 | case CX23885_BOARD_TEVII_S470: |
292 | /* Integrated CX23885 IR controller */ |
293 | allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; |
294 | /* A guess at the remote */ |
295 | rc_map = RC_MAP_TEVII_NEC; |
296 | break; |
297 | case CX23885_BOARD_MYGICA_X8507: |
298 | /* Integrated CX23885 IR controller */ |
299 | allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; |
300 | /* A guess at the remote */ |
301 | rc_map = RC_MAP_TOTAL_MEDIA_IN_HAND_02; |
302 | break; |
303 | case CX23885_BOARD_TBS_6980: |
304 | case CX23885_BOARD_TBS_6981: |
305 | /* Integrated CX23885 IR controller */ |
306 | allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; |
307 | /* A guess at the remote */ |
308 | rc_map = RC_MAP_TBS_NEC; |
309 | break; |
310 | case CX23885_BOARD_DVBSKY_T9580: |
311 | case CX23885_BOARD_DVBSKY_T980C: |
312 | case CX23885_BOARD_DVBSKY_S950C: |
313 | case CX23885_BOARD_DVBSKY_S950: |
314 | case CX23885_BOARD_DVBSKY_S952: |
315 | case CX23885_BOARD_DVBSKY_T982: |
316 | /* Integrated CX23885 IR controller */ |
317 | allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; |
318 | rc_map = RC_MAP_DVBSKY; |
319 | break; |
320 | case CX23885_BOARD_TT_CT2_4500_CI: |
321 | /* Integrated CX23885 IR controller */ |
322 | allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER; |
323 | rc_map = RC_MAP_TT_1500; |
324 | break; |
325 | default: |
326 | return -ENODEV; |
327 | } |
328 | |
329 | /* cx23885 board instance kernel IR state */ |
330 | kernel_ir = kzalloc(size: sizeof(struct cx23885_kernel_ir), GFP_KERNEL); |
331 | if (kernel_ir == NULL) |
332 | return -ENOMEM; |
333 | |
334 | kernel_ir->cx = dev; |
335 | kernel_ir->name = kasprintf(GFP_KERNEL, fmt: "cx23885 IR (%s)" , |
336 | cx23885_boards[dev->board].name); |
337 | if (!kernel_ir->name) { |
338 | ret = -ENOMEM; |
339 | goto err_out_free; |
340 | } |
341 | |
342 | kernel_ir->phys = kasprintf(GFP_KERNEL, fmt: "pci-%s/ir0" , |
343 | pci_name(pdev: dev->pci)); |
344 | if (!kernel_ir->phys) { |
345 | ret = -ENOMEM; |
346 | goto err_out_free_name; |
347 | } |
348 | |
349 | /* input device */ |
350 | rc = rc_allocate_device(RC_DRIVER_IR_RAW); |
351 | if (!rc) { |
352 | ret = -ENOMEM; |
353 | goto err_out_free_phys; |
354 | } |
355 | |
356 | kernel_ir->rc = rc; |
357 | rc->device_name = kernel_ir->name; |
358 | rc->input_phys = kernel_ir->phys; |
359 | rc->input_id.bustype = BUS_PCI; |
360 | rc->input_id.version = 1; |
361 | if (dev->pci->subsystem_vendor) { |
362 | rc->input_id.vendor = dev->pci->subsystem_vendor; |
363 | rc->input_id.product = dev->pci->subsystem_device; |
364 | } else { |
365 | rc->input_id.vendor = dev->pci->vendor; |
366 | rc->input_id.product = dev->pci->device; |
367 | } |
368 | rc->dev.parent = &dev->pci->dev; |
369 | rc->allowed_protocols = allowed_protos; |
370 | rc->priv = kernel_ir; |
371 | rc->open = cx23885_input_ir_open; |
372 | rc->close = cx23885_input_ir_close; |
373 | rc->map_name = rc_map; |
374 | rc->driver_name = MODULE_NAME; |
375 | |
376 | /* Go */ |
377 | dev->kernel_ir = kernel_ir; |
378 | ret = rc_register_device(dev: rc); |
379 | if (ret) |
380 | goto err_out_stop; |
381 | |
382 | return 0; |
383 | |
384 | err_out_stop: |
385 | cx23885_input_ir_stop(dev); |
386 | dev->kernel_ir = NULL; |
387 | rc_free_device(dev: rc); |
388 | err_out_free_phys: |
389 | kfree(objp: kernel_ir->phys); |
390 | err_out_free_name: |
391 | kfree(objp: kernel_ir->name); |
392 | err_out_free: |
393 | kfree(objp: kernel_ir); |
394 | return ret; |
395 | } |
396 | |
397 | void cx23885_input_fini(struct cx23885_dev *dev) |
398 | { |
399 | /* Always stop the IR hardware from generating interrupts */ |
400 | cx23885_input_ir_stop(dev); |
401 | |
402 | if (dev->kernel_ir == NULL) |
403 | return; |
404 | rc_unregister_device(dev: dev->kernel_ir->rc); |
405 | kfree(objp: dev->kernel_ir->phys); |
406 | kfree(objp: dev->kernel_ir->name); |
407 | kfree(objp: dev->kernel_ir); |
408 | dev->kernel_ir = NULL; |
409 | } |
410 | |