forked from Minki/linux
76336b41bd
Instead of splitting probe code into separate USB and input setup, flatten it. This allows for easier inspection of order of set up steps, since the probe code is reasonably small. Move input-related initialization (phys) from USB block to input block. Reviewed-by: Marcus Folkesson <marcus.folkesson@gmail.com> Tested-by: Marcus Folkesson <marcus.folkesson@gmail.com> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
282 lines
6.5 KiB
C
282 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Driver for Phoenix RC Flight Controller Adapter
|
|
*
|
|
* Copyright (C) 2018 Marcus Folkesson <marcus.folkesson@gmail.com>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/input.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/input.h>
|
|
|
|
#define PXRC_VENDOR_ID 0x1781
|
|
#define PXRC_PRODUCT_ID 0x0898
|
|
|
|
struct pxrc {
|
|
struct input_dev *input;
|
|
struct usb_interface *intf;
|
|
struct urb *urb;
|
|
struct mutex pm_mutex;
|
|
bool is_open;
|
|
char phys[64];
|
|
};
|
|
|
|
static void pxrc_usb_irq(struct urb *urb)
|
|
{
|
|
struct pxrc *pxrc = urb->context;
|
|
u8 *data = urb->transfer_buffer;
|
|
int error;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
/* success */
|
|
break;
|
|
case -ETIME:
|
|
/* this urb is timing out */
|
|
dev_dbg(&pxrc->intf->dev,
|
|
"%s - urb timed out - was the device unplugged?\n",
|
|
__func__);
|
|
return;
|
|
case -ECONNRESET:
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
case -EPIPE:
|
|
/* this urb is terminated, clean up */
|
|
dev_dbg(&pxrc->intf->dev, "%s - urb shutting down with status: %d\n",
|
|
__func__, urb->status);
|
|
return;
|
|
default:
|
|
dev_dbg(&pxrc->intf->dev, "%s - nonzero urb status received: %d\n",
|
|
__func__, urb->status);
|
|
goto exit;
|
|
}
|
|
|
|
if (urb->actual_length == 8) {
|
|
input_report_abs(pxrc->input, ABS_X, data[0]);
|
|
input_report_abs(pxrc->input, ABS_Y, data[2]);
|
|
input_report_abs(pxrc->input, ABS_RX, data[3]);
|
|
input_report_abs(pxrc->input, ABS_RY, data[4]);
|
|
input_report_abs(pxrc->input, ABS_RUDDER, data[5]);
|
|
input_report_abs(pxrc->input, ABS_THROTTLE, data[6]);
|
|
input_report_abs(pxrc->input, ABS_MISC, data[7]);
|
|
|
|
input_report_key(pxrc->input, BTN_A, data[1]);
|
|
}
|
|
|
|
exit:
|
|
/* Resubmit to fetch new fresh URBs */
|
|
error = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (error && error != -EPERM)
|
|
dev_err(&pxrc->intf->dev,
|
|
"%s - usb_submit_urb failed with result: %d",
|
|
__func__, error);
|
|
}
|
|
|
|
static int pxrc_open(struct input_dev *input)
|
|
{
|
|
struct pxrc *pxrc = input_get_drvdata(input);
|
|
int retval;
|
|
|
|
mutex_lock(&pxrc->pm_mutex);
|
|
retval = usb_submit_urb(pxrc->urb, GFP_KERNEL);
|
|
if (retval) {
|
|
dev_err(&pxrc->intf->dev,
|
|
"%s - usb_submit_urb failed, error: %d\n",
|
|
__func__, retval);
|
|
retval = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
pxrc->is_open = true;
|
|
|
|
out:
|
|
mutex_unlock(&pxrc->pm_mutex);
|
|
return retval;
|
|
}
|
|
|
|
static void pxrc_close(struct input_dev *input)
|
|
{
|
|
struct pxrc *pxrc = input_get_drvdata(input);
|
|
|
|
mutex_lock(&pxrc->pm_mutex);
|
|
usb_kill_urb(pxrc->urb);
|
|
pxrc->is_open = false;
|
|
mutex_unlock(&pxrc->pm_mutex);
|
|
}
|
|
|
|
static void pxrc_free_urb(void *_pxrc)
|
|
{
|
|
struct pxrc *pxrc = _pxrc;
|
|
|
|
usb_free_urb(pxrc->urb);
|
|
}
|
|
|
|
static int pxrc_probe(struct usb_interface *intf,
|
|
const struct usb_device_id *id)
|
|
{
|
|
struct usb_device *udev = interface_to_usbdev(intf);
|
|
struct pxrc *pxrc;
|
|
struct usb_endpoint_descriptor *epirq;
|
|
size_t xfer_size;
|
|
void *xfer_buf;
|
|
int error;
|
|
|
|
/*
|
|
* Locate the endpoint information. This device only has an
|
|
* interrupt endpoint.
|
|
*/
|
|
error = usb_find_common_endpoints(intf->cur_altsetting,
|
|
NULL, NULL, &epirq, NULL);
|
|
if (error) {
|
|
dev_err(&intf->dev, "Could not find endpoint\n");
|
|
return error;
|
|
}
|
|
|
|
pxrc = devm_kzalloc(&intf->dev, sizeof(*pxrc), GFP_KERNEL);
|
|
if (!pxrc)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&pxrc->pm_mutex);
|
|
pxrc->intf = intf;
|
|
|
|
usb_set_intfdata(pxrc->intf, pxrc);
|
|
|
|
xfer_size = usb_endpoint_maxp(epirq);
|
|
xfer_buf = devm_kmalloc(&intf->dev, xfer_size, GFP_KERNEL);
|
|
if (!xfer_buf)
|
|
return -ENOMEM;
|
|
|
|
pxrc->urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (!pxrc->urb)
|
|
return -ENOMEM;
|
|
|
|
error = devm_add_action_or_reset(&intf->dev, pxrc_free_urb, pxrc);
|
|
if (error)
|
|
return error;
|
|
|
|
usb_fill_int_urb(pxrc->urb, udev,
|
|
usb_rcvintpipe(udev, epirq->bEndpointAddress),
|
|
xfer_buf, xfer_size, pxrc_usb_irq, pxrc, 1);
|
|
|
|
pxrc->input = devm_input_allocate_device(&intf->dev);
|
|
if (!pxrc->input) {
|
|
dev_err(&intf->dev, "couldn't allocate input device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pxrc->input->name = "PXRC Flight Controller Adapter";
|
|
|
|
usb_make_path(udev, pxrc->phys, sizeof(pxrc->phys));
|
|
strlcat(pxrc->phys, "/input0", sizeof(pxrc->phys));
|
|
pxrc->input->phys = pxrc->phys;
|
|
|
|
usb_to_input_id(udev, &pxrc->input->id);
|
|
|
|
pxrc->input->open = pxrc_open;
|
|
pxrc->input->close = pxrc_close;
|
|
|
|
input_set_capability(pxrc->input, EV_KEY, BTN_A);
|
|
input_set_abs_params(pxrc->input, ABS_X, 0, 255, 0, 0);
|
|
input_set_abs_params(pxrc->input, ABS_Y, 0, 255, 0, 0);
|
|
input_set_abs_params(pxrc->input, ABS_RX, 0, 255, 0, 0);
|
|
input_set_abs_params(pxrc->input, ABS_RY, 0, 255, 0, 0);
|
|
input_set_abs_params(pxrc->input, ABS_RUDDER, 0, 255, 0, 0);
|
|
input_set_abs_params(pxrc->input, ABS_THROTTLE, 0, 255, 0, 0);
|
|
input_set_abs_params(pxrc->input, ABS_MISC, 0, 255, 0, 0);
|
|
|
|
input_set_drvdata(pxrc->input, pxrc);
|
|
|
|
error = input_register_device(pxrc->input);
|
|
if (error)
|
|
return error;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pxrc_disconnect(struct usb_interface *intf)
|
|
{
|
|
/* All driver resources are devm-managed. */
|
|
}
|
|
|
|
static int pxrc_suspend(struct usb_interface *intf, pm_message_t message)
|
|
{
|
|
struct pxrc *pxrc = usb_get_intfdata(intf);
|
|
|
|
mutex_lock(&pxrc->pm_mutex);
|
|
if (pxrc->is_open)
|
|
usb_kill_urb(pxrc->urb);
|
|
mutex_unlock(&pxrc->pm_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxrc_resume(struct usb_interface *intf)
|
|
{
|
|
struct pxrc *pxrc = usb_get_intfdata(intf);
|
|
int retval = 0;
|
|
|
|
mutex_lock(&pxrc->pm_mutex);
|
|
if (pxrc->is_open && usb_submit_urb(pxrc->urb, GFP_KERNEL) < 0)
|
|
retval = -EIO;
|
|
|
|
mutex_unlock(&pxrc->pm_mutex);
|
|
return retval;
|
|
}
|
|
|
|
static int pxrc_pre_reset(struct usb_interface *intf)
|
|
{
|
|
struct pxrc *pxrc = usb_get_intfdata(intf);
|
|
|
|
mutex_lock(&pxrc->pm_mutex);
|
|
usb_kill_urb(pxrc->urb);
|
|
return 0;
|
|
}
|
|
|
|
static int pxrc_post_reset(struct usb_interface *intf)
|
|
{
|
|
struct pxrc *pxrc = usb_get_intfdata(intf);
|
|
int retval = 0;
|
|
|
|
if (pxrc->is_open && usb_submit_urb(pxrc->urb, GFP_KERNEL) < 0)
|
|
retval = -EIO;
|
|
|
|
mutex_unlock(&pxrc->pm_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int pxrc_reset_resume(struct usb_interface *intf)
|
|
{
|
|
return pxrc_resume(intf);
|
|
}
|
|
|
|
static const struct usb_device_id pxrc_table[] = {
|
|
{ USB_DEVICE(PXRC_VENDOR_ID, PXRC_PRODUCT_ID) },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(usb, pxrc_table);
|
|
|
|
static struct usb_driver pxrc_driver = {
|
|
.name = "pxrc",
|
|
.probe = pxrc_probe,
|
|
.disconnect = pxrc_disconnect,
|
|
.id_table = pxrc_table,
|
|
.suspend = pxrc_suspend,
|
|
.resume = pxrc_resume,
|
|
.pre_reset = pxrc_pre_reset,
|
|
.post_reset = pxrc_post_reset,
|
|
.reset_resume = pxrc_reset_resume,
|
|
};
|
|
|
|
module_usb_driver(pxrc_driver);
|
|
|
|
MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>");
|
|
MODULE_DESCRIPTION("PhoenixRC Flight Controller Adapter");
|
|
MODULE_LICENSE("GPL v2");
|