1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * IgorPlug-USB IR Receiver
5 * Copyright (C) 2014 Sean Young <sean@mess.org>
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
10 * Based on the lirc_igorplugusb.c driver:
11 * Copyright (C) 2004 Jan M. Hochstein
12 * <hochstein@algo.informatik.tu-darmstadt.de>
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>
21 #define DRIVER_DESC "IgorPlug-USB IR Receiver"
22 #define DRIVER_NAME "igorplugusb"
26 #define MAX_PACKET (HEADERLEN + BUFLEN)
28 #define SET_INFRABUFFER_EMPTY 1
29 #define GET_INFRACODE 2
37 struct usb_ctrlrequest request
;
39 struct timer_list timer
;
46 static void igorplugusb_cmd(struct igorplugusb
*ir
, int cmd
);
48 static void igorplugusb_irdata(struct igorplugusb
*ir
, unsigned len
)
50 struct ir_raw_event rawir
= {};
51 unsigned i
, start
, overflow
;
53 dev_dbg(ir
->dev
, "irdata: %*ph (len=%u)", len
, ir
->buf_in
, len
);
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.
61 overflow
= ir
->buf_in
[2];
62 i
= start
= overflow
+ HEADERLEN
;
65 dev_err(ir
->dev
, "receive overflow invalid: %u", overflow
);
68 dev_warn(ir
->dev
, "receive overflow, at least %u lost",
70 ir_raw_event_overflow(ir
->rc
);
74 rawir
.duration
= ir
->buf_in
[i
] * 85;
77 ir_raw_event_store_with_filter(ir
->rc
, &rawir
);
83 /* add a trailing space */
84 rawir
.duration
= ir
->rc
->timeout
;
86 ir_raw_event_store_with_filter(ir
->rc
, &rawir
);
88 ir_raw_event_handle(ir
->rc
);
91 igorplugusb_cmd(ir
, SET_INFRABUFFER_EMPTY
);
94 static void igorplugusb_callback(struct urb
*urb
)
96 struct usb_ctrlrequest
*req
;
97 struct igorplugusb
*ir
= urb
->context
;
99 req
= (struct usb_ctrlrequest
*)urb
->setup_packet
;
101 switch (urb
->status
) {
103 if (req
->bRequest
== GET_INFRACODE
&&
104 urb
->actual_length
> HEADERLEN
)
105 igorplugusb_irdata(ir
, urb
->actual_length
);
106 else /* request IR */
107 mod_timer(&ir
->timer
, jiffies
+ msecs_to_jiffies(50));
115 dev_warn(ir
->dev
, "Error: urb status = %d\n", urb
->status
);
116 igorplugusb_cmd(ir
, SET_INFRABUFFER_EMPTY
);
121 static void igorplugusb_cmd(struct igorplugusb
*ir
, int cmd
)
125 ir
->request
.bRequest
= cmd
;
126 ir
->urb
->transfer_flags
= 0;
127 ret
= usb_submit_urb(ir
->urb
, GFP_ATOMIC
);
128 if (ret
&& ret
!= -EPERM
)
129 dev_err(ir
->dev
, "submit urb failed: %d", ret
);
132 static void igorplugusb_timer(struct timer_list
*t
)
134 struct igorplugusb
*ir
= from_timer(ir
, t
, timer
);
136 igorplugusb_cmd(ir
, GET_INFRACODE
);
139 static int igorplugusb_probe(struct usb_interface
*intf
,
140 const struct usb_device_id
*id
)
142 struct usb_device
*udev
;
143 struct usb_host_interface
*idesc
;
144 struct usb_endpoint_descriptor
*ep
;
145 struct igorplugusb
*ir
;
149 udev
= interface_to_usbdev(intf
);
150 idesc
= intf
->cur_altsetting
;
152 if (idesc
->desc
.bNumEndpoints
!= 1) {
153 dev_err(&intf
->dev
, "incorrect number of endpoints");
157 ep
= &idesc
->endpoint
[0].desc
;
158 if (!usb_endpoint_dir_in(ep
) || !usb_endpoint_xfer_control(ep
)) {
159 dev_err(&intf
->dev
, "endpoint incorrect");
163 ir
= devm_kzalloc(&intf
->dev
, sizeof(*ir
), GFP_KERNEL
);
167 ir
->dev
= &intf
->dev
;
169 timer_setup(&ir
->timer
, igorplugusb_timer
, 0);
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
);
175 ir
->urb
= usb_alloc_urb(0, GFP_KERNEL
);
179 ir
->buf_in
= kmalloc(MAX_PACKET
, GFP_KERNEL
);
182 usb_fill_control_urb(ir
->urb
, udev
,
183 usb_rcvctrlpipe(udev
, 0), (uint8_t *)&ir
->request
,
184 ir
->buf_in
, MAX_PACKET
, igorplugusb_callback
, ir
);
186 usb_make_path(udev
, ir
->phys
, sizeof(ir
->phys
));
188 rc
= rc_allocate_device(RC_DRIVER_IR_RAW
);
192 rc
->device_name
= DRIVER_DESC
;
193 rc
->input_phys
= ir
->phys
;
194 usb_to_input_id(udev
, &rc
->input_id
);
195 rc
->dev
.parent
= &intf
->dev
;
197 * This device can only store 36 pulses + spaces, which is not enough
198 * for the NEC protocol and many others.
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
);
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;
213 ret
= rc_register_device(rc
);
215 dev_err(&intf
->dev
, "failed to register rc device: %d", ret
);
219 usb_set_intfdata(intf
, ir
);
221 igorplugusb_cmd(ir
, SET_INFRABUFFER_EMPTY
);
225 usb_poison_urb(ir
->urb
);
226 del_timer(&ir
->timer
);
227 usb_unpoison_urb(ir
->urb
);
228 usb_free_urb(ir
->urb
);
229 rc_free_device(ir
->rc
);
235 static void igorplugusb_disconnect(struct usb_interface
*intf
)
237 struct igorplugusb
*ir
= usb_get_intfdata(intf
);
239 rc_unregister_device(ir
->rc
);
240 usb_poison_urb(ir
->urb
);
241 del_timer_sync(&ir
->timer
);
242 usb_set_intfdata(intf
, NULL
);
243 usb_unpoison_urb(ir
->urb
);
244 usb_free_urb(ir
->urb
);
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 */
257 static struct usb_driver igorplugusb_driver
= {
259 .probe
= igorplugusb_probe
,
260 .disconnect
= igorplugusb_disconnect
,
261 .id_table
= igorplugusb_table
264 module_usb_driver(igorplugusb_driver
);
266 MODULE_DESCRIPTION(DRIVER_DESC
);
267 MODULE_AUTHOR("Sean Young <sean@mess.org>");
268 MODULE_LICENSE("GPL");
269 MODULE_DEVICE_TABLE(usb
, igorplugusb_table
);