1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Streamzap Remote Control driver |
4 | * |
5 | * Copyright (c) 2005 Christoph Bartelmus <lirc@bartelmus.de> |
6 | * Copyright (c) 2010 Jarod Wilson <jarod@wilsonet.com> |
7 | * |
8 | * This driver was based on the work of Greg Wickham and Adrian |
9 | * Dewhurst. It was substantially rewritten to support correct signal |
10 | * gaps and now maintains a delay buffer, which is used to present |
11 | * consistent timing behaviour to user space applications. Without the |
12 | * delay buffer an ugly hack would be required in lircd, which can |
13 | * cause sluggish signal decoding in certain situations. |
14 | * |
15 | * Ported to in-kernel ir-core interface by Jarod Wilson |
16 | * |
17 | * This driver is based on the USB skeleton driver packaged with the |
18 | * kernel; copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.com) |
19 | */ |
20 | |
21 | #include <linux/device.h> |
22 | #include <linux/module.h> |
23 | #include <linux/slab.h> |
24 | #include <linux/usb.h> |
25 | #include <linux/usb/input.h> |
26 | #include <media/rc-core.h> |
27 | |
28 | #define DRIVER_NAME "streamzap" |
29 | #define DRIVER_DESC "Streamzap Remote Control driver" |
30 | |
31 | #define USB_STREAMZAP_VENDOR_ID 0x0e9c |
32 | #define USB_STREAMZAP_PRODUCT_ID 0x0000 |
33 | |
34 | /* table of devices that work with this driver */ |
35 | static const struct usb_device_id streamzap_table[] = { |
36 | /* Streamzap Remote Control */ |
37 | { USB_DEVICE(USB_STREAMZAP_VENDOR_ID, USB_STREAMZAP_PRODUCT_ID) }, |
38 | /* Terminating entry */ |
39 | { } |
40 | }; |
41 | |
42 | MODULE_DEVICE_TABLE(usb, streamzap_table); |
43 | |
44 | #define SZ_PULSE_MASK 0xf0 |
45 | #define SZ_SPACE_MASK 0x0f |
46 | #define SZ_TIMEOUT 0xff |
47 | #define SZ_RESOLUTION 256 |
48 | |
49 | /* number of samples buffered */ |
50 | #define SZ_BUF_LEN 128 |
51 | |
52 | enum StreamzapDecoderState { |
53 | PulseSpace, |
54 | FullPulse, |
55 | FullSpace, |
56 | IgnorePulse |
57 | }; |
58 | |
59 | /* structure to hold our device specific stuff */ |
60 | struct streamzap_ir { |
61 | /* ir-core */ |
62 | struct rc_dev *rdev; |
63 | |
64 | /* core device info */ |
65 | struct device *dev; |
66 | |
67 | /* usb */ |
68 | struct urb *urb_in; |
69 | |
70 | /* buffer & dma */ |
71 | unsigned char *buf_in; |
72 | dma_addr_t dma_in; |
73 | unsigned int buf_in_len; |
74 | |
75 | /* track what state we're in */ |
76 | enum StreamzapDecoderState decoder_state; |
77 | |
78 | char phys[64]; |
79 | }; |
80 | |
81 | |
82 | /* local function prototypes */ |
83 | static int streamzap_probe(struct usb_interface *interface, |
84 | const struct usb_device_id *id); |
85 | static void streamzap_disconnect(struct usb_interface *interface); |
86 | static void streamzap_callback(struct urb *urb); |
87 | static int streamzap_suspend(struct usb_interface *intf, pm_message_t message); |
88 | static int streamzap_resume(struct usb_interface *intf); |
89 | |
90 | /* usb specific object needed to register this driver with the usb subsystem */ |
91 | static struct usb_driver streamzap_driver = { |
92 | .name = DRIVER_NAME, |
93 | .probe = streamzap_probe, |
94 | .disconnect = streamzap_disconnect, |
95 | .suspend = streamzap_suspend, |
96 | .resume = streamzap_resume, |
97 | .id_table = streamzap_table, |
98 | }; |
99 | |
100 | static void sz_push(struct streamzap_ir *sz, struct ir_raw_event rawir) |
101 | { |
102 | dev_dbg(sz->dev, "Storing %s with duration %u us\n" , |
103 | (rawir.pulse ? "pulse" : "space" ), rawir.duration); |
104 | ir_raw_event_store_with_filter(dev: sz->rdev, ev: &rawir); |
105 | } |
106 | |
107 | static void sz_push_full_pulse(struct streamzap_ir *sz, |
108 | unsigned char value) |
109 | { |
110 | struct ir_raw_event rawir = { |
111 | .pulse = true, |
112 | .duration = value * SZ_RESOLUTION + SZ_RESOLUTION / 2, |
113 | }; |
114 | |
115 | sz_push(sz, rawir); |
116 | } |
117 | |
118 | static void sz_push_half_pulse(struct streamzap_ir *sz, |
119 | unsigned char value) |
120 | { |
121 | sz_push_full_pulse(sz, value: (value & SZ_PULSE_MASK) >> 4); |
122 | } |
123 | |
124 | static void sz_push_full_space(struct streamzap_ir *sz, |
125 | unsigned char value) |
126 | { |
127 | struct ir_raw_event rawir = { |
128 | .pulse = false, |
129 | .duration = value * SZ_RESOLUTION + SZ_RESOLUTION / 2, |
130 | }; |
131 | |
132 | sz_push(sz, rawir); |
133 | } |
134 | |
135 | static void sz_push_half_space(struct streamzap_ir *sz, |
136 | unsigned long value) |
137 | { |
138 | sz_push_full_space(sz, value: value & SZ_SPACE_MASK); |
139 | } |
140 | |
141 | /* |
142 | * streamzap_callback - usb IRQ handler callback |
143 | * |
144 | * This procedure is invoked on reception of data from |
145 | * the usb remote. |
146 | */ |
147 | static void streamzap_callback(struct urb *urb) |
148 | { |
149 | struct streamzap_ir *sz; |
150 | unsigned int i; |
151 | int len; |
152 | |
153 | if (!urb) |
154 | return; |
155 | |
156 | sz = urb->context; |
157 | len = urb->actual_length; |
158 | |
159 | switch (urb->status) { |
160 | case -ECONNRESET: |
161 | case -ENOENT: |
162 | case -ESHUTDOWN: |
163 | /* |
164 | * this urb is terminated, clean up. |
165 | * sz might already be invalid at this point |
166 | */ |
167 | dev_err(sz->dev, "urb terminated, status: %d\n" , urb->status); |
168 | return; |
169 | default: |
170 | break; |
171 | } |
172 | |
173 | dev_dbg(sz->dev, "%s: received urb, len %d\n" , __func__, len); |
174 | for (i = 0; i < len; i++) { |
175 | dev_dbg(sz->dev, "sz->buf_in[%d]: %x\n" , |
176 | i, (unsigned char)sz->buf_in[i]); |
177 | switch (sz->decoder_state) { |
178 | case PulseSpace: |
179 | if ((sz->buf_in[i] & SZ_PULSE_MASK) == |
180 | SZ_PULSE_MASK) { |
181 | sz->decoder_state = FullPulse; |
182 | continue; |
183 | } else if ((sz->buf_in[i] & SZ_SPACE_MASK) |
184 | == SZ_SPACE_MASK) { |
185 | sz_push_half_pulse(sz, value: sz->buf_in[i]); |
186 | sz->decoder_state = FullSpace; |
187 | continue; |
188 | } else { |
189 | sz_push_half_pulse(sz, value: sz->buf_in[i]); |
190 | sz_push_half_space(sz, value: sz->buf_in[i]); |
191 | } |
192 | break; |
193 | case FullPulse: |
194 | sz_push_full_pulse(sz, value: sz->buf_in[i]); |
195 | sz->decoder_state = IgnorePulse; |
196 | break; |
197 | case FullSpace: |
198 | if (sz->buf_in[i] == SZ_TIMEOUT) { |
199 | struct ir_raw_event rawir = { |
200 | .pulse = false, |
201 | .duration = sz->rdev->timeout |
202 | }; |
203 | sz_push(sz, rawir); |
204 | } else { |
205 | sz_push_full_space(sz, value: sz->buf_in[i]); |
206 | } |
207 | sz->decoder_state = PulseSpace; |
208 | break; |
209 | case IgnorePulse: |
210 | if ((sz->buf_in[i] & SZ_SPACE_MASK) == |
211 | SZ_SPACE_MASK) { |
212 | sz->decoder_state = FullSpace; |
213 | continue; |
214 | } |
215 | sz_push_half_space(sz, value: sz->buf_in[i]); |
216 | sz->decoder_state = PulseSpace; |
217 | break; |
218 | } |
219 | } |
220 | |
221 | ir_raw_event_handle(dev: sz->rdev); |
222 | usb_submit_urb(urb, GFP_ATOMIC); |
223 | } |
224 | |
225 | static struct rc_dev *streamzap_init_rc_dev(struct streamzap_ir *sz, |
226 | struct usb_device *usbdev) |
227 | { |
228 | struct rc_dev *rdev; |
229 | struct device *dev = sz->dev; |
230 | int ret; |
231 | |
232 | rdev = rc_allocate_device(RC_DRIVER_IR_RAW); |
233 | if (!rdev) |
234 | goto out; |
235 | |
236 | usb_make_path(dev: usbdev, buf: sz->phys, size: sizeof(sz->phys)); |
237 | strlcat(p: sz->phys, q: "/input0" , avail: sizeof(sz->phys)); |
238 | |
239 | rdev->device_name = "Streamzap PC Remote Infrared Receiver" ; |
240 | rdev->input_phys = sz->phys; |
241 | usb_to_input_id(dev: usbdev, id: &rdev->input_id); |
242 | rdev->dev.parent = dev; |
243 | rdev->priv = sz; |
244 | rdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; |
245 | rdev->driver_name = DRIVER_NAME; |
246 | rdev->map_name = RC_MAP_STREAMZAP; |
247 | rdev->rx_resolution = SZ_RESOLUTION; |
248 | |
249 | ret = rc_register_device(dev: rdev); |
250 | if (ret < 0) { |
251 | dev_err(dev, "remote input device register failed\n" ); |
252 | goto out; |
253 | } |
254 | |
255 | return rdev; |
256 | |
257 | out: |
258 | rc_free_device(dev: rdev); |
259 | return NULL; |
260 | } |
261 | |
262 | /* |
263 | * streamzap_probe |
264 | * |
265 | * Called by usb-core to associated with a candidate device |
266 | * On any failure the return value is the ERROR |
267 | * On success return 0 |
268 | */ |
269 | static int streamzap_probe(struct usb_interface *intf, |
270 | const struct usb_device_id *id) |
271 | { |
272 | struct usb_device *usbdev = interface_to_usbdev(intf); |
273 | struct usb_endpoint_descriptor *endpoint; |
274 | struct usb_host_interface *iface_host; |
275 | struct streamzap_ir *sz = NULL; |
276 | int retval = -ENOMEM; |
277 | int pipe, maxp; |
278 | |
279 | /* Allocate space for device driver specific data */ |
280 | sz = kzalloc(size: sizeof(struct streamzap_ir), GFP_KERNEL); |
281 | if (!sz) |
282 | return -ENOMEM; |
283 | |
284 | /* Check to ensure endpoint information matches requirements */ |
285 | iface_host = intf->cur_altsetting; |
286 | |
287 | if (iface_host->desc.bNumEndpoints != 1) { |
288 | dev_err(&intf->dev, "%s: Unexpected desc.bNumEndpoints (%d)\n" , |
289 | __func__, iface_host->desc.bNumEndpoints); |
290 | retval = -ENODEV; |
291 | goto free_sz; |
292 | } |
293 | |
294 | endpoint = &iface_host->endpoint[0].desc; |
295 | if (!usb_endpoint_dir_in(epd: endpoint)) { |
296 | dev_err(&intf->dev, "%s: endpoint doesn't match input device 02%02x\n" , |
297 | __func__, endpoint->bEndpointAddress); |
298 | retval = -ENODEV; |
299 | goto free_sz; |
300 | } |
301 | |
302 | if (!usb_endpoint_xfer_int(epd: endpoint)) { |
303 | dev_err(&intf->dev, "%s: endpoint attributes don't match xfer 02%02x\n" , |
304 | __func__, endpoint->bmAttributes); |
305 | retval = -ENODEV; |
306 | goto free_sz; |
307 | } |
308 | |
309 | pipe = usb_rcvintpipe(usbdev, endpoint->bEndpointAddress); |
310 | maxp = usb_maxpacket(udev: usbdev, pipe); |
311 | |
312 | if (maxp == 0) { |
313 | dev_err(&intf->dev, "%s: endpoint Max Packet Size is 0!?!\n" , |
314 | __func__); |
315 | retval = -ENODEV; |
316 | goto free_sz; |
317 | } |
318 | |
319 | /* Allocate the USB buffer and IRQ URB */ |
320 | sz->buf_in = usb_alloc_coherent(dev: usbdev, size: maxp, GFP_ATOMIC, dma: &sz->dma_in); |
321 | if (!sz->buf_in) |
322 | goto free_sz; |
323 | |
324 | sz->urb_in = usb_alloc_urb(iso_packets: 0, GFP_KERNEL); |
325 | if (!sz->urb_in) |
326 | goto free_buf_in; |
327 | |
328 | sz->dev = &intf->dev; |
329 | sz->buf_in_len = maxp; |
330 | |
331 | sz->rdev = streamzap_init_rc_dev(sz, usbdev); |
332 | if (!sz->rdev) |
333 | goto rc_dev_fail; |
334 | |
335 | sz->decoder_state = PulseSpace; |
336 | /* FIXME: don't yet have a way to set this */ |
337 | sz->rdev->timeout = SZ_TIMEOUT * SZ_RESOLUTION; |
338 | #if 0 |
339 | /* not yet supported, depends on patches from maxim */ |
340 | /* see also: LIRC_GET_REC_RESOLUTION and LIRC_SET_REC_TIMEOUT */ |
341 | sz->min_timeout = SZ_TIMEOUT * SZ_RESOLUTION; |
342 | sz->max_timeout = SZ_TIMEOUT * SZ_RESOLUTION; |
343 | #endif |
344 | |
345 | /* Complete final initialisations */ |
346 | usb_fill_int_urb(urb: sz->urb_in, dev: usbdev, pipe, transfer_buffer: sz->buf_in, |
347 | buffer_length: maxp, complete_fn: streamzap_callback, context: sz, interval: endpoint->bInterval); |
348 | sz->urb_in->transfer_dma = sz->dma_in; |
349 | sz->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; |
350 | |
351 | usb_set_intfdata(intf, data: sz); |
352 | |
353 | if (usb_submit_urb(urb: sz->urb_in, GFP_ATOMIC)) |
354 | dev_err(sz->dev, "urb submit failed\n" ); |
355 | |
356 | return 0; |
357 | |
358 | rc_dev_fail: |
359 | usb_free_urb(urb: sz->urb_in); |
360 | free_buf_in: |
361 | usb_free_coherent(dev: usbdev, size: maxp, addr: sz->buf_in, dma: sz->dma_in); |
362 | free_sz: |
363 | kfree(objp: sz); |
364 | |
365 | return retval; |
366 | } |
367 | |
368 | /* |
369 | * streamzap_disconnect |
370 | * |
371 | * Called by the usb core when the device is removed from the system. |
372 | * |
373 | * This routine guarantees that the driver will not submit any more urbs |
374 | * by clearing dev->usbdev. It is also supposed to terminate any currently |
375 | * active urbs. Unfortunately, usb_bulk_msg(), used in streamzap_read(), |
376 | * does not provide any way to do this. |
377 | */ |
378 | static void streamzap_disconnect(struct usb_interface *interface) |
379 | { |
380 | struct streamzap_ir *sz = usb_get_intfdata(intf: interface); |
381 | struct usb_device *usbdev = interface_to_usbdev(interface); |
382 | |
383 | usb_set_intfdata(intf: interface, NULL); |
384 | |
385 | if (!sz) |
386 | return; |
387 | |
388 | rc_unregister_device(dev: sz->rdev); |
389 | usb_kill_urb(urb: sz->urb_in); |
390 | usb_free_urb(urb: sz->urb_in); |
391 | usb_free_coherent(dev: usbdev, size: sz->buf_in_len, addr: sz->buf_in, dma: sz->dma_in); |
392 | |
393 | kfree(objp: sz); |
394 | } |
395 | |
396 | static int streamzap_suspend(struct usb_interface *intf, pm_message_t message) |
397 | { |
398 | struct streamzap_ir *sz = usb_get_intfdata(intf); |
399 | |
400 | usb_kill_urb(urb: sz->urb_in); |
401 | |
402 | return 0; |
403 | } |
404 | |
405 | static int streamzap_resume(struct usb_interface *intf) |
406 | { |
407 | struct streamzap_ir *sz = usb_get_intfdata(intf); |
408 | |
409 | if (usb_submit_urb(urb: sz->urb_in, GFP_NOIO)) { |
410 | dev_err(sz->dev, "Error submitting urb\n" ); |
411 | return -EIO; |
412 | } |
413 | |
414 | return 0; |
415 | } |
416 | |
417 | module_usb_driver(streamzap_driver); |
418 | |
419 | MODULE_AUTHOR("Jarod Wilson <jarod@wilsonet.com>" ); |
420 | MODULE_DESCRIPTION(DRIVER_DESC); |
421 | MODULE_LICENSE("GPL" ); |
422 | |