2019-07-21 20:20:28 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* FS - iA6B iBus RC receiver driver
*
* This driver provides all 14 channels of the FlySky FS - ia6B RC receiver
* as analog values .
*
* Additionally , the channels can be converted to discrete switch values .
* By default , it is configured for the offical FS - i6 remote control .
* If you use a different hardware configuration , you can configure it
* using the ` switch_config ` parameter .
*/
# include <linux/device.h>
# include <linux/input.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/serio.h>
# include <linux/slab.h>
# include <linux/types.h>
# define DRIVER_DESC "FS-iA6B iBus RC receiver"
MODULE_AUTHOR ( " Markus Koch <markus@notsyncing.net> " ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
# define IBUS_SERVO_COUNT 14
static char * switch_config = " 00000022320000 " ;
module_param ( switch_config , charp , 0444 ) ;
MODULE_PARM_DESC ( switch_config ,
" Amount of switch positions per channel (14 characters, 0-3) " ) ;
static int fsia6b_axes [ IBUS_SERVO_COUNT ] = {
ABS_X , ABS_Y ,
ABS_Z , ABS_RX ,
ABS_RY , ABS_RZ ,
ABS_HAT0X , ABS_HAT0Y ,
ABS_HAT1X , ABS_HAT1Y ,
ABS_HAT2X , ABS_HAT2Y ,
ABS_HAT3X , ABS_HAT3Y
} ;
enum ibus_state { SYNC , COLLECT , PROCESS } ;
struct ibus_packet {
enum ibus_state state ;
int offset ;
u16 ibuf ;
u16 channel [ IBUS_SERVO_COUNT ] ;
} ;
struct fsia6b {
struct input_dev * dev ;
struct ibus_packet packet ;
char phys [ 32 ] ;
} ;
static irqreturn_t fsia6b_serio_irq ( struct serio * serio ,
unsigned char data , unsigned int flags )
{
struct fsia6b * fsia6b = serio_get_drvdata ( serio ) ;
int i ;
int sw_state ;
int sw_id = BTN_0 ;
fsia6b - > packet . ibuf = ( data < < 8 ) | ( ( fsia6b - > packet . ibuf > > 8 ) & 0xFF ) ;
switch ( fsia6b - > packet . state ) {
case SYNC :
if ( fsia6b - > packet . ibuf = = 0x4020 )
fsia6b - > packet . state = COLLECT ;
break ;
case COLLECT :
fsia6b - > packet . state = PROCESS ;
break ;
case PROCESS :
fsia6b - > packet . channel [ fsia6b - > packet . offset ] =
fsia6b - > packet . ibuf ;
fsia6b - > packet . offset + + ;
if ( fsia6b - > packet . offset = = IBUS_SERVO_COUNT ) {
fsia6b - > packet . offset = 0 ;
fsia6b - > packet . state = SYNC ;
for ( i = 0 ; i < IBUS_SERVO_COUNT ; + + i ) {
input_report_abs ( fsia6b - > dev , fsia6b_axes [ i ] ,
fsia6b - > packet . channel [ i ] ) ;
sw_state = 0 ;
if ( fsia6b - > packet . channel [ i ] > 1900 )
sw_state = 1 ;
else if ( fsia6b - > packet . channel [ i ] < 1100 )
sw_state = 2 ;
switch ( switch_config [ i ] ) {
case ' 3 ' :
input_report_key ( fsia6b - > dev ,
sw_id + + ,
sw_state = = 0 ) ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2019-07-21 20:20:28 +03:00
case ' 2 ' :
input_report_key ( fsia6b - > dev ,
sw_id + + ,
sw_state = = 1 ) ;
2020-08-23 17:36:59 -05:00
fallthrough ;
2019-07-21 20:20:28 +03:00
case ' 1 ' :
input_report_key ( fsia6b - > dev ,
sw_id + + ,
sw_state = = 2 ) ;
}
}
input_sync ( fsia6b - > dev ) ;
} else {
fsia6b - > packet . state = COLLECT ;
}
break ;
}
return IRQ_HANDLED ;
}
static int fsia6b_serio_connect ( struct serio * serio , struct serio_driver * drv )
{
struct fsia6b * fsia6b ;
struct input_dev * input_dev ;
int err ;
int i , j ;
int sw_id = 0 ;
fsia6b = kzalloc ( sizeof ( * fsia6b ) , GFP_KERNEL ) ;
if ( ! fsia6b )
return - ENOMEM ;
fsia6b - > packet . ibuf = 0 ;
fsia6b - > packet . offset = 0 ;
fsia6b - > packet . state = SYNC ;
serio_set_drvdata ( serio , fsia6b ) ;
input_dev = input_allocate_device ( ) ;
if ( ! input_dev ) {
err = - ENOMEM ;
goto fail1 ;
}
fsia6b - > dev = input_dev ;
snprintf ( fsia6b - > phys , sizeof ( fsia6b - > phys ) , " %s/input0 " , serio - > phys ) ;
input_dev - > name = DRIVER_DESC ;
input_dev - > phys = fsia6b - > phys ;
input_dev - > id . bustype = BUS_RS232 ;
input_dev - > id . vendor = SERIO_FSIA6B ;
input_dev - > id . product = serio - > id . id ;
input_dev - > id . version = 0x0100 ;
input_dev - > dev . parent = & serio - > dev ;
for ( i = 0 ; i < IBUS_SERVO_COUNT ; i + + )
input_set_abs_params ( input_dev , fsia6b_axes [ i ] ,
1000 , 2000 , 2 , 2 ) ;
/* Register switch configuration */
for ( i = 0 ; i < IBUS_SERVO_COUNT ; i + + ) {
if ( switch_config [ i ] < ' 0 ' | | switch_config [ i ] > ' 3 ' ) {
dev_err ( & fsia6b - > dev - > dev ,
" Invalid switch configuration supplied for fsia6b. \n " ) ;
err = - EINVAL ;
goto fail2 ;
}
for ( j = ' 1 ' ; j < = switch_config [ i ] ; j + + ) {
input_set_capability ( input_dev , EV_KEY , BTN_0 + sw_id ) ;
sw_id + + ;
}
}
err = serio_open ( serio , drv ) ;
if ( err )
goto fail2 ;
err = input_register_device ( fsia6b - > dev ) ;
if ( err )
goto fail3 ;
return 0 ;
fail3 : serio_close ( serio ) ;
fail2 : input_free_device ( input_dev ) ;
fail1 : serio_set_drvdata ( serio , NULL ) ;
kfree ( fsia6b ) ;
return err ;
}
static void fsia6b_serio_disconnect ( struct serio * serio )
{
struct fsia6b * fsia6b = serio_get_drvdata ( serio ) ;
serio_close ( serio ) ;
serio_set_drvdata ( serio , NULL ) ;
input_unregister_device ( fsia6b - > dev ) ;
kfree ( fsia6b ) ;
}
static const struct serio_device_id fsia6b_serio_ids [ ] = {
{
. type = SERIO_RS232 ,
. proto = SERIO_FSIA6B ,
. id = SERIO_ANY ,
. extra = SERIO_ANY ,
} ,
{ 0 }
} ;
MODULE_DEVICE_TABLE ( serio , fsia6b_serio_ids ) ;
static struct serio_driver fsia6b_serio_drv = {
. driver = {
. name = " fsia6b "
} ,
. description = DRIVER_DESC ,
. id_table = fsia6b_serio_ids ,
. interrupt = fsia6b_serio_irq ,
. connect = fsia6b_serio_connect ,
. disconnect = fsia6b_serio_disconnect
} ;
module_serio_driver ( fsia6b_serio_drv )