From patchwork Wed Oct 6 15:50:26 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Drake X-Patchwork-Id: 236081 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id o96FpiJ9000347 for ; Wed, 6 Oct 2010 15:51:44 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1759250Ab0JFPvT (ORCPT ); Wed, 6 Oct 2010 11:51:19 -0400 Received: from mtaout02-winn.ispmail.ntl.com ([81.103.221.48]:53677 "EHLO mtaout02-winn.ispmail.ntl.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1758828Ab0JFPvR (ORCPT ); Wed, 6 Oct 2010 11:51:17 -0400 Received: from aamtaout04-winn.ispmail.ntl.com ([81.103.221.35]) by mtaout02-winn.ispmail.ntl.com (InterMail vM.7.08.04.00 201-2186-134-20080326) with ESMTP id <20101006155041.JVAK7723.mtaout02-winn.ispmail.ntl.com@aamtaout04-winn.ispmail.ntl.com>; Wed, 6 Oct 2010 16:50:41 +0100 Received: from zog.reactivated.net ([86.14.215.141]) by aamtaout04-winn.ispmail.ntl.com (InterMail vG.2.02.00.01 201-2161-120-102-20060912) with ESMTP id <20101006155039.FNGQ22376.aamtaout04-winn.ispmail.ntl.com@zog.reactivated.net>; Wed, 6 Oct 2010 16:50:39 +0100 Received: by zog.reactivated.net (Postfix, from userid 1000) id 5BB289D401B; Wed, 6 Oct 2010 16:50:26 +0100 (BST) From: Daniel Drake To: dmitry.torokhov@gmail.com To: dtor@mail.ru Cc: linux-input@vger.kernel.org Cc: pgf@laptop.org Subject: [PATCH] Input: hgpk - support GlideSensor and PenTablet modes Message-Id: <20101006155026.5BB289D401B@zog.reactivated.net> Date: Wed, 6 Oct 2010 16:50:26 +0100 (BST) X-Cloudmark-Analysis: v=1.1 cv=4QByPj+6Iq2k/6L54d+eVKTdgQxdscpRskJJReCfdXo= c=1 sm=0 a=rapgC87yYecA:10 a=Op-mwl0xAAAA:8 a=9g2umvp-_N7mLMFYxAUA:9 a=Nrnp5XviIZgjdMglQ0EA:7 a=Jq2baByXRqnF0L58RHEZmGEbaC0A:4 a=d4CUUju0HPYA:10 a=HpAAvcLHHh0Zw7uRqdWCyQ==:117 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter1.kernel.org [140.211.167.41]); Wed, 06 Oct 2010 15:51:44 +0000 (UTC) diff --git a/drivers/input/mouse/hgpk.c b/drivers/input/mouse/hgpk.c index 1d2205b..6115e0d 100644 --- a/drivers/input/mouse/hgpk.c +++ b/drivers/input/mouse/hgpk.c @@ -69,6 +69,13 @@ module_param(post_interrupt_delay, int, 0644); MODULE_PARM_DESC(post_interrupt_delay, "delay (ms) before recal after recal interrupt detected"); +int hgpk_mode = HGPK_MODE_MOUSE; +static const char * const mode_names[] = { + [HGPK_MODE_MOUSE] = "Mouse", + [HGPK_MODE_GLIDESENSOR] = "GlideSensor", + [HGPK_MODE_PENTABLET] = "PenTablet", +}; + /* * When the touchpad gets ultra-sensitive, one can keep their finger 1/2" * above the pad and still have it send packets. This causes a jump cursor @@ -143,23 +150,137 @@ static void hgpk_spewing_hack(struct psmouse *psmouse, * swr/swl are the left/right buttons. * x-neg/y-neg are the x and y delta negative bits * x-over/y-over are the x and y overflow bits + * + * --- + * + * HGPK Advanced Mode - single-mode format + * + * byte 0(PT): 1 1 0 0 1 1 1 1 + * byte 0(GS): 1 1 1 1 1 1 1 1 + * byte 1: 0 x6 x5 x4 x3 x2 x1 x0 + * byte 2(PT): 0 0 x9 x8 x7 ? pt-dsw 0 + * byte 2(GS): 0 x10 x9 x8 x7 ? gs-dsw pt-dsw + * byte 3: 0 y9 y8 y7 1 0 swr swl + * byte 4: 0 y6 y5 y4 y3 y2 y1 y0 + * byte 5: 0 z6 z5 z4 z3 z2 z1 z0 + * + * ?'s are not defined in the protocol spec, may vary between models. + * + * swr/swl are the left/right buttons. + * + * pt-dsw/gs-dsw indicate that the pt/gs sensor is detecting a + * pen/finger */ -static int hgpk_validate_byte(unsigned char *packet) +static int hgpk_validate_byte(struct psmouse *psmouse, unsigned char *packet) { - return (packet[0] & 0x0C) != 0x08; + struct hgpk_data *priv = psmouse->private; + int pktcnt = psmouse->pktcnt; + int r = 0; + + switch (priv->mode) { + case HGPK_MODE_MOUSE: + r = (packet[0] & 0x0C) != 0x08; + if (r) + hgpk_dbg(psmouse, "bad data (%d) %02x %02x %02x\n", + psmouse->pktcnt, psmouse->packet[0], + psmouse->packet[1], psmouse->packet[2]); + break; + + case HGPK_MODE_GLIDESENSOR: + case HGPK_MODE_PENTABLET: + /* bytes 2 - 6 should have 0 in the highest bit */ + if (pktcnt >= 2 && pktcnt <= 6 && (packet[pktcnt - 1] & 0x80)) + r = -1; + if (priv->mode == HGPK_MODE_GLIDESENSOR && packet[0] != HGPK_GS) + r = -1; + if (priv->mode == HGPK_MODE_PENTABLET && packet[0] != HGPK_PT) + r = -1; + if (r) + hgpk_dbg(psmouse, "bad data, mode %d (%d) " + "%02x %02x %02x %02x %02x %02x\n", + priv->mode, psmouse->pktcnt, + psmouse->packet[0], psmouse->packet[1], + psmouse->packet[2], psmouse->packet[3], + psmouse->packet[4], psmouse->packet[5]); + break; + } + return r; } -static void hgpk_process_packet(struct psmouse *psmouse) +static void hgpk_process_advanced_packet(struct psmouse *psmouse) { - struct input_dev *dev = psmouse->dev; + struct hgpk_data *priv = psmouse->private; + struct input_dev *idev = psmouse->dev; unsigned char *packet = psmouse->packet; - int x, y, left, right; + int left = !!(packet[3] & 1); + int right = !!(packet[3] & 2); + int x = packet[1] | ((packet[2] & 0x78) << 4); + int y = packet[4] | ((packet[3] & 0x70) << 3); + int z = packet[5]; + int down; + + if (priv->mode == HGPK_MODE_GLIDESENSOR) { + int pt_down = !!(packet[2] & 1); + int finger_down = !!(packet[2] & 2); + + BUG_ON(packet[0] == HGPK_PT); + input_report_abs(idev, ABS_PRESSURE, z); + down = finger_down; + if (tpdebug) + hgpk_dbg(psmouse, "pd=%d fd=%d ", + pt_down, finger_down); + } else { + BUG_ON(packet[0] == HGPK_GS); + down = !!(packet[2] & 2); + if (tpdebug) + hgpk_dbg(psmouse, "pd=%d ", down); + } - left = packet[0] & 1; - right = (packet[0] >> 1) & 1; + if (tpdebug) + hgpk_dbg(psmouse, "l=%d r=%d x=%d y=%d z=%d\n", + left, right, x, y, z); - x = packet[1] - ((packet[0] << 4) & 0x100); - y = ((packet[0] << 3) & 0x100) - packet[2]; + input_report_key(idev, BTN_TOUCH, down); + input_report_key(idev, BTN_LEFT, left); + input_report_key(idev, BTN_RIGHT, right); + + /* + * if this packet says that the finger was removed, reset our position + * tracking so that we don't erroneously detect a jump on next press. + */ + if (!down) + priv->abs_x = priv->abs_y = -1; + + /* Report position if finger/pen is down, but weed out duplicate + * packets (we get quite a few in this mode, and they mess up our + * jump detection */ + if (down && (x != priv->abs_x || y != priv->abs_y)) { + + /* Don't apply hacks in PT mode, it seems reliable */ + if (priv->mode != HGPK_MODE_PENTABLET && priv->abs_x != -1) { + hgpk_jumpy_hack(psmouse, + priv->abs_x - x, priv->abs_y - y); + hgpk_spewing_hack(psmouse, left, right, + priv->abs_x - x, priv->abs_y - y); + } + + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + priv->abs_x = x; + priv->abs_y = y; + } + + input_sync(idev); +} + +static void hgpk_process_simple_packet(struct psmouse *psmouse) +{ + struct input_dev *dev = psmouse->dev; + unsigned char *packet = psmouse->packet; + int left = packet[0] & 1; + int right = (packet[0] >> 1) & 1; + int x = packet[1] - ((packet[0] << 4) & 0x100); + int y = ((packet[0] << 3) & 0x100) - packet[2]; hgpk_jumpy_hack(psmouse, x, y); hgpk_spewing_hack(psmouse, left, right, x, y); @@ -180,15 +301,14 @@ static psmouse_ret_t hgpk_process_byte(struct psmouse *psmouse) { struct hgpk_data *priv = psmouse->private; - if (hgpk_validate_byte(psmouse->packet)) { - hgpk_dbg(psmouse, "%s: (%d) %02x %02x %02x\n", - __func__, psmouse->pktcnt, psmouse->packet[0], - psmouse->packet[1], psmouse->packet[2]); + if (hgpk_validate_byte(psmouse, psmouse->packet)) return PSMOUSE_BAD_DATA; - } if (psmouse->pktcnt >= psmouse->pktsize) { - hgpk_process_packet(psmouse); + if (priv->mode == HGPK_MODE_MOUSE) + hgpk_process_simple_packet(psmouse); + else + hgpk_process_advanced_packet(psmouse); return PSMOUSE_FULL_PACKET; } @@ -210,6 +330,59 @@ static psmouse_ret_t hgpk_process_byte(struct psmouse *psmouse) return PSMOUSE_GOOD_DATA; } +static int hgpk_select_mode(struct psmouse *psmouse) +{ + struct ps2dev *ps2dev = &psmouse->ps2dev; + struct hgpk_data *priv = psmouse->private; + int i; + int cmd; + + /* + * 4 disables to enable advanced mode + * then 3 0xf2 bytes as the preamble for GS/PT selection + */ + const int advanced_init[] = { + PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE, + PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE, + 0xf2, 0xf2, 0xf2, + }; + + switch (priv->mode) { + case HGPK_MODE_MOUSE: + psmouse->pktsize = 3; + break; + + case HGPK_MODE_GLIDESENSOR: + case HGPK_MODE_PENTABLET: + psmouse->pktsize = 6; + + /* Switch to 'Advanced mode.', four disables in a row. */ + for (i = 0; i < ARRAY_SIZE(advanced_init); i++) + if (ps2_command(ps2dev, NULL, advanced_init[i])) + return -EIO; + + /* select between GlideSensor (mouse) or PenTablet */ + if (priv->mode == HGPK_MODE_GLIDESENSOR) + cmd = PSMOUSE_CMD_SETSCALE11; + else + cmd = PSMOUSE_CMD_SETSCALE21; + + if (ps2_command(ps2dev, NULL, cmd)) + return -EIO; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void reset_hack_state(struct psmouse *psmouse) +{ + struct hgpk_data *priv = psmouse->private; + priv->abs_x = priv->abs_y = -1; +} + static int hgpk_force_recalibrate(struct psmouse *psmouse) { struct ps2dev *ps2dev = &psmouse->ps2dev; @@ -236,6 +409,13 @@ static int hgpk_force_recalibrate(struct psmouse *psmouse) /* according to ALPS, 150mS is required for recalibration */ msleep(150); + if (hgpk_select_mode(psmouse)) { + hgpk_err(psmouse, "failed to select mode\n"); + return -1; + } + + reset_hack_state(psmouse); + /* XXX: If a finger is down during this delay, recalibration will * detect capacitance incorrectly. This is a hardware bug, and * we don't have a good way to deal with it. The 2s window stuff @@ -290,6 +470,13 @@ static int hgpk_toggle_power(struct psmouse *psmouse, int enable) psmouse_reset(psmouse); + if (hgpk_select_mode(psmouse)) { + hgpk_err(psmouse, "Failed to select mode!\n"); + return -1; + } + + reset_hack_state(psmouse); + /* should be all set, enable the touchpad */ ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_ENABLE); psmouse_set_state(psmouse, PSMOUSE_ACTIVATED); @@ -328,7 +515,12 @@ static int hgpk_reconnect(struct psmouse *psmouse) return 0; psmouse_reset(psmouse); + if (hgpk_select_mode(psmouse)) { + hgpk_err(psmouse, "Failed to select mode!\n"); + return -1; + } + reset_hack_state(psmouse); return 0; } @@ -366,6 +558,35 @@ static ssize_t hgpk_set_powered(struct psmouse *psmouse, void *data, __PSMOUSE_DEFINE_ATTR(powered, S_IWUSR | S_IRUGO, NULL, hgpk_show_powered, hgpk_set_powered, false); +static ssize_t attr_show_mode(struct psmouse *psmouse, void *data, char *buf) +{ + return sprintf(buf, "%s\n", mode_names[hgpk_mode]); +} + +static ssize_t attr_set_mode(struct psmouse *psmouse, void *data, + const char *buf, size_t len) +{ + int i; + int new_mode = -1; + + for (i = 0; i < ARRAY_SIZE(mode_names); i++) { + const char *name = mode_names[i]; + if (strlen(name) == len && !strncasecmp(name, buf, len)) { + new_mode = i; + break; + } + } + + if (new_mode == -1) + return -EINVAL; + + hgpk_mode = new_mode; + return len; +} + +__PSMOUSE_DEFINE_ATTR(hgpk_mode, S_IWUSR | S_IRUGO, NULL, + attr_show_mode, attr_set_mode, 0); + static ssize_t hgpk_trigger_recal_show(struct psmouse *psmouse, void *data, char *buf) { @@ -401,6 +622,8 @@ static void hgpk_disconnect(struct psmouse *psmouse) device_remove_file(&psmouse->ps2dev.serio->dev, &psmouse_attr_powered.dattr); + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_hgpk_mode.dattr); if (psmouse->model >= HGPK_MODEL_C) device_remove_file(&psmouse->ps2dev.serio->dev, @@ -424,6 +647,8 @@ static void hgpk_recalib_work(struct work_struct *work) static int hgpk_register(struct psmouse *psmouse) { + struct hgpk_data *priv = psmouse->private; + struct input_dev *idev = psmouse->dev; int err; /* register handlers */ @@ -431,13 +656,45 @@ static int hgpk_register(struct psmouse *psmouse) psmouse->poll = hgpk_poll; psmouse->disconnect = hgpk_disconnect; psmouse->reconnect = hgpk_reconnect; - psmouse->pktsize = 3; /* Disable the idle resync. */ psmouse->resync_time = 0; /* Reset after a lot of bad bytes. */ psmouse->resetafter = 1024; + if (priv->mode != HGPK_MODE_MOUSE) { + __set_bit(EV_ABS, idev->evbit); + __set_bit(EV_KEY, idev->evbit); + __set_bit(BTN_TOUCH, idev->keybit); + __set_bit(BTN_TOOL_FINGER, idev->keybit); + __set_bit(BTN_LEFT, idev->keybit); + __set_bit(BTN_RIGHT, idev->keybit); + __clear_bit(EV_REL, idev->evbit); + __clear_bit(REL_X, idev->relbit); + __clear_bit(REL_Y, idev->relbit); + } + + if (priv->mode == HGPK_MODE_GLIDESENSOR) { + /* GlideSensor has pressure sensor, PenTablet does not */ + input_set_abs_params(idev, ABS_PRESSURE, 0, 15, 0, 0); + + /* From device specs */ + input_set_abs_params(idev, ABS_X, 0, 399, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, 290, 0, 0); + + /* Calculated by hand based on usable size (52mm x 38mm) */ + input_abs_set_res(idev, ABS_X, 8); + input_abs_set_res(idev, ABS_Y, 8); + } else if (priv->mode == HGPK_MODE_PENTABLET) { + /* From device specs */ + input_set_abs_params(idev, ABS_X, 0, 999, 0, 0); + input_set_abs_params(idev, ABS_Y, 5, 239, 0, 0); + + /* Calculated by hand based on usable size (156mm x 38mm) */ + input_abs_set_res(idev, ABS_X, 6); + input_abs_set_res(idev, ABS_Y, 8); + } + err = device_create_file(&psmouse->ps2dev.serio->dev, &psmouse_attr_powered.dattr); if (err) { @@ -445,6 +702,13 @@ static int hgpk_register(struct psmouse *psmouse) return err; } + err = device_create_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_hgpk_mode.dattr); + if (err) { + hgpk_err(psmouse, "Failed creating 'hgpk_mode' sysfs node\n"); + goto err_remove_powered; + } + /* C-series touchpads added the recalibrate command */ if (psmouse->model >= HGPK_MODEL_C) { err = device_create_file(&psmouse->ps2dev.serio->dev, @@ -452,13 +716,19 @@ static int hgpk_register(struct psmouse *psmouse) if (err) { hgpk_err(psmouse, "Failed creating 'recalibrate' sysfs node\n"); - device_remove_file(&psmouse->ps2dev.serio->dev, - &psmouse_attr_powered.dattr); - return err; + goto err_remove_mode; } } return 0; + +err_remove_mode: + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_hgpk_mode.dattr); +err_remove_powered: + device_remove_file(&psmouse->ps2dev.serio->dev, + &psmouse_attr_powered.dattr); + return err; } int hgpk_init(struct psmouse *psmouse) @@ -473,12 +743,19 @@ int hgpk_init(struct psmouse *psmouse) psmouse->private = priv; priv->psmouse = psmouse; priv->powered = true; + priv->mode = hgpk_mode; INIT_DELAYED_WORK(&priv->recalib_wq, hgpk_recalib_work); err = psmouse_reset(psmouse); if (err) goto init_fail; + err = hgpk_select_mode(psmouse); + if (err) + goto init_fail; + + reset_hack_state(psmouse); + err = hgpk_register(psmouse); if (err) goto init_fail; diff --git a/drivers/input/mouse/hgpk.h b/drivers/input/mouse/hgpk.h index d61cfd3..430f29f 100644 --- a/drivers/input/mouse/hgpk.h +++ b/drivers/input/mouse/hgpk.h @@ -5,6 +5,9 @@ #ifndef _HGPK_H #define _HGPK_H +#define HGPK_GS 0xff /* The GlideSensor */ +#define HGPK_PT 0xcf /* The PenTablet */ + enum hgpk_model_t { HGPK_MODEL_PREA = 0x0a, /* pre-B1s */ HGPK_MODEL_A = 0x14, /* found on B1s, PT disabled in hardware */ @@ -13,12 +16,20 @@ enum hgpk_model_t { HGPK_MODEL_D = 0x50, /* C1, mass production */ }; +enum hgpk_mode { + HGPK_MODE_MOUSE, + HGPK_MODE_GLIDESENSOR, + HGPK_MODE_PENTABLET, +}; + struct hgpk_data { struct psmouse *psmouse; + int mode; bool powered; int count, x_tally, y_tally; /* hardware workaround stuff */ unsigned long recalib_window; struct delayed_work recalib_wq; + int abs_x, abs_y; }; #define hgpk_dbg(psmouse, format, arg...) \