2005-04-16 15:20:36 -07:00
/*
* PS / 2 mouse driver
*
* Copyright ( c ) 1999 - 2002 Vojtech Pavlik
* Copyright ( c ) 2003 - 2004 Dmitry Torokhov
*/
/*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation .
*/
# include <linux/delay.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/input.h>
# include <linux/serio.h>
# include <linux/init.h>
# include <linux/libps2.h>
# include "psmouse.h"
# include "synaptics.h"
# include "logips2pp.h"
# include "alps.h"
2005-05-29 02:30:22 -05:00
# include "lifebook.h"
2005-08-08 01:26:18 -05:00
# include "trackpoint.h"
2005-04-16 15:20:36 -07:00
# define DRIVER_DESC "PS / 2 mouse driver"
MODULE_AUTHOR ( " Vojtech Pavlik <vojtech@suse.cz> " ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
2005-06-01 02:40:01 -05:00
static unsigned int psmouse_max_proto = PSMOUSE_AUTO ;
2005-04-16 15:20:36 -07:00
static int psmouse_set_maxproto ( const char * val , struct kernel_param * kp ) ;
static int psmouse_get_maxproto ( char * buffer , struct kernel_param * kp ) ;
# define param_check_proto_abbrev(name, p) __param_check(name, p, unsigned int)
# define param_set_proto_abbrev psmouse_set_maxproto
# define param_get_proto_abbrev psmouse_get_maxproto
module_param_named ( proto , psmouse_max_proto , proto_abbrev , 0644 ) ;
2005-06-01 02:40:01 -05:00
MODULE_PARM_DESC ( proto , " Highest protocol extension to probe (bare, imps, exps, any). Useful for KVM switches. " ) ;
2005-04-16 15:20:36 -07:00
static unsigned int psmouse_resolution = 200 ;
module_param_named ( resolution , psmouse_resolution , uint , 0644 ) ;
MODULE_PARM_DESC ( resolution , " Resolution, in dpi. " ) ;
static unsigned int psmouse_rate = 100 ;
module_param_named ( rate , psmouse_rate , uint , 0644 ) ;
MODULE_PARM_DESC ( rate , " Report rate, in reports per second. " ) ;
static unsigned int psmouse_smartscroll = 1 ;
module_param_named ( smartscroll , psmouse_smartscroll , bool , 0644 ) ;
MODULE_PARM_DESC ( smartscroll , " Logitech Smartscroll autorepeat, 1 = enabled (default), 0 = disabled. " ) ;
static unsigned int psmouse_resetafter ;
module_param_named ( resetafter , psmouse_resetafter , uint , 0644 ) ;
MODULE_PARM_DESC ( resetafter , " Reset device after so many bad packets (0 = never). " ) ;
2005-06-01 02:40:01 -05:00
PSMOUSE_DEFINE_ATTR ( protocol ) ;
2005-04-16 15:20:36 -07:00
PSMOUSE_DEFINE_ATTR ( rate ) ;
PSMOUSE_DEFINE_ATTR ( resolution ) ;
PSMOUSE_DEFINE_ATTR ( resetafter ) ;
__obsolete_setup ( " psmouse_noext " ) ;
__obsolete_setup ( " psmouse_resolution= " ) ;
__obsolete_setup ( " psmouse_smartscroll= " ) ;
__obsolete_setup ( " psmouse_resetafter= " ) ;
__obsolete_setup ( " psmouse_rate= " ) ;
2005-06-01 02:39:44 -05:00
/*
* psmouse_sem protects all operations changing state of mouse
* ( connecting , disconnecting , changing rate or resolution via
* sysfs ) . We could use a per - device semaphore but since there
* rarely more than one PS / 2 mouse connected and since semaphore
* is taken in " slow " paths it is not worth it .
*/
static DECLARE_MUTEX ( psmouse_sem ) ;
2005-06-01 02:40:01 -05:00
struct psmouse_protocol {
enum psmouse_type type ;
char * name ;
char * alias ;
int maxproto ;
int ( * detect ) ( struct psmouse * , int ) ;
int ( * init ) ( struct psmouse * ) ;
} ;
2005-04-16 15:20:36 -07:00
/*
* psmouse_process_byte ( ) analyzes the PS / 2 data stream and reports
* relevant events to the input module once full packet has arrived .
*/
static psmouse_ret_t psmouse_process_byte ( struct psmouse * psmouse , struct pt_regs * regs )
{
struct input_dev * dev = & psmouse - > dev ;
unsigned char * packet = psmouse - > packet ;
if ( psmouse - > pktcnt < psmouse - > pktsize )
return PSMOUSE_GOOD_DATA ;
/*
* Full packet accumulated , process it
*/
input_regs ( dev , regs ) ;
/*
* Scroll wheel on IntelliMice , scroll buttons on NetMice
*/
if ( psmouse - > type = = PSMOUSE_IMPS | | psmouse - > type = = PSMOUSE_GENPS )
input_report_rel ( dev , REL_WHEEL , - ( signed char ) packet [ 3 ] ) ;
/*
* Scroll wheel and buttons on IntelliMouse Explorer
*/
if ( psmouse - > type = = PSMOUSE_IMEX ) {
input_report_rel ( dev , REL_WHEEL , ( int ) ( packet [ 3 ] & 8 ) - ( int ) ( packet [ 3 ] & 7 ) ) ;
input_report_key ( dev , BTN_SIDE , ( packet [ 3 ] > > 4 ) & 1 ) ;
input_report_key ( dev , BTN_EXTRA , ( packet [ 3 ] > > 5 ) & 1 ) ;
}
/*
* Extra buttons on Genius NewNet 3 D
*/
if ( psmouse - > type = = PSMOUSE_GENPS ) {
input_report_key ( dev , BTN_SIDE , ( packet [ 0 ] > > 6 ) & 1 ) ;
input_report_key ( dev , BTN_EXTRA , ( packet [ 0 ] > > 7 ) & 1 ) ;
}
/*
* Extra button on ThinkingMouse
*/
if ( psmouse - > type = = PSMOUSE_THINKPS ) {
input_report_key ( dev , BTN_EXTRA , ( packet [ 0 ] > > 3 ) & 1 ) ;
/* Without this bit of weirdness moving up gives wildly high Y changes. */
packet [ 1 ] | = ( packet [ 0 ] & 0x40 ) < < 1 ;
}
/*
* Generic PS / 2 Mouse
*/
input_report_key ( dev , BTN_LEFT , packet [ 0 ] & 1 ) ;
input_report_key ( dev , BTN_MIDDLE , ( packet [ 0 ] > > 2 ) & 1 ) ;
input_report_key ( dev , BTN_RIGHT , ( packet [ 0 ] > > 1 ) & 1 ) ;
input_report_rel ( dev , REL_X , packet [ 1 ] ? ( int ) packet [ 1 ] - ( int ) ( ( packet [ 0 ] < < 4 ) & 0x100 ) : 0 ) ;
input_report_rel ( dev , REL_Y , packet [ 2 ] ? ( int ) ( ( packet [ 0 ] < < 3 ) & 0x100 ) - ( int ) packet [ 2 ] : 0 ) ;
input_sync ( dev ) ;
return PSMOUSE_FULL_PACKET ;
}
/*
* psmouse_interrupt ( ) handles incoming characters , either gathering them into
* packets or passing them to the command routine as command output .
*/
static irqreturn_t psmouse_interrupt ( struct serio * serio ,
unsigned char data , unsigned int flags , struct pt_regs * regs )
{
struct psmouse * psmouse = serio_get_drvdata ( serio ) ;
psmouse_ret_t rc ;
if ( psmouse - > state = = PSMOUSE_IGNORE )
goto out ;
if ( flags & ( SERIO_PARITY | SERIO_TIMEOUT ) ) {
if ( psmouse - > state = = PSMOUSE_ACTIVATED )
printk ( KERN_WARNING " psmouse.c: bad data from KBC -%s%s \n " ,
flags & SERIO_TIMEOUT ? " timeout " : " " ,
flags & SERIO_PARITY ? " bad parity " : " " ) ;
ps2_cmd_aborted ( & psmouse - > ps2dev ) ;
goto out ;
}
if ( unlikely ( psmouse - > ps2dev . flags & PS2_FLAG_ACK ) )
if ( ps2_handle_ack ( & psmouse - > ps2dev , data ) )
goto out ;
if ( unlikely ( psmouse - > ps2dev . flags & PS2_FLAG_CMD ) )
if ( ps2_handle_response ( & psmouse - > ps2dev , data ) )
goto out ;
if ( psmouse - > state = = PSMOUSE_INITIALIZING )
goto out ;
if ( psmouse - > state = = PSMOUSE_ACTIVATED & &
psmouse - > pktcnt & & time_after ( jiffies , psmouse - > last + HZ / 2 ) ) {
printk ( KERN_WARNING " psmouse.c: %s at %s lost synchronization, throwing %d bytes away. \n " ,
psmouse - > name , psmouse - > phys , psmouse - > pktcnt ) ;
psmouse - > pktcnt = 0 ;
}
psmouse - > last = jiffies ;
psmouse - > packet [ psmouse - > pktcnt + + ] = data ;
if ( psmouse - > packet [ 0 ] = = PSMOUSE_RET_BAT ) {
if ( psmouse - > pktcnt = = 1 )
goto out ;
if ( psmouse - > pktcnt = = 2 ) {
if ( psmouse - > packet [ 1 ] = = PSMOUSE_RET_ID ) {
psmouse - > state = PSMOUSE_IGNORE ;
serio_reconnect ( serio ) ;
goto out ;
}
if ( psmouse - > type = = PSMOUSE_SYNAPTICS ) {
/* neither 0xAA nor 0x00 are valid first bytes
* for a packet in absolute mode
*/
psmouse - > pktcnt = 0 ;
goto out ;
}
}
}
rc = psmouse - > protocol_handler ( psmouse , regs ) ;
switch ( rc ) {
case PSMOUSE_BAD_DATA :
printk ( KERN_WARNING " psmouse.c: %s at %s lost sync at byte %d \n " ,
psmouse - > name , psmouse - > phys , psmouse - > pktcnt ) ;
psmouse - > pktcnt = 0 ;
if ( + + psmouse - > out_of_sync = = psmouse - > resetafter ) {
psmouse - > state = PSMOUSE_IGNORE ;
printk ( KERN_NOTICE " psmouse.c: issuing reconnect request \n " ) ;
serio_reconnect ( psmouse - > ps2dev . serio ) ;
}
break ;
case PSMOUSE_FULL_PACKET :
psmouse - > pktcnt = 0 ;
if ( psmouse - > out_of_sync ) {
psmouse - > out_of_sync = 0 ;
printk ( KERN_NOTICE " psmouse.c: %s at %s - driver resynched. \n " ,
psmouse - > name , psmouse - > phys ) ;
}
break ;
case PSMOUSE_GOOD_DATA :
break ;
}
out :
return IRQ_HANDLED ;
}
/*
* psmouse_sliced_command ( ) sends an extended PS / 2 command to the mouse
* using sliced syntax , understood by advanced devices , such as Logitech
* or Synaptics touchpads . The command is encoded as :
* 0xE6 0xE8 rr 0xE8 ss 0xE8 tt 0xE8 uu where ( rr * 64 ) + ( ss * 16 ) + ( tt * 4 ) + uu
* is the command .
*/
int psmouse_sliced_command ( struct psmouse * psmouse , unsigned char command )
{
int i ;
if ( ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_SETSCALE11 ) )
return - 1 ;
for ( i = 6 ; i > = 0 ; i - = 2 ) {
unsigned char d = ( command > > i ) & 3 ;
if ( ps2_command ( & psmouse - > ps2dev , & d , PSMOUSE_CMD_SETRES ) )
return - 1 ;
}
return 0 ;
}
/*
* psmouse_reset ( ) resets the mouse into power - on state .
*/
int psmouse_reset ( struct psmouse * psmouse )
{
unsigned char param [ 2 ] ;
if ( ps2_command ( & psmouse - > ps2dev , param , PSMOUSE_CMD_RESET_BAT ) )
return - 1 ;
if ( param [ 0 ] ! = PSMOUSE_RET_BAT & & param [ 1 ] ! = PSMOUSE_RET_ID )
return - 1 ;
return 0 ;
}
/*
* Genius NetMouse magic init .
*/
static int genius_detect ( struct psmouse * psmouse , int set_properties )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
unsigned char param [ 4 ] ;
param [ 0 ] = 3 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRES ) ;
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_SETSCALE11 ) ;
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_SETSCALE11 ) ;
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_SETSCALE11 ) ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_GETINFO ) ;
if ( param [ 0 ] ! = 0x00 | | param [ 1 ] ! = 0x33 | | param [ 2 ] ! = 0x55 )
return - 1 ;
if ( set_properties ) {
set_bit ( BTN_EXTRA , psmouse - > dev . keybit ) ;
set_bit ( BTN_SIDE , psmouse - > dev . keybit ) ;
set_bit ( REL_WHEEL , psmouse - > dev . relbit ) ;
psmouse - > vendor = " Genius " ;
psmouse - > name = " Wheel Mouse " ;
psmouse - > pktsize = 4 ;
}
return 0 ;
}
/*
* IntelliMouse magic init .
*/
static int intellimouse_detect ( struct psmouse * psmouse , int set_properties )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
unsigned char param [ 2 ] ;
param [ 0 ] = 200 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRATE ) ;
param [ 0 ] = 100 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRATE ) ;
param [ 0 ] = 80 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRATE ) ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_GETID ) ;
if ( param [ 0 ] ! = 3 )
return - 1 ;
if ( set_properties ) {
2005-07-15 01:50:08 -05:00
set_bit ( BTN_MIDDLE , psmouse - > dev . keybit ) ;
2005-04-16 15:20:36 -07:00
set_bit ( REL_WHEEL , psmouse - > dev . relbit ) ;
if ( ! psmouse - > vendor ) psmouse - > vendor = " Generic " ;
if ( ! psmouse - > name ) psmouse - > name = " Wheel Mouse " ;
psmouse - > pktsize = 4 ;
}
return 0 ;
}
/*
* Try IntelliMouse / Explorer magic init .
*/
static int im_explorer_detect ( struct psmouse * psmouse , int set_properties )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
unsigned char param [ 2 ] ;
intellimouse_detect ( psmouse , 0 ) ;
param [ 0 ] = 200 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRATE ) ;
param [ 0 ] = 200 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRATE ) ;
param [ 0 ] = 80 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRATE ) ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_GETID ) ;
if ( param [ 0 ] ! = 4 )
return - 1 ;
if ( set_properties ) {
2005-07-15 01:50:08 -05:00
set_bit ( BTN_MIDDLE , psmouse - > dev . keybit ) ;
2005-04-16 15:20:36 -07:00
set_bit ( REL_WHEEL , psmouse - > dev . relbit ) ;
set_bit ( BTN_SIDE , psmouse - > dev . keybit ) ;
set_bit ( BTN_EXTRA , psmouse - > dev . keybit ) ;
if ( ! psmouse - > vendor ) psmouse - > vendor = " Generic " ;
if ( ! psmouse - > name ) psmouse - > name = " Explorer Mouse " ;
psmouse - > pktsize = 4 ;
}
return 0 ;
}
/*
* Kensington ThinkingMouse / ExpertMouse magic init .
*/
static int thinking_detect ( struct psmouse * psmouse , int set_properties )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
unsigned char param [ 2 ] ;
unsigned char seq [ ] = { 20 , 60 , 40 , 20 , 20 , 60 , 40 , 20 , 20 , 0 } ;
int i ;
param [ 0 ] = 10 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRATE ) ;
param [ 0 ] = 0 ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRES ) ;
for ( i = 0 ; seq [ i ] ; i + + )
ps2_command ( ps2dev , seq + i , PSMOUSE_CMD_SETRATE ) ;
ps2_command ( ps2dev , param , PSMOUSE_CMD_GETID ) ;
if ( param [ 0 ] ! = 2 )
return - 1 ;
if ( set_properties ) {
set_bit ( BTN_EXTRA , psmouse - > dev . keybit ) ;
psmouse - > vendor = " Kensington " ;
psmouse - > name = " ThinkingMouse " ;
}
return 0 ;
}
/*
* Bare PS / 2 protocol " detection " . Always succeeds .
*/
static int ps2bare_detect ( struct psmouse * psmouse , int set_properties )
{
2005-06-01 02:40:01 -05:00
if ( set_properties ) {
if ( ! psmouse - > vendor ) psmouse - > vendor = " Generic " ;
if ( ! psmouse - > name ) psmouse - > name = " Mouse " ;
}
2005-04-16 15:20:36 -07:00
return 0 ;
}
2005-06-01 02:40:01 -05:00
2005-04-16 15:20:36 -07:00
/*
* psmouse_extensions ( ) probes for any extensions to the basic PS / 2 protocol
* the mouse may have .
*/
static int psmouse_extensions ( struct psmouse * psmouse ,
unsigned int max_proto , int set_properties )
{
int synaptics_hardware = 0 ;
2005-05-29 02:30:32 -05:00
/*
* We always check for lifebook because it does not disturb mouse
* ( it only checks DMI information ) .
*/
2005-06-01 02:40:01 -05:00
if ( lifebook_detect ( psmouse , set_properties ) = = 0 ) {
2005-05-29 02:30:32 -05:00
if ( max_proto > PSMOUSE_IMEX ) {
if ( ! set_properties | | lifebook_init ( psmouse ) = = 0 )
return PSMOUSE_LIFEBOOK ;
}
}
2005-05-29 02:30:22 -05:00
2005-04-16 15:20:36 -07:00
/*
* Try Kensington ThinkingMouse ( we try first , because synaptics probe
* upsets the thinkingmouse ) .
*/
if ( max_proto > PSMOUSE_IMEX & & thinking_detect ( psmouse , set_properties ) = = 0 )
return PSMOUSE_THINKPS ;
/*
* Try Synaptics TouchPad
*/
if ( max_proto > PSMOUSE_PS2 & & synaptics_detect ( psmouse , set_properties ) = = 0 ) {
synaptics_hardware = 1 ;
if ( max_proto > PSMOUSE_IMEX ) {
if ( ! set_properties | | synaptics_init ( psmouse ) = = 0 )
return PSMOUSE_SYNAPTICS ;
/*
* Some Synaptics touchpads can emulate extended protocols ( like IMPS / 2 ) .
* Unfortunately Logitech / Genius probes confuse some firmware versions so
* we ' ll have to skip them .
*/
max_proto = PSMOUSE_IMEX ;
}
/*
* Make sure that touchpad is in relative mode , gestures ( taps ) are enabled
*/
synaptics_reset ( psmouse ) ;
}
/*
* Try ALPS TouchPad
*/
if ( max_proto > PSMOUSE_IMEX ) {
ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_RESET_DIS ) ;
if ( alps_detect ( psmouse , set_properties ) = = 0 ) {
if ( ! set_properties | | alps_init ( psmouse ) = = 0 )
return PSMOUSE_ALPS ;
/*
* Init failed , try basic relative protocols
*/
max_proto = PSMOUSE_IMEX ;
}
}
if ( max_proto > PSMOUSE_IMEX & & genius_detect ( psmouse , set_properties ) = = 0 )
return PSMOUSE_GENPS ;
if ( max_proto > PSMOUSE_IMEX & & ps2pp_init ( psmouse , set_properties ) = = 0 )
return PSMOUSE_PS2PP ;
/*
* Reset to defaults in case the device got confused by extended
* protocol probes .
*/
ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_RESET_DIS ) ;
if ( max_proto > = PSMOUSE_IMEX & & im_explorer_detect ( psmouse , set_properties ) = = 0 )
return PSMOUSE_IMEX ;
if ( max_proto > = PSMOUSE_IMPS & & intellimouse_detect ( psmouse , set_properties ) = = 0 )
return PSMOUSE_IMPS ;
2005-08-08 01:26:18 -05:00
/*
* Try to initialize the IBM TrackPoint
*/
if ( max_proto > PSMOUSE_IMEX & & trackpoint_detect ( psmouse , set_properties ) = = 0 )
return PSMOUSE_TRACKPOINT ;
2005-04-16 15:20:36 -07:00
/*
* Okay , all failed , we have a standard mouse here . The number of the buttons
* is still a question , though . We assume 3.
*/
ps2bare_detect ( psmouse , set_properties ) ;
if ( synaptics_hardware ) {
/*
* We detected Synaptics hardware but it did not respond to IMPS / 2 probes .
* We need to reset the touchpad because if there is a track point on the
* pass through port it could get disabled while probing for protocol
* extensions .
*/
psmouse_reset ( psmouse ) ;
ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_RESET_DIS ) ;
}
return PSMOUSE_PS2 ;
}
2005-06-01 02:40:01 -05:00
static struct psmouse_protocol psmouse_protocols [ ] = {
{
. type = PSMOUSE_PS2 ,
. name = " PS/2 " ,
. alias = " bare " ,
. maxproto = 1 ,
. detect = ps2bare_detect ,
} ,
{
. type = PSMOUSE_PS2PP ,
. name = " PS2++ " ,
. alias = " logitech " ,
. detect = ps2pp_init ,
} ,
{
. type = PSMOUSE_THINKPS ,
. name = " ThinkPS/2 " ,
. alias = " thinkps " ,
. detect = thinking_detect ,
} ,
{
. type = PSMOUSE_GENPS ,
. name = " GenPS/2 " ,
. alias = " genius " ,
. detect = genius_detect ,
} ,
{
. type = PSMOUSE_IMPS ,
. name = " ImPS/2 " ,
. alias = " imps " ,
. maxproto = 1 ,
. detect = intellimouse_detect ,
} ,
{
. type = PSMOUSE_IMEX ,
. name = " ImExPS/2 " ,
. alias = " exps " ,
. maxproto = 1 ,
. detect = im_explorer_detect ,
} ,
{
. type = PSMOUSE_SYNAPTICS ,
. name = " SynPS/2 " ,
. alias = " synaptics " ,
. detect = synaptics_detect ,
. init = synaptics_init ,
} ,
{
. type = PSMOUSE_ALPS ,
. name = " AlpsPS/2 " ,
. alias = " alps " ,
. detect = alps_detect ,
. init = alps_init ,
} ,
{
. type = PSMOUSE_LIFEBOOK ,
. name = " LBPS/2 " ,
. alias = " lifebook " ,
. init = lifebook_init ,
} ,
2005-08-08 01:26:18 -05:00
{
. type = PSMOUSE_TRACKPOINT ,
. name = " TPPS/2 " ,
. alias = " trackpoint " ,
. detect = trackpoint_detect ,
} ,
2005-06-01 02:40:01 -05:00
{
. type = PSMOUSE_AUTO ,
. name = " auto " ,
. alias = " any " ,
. maxproto = 1 ,
} ,
} ;
static struct psmouse_protocol * psmouse_protocol_by_type ( enum psmouse_type type )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( psmouse_protocols ) ; i + + )
if ( psmouse_protocols [ i ] . type = = type )
return & psmouse_protocols [ i ] ;
WARN_ON ( 1 ) ;
return & psmouse_protocols [ 0 ] ;
}
static struct psmouse_protocol * psmouse_protocol_by_name ( const char * name , size_t len )
{
struct psmouse_protocol * p ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( psmouse_protocols ) ; i + + ) {
p = & psmouse_protocols [ i ] ;
if ( ( strlen ( p - > name ) = = len & & ! strncmp ( p - > name , name , len ) ) | |
( strlen ( p - > alias ) = = len & & ! strncmp ( p - > alias , name , len ) ) )
return & psmouse_protocols [ i ] ;
}
return NULL ;
}
2005-04-16 15:20:36 -07:00
/*
* psmouse_probe ( ) probes for a PS / 2 mouse .
*/
static int psmouse_probe ( struct psmouse * psmouse )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
unsigned char param [ 2 ] ;
/*
* First , we check if it ' s a mouse . It should send 0x00 or 0x03
* in case of an IntelliMouse in 4 - byte mode or 0x04 for IM Explorer .
2005-05-28 02:11:42 -05:00
* Sunrex K8561 IR Keyboard / Mouse reports 0xff on second and subsequent
* ID queries , probably due to a firmware bug .
2005-04-16 15:20:36 -07:00
*/
param [ 0 ] = 0xa5 ;
if ( ps2_command ( ps2dev , param , PSMOUSE_CMD_GETID ) )
return - 1 ;
2005-05-28 02:11:42 -05:00
if ( param [ 0 ] ! = 0x00 & & param [ 0 ] ! = 0x03 & &
param [ 0 ] ! = 0x04 & & param [ 0 ] ! = 0xff )
2005-04-16 15:20:36 -07:00
return - 1 ;
/*
* Then we reset and disable the mouse so that it doesn ' t generate events .
*/
if ( ps2_command ( ps2dev , NULL , PSMOUSE_CMD_RESET_DIS ) )
printk ( KERN_WARNING " psmouse.c: Failed to reset mouse on %s \n " , ps2dev - > serio - > phys ) ;
return 0 ;
}
/*
* Here we set the mouse resolution .
*/
void psmouse_set_resolution ( struct psmouse * psmouse , unsigned int resolution )
{
unsigned char params [ ] = { 0 , 1 , 2 , 2 , 3 } ;
if ( resolution = = 0 | | resolution > 200 )
resolution = 200 ;
ps2_command ( & psmouse - > ps2dev , & params [ resolution / 50 ] , PSMOUSE_CMD_SETRES ) ;
psmouse - > resolution = 25 < < params [ resolution / 50 ] ;
}
/*
* Here we set the mouse report rate .
*/
static void psmouse_set_rate ( struct psmouse * psmouse , unsigned int rate )
{
unsigned char rates [ ] = { 200 , 100 , 80 , 60 , 40 , 20 , 10 , 0 } ;
int i = 0 ;
while ( rates [ i ] > rate ) i + + ;
ps2_command ( & psmouse - > ps2dev , & rates [ i ] , PSMOUSE_CMD_SETRATE ) ;
psmouse - > rate = rates [ i ] ;
}
/*
* psmouse_initialize ( ) initializes the mouse to a sane state .
*/
static void psmouse_initialize ( struct psmouse * psmouse )
{
/*
* We set the mouse into streaming mode .
*/
ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_SETSTREAM ) ;
/*
* We set the mouse report rate , resolution and scaling .
*/
if ( psmouse_max_proto ! = PSMOUSE_PS2 ) {
psmouse - > set_rate ( psmouse , psmouse - > rate ) ;
psmouse - > set_resolution ( psmouse , psmouse - > resolution ) ;
ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_SETSCALE11 ) ;
}
}
/*
* psmouse_set_state ( ) sets new psmouse state and resets all flags and
* counters while holding serio lock so fighting with interrupt handler
* is not a concern .
*/
static void psmouse_set_state ( struct psmouse * psmouse , enum psmouse_state new_state )
{
serio_pause_rx ( psmouse - > ps2dev . serio ) ;
psmouse - > state = new_state ;
psmouse - > pktcnt = psmouse - > out_of_sync = 0 ;
psmouse - > ps2dev . flags = 0 ;
serio_continue_rx ( psmouse - > ps2dev . serio ) ;
}
/*
* psmouse_activate ( ) enables the mouse so that we get motion reports from it .
*/
static void psmouse_activate ( struct psmouse * psmouse )
{
if ( ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_ENABLE ) )
printk ( KERN_WARNING " psmouse.c: Failed to enable mouse on %s \n " ,
psmouse - > ps2dev . serio - > phys ) ;
psmouse_set_state ( psmouse , PSMOUSE_ACTIVATED ) ;
}
/*
* psmouse_deactivate ( ) puts the mouse into poll mode so that we don ' t get motion
* reports from it unless we explicitely request it .
*/
static void psmouse_deactivate ( struct psmouse * psmouse )
{
if ( ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_DISABLE ) )
printk ( KERN_WARNING " psmouse.c: Failed to deactivate mouse on %s \n " ,
psmouse - > ps2dev . serio - > phys ) ;
psmouse_set_state ( psmouse , PSMOUSE_CMD_MODE ) ;
}
/*
* psmouse_cleanup ( ) resets the mouse into power - on state .
*/
static void psmouse_cleanup ( struct serio * serio )
{
struct psmouse * psmouse = serio_get_drvdata ( serio ) ;
psmouse_reset ( psmouse ) ;
}
/*
* psmouse_disconnect ( ) closes and frees .
*/
static void psmouse_disconnect ( struct serio * serio )
{
2005-06-01 02:39:44 -05:00
struct psmouse * psmouse , * parent = NULL ;
psmouse = serio_get_drvdata ( serio ) ;
2005-04-16 15:20:36 -07:00
2005-06-01 02:40:01 -05:00
device_remove_file ( & serio - > dev , & psmouse_attr_protocol ) ;
2005-04-16 15:20:36 -07:00
device_remove_file ( & serio - > dev , & psmouse_attr_rate ) ;
device_remove_file ( & serio - > dev , & psmouse_attr_resolution ) ;
device_remove_file ( & serio - > dev , & psmouse_attr_resetafter ) ;
2005-06-01 02:39:44 -05:00
down ( & psmouse_sem ) ;
2005-04-16 15:20:36 -07:00
psmouse_set_state ( psmouse , PSMOUSE_CMD_MODE ) ;
if ( serio - > parent & & serio - > id . type = = SERIO_PS_PSTHRU ) {
parent = serio_get_drvdata ( serio - > parent ) ;
2005-06-01 02:39:44 -05:00
psmouse_deactivate ( parent ) ;
2005-04-16 15:20:36 -07:00
}
if ( psmouse - > disconnect )
psmouse - > disconnect ( psmouse ) ;
2005-06-01 02:39:44 -05:00
if ( parent & & parent - > pt_deactivate )
parent - > pt_deactivate ( parent ) ;
2005-04-16 15:20:36 -07:00
psmouse_set_state ( psmouse , PSMOUSE_IGNORE ) ;
input_unregister_device ( & psmouse - > dev ) ;
serio_close ( serio ) ;
serio_set_drvdata ( serio , NULL ) ;
kfree ( psmouse ) ;
2005-06-01 02:39:44 -05:00
if ( parent )
psmouse_activate ( parent ) ;
up ( & psmouse_sem ) ;
2005-04-16 15:20:36 -07:00
}
2005-06-01 02:40:01 -05:00
static int psmouse_switch_protocol ( struct psmouse * psmouse , struct psmouse_protocol * proto )
{
memset ( & psmouse - > dev , 0 , sizeof ( struct input_dev ) ) ;
init_input_dev ( & psmouse - > dev ) ;
psmouse - > dev . private = psmouse ;
psmouse - > dev . dev = & psmouse - > ps2dev . serio - > dev ;
psmouse - > dev . evbit [ 0 ] = BIT ( EV_KEY ) | BIT ( EV_REL ) ;
psmouse - > dev . keybit [ LONG ( BTN_MOUSE ) ] = BIT ( BTN_LEFT ) | BIT ( BTN_MIDDLE ) | BIT ( BTN_RIGHT ) ;
psmouse - > dev . relbit [ 0 ] = BIT ( REL_X ) | BIT ( REL_Y ) ;
psmouse - > set_rate = psmouse_set_rate ;
psmouse - > set_resolution = psmouse_set_resolution ;
psmouse - > protocol_handler = psmouse_process_byte ;
psmouse - > pktsize = 3 ;
if ( proto & & ( proto - > detect | | proto - > init ) ) {
if ( proto - > detect & & proto - > detect ( psmouse , 1 ) < 0 )
return - 1 ;
if ( proto - > init & & proto - > init ( psmouse ) < 0 )
return - 1 ;
psmouse - > type = proto - > type ;
}
else
psmouse - > type = psmouse_extensions ( psmouse , psmouse_max_proto , 1 ) ;
sprintf ( psmouse - > devname , " %s %s %s " ,
psmouse_protocol_by_type ( psmouse - > type ) - > name , psmouse - > vendor , psmouse - > name ) ;
psmouse - > dev . name = psmouse - > devname ;
psmouse - > dev . phys = psmouse - > phys ;
psmouse - > dev . id . bustype = BUS_I8042 ;
psmouse - > dev . id . vendor = 0x0002 ;
psmouse - > dev . id . product = psmouse - > type ;
psmouse - > dev . id . version = psmouse - > model ;
return 0 ;
}
2005-04-16 15:20:36 -07:00
/*
* psmouse_connect ( ) is a callback from the serio module when
* an unhandled serio port is found .
*/
static int psmouse_connect ( struct serio * serio , struct serio_driver * drv )
{
struct psmouse * psmouse , * parent = NULL ;
int retval ;
2005-06-01 02:39:44 -05:00
down ( & psmouse_sem ) ;
2005-04-16 15:20:36 -07:00
/*
* If this is a pass - through port deactivate parent so the device
* connected to this port can be successfully identified
*/
if ( serio - > parent & & serio - > id . type = = SERIO_PS_PSTHRU ) {
parent = serio_get_drvdata ( serio - > parent ) ;
psmouse_deactivate ( parent ) ;
}
2005-06-01 02:39:44 -05:00
if ( ! ( psmouse = kcalloc ( 1 , sizeof ( struct psmouse ) , GFP_KERNEL ) ) ) {
2005-04-16 15:20:36 -07:00
retval = - ENOMEM ;
goto out ;
}
ps2_init ( & psmouse - > ps2dev , serio ) ;
sprintf ( psmouse - > phys , " %s/input0 " , serio - > phys ) ;
2005-06-01 02:40:01 -05:00
2005-04-16 15:20:36 -07:00
psmouse_set_state ( psmouse , PSMOUSE_INITIALIZING ) ;
serio_set_drvdata ( serio , psmouse ) ;
retval = serio_open ( serio , drv ) ;
if ( retval ) {
serio_set_drvdata ( serio , NULL ) ;
kfree ( psmouse ) ;
goto out ;
}
if ( psmouse_probe ( psmouse ) < 0 ) {
serio_close ( serio ) ;
serio_set_drvdata ( serio , NULL ) ;
kfree ( psmouse ) ;
retval = - ENODEV ;
goto out ;
}
psmouse - > rate = psmouse_rate ;
psmouse - > resolution = psmouse_resolution ;
psmouse - > resetafter = psmouse_resetafter ;
psmouse - > smartscroll = psmouse_smartscroll ;
2005-06-01 02:40:01 -05:00
psmouse_switch_protocol ( psmouse , NULL ) ;
2005-04-16 15:20:36 -07:00
input_register_device ( & psmouse - > dev ) ;
printk ( KERN_INFO " input: %s on %s \n " , psmouse - > devname , serio - > phys ) ;
psmouse_set_state ( psmouse , PSMOUSE_CMD_MODE ) ;
psmouse_initialize ( psmouse ) ;
if ( parent & & parent - > pt_activate )
parent - > pt_activate ( parent ) ;
2005-06-01 02:40:01 -05:00
device_create_file ( & serio - > dev , & psmouse_attr_protocol ) ;
2005-04-16 15:20:36 -07:00
device_create_file ( & serio - > dev , & psmouse_attr_rate ) ;
device_create_file ( & serio - > dev , & psmouse_attr_resolution ) ;
device_create_file ( & serio - > dev , & psmouse_attr_resetafter ) ;
psmouse_activate ( psmouse ) ;
retval = 0 ;
out :
2005-06-01 02:39:44 -05:00
/* If this is a pass-through port the parent needs to be re-activated */
2005-04-16 15:20:36 -07:00
if ( parent )
psmouse_activate ( parent ) ;
2005-06-01 02:39:44 -05:00
up ( & psmouse_sem ) ;
2005-04-16 15:20:36 -07:00
return retval ;
}
static int psmouse_reconnect ( struct serio * serio )
{
struct psmouse * psmouse = serio_get_drvdata ( serio ) ;
struct psmouse * parent = NULL ;
struct serio_driver * drv = serio - > drv ;
int rc = - 1 ;
if ( ! drv | | ! psmouse ) {
printk ( KERN_DEBUG " psmouse: reconnect request, but serio is disconnected, ignoring... \n " ) ;
return - 1 ;
}
2005-06-01 02:39:44 -05:00
down ( & psmouse_sem ) ;
2005-04-16 15:20:36 -07:00
if ( serio - > parent & & serio - > id . type = = SERIO_PS_PSTHRU ) {
parent = serio_get_drvdata ( serio - > parent ) ;
psmouse_deactivate ( parent ) ;
}
psmouse_set_state ( psmouse , PSMOUSE_INITIALIZING ) ;
if ( psmouse - > reconnect ) {
if ( psmouse - > reconnect ( psmouse ) )
goto out ;
} else if ( psmouse_probe ( psmouse ) < 0 | |
psmouse - > type ! = psmouse_extensions ( psmouse , psmouse_max_proto , 0 ) )
goto out ;
/* ok, the device type (and capabilities) match the old one,
* we can continue using it , complete intialization
*/
psmouse_set_state ( psmouse , PSMOUSE_CMD_MODE ) ;
psmouse_initialize ( psmouse ) ;
if ( parent & & parent - > pt_activate )
parent - > pt_activate ( parent ) ;
psmouse_activate ( psmouse ) ;
rc = 0 ;
out :
/* If this is a pass-through port the parent waits to be activated */
if ( parent )
psmouse_activate ( parent ) ;
2005-06-01 02:39:44 -05:00
up ( & psmouse_sem ) ;
2005-04-16 15:20:36 -07:00
return rc ;
}
static struct serio_device_id psmouse_serio_ids [ ] = {
{
. type = SERIO_8042 ,
. proto = SERIO_ANY ,
. id = SERIO_ANY ,
. extra = SERIO_ANY ,
} ,
{
. type = SERIO_PS_PSTHRU ,
. proto = SERIO_ANY ,
. id = SERIO_ANY ,
. extra = SERIO_ANY ,
} ,
{ 0 }
} ;
MODULE_DEVICE_TABLE ( serio , psmouse_serio_ids ) ;
static struct serio_driver psmouse_drv = {
. driver = {
. name = " psmouse " ,
} ,
. description = DRIVER_DESC ,
. id_table = psmouse_serio_ids ,
. interrupt = psmouse_interrupt ,
. connect = psmouse_connect ,
. reconnect = psmouse_reconnect ,
. disconnect = psmouse_disconnect ,
. cleanup = psmouse_cleanup ,
} ;
ssize_t psmouse_attr_show_helper ( struct device * dev , char * buf ,
ssize_t ( * handler ) ( struct psmouse * , char * ) )
{
struct serio * serio = to_serio_port ( dev ) ;
int retval ;
retval = serio_pin_driver ( serio ) ;
if ( retval )
return retval ;
if ( serio - > drv ! = & psmouse_drv ) {
retval = - ENODEV ;
goto out ;
}
retval = handler ( serio_get_drvdata ( serio ) , buf ) ;
out :
serio_unpin_driver ( serio ) ;
return retval ;
}
ssize_t psmouse_attr_set_helper ( struct device * dev , const char * buf , size_t count ,
ssize_t ( * handler ) ( struct psmouse * , const char * , size_t ) )
{
struct serio * serio = to_serio_port ( dev ) ;
struct psmouse * psmouse = serio_get_drvdata ( serio ) ;
struct psmouse * parent = NULL ;
int retval ;
retval = serio_pin_driver ( serio ) ;
if ( retval )
return retval ;
if ( serio - > drv ! = & psmouse_drv ) {
retval = - ENODEV ;
2005-06-01 02:39:44 -05:00
goto out_unpin ;
}
retval = down_interruptible ( & psmouse_sem ) ;
if ( retval )
goto out_unpin ;
if ( psmouse - > state = = PSMOUSE_IGNORE ) {
retval = - ENODEV ;
goto out_up ;
2005-04-16 15:20:36 -07:00
}
if ( serio - > parent & & serio - > id . type = = SERIO_PS_PSTHRU ) {
parent = serio_get_drvdata ( serio - > parent ) ;
psmouse_deactivate ( parent ) ;
}
2005-06-01 02:40:01 -05:00
2005-04-16 15:20:36 -07:00
psmouse_deactivate ( psmouse ) ;
retval = handler ( psmouse , buf , count ) ;
2005-06-01 02:40:01 -05:00
if ( retval ! = - ENODEV )
psmouse_activate ( psmouse ) ;
2005-04-16 15:20:36 -07:00
if ( parent )
psmouse_activate ( parent ) ;
2005-06-01 02:39:44 -05:00
out_up :
up ( & psmouse_sem ) ;
out_unpin :
2005-04-16 15:20:36 -07:00
serio_unpin_driver ( serio ) ;
return retval ;
}
2005-06-01 02:40:01 -05:00
static ssize_t psmouse_attr_show_protocol ( struct psmouse * psmouse , char * buf )
{
return sprintf ( buf , " %s \n " , psmouse_protocol_by_type ( psmouse - > type ) - > name ) ;
}
static ssize_t psmouse_attr_set_protocol ( struct psmouse * psmouse , const char * buf , size_t count )
{
struct serio * serio = psmouse - > ps2dev . serio ;
struct psmouse * parent = NULL ;
struct psmouse_protocol * proto ;
int retry = 0 ;
if ( ! ( proto = psmouse_protocol_by_name ( buf , count ) ) )
return - EINVAL ;
if ( psmouse - > type = = proto - > type )
return count ;
while ( serio - > child ) {
if ( + + retry > 3 ) {
printk ( KERN_WARNING " psmouse: failed to destroy child port, protocol change aborted. \n " ) ;
return - EIO ;
}
up ( & psmouse_sem ) ;
serio_unpin_driver ( serio ) ;
serio_unregister_child_port ( serio ) ;
serio_pin_driver_uninterruptible ( serio ) ;
down ( & psmouse_sem ) ;
if ( serio - > drv ! = & psmouse_drv )
return - ENODEV ;
if ( psmouse - > type = = proto - > type )
return count ; /* switched by other thread */
}
if ( serio - > parent & & serio - > id . type = = SERIO_PS_PSTHRU ) {
parent = serio_get_drvdata ( serio - > parent ) ;
if ( parent - > pt_deactivate )
parent - > pt_deactivate ( parent ) ;
}
if ( psmouse - > disconnect )
psmouse - > disconnect ( psmouse ) ;
psmouse_set_state ( psmouse , PSMOUSE_IGNORE ) ;
input_unregister_device ( & psmouse - > dev ) ;
psmouse_set_state ( psmouse , PSMOUSE_INITIALIZING ) ;
if ( psmouse_switch_protocol ( psmouse , proto ) < 0 ) {
psmouse_reset ( psmouse ) ;
/* default to PSMOUSE_PS2 */
psmouse_switch_protocol ( psmouse , & psmouse_protocols [ 0 ] ) ;
}
psmouse_initialize ( psmouse ) ;
psmouse_set_state ( psmouse , PSMOUSE_CMD_MODE ) ;
input_register_device ( & psmouse - > dev ) ;
printk ( KERN_INFO " input: %s on %s \n " , psmouse - > devname , serio - > phys ) ;
if ( parent & & parent - > pt_activate )
parent - > pt_activate ( parent ) ;
return count ;
}
2005-04-16 15:20:36 -07:00
static ssize_t psmouse_attr_show_rate ( struct psmouse * psmouse , char * buf )
{
return sprintf ( buf , " %d \n " , psmouse - > rate ) ;
}
static ssize_t psmouse_attr_set_rate ( struct psmouse * psmouse , const char * buf , size_t count )
{
unsigned long value ;
char * rest ;
value = simple_strtoul ( buf , & rest , 10 ) ;
if ( * rest )
return - EINVAL ;
psmouse - > set_rate ( psmouse , value ) ;
return count ;
}
static ssize_t psmouse_attr_show_resolution ( struct psmouse * psmouse , char * buf )
{
return sprintf ( buf , " %d \n " , psmouse - > resolution ) ;
}
static ssize_t psmouse_attr_set_resolution ( struct psmouse * psmouse , const char * buf , size_t count )
{
unsigned long value ;
char * rest ;
value = simple_strtoul ( buf , & rest , 10 ) ;
if ( * rest )
return - EINVAL ;
psmouse - > set_resolution ( psmouse , value ) ;
return count ;
}
static ssize_t psmouse_attr_show_resetafter ( struct psmouse * psmouse , char * buf )
{
return sprintf ( buf , " %d \n " , psmouse - > resetafter ) ;
}
static ssize_t psmouse_attr_set_resetafter ( struct psmouse * psmouse , const char * buf , size_t count )
{
unsigned long value ;
char * rest ;
value = simple_strtoul ( buf , & rest , 10 ) ;
if ( * rest )
return - EINVAL ;
psmouse - > resetafter = value ;
return count ;
}
static int psmouse_set_maxproto ( const char * val , struct kernel_param * kp )
{
2005-06-01 02:40:01 -05:00
struct psmouse_protocol * proto ;
2005-04-16 15:20:36 -07:00
if ( ! val )
return - EINVAL ;
2005-06-01 02:40:01 -05:00
proto = psmouse_protocol_by_name ( val , strlen ( val ) ) ;
2005-04-16 15:20:36 -07:00
2005-06-01 02:40:01 -05:00
if ( ! proto | | ! proto - > maxproto )
return - EINVAL ;
2005-04-16 15:20:36 -07:00
2005-06-01 02:40:01 -05:00
* ( ( unsigned int * ) kp - > arg ) = proto - > type ;
2005-04-16 15:20:36 -07:00
2005-08-08 01:26:18 -05:00
return 0 ;
2005-04-16 15:20:36 -07:00
}
static int psmouse_get_maxproto ( char * buffer , struct kernel_param * kp )
{
2005-06-01 02:40:01 -05:00
int type = * ( ( unsigned int * ) kp - > arg ) ;
return sprintf ( buffer , " %s \n " , psmouse_protocol_by_type ( type ) - > name ) ;
2005-04-16 15:20:36 -07:00
}
static int __init psmouse_init ( void )
{
serio_register_driver ( & psmouse_drv ) ;
return 0 ;
}
static void __exit psmouse_exit ( void )
{
serio_unregister_driver ( & psmouse_drv ) ;
}
module_init ( psmouse_init ) ;
module_exit ( psmouse_exit ) ;