Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | #include <linux/types.h> |
2 | #include <linux/init.h> | |
3 | #include <linux/module.h> | |
4 | #include <linux/moduleparam.h> | |
5 | #include <linux/input.h> | |
6 | #include <linux/proc_fs.h> | |
7 | #include <asm/bitops.h> | |
8 | ||
9 | #include "av7110.h" | |
03388ae3 | 10 | #include "av7110_hw.h" |
1da177e4 | 11 | |
03388ae3 | 12 | #define UP_TIMEOUT (HZ*7/25) |
1da177e4 | 13 | |
3dbce49e | 14 | /* enable ir debugging by or'ing debug with 16 */ |
1da177e4 | 15 | |
03388ae3 OE |
16 | static int av_cnt; |
17 | static struct av7110 *av_list[4]; | |
b7df3910 | 18 | static struct input_dev *input_dev; |
357a268d | 19 | static char input_phys[32]; |
1da177e4 | 20 | |
26a0f5db NI |
21 | static u8 delay_timer_finished; |
22 | ||
1da177e4 LT |
23 | static u16 key_map [256] = { |
24 | KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, | |
25 | KEY_8, KEY_9, KEY_BACK, 0, KEY_POWER, KEY_MUTE, 0, KEY_INFO, | |
26 | KEY_VOLUMEUP, KEY_VOLUMEDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
27 | KEY_CHANNELUP, KEY_CHANNELDOWN, 0, 0, 0, 0, 0, 0, | |
28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
29 | 0, 0, 0, 0, KEY_TEXT, 0, 0, KEY_TV, 0, 0, 0, 0, 0, KEY_SETUP, 0, 0, | |
30 | 0, 0, 0, KEY_SUBTITLE, 0, 0, KEY_LANGUAGE, 0, | |
31 | KEY_RADIO, 0, 0, 0, 0, KEY_EXIT, 0, 0, | |
32 | KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_OK, 0, 0, 0, | |
33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RED, KEY_GREEN, KEY_YELLOW, | |
34 | KEY_BLUE, 0, 0, 0, 0, 0, 0, 0, KEY_MENU, KEY_LIST, 0, 0, 0, 0, 0, 0, | |
35 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
36 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
37 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
38 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
39 | 0, 0, 0, 0, KEY_UP, KEY_UP, KEY_DOWN, KEY_DOWN, | |
40 | 0, 0, 0, 0, KEY_EPG, 0, 0, 0, | |
41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_VCR | |
44 | }; | |
45 | ||
46 | ||
47 | static void av7110_emit_keyup(unsigned long data) | |
48 | { | |
b7df3910 | 49 | if (!data || !test_bit(data, input_dev->key)) |
1da177e4 LT |
50 | return; |
51 | ||
b07b4783 DT |
52 | input_report_key(input_dev, data, 0); |
53 | input_sync(input_dev); | |
1da177e4 LT |
54 | } |
55 | ||
56 | ||
57 | static struct timer_list keyup_timer = { .function = av7110_emit_keyup }; | |
58 | ||
59 | ||
03388ae3 | 60 | static void av7110_emit_key(unsigned long parm) |
1da177e4 | 61 | { |
03388ae3 OE |
62 | struct av7110 *av7110 = (struct av7110 *) parm; |
63 | u32 ir_config = av7110->ir_config; | |
64 | u32 ircom = av7110->ir_command; | |
1da177e4 LT |
65 | u8 data; |
66 | u8 addr; | |
67 | static u16 old_toggle = 0; | |
68 | u16 new_toggle; | |
69 | u16 keycode; | |
70 | ||
71 | /* extract device address and data */ | |
03388ae3 OE |
72 | switch (ir_config & 0x0003) { |
73 | case 0: /* RC5: 5 bits device address, 6 bits data */ | |
74 | data = ircom & 0x3f; | |
75 | addr = (ircom >> 6) & 0x1f; | |
76 | break; | |
77 | ||
78 | case 1: /* RCMM: 8(?) bits device address, 8(?) bits data */ | |
1da177e4 LT |
79 | data = ircom & 0xff; |
80 | addr = (ircom >> 8) & 0xff; | |
03388ae3 OE |
81 | break; |
82 | ||
83 | case 2: /* extended RC5: 5 bits device address, 7 bits data */ | |
1da177e4 LT |
84 | data = ircom & 0x3f; |
85 | addr = (ircom >> 6) & 0x1f; | |
03388ae3 OE |
86 | /* invert 7th data bit for backward compatibility with RC5 keymaps */ |
87 | if (!(ircom & 0x1000)) | |
88 | data |= 0x40; | |
89 | break; | |
90 | ||
91 | default: | |
92 | printk("invalid ir_config %x\n", ir_config); | |
93 | return; | |
1da177e4 LT |
94 | } |
95 | ||
96 | keycode = key_map[data]; | |
97 | ||
03388ae3 | 98 | dprintk(16, "code %08x -> addr %i data 0x%02x -> keycode %i\n", |
1da177e4 LT |
99 | ircom, addr, data, keycode); |
100 | ||
101 | /* check device address (if selected) */ | |
102 | if (ir_config & 0x4000) | |
103 | if (addr != ((ir_config >> 16) & 0xff)) | |
104 | return; | |
105 | ||
106 | if (!keycode) { | |
107 | printk ("%s: unknown key 0x%02x!!\n", __FUNCTION__, data); | |
108 | return; | |
109 | } | |
110 | ||
03388ae3 | 111 | if ((ir_config & 0x0003) == 1) |
1da177e4 LT |
112 | new_toggle = 0; /* RCMM */ |
113 | else | |
03388ae3 | 114 | new_toggle = (ircom & 0x800); /* RC5, extended RC5 */ |
1da177e4 LT |
115 | |
116 | if (timer_pending(&keyup_timer)) { | |
117 | del_timer(&keyup_timer); | |
118 | if (keyup_timer.data != keycode || new_toggle != old_toggle) { | |
26a0f5db | 119 | delay_timer_finished = 0; |
b07b4783 DT |
120 | input_event(input_dev, EV_KEY, keyup_timer.data, 0); |
121 | input_event(input_dev, EV_KEY, keycode, 1); | |
122 | input_sync(input_dev); | |
123 | } else if (delay_timer_finished) { | |
124 | input_event(input_dev, EV_KEY, keycode, 2); | |
125 | input_sync(input_dev); | |
126 | } | |
26a0f5db NI |
127 | } else { |
128 | delay_timer_finished = 0; | |
b07b4783 DT |
129 | input_event(input_dev, EV_KEY, keycode, 1); |
130 | input_sync(input_dev); | |
26a0f5db | 131 | } |
1da177e4 LT |
132 | |
133 | keyup_timer.expires = jiffies + UP_TIMEOUT; | |
134 | keyup_timer.data = keycode; | |
135 | ||
136 | add_timer(&keyup_timer); | |
137 | ||
138 | old_toggle = new_toggle; | |
139 | } | |
140 | ||
141 | static void input_register_keys(void) | |
142 | { | |
143 | int i; | |
144 | ||
b7df3910 | 145 | memset(input_dev->keybit, 0, sizeof(input_dev->keybit)); |
1da177e4 | 146 | |
b7df3910 | 147 | for (i = 0; i < ARRAY_SIZE(key_map); i++) { |
1da177e4 LT |
148 | if (key_map[i] > KEY_MAX) |
149 | key_map[i] = 0; | |
150 | else if (key_map[i] > KEY_RESERVED) | |
b7df3910 | 151 | set_bit(key_map[i], input_dev->keybit); |
1da177e4 LT |
152 | } |
153 | } | |
154 | ||
155 | ||
156 | static void input_repeat_key(unsigned long data) | |
157 | { | |
26a0f5db NI |
158 | /* called by the input driver after rep[REP_DELAY] ms */ |
159 | delay_timer_finished = 1; | |
1da177e4 LT |
160 | } |
161 | ||
162 | ||
a22a6865 AB |
163 | static int av7110_setup_irc_config(struct av7110 *av7110, u32 ir_config) |
164 | { | |
165 | int ret = 0; | |
166 | ||
167 | dprintk(4, "%p\n", av7110); | |
168 | if (av7110) { | |
169 | ret = av7110_fw_cmd(av7110, COMTYPE_PIDFILTER, SetIR, 1, ir_config); | |
170 | av7110->ir_config = ir_config; | |
171 | } | |
172 | return ret; | |
173 | } | |
174 | ||
175 | ||
1da177e4 LT |
176 | static int av7110_ir_write_proc(struct file *file, const char __user *buffer, |
177 | unsigned long count, void *data) | |
178 | { | |
179 | char *page; | |
180 | int size = 4 + 256 * sizeof(u16); | |
03388ae3 OE |
181 | u32 ir_config; |
182 | int i; | |
1da177e4 LT |
183 | |
184 | if (count < size) | |
185 | return -EINVAL; | |
186 | ||
187 | page = (char *) vmalloc(size); | |
188 | if (!page) | |
189 | return -ENOMEM; | |
190 | ||
191 | if (copy_from_user(page, buffer, size)) { | |
192 | vfree(page); | |
193 | return -EFAULT; | |
194 | } | |
195 | ||
196 | memcpy(&ir_config, page, 4); | |
197 | memcpy(&key_map, page + 4, 256 * sizeof(u16)); | |
198 | vfree(page); | |
03388ae3 OE |
199 | if (FW_VERSION(av_list[0]->arm_app) >= 0x2620 && !(ir_config & 0x0001)) |
200 | ir_config |= 0x0002; /* enable extended RC5 */ | |
201 | for (i = 0; i < av_cnt; i++) | |
202 | av7110_setup_irc_config(av_list[i], ir_config); | |
1da177e4 LT |
203 | input_register_keys(); |
204 | return count; | |
205 | } | |
206 | ||
207 | ||
03388ae3 OE |
208 | static void ir_handler(struct av7110 *av7110, u32 ircom) |
209 | { | |
210 | dprintk(4, "ircommand = %08x\n", ircom); | |
211 | av7110->ir_command = ircom; | |
212 | tasklet_schedule(&av7110->ir_tasklet); | |
213 | } | |
1da177e4 | 214 | |
1da177e4 | 215 | |
11dc3ffa | 216 | int __devinit av7110_ir_init(struct av7110 *av7110) |
03388ae3 OE |
217 | { |
218 | static struct proc_dir_entry *e; | |
b07b4783 | 219 | int err; |
1da177e4 | 220 | |
03388ae3 OE |
221 | if (av_cnt >= sizeof av_list/sizeof av_list[0]) |
222 | return -ENOSPC; | |
1da177e4 | 223 | |
03388ae3 OE |
224 | av7110_setup_irc_config(av7110, 0x0001); |
225 | av_list[av_cnt++] = av7110; | |
1da177e4 | 226 | |
03388ae3 OE |
227 | if (av_cnt == 1) { |
228 | init_timer(&keyup_timer); | |
229 | keyup_timer.data = 0; | |
230 | ||
b7df3910 DT |
231 | input_dev = input_allocate_device(); |
232 | if (!input_dev) | |
233 | return -ENOMEM; | |
234 | ||
357a268d MS |
235 | snprintf(input_phys, sizeof(input_phys), |
236 | "pci-%s/ir0", pci_name(av7110->dev->pci)); | |
237 | ||
b7df3910 DT |
238 | input_dev->name = "DVB on-card IR receiver"; |
239 | ||
357a268d MS |
240 | input_dev->phys = input_phys; |
241 | input_dev->id.bustype = BUS_PCI; | |
242 | input_dev->id.version = 1; | |
243 | if (av7110->dev->pci->subsystem_vendor) { | |
244 | input_dev->id.vendor = av7110->dev->pci->subsystem_vendor; | |
245 | input_dev->id.product = av7110->dev->pci->subsystem_device; | |
246 | } else { | |
247 | input_dev->id.vendor = av7110->dev->pci->vendor; | |
248 | input_dev->id.product = av7110->dev->pci->device; | |
249 | } | |
250 | input_dev->cdev.dev = &av7110->dev->pci->dev; | |
b7df3910 DT |
251 | set_bit(EV_KEY, input_dev->evbit); |
252 | set_bit(EV_REP, input_dev->evbit); | |
03388ae3 | 253 | input_register_keys(); |
b07b4783 DT |
254 | err = input_register_device(input_dev); |
255 | if (err) { | |
256 | input_free_device(input_dev); | |
257 | return err; | |
258 | } | |
b7df3910 | 259 | input_dev->timer.function = input_repeat_key; |
03388ae3 OE |
260 | |
261 | e = create_proc_entry("av7110_ir", S_IFREG | S_IRUGO | S_IWUSR, NULL); | |
262 | if (e) { | |
263 | e->write_proc = av7110_ir_write_proc; | |
264 | e->size = 4 + 256 * sizeof(u16); | |
265 | } | |
1da177e4 LT |
266 | } |
267 | ||
03388ae3 OE |
268 | tasklet_init(&av7110->ir_tasklet, av7110_emit_key, (unsigned long) av7110); |
269 | av7110->ir_handler = ir_handler; | |
270 | ||
1da177e4 LT |
271 | return 0; |
272 | } | |
273 | ||
274 | ||
11dc3ffa | 275 | void __devexit av7110_ir_exit(struct av7110 *av7110) |
1da177e4 | 276 | { |
03388ae3 OE |
277 | int i; |
278 | ||
279 | if (av_cnt == 0) | |
1da177e4 | 280 | return; |
03388ae3 OE |
281 | |
282 | av7110->ir_handler = NULL; | |
283 | tasklet_kill(&av7110->ir_tasklet); | |
284 | for (i = 0; i < av_cnt; i++) | |
285 | if (av_list[i] == av7110) { | |
286 | av_list[i] = av_list[av_cnt-1]; | |
287 | av_list[av_cnt-1] = NULL; | |
288 | break; | |
289 | } | |
290 | ||
291 | if (av_cnt == 1) { | |
292 | del_timer_sync(&keyup_timer); | |
293 | remove_proc_entry("av7110_ir", NULL); | |
b7df3910 | 294 | input_unregister_device(input_dev); |
03388ae3 OE |
295 | } |
296 | ||
297 | av_cnt--; | |
1da177e4 LT |
298 | } |
299 | ||
300 | //MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>"); | |
301 | //MODULE_LICENSE("GPL"); |