1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for Phoenix RC Flight Controller Adapter |
4 | * |
5 | * Copyright (C) 2018 Marcus Folkesson <marcus.folkesson@gmail.com> |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/errno.h> |
10 | #include <linux/slab.h> |
11 | #include <linux/module.h> |
12 | #include <linux/uaccess.h> |
13 | #include <linux/usb.h> |
14 | #include <linux/usb/input.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/input.h> |
17 | |
18 | #define PXRC_VENDOR_ID 0x1781 |
19 | #define PXRC_PRODUCT_ID 0x0898 |
20 | |
21 | struct pxrc { |
22 | struct input_dev *input; |
23 | struct usb_interface *intf; |
24 | struct urb *urb; |
25 | struct mutex pm_mutex; |
26 | bool is_open; |
27 | char phys[64]; |
28 | }; |
29 | |
30 | static void pxrc_usb_irq(struct urb *urb) |
31 | { |
32 | struct pxrc *pxrc = urb->context; |
33 | u8 *data = urb->transfer_buffer; |
34 | int error; |
35 | |
36 | switch (urb->status) { |
37 | case 0: |
38 | /* success */ |
39 | break; |
40 | case -ETIME: |
41 | /* this urb is timing out */ |
42 | dev_dbg(&pxrc->intf->dev, |
43 | "%s - urb timed out - was the device unplugged?\n" , |
44 | __func__); |
45 | return; |
46 | case -ECONNRESET: |
47 | case -ENOENT: |
48 | case -ESHUTDOWN: |
49 | case -EPIPE: |
50 | /* this urb is terminated, clean up */ |
51 | dev_dbg(&pxrc->intf->dev, "%s - urb shutting down with status: %d\n" , |
52 | __func__, urb->status); |
53 | return; |
54 | default: |
55 | dev_dbg(&pxrc->intf->dev, "%s - nonzero urb status received: %d\n" , |
56 | __func__, urb->status); |
57 | goto exit; |
58 | } |
59 | |
60 | if (urb->actual_length == 8) { |
61 | input_report_abs(dev: pxrc->input, ABS_X, value: data[0]); |
62 | input_report_abs(dev: pxrc->input, ABS_Y, value: data[2]); |
63 | input_report_abs(dev: pxrc->input, ABS_RX, value: data[3]); |
64 | input_report_abs(dev: pxrc->input, ABS_RY, value: data[4]); |
65 | input_report_abs(dev: pxrc->input, ABS_RUDDER, value: data[5]); |
66 | input_report_abs(dev: pxrc->input, ABS_THROTTLE, value: data[6]); |
67 | input_report_abs(dev: pxrc->input, ABS_MISC, value: data[7]); |
68 | |
69 | input_report_key(dev: pxrc->input, BTN_A, value: data[1]); |
70 | } |
71 | |
72 | exit: |
73 | /* Resubmit to fetch new fresh URBs */ |
74 | error = usb_submit_urb(urb, GFP_ATOMIC); |
75 | if (error && error != -EPERM) |
76 | dev_err(&pxrc->intf->dev, |
77 | "%s - usb_submit_urb failed with result: %d" , |
78 | __func__, error); |
79 | } |
80 | |
81 | static int pxrc_open(struct input_dev *input) |
82 | { |
83 | struct pxrc *pxrc = input_get_drvdata(dev: input); |
84 | int retval; |
85 | |
86 | mutex_lock(&pxrc->pm_mutex); |
87 | retval = usb_submit_urb(urb: pxrc->urb, GFP_KERNEL); |
88 | if (retval) { |
89 | dev_err(&pxrc->intf->dev, |
90 | "%s - usb_submit_urb failed, error: %d\n" , |
91 | __func__, retval); |
92 | retval = -EIO; |
93 | goto out; |
94 | } |
95 | |
96 | pxrc->is_open = true; |
97 | |
98 | out: |
99 | mutex_unlock(lock: &pxrc->pm_mutex); |
100 | return retval; |
101 | } |
102 | |
103 | static void pxrc_close(struct input_dev *input) |
104 | { |
105 | struct pxrc *pxrc = input_get_drvdata(dev: input); |
106 | |
107 | mutex_lock(&pxrc->pm_mutex); |
108 | usb_kill_urb(urb: pxrc->urb); |
109 | pxrc->is_open = false; |
110 | mutex_unlock(lock: &pxrc->pm_mutex); |
111 | } |
112 | |
113 | static void pxrc_free_urb(void *_pxrc) |
114 | { |
115 | struct pxrc *pxrc = _pxrc; |
116 | |
117 | usb_free_urb(urb: pxrc->urb); |
118 | } |
119 | |
120 | static int pxrc_probe(struct usb_interface *intf, |
121 | const struct usb_device_id *id) |
122 | { |
123 | struct usb_device *udev = interface_to_usbdev(intf); |
124 | struct pxrc *pxrc; |
125 | struct usb_endpoint_descriptor *epirq; |
126 | size_t xfer_size; |
127 | void *xfer_buf; |
128 | int error; |
129 | |
130 | /* |
131 | * Locate the endpoint information. This device only has an |
132 | * interrupt endpoint. |
133 | */ |
134 | error = usb_find_common_endpoints(alt: intf->cur_altsetting, |
135 | NULL, NULL, int_in: &epirq, NULL); |
136 | if (error) { |
137 | dev_err(&intf->dev, "Could not find endpoint\n" ); |
138 | return error; |
139 | } |
140 | |
141 | pxrc = devm_kzalloc(dev: &intf->dev, size: sizeof(*pxrc), GFP_KERNEL); |
142 | if (!pxrc) |
143 | return -ENOMEM; |
144 | |
145 | mutex_init(&pxrc->pm_mutex); |
146 | pxrc->intf = intf; |
147 | |
148 | usb_set_intfdata(intf: pxrc->intf, data: pxrc); |
149 | |
150 | xfer_size = usb_endpoint_maxp(epd: epirq); |
151 | xfer_buf = devm_kmalloc(dev: &intf->dev, size: xfer_size, GFP_KERNEL); |
152 | if (!xfer_buf) |
153 | return -ENOMEM; |
154 | |
155 | pxrc->urb = usb_alloc_urb(iso_packets: 0, GFP_KERNEL); |
156 | if (!pxrc->urb) |
157 | return -ENOMEM; |
158 | |
159 | error = devm_add_action_or_reset(&intf->dev, pxrc_free_urb, pxrc); |
160 | if (error) |
161 | return error; |
162 | |
163 | usb_fill_int_urb(urb: pxrc->urb, dev: udev, |
164 | usb_rcvintpipe(udev, epirq->bEndpointAddress), |
165 | transfer_buffer: xfer_buf, buffer_length: xfer_size, complete_fn: pxrc_usb_irq, context: pxrc, interval: 1); |
166 | |
167 | pxrc->input = devm_input_allocate_device(&intf->dev); |
168 | if (!pxrc->input) { |
169 | dev_err(&intf->dev, "couldn't allocate input device\n" ); |
170 | return -ENOMEM; |
171 | } |
172 | |
173 | pxrc->input->name = "PXRC Flight Controller Adapter" ; |
174 | |
175 | usb_make_path(dev: udev, buf: pxrc->phys, size: sizeof(pxrc->phys)); |
176 | strlcat(p: pxrc->phys, q: "/input0" , avail: sizeof(pxrc->phys)); |
177 | pxrc->input->phys = pxrc->phys; |
178 | |
179 | usb_to_input_id(dev: udev, id: &pxrc->input->id); |
180 | |
181 | pxrc->input->open = pxrc_open; |
182 | pxrc->input->close = pxrc_close; |
183 | |
184 | input_set_capability(dev: pxrc->input, EV_KEY, BTN_A); |
185 | input_set_abs_params(dev: pxrc->input, ABS_X, min: 0, max: 255, fuzz: 0, flat: 0); |
186 | input_set_abs_params(dev: pxrc->input, ABS_Y, min: 0, max: 255, fuzz: 0, flat: 0); |
187 | input_set_abs_params(dev: pxrc->input, ABS_RX, min: 0, max: 255, fuzz: 0, flat: 0); |
188 | input_set_abs_params(dev: pxrc->input, ABS_RY, min: 0, max: 255, fuzz: 0, flat: 0); |
189 | input_set_abs_params(dev: pxrc->input, ABS_RUDDER, min: 0, max: 255, fuzz: 0, flat: 0); |
190 | input_set_abs_params(dev: pxrc->input, ABS_THROTTLE, min: 0, max: 255, fuzz: 0, flat: 0); |
191 | input_set_abs_params(dev: pxrc->input, ABS_MISC, min: 0, max: 255, fuzz: 0, flat: 0); |
192 | |
193 | input_set_drvdata(dev: pxrc->input, data: pxrc); |
194 | |
195 | error = input_register_device(pxrc->input); |
196 | if (error) |
197 | return error; |
198 | |
199 | return 0; |
200 | } |
201 | |
202 | static void pxrc_disconnect(struct usb_interface *intf) |
203 | { |
204 | /* All driver resources are devm-managed. */ |
205 | } |
206 | |
207 | static int pxrc_suspend(struct usb_interface *intf, pm_message_t message) |
208 | { |
209 | struct pxrc *pxrc = usb_get_intfdata(intf); |
210 | |
211 | mutex_lock(&pxrc->pm_mutex); |
212 | if (pxrc->is_open) |
213 | usb_kill_urb(urb: pxrc->urb); |
214 | mutex_unlock(lock: &pxrc->pm_mutex); |
215 | |
216 | return 0; |
217 | } |
218 | |
219 | static int pxrc_resume(struct usb_interface *intf) |
220 | { |
221 | struct pxrc *pxrc = usb_get_intfdata(intf); |
222 | int retval = 0; |
223 | |
224 | mutex_lock(&pxrc->pm_mutex); |
225 | if (pxrc->is_open && usb_submit_urb(urb: pxrc->urb, GFP_KERNEL) < 0) |
226 | retval = -EIO; |
227 | |
228 | mutex_unlock(lock: &pxrc->pm_mutex); |
229 | return retval; |
230 | } |
231 | |
232 | static int pxrc_pre_reset(struct usb_interface *intf) |
233 | { |
234 | struct pxrc *pxrc = usb_get_intfdata(intf); |
235 | |
236 | mutex_lock(&pxrc->pm_mutex); |
237 | usb_kill_urb(urb: pxrc->urb); |
238 | return 0; |
239 | } |
240 | |
241 | static int pxrc_post_reset(struct usb_interface *intf) |
242 | { |
243 | struct pxrc *pxrc = usb_get_intfdata(intf); |
244 | int retval = 0; |
245 | |
246 | if (pxrc->is_open && usb_submit_urb(urb: pxrc->urb, GFP_KERNEL) < 0) |
247 | retval = -EIO; |
248 | |
249 | mutex_unlock(lock: &pxrc->pm_mutex); |
250 | |
251 | return retval; |
252 | } |
253 | |
254 | static int pxrc_reset_resume(struct usb_interface *intf) |
255 | { |
256 | return pxrc_resume(intf); |
257 | } |
258 | |
259 | static const struct usb_device_id pxrc_table[] = { |
260 | { USB_DEVICE(PXRC_VENDOR_ID, PXRC_PRODUCT_ID) }, |
261 | { } |
262 | }; |
263 | MODULE_DEVICE_TABLE(usb, pxrc_table); |
264 | |
265 | static struct usb_driver pxrc_driver = { |
266 | .name = "pxrc" , |
267 | .probe = pxrc_probe, |
268 | .disconnect = pxrc_disconnect, |
269 | .id_table = pxrc_table, |
270 | .suspend = pxrc_suspend, |
271 | .resume = pxrc_resume, |
272 | .pre_reset = pxrc_pre_reset, |
273 | .post_reset = pxrc_post_reset, |
274 | .reset_resume = pxrc_reset_resume, |
275 | }; |
276 | |
277 | module_usb_driver(pxrc_driver); |
278 | |
279 | MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>" ); |
280 | MODULE_DESCRIPTION("PhoenixRC Flight Controller Adapter" ); |
281 | MODULE_LICENSE("GPL v2" ); |
282 | |