Commit 25bded7c authored by Seth Forshee's avatar Seth Forshee Committed by Dmitry Torokhov

Input: ALPS - add support for protocol versions 3 and 4

This patch adds support for two ALPS touchpad protocols not
supported currently by the driver, which I am arbitrarily naming
version 3 and version 4. Support is single-touch only at this time,
although both protocols are capable of limited multitouch support.

Thanks to Andrew Skalski, who did the initial reverse-engineering
of the v3 protocol.
Signed-off-by: default avatarSeth Forshee <seth.forshee@canonical.com>
Acked-by: default avatarChase Douglas <chase.douglas@canonical.com>
Signed-off-by: default avatarDmitry Torokhov <dtor@mail.ru>
parent b46615fe
...@@ -23,6 +23,50 @@ ...@@ -23,6 +23,50 @@
#include "psmouse.h" #include "psmouse.h"
#include "alps.h" #include "alps.h"
/*
* Definitions for ALPS version 3 and 4 command mode protocol
*/
#define ALPS_CMD_NIBBLE_10 0x01f2
static const struct alps_nibble_commands alps_v3_nibble_commands[] = {
{ PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */
{ PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
{ PSMOUSE_CMD_SETSCALE21, 0x00 }, /* 2 */
{ PSMOUSE_CMD_SETRATE, 0x0a }, /* 3 */
{ PSMOUSE_CMD_SETRATE, 0x14 }, /* 4 */
{ PSMOUSE_CMD_SETRATE, 0x28 }, /* 5 */
{ PSMOUSE_CMD_SETRATE, 0x3c }, /* 6 */
{ PSMOUSE_CMD_SETRATE, 0x50 }, /* 7 */
{ PSMOUSE_CMD_SETRATE, 0x64 }, /* 8 */
{ PSMOUSE_CMD_SETRATE, 0xc8 }, /* 9 */
{ ALPS_CMD_NIBBLE_10, 0x00 }, /* a */
{ PSMOUSE_CMD_SETRES, 0x00 }, /* b */
{ PSMOUSE_CMD_SETRES, 0x01 }, /* c */
{ PSMOUSE_CMD_SETRES, 0x02 }, /* d */
{ PSMOUSE_CMD_SETRES, 0x03 }, /* e */
{ PSMOUSE_CMD_SETSCALE11, 0x00 }, /* f */
};
static const struct alps_nibble_commands alps_v4_nibble_commands[] = {
{ PSMOUSE_CMD_ENABLE, 0x00 }, /* 0 */
{ PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
{ PSMOUSE_CMD_SETSCALE21, 0x00 }, /* 2 */
{ PSMOUSE_CMD_SETRATE, 0x0a }, /* 3 */
{ PSMOUSE_CMD_SETRATE, 0x14 }, /* 4 */
{ PSMOUSE_CMD_SETRATE, 0x28 }, /* 5 */
{ PSMOUSE_CMD_SETRATE, 0x3c }, /* 6 */
{ PSMOUSE_CMD_SETRATE, 0x50 }, /* 7 */
{ PSMOUSE_CMD_SETRATE, 0x64 }, /* 8 */
{ PSMOUSE_CMD_SETRATE, 0xc8 }, /* 9 */
{ ALPS_CMD_NIBBLE_10, 0x00 }, /* a */
{ PSMOUSE_CMD_SETRES, 0x00 }, /* b */
{ PSMOUSE_CMD_SETRES, 0x01 }, /* c */
{ PSMOUSE_CMD_SETRES, 0x02 }, /* d */
{ PSMOUSE_CMD_SETRES, 0x03 }, /* e */
{ PSMOUSE_CMD_SETSCALE11, 0x00 }, /* f */
};
#define ALPS_DUALPOINT 0x02 /* touchpad has trackstick */ #define ALPS_DUALPOINT 0x02 /* touchpad has trackstick */
#define ALPS_PASS 0x04 /* device has a pass-through port */ #define ALPS_PASS 0x04 /* device has a pass-through port */
...@@ -34,30 +78,33 @@ ...@@ -34,30 +78,33 @@
6-byte ALPS packet */ 6-byte ALPS packet */
static const struct alps_model_info alps_model_data[] = { static const struct alps_model_info alps_model_data[] = {
{ { 0x32, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */ { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
{ { 0x33, 0x02, 0x0a }, ALPS_PROTO_V1, 0x88, 0xf8, 0 }, /* UMAX-530T */ { { 0x33, 0x02, 0x0a }, 0x00, ALPS_PROTO_V1, 0x88, 0xf8, 0 }, /* UMAX-530T */
{ { 0x53, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, { { 0x53, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
{ { 0x53, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, { { 0x53, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
{ { 0x60, 0x03, 0xc8 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, /* HP ze1115 */ { { 0x60, 0x03, 0xc8 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, /* HP ze1115 */
{ { 0x63, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, { { 0x63, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
{ { 0x63, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, { { 0x63, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
{ { 0x63, 0x02, 0x28 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 }, /* Fujitsu Siemens S6010 */ { { 0x63, 0x02, 0x28 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 }, /* Fujitsu Siemens S6010 */
{ { 0x63, 0x02, 0x3c }, ALPS_PROTO_V2, 0x8f, 0x8f, ALPS_WHEEL }, /* Toshiba Satellite S2400-103 */ { { 0x63, 0x02, 0x3c }, 0x00, ALPS_PROTO_V2, 0x8f, 0x8f, ALPS_WHEEL }, /* Toshiba Satellite S2400-103 */
{ { 0x63, 0x02, 0x50 }, ALPS_PROTO_V2, 0xef, 0xef, ALPS_FW_BK_1 }, /* NEC Versa L320 */ { { 0x63, 0x02, 0x50 }, 0x00, ALPS_PROTO_V2, 0xef, 0xef, ALPS_FW_BK_1 }, /* NEC Versa L320 */
{ { 0x63, 0x02, 0x64 }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, { { 0x63, 0x02, 0x64 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
{ { 0x63, 0x03, 0xc8 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D800 */ { { 0x63, 0x03, 0xc8 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D800 */
{ { 0x73, 0x00, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_DUALPOINT }, /* ThinkPad R61 8918-5QG */ { { 0x73, 0x00, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_DUALPOINT }, /* ThinkPad R61 8918-5QG */
{ { 0x73, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, 0 }, { { 0x73, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, 0 },
{ { 0x73, 0x02, 0x14 }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 }, /* Ahtec Laptop */ { { 0x73, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_FW_BK_2 }, /* Ahtec Laptop */
{ { 0x20, 0x02, 0x0e }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */ { { 0x20, 0x02, 0x0e }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */
{ { 0x22, 0x02, 0x0a }, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, { { 0x22, 0x02, 0x0a }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },
{ { 0x22, 0x02, 0x14 }, ALPS_PROTO_V2, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D600 */ { { 0x22, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D600 */
/* Dell Latitude E5500, E6400, E6500, Precision M4400 */ /* Dell Latitude E5500, E6400, E6500, Precision M4400 */
{ { 0x62, 0x02, 0x14 }, ALPS_PROTO_V2, 0xcf, 0xcf, { { 0x62, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xcf, 0xcf,
ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED }, ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED },
{ { 0x73, 0x02, 0x50 }, ALPS_PROTO_V2, 0xcf, 0xcf, ALPS_FOUR_BUTTONS }, /* Dell Vostro 1400 */ { { 0x73, 0x02, 0x50 }, 0x00, ALPS_PROTO_V2, 0xcf, 0xcf, ALPS_FOUR_BUTTONS }, /* Dell Vostro 1400 */
{ { 0x52, 0x01, 0x14 }, ALPS_PROTO_V2, 0xff, 0xff, { { 0x52, 0x01, 0x14 }, 0x00, ALPS_PROTO_V2, 0xff, 0xff,
ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED }, /* Toshiba Tecra A11-11L */ ALPS_PASS | ALPS_DUALPOINT | ALPS_PS2_INTERLEAVED }, /* Toshiba Tecra A11-11L */
{ { 0x73, 0x02, 0x64 }, 0x9b, ALPS_PROTO_V3, 0x8f, 0x8f, ALPS_DUALPOINT },
{ { 0x73, 0x02, 0x64 }, 0x9d, ALPS_PROTO_V3, 0x8f, 0x8f, ALPS_DUALPOINT },
{ { 0x73, 0x02, 0x64 }, 0x8a, ALPS_PROTO_V4, 0x8f, 0x8f, 0 },
}; };
/* /*
...@@ -101,7 +148,7 @@ static void alps_report_buttons(struct psmouse *psmouse, ...@@ -101,7 +148,7 @@ static void alps_report_buttons(struct psmouse *psmouse,
input_sync(dev2); input_sync(dev2);
} }
static void alps_process_packet(struct psmouse *psmouse) static void alps_process_packet_v1_v2(struct psmouse *psmouse)
{ {
struct alps_data *priv = psmouse->private; struct alps_data *priv = psmouse->private;
const struct alps_model_info *model = priv->i; const struct alps_model_info *model = priv->i;
...@@ -203,6 +250,224 @@ static void alps_process_packet(struct psmouse *psmouse) ...@@ -203,6 +250,224 @@ static void alps_process_packet(struct psmouse *psmouse)
input_sync(dev); input_sync(dev);
} }
static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
{
struct alps_data *priv = psmouse->private;
unsigned char *packet = psmouse->packet;
struct input_dev *dev = priv->dev2;
int x, y, z, left, right, middle;
/* Sanity check packet */
if (!(packet[0] & 0x40)) {
psmouse_dbg(psmouse, "Bad trackstick packet, discarding\n");
return;
}
/*
* There's a special packet that seems to indicate the end
* of a stream of trackstick data. Filter these out.
*/
if (packet[1] == 0x7f && packet[2] == 0x7f && packet[4] == 0x7f)
return;
x = (s8)(((packet[0] & 0x20) << 2) | (packet[1] & 0x7f));
y = (s8)(((packet[0] & 0x10) << 3) | (packet[2] & 0x7f));
z = (packet[4] & 0x7c) >> 2;
/*
* The x and y values tend to be quite large, and when used
* alone the trackstick is difficult to use. Scale them down
* to compensate.
*/
x /= 8;
y /= 8;
input_report_rel(dev, REL_X, x);
input_report_rel(dev, REL_Y, -y);
/*
* Most ALPS models report the trackstick buttons in the touchpad
* packets, but a few report them here. No reliable way has been
* found to differentiate between the models upfront, so we enable
* the quirk in response to seeing a button press in the trackstick
* packet.
*/
left = packet[3] & 0x01;
right = packet[3] & 0x02;
middle = packet[3] & 0x04;
if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS) &&
(left || right || middle))
priv->quirks |= ALPS_QUIRK_TRACKSTICK_BUTTONS;
if (priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS) {
input_report_key(dev, BTN_LEFT, left);
input_report_key(dev, BTN_RIGHT, right);
input_report_key(dev, BTN_MIDDLE, middle);
}
input_sync(dev);
return;
}
static void alps_process_touchpad_packet_v3(struct psmouse *psmouse)
{
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;
int left, right, middle;
/*
* There's no single feature of touchpad position and bitmap
* packets that can be used to distinguish between them. We
* rely on the fact that a bitmap packet should always follow
* a position packet with bit 6 of packet[4] set.
*/
if (priv->multi_packet) {
priv->multi_packet = 0;
/*
* Sometimes a position packet will indicate a multi-packet
* sequence, but then what follows is another position
* packet. Check for this, and when it happens process the
* position packet as usual.
*/
if (packet[0] & 0x40) {
/*
* Bitmap packets are not yet supported, so for now
* just ignore them.
*/
return;
}
}
if (!priv->multi_packet && (packet[4] & 0x40))
priv->multi_packet = 1;
else
priv->multi_packet = 0;
left = packet[3] & 0x01;
right = packet[3] & 0x02;
middle = packet[3] & 0x04;
x = ((packet[1] & 0x7f) << 4) | ((packet[4] & 0x30) >> 2) |
((packet[0] & 0x30) >> 4);
y = ((packet[2] & 0x7f) << 4) | (packet[4] & 0x0f);
z = packet[5] & 0x7f;
/*
* Sometimes the hardware sends a single packet with z = 0
* in the middle of a stream. Real releases generate packets
* with x, y, and z all zero, so these seem to be flukes.
* Ignore them.
*/
if (x && y && !z)
return;
if (z >= 64)
input_report_key(dev, BTN_TOUCH, 1);
else
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);
input_report_key(dev, BTN_LEFT, left);
input_report_key(dev, BTN_RIGHT, right);
input_report_key(dev, BTN_MIDDLE, middle);
input_sync(dev);
if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
left = packet[3] & 0x10;
right = packet[3] & 0x20;
middle = packet[3] & 0x40;
input_report_key(dev2, BTN_LEFT, left);
input_report_key(dev2, BTN_RIGHT, right);
input_report_key(dev2, BTN_MIDDLE, middle);
input_sync(dev2);
}
}
static void alps_process_packet_v3(struct psmouse *psmouse)
{
unsigned char *packet = psmouse->packet;
/*
* v3 protocol packets come in three types, two representing
* touchpad data and one representing trackstick data.
* Trackstick packets seem to be distinguished by always
* having 0x3f in the last byte. This value has never been
* observed in the last byte of either of the other types
* of packets.
*/
if (packet[5] == 0x3f) {
alps_process_trackstick_packet_v3(psmouse);
return;
}
alps_process_touchpad_packet_v3(psmouse);
}
static void alps_process_packet_v4(struct psmouse *psmouse)
{
unsigned char *packet = psmouse->packet;
struct input_dev *dev = psmouse->dev;
int x, y, z;
int left, right;
left = packet[4] & 0x01;
right = packet[4] & 0x02;
x = ((packet[1] & 0x7f) << 4) | ((packet[3] & 0x30) >> 2) |
((packet[0] & 0x30) >> 4);
y = ((packet[2] & 0x7f) << 4) | (packet[3] & 0x0f);
z = packet[5] & 0x7f;
if (z >= 64)
input_report_key(dev, BTN_TOUCH, 1);
else
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);
input_report_key(dev, BTN_LEFT, left);
input_report_key(dev, BTN_RIGHT, right);
input_sync(dev);
}
static void alps_process_packet(struct psmouse *psmouse)
{
struct alps_data *priv = psmouse->private;
const struct alps_model_info *model = priv->i;
switch (model->proto_version) {
case ALPS_PROTO_V1:
case ALPS_PROTO_V2:
alps_process_packet_v1_v2(psmouse);
break;
case ALPS_PROTO_V3:
alps_process_packet_v3(psmouse);
break;
case ALPS_PROTO_V4:
alps_process_packet_v4(psmouse);
break;
}
}
static void alps_report_bare_ps2_packet(struct psmouse *psmouse, static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
unsigned char packet[], unsigned char packet[],
bool report_buttons) bool report_buttons)
...@@ -376,11 +641,127 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse) ...@@ -376,11 +641,127 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
return PSMOUSE_GOOD_DATA; return PSMOUSE_GOOD_DATA;
} }
static int alps_command_mode_send_nibble(struct psmouse *psmouse, int nibble)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
struct alps_data *priv = psmouse->private;
int command;
unsigned char *param;
unsigned char dummy[4];
BUG_ON(nibble > 0xf);
command = priv->nibble_commands[nibble].command;
param = (command & 0x0f00) ?
dummy : (unsigned char *)&priv->nibble_commands[nibble].data;
if (ps2_command(ps2dev, param, command))
return -1;
return 0;
}
static int alps_command_mode_set_addr(struct psmouse *psmouse, int addr)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
struct alps_data *priv = psmouse->private;
int i, nibble;
if (ps2_command(ps2dev, NULL, priv->addr_command))
return -1;
for (i = 12; i >= 0; i -= 4) {
nibble = (addr >> i) & 0xf;
if (alps_command_mode_send_nibble(psmouse, nibble))
return -1;
}
return 0;
}
static int __alps_command_mode_read_reg(struct psmouse *psmouse, int addr)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
unsigned char param[4];
if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
return -1;
/*
* The address being read is returned in the first two bytes
* of the result. Check that this address matches the expected
* address.
*/
if (addr != ((param[0] << 8) | param[1]))
return -1;
return param[2];
}
static int alps_command_mode_read_reg(struct psmouse *psmouse, int addr)
{
if (alps_command_mode_set_addr(psmouse, addr))
return -1;
return __alps_command_mode_read_reg(psmouse, addr);
}
static int __alps_command_mode_write_reg(struct psmouse *psmouse, u8 value)
{
if (alps_command_mode_send_nibble(psmouse, (value >> 4) & 0xf))
return -1;
if (alps_command_mode_send_nibble(psmouse, value & 0xf))
return -1;
return 0;
}
static int alps_command_mode_write_reg(struct psmouse *psmouse, int addr,
u8 value)
{
if (alps_command_mode_set_addr(psmouse, addr))
return -1;
return __alps_command_mode_write_reg(psmouse, value);
}
static int alps_enter_command_mode(struct psmouse *psmouse,
unsigned char *resp)
{
unsigned char param[4];
struct ps2dev *ps2dev = &psmouse->ps2dev;
if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_RESET_WRAP) ||
ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) {
psmouse_err(psmouse, "failed to enter command mode\n");
return -1;
}
if (param[0] != 0x88 && param[1] != 0x07) {
psmouse_dbg(psmouse,
"unknown response while entering command mode: %2.2x %2.2x %2.2x\n",
param[0], param[1], param[2]);
return -1;
}
if (resp)
*resp = param[2];
return 0;
}
static inline int alps_exit_command_mode(struct psmouse *psmouse)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSTREAM))
return -1;
return 0;
}
static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int *version) static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int *version)
{ {
struct ps2dev *ps2dev = &psmouse->ps2dev; struct ps2dev *ps2dev = &psmouse->ps2dev;
static const unsigned char rates[] = { 0, 10, 20, 40, 60, 80, 100, 200 }; static const unsigned char rates[] = { 0, 10, 20, 40, 60, 80, 100, 200 };
unsigned char param[4]; unsigned char param[4];
const struct alps_model_info *model = NULL;
int i; int i;
/* /*
...@@ -428,12 +809,41 @@ static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int ...@@ -428,12 +809,41 @@ static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int
*version = (param[0] << 8) | (param[1] << 4) | i; *version = (param[0] << 8) | (param[1] << 4) | i;
} }
for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) {
if (!memcmp(param, alps_model_data[i].signature, if (!memcmp(param, alps_model_data[i].signature,
sizeof(alps_model_data[i].signature))) sizeof(alps_model_data[i].signature))) {
return alps_model_data + i; model = alps_model_data + i;
break;
}
}
return NULL; if (model && model->proto_version > ALPS_PROTO_V2) {
/*
* Need to check command mode response to identify
* model
*/
model = NULL;
if (alps_enter_command_mode(psmouse, param)) {
psmouse_warn(psmouse,
"touchpad failed to enter command mode\n");
} else {
for (i = 0; i < ARRAY_SIZE(alps_model_data); i++) {
if (alps_model_data[i].proto_version > ALPS_PROTO_V2 &&
alps_model_data[i].command_mode_resp == param[0]) {
model = alps_model_data + i;
break;
}
}
alps_exit_command_mode(psmouse);
if (!model)
psmouse_dbg(psmouse,
"Unknown command mode response %2.2x\n",
param[0]);
}
}
return model;
} }
/* /*
...@@ -441,7 +851,7 @@ static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int ...@@ -441,7 +851,7 @@ static const struct alps_model_info *alps_get_model(struct psmouse *psmouse, int
* subsequent commands. It looks like glidepad is behind stickpointer, * subsequent commands. It looks like glidepad is behind stickpointer,
* I'd thought it would be other way around... * I'd thought it would be other way around...
*/ */
static int alps_passthrough_mode(struct psmouse *psmouse, bool enable) static int alps_passthrough_mode_v2(struct psmouse *psmouse, bool enable)
{ {
struct ps2dev *ps2dev = &psmouse->ps2dev; struct ps2dev *ps2dev = &psmouse->ps2dev;
int cmd = enable ? PSMOUSE_CMD_SETSCALE21 : PSMOUSE_CMD_SETSCALE11; int cmd = enable ? PSMOUSE_CMD_SETSCALE21 : PSMOUSE_CMD_SETSCALE11;
...@@ -458,7 +868,7 @@ static int alps_passthrough_mode(struct psmouse *psmouse, bool enable) ...@@ -458,7 +868,7 @@ static int alps_passthrough_mode(struct psmouse *psmouse, bool enable)
return 0; return 0;
} }
static int alps_absolute_mode(struct psmouse *psmouse) static int alps_absolute_mode_v1_v2(struct psmouse *psmouse)
{ {
struct ps2dev *ps2dev = &psmouse->ps2dev; struct ps2dev *ps2dev = &psmouse->ps2dev;
...@@ -533,13 +943,13 @@ static int alps_poll(struct psmouse *psmouse) ...@@ -533,13 +943,13 @@ static int alps_poll(struct psmouse *psmouse)
bool poll_failed; bool poll_failed;
if (priv->i->flags & ALPS_PASS) if (priv->i->flags & ALPS_PASS)
alps_passthrough_mode(psmouse, true); alps_passthrough_mode_v2(psmouse, true);
poll_failed = ps2_command(&psmouse->ps2dev, buf, poll_failed = ps2_command(&psmouse->ps2dev, buf,
PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)) < 0; PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)) < 0;
if (priv->i->flags & ALPS_PASS) if (priv->i->flags & ALPS_PASS)
alps_passthrough_mode(psmouse, false); alps_passthrough_mode_v2(psmouse, false);
if (poll_failed || (buf[0] & priv->i->mask0) != priv->i->byte0) if (poll_failed || (buf[0] & priv->i->mask0) != priv->i->byte0)
return -1; return -1;
...@@ -556,13 +966,13 @@ static int alps_poll(struct psmouse *psmouse) ...@@ -556,13 +966,13 @@ static int alps_poll(struct psmouse *psmouse)
return 0; return 0;
} }
static int alps_hw_init(struct psmouse *psmouse) static int alps_hw_init_v1_v2(struct psmouse *psmouse)
{ {
struct alps_data *priv = psmouse->private; struct alps_data *priv = psmouse->private;
const struct alps_model_info *model = priv->i; const struct alps_model_info *model = priv->i;
if ((model->flags & ALPS_PASS) && if ((model->flags & ALPS_PASS) &&
alps_passthrough_mode(psmouse, true)) { alps_passthrough_mode_v2(psmouse, true)) {
return -1; return -1;
} }
...@@ -571,13 +981,13 @@ static int alps_hw_init(struct psmouse *psmouse) ...@@ -571,13 +981,13 @@ static int alps_hw_init(struct psmouse *psmouse)
return -1; return -1;
} }
if (alps_absolute_mode(psmouse)) { if (alps_absolute_mode_v1_v2(psmouse)) {
psmouse_err(psmouse, "Failed to enable absolute mode\n"); psmouse_err(psmouse, "Failed to enable absolute mode\n");
return -1; return -1;
} }
if ((model->flags & ALPS_PASS) && if ((model->flags & ALPS_PASS) &&
alps_passthrough_mode(psmouse, false)) { alps_passthrough_mode_v2(psmouse, false)) {
return -1; return -1;
} }
...@@ -590,6 +1000,297 @@ static int alps_hw_init(struct psmouse *psmouse) ...@@ -590,6 +1000,297 @@ static int alps_hw_init(struct psmouse *psmouse)
return 0; return 0;
} }
/*
* Enable or disable passthrough mode to the trackstick. Must be in
* command mode when calling this function.
*/
static int alps_passthrough_mode_v3(struct psmouse *psmouse, bool enable)
{
int reg_val;
reg_val = alps_command_mode_read_reg(psmouse, 0x0008);
if (reg_val == -1)
return -1;
if (enable)
reg_val |= 0x01;
else
reg_val &= ~0x01;
if (__alps_command_mode_write_reg(psmouse, reg_val))
return -1;
return 0;
}
/* Must be in command mode when calling this function */
static int alps_absolute_mode_v3(struct psmouse *psmouse)
{
int reg_val;
reg_val = alps_command_mode_read_reg(psmouse, 0x0004);
if (reg_val == -1)
return -1;
reg_val |= 0x06;
if (__alps_command_mode_write_reg(psmouse, reg_val))
return -1;
return 0;
}
static int alps_hw_init_v3(struct psmouse *psmouse)
{
struct alps_data *priv = psmouse->private;
struct ps2dev *ps2dev = &psmouse->ps2dev;
int reg_val;
unsigned char param[4];
priv->nibble_commands = alps_v3_nibble_commands;
priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
if (alps_enter_command_mode(psmouse, NULL))
goto error;
/* Check for trackstick */
reg_val = alps_command_mode_read_reg(psmouse, 0x0008);
if (reg_val == -1)
goto error;
if (reg_val & 0x80) {
if (alps_passthrough_mode_v3(psmouse, true))
goto error;
if (alps_exit_command_mode(psmouse))
goto error;
/*
* E7 report for the trackstick
*
* There have been reports of failures to seem to trace back
* to the above trackstick check failing. When these occur
* this E7 report fails, so when that happens we continue
* with the assumption that there isn't a trackstick after
* all.
*/
param[0] = 0x64;
if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) {
psmouse_warn(psmouse, "trackstick E7 report failed\n");
} else {
psmouse_dbg(psmouse,
"trackstick E7 report: %2.2x %2.2x %2.2x\n",
param[0], param[1], param[2]);
/*
* Not sure what this does, but it is absolutely
* essential. Without it, the touchpad does not
* work at all and the trackstick just emits normal
* PS/2 packets.
*/
if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE11) ||
alps_command_mode_send_nibble(psmouse, 0x9) ||
alps_command_mode_send_nibble(psmouse, 0x4)) {
psmouse_err(psmouse,
"Error sending magic E6 sequence\n");
goto error_passthrough;
}
}
if (alps_enter_command_mode(psmouse, NULL))
goto error_passthrough;
if (alps_passthrough_mode_v3(psmouse, false))
goto error;
}
if (alps_absolute_mode_v3(psmouse)) {
psmouse_err(psmouse, "Failed to enter absolute mode\n");
goto error;
}
reg_val = alps_command_mode_read_reg(psmouse, 0x0006);
if (reg_val == -1)
goto error;
if (__alps_command_mode_write_reg(psmouse, reg_val | 0x01))
goto error;
reg_val = alps_command_mode_read_reg(psmouse, 0x0007);
if (reg_val == -1)
goto error;
if (__alps_command_mode_write_reg(psmouse, reg_val | 0x01))
goto error;
if (alps_command_mode_read_reg(psmouse, 0x0144) == -1)
goto error;
if (__alps_command_mode_write_reg(psmouse, 0x04))
goto error;
if (alps_command_mode_read_reg(psmouse, 0x0159) == -1)
goto error;
if (__alps_command_mode_write_reg(psmouse, 0x03))
goto error;
if (alps_command_mode_read_reg(psmouse, 0x0163) == -1)
goto error;
if (alps_command_mode_write_reg(psmouse, 0x0163, 0x03))
goto error;
if (alps_command_mode_read_reg(psmouse, 0x0162) == -1)
goto error;
if (alps_command_mode_write_reg(psmouse, 0x0162, 0x04))
goto error;
/*
* This ensures the trackstick packets are in the format
* supported by this driver. If bit 1 isn't set the packet
* format is different.
*/
if (alps_command_mode_write_reg(psmouse, 0x0008, 0x82))
goto error;
alps_exit_command_mode(psmouse);
/* Set rate and enable data reporting */
param[0] = 0x64;
if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) {
psmouse_err(psmouse, "Failed to enable data reporting\n");
return -1;
}
return 0;
error_passthrough:
/* Something failed while in passthrough mode, so try to get out */
if (!alps_enter_command_mode(psmouse, NULL))
alps_passthrough_mode_v3(psmouse, false);
error:
/*
* Leaving the touchpad in command mode will essentially render
* it unusable until the machine reboots, so exit it here just
* to be safe
*/
alps_exit_command_mode(psmouse);
return -1;
}
/* Must be in command mode when calling this function */
static int alps_absolute_mode_v4(struct psmouse *psmouse)
{
int reg_val;
reg_val = alps_command_mode_read_reg(psmouse, 0x0004);
if (reg_val == -1)
return -1;
reg_val |= 0x02;
if (__alps_command_mode_write_reg(psmouse, reg_val))
return -1;
return 0;
}
static int alps_hw_init_v4(struct psmouse *psmouse)
{
struct alps_data *priv = psmouse->private;
struct ps2dev *ps2dev = &psmouse->ps2dev;
unsigned char param[4];
priv->nibble_commands = alps_v4_nibble_commands;
priv->addr_command = PSMOUSE_CMD_DISABLE;
if (alps_enter_command_mode(psmouse, NULL))
goto error;
if (alps_absolute_mode_v4(psmouse)) {
psmouse_err(psmouse, "Failed to enter absolute mode\n");
goto error;
}
if (alps_command_mode_write_reg(psmouse, 0x0007, 0x8c))
goto error;
if (alps_command_mode_write_reg(psmouse, 0x0149, 0x03))
goto error;
if (alps_command_mode_write_reg(psmouse, 0x0160, 0x03))
goto error;
if (alps_command_mode_write_reg(psmouse, 0x017f, 0x15))
goto error;
if (alps_command_mode_write_reg(psmouse, 0x0151, 0x01))
goto error;
if (alps_command_mode_write_reg(psmouse, 0x0168, 0x03))
goto error;
if (alps_command_mode_write_reg(psmouse, 0x014a, 0x03))
goto error;
if (alps_command_mode_write_reg(psmouse, 0x0161, 0x03))
goto error;
alps_exit_command_mode(psmouse);
/*
* This sequence changes the output from a 9-byte to an
* 8-byte format. All the same data seems to be present,
* just in a more compact format.
*/
param[0] = 0xc8;
param[1] = 0x64;
param[2] = 0x50;
if (ps2_command(ps2dev, &param[0], PSMOUSE_CMD_SETRATE) ||
ps2_command(ps2dev, &param[1], PSMOUSE_CMD_SETRATE) ||
ps2_command(ps2dev, &param[2], PSMOUSE_CMD_SETRATE) ||
ps2_command(ps2dev, param, PSMOUSE_CMD_GETID))
return -1;
/* Set rate and enable data reporting */
param[0] = 0x64;
if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRATE) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)) {
psmouse_err(psmouse, "Failed to enable data reporting\n");
return -1;
}
return 0;
error:
/*
* Leaving the touchpad in command mode will essentially render
* it unusable until the machine reboots, so exit it here just
* to be safe
*/
alps_exit_command_mode(psmouse);
return -1;
}
static int alps_hw_init(struct psmouse *psmouse)
{
struct alps_data *priv = psmouse->private;
const struct alps_model_info *model = priv->i;
int ret = -1;
switch (model->proto_version) {
case ALPS_PROTO_V1:
case ALPS_PROTO_V2:
ret = alps_hw_init_v1_v2(psmouse);
break;
case ALPS_PROTO_V3:
ret = alps_hw_init_v3(psmouse);
break;
case ALPS_PROTO_V4:
ret = alps_hw_init_v4(psmouse);
break;
}
return ret;
}
static int alps_reconnect(struct psmouse *psmouse) static int alps_reconnect(struct psmouse *psmouse)
{ {
const struct alps_model_info *model; const struct alps_model_info *model;
...@@ -630,6 +1331,8 @@ int alps_init(struct psmouse *psmouse) ...@@ -630,6 +1331,8 @@ int alps_init(struct psmouse *psmouse)
psmouse->private = priv; psmouse->private = priv;
psmouse_reset(psmouse);
model = alps_get_model(psmouse, &version); model = alps_get_model(psmouse, &version);
if (!model) if (!model)
goto init_fail; goto init_fail;
...@@ -657,8 +1360,20 @@ int alps_init(struct psmouse *psmouse) ...@@ -657,8 +1360,20 @@ int alps_init(struct psmouse *psmouse)
BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT); BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT);
dev1->evbit[BIT_WORD(EV_ABS)] |= BIT_MASK(EV_ABS); dev1->evbit[BIT_WORD(EV_ABS)] |= BIT_MASK(EV_ABS);
input_set_abs_params(dev1, ABS_X, 0, 1023, 0, 0);
input_set_abs_params(dev1, ABS_Y, 0, 767, 0, 0); switch (model->proto_version) {
case ALPS_PROTO_V1:
case ALPS_PROTO_V2:
input_set_abs_params(dev1, ABS_X, 0, 1023, 0, 0);
input_set_abs_params(dev1, ABS_Y, 0, 767, 0, 0);
break;
case ALPS_PROTO_V3:
case ALPS_PROTO_V4:
input_set_abs_params(dev1, ABS_X, 0, 2000, 0, 0);
input_set_abs_params(dev1, ABS_Y, 0, 1400, 0, 0);
break;
}
input_set_abs_params(dev1, ABS_PRESSURE, 0, 127, 0, 0); input_set_abs_params(dev1, ABS_PRESSURE, 0, 127, 0, 0);
if (model->flags & ALPS_WHEEL) { if (model->flags & ALPS_WHEEL) {
...@@ -701,7 +1416,7 @@ int alps_init(struct psmouse *psmouse) ...@@ -701,7 +1416,7 @@ int alps_init(struct psmouse *psmouse)
psmouse->poll = alps_poll; psmouse->poll = alps_poll;
psmouse->disconnect = alps_disconnect; psmouse->disconnect = alps_disconnect;
psmouse->reconnect = alps_reconnect; psmouse->reconnect = alps_reconnect;
psmouse->pktsize = 6; psmouse->pktsize = model->proto_version == ALPS_PROTO_V4 ? 8 : 6;
/* We are having trouble resyncing ALPS touchpads so disable it for now */ /* We are having trouble resyncing ALPS touchpads so disable it for now */
psmouse->resync_time = 0; psmouse->resync_time = 0;
......
...@@ -14,22 +14,36 @@ ...@@ -14,22 +14,36 @@
#define ALPS_PROTO_V1 0 #define ALPS_PROTO_V1 0
#define ALPS_PROTO_V2 1 #define ALPS_PROTO_V2 1
#define ALPS_PROTO_V3 2
#define ALPS_PROTO_V4 3
struct alps_model_info { struct alps_model_info {
unsigned char signature[3]; unsigned char signature[3];
unsigned char command_mode_resp; /* v3/v4 only */
unsigned char proto_version; unsigned char proto_version;
unsigned char byte0, mask0; unsigned char byte0, mask0;
unsigned char flags; unsigned char flags;
}; };
struct alps_nibble_commands {
int command;
unsigned char data;
};
struct alps_data { struct alps_data {
struct input_dev *dev2; /* Relative device */ struct input_dev *dev2; /* Relative device */
char phys[32]; /* Phys */ char phys[32]; /* Phys */
const struct alps_model_info *i;/* Info */ const struct alps_model_info *i;/* Info */
const struct alps_nibble_commands *nibble_commands;
int addr_command; /* Command to set register address */
int prev_fin; /* Finger bit from previous packet */ int prev_fin; /* Finger bit from previous packet */
int multi_packet; /* Multi-packet data in progress */
u8 quirks;
struct timer_list timer; struct timer_list timer;
}; };
#define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /* trakcstick buttons in trackstick packet */
#ifdef CONFIG_MOUSE_PS2_ALPS #ifdef CONFIG_MOUSE_PS2_ALPS
int alps_detect(struct psmouse *psmouse, bool set_properties); int alps_detect(struct psmouse *psmouse, bool set_properties);
int alps_init(struct psmouse *psmouse); int alps_init(struct psmouse *psmouse);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#define PSMOUSE_CMD_SETSTREAM 0x00ea #define PSMOUSE_CMD_SETSTREAM 0x00ea
#define PSMOUSE_CMD_SETPOLL 0x00f0 #define PSMOUSE_CMD_SETPOLL 0x00f0
#define PSMOUSE_CMD_POLL 0x00eb /* caller sets number of bytes to receive */ #define PSMOUSE_CMD_POLL 0x00eb /* caller sets number of bytes to receive */
#define PSMOUSE_CMD_RESET_WRAP 0x00ec
#define PSMOUSE_CMD_GETID 0x02f2 #define PSMOUSE_CMD_GETID 0x02f2
#define PSMOUSE_CMD_SETRATE 0x10f3 #define PSMOUSE_CMD_SETRATE 0x10f3
#define PSMOUSE_CMD_ENABLE 0x00f4 #define PSMOUSE_CMD_ENABLE 0x00f4
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment