Blame |
Last modification |
View Log
| RSS feed
/*
* Synaptics TouchPad PS/2 mouse driver
*
* 2003 Dmitry Torokhov <dtor@mail.ru>
* Added support for pass-through port
*
* 2003 Peter Osterlund <petero2@telia.com>
* Ported to 2.5 input device infrastructure.
*
* Copyright (C) 2001 Stefan Gmeiner <riddlebox@freesurf.ch>
* start merging tpconfig and gpm code to a xfree-input module
* adding some changes and extensions (ex. 3rd and 4th button)
*
* Copyright (c) 1997 C. Scott Ananian <cananian@alumni.priceton.edu>
* Copyright (c) 1998-2000 Bruce Kalk <kall@compass.com>
* code for the special synaptics commands (from the tpconfig-source)
*
* 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.
*
* Trademarks are the property of their respective owners.
*/
#include <linuxcomp.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/serio.h>
#include "psmouse.h"
#include "synaptics.h"
/*
* The x/y limits are taken from the Synaptics TouchPad interfacing Guide,
* section 2.3.2, which says that they should be valid regardless of the
* actual size of the sensor.
*/
#define XMIN_NOMINAL 1472
#define XMAX_NOMINAL 5472
#define YMIN_NOMINAL 1408
#define YMAX_NOMINAL 4448
/*****************************************************************************
* Synaptics communications functions
****************************************************************************/
/*
* Use the Synaptics extended ps/2 syntax to write a special command byte.
* special command: 0xE8 rr 0xE8 ss 0xE8 tt 0xE8 uu where (rr*64)+(ss*16)+(tt*4)+uu
* is the command. A 0xF3 or 0xE9 must follow (see synaptics_send_cmd
* and synaptics_mode_cmd)
*/
static int synaptics_special_cmd
(struct psmouse
*psmouse
, unsigned char command
)
{
int i
;
if (psmouse_command
(psmouse
, NULL
, PSMOUSE_CMD_SETSCALE11
))
return -1;
for (i
= 6; i
>= 0; i
-= 2) {
unsigned char d
= (command
>> i
) & 3;
if (psmouse_command
(psmouse
, &d
, PSMOUSE_CMD_SETRES
))
return -1;
}
return 0;
}
/*
* Send a command to the synpatics touchpad by special commands
*/
static int synaptics_send_cmd
(struct psmouse
*psmouse
, unsigned char c
, unsigned char *param
)
{
if (synaptics_special_cmd
(psmouse
, c
))
return -1;
if (psmouse_command
(psmouse
, param
, PSMOUSE_CMD_GETINFO
))
return -1;
return 0;
}
/*
* Set the synaptics touchpad mode byte by special commands
*/
static int synaptics_mode_cmd
(struct psmouse
*psmouse
, unsigned char mode
)
{
unsigned char param
[1];
if (synaptics_special_cmd
(psmouse
, mode
))
return -1;
param
[0] = SYN_PS_SET_MODE2
;
if (psmouse_command
(psmouse
, param
, PSMOUSE_CMD_SETRATE
))
return -1;
return 0;
}
static int synaptics_reset
(struct psmouse
*psmouse
)
{
unsigned char r
[2];
if (psmouse_command
(psmouse
, r
, PSMOUSE_CMD_RESET_BAT
))
return -1;
if (r
[0] == PSMOUSE_RET_BAT
&& r
[1] == PSMOUSE_RET_ID
)
return 0;
return -1;
}
/*
* Read the model-id bytes from the touchpad
* see also SYN_MODEL_* macros
*/
static int synaptics_model_id
(struct psmouse
*psmouse
)
{
struct synaptics_data
*priv
= psmouse
->private
;
unsigned char mi
[3];
if (synaptics_send_cmd
(psmouse
, SYN_QUE_MODEL
, mi
))
return -1;
priv
->model_id
= (mi
[0]<<16) | (mi
[1]<<8) | mi
[2];
return 0;
}
/*
* Read the capability-bits from the touchpad
* see also the SYN_CAP_* macros
*/
static int synaptics_capability
(struct psmouse
*psmouse
)
{
struct synaptics_data
*priv
= psmouse
->private
;
unsigned char cap
[3];
if (synaptics_send_cmd
(psmouse
, SYN_QUE_CAPABILITIES
, cap
))
return -1;
priv
->capabilities
= (cap
[0]<<16) | (cap
[1]<<8) | cap
[2];
priv
->ext_cap
= 0;
if (!SYN_CAP_VALID
(priv
->capabilities
))
return -1;
if (SYN_EXT_CAP_REQUESTS
(priv
->capabilities
)) {
if (synaptics_send_cmd
(psmouse
, SYN_QUE_EXT_CAPAB
, cap
)) {
printk
(KERN_ERR
"Synaptics claims to have extended capabilities,"
" but I'm not able to read them.");
} else
priv
->ext_cap
= (cap
[0]<<16) | (cap
[1]<<8) | cap
[2];
}
return 0;
}
/*
* Identify Touchpad
* See also the SYN_ID_* macros
*/
static int synaptics_identify
(struct psmouse
*psmouse
)
{
struct synaptics_data
*priv
= psmouse
->private
;
unsigned char id
[3];
if (synaptics_send_cmd
(psmouse
, SYN_QUE_IDENTIFY
, id
))
return -1;
priv
->identity
= (id
[0]<<16) | (id
[1]<<8) | id
[2];
if (SYN_ID_IS_SYNAPTICS
(priv
->identity
))
return 0;
return -1;
}
static void print_ident
(struct synaptics_data
*priv
)
{
printk
(KERN_INFO
"Synaptics Touchpad, model: %ld\n", SYN_ID_MODEL
(priv
->identity
));
printk
(KERN_INFO
" Firmware: %ld.%ld\n", SYN_ID_MAJOR
(priv
->identity
),
SYN_ID_MINOR
(priv
->identity
));
if (SYN_MODEL_ROT180
(priv
->model_id
))
printk
(KERN_INFO
" 180 degree mounted touchpad\n");
if (SYN_MODEL_PORTRAIT
(priv
->model_id
))
printk
(KERN_INFO
" portrait touchpad\n");
printk
(KERN_INFO
" Sensor: %ld\n", SYN_MODEL_SENSOR
(priv
->model_id
));
if (SYN_MODEL_NEWABS
(priv
->model_id
))
printk
(KERN_INFO
" new absolute packet format\n");
if (SYN_MODEL_PEN
(priv
->model_id
))
printk
(KERN_INFO
" pen detection\n");
if (SYN_CAP_EXTENDED
(priv
->capabilities
)) {
printk
(KERN_INFO
" Touchpad has extended capability bits\n");
if (SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
) &&
SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
) <= 8)
printk
(KERN_INFO
" -> %d multi-buttons, i.e. besides standard buttons\n",
(int)(SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
)));
else if (SYN_CAP_FOUR_BUTTON
(priv
->capabilities
))
printk
(KERN_INFO
" -> four buttons\n");
if (SYN_CAP_MULTIFINGER
(priv
->capabilities
))
printk
(KERN_INFO
" -> multifinger detection\n");
if (SYN_CAP_PALMDETECT
(priv
->capabilities
))
printk
(KERN_INFO
" -> palm detection\n");
if (SYN_CAP_PASS_THROUGH
(priv
->capabilities
))
printk
(KERN_INFO
" -> pass-through port\n");
}
}
static int synaptics_query_hardware
(struct psmouse
*psmouse
)
{
struct synaptics_data
*priv
= psmouse
->private
;
int retries
= 0;
int mode
;
while ((retries
++ < 3) && synaptics_reset
(psmouse
))
printk
(KERN_ERR
"synaptics reset failed\n");
if (synaptics_identify
(psmouse
))
return -1;
if (synaptics_model_id
(psmouse
))
return -1;
if (synaptics_capability
(psmouse
))
return -1;
mode
= SYN_BIT_ABSOLUTE_MODE
| SYN_BIT_HIGH_RATE
;
if (SYN_ID_MAJOR
(priv
->identity
) >= 4)
mode
|= SYN_BIT_DISABLE_GESTURE
;
if (SYN_CAP_EXTENDED
(priv
->capabilities
))
mode
|= SYN_BIT_W_MODE
;
if (synaptics_mode_cmd
(psmouse
, mode
))
return -1;
return 0;
}
/*****************************************************************************
* Synaptics pass-through PS/2 port support
****************************************************************************/
static int synaptics_pt_open
(struct serio
*port
)
{
return 0;
}
static void synaptics_pt_close
(struct serio
*port
)
{
}
static int synaptics_pt_write
(struct serio
*port
, unsigned char c
)
{
struct psmouse
*parent
= port
->driver
;
char rate_param
= SYN_PS_CLIENT_CMD
; /* indicates that we want pass-through port */
if (synaptics_special_cmd
(parent
, c
))
return -1;
if (psmouse_command
(parent
, &rate_param
, PSMOUSE_CMD_SETRATE
))
return -1;
return 0;
}
static inline int synaptics_is_pt_packet
(unsigned char *buf
)
{
return (buf
[0] & 0xFC) == 0x84 && (buf
[3] & 0xCC) == 0xC4;
}
static void synaptics_pass_pt_packet
(struct serio
*ptport
, unsigned char *packet
)
{
struct psmouse
*child
= ptport
->private
;
if (child
) {
if (child
->state
== PSMOUSE_ACTIVATED
) {
serio_interrupt
(ptport
, packet
[1], 0, NULL
);
serio_interrupt
(ptport
, packet
[4], 0, NULL
);
serio_interrupt
(ptport
, packet
[5], 0, NULL
);
if (child
->type
>= PSMOUSE_GENPS
)
serio_interrupt
(ptport
, packet
[2], 0, NULL
);
} else if (child
->state
!= PSMOUSE_IGNORE
) {
serio_interrupt
(ptport
, packet
[1], 0, NULL
);
}
}
}
int synaptics_pt_init
(struct psmouse
*psmouse
)
{
struct synaptics_data
*priv
= psmouse
->private
;
struct serio
*port
;
struct psmouse
*child
;
if (psmouse
->type
!= PSMOUSE_SYNAPTICS
)
return -1;
if (!SYN_CAP_EXTENDED
(priv
->capabilities
))
return -1;
if (!SYN_CAP_PASS_THROUGH
(priv
->capabilities
))
return -1;
priv
->ptport
= port
= kmalloc
(sizeof(struct serio
), GFP_KERNEL
);
if (!port
) {
printk
(KERN_ERR
"synaptics: not enough memory to allocate serio port\n");
return -1;
}
memset(port
, 0, sizeof(struct serio
));
port
->type
= SERIO_PS_PSTHRU
;
port
->name
= "Synaptics pass-through";
port
->phys
= "synaptics-pt/serio0";
port
->write
= synaptics_pt_write
;
port
->open
= synaptics_pt_open
;
port
->close
= synaptics_pt_close
;
port
->driver
= psmouse
;
printk
(KERN_INFO
"serio: %s port at %s\n", port
->name
, psmouse
->phys
);
serio_register_slave_port
(port
);
/* adjust the touchpad to child's choice of protocol */
child
= port
->private
;
if (child
&& child
->type
>= PSMOUSE_GENPS
) {
if (synaptics_mode_cmd
(psmouse
, (SYN_BIT_ABSOLUTE_MODE
|
SYN_BIT_HIGH_RATE
|
SYN_BIT_DISABLE_GESTURE
|
SYN_BIT_FOUR_BYTE_CLIENT
|
SYN_BIT_W_MODE
)))
printk
(KERN_INFO
"synaptics: failed to enable 4-byte guest protocol\n");
}
return 0;
}
/*****************************************************************************
* Driver initialization/cleanup functions
****************************************************************************/
static inline void set_abs_params
(struct input_dev
*dev
, int axis
, int min
, int max
, int fuzz
, int flat
)
{
dev
->absmin
[axis
] = min
;
dev
->absmax
[axis
] = max
;
dev
->absfuzz
[axis
] = fuzz
;
dev
->absflat
[axis
] = flat
;
set_bit
(axis
, dev
->absbit
);
}
static void set_input_params
(struct input_dev
*dev
, struct synaptics_data
*priv
)
{
set_bit
(EV_ABS
, dev
->evbit
);
set_abs_params
(dev
, ABS_X
, XMIN_NOMINAL
, XMAX_NOMINAL
, 0, 0);
set_abs_params
(dev
, ABS_Y
, YMIN_NOMINAL
, YMAX_NOMINAL
, 0, 0);
set_abs_params
(dev
, ABS_PRESSURE
, 0, 255, 0, 0);
set_bit
(ABS_TOOL_WIDTH
, dev
->absbit
);
set_bit
(EV_KEY
, dev
->evbit
);
set_bit
(BTN_TOUCH
, dev
->keybit
);
set_bit
(BTN_TOOL_FINGER
, dev
->keybit
);
set_bit
(BTN_TOOL_DOUBLETAP
, dev
->keybit
);
set_bit
(BTN_TOOL_TRIPLETAP
, dev
->keybit
);
set_bit
(BTN_LEFT
, dev
->keybit
);
set_bit
(BTN_RIGHT
, dev
->keybit
);
set_bit
(BTN_FORWARD
, dev
->keybit
);
set_bit
(BTN_BACK
, dev
->keybit
);
if (SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
)) {
switch (SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
) & ~
0x01) {
default:
/*
* if nExtBtn is greater than 8 it should be considered
* invalid and treated as 0
*/
break;
case 8:
set_bit
(BTN_7
, dev
->keybit
);
set_bit
(BTN_6
, dev
->keybit
);
case 6:
set_bit
(BTN_5
, dev
->keybit
);
set_bit
(BTN_4
, dev
->keybit
);
case 4:
set_bit
(BTN_3
, dev
->keybit
);
set_bit
(BTN_2
, dev
->keybit
);
case 2:
set_bit
(BTN_1
, dev
->keybit
);
set_bit
(BTN_0
, dev
->keybit
);
break;
}
}
clear_bit
(EV_REL
, dev
->evbit
);
clear_bit
(REL_X
, dev
->relbit
);
clear_bit
(REL_Y
, dev
->relbit
);
}
int synaptics_init
(struct psmouse
*psmouse
)
{
struct synaptics_data
*priv
;
#ifndef CONFIG_MOUSE_PS2_SYNAPTICS
return -1;
#endif
psmouse
->private
= priv
= kmalloc
(sizeof(struct synaptics_data
), GFP_KERNEL
);
if (!priv
)
return -1;
memset(priv
, 0, sizeof(struct synaptics_data
));
if (synaptics_query_hardware
(psmouse
)) {
printk
(KERN_ERR
"Unable to query/initialize Synaptics hardware.\n");
goto init_fail
;
}
print_ident
(priv
);
set_input_params
(&psmouse
->dev
, priv
);
return 0;
init_fail
:
kfree
(priv
);
return -1;
}
void synaptics_disconnect
(struct psmouse
*psmouse
)
{
struct synaptics_data
*priv
= psmouse
->private
;
if (psmouse
->type
== PSMOUSE_SYNAPTICS
&& priv
) {
synaptics_mode_cmd
(psmouse
, 0);
if (priv
->ptport
) {
serio_unregister_slave_port
(priv
->ptport
);
kfree
(priv
->ptport
);
}
kfree
(priv
);
}
}
/*****************************************************************************
* Functions to interpret the absolute mode packets
****************************************************************************/
static void synaptics_parse_hw_state
(unsigned char buf
[], struct synaptics_data
*priv
, struct synaptics_hw_state
*hw
)
{
hw
->up
= 0;
hw
->down
= 0;
hw
->b0
= 0;
hw
->b1
= 0;
hw
->b2
= 0;
hw
->b3
= 0;
hw
->b4
= 0;
hw
->b5
= 0;
hw
->b6
= 0;
hw
->b7
= 0;
if (SYN_MODEL_NEWABS
(priv
->model_id
)) {
hw
->x
= (((buf
[3] & 0x10) << 8) |
((buf
[1] & 0x0f) << 8) |
buf
[4]);
hw
->y
= (((buf
[3] & 0x20) << 7) |
((buf
[1] & 0xf0) << 4) |
buf
[5]);
hw
->z
= buf
[2];
hw
->w
= (((buf
[0] & 0x30) >> 2) |
((buf
[0] & 0x04) >> 1) |
((buf
[3] & 0x04) >> 2));
hw
->left
= (buf
[0] & 0x01) ? 1 : 0;
hw
->right
= (buf
[0] & 0x02) ? 1 : 0;
if (SYN_CAP_EXTENDED
(priv
->capabilities
) &&
(SYN_CAP_FOUR_BUTTON
(priv
->capabilities
))) {
hw
->up
= ((buf
[3] & 0x01)) ? 1 : 0;
if (hw
->left
)
hw
->up
= !hw
->up
;
hw
->down
= ((buf
[3] & 0x02)) ? 1 : 0;
if (hw
->right
)
hw
->down
= !hw
->down
;
}
if (SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
) &&
((buf
[3] & 2) ? !hw
->right
: hw
->right
)) {
switch (SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
) & ~
0x01) {
default:
/*
* if nExtBtn is greater than 8 it should be
* considered invalid and treated as 0
*/
break;
case 8:
hw
->b7
= ((buf
[5] & 0x08)) ? 1 : 0;
hw
->b6
= ((buf
[4] & 0x08)) ? 1 : 0;
case 6:
hw
->b5
= ((buf
[5] & 0x04)) ? 1 : 0;
hw
->b4
= ((buf
[4] & 0x04)) ? 1 : 0;
case 4:
hw
->b3
= ((buf
[5] & 0x02)) ? 1 : 0;
hw
->b2
= ((buf
[4] & 0x02)) ? 1 : 0;
case 2:
hw
->b1
= ((buf
[5] & 0x01)) ? 1 : 0;
hw
->b0
= ((buf
[4] & 0x01)) ? 1 : 0;
}
}
} else {
hw
->x
= (((buf
[1] & 0x1f) << 8) | buf
[2]);
hw
->y
= (((buf
[4] & 0x1f) << 8) | buf
[5]);
hw
->z
= (((buf
[0] & 0x30) << 2) | (buf
[3] & 0x3F));
hw
->w
= (((buf
[1] & 0x80) >> 4) | ((buf
[0] & 0x04) >> 1));
hw
->left
= (buf
[0] & 0x01) ? 1 : 0;
hw
->right
= (buf
[0] & 0x02) ? 1 : 0;
}
}
/*
* called for each full received packet from the touchpad
*/
static void synaptics_process_packet
(struct psmouse
*psmouse
)
{
struct input_dev
*dev
= &psmouse
->dev
;
struct synaptics_data
*priv
= psmouse
->private
;
struct synaptics_hw_state hw
;
int num_fingers
;
int finger_width
;
synaptics_parse_hw_state
(psmouse
->packet
, priv
, &hw
);
if (hw.
z > 0) {
num_fingers
= 1;
finger_width
= 5;
if (SYN_CAP_EXTENDED
(priv
->capabilities
)) {
switch (hw.
w) {
case 0 ...
1:
if (SYN_CAP_MULTIFINGER
(priv
->capabilities
))
num_fingers
= hw.
w + 2;
break;
case 2:
if (SYN_MODEL_PEN
(priv
->model_id
))
; /* Nothing, treat a pen as a single finger */
break;
case 4 ...
15:
if (SYN_CAP_PALMDETECT
(priv
->capabilities
))
finger_width
= hw.
w;
break;
}
}
} else {
num_fingers
= 0;
finger_width
= 0;
}
/* Post events */
if (hw.
z > 0) {
input_report_abs
(dev
, ABS_X
, hw.
x);
input_report_abs
(dev
, ABS_Y
, YMAX_NOMINAL
+ YMIN_NOMINAL
- hw.
y);
}
input_report_abs
(dev
, ABS_PRESSURE
, hw.
z);
if (hw.
z > 30) input_report_key
(dev
, BTN_TOUCH
, 1);
if (hw.
z < 25) input_report_key
(dev
, BTN_TOUCH
, 0);
input_report_abs
(dev
, ABS_TOOL_WIDTH
, finger_width
);
input_report_key
(dev
, BTN_TOOL_FINGER
, num_fingers
== 1);
input_report_key
(dev
, BTN_TOOL_DOUBLETAP
, num_fingers
== 2);
input_report_key
(dev
, BTN_TOOL_TRIPLETAP
, num_fingers
== 3);
input_report_key
(dev
, BTN_LEFT
, hw.
left);
input_report_key
(dev
, BTN_RIGHT
, hw.
right);
input_report_key
(dev
, BTN_FORWARD
, hw.
up);
input_report_key
(dev
, BTN_BACK
, hw.
down);
if (SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
))
switch(SYN_CAP_MULTI_BUTTON_NO
(priv
->ext_cap
) & ~
0x01) {
default:
/*
* if nExtBtn is greater than 8 it should be considered
* invalid and treated as 0
*/
break;
case 8:
input_report_key
(dev
, BTN_7
, hw.
b7);
input_report_key
(dev
, BTN_6
, hw.
b6);
case 6:
input_report_key
(dev
, BTN_5
, hw.
b5);
input_report_key
(dev
, BTN_4
, hw.
b4);
case 4:
input_report_key
(dev
, BTN_3
, hw.
b3);
input_report_key
(dev
, BTN_2
, hw.
b2);
case 2:
input_report_key
(dev
, BTN_1
, hw.
b1);
input_report_key
(dev
, BTN_0
, hw.
b0);
break;
}
input_sync
(dev
);
}
void synaptics_process_byte
(struct psmouse
*psmouse
, struct pt_regs
*regs
)
{
struct input_dev
*dev
= &psmouse
->dev
;
struct synaptics_data
*priv
= psmouse
->private
;
unsigned char data
= psmouse
->packet
[psmouse
->pktcnt
- 1];
int newabs
= SYN_MODEL_NEWABS
(priv
->model_id
);
input_regs
(dev
, regs
);
switch (psmouse
->pktcnt
) {
case 1:
if (newabs
? ((data
& 0xC8) != 0x80) : ((data
& 0xC0) != 0xC0)) {
printk
(KERN_WARNING
"Synaptics driver lost sync at 1st byte\n");
goto bad_sync
;
}
break;
case 2:
if (!newabs
&& ((data
& 0x60) != 0x00)) {
printk
(KERN_WARNING
"Synaptics driver lost sync at 2nd byte\n");
goto bad_sync
;
}
break;
case 4:
if (newabs
? ((data
& 0xC8) != 0xC0) : ((data
& 0xC0) != 0x80)) {
printk
(KERN_WARNING
"Synaptics driver lost sync at 4th byte\n");
goto bad_sync
;
}
break;
case 5:
if (!newabs
&& ((data
& 0x60) != 0x00)) {
printk
(KERN_WARNING
"Synaptics driver lost sync at 5th byte\n");
goto bad_sync
;
}
break;
default:
if (psmouse
->pktcnt
< 6)
break; /* Wait for full packet */
if (priv
->out_of_sync
) {
priv
->out_of_sync
= 0;
printk
(KERN_NOTICE
"Synaptics driver resynced.\n");
}
if (priv
->ptport
&& synaptics_is_pt_packet
(psmouse
->packet
))
synaptics_pass_pt_packet
(priv
->ptport
, psmouse
->packet
);
else
synaptics_process_packet
(psmouse
);
psmouse
->pktcnt
= 0;
break;
}
return;
bad_sync
:
priv
->out_of_sync
++;
psmouse
->pktcnt
= 0;
if (psmouse_resetafter
> 0 && priv
->out_of_sync
== psmouse_resetafter
) {
psmouse
->state
= PSMOUSE_IGNORE
;
serio_rescan
(psmouse
->serio
);
}
}