2008-06-26 00:31:48 +04:00
/*
* HID driver for some samsung " special " devices
*
* Copyright ( c ) 1999 Andreas Gal
* Copyright ( c ) 2000 - 2005 Vojtech Pavlik < vojtech @ suse . cz >
* Copyright ( c ) 2005 Michael Haboustak < mike - @ cinci . rr . com > for Concept2 , Inc
* Copyright ( c ) 2006 - 2007 Jiri Kosina
* Copyright ( c ) 2007 Paul Walmsley
* Copyright ( c ) 2008 Jiri Slaby
2010-05-17 14:42:39 +04:00
* Copyright ( c ) 2010 Don Prince < dhprince . devel @ yahoo . co . uk >
*
*
* This driver supports several HID devices :
*
* [ 041 9 : 0001 ] Samsung IrDA remote controller ( reports as Cypress USB Mouse ) .
* various hid report fixups for different variants .
*
* [ 041 9 : 0600 ] Creative Desktop Wireless 6000 keyboard / mouse combo
* several key mappings used from the consumer usage page
* deviate from the USB HUT 1.12 standard .
*
2008-06-26 00:31:48 +04:00
*/
/*
* 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 .
*/
# include <linux/device.h>
2010-05-17 14:42:39 +04:00
# include <linux/usb.h>
2008-06-26 00:31:48 +04:00
# include <linux/hid.h>
# include <linux/module.h>
# include "hid-ids.h"
/*
2009-06-23 13:26:48 +04:00
* There are several variants for 041 9 : 0001 :
*
* 1. 184 byte report descriptor
2008-06-26 00:31:48 +04:00
* Vendor specific report # 4 has a size of 48 bit ,
* and therefore is not accepted when inspecting the descriptors .
* As a workaround we reinterpret the report as :
* Variable type , count 6 , size 8 bit , log . maximum 255
* The burden to reconstruct the data is moved into user space .
2009-06-23 13:26:48 +04:00
*
* 2. 203 byte report descriptor
* Report # 4 has an array field with logical range 0. .18 instead of 1. .15 .
*
* 3. 135 byte report descriptor
* Report # 4 has an array field with logical range 0. .17 instead of 1. .14 .
2009-12-11 02:37:11 +03:00
*
* 4. 171 byte report descriptor
* Report # 3 has an array field with logical range 0. .1 instead of 1. .3 .
2008-06-26 00:31:48 +04:00
*/
2010-05-17 14:42:39 +04:00
static inline void samsung_irda_dev_trace ( struct hid_device * hdev ,
2009-12-11 02:37:11 +03:00
unsigned int rsize )
{
2010-12-10 06:29:03 +03:00
hid_info ( hdev , " fixing up Samsung IrDA %d byte report descriptor \n " ,
rsize ) ;
2009-12-11 02:37:11 +03:00
}
2010-08-06 23:03:06 +04:00
static __u8 * samsung_irda_report_fixup ( struct hid_device * hdev , __u8 * rdesc ,
unsigned int * rsize )
2008-06-26 00:31:48 +04:00
{
2010-08-06 23:03:06 +04:00
if ( * rsize = = 184 & & rdesc [ 175 ] = = 0x25 & & rdesc [ 176 ] = = 0x40 & &
2008-06-26 00:31:48 +04:00
rdesc [ 177 ] = = 0x75 & & rdesc [ 178 ] = = 0x30 & &
rdesc [ 179 ] = = 0x95 & & rdesc [ 180 ] = = 0x01 & &
rdesc [ 182 ] = = 0x40 ) {
2010-05-17 14:42:39 +04:00
samsung_irda_dev_trace ( hdev , 184 ) ;
2008-06-26 00:31:48 +04:00
rdesc [ 176 ] = 0xff ;
rdesc [ 178 ] = 0x08 ;
rdesc [ 180 ] = 0x06 ;
rdesc [ 182 ] = 0x42 ;
2009-06-23 13:26:48 +04:00
} else
2010-08-06 23:03:06 +04:00
if ( * rsize = = 203 & & rdesc [ 192 ] = = 0x15 & & rdesc [ 193 ] = = 0x0 & &
2009-06-23 13:26:48 +04:00
rdesc [ 194 ] = = 0x25 & & rdesc [ 195 ] = = 0x12 ) {
2010-05-17 14:42:39 +04:00
samsung_irda_dev_trace ( hdev , 203 ) ;
2009-06-23 13:26:48 +04:00
rdesc [ 193 ] = 0x1 ;
rdesc [ 195 ] = 0xf ;
} else
2010-08-06 23:03:06 +04:00
if ( * rsize = = 135 & & rdesc [ 124 ] = = 0x15 & & rdesc [ 125 ] = = 0x0 & &
2009-06-23 13:26:48 +04:00
rdesc [ 126 ] = = 0x25 & & rdesc [ 127 ] = = 0x11 ) {
2010-05-17 14:42:39 +04:00
samsung_irda_dev_trace ( hdev , 135 ) ;
2009-06-23 13:26:48 +04:00
rdesc [ 125 ] = 0x1 ;
rdesc [ 127 ] = 0xe ;
2009-12-11 02:37:11 +03:00
} else
2010-08-06 23:03:06 +04:00
if ( * rsize = = 171 & & rdesc [ 160 ] = = 0x15 & & rdesc [ 161 ] = = 0x0 & &
2009-12-11 02:37:11 +03:00
rdesc [ 162 ] = = 0x25 & & rdesc [ 163 ] = = 0x01 ) {
2010-05-17 14:42:39 +04:00
samsung_irda_dev_trace ( hdev , 171 ) ;
2009-12-11 02:37:11 +03:00
rdesc [ 161 ] = 0x1 ;
rdesc [ 163 ] = 0x3 ;
2008-06-26 00:31:48 +04:00
}
2010-08-06 23:03:06 +04:00
return rdesc ;
2008-06-26 00:31:48 +04:00
}
2010-05-17 14:42:39 +04:00
# define samsung_kbd_mouse_map_key_clear(c) \
hid_map_usage_clear ( hi , usage , bit , max , EV_KEY , ( c ) )
static int samsung_kbd_mouse_input_mapping ( struct hid_device * hdev ,
struct hid_input * hi , struct hid_field * field , struct hid_usage * usage ,
unsigned long * * bit , int * max )
{
struct usb_interface * intf = to_usb_interface ( hdev - > dev . parent ) ;
unsigned short ifnum = intf - > cur_altsetting - > desc . bInterfaceNumber ;
if ( 1 ! = ifnum | | HID_UP_CONSUMER ! = ( usage - > hid & HID_USAGE_PAGE ) )
return 0 ;
dbg_hid ( " samsung wireless keyboard/mouse input mapping event [0x%x] \n " ,
usage - > hid & HID_USAGE ) ;
switch ( usage - > hid & HID_USAGE ) {
/* report 2 */
case 0x183 : samsung_kbd_mouse_map_key_clear ( KEY_MEDIA ) ; break ;
case 0x195 : samsung_kbd_mouse_map_key_clear ( KEY_EMAIL ) ; break ;
case 0x196 : samsung_kbd_mouse_map_key_clear ( KEY_CALC ) ; break ;
case 0x197 : samsung_kbd_mouse_map_key_clear ( KEY_COMPUTER ) ; break ;
case 0x22b : samsung_kbd_mouse_map_key_clear ( KEY_SEARCH ) ; break ;
case 0x22c : samsung_kbd_mouse_map_key_clear ( KEY_WWW ) ; break ;
case 0x22d : samsung_kbd_mouse_map_key_clear ( KEY_BACK ) ; break ;
case 0x22e : samsung_kbd_mouse_map_key_clear ( KEY_FORWARD ) ; break ;
case 0x22f : samsung_kbd_mouse_map_key_clear ( KEY_FAVORITES ) ; break ;
case 0x230 : samsung_kbd_mouse_map_key_clear ( KEY_REFRESH ) ; break ;
case 0x231 : samsung_kbd_mouse_map_key_clear ( KEY_STOP ) ; break ;
default :
return 0 ;
}
return 1 ;
}
2010-08-06 23:03:06 +04:00
static __u8 * samsung_report_fixup ( struct hid_device * hdev , __u8 * rdesc ,
unsigned int * rsize )
2010-05-17 14:42:39 +04:00
{
if ( USB_DEVICE_ID_SAMSUNG_IR_REMOTE = = hdev - > product )
2010-08-06 23:03:06 +04:00
rdesc = samsung_irda_report_fixup ( hdev , rdesc , rsize ) ;
return rdesc ;
2010-05-17 14:42:39 +04:00
}
static int samsung_input_mapping ( struct hid_device * hdev , struct hid_input * hi ,
struct hid_field * field , struct hid_usage * usage ,
unsigned long * * bit , int * max )
{
int ret = 0 ;
if ( USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE = = hdev - > product )
ret = samsung_kbd_mouse_input_mapping ( hdev ,
hi , field , usage , bit , max ) ;
return ret ;
}
2008-06-26 00:31:48 +04:00
static int samsung_probe ( struct hid_device * hdev ,
const struct hid_device_id * id )
{
int ret ;
2009-06-23 13:26:48 +04:00
unsigned int cmask = HID_CONNECT_DEFAULT ;
2008-06-26 00:31:48 +04:00
ret = hid_parse ( hdev ) ;
if ( ret ) {
2010-12-10 06:29:03 +03:00
hid_err ( hdev , " parse failed \n " ) ;
2008-06-26 00:31:48 +04:00
goto err_free ;
}
2010-05-17 14:42:39 +04:00
if ( USB_DEVICE_ID_SAMSUNG_IR_REMOTE = = hdev - > product ) {
if ( hdev - > rsize = = 184 ) {
/* disable hidinput, force hiddev */
cmask = ( cmask & ~ HID_CONNECT_HIDINPUT ) |
HID_CONNECT_HIDDEV_FORCE ;
}
2009-06-23 13:26:48 +04:00
}
ret = hid_hw_start ( hdev , cmask ) ;
2008-06-26 00:31:48 +04:00
if ( ret ) {
2010-12-10 06:29:03 +03:00
hid_err ( hdev , " hw start failed \n " ) ;
2008-06-26 00:31:48 +04:00
goto err_free ;
}
return 0 ;
err_free :
return ret ;
}
static const struct hid_device_id samsung_devices [ ] = {
{ HID_USB_DEVICE ( USB_VENDOR_ID_SAMSUNG , USB_DEVICE_ID_SAMSUNG_IR_REMOTE ) } ,
2010-05-17 14:42:39 +04:00
{ HID_USB_DEVICE ( USB_VENDOR_ID_SAMSUNG , USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE ) } ,
2008-06-26 00:31:48 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( hid , samsung_devices ) ;
static struct hid_driver samsung_driver = {
. name = " samsung " ,
. id_table = samsung_devices ,
. report_fixup = samsung_report_fixup ,
2010-05-17 14:42:39 +04:00
. input_mapping = samsung_input_mapping ,
2008-06-26 00:31:48 +04:00
. probe = samsung_probe ,
} ;
2009-07-02 21:08:38 +04:00
static int __init samsung_init ( void )
2008-06-26 00:31:48 +04:00
{
return hid_register_driver ( & samsung_driver ) ;
}
2009-07-02 21:08:38 +04:00
static void __exit samsung_exit ( void )
2008-06-26 00:31:48 +04:00
{
hid_unregister_driver ( & samsung_driver ) ;
}
module_init ( samsung_init ) ;
module_exit ( samsung_exit ) ;
MODULE_LICENSE ( " GPL " ) ;