Re: [RPC] OLPC tablet input driver.

Dmitry Torokhov
2006-08-29T12:53:21Z

Hi,

On 8/29/06, Zephaniah E. Hull <warp@aehallh.com> wrote:
> The OLPC will ship with a somewhat unique input device made by ALPS,
> connected via PS/2 and speaking a protocol only loosely based on that
> spoken by other ALPS devices.
>

Do you have a formal programming spec for it?

> This is required by the noticeable different between this device and
> others made by alps, specificly that it is very wide, with the center
> 1/3rd usable with the GS sensor, and the entire area usable with the PT
> sensor, with support for using both at once.
>

Coudl youp please tell me what GS and PT stand for?

> The protocol differs enough that I split the driver for this off from
> the ALPS driver.
>
> The patch is below, but there are a few things of note.
>
> 1: Cosmetic: Some line lengths, and outputs with debugging enabled, are
> over 80 columns wide. Will be fixed in the next version of this patch.
>

If going to 80 colums will require monstrocities like this:

... do_bla(struct_b->
array_g[
index_i].
submember);

(and I seen quite a few such attempts to "improve" code) then please don't ;)

> 2: Cosmetic: Input device IDs need to be decided on, some feedback on
> the best values to use here would be appreciated.

I think what you've done is fine.

>
> 3: Patch stuff: Because the protocol uses 9 byte packets I had to
> increase the size of the buffer in struct psmouse. Should this be split
> off into a separate patch?
>

No.

> 4: Technical/policy: Buttons are currently sent to both of the input
> devices we generate, I don't see any way to avoid this that is not a
> policy decision on which buttons belong to which device, but I'm open to
> suggestions.
>

Is it not known how actual hardware wired?

> 5: Technical: Min/max on absolute values are currently reported as the
> protocol limits (10 bits on GS X, GS Y, and PT Y. 11 bits on PT X. 7
> bits on GS pressure). Until we get samples based on the newer design
> and do some testing to see how big the variations are, we just don't
> have any numbers to put here.
>

Using protocol limits is fine, I don't think anyone actually uses it.
Synaptics X driver allows users to tweak it to their preference.

> 6: Technical, maybe: The early samples I have that speak this protocol
> are doing some odd things with this driver. Mostly in the realm of
> sample rate and pressure reporting. I'm fairly sure that this is
> hardware related, but it's worth mentioning.
>
>
> That said, here the patch is for comments.
> (And possibly for the OLPC kernel tree for others with samples to play
> with.)
>
>
> Signed-off-by: Zephaniah E. Hull <warp@aehallh.com>
>
> diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
> index 21a1de6..6218e5a 100644
> --- a/drivers/input/mouse/Makefile
> +++ b/drivers/input/mouse/Makefile
> @@ -14,4 +14,4 @@ obj-$(CONFIG_MOUSE_SERIAL) += sermouse.o
> obj-$(CONFIG_MOUSE_HIL) += hil_ptr.o
> obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o
>
> -psmouse-objs := psmouse-base.o alps.o logips2pp.o synaptics.o lifebook.o trackpoint.o
> +psmouse-objs := psmouse-base.o alps.o logips2pp.o synaptics.o lifebook.o trackpoint.o olpc.o
> diff --git a/drivers/input/mouse/olpc.c b/drivers/input/mouse/olpc.c
> new file mode 100644
> index 0000000..245f29e
> --- /dev/null
> +++ b/drivers/input/mouse/olpc.c
> @@ -0,0 +1,327 @@
> +/*
> + * OLPC touchpad PS/2 mouse driver
> + *
> + * Copyright (c) 2006 One Laptop Per Child, inc.
> + * Author Zephaniah E. Hull.
> + *
> + * This driver is partly based on the ALPS driver, which is:
> + *
> + * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au>
> + * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com>
> + * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru>
> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
> + *
> + * 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.
> + */
> +
> +/*
> + * The touchpad on the OLPC is fairly wide, with the entire area usable
> + * as a tablet, and the center 1/3rd also usable as a touchpad.
> + *
> + * The device has simultanious reporting, so that both can be used at once.
> + *
> + * The PT+GS protocol is similar to the base ALPS protocol, in that the
> + * GS data is where the ALPS parser would expect to find it, however
> + * there are several additional bytes, the button bits are in a
> + * different byte, and the bits used for finger and gesture indication
> + * are replaced by two bits which indicate if it is reporting PT or GS
> + * coordinate data in that packet.
> + */
> +
> +#include <linux/input.h>
> +#include <linux/serio.h>
> +#include <linux/libps2.h>
> +
> +#include "psmouse.h"
> +#include "olpc.h"
> +
> +#undef DEBUG
> +#ifdef DEBUG
> +#define dbg(format, arg...) printk(KERN_INFO "olpc.c(%d): " format "\n", __LINE__, ## arg)
> +#else
> +#define dbg(format, arg...) do {} while (0)
> +#endif
> +
> +#define OLPC_PT 0x01
> +#define OLPC_GS 0x02
> +#define OLPC_PTGS 0x04
> +

Do you need a separate #define? I'd expect it to be OLPC_PT | OLPC_GS?

> +static struct olpc_model_info olpc_model_data[] = {
> + { { 0x67, 0x00, 0x0a }, 0xeb, 0xff, OLPC_PTGS }, /* OLPC in PT+GS mode. */
> +};
> +
> +/*
> + * OLPC absolute 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 0 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 olpc_process_packet(struct psmouse *psmouse, struct pt_regs *regs)
> +{
> + struct olpc_data *priv = psmouse->private;
> + unsigned char *packet = psmouse->packet;
> + struct input_dev *dev = psmouse->dev;
> + struct input_dev *dev2 = priv->dev2;
> + int px, py, gx, gy, gz, gs_down, pt_down, left, right;
> +
> + input_regs(dev, regs);
> +
> + left = packet[6] & 1;
> + right = packet[6] & 2;
> + gx = packet[1] | ((packet[2] & 0x78) << (7 - 3));
> + gy = packet[4] | ((packet[3] & 0x70) << (7 - 4));
> + gz = packet[5];
> + px = packet[8] | ((packet[2] & 0x7) << 7);
> + py = packet[7] | ((packet[6] & 0x70) << (7 - 4));
> +
> + pt_down = packet[3] & 1;
> + gs_down = packet[3] & 2;
> +
> + input_report_key(dev, BTN_LEFT, left);
> + input_report_key(dev2, BTN_LEFT, left);
> + input_report_key(dev, BTN_RIGHT, right);
> + input_report_key(dev2, BTN_RIGHT, right);
> +
> + input_report_key(dev, BTN_TOUCH, pt_down);
> + input_report_key(dev, BTN_TOOL_PEN, pt_down);
> + input_report_key(dev2, BTN_TOUCH, gs_down);
> + input_report_key(dev2, BTN_TOOL_FINGER, gs_down);
> +
> + if (gs_down) {
> + input_report_abs(dev2, ABS_X, gx);
> + input_report_abs(dev2, ABS_Y, gy);
> + }
> + input_report_abs(dev2, ABS_PRESSURE, gz);
> +
> + if (pt_down) {
> + input_report_abs(dev, ABS_X, px);
> + input_report_abs(dev, ABS_Y, py);
> + }
> +
> + input_sync(dev);
> +}
> +
> +static psmouse_ret_t olpc_process_byte(struct psmouse *psmouse, struct pt_regs *regs)
> +{
> + struct olpc_data *priv = psmouse->private;
> + psmouse_ret_t ret = PSMOUSE_BAD_DATA;
> +
> + if ((psmouse->packet[0] & priv->i->mask0) != priv->i->byte0) {
> + ret = PSMOUSE_BAD_DATA;
> + goto out;
> + }
> +
> + /* Bytes 2 - 6 should have 0 in the highest bit */
> + if (psmouse->pktcnt >= 2 && psmouse->pktcnt <= 9 &&
> + (psmouse->packet[psmouse->pktcnt - 1] & 0x80)) {
> + ret = PSMOUSE_BAD_DATA;
> + goto out;
> + }
> +
> + if ((psmouse->pktcnt == 4 || psmouse->pktcnt == 7) &&
> + ((psmouse->packet[psmouse->pktcnt - 1] & 0x88) != 8)) {
> + ret = PSMOUSE_BAD_DATA;
> + goto out;
> + }
> +
> + if (psmouse->pktcnt == 9) {
> + olpc_process_packet(psmouse, regs);
> +
> + ret = PSMOUSE_FULL_PACKET;
> + goto out;
> + }
> +
> + ret = PSMOUSE_GOOD_DATA;
> +out:
> + if (ret != PSMOUSE_GOOD_DATA)
> + dbg("ret: %d, len: %u, data: %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x", ret, psmouse->pktcnt,
> + psmouse->packet[0], psmouse->packet[1], psmouse->packet[2],
> + psmouse->packet[3], psmouse->packet[4], psmouse->packet[5],
> + psmouse->packet[6], psmouse->packet[7], psmouse->packet[8]);
> + return ret;
> +}
> +
> +static struct olpc_model_info *olpc_get_model(struct psmouse *psmouse)
> +{
> + struct ps2dev *ps2dev = &psmouse->ps2dev;
> + unsigned char param[4];
> + int i;
> +
> + /*
> + * Now try "E7 report". Allowed responses are in
> + * olpc_model_data[].signature
> + */
> + if (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(olpc_model_data); i++)
> + if (!memcmp(param, olpc_model_data[i].signature, sizeof(olpc_model_data[i].signature)))
> + return olpc_model_data + i;
> +
> + return NULL;
> +}
> +
> +static int olpc_absolute_mode(struct psmouse *psmouse)
> +{
> + struct ps2dev *ps2dev = &psmouse->ps2dev;
> + unsigned char param;
> +
> + /* Switch to 'Advanced mode.', four disables in a row. */
> + 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))
> + return -1;
> +
> + /*
> + * Switch to simultanious mode, F2 (GETID) three times with no
> + * arguments or reply, followed by SETRES with an argument of 2.
> + */
> + ps2_command(ps2dev, NULL, 0xF2);
> + ps2_command(ps2dev, NULL, 0xF2);
> + ps2_command(ps2dev, NULL, 0xF2);
> + param = 0x02;
> + ps2_command(ps2dev, &param, PSMOUSE_CMD_SETRES);
> +
> + return 0;
> +}
> +
> +/*
> + * olpc_poll() - poll the touchpad for current motion packet.
> + * Used in resync.
> + */
> +static int olpc_poll(struct psmouse *psmouse)
> +{
> + /*
> + * FIXME: We can't poll, find a way to make resync work better.
> + */
> + return 0;
> +}

I'd expect it to return -1. It's OK that it can't poll, it looks like
it should resync fairly well on its own.

> +
> +static int olpc_reconnect(struct psmouse *psmouse)
> +{
> + struct olpc_data *priv = psmouse->private;
> +
> + psmouse_reset(psmouse);
> +
> + if (!(priv->i = olpc_get_model(psmouse)))
> + return -1;
> +
> + if (olpc_absolute_mode(psmouse)) {
> + printk(KERN_ERR "olpc.c: Failed to reenable absolute mode\n");
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static void olpc_disconnect(struct psmouse *psmouse)
> +{
> + struct olpc_data *priv = psmouse->private;
> +
> + psmouse_reset(psmouse);
> + input_unregister_device(priv->dev2);
> + kfree(priv);
> +}
> +
> +int olpc_init(struct psmouse *psmouse)
> +{
> + struct olpc_data *priv;
> + struct input_dev *dev = psmouse->dev;
> + struct input_dev *dev2;
> +
> + psmouse->private = priv = kzalloc(sizeof(struct olpc_data), GFP_KERNEL);
> + dev2 = input_allocate_device();
> + if (!priv || !dev2)
> + goto init_fail;
> +
> + priv->dev2 = dev2;
> +
> + if (!(priv->i = olpc_get_model(psmouse)))
> + goto init_fail;
> +
> + if (olpc_absolute_mode(psmouse)) {
> + printk(KERN_ERR "olpc.c: Failed to enable absolute mode\n");
> + goto init_fail;
> + }
> +
> + dev->evbit[LONG(EV_KEY)] |= BIT(EV_KEY);
> + dev->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH);
> + dev->keybit[LONG(BTN_TOOL_PEN)] |= BIT(BTN_TOOL_PEN);
> + dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT);
> +
> + dev->evbit[LONG(EV_ABS)] |= BIT(EV_ABS);
> + input_set_abs_params(dev, ABS_X, 0, 1023, 0, 0);
> + input_set_abs_params(dev, ABS_Y, 0, 1023, 0, 0);
> +
> + snprintf(priv->phys, sizeof(priv->phys), "%s/input1", psmouse->ps2dev.serio->phys);
> + dev2->phys = priv->phys;
> + dev2->name = "OLPC OLPC GlideSensor";

"OLPC OLPC"?

> + dev2->id.bustype = BUS_I8042;
> + dev2->id.vendor = 0x0002;
> + dev2->id.product = PSMOUSE_OLPC;
> + dev2->id.version = 0x0000;
> +
> + dev2->evbit[LONG(EV_KEY)] |= BIT(EV_KEY);
> + dev2->evbit[LONG(EV_ABS)] |= BIT(EV_ABS);
> + input_set_abs_params(dev2, ABS_X, 0, 2047, 0, 0);
> + input_set_abs_params(dev2, ABS_Y, 0, 1023, 0, 0);
> + input_set_abs_params(dev2, ABS_PRESSURE, 0, 63, 0, 0);
> + dev2->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH);
> + dev2->keybit[LONG(BTN_TOOL_FINGER)] |= BIT(BTN_TOOL_FINGER);
> + dev2->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT);
> +
> + input_register_device(priv->dev2);
> +
> +
> + psmouse->protocol_handler = olpc_process_byte;
> + psmouse->poll = olpc_poll;
> + psmouse->disconnect = olpc_disconnect;
> + psmouse->reconnect = olpc_reconnect;
> + psmouse->pktsize = 9;
> +
> + /* We are having trouble resyncing OLPC touchpads so disable it for now */
> + psmouse->resync_time = 0;
> +
> + return 0;
> +
> +init_fail:
> + input_free_device(dev2);
> + kfree(priv);
> + return -1;
> +}
> +
> +int olpc_detect(struct psmouse *psmouse, int set_properties)
> +{
> + struct olpc_model_info *model;
> +
> + if (!(model = olpc_get_model(psmouse)))
> + return -1;
> +
> + if (set_properties) {
> + psmouse->vendor = "OLPC";
> + psmouse->name = "PenTablet";
> + psmouse->model = 0;
> + }
> + return 0;
> +}
> +
> diff --git a/drivers/input/mouse/olpc.h b/drivers/input/mouse/olpc.h
> new file mode 100644
> index 0000000..49f4e3e
> --- /dev/null
> +++ b/drivers/input/mouse/olpc.h
> @@ -0,0 +1,37 @@
> +/*
> + * OLPC touchpad PS/2 mouse driver
> + *
> + * Copyright (c) 2006 One Laptop Per Child, inc.
> + *
> + * This driver is partly based on the ALPS driver.
> + * Copyright (c) 2003 Peter Osterlund <petero2@telia.com>
> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
> + *
> + * 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.
> + */
> +
> +#ifndef _OLPC_H
> +#define _OLPC_H
> +
> +int olpc_detect(struct psmouse *psmouse, int set_properties);
> +int olpc_init(struct psmouse *psmouse);
> +
> +struct olpc_model_info {
> + unsigned char signature[3];
> + unsigned char byte0, mask0;
> + unsigned char flags;
> +};
> +
> +struct olpc_data {
> + struct input_dev *dev2; /* Relative device */
> + char name[32]; /* Name */
> + char phys[32]; /* Phys */
> + struct olpc_model_info *i; /* Info */
> + int prev_fin_pt; /* Finger bit from previous packet */
> + int prev_fin_gs; /* Finger bit from previous packet */

Bad (duplicate) comment?

> +};
> +
> +
> +#endif
> diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
> index 8bc9f51..20060b0 100644
> --- a/drivers/input/mouse/psmouse-base.c
> +++ b/drivers/input/mouse/psmouse-base.c
> @@ -26,6 +26,7 @@
> #include "synaptics.h"
> #include "logips2pp.h"
> #include "alps.h"
> +#include "olpc.h"
> #include "lifebook.h"
> #include "trackpoint.h"
>
> @@ -616,6 +617,15 @@ static int psmouse_extensions(struct psm
> */
> max_proto = PSMOUSE_IMEX;
> }
> + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS);
> + if (olpc_detect(psmouse, set_properties) == 0) {
> + if (!set_properties || olpc_init(psmouse) == 0)
> + return PSMOUSE_OLPC;
> +/*
> + * Init failed, try basic relative protocols
> + */
> + max_proto = PSMOUSE_IMEX;
> + }
> }
>
> if (max_proto > PSMOUSE_IMEX && genius_detect(psmouse, set_properties) == 0)
> @@ -726,6 +736,13 @@ static struct psmouse_protocol psmouse_p
> .detect = trackpoint_detect,
> },
> {
> + .type = PSMOUSE_OLPC,
> + .name = "OLPC",
> + .alias = "olpc",
> + .maxproto = 1,
> + .detect = olpc_detect,
> + },
> + {
> .type = PSMOUSE_AUTO,
> .name = "auto",
> .alias = "any",
> diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
> index 4d9107f..f3d7199 100644
> --- a/drivers/input/mouse/psmouse.h
> +++ b/drivers/input/mouse/psmouse.h
> @@ -42,7 +42,7 @@ struct psmouse {
> struct work_struct resync_work;
> char *vendor;
> char *name;
> - unsigned char packet[8];
> + unsigned char packet[9];
> unsigned char badbyte;
> unsigned char pktcnt;
> unsigned char pktsize;
> @@ -86,6 +86,7 @@ enum psmouse_type {
> PSMOUSE_ALPS,
> PSMOUSE_LIFEBOOK,
> PSMOUSE_TRACKPOINT,
> + PSMOUSE_OLPC,
> PSMOUSE_AUTO /* This one should always be last */
> };
>
> --
> 1024D/E65A7801 Zephaniah E. Hull <warp@aehallh.com>
> 92ED 94E4 B1E6 3624 226D 5727 4453 008B E65A 7801
> CCs of replies from mailing lists are requested.
>
>
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.5 (GNU/Linux)
>
> iD8DBQFE8+3TRFMAi+ZaeAERAhiNAJ4t4uF3q9G+bdsGUAYDafNearwMUgCeN0kl
> se5meohSaoJEMhbRsrxtIOo=
> =Vd5o
> -----END PGP SIGNATURE-----
>
>
>

--
Dmitry