2005-04-17 02:20:36 +04:00
/*
* Force feedback support for various HID compliant devices by ThrustMaster :
* ThrustMaster FireStorm Dual Power 2
* and possibly others whose device ids haven ' t been added .
*
* Modified to support ThrustMaster devices by Zinx Verituse
* on 2003 - 01 - 25 from the Logitech force feedback driver ,
* which is by Johann Deneux .
*
* Copyright ( c ) 2003 Zinx Verituse < zinx @ epicsol . org >
* Copyright ( c ) 2002 Johann Deneux
*/
/*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/input.h>
# include <linux/sched.h>
# undef DEBUG
# include <linux/usb.h>
# include <linux/circ_buf.h>
# include "hid.h"
2006-07-19 09:40:14 +04:00
# include "../../input/fixp-arith.h"
2005-04-17 02:20:36 +04:00
/* Usages for thrustmaster devices I know about */
# define THRUSTMASTER_USAGE_RUMBLE_LR (HID_UP_GENDESK | 0xbb)
# define DELAY_CALC(t,delay) ((t) + (delay)*HZ / 1000)
/* Effect status */
# define EFFECT_STARTED 0 /* Effect is going to play after some time */
# define EFFECT_PLAYING 1 /* Effect is playing */
# define EFFECT_USED 2
/* For tmff_device::flags */
# define DEVICE_CLOSING 0 /* The driver is being unitialised */
/* Check that the current process can access an effect */
# define CHECK_OWNERSHIP(effect) (current->pid == 0 \
| | effect . owner = = current - > pid )
# define TMFF_CHECK_ID(id) ((id) >= 0 && (id) < TMFF_EFFECTS)
# define TMFF_CHECK_OWNERSHIP(i, l) \
( test_bit ( EFFECT_USED , l - > effects [ i ] . flags ) \
& & CHECK_OWNERSHIP ( l - > effects [ i ] ) )
# define TMFF_EFFECTS 8
struct tmff_effect {
pid_t owner ;
struct ff_effect effect ;
unsigned long flags [ 1 ] ;
unsigned int count ; /* Number of times left to play */
unsigned long play_at ; /* When the effect starts to play */
unsigned long stop_at ; /* When the effect ends */
} ;
struct tmff_device {
struct hid_device * hid ;
struct hid_report * report ;
struct hid_field * rumble ;
unsigned int effects_playing ;
struct tmff_effect effects [ TMFF_EFFECTS ] ;
spinlock_t lock ; /* device-level lock. Having locks on
a per - effect basis could be nice , but
isn ' t really necessary */
unsigned long flags [ 1 ] ; /* Contains various information about the
state of the driver for this device */
struct timer_list timer ;
} ;
/* Callbacks */
static void hid_tmff_exit ( struct hid_device * hid ) ;
static int hid_tmff_event ( struct hid_device * hid , struct input_dev * input ,
unsigned int type , unsigned int code , int value ) ;
static int hid_tmff_flush ( struct input_dev * input , struct file * file ) ;
static int hid_tmff_upload_effect ( struct input_dev * input ,
struct ff_effect * effect ) ;
static int hid_tmff_erase ( struct input_dev * input , int id ) ;
/* Local functions */
static void hid_tmff_recalculate_timer ( struct tmff_device * tmff ) ;
static void hid_tmff_timer ( unsigned long timer_data ) ;
int hid_tmff_init ( struct hid_device * hid )
{
struct tmff_device * private ;
struct list_head * pos ;
struct hid_input * hidinput = list_entry ( hid - > inputs . next , struct hid_input , list ) ;
2005-09-15 11:01:47 +04:00
struct input_dev * input_dev = hidinput - > input ;
2005-04-17 02:20:36 +04:00
2006-01-06 22:54:29 +03:00
private = kzalloc ( sizeof ( struct tmff_device ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! private )
return - ENOMEM ;
hid - > ff_private = private ;
/* Find the report to use */
__list_for_each ( pos , & hid - > report_enum [ HID_OUTPUT_REPORT ] . report_list ) {
struct hid_report * report = ( struct hid_report * ) pos ;
int fieldnum ;
for ( fieldnum = 0 ; fieldnum < report - > maxfield ; + + fieldnum ) {
struct hid_field * field = report - > field [ fieldnum ] ;
if ( field - > maxusage < = 0 )
continue ;
switch ( field - > usage [ 0 ] . hid ) {
case THRUSTMASTER_USAGE_RUMBLE_LR :
if ( field - > report_count < 2 ) {
warn ( " ignoring THRUSTMASTER_USAGE_RUMBLE_LR with report_count < 2 " ) ;
continue ;
}
if ( field - > logical_maximum = = field - > logical_minimum ) {
warn ( " ignoring THRUSTMASTER_USAGE_RUMBLE_LR with logical_maximum == logical_minimum " ) ;
continue ;
}
if ( private - > report & & private - > report ! = report ) {
warn ( " ignoring THRUSTMASTER_USAGE_RUMBLE_LR in other report " ) ;
continue ;
}
if ( private - > rumble & & private - > rumble ! = field ) {
warn ( " ignoring duplicate THRUSTMASTER_USAGE_RUMBLE_LR " ) ;
continue ;
}
private - > report = report ;
private - > rumble = field ;
2005-09-15 11:01:47 +04:00
set_bit ( FF_RUMBLE , input_dev - > ffbit ) ;
2005-04-17 02:20:36 +04:00
break ;
default :
warn ( " ignoring unknown output usage %08x " , field - > usage [ 0 ] . hid ) ;
continue ;
}
/* Fallthrough to here only when a valid usage is found */
2005-09-15 11:01:47 +04:00
input_dev - > upload_effect = hid_tmff_upload_effect ;
input_dev - > flush = hid_tmff_flush ;
2005-04-17 02:20:36 +04:00
2005-09-15 11:01:47 +04:00
set_bit ( EV_FF , input_dev - > evbit ) ;
input_dev - > ff_effects_max = TMFF_EFFECTS ;
2005-04-17 02:20:36 +04:00
}
}
private - > hid = hid ;
spin_lock_init ( & private - > lock ) ;
init_timer ( & private - > timer ) ;
private - > timer . data = ( unsigned long ) private ;
private - > timer . function = hid_tmff_timer ;
/* Event and exit callbacks */
hid - > ff_exit = hid_tmff_exit ;
hid - > ff_event = hid_tmff_event ;
info ( " Force feedback for ThrustMaster rumble pad devices by Zinx Verituse <zinx@epicsol.org> " ) ;
return 0 ;
}
static void hid_tmff_exit ( struct hid_device * hid )
{
struct tmff_device * tmff = hid - > ff_private ;
unsigned long flags ;
spin_lock_irqsave ( & tmff - > lock , flags ) ;
set_bit ( DEVICE_CLOSING , tmff - > flags ) ;
del_timer_sync ( & tmff - > timer ) ;
spin_unlock_irqrestore ( & tmff - > lock , flags ) ;
kfree ( tmff ) ;
}
static int hid_tmff_event ( struct hid_device * hid , struct input_dev * input ,
unsigned int type , unsigned int code , int value )
{
struct tmff_device * tmff = hid - > ff_private ;
struct tmff_effect * effect = & tmff - > effects [ code ] ;
unsigned long flags ;
if ( type ! = EV_FF )
return - EINVAL ;
if ( ! TMFF_CHECK_ID ( code ) )
return - EINVAL ;
if ( ! TMFF_CHECK_OWNERSHIP ( code , tmff ) )
return - EACCES ;
if ( value < 0 )
return - EINVAL ;
spin_lock_irqsave ( & tmff - > lock , flags ) ;
if ( value > 0 ) {
set_bit ( EFFECT_STARTED , effect - > flags ) ;
clear_bit ( EFFECT_PLAYING , effect - > flags ) ;
effect - > count = value ;
effect - > play_at = DELAY_CALC ( jiffies , effect - > effect . replay . delay ) ;
} else {
clear_bit ( EFFECT_STARTED , effect - > flags ) ;
clear_bit ( EFFECT_PLAYING , effect - > flags ) ;
}
hid_tmff_recalculate_timer ( tmff ) ;
spin_unlock_irqrestore ( & tmff - > lock , flags ) ;
return 0 ;
}
/* Erase all effects this process owns */
static int hid_tmff_flush ( struct input_dev * dev , struct file * file )
{
struct hid_device * hid = dev - > private ;
struct tmff_device * tmff = hid - > ff_private ;
int i ;
for ( i = 0 ; i < dev - > ff_effects_max ; + + i )
/* NOTE: no need to lock here. The only times EFFECT_USED is
modified is when effects are uploaded or when an effect is
erased . But a process cannot close its dev / input / eventX fd
and perform ioctls on the same fd all at the same time */
if ( current - > pid = = tmff - > effects [ i ] . owner
& & test_bit ( EFFECT_USED , tmff - > effects [ i ] . flags ) )
if ( hid_tmff_erase ( dev , i ) )
warn ( " erase effect %d failed " , i ) ;
return 0 ;
}
static int hid_tmff_erase ( struct input_dev * dev , int id )
{
struct hid_device * hid = dev - > private ;
struct tmff_device * tmff = hid - > ff_private ;
unsigned long flags ;
if ( ! TMFF_CHECK_ID ( id ) )
return - EINVAL ;
if ( ! TMFF_CHECK_OWNERSHIP ( id , tmff ) )
return - EACCES ;
spin_lock_irqsave ( & tmff - > lock , flags ) ;
tmff - > effects [ id ] . flags [ 0 ] = 0 ;
hid_tmff_recalculate_timer ( tmff ) ;
spin_unlock_irqrestore ( & tmff - > lock , flags ) ;
return 0 ;
}
static int hid_tmff_upload_effect ( struct input_dev * input ,
struct ff_effect * effect )
{
struct hid_device * hid = input - > private ;
struct tmff_device * tmff = hid - > ff_private ;
int id ;
unsigned long flags ;
if ( ! test_bit ( effect - > type , input - > ffbit ) )
return - EINVAL ;
if ( effect - > id ! = - 1 & & ! TMFF_CHECK_ID ( effect - > id ) )
return - EINVAL ;
spin_lock_irqsave ( & tmff - > lock , flags ) ;
if ( effect - > id = = - 1 ) {
/* Find a free effect */
for ( id = 0 ; id < TMFF_EFFECTS & & test_bit ( EFFECT_USED , tmff - > effects [ id ] . flags ) ; + + id ) ;
if ( id > = TMFF_EFFECTS ) {
spin_unlock_irqrestore ( & tmff - > lock , flags ) ;
return - ENOSPC ;
}
effect - > id = id ;
tmff - > effects [ id ] . owner = current - > pid ;
tmff - > effects [ id ] . flags [ 0 ] = 0 ;
set_bit ( EFFECT_USED , tmff - > effects [ id ] . flags ) ;
} else {
/* Re-uploading an owned effect, to change parameters */
id = effect - > id ;
clear_bit ( EFFECT_PLAYING , tmff - > effects [ id ] . flags ) ;
}
tmff - > effects [ id ] . effect = * effect ;
hid_tmff_recalculate_timer ( tmff ) ;
spin_unlock_irqrestore ( & tmff - > lock , flags ) ;
return 0 ;
}
/* Start the timer for the next start/stop/delay */
/* Always call this while tmff->lock is locked */
static void hid_tmff_recalculate_timer ( struct tmff_device * tmff )
{
int i ;
int events = 0 ;
unsigned long next_time ;
next_time = 0 ; /* Shut up compiler's incorrect warning */
/* Find the next change in an effect's status */
for ( i = 0 ; i < TMFF_EFFECTS ; + + i ) {
struct tmff_effect * effect = & tmff - > effects [ i ] ;
unsigned long play_time ;
if ( ! test_bit ( EFFECT_STARTED , effect - > flags ) )
continue ;
effect - > stop_at = DELAY_CALC ( effect - > play_at , effect - > effect . replay . length ) ;
if ( ! test_bit ( EFFECT_PLAYING , effect - > flags ) )
play_time = effect - > play_at ;
else
play_time = effect - > stop_at ;
events + + ;
if ( time_after ( jiffies , play_time ) )
play_time = jiffies ;
if ( events = = 1 )
next_time = play_time ;
else {
if ( time_after ( next_time , play_time ) )
next_time = play_time ;
}
}
if ( ! events & & tmff - > effects_playing ) {
/* Treat all effects turning off as an event */
events = 1 ;
next_time = jiffies ;
}
if ( ! events ) {
/* No events, no time, no need for a timer. */
del_timer_sync ( & tmff - > timer ) ;
return ;
}
mod_timer ( & tmff - > timer , next_time ) ;
}
/* Changes values from 0 to 0xffff into values from minimum to maximum */
static inline int hid_tmff_scale ( unsigned int in , int minimum , int maximum )
{
int ret ;
ret = ( in * ( maximum - minimum ) / 0xffff ) + minimum ;
if ( ret < minimum )
return minimum ;
if ( ret > maximum )
return maximum ;
return ret ;
}
static void hid_tmff_timer ( unsigned long timer_data )
{
struct tmff_device * tmff = ( struct tmff_device * ) timer_data ;
struct hid_device * hid = tmff - > hid ;
unsigned long flags ;
int left = 0 , right = 0 ; /* Rumbling */
int i ;
spin_lock_irqsave ( & tmff - > lock , flags ) ;
tmff - > effects_playing = 0 ;
for ( i = 0 ; i < TMFF_EFFECTS ; + + i ) {
struct tmff_effect * effect = & tmff - > effects [ i ] ;
if ( ! test_bit ( EFFECT_STARTED , effect - > flags ) )
continue ;
if ( ! time_after ( jiffies , effect - > play_at ) )
continue ;
if ( time_after ( jiffies , effect - > stop_at ) ) {
dbg ( " Finished playing once %d " , i ) ;
clear_bit ( EFFECT_PLAYING , effect - > flags ) ;
if ( - - effect - > count < = 0 ) {
dbg ( " Stopped %d " , i ) ;
clear_bit ( EFFECT_STARTED , effect - > flags ) ;
continue ;
} else {
dbg ( " Start again %d " , i ) ;
effect - > play_at = DELAY_CALC ( jiffies , effect - > effect . replay . delay ) ;
continue ;
}
}
+ + tmff - > effects_playing ;
set_bit ( EFFECT_PLAYING , effect - > flags ) ;
switch ( effect - > effect . type ) {
case FF_RUMBLE :
right + = effect - > effect . u . rumble . strong_magnitude ;
left + = effect - > effect . u . rumble . weak_magnitude ;
break ;
default :
BUG ( ) ;
break ;
}
}
left = hid_tmff_scale ( left , tmff - > rumble - > logical_minimum , tmff - > rumble - > logical_maximum ) ;
right = hid_tmff_scale ( right , tmff - > rumble - > logical_minimum , tmff - > rumble - > logical_maximum ) ;
if ( left ! = tmff - > rumble - > value [ 0 ] | | right ! = tmff - > rumble - > value [ 1 ] ) {
tmff - > rumble - > value [ 0 ] = left ;
tmff - > rumble - > value [ 1 ] = right ;
dbg ( " (left,right)=(%08x, %08x) " , left , right ) ;
hid_submit_report ( hid , tmff - > report , USB_DIR_OUT ) ;
}
if ( ! test_bit ( DEVICE_CLOSING , tmff - > flags ) )
hid_tmff_recalculate_timer ( tmff ) ;
spin_unlock_irqrestore ( & tmff - > lock , flags ) ;
}