2005-04-17 02:20:36 +04:00
/*
* ALPS touchpad PS / 2 mouse driver
*
* Copyright ( c ) 2003 Neil Brown < neilb @ cse . unsw . edu . au >
* Copyright ( c ) 2003 Peter Osterlund < petero2 @ telia . com >
* Copyright ( c ) 2004 Dmitry Torokhov < dtor @ mail . ru >
* Copyright ( c ) 2005 Vojtech Pavlik < vojtech @ suse . cz >
*
* ALPS detection , tap switching and status querying info is taken from
* tpconfig utility ( by C . Scott Ananian and Bruce Kall ) .
*
* 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/input.h>
# include <linux/serio.h>
# include <linux/libps2.h>
# include "psmouse.h"
# include "alps.h"
# undef DEBUG
# ifdef DEBUG
# define dbg(format, arg...) printk(KERN_INFO "alps.c: " format "\n", ## arg)
# else
# define dbg(format, arg...) do {} while (0)
# endif
# define ALPS_DUALPOINT 0x01
# define ALPS_WHEEL 0x02
# define ALPS_FW_BK 0x04
# define ALPS_4BTN 0x08
# define ALPS_OLDPROTO 0x10
# define ALPS_PASS 0x20
static struct alps_model_info alps_model_data [ ] = {
{ { 0x33 , 0x02 , 0x0a } , 0x88 , 0xf8 , ALPS_OLDPROTO } , /* UMAX-530T */
{ { 0x53 , 0x02 , 0x0a } , 0xf8 , 0xf8 , 0 } ,
{ { 0x53 , 0x02 , 0x14 } , 0xf8 , 0xf8 , 0 } ,
{ { 0x63 , 0x02 , 0x0a } , 0xf8 , 0xf8 , 0 } ,
{ { 0x63 , 0x02 , 0x14 } , 0xf8 , 0xf8 , 0 } ,
{ { 0x63 , 0x02 , 0x28 } , 0xf8 , 0xf8 , 0 } ,
{ { 0x63 , 0x02 , 0x3c } , 0x8f , 0x8f , ALPS_WHEEL } , /* Toshiba Satellite S2400-103 */
{ { 0x63 , 0x02 , 0x50 } , 0xef , 0xef , ALPS_FW_BK } , /* NEC Versa L320 */
{ { 0x63 , 0x02 , 0x64 } , 0xf8 , 0xf8 , 0 } ,
{ { 0x63 , 0x03 , 0xc8 } , 0xf8 , 0xf8 , ALPS_PASS } , /* Dell Latitude D800 */
{ { 0x73 , 0x02 , 0x0a } , 0xf8 , 0xf8 , 0 } ,
{ { 0x73 , 0x02 , 0x14 } , 0xf8 , 0xf8 , 0 } ,
{ { 0x20 , 0x02 , 0x0e } , 0xf8 , 0xf8 , ALPS_PASS | ALPS_DUALPOINT } , /* XXX */
{ { 0x22 , 0x02 , 0x0a } , 0xf8 , 0xf8 , ALPS_PASS | ALPS_DUALPOINT } ,
{ { 0x22 , 0x02 , 0x14 } , 0xff , 0xff , ALPS_PASS | ALPS_DUALPOINT } , /* Dell Latitude D600 */
} ;
/*
* XXX - this entry is suspicious . First byte has zero lower nibble ,
* which is what a normal mouse would report . Also , the value 0x0e
* isn ' t valid per PS / 2 spec .
*/
/*
* ALPS abolute Mode - new format
*
* byte 0 : 1 ? ? ? 1 ? ? ?
* byte 1 : 0 x6 x5 x4 x3 x2 x1 x0
* byte 2 : 0 x10 x9 x8 x7 ? fin ges
* byte 3 : 0 y9 y8 y7 1 M R L
* byte 4 : 0 y6 y5 y4 y3 y2 y1 y0
* byte 5 : 0 z6 z5 z4 z3 z2 z1 z0
*
* ? ' s can have different meanings on different models ,
* such as wheel rotation , extra buttons , stick buttons
* on a dualpoint , etc .
*/
static void alps_process_packet ( struct psmouse * psmouse , struct pt_regs * regs )
{
struct alps_data * priv = psmouse - > private ;
unsigned char * packet = psmouse - > packet ;
struct input_dev * dev = & psmouse - > dev ;
struct input_dev * dev2 = & priv - > dev2 ;
int x , y , z , ges , fin , left , right , middle ;
input_regs ( dev , regs ) ;
if ( ( packet [ 0 ] & 0xc8 ) = = 0x08 ) { /* 3-byte PS/2 packet */
input_report_key ( dev2 , BTN_LEFT , packet [ 0 ] & 1 ) ;
input_report_key ( dev2 , BTN_RIGHT , packet [ 0 ] & 2 ) ;
input_report_key ( dev2 , BTN_MIDDLE , packet [ 0 ] & 4 ) ;
input_report_rel ( dev2 , REL_X ,
packet [ 1 ] ? packet [ 1 ] - ( ( packet [ 0 ] < < 4 ) & 0x100 ) : 0 ) ;
input_report_rel ( dev2 , REL_Y ,
packet [ 2 ] ? ( ( packet [ 0 ] < < 3 ) & 0x100 ) - packet [ 2 ] : 0 ) ;
input_sync ( dev2 ) ;
return ;
}
if ( priv - > i - > flags & ALPS_OLDPROTO ) {
left = packet [ 2 ] & 0x08 ;
right = packet [ 2 ] & 0x10 ;
middle = 0 ;
x = packet [ 1 ] | ( ( packet [ 0 ] & 0x07 ) < < 7 ) ;
y = packet [ 4 ] | ( ( packet [ 3 ] & 0x07 ) < < 7 ) ;
z = packet [ 5 ] ;
} else {
left = packet [ 3 ] & 1 ;
right = packet [ 3 ] & 2 ;
middle = packet [ 3 ] & 4 ;
x = packet [ 1 ] | ( ( packet [ 2 ] & 0x78 ) < < ( 7 - 3 ) ) ;
y = packet [ 4 ] | ( ( packet [ 3 ] & 0x70 ) < < ( 7 - 4 ) ) ;
z = packet [ 5 ] ;
}
ges = packet [ 2 ] & 1 ;
fin = packet [ 2 ] & 2 ;
input_report_key ( dev , BTN_LEFT , left ) ;
input_report_key ( dev , BTN_RIGHT , right ) ;
input_report_key ( dev , BTN_MIDDLE , middle ) ;
if ( ( priv - > i - > flags & ALPS_DUALPOINT ) & & z = = 127 ) {
input_report_rel ( dev2 , REL_X , ( x > 383 ? ( x - 768 ) : x ) ) ;
input_report_rel ( dev2 , REL_Y , - ( y > 255 ? ( y - 512 ) : y ) ) ;
input_sync ( dev ) ;
input_sync ( dev2 ) ;
return ;
}
/* Convert hardware tap to a reasonable Z value */
if ( ges & & ! fin ) z = 40 ;
/*
* A " tap and drag " operation is reported by the hardware as a transition
* from ( ! fin & & ges ) to ( fin & & ges ) . This should be translated to the
* sequence Z > 0 , Z = = 0 , Z > 0 , so the Z = = 0 event has to be generated manually .
*/
if ( ges & & fin & & ! priv - > prev_fin ) {
input_report_abs ( dev , ABS_X , x ) ;
input_report_abs ( dev , ABS_Y , y ) ;
input_report_abs ( dev , ABS_PRESSURE , 0 ) ;
input_report_key ( dev , BTN_TOOL_FINGER , 0 ) ;
input_sync ( dev ) ;
}
priv - > prev_fin = fin ;
if ( z > 30 ) input_report_key ( dev , BTN_TOUCH , 1 ) ;
if ( z < 25 ) input_report_key ( dev , BTN_TOUCH , 0 ) ;
if ( z > 0 ) {
input_report_abs ( dev , ABS_X , x ) ;
input_report_abs ( dev , ABS_Y , y ) ;
}
input_report_abs ( dev , ABS_PRESSURE , z ) ;
input_report_key ( dev , BTN_TOOL_FINGER , z > 0 ) ;
if ( priv - > i - > flags & ALPS_WHEEL )
input_report_rel ( dev , REL_WHEEL , ( ( packet [ 0 ] > > 4 ) & 0x07 ) | ( ( packet [ 2 ] > > 2 ) & 0x08 ) ) ;
if ( priv - > i - > flags & ALPS_FW_BK ) {
input_report_key ( dev , BTN_FORWARD , packet [ 0 ] & 0x10 ) ;
input_report_key ( dev , BTN_BACK , packet [ 2 ] & 0x04 ) ;
}
input_sync ( dev ) ;
}
static psmouse_ret_t alps_process_byte ( struct psmouse * psmouse , struct pt_regs * regs )
{
struct alps_data * priv = psmouse - > private ;
if ( ( psmouse - > packet [ 0 ] & 0xc8 ) = = 0x08 ) { /* PS/2 packet */
if ( psmouse - > pktcnt = = 3 ) {
alps_process_packet ( psmouse , regs ) ;
return PSMOUSE_FULL_PACKET ;
}
return PSMOUSE_GOOD_DATA ;
}
if ( ( psmouse - > packet [ 0 ] & priv - > i - > mask0 ) ! = priv - > i - > byte0 )
return PSMOUSE_BAD_DATA ;
/* Bytes 2 - 6 should have 0 in the highest bit */
if ( psmouse - > pktcnt > = 2 & & psmouse - > pktcnt < = 6 & &
( psmouse - > packet [ psmouse - > pktcnt - 1 ] & 0x80 ) )
return PSMOUSE_BAD_DATA ;
if ( psmouse - > pktcnt = = 6 ) {
alps_process_packet ( psmouse , regs ) ;
return PSMOUSE_FULL_PACKET ;
}
return PSMOUSE_GOOD_DATA ;
}
static struct alps_model_info * alps_get_model ( struct psmouse * psmouse , int * version )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
unsigned char rates [ ] = { 0 , 10 , 20 , 40 , 60 , 80 , 100 , 200 } ;
unsigned char param [ 4 ] ;
int i ;
/*
* First try " E6 report " .
* ALPS should return 0 , 0 , 10 or 0 , 0 , 100
*/
param [ 0 ] = 0 ;
if ( 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 ) )
return NULL ;
param [ 0 ] = param [ 1 ] = param [ 2 ] = 0xff ;
if ( ps2_command ( ps2dev , param , PSMOUSE_CMD_GETINFO ) )
return NULL ;
dbg ( " E6 report: %2.2x %2.2x %2.2x " , param [ 0 ] , param [ 1 ] , param [ 2 ] ) ;
if ( param [ 0 ] ! = 0 | | param [ 1 ] ! = 0 | | ( param [ 2 ] ! = 10 & & param [ 2 ] ! = 100 ) )
return NULL ;
/*
* Now try " E7 report " . Allowed responses are in
* alps_model_data [ ] . signature
*/
param [ 0 ] = 0 ;
if ( ps2_command ( ps2dev , param , PSMOUSE_CMD_SETRES ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_SETSCALE21 ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_SETSCALE21 ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_SETSCALE21 ) )
return NULL ;
param [ 0 ] = param [ 1 ] = param [ 2 ] = 0xff ;
if ( ps2_command ( ps2dev , param , PSMOUSE_CMD_GETINFO ) )
return NULL ;
dbg ( " E7 report: %2.2x %2.2x %2.2x " , param [ 0 ] , param [ 1 ] , param [ 2 ] ) ;
for ( i = 0 ; i < ARRAY_SIZE ( rates ) & & param [ 2 ] ! = rates [ i ] ; i + + ) ;
* version = ( param [ 0 ] < < 8 ) | ( param [ 1 ] < < 4 ) | i ;
for ( i = 0 ; i < ARRAY_SIZE ( alps_model_data ) ; i + + )
if ( ! memcmp ( param , alps_model_data [ i ] . signature , sizeof ( alps_model_data [ i ] . signature ) ) )
return alps_model_data + i ;
return NULL ;
}
/*
* For DualPoint devices select the device that should respond to
* subsequent commands . It looks like glidepad is behind stickpointer ,
* I ' d thought it would be other way around . . .
*/
static int alps_passthrough_mode ( struct psmouse * psmouse , int enable )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
unsigned char param [ 3 ] ;
int cmd = enable ? PSMOUSE_CMD_SETSCALE21 : PSMOUSE_CMD_SETSCALE11 ;
if ( ps2_command ( ps2dev , NULL , cmd ) | |
ps2_command ( ps2dev , NULL , cmd ) | |
ps2_command ( ps2dev , NULL , cmd ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) )
return - 1 ;
/* we may get 3 more bytes, just ignore them */
ps2_command ( ps2dev , param , 0x0300 ) ;
return 0 ;
}
static int alps_absolute_mode ( struct psmouse * psmouse )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
/* Try ALPS magic knock - 4 disable before enable */
if ( ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_ENABLE ) )
return - 1 ;
/*
* Switch mouse to poll ( remote ) mode so motion data will not
* get in our way
*/
return ps2_command ( & psmouse - > ps2dev , NULL , PSMOUSE_CMD_SETPOLL ) ;
}
static int alps_get_status ( struct psmouse * psmouse , char * param )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
/* Get status: 0xF5 0xF5 0xF5 0xE9 */
if ( ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , param , PSMOUSE_CMD_GETINFO ) )
return - 1 ;
dbg ( " Status: %2.2x %2.2x %2.2x " , param [ 0 ] , param [ 1 ] , param [ 2 ] ) ;
return 0 ;
}
/*
* Turn touchpad tapping on or off . The sequences are :
* 0xE9 0xF5 0xF5 0xF3 0x0A to enable ,
* 0xE9 0xF5 0xF5 0xE8 0x00 to disable .
* My guess that 0xE9 ( GetInfo ) is here as a sync point .
* For models that also have stickpointer ( DualPoints ) its tapping
* is controlled separately ( 0xE6 0xE6 0xE6 0xF3 0x14 | 0x0A ) but
* we don ' t fiddle with it .
*/
static int alps_tap_mode ( struct psmouse * psmouse , int enable )
{
struct ps2dev * ps2dev = & psmouse - > ps2dev ;
int cmd = enable ? PSMOUSE_CMD_SETRATE : PSMOUSE_CMD_SETRES ;
unsigned char tap_arg = enable ? 0x0A : 0x00 ;
unsigned char param [ 4 ] ;
if ( ps2_command ( ps2dev , param , PSMOUSE_CMD_GETINFO ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , NULL , PSMOUSE_CMD_DISABLE ) | |
ps2_command ( ps2dev , & tap_arg , cmd ) )
return - 1 ;
if ( alps_get_status ( psmouse , param ) )
return - 1 ;
return 0 ;
}
static int alps_reconnect ( struct psmouse * psmouse )
{
struct alps_data * priv = psmouse - > private ;
unsigned char param [ 4 ] ;
int version ;
if ( ! ( priv - > i = alps_get_model ( psmouse , & version ) ) )
return - 1 ;
if ( priv - > i - > flags & ALPS_PASS & & alps_passthrough_mode ( psmouse , 1 ) )
return - 1 ;
if ( alps_get_status ( psmouse , param ) )
return - 1 ;
if ( param [ 0 ] & 0x04 )
alps_tap_mode ( psmouse , 1 ) ;
if ( alps_absolute_mode ( psmouse ) ) {
printk ( KERN_ERR " alps.c: Failed to enable absolute mode \n " ) ;
return - 1 ;
}
if ( priv - > i - > flags = = ALPS_PASS & & alps_passthrough_mode ( psmouse , 0 ) )
return - 1 ;
return 0 ;
}
static void alps_disconnect ( struct psmouse * psmouse )
{
struct alps_data * priv = psmouse - > private ;
psmouse_reset ( psmouse ) ;
input_unregister_device ( & priv - > dev2 ) ;
kfree ( priv ) ;
}
int alps_init ( struct psmouse * psmouse )
{
struct alps_data * priv ;
unsigned char param [ 4 ] ;
int version ;
psmouse - > private = priv = kmalloc ( sizeof ( struct alps_data ) , GFP_KERNEL ) ;
if ( ! priv )
goto init_fail ;
memset ( priv , 0 , sizeof ( struct alps_data ) ) ;
if ( ! ( priv - > i = alps_get_model ( psmouse , & version ) ) )
goto init_fail ;
if ( ( priv - > i - > flags & ALPS_PASS ) & & alps_passthrough_mode ( psmouse , 1 ) )
goto init_fail ;
if ( alps_get_status ( psmouse , param ) ) {
printk ( KERN_ERR " alps.c: touchpad status report request failed \n " ) ;
goto init_fail ;
}
if ( param [ 0 ] & 0x04 ) {
2005-05-17 08:53:06 +04:00
printk ( KERN_INFO " alps.c: Enabling hardware tapping \n " ) ;
2005-04-17 02:20:36 +04:00
if ( alps_tap_mode ( psmouse , 1 ) )
printk ( KERN_WARNING " alps.c: Failed to enable hardware tapping \n " ) ;
}
if ( alps_absolute_mode ( psmouse ) ) {
printk ( KERN_ERR " alps.c: Failed to enable absolute mode \n " ) ;
goto init_fail ;
}
if ( ( priv - > i - > flags & ALPS_PASS ) & & alps_passthrough_mode ( psmouse , 0 ) )
goto init_fail ;
psmouse - > dev . evbit [ LONG ( EV_KEY ) ] | = BIT ( EV_KEY ) ;
psmouse - > dev . keybit [ LONG ( BTN_TOUCH ) ] | = BIT ( BTN_TOUCH ) ;
psmouse - > dev . keybit [ LONG ( BTN_TOOL_FINGER ) ] | = BIT ( BTN_TOOL_FINGER ) ;
psmouse - > dev . keybit [ LONG ( BTN_LEFT ) ] | = BIT ( BTN_LEFT ) | BIT ( BTN_MIDDLE ) | BIT ( BTN_RIGHT ) ;
psmouse - > dev . evbit [ LONG ( EV_ABS ) ] | = BIT ( EV_ABS ) ;
input_set_abs_params ( & psmouse - > dev , ABS_X , 0 , 1023 , 0 , 0 ) ;
input_set_abs_params ( & psmouse - > dev , ABS_Y , 0 , 767 , 0 , 0 ) ;
input_set_abs_params ( & psmouse - > dev , ABS_PRESSURE , 0 , 127 , 0 , 0 ) ;
if ( priv - > i - > flags & ALPS_WHEEL ) {
psmouse - > dev . evbit [ LONG ( EV_REL ) ] | = BIT ( EV_REL ) ;
psmouse - > dev . relbit [ LONG ( REL_WHEEL ) ] | = BIT ( REL_WHEEL ) ;
}
if ( priv - > i - > flags & ALPS_FW_BK ) {
psmouse - > dev . keybit [ LONG ( BTN_FORWARD ) ] | = BIT ( BTN_FORWARD ) ;
psmouse - > dev . keybit [ LONG ( BTN_BACK ) ] | = BIT ( BTN_BACK ) ;
}
sprintf ( priv - > phys , " %s/input1 " , psmouse - > ps2dev . serio - > phys ) ;
priv - > dev2 . phys = priv - > phys ;
priv - > dev2 . name = ( priv - > i - > flags & ALPS_DUALPOINT ) ? " DualPoint Stick " : " PS/2 Mouse " ;
priv - > dev2 . id . bustype = BUS_I8042 ;
priv - > dev2 . id . vendor = 0x0002 ;
priv - > dev2 . id . product = PSMOUSE_ALPS ;
priv - > dev2 . id . version = 0x0000 ;
priv - > dev2 . evbit [ 0 ] = BIT ( EV_KEY ) | BIT ( EV_REL ) ;
priv - > dev2 . relbit [ LONG ( REL_X ) ] | = BIT ( REL_X ) | BIT ( REL_Y ) ;
priv - > dev2 . keybit [ LONG ( BTN_LEFT ) ] | = BIT ( BTN_LEFT ) | BIT ( BTN_MIDDLE ) | BIT ( BTN_RIGHT ) ;
input_register_device ( & priv - > dev2 ) ;
printk ( KERN_INFO " input: %s on %s \n " , priv - > dev2 . name , psmouse - > ps2dev . serio - > phys ) ;
psmouse - > protocol_handler = alps_process_byte ;
psmouse - > disconnect = alps_disconnect ;
psmouse - > reconnect = alps_reconnect ;
psmouse - > pktsize = 6 ;
return 0 ;
init_fail :
kfree ( priv ) ;
return - 1 ;
}
int alps_detect ( struct psmouse * psmouse , int set_properties )
{
int version ;
struct alps_model_info * model ;
if ( ! ( model = alps_get_model ( psmouse , & version ) ) )
return - 1 ;
if ( set_properties ) {
psmouse - > vendor = " ALPS " ;
if ( model - > flags & ALPS_DUALPOINT )
psmouse - > name = " DualPoint TouchPad " ;
else
psmouse - > name = " GlidePoint " ;
psmouse - > model = version ;
}
return 0 ;
}