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 */ |
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(dev: rcdev, protocol: proto, scancode, toggle: toggle != 0); |
68 | } |
69 | } |
70 | |
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, type: COMTYPE_PIDFILTER, com: SetIR, num: 1, |
76 | av7110->ir.ir_config); |
77 | } |
78 | |
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 | |
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(buf: av7110->ir.input_phys, size: sizeof(av7110->ir.input_phys), |
118 | fmt: "pci-%s/ir0" , pci_name(pdev: 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(dev: rcdev); |
144 | if (ret) { |
145 | av7110->ir.rcdev = NULL; |
146 | rc_free_device(dev: rcdev); |
147 | } |
148 | |
149 | return ret; |
150 | } |
151 | |
152 | void av7110_ir_exit(struct av7110 *av7110) |
153 | { |
154 | rc_unregister_device(dev: av7110->ir.rcdev); |
155 | } |
156 | |
157 | //MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>, Oliver Endriss <o.endriss@gmx.de>"); |
158 | //MODULE_LICENSE("GPL"); |
159 | |