2020-05-03 11:27:55 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Infrared Toy and IR Droid RC core driver
*
* Copyright ( C ) 2020 Sean Young < sean @ mess . org >
2021-08-10 20:08:01 +02:00
*
* http : //dangerousprototypes.com/docs/USB_IR_Toy:_Sampling_mode
*
2020-05-03 11:27:55 +02:00
* This driver is based on the lirc driver which can be found here :
* https : //sourceforge.net/p/lirc/git/ci/master/tree/plugins/irtoy.c
* Copyright ( C ) 2011 Peter Kooiman < pkooiman @ gmail . com >
*/
# include <asm/unaligned.h>
# include <linux/completion.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/usb.h>
# include <linux/slab.h>
# include <linux/usb/input.h>
# include <media/rc-core.h>
static const u8 COMMAND_VERSION [ ] = { ' v ' } ;
// End transmit and repeat reset command so we exit sump mode
static const u8 COMMAND_RESET [ ] = { 0xff , 0xff , 0 , 0 , 0 , 0 , 0 } ;
static const u8 COMMAND_SMODE_ENTER [ ] = { ' s ' } ;
2021-09-14 16:57:46 +02:00
static const u8 COMMAND_SMODE_EXIT [ ] = { 0 } ;
2020-05-03 11:27:55 +02:00
static const u8 COMMAND_TXSTART [ ] = { 0x26 , 0x24 , 0x25 , 0x03 } ;
# define REPLY_XMITCOUNT 't'
# define REPLY_XMITSUCCESS 'C'
# define REPLY_VERSION 'V'
# define REPLY_SAMPLEMODEPROTO 'S'
# define TIMEOUT 500
# define LEN_XMITRES 3
# define LEN_VERSION 4
# define LEN_SAMPLEMODEPROTO 3
# define MIN_FW_VERSION 20
2020-08-23 19:23:05 +02:00
# define UNIT_US 21
# define MAX_TIMEOUT_US (UNIT_US * U16_MAX)
2020-05-03 11:27:55 +02:00
# define MAX_PACKET 64
enum state {
STATE_IRDATA ,
2021-08-10 20:08:01 +02:00
STATE_COMMAND_NO_RESP ,
2020-05-03 11:27:55 +02:00
STATE_COMMAND ,
STATE_TX ,
} ;
struct irtoy {
struct device * dev ;
struct usb_device * usbdev ;
struct rc_dev * rc ;
struct urb * urb_in , * urb_out ;
u8 * in ;
u8 * out ;
struct completion command_done ;
bool pulse ;
enum state state ;
void * tx_buf ;
uint tx_len ;
uint emitted ;
uint hw_version ;
uint sw_version ;
uint proto_version ;
char phys [ 64 ] ;
} ;
static void irtoy_response ( struct irtoy * irtoy , u32 len )
{
switch ( irtoy - > state ) {
case STATE_COMMAND :
if ( len = = LEN_VERSION & & irtoy - > in [ 0 ] = = REPLY_VERSION ) {
uint version ;
irtoy - > in [ LEN_VERSION ] = 0 ;
if ( kstrtouint ( irtoy - > in + 1 , 10 , & version ) ) {
dev_err ( irtoy - > dev , " invalid version %*phN. Please make sure you are using firmware v20 or higher " ,
LEN_VERSION , irtoy - > in ) ;
break ;
}
dev_dbg ( irtoy - > dev , " version %s \n " , irtoy - > in ) ;
irtoy - > hw_version = version / 100 ;
irtoy - > sw_version = version % 100 ;
irtoy - > state = STATE_IRDATA ;
complete ( & irtoy - > command_done ) ;
} else if ( len = = LEN_SAMPLEMODEPROTO & &
irtoy - > in [ 0 ] = = REPLY_SAMPLEMODEPROTO ) {
uint version ;
irtoy - > in [ LEN_SAMPLEMODEPROTO ] = 0 ;
if ( kstrtouint ( irtoy - > in + 1 , 10 , & version ) ) {
dev_err ( irtoy - > dev , " invalid sample mode response %*phN " ,
LEN_SAMPLEMODEPROTO , irtoy - > in ) ;
return ;
}
dev_dbg ( irtoy - > dev , " protocol %s \n " , irtoy - > in ) ;
irtoy - > proto_version = version ;
irtoy - > state = STATE_IRDATA ;
complete ( & irtoy - > command_done ) ;
} else {
dev_err ( irtoy - > dev , " unexpected response to command: %*phN \n " ,
len , irtoy - > in ) ;
}
break ;
2021-09-08 15:05:52 +02:00
case STATE_COMMAND_NO_RESP :
2020-05-03 11:27:55 +02:00
case STATE_IRDATA : {
struct ir_raw_event rawir = { . pulse = irtoy - > pulse } ;
__be16 * in = ( __be16 * ) irtoy - > in ;
int i ;
for ( i = 0 ; i < len / sizeof ( __be16 ) ; i + + ) {
u16 v = be16_to_cpu ( in [ i ] ) ;
if ( v = = 0xffff ) {
rawir . pulse = false ;
} else {
2020-08-23 19:23:05 +02:00
rawir . duration = v * UNIT_US ;
2020-05-03 11:27:55 +02:00
ir_raw_event_store_with_timeout ( irtoy - > rc ,
& rawir ) ;
}
rawir . pulse = ! rawir . pulse ;
}
irtoy - > pulse = rawir . pulse ;
ir_raw_event_handle ( irtoy - > rc ) ;
break ;
}
case STATE_TX :
if ( irtoy - > tx_len = = 0 ) {
if ( len = = LEN_XMITRES & &
irtoy - > in [ 0 ] = = REPLY_XMITCOUNT ) {
u16 emitted = get_unaligned_be16 ( irtoy - > in + 1 ) ;
dev_dbg ( irtoy - > dev , " emitted:%u \n " , emitted ) ;
irtoy - > emitted = emitted ;
} else if ( len = = 1 & &
irtoy - > in [ 0 ] = = REPLY_XMITSUCCESS ) {
irtoy - > state = STATE_IRDATA ;
complete ( & irtoy - > command_done ) ;
}
} else {
// send next part of tx buffer
uint space = irtoy - > in [ 0 ] ;
uint buf_len ;
int err ;
if ( len ! = 1 | | space > MAX_PACKET | | space = = 0 ) {
2021-09-08 15:05:52 +02:00
dev_dbg ( irtoy - > dev , " packet length expected: %*phN \n " ,
2020-05-03 11:27:55 +02:00
len , irtoy - > in ) ;
break ;
}
buf_len = min ( space , irtoy - > tx_len ) ;
dev_dbg ( irtoy - > dev , " remaining:%u sending:%u \n " ,
irtoy - > tx_len , buf_len ) ;
memcpy ( irtoy - > out , irtoy - > tx_buf , buf_len ) ;
irtoy - > urb_out - > transfer_buffer_length = buf_len ;
err = usb_submit_urb ( irtoy - > urb_out , GFP_ATOMIC ) ;
if ( err ! = 0 ) {
dev_err ( irtoy - > dev , " fail to submit tx buf urb: %d \n " ,
err ) ;
irtoy - > state = STATE_IRDATA ;
complete ( & irtoy - > command_done ) ;
break ;
}
irtoy - > tx_buf + = buf_len ;
irtoy - > tx_len - = buf_len ;
}
break ;
}
}
static void irtoy_out_callback ( struct urb * urb )
{
struct irtoy * irtoy = urb - > context ;
if ( urb - > status = = 0 ) {
2021-08-10 20:08:01 +02:00
if ( irtoy - > state = = STATE_COMMAND_NO_RESP )
2020-05-03 11:27:55 +02:00
complete ( & irtoy - > command_done ) ;
} else {
dev_warn ( irtoy - > dev , " out urb status: %d \n " , urb - > status ) ;
}
}
static void irtoy_in_callback ( struct urb * urb )
{
struct irtoy * irtoy = urb - > context ;
int ret ;
2021-09-08 11:22:40 +02:00
switch ( urb - > status ) {
case 0 :
2020-05-03 11:27:55 +02:00
irtoy_response ( irtoy , urb - > actual_length ) ;
2021-09-08 11:22:40 +02:00
break ;
case - ECONNRESET :
case - ENOENT :
case - ESHUTDOWN :
case - EPROTO :
case - EPIPE :
usb_unlink_urb ( urb ) ;
return ;
default :
2020-05-03 11:27:55 +02:00
dev_dbg ( irtoy - > dev , " in urb status: %d \n " , urb - > status ) ;
2021-09-08 11:22:40 +02:00
}
2020-05-03 11:27:55 +02:00
ret = usb_submit_urb ( urb , GFP_ATOMIC ) ;
if ( ret & & ret ! = - ENODEV )
dev_warn ( irtoy - > dev , " failed to resubmit urb: %d \n " , ret ) ;
}
static int irtoy_command ( struct irtoy * irtoy , const u8 * cmd , int cmd_len ,
enum state state )
{
int err ;
init_completion ( & irtoy - > command_done ) ;
irtoy - > state = state ;
memcpy ( irtoy - > out , cmd , cmd_len ) ;
irtoy - > urb_out - > transfer_buffer_length = cmd_len ;
err = usb_submit_urb ( irtoy - > urb_out , GFP_KERNEL ) ;
if ( err ! = 0 )
return err ;
if ( ! wait_for_completion_timeout ( & irtoy - > command_done ,
msecs_to_jiffies ( TIMEOUT ) ) ) {
usb_kill_urb ( irtoy - > urb_out ) ;
return - ETIMEDOUT ;
}
return 0 ;
}
static int irtoy_setup ( struct irtoy * irtoy )
{
int err ;
err = irtoy_command ( irtoy , COMMAND_RESET , sizeof ( COMMAND_RESET ) ,
2021-08-10 20:08:01 +02:00
STATE_COMMAND_NO_RESP ) ;
2020-05-03 11:27:55 +02:00
if ( err ! = 0 ) {
dev_err ( irtoy - > dev , " could not write reset command: %d \n " ,
err ) ;
return err ;
}
usleep_range ( 50 , 50 ) ;
// get version
err = irtoy_command ( irtoy , COMMAND_VERSION , sizeof ( COMMAND_VERSION ) ,
STATE_COMMAND ) ;
if ( err ) {
dev_err ( irtoy - > dev , " could not write version command: %d \n " ,
err ) ;
return err ;
}
// enter sample mode
err = irtoy_command ( irtoy , COMMAND_SMODE_ENTER ,
sizeof ( COMMAND_SMODE_ENTER ) , STATE_COMMAND ) ;
if ( err )
dev_err ( irtoy - > dev , " could not write sample command: %d \n " ,
err ) ;
return err ;
}
/*
* When sending IR , it is imperative that we send the IR data as quickly
* as possible to the device , so it does not run out of IR data and
* introduce gaps . Allocate the buffer here , and then feed the data from
* the urb callback handler .
*/
static int irtoy_tx ( struct rc_dev * rc , uint * txbuf , uint count )
{
struct irtoy * irtoy = rc - > priv ;
unsigned int i , size ;
__be16 * buf ;
int err ;
size = sizeof ( u16 ) * ( count + 1 ) ;
buf = kmalloc ( size , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
for ( i = 0 ; i < count ; i + + ) {
2020-08-23 19:23:05 +02:00
u16 v = DIV_ROUND_CLOSEST ( txbuf [ i ] , UNIT_US ) ;
2020-05-03 11:27:55 +02:00
if ( ! v )
v = 1 ;
buf [ i ] = cpu_to_be16 ( v ) ;
}
2021-10-13 09:14:10 +01:00
buf [ count ] = cpu_to_be16 ( 0xffff ) ;
2020-05-03 11:27:55 +02:00
irtoy - > tx_buf = buf ;
irtoy - > tx_len = size ;
irtoy - > emitted = 0 ;
2021-09-14 16:57:46 +02:00
// There is an issue where if the unit is receiving IR while the
// first TXSTART command is sent, the device might end up hanging
// with its led on. It does not respond to any command when this
// happens. To work around this, re-enter sample mode.
err = irtoy_command ( irtoy , COMMAND_SMODE_EXIT ,
sizeof ( COMMAND_SMODE_EXIT ) , STATE_COMMAND_NO_RESP ) ;
if ( err ) {
dev_err ( irtoy - > dev , " exit sample mode: %d \n " , err ) ;
return err ;
}
err = irtoy_command ( irtoy , COMMAND_SMODE_ENTER ,
sizeof ( COMMAND_SMODE_ENTER ) , STATE_COMMAND ) ;
if ( err ) {
dev_err ( irtoy - > dev , " enter sample mode: %d \n " , err ) ;
return err ;
}
2020-05-03 11:27:55 +02:00
err = irtoy_command ( irtoy , COMMAND_TXSTART , sizeof ( COMMAND_TXSTART ) ,
STATE_TX ) ;
kfree ( buf ) ;
if ( err ) {
dev_err ( irtoy - > dev , " failed to send tx start command: %d \n " ,
err ) ;
// not sure what state the device is in, reset it
irtoy_setup ( irtoy ) ;
return err ;
}
if ( size ! = irtoy - > emitted ) {
dev_err ( irtoy - > dev , " expected %u emitted, got %u \n " , size ,
irtoy - > emitted ) ;
// not sure what state the device is in, reset it
irtoy_setup ( irtoy ) ;
return - EINVAL ;
}
return count ;
}
2021-08-10 20:08:01 +02:00
static int irtoy_tx_carrier ( struct rc_dev * rc , uint32_t carrier )
{
struct irtoy * irtoy = rc - > priv ;
u8 buf [ 3 ] ;
int err ;
if ( carrier < 11800 )
return - EINVAL ;
buf [ 0 ] = 0x06 ;
buf [ 1 ] = DIV_ROUND_CLOSEST ( 48000000 , 16 * carrier ) - 1 ;
buf [ 2 ] = 0 ;
err = irtoy_command ( irtoy , buf , sizeof ( buf ) , STATE_COMMAND_NO_RESP ) ;
if ( err )
dev_err ( irtoy - > dev , " could not write carrier command: %d \n " ,
err ) ;
return err ;
}
2020-05-03 11:27:55 +02:00
static int irtoy_probe ( struct usb_interface * intf ,
const struct usb_device_id * id )
{
struct usb_host_interface * idesc = intf - > cur_altsetting ;
struct usb_device * usbdev = interface_to_usbdev ( intf ) ;
struct usb_endpoint_descriptor * ep_in = NULL ;
struct usb_endpoint_descriptor * ep_out = NULL ;
struct usb_endpoint_descriptor * ep = NULL ;
struct irtoy * irtoy ;
struct rc_dev * rc ;
struct urb * urb ;
int i , pipe , err = - ENOMEM ;
for ( i = 0 ; i < idesc - > desc . bNumEndpoints ; i + + ) {
ep = & idesc - > endpoint [ i ] . desc ;
if ( ! ep_in & & usb_endpoint_is_bulk_in ( ep ) & &
usb_endpoint_maxp ( ep ) = = MAX_PACKET )
ep_in = ep ;
if ( ! ep_out & & usb_endpoint_is_bulk_out ( ep ) & &
usb_endpoint_maxp ( ep ) = = MAX_PACKET )
ep_out = ep ;
}
if ( ! ep_in | | ! ep_out ) {
dev_err ( & intf - > dev , " required endpoints not found \n " ) ;
return - ENODEV ;
}
irtoy = kzalloc ( sizeof ( * irtoy ) , GFP_KERNEL ) ;
if ( ! irtoy )
return - ENOMEM ;
irtoy - > in = kmalloc ( MAX_PACKET , GFP_KERNEL ) ;
if ( ! irtoy - > in )
goto free_irtoy ;
irtoy - > out = kmalloc ( MAX_PACKET , GFP_KERNEL ) ;
if ( ! irtoy - > out )
goto free_irtoy ;
rc = rc_allocate_device ( RC_DRIVER_IR_RAW ) ;
if ( ! rc )
goto free_irtoy ;
urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! urb )
goto free_rcdev ;
pipe = usb_rcvbulkpipe ( usbdev , ep_in - > bEndpointAddress ) ;
usb_fill_bulk_urb ( urb , usbdev , pipe , irtoy - > in , MAX_PACKET ,
irtoy_in_callback , irtoy ) ;
irtoy - > urb_in = urb ;
urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! urb )
goto free_rcdev ;
pipe = usb_sndbulkpipe ( usbdev , ep_out - > bEndpointAddress ) ;
usb_fill_bulk_urb ( urb , usbdev , pipe , irtoy - > out , MAX_PACKET ,
irtoy_out_callback , irtoy ) ;
irtoy - > dev = & intf - > dev ;
irtoy - > usbdev = usbdev ;
irtoy - > rc = rc ;
irtoy - > urb_out = urb ;
irtoy - > pulse = true ;
err = usb_submit_urb ( irtoy - > urb_in , GFP_KERNEL ) ;
if ( err ! = 0 ) {
dev_err ( irtoy - > dev , " fail to submit in urb: %d \n " , err ) ;
2021-12-29 02:15:18 +01:00
goto free_rcdev ;
2020-05-03 11:27:55 +02:00
}
err = irtoy_setup ( irtoy ) ;
if ( err )
goto free_rcdev ;
2021-09-02 11:50:23 +02:00
dev_info ( irtoy - > dev , " version: hardware %u, firmware %u.%u, protocol %u " ,
irtoy - > hw_version , irtoy - > sw_version / 10 ,
irtoy - > sw_version % 10 , irtoy - > proto_version ) ;
2020-05-03 11:27:55 +02:00
if ( irtoy - > sw_version < MIN_FW_VERSION ) {
dev_err ( irtoy - > dev , " need firmware V%02u or higher " ,
MIN_FW_VERSION ) ;
err = - ENODEV ;
goto free_rcdev ;
}
usb_make_path ( usbdev , irtoy - > phys , sizeof ( irtoy - > phys ) ) ;
rc - > device_name = " Infrared Toy " ;
rc - > driver_name = KBUILD_MODNAME ;
rc - > input_phys = irtoy - > phys ;
usb_to_input_id ( usbdev , & rc - > input_id ) ;
rc - > dev . parent = & intf - > dev ;
rc - > priv = irtoy ;
rc - > tx_ir = irtoy_tx ;
2021-08-10 20:08:01 +02:00
rc - > s_tx_carrier = irtoy_tx_carrier ;
2020-05-03 11:27:55 +02:00
rc - > allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER ;
rc - > map_name = RC_MAP_RC6_MCE ;
2020-08-23 19:23:05 +02:00
rc - > rx_resolution = UNIT_US ;
2020-05-03 11:27:55 +02:00
rc - > timeout = IR_DEFAULT_TIMEOUT ;
/*
* end of transmission is detected by absence of a usb packet
* with more pulse / spaces . However , each usb packet sent can
* contain 32 pulse / spaces , which can be quite lengthy , so there
* can be a delay between usb packets . For example with nec there is a
* 17 ms gap between packets .
*
* So , make timeout a largish minimum which works with most protocols .
*/
2020-08-23 19:23:05 +02:00
rc - > min_timeout = MS_TO_US ( 40 ) ;
rc - > max_timeout = MAX_TIMEOUT_US ;
2020-05-03 11:27:55 +02:00
err = rc_register_device ( rc ) ;
if ( err )
goto free_rcdev ;
usb_set_intfdata ( intf , irtoy ) ;
return 0 ;
free_rcdev :
usb_kill_urb ( irtoy - > urb_out ) ;
usb_free_urb ( irtoy - > urb_out ) ;
usb_kill_urb ( irtoy - > urb_in ) ;
usb_free_urb ( irtoy - > urb_in ) ;
rc_free_device ( rc ) ;
free_irtoy :
kfree ( irtoy - > in ) ;
kfree ( irtoy - > out ) ;
kfree ( irtoy ) ;
return err ;
}
static void irtoy_disconnect ( struct usb_interface * intf )
{
struct irtoy * ir = usb_get_intfdata ( intf ) ;
rc_unregister_device ( ir - > rc ) ;
usb_set_intfdata ( intf , NULL ) ;
usb_kill_urb ( ir - > urb_out ) ;
usb_free_urb ( ir - > urb_out ) ;
usb_kill_urb ( ir - > urb_in ) ;
usb_free_urb ( ir - > urb_in ) ;
kfree ( ir - > in ) ;
kfree ( ir - > out ) ;
kfree ( ir ) ;
}
static const struct usb_device_id irtoy_table [ ] = {
{ USB_DEVICE_INTERFACE_CLASS ( 0x04d8 , 0xfd08 , USB_CLASS_CDC_DATA ) } ,
2020-12-27 14:45:01 +01:00
{ USB_DEVICE_INTERFACE_CLASS ( 0x04d8 , 0xf58b , USB_CLASS_CDC_DATA ) } ,
2020-05-03 11:27:55 +02:00
{ }
} ;
static struct usb_driver irtoy_driver = {
. name = KBUILD_MODNAME ,
. probe = irtoy_probe ,
. disconnect = irtoy_disconnect ,
. id_table = irtoy_table ,
} ;
module_usb_driver ( irtoy_driver ) ;
MODULE_AUTHOR ( " Sean Young <sean@mess.org> " ) ;
MODULE_DESCRIPTION ( " Infrared Toy and IR Droid driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DEVICE_TABLE ( usb , irtoy_table ) ;