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
*/
2008-09-18 14:23:31 +04:00
# include <linux/hid.h>
2005-04-17 02:20:36 +04:00
# include <linux/input.h>
# include <linux/usb.h>
2008-09-18 14:23:31 +04:00
# include "hid-ids.h"
2007-07-30 16:56:26 +04:00
static const signed short ff_rumble [ ] = {
FF_RUMBLE ,
- 1
} ;
static const signed short ff_joystick [ ] = {
FF_CONSTANT ,
- 1
} ;
2009-05-15 17:46:44 +04:00
# ifdef CONFIG_THRUSTMASTER_FF
# include "usbhid/usbhid.h"
/* Usages for thrustmaster devices I know about */
# define THRUSTMASTER_USAGE_FF (HID_UP_GENDESK | 0xbb)
2005-04-17 02:20:36 +04:00
struct tmff_device {
struct hid_report * report ;
2007-07-30 16:56:26 +04:00
struct hid_field * ff_field ;
2006-07-19 09:40:55 +04:00
} ;
2005-04-17 02:20:36 +04:00
2006-07-19 09:40:55 +04:00
/* Changes values from 0 to 0xffff into values from minimum to maximum */
2008-09-18 14:23:31 +04:00
static inline int tmff_scale_u16 ( unsigned int in , int minimum , int maximum )
2006-07-19 09:40:55 +04:00
{
int ret ;
2005-04-17 02:20:36 +04:00
2006-07-19 09:40:55 +04:00
ret = ( in * ( maximum - minimum ) / 0xffff ) + minimum ;
if ( ret < minimum )
return minimum ;
if ( ret > maximum )
return maximum ;
return ret ;
}
2005-04-17 02:20:36 +04:00
2007-07-30 16:56:26 +04:00
/* Changes values from -0x80 to 0x7f into values from minimum to maximum */
2008-09-18 14:23:31 +04:00
static inline int tmff_scale_s8 ( int in , int minimum , int maximum )
2007-07-30 16:56:26 +04:00
{
int ret ;
ret = ( ( ( in + 0x80 ) * ( maximum - minimum ) ) / 0xff ) + minimum ;
if ( ret < minimum )
return minimum ;
if ( ret > maximum )
return maximum ;
return ret ;
}
2008-09-18 14:23:31 +04:00
static int tmff_play ( struct input_dev * dev , void * data ,
struct ff_effect * effect )
2006-07-19 09:40:55 +04:00
{
2007-05-09 12:17:31 +04:00
struct hid_device * hid = input_get_drvdata ( dev ) ;
2006-07-19 09:40:55 +04:00
struct tmff_device * tmff = data ;
2007-07-30 16:56:26 +04:00
struct hid_field * ff_field = tmff - > ff_field ;
int x , y ;
2006-07-19 09:40:55 +04:00
int left , right ; /* Rumbling */
2005-04-17 02:20:36 +04:00
2007-07-30 16:56:26 +04:00
switch ( effect - > type ) {
case FF_CONSTANT :
2008-09-18 14:23:31 +04:00
x = tmff_scale_s8 ( effect - > u . ramp . start_level ,
2007-07-30 16:56:26 +04:00
ff_field - > logical_minimum ,
ff_field - > logical_maximum ) ;
2008-09-18 14:23:31 +04:00
y = tmff_scale_s8 ( effect - > u . ramp . end_level ,
2007-07-30 16:56:26 +04:00
ff_field - > logical_minimum ,
ff_field - > logical_maximum ) ;
dbg_hid ( " (x, y)=(%04x, %04x) \n " , x , y ) ;
ff_field - > value [ 0 ] = x ;
ff_field - > value [ 1 ] = y ;
usbhid_submit_report ( hid , tmff - > report , USB_DIR_OUT ) ;
break ;
case FF_RUMBLE :
2008-09-18 14:23:31 +04:00
left = tmff_scale_u16 ( effect - > u . rumble . weak_magnitude ,
2007-07-30 16:56:26 +04:00
ff_field - > logical_minimum ,
ff_field - > logical_maximum ) ;
2008-09-18 14:23:31 +04:00
right = tmff_scale_u16 ( effect - > u . rumble . strong_magnitude ,
2007-07-30 16:56:26 +04:00
ff_field - > logical_minimum ,
ff_field - > logical_maximum ) ;
dbg_hid ( " (left,right)=(%08x, %08x) \n " , left , right ) ;
ff_field - > value [ 0 ] = left ;
ff_field - > value [ 1 ] = right ;
usbhid_submit_report ( hid , tmff - > report , USB_DIR_OUT ) ;
break ;
}
2006-07-19 09:40:55 +04:00
return 0 ;
}
2005-04-17 02:20:36 +04:00
2008-09-18 14:23:31 +04:00
static int tmff_init ( struct hid_device * hid , const signed short * ff_bits )
2005-04-17 02:20:36 +04:00
{
2006-07-19 09:40:55 +04:00
struct tmff_device * tmff ;
2007-11-14 13:31:05 +03:00
struct hid_report * report ;
struct list_head * report_list ;
2008-09-18 14:23:31 +04:00
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 ;
2006-07-19 09:40:55 +04:00
int error ;
2007-07-30 16:56:26 +04:00
int i ;
2005-04-17 02:20:36 +04:00
2006-07-19 09:40:55 +04:00
tmff = kzalloc ( sizeof ( struct tmff_device ) , GFP_KERNEL ) ;
if ( ! tmff )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
/* Find the report to use */
2007-11-14 13:31:05 +03:00
report_list = & hid - > report_enum [ HID_OUTPUT_REPORT ] . report_list ;
list_for_each_entry ( report , report_list , list ) {
2005-04-17 02:20:36 +04:00
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 ) {
2007-07-30 16:56:26 +04:00
case THRUSTMASTER_USAGE_FF :
if ( field - > report_count < 2 ) {
2008-09-18 14:23:34 +04:00
dev_warn ( & hid - > dev , " ignoring FF field "
" with report_count < 2 \n " ) ;
2007-07-30 16:56:26 +04:00
continue ;
}
2005-04-17 02:20:36 +04:00
2008-09-18 14:23:31 +04:00
if ( field - > logical_maximum = =
field - > logical_minimum ) {
2008-09-18 14:23:34 +04:00
dev_warn ( & hid - > dev , " ignoring FF field "
" with logical_maximum "
" == logical_minimum \n " ) ;
2007-07-30 16:56:26 +04:00
continue ;
}
2005-04-17 02:20:36 +04:00
2007-07-30 16:56:26 +04:00
if ( tmff - > report & & tmff - > report ! = report ) {
2008-09-18 14:23:34 +04:00
dev_warn ( & hid - > dev , " ignoring FF field "
" in other report \n " ) ;
2007-07-30 16:56:26 +04:00
continue ;
}
2005-04-17 02:20:36 +04:00
2007-07-30 16:56:26 +04:00
if ( tmff - > ff_field & & tmff - > ff_field ! = field ) {
2008-09-18 14:23:34 +04:00
dev_warn ( & hid - > dev , " ignoring "
" duplicate FF field \n " ) ;
2007-07-30 16:56:26 +04:00
continue ;
}
tmff - > report = report ;
tmff - > ff_field = field ;
for ( i = 0 ; ff_bits [ i ] > = 0 ; i + + )
set_bit ( ff_bits [ i ] , input_dev - > ffbit ) ;
2005-04-17 02:20:36 +04:00
2007-07-30 16:56:26 +04:00
break ;
2005-04-17 02:20:36 +04:00
2007-07-30 16:56:26 +04:00
default :
2008-09-18 14:23:34 +04:00
dev_warn ( & hid - > dev , " ignoring unknown output "
" usage %08x \n " ,
2008-09-18 14:23:31 +04:00
field - > usage [ 0 ] . hid ) ;
2007-07-30 16:56:26 +04:00
continue ;
2005-04-17 02:20:36 +04:00
}
}
}
2007-07-30 16:56:26 +04:00
if ( ! tmff - > report ) {
2008-09-18 14:23:34 +04:00
dev_err ( & hid - > dev , " can't find FF field in output reports \n " ) ;
2007-07-30 16:56:26 +04:00
error = - ENODEV ;
goto fail ;
2005-04-17 02:20:36 +04:00
}
2008-09-18 14:23:31 +04:00
error = input_ff_create_memless ( input_dev , tmff , tmff_play ) ;
2007-07-30 16:56:26 +04:00
if ( error )
goto fail ;
2005-04-17 02:20:36 +04:00
2008-09-18 14:23:34 +04:00
dev_info ( & hid - > dev , " force feedback for ThrustMaster devices by Zinx "
" Verituse <zinx@epicsol.org> " ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
2007-07-30 16:56:26 +04:00
2008-09-18 14:23:31 +04:00
fail :
2007-07-30 16:56:26 +04:00
kfree ( tmff ) ;
return error ;
2005-04-17 02:20:36 +04:00
}
2009-05-15 17:46:44 +04:00
# else
static inline int tmff_init ( struct hid_device * hid , const signed short * ff_bits )
{
return 0 ;
}
# endif
2005-04-17 02:20:36 +04:00
2008-09-18 14:23:31 +04:00
static int tm_probe ( struct hid_device * hdev , const struct hid_device_id * id )
{
int ret ;
ret = hid_parse ( hdev ) ;
if ( ret ) {
dev_err ( & hdev - > dev , " parse failed \n " ) ;
goto err ;
}
ret = hid_hw_start ( hdev , HID_CONNECT_DEFAULT & ~ HID_CONNECT_FF ) ;
if ( ret ) {
dev_err ( & hdev - > dev , " hw start failed \n " ) ;
goto err ;
}
tmff_init ( hdev , ( void * ) id - > driver_data ) ;
return 0 ;
err :
return ret ;
}
static const struct hid_device_id tm_devices [ ] = {
{ HID_USB_DEVICE ( USB_VENDOR_ID_THRUSTMASTER , 0xb300 ) ,
. driver_data = ( unsigned long ) ff_rumble } ,
2009-06-29 11:41:29 +04:00
{ HID_USB_DEVICE ( USB_VENDOR_ID_THRUSTMASTER , 0xb304 ) , /* FireStorm Dual Power 2 (and 3) */
. driver_data = ( unsigned long ) ff_rumble } ,
{ HID_USB_DEVICE ( USB_VENDOR_ID_THRUSTMASTER , 0xb323 ) , /* Dual Trigger 3-in-1 (PC Mode) */
. driver_data = ( unsigned long ) ff_rumble } ,
{ HID_USB_DEVICE ( USB_VENDOR_ID_THRUSTMASTER , 0xb324 ) , /* Dual Trigger 3-in-1 (PS3 Mode) */
2008-09-18 14:23:31 +04:00
. driver_data = ( unsigned long ) ff_rumble } ,
{ HID_USB_DEVICE ( USB_VENDOR_ID_THRUSTMASTER , 0xb651 ) , /* FGT Rumble Force Wheel */
. driver_data = ( unsigned long ) ff_rumble } ,
{ HID_USB_DEVICE ( USB_VENDOR_ID_THRUSTMASTER , 0xb654 ) , /* FGT Force Feedback Wheel */
. driver_data = ( unsigned long ) ff_joystick } ,
{ }
} ;
MODULE_DEVICE_TABLE ( hid , tm_devices ) ;
static struct hid_driver tm_driver = {
. name = " thrustmaster " ,
. id_table = tm_devices ,
. probe = tm_probe ,
} ;
2009-07-02 21:08:38 +04:00
static int __init tm_init ( void )
2008-09-18 14:23:31 +04:00
{
return hid_register_driver ( & tm_driver ) ;
}
2009-07-02 21:08:38 +04:00
static void __exit tm_exit ( void )
2008-09-18 14:23:31 +04:00
{
hid_unregister_driver ( & tm_driver ) ;
}
module_init ( tm_init ) ;
module_exit ( tm_exit ) ;
MODULE_LICENSE ( " GPL " ) ;