Commit | Line | Data |
---|---|---|
68ef4836 MF |
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> | |
68ef4836 MF |
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 | ||
76336b41 DT |
18 | #define PXRC_VENDOR_ID 0x1781 |
19 | #define PXRC_PRODUCT_ID 0x0898 | |
68ef4836 | 20 | |
68ef4836 MF |
21 | struct pxrc { |
22 | struct input_dev *input; | |
68ef4836 MF |
23 | struct usb_interface *intf; |
24 | struct urb *urb; | |
25 | struct mutex pm_mutex; | |
26 | bool is_open; | |
68ef4836 | 27 | char phys[64]; |
68ef4836 MF |
28 | }; |
29 | ||
30 | static void pxrc_usb_irq(struct urb *urb) | |
31 | { | |
32 | struct pxrc *pxrc = urb->context; | |
c8783d39 | 33 | u8 *data = urb->transfer_buffer; |
68ef4836 MF |
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) { | |
c8783d39 DT |
61 | input_report_abs(pxrc->input, ABS_X, data[0]); |
62 | input_report_abs(pxrc->input, ABS_Y, data[2]); | |
63 | input_report_abs(pxrc->input, ABS_RX, data[3]); | |
64 | input_report_abs(pxrc->input, ABS_RY, data[4]); | |
65 | input_report_abs(pxrc->input, ABS_RUDDER, data[5]); | |
66 | input_report_abs(pxrc->input, ABS_THROTTLE, data[6]); | |
67 | input_report_abs(pxrc->input, ABS_MISC, data[7]); | |
68 | ||
69 | input_report_key(pxrc->input, BTN_A, data[1]); | |
68ef4836 MF |
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(input); | |
84 | int retval; | |
85 | ||
86 | mutex_lock(&pxrc->pm_mutex); | |
87 | retval = usb_submit_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(&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(input); | |
106 | ||
107 | mutex_lock(&pxrc->pm_mutex); | |
108 | usb_kill_urb(pxrc->urb); | |
109 | pxrc->is_open = false; | |
110 | mutex_unlock(&pxrc->pm_mutex); | |
111 | } | |
112 | ||
34dad2cf DT |
113 | static void pxrc_free_urb(void *_pxrc) |
114 | { | |
115 | struct pxrc *pxrc = _pxrc; | |
116 | ||
117 | usb_free_urb(pxrc->urb); | |
118 | } | |
119 | ||
76336b41 DT |
120 | static int pxrc_probe(struct usb_interface *intf, |
121 | const struct usb_device_id *id) | |
68ef4836 | 122 | { |
76336b41 DT |
123 | struct usb_device *udev = interface_to_usbdev(intf); |
124 | struct pxrc *pxrc; | |
68ef4836 | 125 | struct usb_endpoint_descriptor *epirq; |
c8783d39 DT |
126 | size_t xfer_size; |
127 | void *xfer_buf; | |
34dad2cf | 128 | int error; |
68ef4836 | 129 | |
76336b41 DT |
130 | /* |
131 | * Locate the endpoint information. This device only has an | |
132 | * interrupt endpoint. | |
133 | */ | |
134 | error = usb_find_common_endpoints(intf->cur_altsetting, | |
34dad2cf DT |
135 | NULL, NULL, &epirq, NULL); |
136 | if (error) { | |
76336b41 | 137 | dev_err(&intf->dev, "Could not find endpoint\n"); |
34dad2cf | 138 | return error; |
68ef4836 MF |
139 | } |
140 | ||
76336b41 DT |
141 | pxrc = devm_kzalloc(&intf->dev, sizeof(*pxrc), GFP_KERNEL); |
142 | if (!pxrc) | |
34dad2cf | 143 | return -ENOMEM; |
68ef4836 | 144 | |
76336b41 DT |
145 | mutex_init(&pxrc->pm_mutex); |
146 | pxrc->intf = intf; | |
147 | ||
68ef4836 | 148 | usb_set_intfdata(pxrc->intf, pxrc); |
76336b41 DT |
149 | |
150 | xfer_size = usb_endpoint_maxp(epirq); | |
151 | xfer_buf = devm_kmalloc(&intf->dev, xfer_size, GFP_KERNEL); | |
152 | if (!xfer_buf) | |
153 | return -ENOMEM; | |
68ef4836 MF |
154 | |
155 | pxrc->urb = usb_alloc_urb(0, GFP_KERNEL); | |
34dad2cf DT |
156 | if (!pxrc->urb) |
157 | return -ENOMEM; | |
158 | ||
76336b41 | 159 | error = devm_add_action_or_reset(&intf->dev, pxrc_free_urb, pxrc); |
34dad2cf DT |
160 | if (error) |
161 | return error; | |
68ef4836 | 162 | |
76336b41 DT |
163 | usb_fill_int_urb(pxrc->urb, udev, |
164 | usb_rcvintpipe(udev, epirq->bEndpointAddress), | |
165 | xfer_buf, xfer_size, pxrc_usb_irq, pxrc, 1); | |
68ef4836 | 166 | |
76336b41 DT |
167 | pxrc->input = devm_input_allocate_device(&intf->dev); |
168 | if (!pxrc->input) { | |
169 | dev_err(&intf->dev, "couldn't allocate input device\n"); | |
68ef4836 MF |
170 | return -ENOMEM; |
171 | } | |
172 | ||
173 | pxrc->input->name = "PXRC Flight Controller Adapter"; | |
76336b41 DT |
174 | |
175 | usb_make_path(udev, pxrc->phys, sizeof(pxrc->phys)); | |
176 | strlcat(pxrc->phys, "/input0", sizeof(pxrc->phys)); | |
68ef4836 | 177 | pxrc->input->phys = pxrc->phys; |
76336b41 DT |
178 | |
179 | usb_to_input_id(udev, &pxrc->input->id); | |
68ef4836 MF |
180 | |
181 | pxrc->input->open = pxrc_open; | |
182 | pxrc->input->close = pxrc_close; | |
183 | ||
184 | input_set_capability(pxrc->input, EV_KEY, BTN_A); | |
185 | input_set_abs_params(pxrc->input, ABS_X, 0, 255, 0, 0); | |
186 | input_set_abs_params(pxrc->input, ABS_Y, 0, 255, 0, 0); | |
187 | input_set_abs_params(pxrc->input, ABS_RX, 0, 255, 0, 0); | |
188 | input_set_abs_params(pxrc->input, ABS_RY, 0, 255, 0, 0); | |
189 | input_set_abs_params(pxrc->input, ABS_RUDDER, 0, 255, 0, 0); | |
190 | input_set_abs_params(pxrc->input, ABS_THROTTLE, 0, 255, 0, 0); | |
191 | input_set_abs_params(pxrc->input, ABS_MISC, 0, 255, 0, 0); | |
192 | ||
193 | input_set_drvdata(pxrc->input, pxrc); | |
194 | ||
76336b41 | 195 | error = input_register_device(pxrc->input); |
34dad2cf DT |
196 | if (error) |
197 | return error; | |
68ef4836 MF |
198 | |
199 | return 0; | |
68ef4836 MF |
200 | } |
201 | ||
202 | static void pxrc_disconnect(struct usb_interface *intf) | |
203 | { | |
34dad2cf | 204 | /* All driver resources are devm-managed. */ |
68ef4836 MF |
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(pxrc->urb); | |
214 | mutex_unlock(&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(pxrc->urb, GFP_KERNEL) < 0) | |
226 | retval = -EIO; | |
227 | ||
228 | mutex_unlock(&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(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(pxrc->urb, GFP_KERNEL) < 0) | |
247 | retval = -EIO; | |
248 | ||
249 | mutex_unlock(&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 | ||
ce55f75f DT |
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 | ||
68ef4836 MF |
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"); |