2019-05-27 08:55:06 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2012-08-13 08:59:46 -03:00
/*
* TechnoTrend USB IR Receiver
*
* Copyright ( C ) 2012 Sean Young < sean @ mess . org >
*/
# include <linux/module.h>
# include <linux/usb.h>
# include <linux/usb/input.h>
# include <linux/slab.h>
# include <linux/leds.h>
# include <media/rc-core.h>
# define DRIVER_NAME "ttusbir"
# define DRIVER_DESC "TechnoTrend USB IR Receiver"
/*
* The Windows driver uses 8 URBS , the original lirc drivers has a
* configurable amount ( 2 default , 4 max ) . This device generates about 125
* messages per second ( ! ) , whether IR is idle or not .
*/
# define NUM_URBS 4
2020-08-23 19:23:05 +02:00
# define US_PER_BYTE 62
# define US_PER_BIT (US_PER_BYTE / 8)
2012-08-13 08:59:46 -03:00
struct ttusbir {
struct rc_dev * rc ;
struct device * dev ;
struct usb_device * udev ;
struct urb * urb [ NUM_URBS ] ;
struct led_classdev led ;
struct urb * bulk_urb ;
uint8_t bulk_buffer [ 5 ] ;
int bulk_out_endp , iso_in_endp ;
bool led_on , is_led_on ;
atomic_t led_complete ;
char phys [ 64 ] ;
} ;
static enum led_brightness ttusbir_brightness_get ( struct led_classdev * led_dev )
{
struct ttusbir * tt = container_of ( led_dev , struct ttusbir , led ) ;
return tt - > led_on ? LED_FULL : LED_OFF ;
}
static void ttusbir_set_led ( struct ttusbir * tt )
{
int ret ;
smp_mb ( ) ;
2012-08-28 13:18:32 -03:00
if ( tt - > led_on ! = tt - > is_led_on & & tt - > udev & &
2012-08-13 08:59:46 -03:00
atomic_add_unless ( & tt - > led_complete , 1 , 1 ) ) {
tt - > bulk_buffer [ 4 ] = tt - > is_led_on = tt - > led_on ;
ret = usb_submit_urb ( tt - > bulk_urb , GFP_ATOMIC ) ;
2012-08-28 13:18:32 -03:00
if ( ret ) {
2012-08-13 08:59:46 -03:00
dev_warn ( tt - > dev , " failed to submit bulk urb: %d \n " ,
ret ) ;
atomic_dec ( & tt - > led_complete ) ;
}
}
}
static void ttusbir_brightness_set ( struct led_classdev * led_dev , enum
led_brightness brightness )
{
struct ttusbir * tt = container_of ( led_dev , struct ttusbir , led ) ;
tt - > led_on = brightness ! = LED_OFF ;
ttusbir_set_led ( tt ) ;
}
/*
* The urb cannot be reused until the urb completes
*/
static void ttusbir_bulk_complete ( struct urb * urb )
{
struct ttusbir * tt = urb - > context ;
atomic_dec ( & tt - > led_complete ) ;
switch ( urb - > status ) {
case 0 :
break ;
case - ECONNRESET :
case - ENOENT :
case - ESHUTDOWN :
usb_unlink_urb ( urb ) ;
return ;
case - EPIPE :
default :
dev_dbg ( tt - > dev , " Error: urb status = %d \n " , urb - > status ) ;
break ;
}
ttusbir_set_led ( tt ) ;
}
/*
* The data is one bit per sample , a set bit signifying silence and samples
* being MSB first . Bit 0 can contain garbage so take it to be whatever
* bit 1 is , so we don ' t have unexpected edges .
*/
static void ttusbir_process_ir_data ( struct ttusbir * tt , uint8_t * buf )
{
2018-08-21 15:57:52 -04:00
struct ir_raw_event rawir = { } ;
2012-08-13 08:59:46 -03:00
unsigned i , v , b ;
2012-08-13 08:59:47 -03:00
bool event = false ;
2012-08-13 08:59:46 -03:00
for ( i = 0 ; i < 128 ; i + + ) {
v = buf [ i ] & 0xfe ;
switch ( v ) {
case 0xfe :
rawir . pulse = false ;
2020-08-23 19:23:05 +02:00
rawir . duration = US_PER_BYTE ;
2012-08-13 08:59:47 -03:00
if ( ir_raw_event_store_with_filter ( tt - > rc , & rawir ) )
event = true ;
2012-08-13 08:59:46 -03:00
break ;
case 0 :
rawir . pulse = true ;
2020-08-23 19:23:05 +02:00
rawir . duration = US_PER_BYTE ;
2012-08-13 08:59:47 -03:00
if ( ir_raw_event_store_with_filter ( tt - > rc , & rawir ) )
event = true ;
2012-08-13 08:59:46 -03:00
break ;
default :
/* one edge per byte */
if ( v & 2 ) {
b = ffz ( v | 1 ) ;
rawir . pulse = true ;
} else {
b = ffs ( v ) - 1 ;
rawir . pulse = false ;
}
2020-08-23 19:23:05 +02:00
rawir . duration = US_PER_BIT * ( 8 - b ) ;
2012-08-13 08:59:47 -03:00
if ( ir_raw_event_store_with_filter ( tt - > rc , & rawir ) )
event = true ;
2012-08-13 08:59:46 -03:00
rawir . pulse = ! rawir . pulse ;
2020-08-23 19:23:05 +02:00
rawir . duration = US_PER_BIT * b ;
2012-08-13 08:59:47 -03:00
if ( ir_raw_event_store_with_filter ( tt - > rc , & rawir ) )
event = true ;
2012-08-13 08:59:46 -03:00
break ;
}
}
2012-08-13 08:59:47 -03:00
/* don't wakeup when there's nothing to do */
if ( event )
ir_raw_event_handle ( tt - > rc ) ;
2012-08-13 08:59:46 -03:00
}
static void ttusbir_urb_complete ( struct urb * urb )
{
struct ttusbir * tt = urb - > context ;
int rc ;
switch ( urb - > status ) {
case 0 :
ttusbir_process_ir_data ( tt , urb - > transfer_buffer ) ;
break ;
case - ECONNRESET :
case - ENOENT :
case - ESHUTDOWN :
usb_unlink_urb ( urb ) ;
return ;
case - EPIPE :
default :
dev_dbg ( tt - > dev , " Error: urb status = %d \n " , urb - > status ) ;
break ;
}
rc = usb_submit_urb ( urb , GFP_ATOMIC ) ;
if ( rc & & rc ! = - ENODEV )
dev_warn ( tt - > dev , " failed to resubmit urb: %d \n " , rc ) ;
}
2012-12-21 13:17:53 -08:00
static int ttusbir_probe ( struct usb_interface * intf ,
const struct usb_device_id * id )
2012-08-13 08:59:46 -03:00
{
struct ttusbir * tt ;
struct usb_interface_descriptor * idesc ;
struct usb_endpoint_descriptor * desc ;
struct rc_dev * rc ;
int i , j , ret ;
int altsetting = - 1 ;
tt = kzalloc ( sizeof ( * tt ) , GFP_KERNEL ) ;
2016-12-16 06:50:58 -02:00
rc = rc_allocate_device ( RC_DRIVER_IR_RAW ) ;
2012-08-13 08:59:46 -03:00
if ( ! tt | | ! rc ) {
ret = - ENOMEM ;
goto out ;
}
/* find the correct alt setting */
for ( i = 0 ; i < intf - > num_altsetting & & altsetting = = - 1 ; i + + ) {
2013-01-29 08:19:28 -03:00
int max_packet , bulk_out_endp = - 1 , iso_in_endp = - 1 ;
2012-08-13 08:59:46 -03:00
idesc = & intf - > altsetting [ i ] . desc ;
for ( j = 0 ; j < idesc - > bNumEndpoints ; j + + ) {
desc = & intf - > altsetting [ i ] . endpoint [ j ] . desc ;
2013-01-29 08:19:28 -03:00
max_packet = le16_to_cpu ( desc - > wMaxPacketSize ) ;
2012-08-13 08:59:46 -03:00
if ( usb_endpoint_dir_in ( desc ) & &
usb_endpoint_xfer_isoc ( desc ) & &
2013-01-29 08:19:28 -03:00
max_packet = = 0x10 )
2012-08-13 08:59:46 -03:00
iso_in_endp = j ;
else if ( usb_endpoint_dir_out ( desc ) & &
usb_endpoint_xfer_bulk ( desc ) & &
2013-01-29 08:19:28 -03:00
max_packet = = 0x20 )
2012-08-13 08:59:46 -03:00
bulk_out_endp = j ;
if ( bulk_out_endp ! = - 1 & & iso_in_endp ! = - 1 ) {
tt - > bulk_out_endp = bulk_out_endp ;
tt - > iso_in_endp = iso_in_endp ;
altsetting = i ;
break ;
}
}
}
if ( altsetting = = - 1 ) {
dev_err ( & intf - > dev , " cannot find expected altsetting \n " ) ;
ret = - ENODEV ;
goto out ;
}
tt - > dev = & intf - > dev ;
tt - > udev = interface_to_usbdev ( intf ) ;
tt - > rc = rc ;
ret = usb_set_interface ( tt - > udev , 0 , altsetting ) ;
if ( ret )
goto out ;
for ( i = 0 ; i < NUM_URBS ; i + + ) {
struct urb * urb = usb_alloc_urb ( 8 , GFP_KERNEL ) ;
void * buffer ;
if ( ! urb ) {
ret = - ENOMEM ;
goto out ;
}
urb - > dev = tt - > udev ;
urb - > context = tt ;
urb - > pipe = usb_rcvisocpipe ( tt - > udev , tt - > iso_in_endp ) ;
urb - > interval = 1 ;
buffer = usb_alloc_coherent ( tt - > udev , 128 , GFP_KERNEL ,
& urb - > transfer_dma ) ;
if ( ! buffer ) {
usb_free_urb ( urb ) ;
ret = - ENOMEM ;
goto out ;
}
urb - > transfer_flags = URB_NO_TRANSFER_DMA_MAP | URB_ISO_ASAP ;
urb - > transfer_buffer = buffer ;
urb - > complete = ttusbir_urb_complete ;
urb - > number_of_packets = 8 ;
urb - > transfer_buffer_length = 128 ;
for ( j = 0 ; j < 8 ; j + + ) {
urb - > iso_frame_desc [ j ] . offset = j * 16 ;
urb - > iso_frame_desc [ j ] . length = 16 ;
}
tt - > urb [ i ] = urb ;
}
tt - > bulk_urb = usb_alloc_urb ( 0 , GFP_KERNEL ) ;
if ( ! tt - > bulk_urb ) {
ret = - ENOMEM ;
goto out ;
}
tt - > bulk_buffer [ 0 ] = 0xaa ;
tt - > bulk_buffer [ 1 ] = 0x01 ;
tt - > bulk_buffer [ 2 ] = 0x05 ;
tt - > bulk_buffer [ 3 ] = 0x01 ;
usb_fill_bulk_urb ( tt - > bulk_urb , tt - > udev , usb_sndbulkpipe ( tt - > udev ,
tt - > bulk_out_endp ) , tt - > bulk_buffer , sizeof ( tt - > bulk_buffer ) ,
ttusbir_bulk_complete , tt ) ;
2012-08-28 13:18:32 -03:00
tt - > led . name = " ttusbir:green:power " ;
2013-07-30 19:00:02 -03:00
tt - > led . default_trigger = " rc-feedback " ;
2012-08-13 08:59:46 -03:00
tt - > led . brightness_set = ttusbir_brightness_set ;
tt - > led . brightness_get = ttusbir_brightness_get ;
tt - > is_led_on = tt - > led_on = true ;
atomic_set ( & tt - > led_complete , 0 ) ;
ret = led_classdev_register ( & intf - > dev , & tt - > led ) ;
if ( ret )
goto out ;
usb_make_path ( tt - > udev , tt - > phys , sizeof ( tt - > phys ) ) ;
2017-07-01 12:13:19 -04:00
rc - > device_name = DRIVER_DESC ;
2012-08-13 08:59:46 -03:00
rc - > input_phys = tt - > phys ;
usb_to_input_id ( tt - > udev , & rc - > input_id ) ;
rc - > dev . parent = & intf - > dev ;
2017-08-07 16:20:58 -04:00
rc - > allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER ;
2012-08-13 08:59:46 -03:00
rc - > priv = tt ;
rc - > driver_name = DRIVER_NAME ;
rc - > map_name = RC_MAP_TT_1500 ;
2016-12-02 15:16:12 -02:00
rc - > min_timeout = 1 ;
rc - > timeout = IR_DEFAULT_TIMEOUT ;
rc - > max_timeout = 10 * IR_DEFAULT_TIMEOUT ;
2012-08-13 08:59:46 -03:00
/*
2020-08-23 19:23:05 +02:00
* The precision is US_PER_BIT , but since every 8 th bit can be
* overwritten with garbage the accuracy is at best 2 * US_PER_BIT .
2012-08-13 08:59:46 -03:00
*/
2020-08-23 19:23:05 +02:00
rc - > rx_resolution = 2 * US_PER_BIT ;
2012-08-13 08:59:46 -03:00
ret = rc_register_device ( rc ) ;
if ( ret ) {
dev_err ( & intf - > dev , " failed to register rc device %d \n " , ret ) ;
goto out2 ;
}
usb_set_intfdata ( intf , tt ) ;
for ( i = 0 ; i < NUM_URBS ; i + + ) {
ret = usb_submit_urb ( tt - > urb [ i ] , GFP_KERNEL ) ;
if ( ret ) {
dev_err ( tt - > dev , " failed to submit urb %d \n " , ret ) ;
goto out3 ;
}
}
return 0 ;
out3 :
rc_unregister_device ( rc ) ;
2013-04-04 16:32:09 -03:00
rc = NULL ;
2012-08-13 08:59:46 -03:00
out2 :
led_classdev_unregister ( & tt - > led ) ;
out :
if ( tt ) {
for ( i = 0 ; i < NUM_URBS & & tt - > urb [ i ] ; i + + ) {
struct urb * urb = tt - > urb [ i ] ;
usb_kill_urb ( urb ) ;
usb_free_coherent ( tt - > udev , 128 , urb - > transfer_buffer ,
urb - > transfer_dma ) ;
usb_free_urb ( urb ) ;
}
usb_kill_urb ( tt - > bulk_urb ) ;
usb_free_urb ( tt - > bulk_urb ) ;
kfree ( tt ) ;
}
rc_free_device ( rc ) ;
return ret ;
}
2012-12-21 13:17:53 -08:00
static void ttusbir_disconnect ( struct usb_interface * intf )
2012-08-13 08:59:46 -03:00
{
struct ttusbir * tt = usb_get_intfdata ( intf ) ;
2012-08-28 13:18:32 -03:00
struct usb_device * udev = tt - > udev ;
2012-08-13 08:59:46 -03:00
int i ;
2012-08-28 13:18:32 -03:00
tt - > udev = NULL ;
2012-08-13 08:59:46 -03:00
rc_unregister_device ( tt - > rc ) ;
led_classdev_unregister ( & tt - > led ) ;
for ( i = 0 ; i < NUM_URBS ; i + + ) {
usb_kill_urb ( tt - > urb [ i ] ) ;
2012-08-28 13:18:32 -03:00
usb_free_coherent ( udev , 128 , tt - > urb [ i ] - > transfer_buffer ,
2012-08-13 08:59:46 -03:00
tt - > urb [ i ] - > transfer_dma ) ;
usb_free_urb ( tt - > urb [ i ] ) ;
}
usb_kill_urb ( tt - > bulk_urb ) ;
usb_free_urb ( tt - > bulk_urb ) ;
usb_set_intfdata ( intf , NULL ) ;
kfree ( tt ) ;
}
2012-08-28 13:18:32 -03:00
static int ttusbir_suspend ( struct usb_interface * intf , pm_message_t message )
{
struct ttusbir * tt = usb_get_intfdata ( intf ) ;
int i ;
for ( i = 0 ; i < NUM_URBS ; i + + )
usb_kill_urb ( tt - > urb [ i ] ) ;
led_classdev_suspend ( & tt - > led ) ;
usb_kill_urb ( tt - > bulk_urb ) ;
return 0 ;
}
static int ttusbir_resume ( struct usb_interface * intf )
{
struct ttusbir * tt = usb_get_intfdata ( intf ) ;
int i , rc ;
tt - > is_led_on = true ;
2013-01-29 08:19:27 -03:00
led_classdev_resume ( & tt - > led ) ;
2012-08-28 13:18:32 -03:00
for ( i = 0 ; i < NUM_URBS ; i + + ) {
rc = usb_submit_urb ( tt - > urb [ i ] , GFP_KERNEL ) ;
if ( rc ) {
dev_warn ( tt - > dev , " failed to submit urb: %d \n " , rc ) ;
break ;
}
}
return rc ;
}
2012-08-13 08:59:46 -03:00
static const struct usb_device_id ttusbir_table [ ] = {
{ USB_DEVICE ( 0x0b48 , 0x2003 ) } ,
{ }
} ;
static struct usb_driver ttusbir_driver = {
. name = DRIVER_NAME ,
. id_table = ttusbir_table ,
. probe = ttusbir_probe ,
2012-08-28 13:18:32 -03:00
. suspend = ttusbir_suspend ,
. resume = ttusbir_resume ,
. reset_resume = ttusbir_resume ,
2012-12-21 13:17:53 -08:00
. disconnect = ttusbir_disconnect ,
2012-08-13 08:59:46 -03:00
} ;
module_usb_driver ( ttusbir_driver ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_AUTHOR ( " Sean Young <sean@mess.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DEVICE_TABLE ( usb , ttusbir_table ) ;