2018-03-17 21:00:58 +03:00
// 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>
2018-07-18 21:37:47 +03:00
# define PXRC_VENDOR_ID 0x1781
# define PXRC_PRODUCT_ID 0x0898
2018-03-17 21:00:58 +03:00
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 ;
2018-07-18 21:24:27 +03:00
u8 * data = urb - > transfer_buffer ;
2018-03-17 21:00:58 +03:00
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 ) {
2018-07-18 21:24:27 +03:00
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 ] ) ;
2018-03-17 21:00:58 +03:00
}
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 ) ;
}
2018-07-18 21:03:36 +03:00
static void pxrc_free_urb ( void * _pxrc )
{
struct pxrc * pxrc = _pxrc ;
usb_free_urb ( pxrc - > urb ) ;
}
2018-07-18 21:37:47 +03:00
static int pxrc_probe ( struct usb_interface * intf ,
const struct usb_device_id * id )
2018-03-17 21:00:58 +03:00
{
2018-07-18 21:37:47 +03:00
struct usb_device * udev = interface_to_usbdev ( intf ) ;
struct pxrc * pxrc ;
2018-03-17 21:00:58 +03:00
struct usb_endpoint_descriptor * epirq ;
2018-07-18 21:24:27 +03:00
size_t xfer_size ;
void * xfer_buf ;
2018-07-18 21:03:36 +03:00
int error ;
2018-03-17 21:00:58 +03:00
2018-07-18 21:37:47 +03:00
/*
* Locate the endpoint information . This device only has an
* interrupt endpoint .
*/
error = usb_find_common_endpoints ( intf - > cur_altsetting ,
2018-07-18 21:03:36 +03:00
NULL , NULL , & epirq , NULL ) ;
if ( error ) {
2018-07-18 21:37:47 +03:00
dev_err ( & intf - > dev , " Could not find endpoint \n " ) ;
2018-07-18 21:03:36 +03:00
return error ;
2018-03-17 21:00:58 +03:00
}
2018-07-18 21:37:47 +03:00
pxrc = devm_kzalloc ( & intf - > dev , sizeof ( * pxrc ) , GFP_KERNEL ) ;
if ( ! pxrc )
2018-07-18 21:03:36 +03:00
return - ENOMEM ;
2018-03-17 21:00:58 +03:00
2018-07-18 21:37:47 +03:00
mutex_init ( & pxrc - > pm_mutex ) ;
pxrc - > intf = intf ;
2018-03-17 21:00:58 +03:00
usb_set_intfdata ( pxrc - > intf , pxrc ) ;
2018-07-18 21:37:47 +03:00
xfer_size = usb_endpoint_maxp ( epirq ) ;
xfer_buf = devm_kmalloc ( & intf - > dev , xfer_size , GFP_KERNEL ) ;
if ( ! xfer_buf )
return - ENOMEM ;
2018-03-17 21:00:58 +03:00
pxrc - > urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
2018-07-18 21:03:36 +03:00
if ( ! pxrc - > urb )
return - ENOMEM ;
2018-07-18 21:37:47 +03:00
error = devm_add_action_or_reset ( & intf - > dev , pxrc_free_urb , pxrc ) ;
2018-07-18 21:03:36 +03:00
if ( error )
return error ;
2018-03-17 21:00:58 +03:00
2018-07-18 21:37:47 +03:00
usb_fill_int_urb ( pxrc - > urb , udev ,
usb_rcvintpipe ( udev , epirq - > bEndpointAddress ) ,
xfer_buf , xfer_size , pxrc_usb_irq , pxrc , 1 ) ;
2018-03-17 21:00:58 +03:00
2018-07-18 21:37:47 +03:00
pxrc - > input = devm_input_allocate_device ( & intf - > dev ) ;
if ( ! pxrc - > input ) {
dev_err ( & intf - > dev , " couldn't allocate input device \n " ) ;
2018-03-17 21:00:58 +03:00
return - ENOMEM ;
}
pxrc - > input - > name = " PXRC Flight Controller Adapter " ;
2018-07-18 21:37:47 +03:00
usb_make_path ( udev , pxrc - > phys , sizeof ( pxrc - > phys ) ) ;
strlcat ( pxrc - > phys , " /input0 " , sizeof ( pxrc - > phys ) ) ;
2018-03-17 21:00:58 +03:00
pxrc - > input - > phys = pxrc - > phys ;
2018-07-18 21:37:47 +03:00
usb_to_input_id ( udev , & pxrc - > input - > id ) ;
2018-03-17 21:00:58 +03:00
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 ) ;
2018-07-18 21:37:47 +03:00
error = input_register_device ( pxrc - > input ) ;
2018-07-18 21:03:36 +03:00
if ( error )
return error ;
2018-03-17 21:00:58 +03:00
return 0 ;
}
static void pxrc_disconnect ( struct usb_interface * intf )
{
2018-07-18 21:03:36 +03:00
/* All driver resources are devm-managed. */
2018-03-17 21:00:58 +03:00
}
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 ) ;
}
2018-07-18 21:12:12 +03:00
static const struct usb_device_id pxrc_table [ ] = {
{ USB_DEVICE ( PXRC_VENDOR_ID , PXRC_PRODUCT_ID ) } ,
{ }
} ;
MODULE_DEVICE_TABLE ( usb , pxrc_table ) ;
2018-03-17 21:00:58 +03:00
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 " ) ;