1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * IgorPlug-USB IR Receiver |
4 | * |
5 | * Copyright (C) 2014 Sean Young <sean@mess.org> |
6 | * |
7 | * Supports the standard homebrew IgorPlugUSB receiver with Igor's firmware. |
8 | * See http://www.cesko.host.sk/IgorPlugUSB/IgorPlug-USB%20(AVR)_eng.htm |
9 | * |
10 | * Based on the lirc_igorplugusb.c driver: |
11 | * Copyright (C) 2004 Jan M. Hochstein |
12 | * <hochstein@algo.informatik.tu-darmstadt.de> |
13 | */ |
14 | #include <linux/device.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/usb.h> |
18 | #include <linux/usb/input.h> |
19 | #include <media/rc-core.h> |
20 | |
21 | #define DRIVER_DESC "IgorPlug-USB IR Receiver" |
22 | #define DRIVER_NAME "igorplugusb" |
23 | |
24 | #define 3 |
25 | #define BUFLEN 36 |
26 | #define MAX_PACKET (HEADERLEN + BUFLEN) |
27 | |
28 | #define SET_INFRABUFFER_EMPTY 1 |
29 | #define GET_INFRACODE 2 |
30 | |
31 | |
32 | struct igorplugusb { |
33 | struct rc_dev *rc; |
34 | struct device *dev; |
35 | |
36 | struct urb *urb; |
37 | struct usb_ctrlrequest request; |
38 | |
39 | struct timer_list timer; |
40 | |
41 | u8 *buf_in; |
42 | |
43 | char phys[64]; |
44 | }; |
45 | |
46 | static void igorplugusb_cmd(struct igorplugusb *ir, int cmd); |
47 | |
48 | static void igorplugusb_irdata(struct igorplugusb *ir, unsigned len) |
49 | { |
50 | struct ir_raw_event rawir = {}; |
51 | unsigned i, start, overflow; |
52 | |
53 | dev_dbg(ir->dev, "irdata: %*ph (len=%u)" , len, ir->buf_in, len); |
54 | |
55 | /* |
56 | * If more than 36 pulses and spaces follow each other, the igorplugusb |
57 | * overwrites its buffer from the beginning. The overflow value is the |
58 | * last offset which was not overwritten. Everything from this offset |
59 | * onwards occurred before everything until this offset. |
60 | */ |
61 | overflow = ir->buf_in[2]; |
62 | i = start = overflow + HEADERLEN; |
63 | |
64 | if (start >= len) { |
65 | dev_err(ir->dev, "receive overflow invalid: %u" , overflow); |
66 | } else { |
67 | if (overflow > 0) { |
68 | dev_warn(ir->dev, "receive overflow, at least %u lost" , |
69 | overflow); |
70 | ir_raw_event_overflow(dev: ir->rc); |
71 | } |
72 | |
73 | do { |
74 | rawir.duration = ir->buf_in[i] * 85; |
75 | rawir.pulse = i & 1; |
76 | |
77 | ir_raw_event_store_with_filter(dev: ir->rc, ev: &rawir); |
78 | |
79 | if (++i == len) |
80 | i = HEADERLEN; |
81 | } while (i != start); |
82 | |
83 | /* add a trailing space */ |
84 | rawir.duration = ir->rc->timeout; |
85 | rawir.pulse = false; |
86 | ir_raw_event_store_with_filter(dev: ir->rc, ev: &rawir); |
87 | |
88 | ir_raw_event_handle(dev: ir->rc); |
89 | } |
90 | |
91 | igorplugusb_cmd(ir, SET_INFRABUFFER_EMPTY); |
92 | } |
93 | |
94 | static void igorplugusb_callback(struct urb *urb) |
95 | { |
96 | struct usb_ctrlrequest *req; |
97 | struct igorplugusb *ir = urb->context; |
98 | |
99 | req = (struct usb_ctrlrequest *)urb->setup_packet; |
100 | |
101 | switch (urb->status) { |
102 | case 0: |
103 | if (req->bRequest == GET_INFRACODE && |
104 | urb->actual_length > HEADERLEN) |
105 | igorplugusb_irdata(ir, len: urb->actual_length); |
106 | else /* request IR */ |
107 | mod_timer(timer: &ir->timer, expires: jiffies + msecs_to_jiffies(m: 50)); |
108 | break; |
109 | case -EPROTO: |
110 | case -ECONNRESET: |
111 | case -ENOENT: |
112 | case -ESHUTDOWN: |
113 | return; |
114 | default: |
115 | dev_warn(ir->dev, "Error: urb status = %d\n" , urb->status); |
116 | igorplugusb_cmd(ir, SET_INFRABUFFER_EMPTY); |
117 | break; |
118 | } |
119 | } |
120 | |
121 | static void igorplugusb_cmd(struct igorplugusb *ir, int cmd) |
122 | { |
123 | int ret; |
124 | |
125 | ir->request.bRequest = cmd; |
126 | ir->urb->transfer_flags = 0; |
127 | ret = usb_submit_urb(urb: ir->urb, GFP_ATOMIC); |
128 | if (ret && ret != -EPERM) |
129 | dev_err(ir->dev, "submit urb failed: %d" , ret); |
130 | } |
131 | |
132 | static void igorplugusb_timer(struct timer_list *t) |
133 | { |
134 | struct igorplugusb *ir = from_timer(ir, t, timer); |
135 | |
136 | igorplugusb_cmd(ir, GET_INFRACODE); |
137 | } |
138 | |
139 | static int igorplugusb_probe(struct usb_interface *intf, |
140 | const struct usb_device_id *id) |
141 | { |
142 | struct usb_device *udev; |
143 | struct usb_host_interface *idesc; |
144 | struct usb_endpoint_descriptor *ep; |
145 | struct igorplugusb *ir; |
146 | struct rc_dev *rc; |
147 | int ret = -ENOMEM; |
148 | |
149 | udev = interface_to_usbdev(intf); |
150 | idesc = intf->cur_altsetting; |
151 | |
152 | if (idesc->desc.bNumEndpoints != 1) { |
153 | dev_err(&intf->dev, "incorrect number of endpoints" ); |
154 | return -ENODEV; |
155 | } |
156 | |
157 | ep = &idesc->endpoint[0].desc; |
158 | if (!usb_endpoint_dir_in(epd: ep) || !usb_endpoint_xfer_control(epd: ep)) { |
159 | dev_err(&intf->dev, "endpoint incorrect" ); |
160 | return -ENODEV; |
161 | } |
162 | |
163 | ir = devm_kzalloc(dev: &intf->dev, size: sizeof(*ir), GFP_KERNEL); |
164 | if (!ir) |
165 | return -ENOMEM; |
166 | |
167 | ir->dev = &intf->dev; |
168 | |
169 | timer_setup(&ir->timer, igorplugusb_timer, 0); |
170 | |
171 | ir->request.bRequest = GET_INFRACODE; |
172 | ir->request.bRequestType = USB_TYPE_VENDOR | USB_DIR_IN; |
173 | ir->request.wLength = cpu_to_le16(MAX_PACKET); |
174 | |
175 | ir->urb = usb_alloc_urb(iso_packets: 0, GFP_KERNEL); |
176 | if (!ir->urb) |
177 | goto fail; |
178 | |
179 | ir->buf_in = kmalloc(MAX_PACKET, GFP_KERNEL); |
180 | if (!ir->buf_in) |
181 | goto fail; |
182 | usb_fill_control_urb(urb: ir->urb, dev: udev, |
183 | usb_rcvctrlpipe(udev, 0), setup_packet: (uint8_t *)&ir->request, |
184 | transfer_buffer: ir->buf_in, MAX_PACKET, complete_fn: igorplugusb_callback, context: ir); |
185 | |
186 | usb_make_path(dev: udev, buf: ir->phys, size: sizeof(ir->phys)); |
187 | |
188 | rc = rc_allocate_device(RC_DRIVER_IR_RAW); |
189 | if (!rc) |
190 | goto fail; |
191 | |
192 | rc->device_name = DRIVER_DESC; |
193 | rc->input_phys = ir->phys; |
194 | usb_to_input_id(dev: udev, id: &rc->input_id); |
195 | rc->dev.parent = &intf->dev; |
196 | /* |
197 | * This device can only store 36 pulses + spaces, which is not enough |
198 | * for the NEC protocol and many others. |
199 | */ |
200 | rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER & |
201 | ~(RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX | RC_PROTO_BIT_NEC32 | |
202 | RC_PROTO_BIT_RC6_6A_20 | RC_PROTO_BIT_RC6_6A_24 | |
203 | RC_PROTO_BIT_RC6_6A_32 | RC_PROTO_BIT_RC6_MCE | |
204 | RC_PROTO_BIT_SONY20 | RC_PROTO_BIT_SANYO); |
205 | |
206 | rc->priv = ir; |
207 | rc->driver_name = DRIVER_NAME; |
208 | rc->map_name = RC_MAP_HAUPPAUGE; |
209 | rc->timeout = MS_TO_US(100); |
210 | rc->rx_resolution = 85; |
211 | |
212 | ir->rc = rc; |
213 | ret = rc_register_device(dev: rc); |
214 | if (ret) { |
215 | dev_err(&intf->dev, "failed to register rc device: %d" , ret); |
216 | goto fail; |
217 | } |
218 | |
219 | usb_set_intfdata(intf, data: ir); |
220 | |
221 | igorplugusb_cmd(ir, SET_INFRABUFFER_EMPTY); |
222 | |
223 | return 0; |
224 | fail: |
225 | usb_poison_urb(urb: ir->urb); |
226 | del_timer(timer: &ir->timer); |
227 | usb_unpoison_urb(urb: ir->urb); |
228 | usb_free_urb(urb: ir->urb); |
229 | rc_free_device(dev: ir->rc); |
230 | kfree(objp: ir->buf_in); |
231 | |
232 | return ret; |
233 | } |
234 | |
235 | static void igorplugusb_disconnect(struct usb_interface *intf) |
236 | { |
237 | struct igorplugusb *ir = usb_get_intfdata(intf); |
238 | |
239 | rc_unregister_device(dev: ir->rc); |
240 | usb_poison_urb(urb: ir->urb); |
241 | del_timer_sync(timer: &ir->timer); |
242 | usb_set_intfdata(intf, NULL); |
243 | usb_unpoison_urb(urb: ir->urb); |
244 | usb_free_urb(urb: ir->urb); |
245 | kfree(objp: ir->buf_in); |
246 | } |
247 | |
248 | static const struct usb_device_id igorplugusb_table[] = { |
249 | /* Igor Plug USB (Atmel's Manufact. ID) */ |
250 | { USB_DEVICE(0x03eb, 0x0002) }, |
251 | /* Fit PC2 Infrared Adapter */ |
252 | { USB_DEVICE(0x03eb, 0x21fe) }, |
253 | /* Terminating entry */ |
254 | { } |
255 | }; |
256 | |
257 | static struct usb_driver igorplugusb_driver = { |
258 | .name = DRIVER_NAME, |
259 | .probe = igorplugusb_probe, |
260 | .disconnect = igorplugusb_disconnect, |
261 | .id_table = igorplugusb_table |
262 | }; |
263 | |
264 | module_usb_driver(igorplugusb_driver); |
265 | |
266 | MODULE_DESCRIPTION(DRIVER_DESC); |
267 | MODULE_AUTHOR("Sean Young <sean@mess.org>" ); |
268 | MODULE_LICENSE("GPL" ); |
269 | MODULE_DEVICE_TABLE(usb, igorplugusb_table); |
270 | |