Hi,

On 11/11/2014 06:15 PM, Dmitry Torokhov wrote:
> On Sat, Nov 01, 2014 at 10:37:18AM +0100, Mathias Gottschlag wrote:
>> Most of the protocol for these touchpads has been reverse engineered. This
>> commit adds a basic multitouch-capable driver.
>>
>> A lot of the protocol is still unknown. Especially, we don't know how to
>> identify the device yet apart from the PNP ID.
>>
>> The previous workaround for these devices has been left in place in case
>> the driver is not compiled into the kernel or in case some other device
>> with the same PNP ID is not recognized by the driver yet still has the same
>> problems with the device probing code.
>>
>> Signed-off-by: Mathias Gottschlag <[email protected]>
>> Reviewed-by: Hans de Goede <[email protected]>
>> ---
>>
>> Here is v4 of the driver, hopefully without any wrapped lines this time. I've
>> received further feedback, the size detection code should be correct on Asus
>> X200 and N550 as well.
>>
>>  drivers/input/mouse/Kconfig        |  10 ++
>>  drivers/input/mouse/focaltech.c    | 300 
>> ++++++++++++++++++++++++++++++++++++-
>>  drivers/input/mouse/focaltech.h    |  60 ++++++++
>>  drivers/input/mouse/psmouse-base.c |  32 ++--
>>  drivers/input/mouse/psmouse.h      |   1 +
>>  5 files changed, 386 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
>> index 366fc7a..db973e5 100644
>> --- a/drivers/input/mouse/Kconfig
>> +++ b/drivers/input/mouse/Kconfig
>> @@ -146,6 +146,16 @@ config MOUSE_PS2_OLPC
>>  
>>        If unsure, say N.
>>  
>> +config MOUSE_PS2_FOCALTECH
>> +    bool "FocalTech PS/2 mouse protocol extension" if EXPERT
>> +    default y
>> +    depends on MOUSE_PS2
>> +    help
>> +      Say Y here if you have a FocalTech PS/2 TouchPad connected to
>> +      your system.
>> +
>> +      If unsure, say Y.
>> +
>>  config MOUSE_SERIAL
>>      tristate "Serial mouse"
>>      select SERIO
>> diff --git a/drivers/input/mouse/focaltech.c 
>> b/drivers/input/mouse/focaltech.c
>> index f4d657e..26bc5b7 100644
>> --- a/drivers/input/mouse/focaltech.c
>> +++ b/drivers/input/mouse/focaltech.c
>> @@ -2,6 +2,7 @@
>>   * Focaltech TouchPad PS/2 mouse driver
>>   *
>>   * Copyright (c) 2014 Red Hat Inc.
>> + * Copyright (c) 2014 Mathias Gottschlag <[email protected]>
>>   *
>>   * This program is free software; you can redistribute it and/or modify
>>   * it under the terms of the GNU General Public License as published by
>> @@ -13,15 +14,14 @@
>>   * Hans de Goede <[email protected]>
>>   */
>>  
>> -/*
>> - * The Focaltech PS/2 touchpad protocol is unknown. This drivers deals with
>> - * detection only, to avoid further detection attempts confusing the 
>> touchpad
>> - * this way it at least works in PS/2 mouse compatibility mode.
>> - */
>>  
>>  #include <linux/device.h>
>>  #include <linux/libps2.h>
>> +#include <linux/input/mt.h>
>> +#include <linux/serio.h>
>> +#include <linux/slab.h>
>>  #include "psmouse.h"
>> +#include "focaltech.h"
>>  
>>  static const char * const focaltech_pnp_ids[] = {
>>      "FLT0101",
>> @@ -30,6 +30,12 @@ static const char * const focaltech_pnp_ids[] = {
>>      NULL
>>  };
>>  
>> +/*
>> + * Even if the kernel is built without support for Focaltech PS/2 touchpads 
>> (or
>> + * when the real driver fails to recognize the device), we still have to 
>> detect
>> + * them in order to avoid further detection attempts confusing the touchpad.
>> + * This way it at least works in PS/2 mouse compatibility mode.
>> + */
>>  int focaltech_detect(struct psmouse *psmouse, bool set_properties)
>>  {
>>      if (!psmouse_matches_pnp_id(psmouse, focaltech_pnp_ids))
>> @@ -37,16 +43,296 @@ int focaltech_detect(struct psmouse *psmouse, bool 
>> set_properties)
>>  
>>      if (set_properties) {
>>              psmouse->vendor = "FocalTech";
>> -            psmouse->name = "FocalTech Touchpad in mouse emulation mode";
>> +            psmouse->name = "FocalTech Touchpad";
>>      }
>>  
>>      return 0;
>>  }
>>  
>> -int focaltech_init(struct psmouse *psmouse)
>> +static void focaltech_reset(struct psmouse *psmouse)
>>  {
>>      ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS);
>>      psmouse_reset(psmouse);
>> +}
>> +
>> +#ifdef CONFIG_MOUSE_PS2_FOCALTECH
>> +
>> +static void focaltech_report_state(struct psmouse *psmouse)
>> +{
>> +    int i;
>> +    struct focaltech_data *priv = psmouse->private;
>> +    struct focaltech_hw_state *state = &priv->state;
>> +    struct input_dev *dev = psmouse->dev;
>> +    int finger_count = 0;
>> +
>> +    for (i = 0; i < FOC_MAX_FINGERS; i++) {
>> +            struct focaltech_finger_state *finger = &state->fingers[i];
>> +            int active = finger->active && finger->valid;
> 
>               bool active;
> 
>> +            input_mt_slot(dev, i);
>> +            input_mt_report_slot_state(dev, MT_TOOL_FINGER, active);
>> +            if (active) {
>> +                    finger_count++;
> 
> Why do we need to count contacts ourselves?

We don't.

> 
>> +                    input_report_abs(dev, ABS_MT_POSITION_X, finger->x);
>> +                    input_report_abs(dev, ABS_MT_POSITION_Y,
>> +                                    focaltech_invert_y(finger->y));
>> +            }
>> +    }
>> +    input_mt_report_pointer_emulation(dev, finger_count);

And this should be:

        input_mt_report_pointer_emulation(dev, true);

As already said in my previous mail, sorry about that.  

>> +
>> +    input_report_key(psmouse->dev, BTN_LEFT, state->pressed);
>> +    input_sync(psmouse->dev);
>> +}
>> +
>> +static void process_touch_packet(struct focaltech_hw_state *state,
>> +            unsigned char *packet)
>> +{
>> +    int i;
>> +    unsigned char fingers = packet[1];
>> +
>> +    state->pressed = (packet[0] >> 4) & 1;
>> +    /* the second byte contains a bitmap of all fingers touching the pad */
>> +    for (i = 0; i < FOC_MAX_FINGERS; i++) {
>> +            if ((fingers & 0x1) && !state->fingers[i].active) {
> 
> 
> I think the test (fingers & 1) is not needed here. If the contact was
> not active we do not have valid coordinates for it, so just checking if
> !state->fingers[i].active should we enough, right?

This is checking for fingers[i].active making a transation from false
to true, state->fingers[i].active is still the old value here (it is
re-assigned below) and (fingers & 1) is the new active.

You're right that it does matter to set valid to 0 when going from
inactive to inactive though, so that only testing if fingers[i].active was
false is enough. Either way works for me.


> 
>> +                    /* we do not have a valid position for the finger yet */
>> +                    state->fingers[i].valid = 0;
>> +            }
>> +            state->fingers[i].active = fingers & 0x1;
>> +            fingers >>= 1;
>> +    }
>> +}
>> +
>> +static void process_abs_packet(struct focaltech_hw_state *state,
>> +            unsigned char *packet)
>> +{
>> +    unsigned int finger = (packet[1] >> 4) - 1;
>> +
>> +    state->pressed = (packet[0] >> 4) & 1;
>> +    if (finger >= FOC_MAX_FINGERS)
>> +            return;
> 
> Please combine checks with respective assignments:
> 
>       finger = (packet[1] >> 4) - 1;
>       if (finger >= FOC_MAX_FINGERS) {
>               psmouse_err(...);
>               return;
>       }
> 
>       state->pressed = (packet[0] >> 4) & 1;
>       ...
> 
>> +    /*
>> +     * packet[5] contains some kind of tool size in the most significant
>> +     * nibble. 0xff is a special value (latching) that signals a large
>> +     * contact area.
>> +     */
>> +    if (packet[5] == 0xff) {
>> +            state->fingers[finger].valid = 0;
> 
> I wonder if we should report it via ABS_TOOL_WIDTH/ABS_MT_TOUCH_MAJOR.
> Can be done in a separate patch though.
> 
>> +            return;
>> +    }
>> +    state->fingers[finger].x = ((packet[1] & 0xf) << 8) | packet[2];
>> +    state->fingers[finger].y = (packet[3] << 8) | packet[4];
>> +    state->fingers[finger].valid = 1;
>> +}
>> +static void process_rel_packet(struct focaltech_hw_state *state,
>> +            unsigned char *packet)
>> +{
>> +    int finger1 = ((packet[0] >> 4) & 0x7) - 1;
>> +    int finger2 = ((packet[3] >> 4) & 0x7) - 1;
> 
> I think you want unsigned int here, otherwise your checks need to be
> 
>       if (f >= 0 && f < FOC_MAX_FINGERS)
>               ...
> 
>> +
>> +    state->pressed = packet[0] >> 7;
>> +    if (finger1 < FOC_MAX_FINGERS) {
>> +            state->fingers[finger1].x += (char)packet[1];
>> +            state->fingers[finger1].y += (char)packet[2];
>> +    }
>> +    /*
>> +     * If there is an odd number of fingers, the last relative packet only
>> +     * contains one finger. In this case, the second finger index in the
>> +     * packet is 0 (we subtract 1 in the lines above to create array
>> +     * indices).
>> +     */
>> +    if (finger2 != -1 && finger2 < FOC_MAX_FINGERS) {
>> +            state->fingers[finger2].x += (char)packet[4];
>> +            state->fingers[finger2].y += (char)packet[5];
>> +    }
>> +}
>> +
>> +static void focaltech_process_packet(struct psmouse *psmouse)
>> +{
>> +    struct focaltech_data *priv = psmouse->private;
>> +    unsigned char *packet = psmouse->packet;
>> +
>> +    switch (packet[0] & 0xf) {
>> +    case FOC_TOUCH:
>> +            process_touch_packet(&priv->state, packet);
>> +            break;
>> +    case FOC_ABS:
>> +            process_abs_packet(&priv->state, packet);
>> +            break;
>> +    case FOC_REL:
>> +            process_rel_packet(&priv->state, packet);
>> +            break;
>> +    default:
>> +            psmouse_err(psmouse, "Unknown packet type: %02x", packet[0]);
>> +            break;
>> +    }
>> +
>> +    focaltech_report_state(psmouse);
>> +}
>> +
>> +static psmouse_ret_t focaltech_process_byte(struct psmouse *psmouse)
>> +{
>> +    if (psmouse->pktcnt >= 6) { /* Full packet received */
>> +            focaltech_process_packet(psmouse);
>> +            return PSMOUSE_FULL_PACKET;
>> +    }
>> +    /*
>> +     * we might want to do some validation of the data here, but we do not
>> +     * know the protocoll well enough
>> +     */
>> +    return PSMOUSE_GOOD_DATA;
>> +}
>> +
>> +static int focaltech_switch_protocol(struct psmouse *psmouse)
>> +{
>> +    struct ps2dev *ps2dev = &psmouse->ps2dev;
>> +    unsigned char param[3];
>> +
>> +    param[0] = 0;
>> +    if (ps2_command(ps2dev, param, 0x10f8))
>> +            return -EIO;
>> +    if (ps2_command(ps2dev, param, 0x10f8))
>> +            return -EIO;
>> +    if (ps2_command(ps2dev, param, 0x10f8))
>> +            return -EIO;
>> +    param[0] = 1;
>> +    if (ps2_command(ps2dev, param, 0x10f8))
>> +            return -EIO;
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETSCALE11))
>> +            return -EIO;
>> +
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_ENABLE))
>> +            return -EIO;
>>  
>>      return 0;
>>  }
>> +
>> +static void focaltech_disconnect(struct psmouse *psmouse)
>> +{
>> +    focaltech_reset(psmouse);
>> +    kfree(psmouse->private);
>> +    psmouse->private = NULL;
>> +}
>> +
>> +static int focaltech_reconnect(struct psmouse *psmouse)
>> +{
>> +    focaltech_reset(psmouse);
>> +    if (focaltech_switch_protocol(psmouse)) {
>> +            psmouse_err(psmouse,
>> +                        "Unable to initialize the device.");
>> +            return -1;
>> +    }
>> +    return 0;
>> +}
>> +
>> +static void set_input_params(struct psmouse *psmouse)
>> +{
>> +    struct input_dev *dev = psmouse->dev;
>> +    struct focaltech_data *priv = psmouse->private;
>> +
>> +    __set_bit(EV_ABS, dev->evbit);
>> +    input_set_abs_params(dev, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0);
>> +    input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0);
>> +    input_mt_init_slots(dev, 5, INPUT_MT_POINTER);
>> +    __clear_bit(EV_REL, dev->evbit);
>> +    __clear_bit(REL_X, dev->relbit);
>> +    __clear_bit(REL_Y, dev->relbit);
>> +    __clear_bit(BTN_RIGHT, dev->keybit);
>> +    __clear_bit(BTN_MIDDLE, dev->keybit);
>> +    __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit);
>> +}
>> +
>> +static int focaltech_read_register(struct ps2dev *ps2dev, int reg,
>> +            unsigned char *param)
>> +{
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETSCALE11))
>> +            return -1;
> 
> I 'd rather see -EIO here as well.
> 
>> +    param[0] = 0;
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
>> +            return -1;
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
>> +            return -1;
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
>> +            return -1;
>> +    param[0] = reg;
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_SETRES))
>> +            return -1;
>> +    if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
>> +            return -1;
>> +    return 0;
>> +}
>> +
>> +static int focaltech_read_size(struct psmouse *psmouse)
>> +{
>> +    struct ps2dev *ps2dev = &psmouse->ps2dev;
>> +    struct focaltech_data *priv = psmouse->private;
>> +    char param[3];
>> +
>> +    if (focaltech_read_register(ps2dev, 2, param))
>> +            return -EIO;
>> +    /* not sure whether this is 100% correct */
>> +    priv->x_max = (unsigned char)param[1] * 128;
>> +    priv->y_max = (unsigned char)param[2] * 128;
>> +
>> +    return 0;
>> +}
>> +int focaltech_init(struct psmouse *psmouse)
>> +{
>> +    struct focaltech_data *priv;
>> +    int err;
>> +
>> +    psmouse->private = priv = kzalloc(sizeof(struct focaltech_data), 
>> GFP_KERNEL);
>> +    if (!priv)
>> +            return -ENOMEM;
>> +
>> +    focaltech_reset(psmouse);
>> +    if (focaltech_read_size(psmouse)) {
>> +            focaltech_reset(psmouse);
>> +            psmouse_err(psmouse,
>> +                        "Unable to read the size of the touchpad.");
>> +            err = -ENOSYS;
> 
> Why -ENOSYS?
> 
>       error = focaltech_read_size(psmouse);
>       if (error) {
>               ...
>               return error;
>       }
> 
> The fact that the rest of psmouse likes returning -1 does not mean that
> the new code should be similarly ugly.
> 
>> +            goto fail;
>> +    }
>> +    if (focaltech_switch_protocol(psmouse)) {
>> +            focaltech_reset(psmouse);
>> +            psmouse_err(psmouse,
>> +                        "Unable to initialize the device.");
>> +            err = -ENOSYS;
>> +            goto fail;
>> +    }
>> +
>> +    set_input_params(psmouse);
>> +
>> +    psmouse->protocol_handler = focaltech_process_byte;
>> +    psmouse->pktsize = 6;
>> +    psmouse->disconnect = focaltech_disconnect;
>> +    psmouse->reconnect = focaltech_reconnect;
>> +    psmouse->cleanup = focaltech_reset;
>> +    /* resync is not supported yet */
>> +    psmouse->resync_time = 0;
>> +
>> +    return 0;
>> +fail:
>> +    focaltech_reset(psmouse);
>> +    kfree(priv);
>> +    return err;
>> +}
>> +
>> +bool focaltech_supported(void)
>> +{
>> +    return true;
>> +}
>> +
>> +#else /* CONFIG_MOUSE_PS2_FOCALTECH */
>> +
>> +int focaltech_init(struct psmouse *psmouse)
>> +{
>> +    focaltech_reset(psmouse);
>> +
>> +    return 0;
>> +}
>> +
>> +bool focaltech_supported(void)
>> +{
>> +    return false;
>> +}
>> +
>> +#endif /* CONFIG_MOUSE_PS2_FOCALTECH */
>> diff --git a/drivers/input/mouse/focaltech.h 
>> b/drivers/input/mouse/focaltech.h
>> index 498650c..68c5cfc 100644
>> --- a/drivers/input/mouse/focaltech.h
>> +++ b/drivers/input/mouse/focaltech.h
>> @@ -2,6 +2,7 @@
>>   * Focaltech TouchPad PS/2 mouse driver
>>   *
>>   * Copyright (c) 2014 Red Hat Inc.
>> + * Copyright (c) 2014 Mathias Gottschlag <[email protected]>
>>   *
>>   * This program is free software; you can redistribute it and/or modify
>>   * it under the terms of the GNU General Public License as published by
>> @@ -16,7 +17,66 @@
>>  #ifndef _FOCALTECH_H
>>  #define _FOCALTECH_H
>>  
>> +/*
>> + * Packet types - the numbers are not consecutive, so we might be missing
>> + * something here.
>> + */
>> +#define FOC_TOUCH 0x3 /* bitmap of active fingers */
>> +#define FOC_ABS 0x6 /* absolute position of one finger */
>> +#define FOC_REL 0x9 /* relative position of 1-2 fingers */
>> +
>> +#define FOC_MAX_FINGERS 5
>> +
>> +#define FOC_MAX_X 2431
>> +#define FOC_MAX_Y 1663
>> +
>> +static inline int focaltech_invert_y(int y)
>> +{
>> +    return FOC_MAX_Y - y;
>> +}
>> +
>> +/*
>> + * Current state of a single finger on the touchpad.
>> + */
>> +struct focaltech_finger_state {
>> +    /* the touchpad has generated a touch event for the finger */
>> +    bool active;
>> +    /*
>> +     * The touchpad has sent position data for the finger. Touch event
>> +     * packages reset this flag for new fingers, and there is a time
>> +     * between the first touch event and the following absolute position
>> +     * packet for the finger where the touchpad has declared the finger to
>> +     * be valid, but we do not have any valid position yet.
>> +     */
>> +    bool valid;
>> +    /* absolute position (from the bottom left corner) of the finger */
>> +    unsigned int x;
>> +    unsigned int y;
>> +};
>> +
>> +/*
>> + * Description of the current state of the touchpad hardware.
>> + */
>> +struct focaltech_hw_state {
>> +    /*
>> +     * The touchpad tracks the positions of the fingers for us, the array
>> +     * indices correspond to the finger indices returned in the report
>> +     * packages.
>> +     */
>> +    struct focaltech_finger_state fingers[FOC_MAX_FINGERS];
>> +    /*
>> +     * True if the clickpad has been pressed.
>> +     */
>> +    bool pressed;
>> +};
>> +
>> +struct focaltech_data {
>> +    unsigned int x_max, y_max;
>> +    struct focaltech_hw_state state;
>> +};
>> +
>>  int focaltech_detect(struct psmouse *psmouse, bool set_properties);
>>  int focaltech_init(struct psmouse *psmouse);
>> +bool focaltech_supported(void);
>>  
>>  #endif
>> diff --git a/drivers/input/mouse/psmouse-base.c 
>> b/drivers/input/mouse/psmouse-base.c
>> index 26994f6..4a9de33 100644
>> --- a/drivers/input/mouse/psmouse-base.c
>> +++ b/drivers/input/mouse/psmouse-base.c
>> @@ -725,16 +725,19 @@ static int psmouse_extensions(struct psmouse *psmouse,
>>  
>>  /* Always check for focaltech, this is safe as it uses pnp-id matching */
>>      if (psmouse_do_detect(focaltech_detect, psmouse, set_properties) == 0) {
>> -            if (!set_properties || focaltech_init(psmouse) == 0) {
>> -                    /*
>> -                     * Not supported yet, use bare protocol.
>> -                     * Note that we need to also restrict
>> -                     * psmouse_max_proto so that psmouse_initialize()
>> -                     * does not try to reset rate and resolution,
>> -                     * because even that upsets the device.
>> -                     */
>> -                    psmouse_max_proto = PSMOUSE_PS2;
>> -                    return PSMOUSE_PS2;
>> +            if (max_proto > PSMOUSE_IMEX) {
>> +                    if (!set_properties || focaltech_init(psmouse) == 0) {
>> +                            if (focaltech_supported())
>> +                                    return PSMOUSE_FOCALTECH;
>> +                            /*
>> +                             * Note that we need to also restrict
>> +                             * psmouse_max_proto so that 
>> psmouse_initialize()
>> +                             * does not try to reset rate and resolution,
>> +                             * because even that upsets the device.
>> +                             */
>> +                            psmouse_max_proto = PSMOUSE_PS2;
>> +                            return PSMOUSE_PS2;
>> +                    }
>>              }
>>      }
>>  
>> @@ -1063,6 +1066,15 @@ static const struct psmouse_protocol 
>> psmouse_protocols[] = {
>>              .alias          = "cortps",
>>              .detect         = cortron_detect,
>>      },
>> +#ifdef CONFIG_MOUSE_PS2_FOCALTECH
>> +    {
>> +            .type           = PSMOUSE_FOCALTECH,
>> +            .name           = "FocalTechPS/2",
>> +            .alias          = "focaltech",
>> +            .detect         = focaltech_detect,
>> +            .init           = focaltech_init,
>> +    },
>> +#endif
>>      {
>>              .type           = PSMOUSE_AUTO,
>>              .name           = "auto",
>> diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
>> index f4cf664..c2ff137 100644
>> --- a/drivers/input/mouse/psmouse.h
>> +++ b/drivers/input/mouse/psmouse.h
>> @@ -96,6 +96,7 @@ enum psmouse_type {
>>      PSMOUSE_FSP,
>>      PSMOUSE_SYNAPTICS_RELATIVE,
>>      PSMOUSE_CYPRESS,
>> +    PSMOUSE_FOCALTECH,
>>      PSMOUSE_AUTO            /* This one should always be last */
>>  };
>>  
>> -- 
>> 1.9.1
>>
> 
> Thanks.
> 


Regards,

Hans
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to