* Re: [PATCH] Input: Add REP_MAX_COUNT to autorepeat parameters
From: Dmitry Torokhov @ 2014-04-21 20:17 UTC (permalink / raw)
To: Petri Gynther; +Cc: linux-input
In-Reply-To: <20140421200008.CBC0F1007B4@puck.mtv.corp.google.com>
Hi Petri,
On Mon, Apr 21, 2014 at 01:00:08PM -0700, Petri Gynther wrote:
> Add REP_MAX_COUNT to autorepeat parameters. This enables an input device to be
> configured for maximum autorepeat count, so that a keypress is not repeated
> forever. This is important for Bluetooth keyboards and remote controls that may
> lose the Bluetooth link at any time, e.g. right after sending a key-down event
> but before sending the corresponding key-up event.
I think this should be solved in the drivers - if link is lost then they
should tear down the device (or generate keyup events if they want to
hand onto the device).
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: Add REP_MAX_COUNT to autorepeat parameters
From: Petri Gynther @ 2014-04-21 22:06 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: open list:HID CORE LAYER
In-Reply-To: <20140421201710.GA30454@core.coreip.homeip.net>
Hi Dmitry,
On Mon, Apr 21, 2014 at 1:17 PM, Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> Hi Petri,
>
> On Mon, Apr 21, 2014 at 01:00:08PM -0700, Petri Gynther wrote:
>> Add REP_MAX_COUNT to autorepeat parameters. This enables an input device to be
>> configured for maximum autorepeat count, so that a keypress is not repeated
>> forever. This is important for Bluetooth keyboards and remote controls that may
>> lose the Bluetooth link at any time, e.g. right after sending a key-down event
>> but before sending the corresponding key-up event.
>
> I think this should be solved in the drivers - if link is lost then they
> should tear down the device (or generate keyup events if they want to
> hand onto the device).
In my opinion, since the keypress autorepeat code lives in the input
subsystem, it should provide some kind of mechanism to limit the
autorepeat if the user wants to configure so. The delay and period are
already configurable, so this just adds the upper limit, so that the
autorepeat doesn't go on forever.
For Bluetooth LE remote controls (HID over GATT), BlueZ daemon uses
uHID kernel driver (drivers/hid/uhid.c) to create the necessary HID
device and pass the HID input events. This device is persistent over
reconnects, so the HID and input devices are not destroyed and
re-created on every disconnect and reconnect. On BLE device
disconnect, BlueZ daemon cannot inject the necessary key-up event
because it would have to know the HID input report details in order to
do so. And, uHID driver doesn't support disconnect-reconnect unless
the input pipeline is completely torn down and re-created (which is
not desirable).
-- Petri
>
> Thanks.
>
> --
> Dmitry
^ permalink raw reply
* Re: [PATCH] Input: evdev - add event-mask API
From: Peter Hutterer @ 2014-04-22 4:29 UTC (permalink / raw)
To: David Herrmann
Cc: linux-input, Dmitry Torokhov, Benjamin Tissoires,
Lennart Poettering
In-Reply-To: <1397601105-17771-1-git-send-email-dh.herrmann@gmail.com>
On Wed, Apr 16, 2014 at 12:31:45AM +0200, David Herrmann wrote:
> Hardware manufacturers group keys in the weirdest way possible. This may
> cause a power-key to be grouped together with normal keyboard keys and
> thus be reported on the same kernel interface.
>
> However, user-space is often only interested in specific sets of events.
> For instance, daemons dealing with system-reboot (like systemd-logind)
> listen for KEY_POWER, but are not interested in any main keyboard keys.
> Usually, power keys are reported via separate interfaces, however,
> some i8042 boards report it in the AT matrix. To avoid waking up those
> system daemons on each key-press, we had two ideas:
> - split off KEY_POWER into a separate interface unconditionally
> - allow masking a specific set of events on evdev FDs
>
> Splitting of KEY_POWER is a rather weird way to deal with this and may
> break backwards-compatibility. It is also specific to KEY_POWER and might
> be required for other stuff, too. Moreover, we might end up with a huge
> set of input-devices just to have them properly split.
>
> Hence, this patchset implements the second idea: An event-mask to specify
> which events you're interested in. Two ioctls allow setting this mask for
> each event-type. If not set, all events are reported. The type==0 entry is
> used same as in EVIOCGBIT to set the actual EV_* mask of masked events.
> This way, you have a two-level filter.
>
> We are heavily forward-compatible to new event-types and event-codes. So
> new user-space will be able to run on an old kernel which doesn't know the
> given event-codes or event event-types.
Looks good in principle, a couple of nitpicks below but on the whole
Acked-by: Peter Hutterer <peter.hutterer@who-t.net>
> Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
> ---
> drivers/input/evdev.c | 155 +++++++++++++++++++++++++++++++++++++++++++++
> include/uapi/linux/input.h | 8 +++
> 2 files changed, 163 insertions(+)
>
> diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
> index 398648b..86778c3 100644
> --- a/drivers/input/evdev.c
> +++ b/drivers/input/evdev.c
> @@ -51,10 +51,139 @@ struct evdev_client {
> struct list_head node;
> int clkid;
> bool revoked;
> + unsigned long *evmasks[EV_CNT];
> unsigned int bufsize;
> struct input_event buffer[];
> };
>
> +static size_t evdev_get_mask_cnt(unsigned int type)
> +{
> + switch (type) {
> + case 0:
> + /* 0 is special (EV-bits instead of EV_SYN) like EVIOCGBIT */
> + return EV_CNT;
> + case EV_KEY:
> + return KEY_CNT;
> + case EV_REL:
> + return REL_CNT;
> + case EV_ABS:
> + return ABS_CNT;
> + case EV_MSC:
> + return MSC_CNT;
> + case EV_SW:
> + return SW_CNT;
> + case EV_LED:
> + return LED_CNT;
> + case EV_SND:
> + return SND_CNT;
> + case EV_FF:
> + return FF_CNT;
> + }
> +
> + return 0;
> +}
> +
> +/* must be called with evdev-mutex held */
> +static int evdev_set_mask(struct evdev_client *client,
> + unsigned int type,
> + const void __user *codes,
> + u32 codes_size)
> +{
> + unsigned long flags, *mask, *oldmask;
> + size_t cnt, size;
> +
> + /* unknown masks are simply ignored for forward-compat */
> + cnt = evdev_get_mask_cnt(type);
> + if (!cnt)
> + return 0;
> +
> + /* we allow 'codes_size > size' for forward-compat */
> + size = sizeof(unsigned long) * BITS_TO_LONGS(cnt);
> +
> + mask = kzalloc(size, GFP_KERNEL);
> + if (!mask)
> + return -ENOMEM;
> +
> + if (copy_from_user(mask, codes, min_t(size_t, codes_size, size))) {
> + kfree(mask);
> + return -EFAULT;
> + }
> +
> + spin_lock_irqsave(&client->buffer_lock, flags);
> + oldmask = client->evmasks[type];
> + client->evmasks[type] = mask;
> + spin_unlock_irqrestore(&client->buffer_lock, flags);
> +
> + kfree(oldmask);
> +
> + return 0;
> +}
> +
> +/* must be called with evdev-mutex held */
> +static int evdev_get_mask(struct evdev_client *client,
> + unsigned int type,
> + void __user *codes,
> + u32 codes_size)
> +{
> + unsigned long *mask;
> + size_t cnt, size, min, i;
> + u8 __user *out;
> +
> + /* we allow unknown types and 'codes_size > size' for forward-compat */
> + cnt = evdev_get_mask_cnt(type);
> + size = sizeof(unsigned long) * BITS_TO_LONGS(cnt);
> + min = min_t(size_t, codes_size, size);
> +
> + if (cnt > 0) {
> + mask = client->evmasks[type];
> + if (mask) {
> + if (copy_to_user(codes, mask, min))
> + return -EFAULT;
> + } else {
> + /* fake mask with all bits set */
> + out = (u8 __user*)codes;
> + for (i = 0; i < min; ++i) {
> + if (put_user((u8)0xff, out + i))
> + return -EFAULT;
> + }
> + }
> + }
> +
> + codes = (u8 __user*)codes + min;
> + codes_size -= min;
> +
> + if (codes_size > 0 && clear_user(codes, codes_size))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +/* requires the buffer lock to be held */
> +static bool __evdev_is_masked(struct evdev_client *client,
> + unsigned int type,
> + unsigned int code)
> +{
> + unsigned long *mask;
> + size_t cnt;
> +
> + /* EV_SYN and unknown codes are never masked */
> + if (!type || type >= EV_CNT)
why not use type == EV_SYN?
> + return false;
> +
> + /* first test whether the type is masked */
> + mask = client->evmasks[0];
if mask is NULL, you already know it's not mask, you can return early.
> + if (mask && !test_bit(type, mask))
> + return true;
> +
> + /* unknown values are never masked */
> + cnt = evdev_get_mask_cnt(type);
> + if (!cnt || code >= cnt)
> + return false;
> +
> + mask = client->evmasks[type];
> + return mask && !test_bit(code, mask);
> +}
> +
> /* Flush queued events of given type @type and code @code. A negative code
> * is interpreted as catch-all. Caller must hold client->buffer_lock. */
> static void __evdev_flush_queue(struct evdev_client *client,
> @@ -137,6 +266,9 @@ static void evdev_queue_syn_dropped(struct evdev_client *client)
> static void __pass_event(struct evdev_client *client,
> const struct input_event *event)
> {
> + if (__evdev_is_masked(client, event->type, event->code))
> + return;
> +
> client->buffer[client->head++] = *event;
> client->head &= client->bufsize - 1;
>
> @@ -368,6 +500,7 @@ static int evdev_release(struct inode *inode, struct file *file)
> {
> struct evdev_client *client = file->private_data;
> struct evdev *evdev = client->evdev;
> + unsigned int i;
>
> mutex_lock(&evdev->mutex);
> evdev_ungrab(evdev, client);
> @@ -375,6 +508,9 @@ static int evdev_release(struct inode *inode, struct file *file)
>
> evdev_detach_client(evdev, client);
>
> + for (i = 0; i < EV_CNT; ++i)
> + kfree(client->evmasks[i]);
> +
> if (is_vmalloc_addr(client))
> vfree(client);
> else
> @@ -866,6 +1002,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
> struct evdev *evdev = client->evdev;
> struct input_dev *dev = evdev->handle.dev;
> struct input_absinfo abs;
> + struct input_mask mask;
> struct ff_effect effect;
> int __user *ip = (int __user *)p;
> unsigned int i, t, u, v;
> @@ -927,6 +1064,24 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
> else
> return evdev_revoke(evdev, client, file);
>
> + case EVIOCGMASK:
> + if (copy_from_user(&mask, p, sizeof(mask)))
> + return -EFAULT;
> +
> + return evdev_get_mask(client,
> + mask.type,
> + (void*)(long)mask.codes_ptr,
> + mask.codes_size);
> +
> + case EVIOCSMASK:
> + if (copy_from_user(&mask, p, sizeof(mask)))
> + return -EFAULT;
> +
> + return evdev_set_mask(client,
> + mask.type,
> + (const void*)(long)mask.codes_ptr,
> + mask.codes_size);
> +
> case EVIOCSCLOCKID:
> if (copy_from_user(&i, p, sizeof(unsigned int)))
> return -EFAULT;
> diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
> index bd24470..5b73712 100644
> --- a/include/uapi/linux/input.h
> +++ b/include/uapi/linux/input.h
> @@ -97,6 +97,12 @@ struct input_keymap_entry {
> __u8 scancode[32];
> };
>
> +struct input_mask {
> + u32 type;
> + u32 codes_size;
> + u64 codes_ptr;
> +};
> +
> #define EVIOCGVERSION _IOR('E', 0x01, int) /* get driver version */
> #define EVIOCGID _IOR('E', 0x02, struct input_id) /* get device ID */
> #define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) /* get repeat settings */
> @@ -153,6 +159,8 @@ struct input_keymap_entry {
>
> #define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */
> #define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */
> +#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) /* Get event-masks */
> +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */
This is missing from all other ioctls but while you're adding a new one
anyway: please add documentation on what the ioctl does, the input and
return value/output expected, side-effects etc. right now, understanding the
evdev ioctls requires either reading the kernel code or existing user-space
code, with the usual risk of getting it wrong.
Cheers,
Peter
>
> #define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */
>
> --
> 1.9.2
>
^ permalink raw reply
* Re: [PATCH] evdev: flush ABS_* events during EVIOCGABS
From: Peter Hutterer @ 2014-04-22 4:15 UTC (permalink / raw)
To: David Herrmann; +Cc: linux-input, Dmitry Torokhov, Benjamin Tissoires
In-Reply-To: <1397156944-5991-1-git-send-email-dh.herrmann@gmail.com>
On Thu, Apr 10, 2014 at 09:09:04PM +0200, David Herrmann wrote:
> We currently flush input events in the outgoing client queue on most
> EVIOCG* ioctls so userspace doesn't get events twice (once during the
> ioctl and once during a later read()).
>
> We introduced this in:
> commit 483180281f0ac60d1138710eb21f4b9961901294
> Author: David Herrmann <dh.herrmann@gmail.com>
> Date: Sun Apr 7 21:13:19 2013 -0700
>
> Input: evdev - flush queues during EVIOCGKEY-like ioctls
>
> However, we didn't modify the EVIOCGABS handler as we considered ABS
> values to change fast enough that flushing the queue seems irrelevant. But
> as it turns out, the ABS SLOT events suffer from the same issues. Hence,
> also flush the input queue from ABS values on EVIOCGABS.
>
> Reported-by: Peter Hutterer <peter.hutterer@who-t.net>
> Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
> ---
> Hi
>
> This is untested as I don't have any multitouch hardware right now. Peter, can
> you give this a try?
How are you planning to handle the slot-based events? We'd either need to
add something similar (but more complex) to evdev_handle_mt_request or rely
on the caller to call the whole EV_ABS range and ditch anything ABS_MT_.
I'd prefer the former, the latter is yet more behaviour that's easy to get
wrong.
Cheers,
Peter
>
> Thanks
> David
>
> drivers/input/evdev.c | 63 ++++++++++++++++++++++++++++++++++++---------------
> 1 file changed, 45 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
> index a06e125..fc55c19 100644
> --- a/drivers/input/evdev.c
> +++ b/drivers/input/evdev.c
> @@ -55,8 +55,11 @@ struct evdev_client {
> struct input_event buffer[];
> };
>
> -/* flush queued events of type @type, caller must hold client->buffer_lock */
> -static void __evdev_flush_queue(struct evdev_client *client, unsigned int type)
> +/* Flush queued events of given type @type and code @code. A negative code
> + * is interpreted as catch-all. Caller must hold client->buffer_lock. */
> +static void __evdev_flush_queue(struct evdev_client *client,
> + unsigned int type,
> + int code)
> {
> unsigned int i, head, num;
> unsigned int mask = client->bufsize - 1;
> @@ -75,7 +78,7 @@ static void __evdev_flush_queue(struct evdev_client *client, unsigned int type)
> ev = &client->buffer[i];
> is_report = ev->type == EV_SYN && ev->code == SYN_REPORT;
>
> - if (ev->type == type) {
> + if (ev->type == type && (code < 0 || ev->code == code)) {
> /* drop matched entry */
> continue;
> } else if (is_report && !num) {
> @@ -774,7 +777,7 @@ static int evdev_handle_get_val(struct evdev_client *client,
>
> spin_unlock(&dev->event_lock);
>
> - __evdev_flush_queue(client, type);
> + __evdev_flush_queue(client, type, -1);
>
> spin_unlock_irq(&client->buffer_lock);
>
> @@ -787,6 +790,40 @@ static int evdev_handle_get_val(struct evdev_client *client,
> return ret;
> }
>
> +static int evdev_handle_get_abs(struct evdev_client *client,
> + struct input_dev *dev,
> + unsigned int code,
> + unsigned int size,
> + void __user *p)
> +{
> + struct input_absinfo abs;
> + int ret;
> +
> + if (!dev->absinfo)
> + return -EINVAL;
> +
> + /* see evdev_handle_get_val() for locking order */
> +
> + spin_lock_irq(&dev->event_lock);
> + spin_lock(&client->buffer_lock);
> +
> + abs = dev->absinfo[code];
> +
> + spin_unlock(&dev->event_lock);
> +
> + __evdev_flush_queue(client, EV_ABS, code);
> +
> + spin_unlock_irq(&client->buffer_lock);
> +
> + ret = copy_to_user(p, &abs, min_t(size_t,
> + size,
> + sizeof(struct input_absinfo)));
> + if (ret < 0)
> + evdev_queue_syn_dropped(client);
> +
> + return ret;
> +}
> +
> static int evdev_handle_mt_request(struct input_dev *dev,
> unsigned int size,
> int __user *ip)
> @@ -972,20 +1009,10 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
> _IOC_NR(cmd) & EV_MAX, size,
> p, compat_mode);
>
> - if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {
> -
> - if (!dev->absinfo)
> - return -EINVAL;
> -
> - t = _IOC_NR(cmd) & ABS_MAX;
> - abs = dev->absinfo[t];
> -
> - if (copy_to_user(p, &abs, min_t(size_t,
> - size, sizeof(struct input_absinfo))))
> - return -EFAULT;
> -
> - return 0;
> - }
> + if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0)))
> + return evdev_handle_get_abs(client, dev,
> + _IOC_NR(cmd) & ABS_MAX,
> + size, p);
> }
>
> if (_IOC_DIR(cmd) == _IOC_WRITE) {
> --
> 1.9.1
>
^ permalink raw reply
* Re: [PATCH] Input: Add REP_MAX_COUNT to autorepeat parameters
From: Dmitry Torokhov @ 2014-04-22 4:59 UTC (permalink / raw)
To: Petri Gynther; +Cc: open list:HID CORE LAYER
In-Reply-To: <CAGXr9JFj4v7wSbr+k9kev9818_FeOJO1Dm38ntTMQbw0HFYK_g@mail.gmail.com>
On Mon, Apr 21, 2014 at 03:06:32PM -0700, Petri Gynther wrote:
> Hi Dmitry,
>
> On Mon, Apr 21, 2014 at 1:17 PM, Dmitry Torokhov
> <dmitry.torokhov@gmail.com> wrote:
> > Hi Petri,
> >
> > On Mon, Apr 21, 2014 at 01:00:08PM -0700, Petri Gynther wrote:
> >> Add REP_MAX_COUNT to autorepeat parameters. This enables an input device to be
> >> configured for maximum autorepeat count, so that a keypress is not repeated
> >> forever. This is important for Bluetooth keyboards and remote controls that may
> >> lose the Bluetooth link at any time, e.g. right after sending a key-down event
> >> but before sending the corresponding key-up event.
> >
> > I think this should be solved in the drivers - if link is lost then they
> > should tear down the device (or generate keyup events if they want to
> > hand onto the device).
>
> In my opinion, since the keypress autorepeat code lives in the input
> subsystem, it should provide some kind of mechanism to limit the
> autorepeat if the user wants to configure so. The delay and period are
> already configurable, so this just adds the upper limit, so that the
> autorepeat doesn't go on forever.
No, autorepeat should run until the key is released; that is your upper
bound. What you are trying to do is work around the issue in a given
driver that does not report key releases properly and such workaround is
not acceptable. Not it is sufficient: one can disable kernel-level
autorepeat and you'll end up with userspace autorepeating in the very
same fashion.
>
> For Bluetooth LE remote controls (HID over GATT), BlueZ daemon uses
> uHID kernel driver (drivers/hid/uhid.c) to create the necessary HID
> device and pass the HID input events. This device is persistent over
> reconnects, so the HID and input devices are not destroyed and
> re-created on every disconnect and reconnect. On BLE device
> disconnect, BlueZ daemon cannot inject the necessary key-up event
> because it would have to know the HID input report details in order to
> do so. And, uHID driver doesn't support disconnect-reconnect unless
> the input pipeline is completely torn down and re-created (which is
> not desirable).
Maybe uhid needs another way to indicate that device was disconnected
and its state is no longer valid, it is still a driver issue as far as I
can see.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] input: add support for ALPS v7 protocol device
From: Dmitry Torokhov @ 2014-04-22 5:26 UTC (permalink / raw)
To: Qiting Chen
Cc: cernekee, dturvene, linux-input, jclift, Qiting Chen,
Peter Hutterer, Hans de Goede
In-Reply-To: <1395219353-27683-1-git-send-email-qiting.chen@cn.alps.com>
Hi Qiting,
On Wed, Mar 19, 2014 at 04:55:53PM +0800, Qiting Chen wrote:
> Here is a patch of supporting ALPS v7 protocol device.
> ALPS v7 protocol device is a clickpad that is currently used on
> Lenovo S430/S435/S530, Lenovo Z410/Z510, HP Odie, HP Revolve 810 G1,
> as well as other machines with ALPS Touchpad of following infomation:
> Device ID = 0x73, 0x03, 0x0a
> Firmware ID = 0x88, 0xb*, 0x**
>
> A v7 protocol support patch is first relesed 2 months ago:
> http://www.spinics.net/lists/linux-input/msg29084.html
> After that some feedbacks were received from end user. Now this patch fixed the bugs
> reported by them:
> 1) Fix cursor jump when doing a right click drag
> 2) Fix cursor jitter when button clicking
My biggest question is whether the soft buttons should be processed in
kernel driver or in userspace.
Peter, don't Synaptics clickpads need similar functionality? I thought X
driver already handles soft button areas... Am I mistaken?
>
> Signed-off-by: Qiting Chen <qiting.chen@cn.alps.com>
> ---
> drivers/input/mouse/alps.c | 560 ++++++++++++++++++++++++++++++++++++++++++---
> drivers/input/mouse/alps.h | 132 +++++++++--
> 2 files changed, 641 insertions(+), 51 deletions(-)
>
> diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
> index fb15c64..383281f 100644
> --- a/drivers/input/mouse/alps.c
> +++ b/drivers/input/mouse/alps.c
> @@ -32,6 +32,13 @@
> #define ALPS_REG_BASE_RUSHMORE 0xc2c0
> #define ALPS_REG_BASE_PINNACLE 0x0000
>
> +#define LEFT_BUTTON_BIT 0x01
> +#define RIGHT_BUTTON_BIT 0x02
> +
> +#define V7_LARGE_MOVEMENT 130
> +#define V7_DEAD_ZONE_OFFSET_X 72
> +#define V7_DEAD_ZONE_OFFSET_Y 72
> +
> static const struct alps_nibble_commands alps_v3_nibble_commands[] = {
> { PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */
> { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
> @@ -99,6 +106,7 @@ static const struct alps_nibble_commands alps_v6_nibble_commands[] = {
> #define ALPS_FOUR_BUTTONS 0x40 /* 4 direction button present */
> #define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet interleaved with
> 6-byte ALPS packet */
> +#define ALPS_BTNLESS 0x100 /* ALPS ClickPad flag */
>
> static const struct alps_model_info alps_model_data[] = {
> { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
> @@ -140,6 +148,20 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> * isn't valid per PS/2 spec.
> */
>
> +static unsigned int alps_pt_distance(struct alps_abs_data *pt0,
> + struct alps_abs_data *pt1)
> +{
> + int vect_x, vect_y;
> +
> + if (!pt0 || !pt1)
> + return 0;
In which case can either of this pointers being NULL?
> +
> + vect_x = pt0->x - pt1->x;
> + vect_y = pt0->y - pt1->y;
> +
> + return int_sqrt(vect_x * vect_x + vect_y * vect_y);
> +}
> +
> /* Packet formats are described in Documentation/input/alps.txt */
>
> static bool alps_is_valid_first_byte(struct alps_data *priv,
> @@ -320,8 +342,8 @@ static void alps_process_bitmap_dolphin(struct alps_data *priv,
> end_bit = y_msb - 1;
> box_middle_y = (priv->y_max * (start_bit + end_bit)) /
> (2 * (priv->y_bits - 1));
> - *x1 = fields->x;
> - *y1 = fields->y;
> + *x1 = fields->pt.x;
> + *y1 = fields->pt.y;
> *x2 = 2 * box_middle_x - *x1;
> *y2 = 2 * box_middle_y - *y1;
> }
> @@ -461,6 +483,38 @@ static void alps_report_semi_mt_data(struct input_dev *dev, int num_fingers,
> alps_set_slot(dev, 1, num_fingers == 2, x2, y2);
> }
>
> +static void alps_report_coord_and_btn(struct psmouse *psmouse,
> + struct alps_fields *f)
> +{
> + struct input_dev *dev;
> +
> + if (!psmouse || !f)
> + return;
Can either of these 2 pointers ever be NULL?
> +
> + dev = psmouse->dev;
> +
> + if (f->fingers) {
> + input_report_key(dev, BTN_TOUCH, 1);
> + alps_report_semi_mt_data(dev, f->fingers,
> + f->pt_img[0].x, f->pt_img[0].y,
> + f->pt_img[1].x, f->pt_img[1].y);
> + input_mt_report_finger_count(dev, f->fingers);
> +
> + input_report_abs(dev, ABS_X, f->pt_img[0].x);
> + input_report_abs(dev, ABS_Y, f->pt_img[0].y);
> + input_report_abs(dev, ABS_PRESSURE, f->pt_img[0].z);
> + } else {
> + input_report_key(dev, BTN_TOUCH, 0);
> + input_mt_report_finger_count(dev, 0);
> + input_report_abs(dev, ABS_PRESSURE, 0);
> + }
> +
> + input_report_key(dev, BTN_LEFT, f->btn.left);
> + input_report_key(dev, BTN_RIGHT, f->btn.right);
> +
> + input_sync(dev);
> +}
> +
> static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
> {
> struct alps_data *priv = psmouse->private;
> @@ -523,13 +577,13 @@ static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
>
> static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p)
> {
> - f->left = !!(p[3] & 0x01);
> - f->right = !!(p[3] & 0x02);
> - f->middle = !!(p[3] & 0x04);
> + f->btn.left = !!(p[3] & 0x01);
> + f->btn.right = !!(p[3] & 0x02);
> + f->btn.middle = !!(p[3] & 0x04);
>
> - f->ts_left = !!(p[3] & 0x10);
> - f->ts_right = !!(p[3] & 0x20);
> - f->ts_middle = !!(p[3] & 0x40);
> + f->btn.ts_left = !!(p[3] & 0x10);
> + f->btn.ts_right = !!(p[3] & 0x20);
> + f->btn.ts_middle = !!(p[3] & 0x40);
> }
>
> static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> @@ -546,10 +600,10 @@ static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> ((p[2] & 0x7f) << 1) |
> (p[4] & 0x01);
>
> - f->x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> + f->pt.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> ((p[0] & 0x30) >> 4);
> - f->y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> - f->z = p[5] & 0x7f;
> + f->pt.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> + f->pt.z = p[5] & 0x7f;
>
> alps_decode_buttons_v3(f, p);
> }
> @@ -573,9 +627,9 @@ static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p,
> f->is_mp = !!(p[0] & 0x20);
>
> if (!f->is_mp) {
> - f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> - f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> - f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> + f->pt.x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> + f->pt.y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> + f->pt.z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> alps_decode_buttons_v3(f, p);
> } else {
> f->fingers = ((p[0] & 0x6) >> 1 |
> @@ -687,7 +741,7 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> * with x, y, and z all zero, so these seem to be flukes.
> * Ignore them.
> */
> - if (f.x && f.y && !f.z)
> + if (f.pt.x && f.pt.y && !f.pt.z)
> return;
>
> /*
> @@ -695,12 +749,12 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> * to rely on ST data.
> */
> if (!fingers) {
> - x1 = f.x;
> - y1 = f.y;
> - fingers = f.z > 0 ? 1 : 0;
> + x1 = f.pt.x;
> + y1 = f.pt.y;
> + fingers = f.pt.z > 0 ? 1 : 0;
> }
>
> - if (f.z >= 64)
> + if (f.pt.z >= 64)
> input_report_key(dev, BTN_TOUCH, 1);
> else
> input_report_key(dev, BTN_TOUCH, 0);
> @@ -709,22 +763,22 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
>
> input_mt_report_finger_count(dev, fingers);
>
> - input_report_key(dev, BTN_LEFT, f.left);
> - input_report_key(dev, BTN_RIGHT, f.right);
> - input_report_key(dev, BTN_MIDDLE, f.middle);
> + input_report_key(dev, BTN_LEFT, f.btn.left);
> + input_report_key(dev, BTN_RIGHT, f.btn.right);
> + input_report_key(dev, BTN_MIDDLE, f.btn.middle);
>
> - if (f.z > 0) {
> - input_report_abs(dev, ABS_X, f.x);
> - input_report_abs(dev, ABS_Y, f.y);
> + if (f.pt.z > 0) {
> + input_report_abs(dev, ABS_X, f.pt.x);
> + input_report_abs(dev, ABS_Y, f.pt.y);
> }
> - input_report_abs(dev, ABS_PRESSURE, f.z);
> + input_report_abs(dev, ABS_PRESSURE, f.pt.z);
>
> input_sync(dev);
>
> if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
> - input_report_key(dev2, BTN_LEFT, f.ts_left);
> - input_report_key(dev2, BTN_RIGHT, f.ts_right);
> - input_report_key(dev2, BTN_MIDDLE, f.ts_middle);
> + input_report_key(dev2, BTN_LEFT, f.btn.ts_left);
> + input_report_key(dev2, BTN_RIGHT, f.btn.ts_right);
> + input_report_key(dev2, BTN_MIDDLE, f.btn.ts_middle);
> input_sync(dev2);
> }
> }
> @@ -916,6 +970,364 @@ static void alps_process_packet_v4(struct psmouse *psmouse)
> input_sync(dev);
> }
>
> +static bool alps_is_valid_package_v7(struct psmouse *psmouse)
> +{
> + if ((psmouse->pktcnt == 3) && ((psmouse->packet[2] & 0x40) != 0x40))
> + return false;
> + if ((psmouse->pktcnt == 4) && ((psmouse->packet[3] & 0x48) != 0x48))
> + return false;
> + if ((psmouse->pktcnt == 6) && ((psmouse->packet[5] & 0x40) != 0x0))
> + return false;
Maybe do:
switch (psmouse->pktcnt) {
case 3:
if ((psmouse->packet[2] & 0x40) != 0x40)
return false;
break;
...
}
> + return true;
> +}
> +
> +static int alps_drop_unsupported_packet_v7(struct psmouse *psmouse)
> +{
> + struct alps_data *priv = psmouse->private;
> + int drop = 1;
bool drop = true;
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> + drop = 0;
if (... ||
...) {
drop = false;
}
> +
> + return drop;
> +}
> +
> +static unsigned char alps_get_packet_id_v7(char *byte)
> +{
> + unsigned char packet_id;
> +
> + if (byte[4] & 0x40)
> + packet_id = V7_PACKET_ID_TWO;
> + else if (byte[4] & 0x01)
> + packet_id = V7_PACKET_ID_MULTI;
> + else if ((byte[0] & 0x10) && !(byte[4] & 0x43))
> + packet_id = V7_PACKET_ID_NEW;
> + else
> + packet_id = V7_PACKET_ID_IDLE;
> +
> + return packet_id;
> +}
> +
> +static void alps_get_finger_coordinate_v7(struct alps_abs_data *pt,
> + unsigned char *pkt,
> + unsigned char pkt_id)
> +{
> + if ((pkt_id == V7_PACKET_ID_TWO) ||
> + (pkt_id == V7_PACKET_ID_MULTI) ||
> + (pkt_id == V7_PACKET_ID_NEW)) {
> + pt[0].x = ((pkt[2] & 0x80) << 4);
> + pt[0].x |= ((pkt[2] & 0x3F) << 5);
> + pt[0].x |= ((pkt[3] & 0x30) >> 1);
> + pt[0].x |= (pkt[3] & 0x07);
> + pt[0].y = (pkt[1] << 3) | (pkt[0] & 0x07);
> +
> + pt[1].x = ((pkt[3] & 0x80) << 4);
> + pt[1].x |= ((pkt[4] & 0x80) << 3);
> + pt[1].x |= ((pkt[4] & 0x3F) << 4);
> + pt[1].y = ((pkt[5] & 0x80) << 3);
> + pt[1].y |= ((pkt[5] & 0x3F) << 4);
> +
> + if (pkt_id == V7_PACKET_ID_TWO) {
> + pt[1].x &= ~0x000F;
> + pt[1].y |= 0x000F;
> + } else if (pkt_id == V7_PACKET_ID_MULTI) {
> + pt[1].x &= ~0x003F;
> + pt[1].y &= ~0x0020;
> + pt[1].y |= ((pkt[4] & 0x02) << 4);
> + pt[1].y |= 0x001F;
> + } else if (pkt_id == V7_PACKET_ID_NEW) {
> + pt[1].x &= ~0x003F;
> + pt[1].x |= (pkt[0] & 0x20);
> + pt[1].y |= 0x000F;
> + }
> +
> + pt[0].y = 0x7FF - pt[0].y;
> + pt[1].y = 0x7FF - pt[1].y;
> +
> + pt[0].z = (pt[0].x && pt[0].y) ? 62 : 0;
> + pt[1].z = (pt[1].x && pt[1].y) ? 62 : 0;
> + }
> +}
> +
> +static void alps_decode_packet_v7(struct alps_fields *f,
> + unsigned char *p,
> + struct psmouse *psmouse)
> +{
> + struct alps_data *priv = psmouse->private;
> + static struct v7_raw prev_r;
> +
> + priv->r.v7.pkt_id = alps_get_packet_id_v7(p);
> +
> + alps_get_finger_coordinate_v7(f->pt_img, p, priv->r.v7.pkt_id);
> +
> + priv->r.v7.rest_left = 0;
> + priv->r.v7.rest_right = 0;
> + priv->r.v7.additional_fingers = 0;
> + priv->phy_btn = 0;
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI) {
> + priv->r.v7.rest_left = (p[0] & 0x10) >> 4;
> + priv->r.v7.rest_right = (p[0] & 0x20) >> 5;
> + }
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> + priv->r.v7.additional_fingers = p[5] & 0x03;
> +
> + priv->phy_btn = (p[0] & 0x80) >> 7;
> +
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO) {
> + if (f->pt_img[0].z != 0 && f->pt_img[1].z != 0)
> + priv->r.v7.raw_fn = 2;
> + else
> + priv->r.v7.raw_fn = 1;
> + } else if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> + priv->r.v7.raw_fn = 3 + priv->r.v7.additional_fingers;
> + else if (priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> + priv->r.v7.raw_fn = 0;
> + else if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW)
> + priv->r.v7.raw_fn = prev_r.raw_fn;
> +
> + /* It is a trick to bypass firmware bug of older version
> + that 'New' Packet is missed when finger number changed.
> + We fake a 'New' Packet in such cases.*/
Multi-line comments should be formatted as follows:
/*
* This is a multi
* line comment.
*/
> + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE) {
> + if (priv->r.v7.raw_fn != prev_r.raw_fn)
> + priv->r.v7.pkt_id = V7_PACKET_ID_NEW;
> + }
> +
> + memcpy(&prev_r, &priv->r.v7, sizeof(struct v7_raw));
> +}
> +
> +static void alps_set_each_pt_attr_v7(struct psmouse *psmouse,
> + struct alps_abs_data *pt,
> + struct alps_bl_pt_attr *pt_attr)
> +{
> + struct alps_data *priv = psmouse->private;
> + unsigned int dist;
> +
> + if (!pt_attr->is_init_pt_got && pt->z != 0) {
> + pt_attr->is_init_pt_got = 1;
> + pt_attr->is_counted = 0;
> + memcpy(&pt_attr->init_pt, pt, sizeof(pt_attr->init_pt));
> + }
> +
> + if (pt->z != 0) {
> + if (pt->y < priv->resting_zone_y_min) {
> + /* A finger is recognized as a non-resting finger
> + if it's position is outside the resting finger zone.*/
> + pt_attr->zone = ZONE_NORMAL;
> + pt_attr->is_counted = 1;
> + } else {
> + /* A finger is recognized as a resting finger if it's
> + position is inside the resting finger zone and there's
> + no large movement from it's touch down position.*/
> + pt_attr->zone = ZONE_RESTING;
> +
> + if (pt->x > priv->x_max / 2)
> + pt_attr->zone |= ZONE_RIGHT_BTN;
> + else
> + pt_attr->zone |= ZONE_LEFT_BTN;
> +
> + /* A resting finger will turn to be a non-resting
> + finger if it has made large movement from it's touch
> + down position. A non-resting finger will never turn
> + to a resting finger before it leaves the touchpad
> + surface.*/
> + if (pt_attr->is_init_pt_got) {
> + dist = alps_pt_distance(pt, &pt_attr->init_pt);
> +
> + if (dist > V7_LARGE_MOVEMENT)
> + pt_attr->is_counted = 1;
> + }
> + }
> + }
> +}
> +
> +static void alps_set_pt_attr_v7(struct psmouse *psmouse,
> + struct alps_fields *f)
> +{
> + struct alps_data *priv = psmouse->private;
> + int i;
> +
> + switch (priv->r.v7.pkt_id) {
> + case V7_PACKET_ID_TWO:
> + case V7_PACKET_ID_MULTI:
> + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> + alps_set_each_pt_attr_v7(psmouse,
> + &f->pt_img[i],
> + &priv->pt_attr[i]);
> + }
> + break;
> + default:
> + /*All finger attributes are cleared when packet ID is
> + 'IDLE', 'New'or other unknown IDs. An 'IDLE' packet
> + indicates that there's no finger and no button activity.
> + A 'NEW' packet indicates the finger position in packet
> + is not continues from previous packet. Such as the
> + condition there's finger placed or lifted. In these cases,
> + finger attributes will be reset.*/
> + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> + break;
> + }
> +}
> +
> +static void alps_cal_output_finger_num_v7(struct psmouse *psmouse,
> + struct alps_fields *f)
> +{
> + struct alps_data *priv = psmouse->private;
> + unsigned int fn = 0;
> + int i;
> +
> + switch (priv->r.v7.pkt_id) {
> + case V7_PACKET_ID_IDLE:
> + case V7_PACKET_ID_NEW:
> + /*No finger is reported when packet ID is 'IDLE' or 'New'.
> + An 'IDLE' packet indicates that there's no finger on touchpad.
> + A 'NEW' packet indicates there's finger placed or lifted.
> + Finger position of 'New' packet is not continues from the
> + previous packet.*/
> + fn = 0;
> + break;
> + case V7_PACKET_ID_TWO:
> + if (f->pt_img[0].z == 0) {
> + /*The first finger slot is zero when a non-resting
> + finger lifted and remaining only one resting finger
> + on touchpad. Hardware report the remaining resting
> + finger in second slot. This resting is ignored*/
> + fn = 0;
> + } else if (f->pt_img[1].z == 0) {
> + /* The second finger slot is zero if there's
> + only one finger*/
> + fn = 1;
> + } else {
> + /*All non-resting fingers will be counted to report*/
> + fn = 0;
> + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> + if (priv->pt_attr[i].is_counted)
> + fn++;
> + }
> +
> + /*In the case that both fingers are
> + resting fingers, report the first one*/
> + if (!priv->pt_attr[0].is_counted &&
> + !priv->pt_attr[1].is_counted) {
> + fn = 1;
> + }
> + }
> + break;
> + case V7_PACKET_ID_MULTI:
> + /*A packet ID 'MULTI' indicats that at least 3 non-resting
> + finger exist.*/
> + fn = 3 + priv->r.v7.additional_fingers;
> + break;
> + }
> +
> + f->fingers = fn;
> +}
> +
> +static void alps_button_dead_zone_filter(struct psmouse *psmouse,
> + struct alps_fields *f,
> + struct alps_fields *prev_f)
> +{
> + struct alps_data *priv = psmouse->private;
> + int dx, dy;
> +
> + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0) {
> + memcpy(&priv->pt_attr[0].init_dead_pt,
> + &f->pt_img[0],
> + sizeof(struct alps_abs_data));
> + }
> +
> + if (priv->pt_attr[0].init_dead_pt.x != 0 &&
> + priv->pt_attr[0].init_dead_pt.x != 0) {
> + dx = f->pt_img[0].x - priv->pt_attr[0].init_dead_pt.x;
> + dy = f->pt_img[0].y - priv->pt_attr[0].init_dead_pt.y;
> + if ((abs(dx) > V7_DEAD_ZONE_OFFSET_X) ||
> + (abs(dy) > V7_DEAD_ZONE_OFFSET_Y)) {
> + memset(&priv->pt_attr[0].init_dead_pt, 0,
> + sizeof(struct alps_abs_data));
> + priv->btn_delay_cnt = 0;
> + } else {
> + memcpy(&f->pt_img[0],
> + &prev_f->pt_img[0],
> + sizeof(struct alps_abs_data));
> + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0)
> + priv->btn_delay_cnt = 2;
> + }
> + }
> +
> + if (priv->btn_delay_cnt > 0) {
> + f->btn.left = 0;
> + f->btn.right = 0;
> + priv->btn_delay_cnt--;
> + }
> +}
> +
> +static void alps_assign_buttons_v7(struct psmouse *psmouse,
> + struct alps_fields *f,
> + struct alps_fields *prev_f)
> +{
> + struct alps_data *priv = psmouse->private;
> +
> + if (priv->phy_btn) {
> + if (!priv->prev_phy_btn) {
> + /* Report a right click as long as there's finger on
> + right button zone. Othrewise, report a left click.*/
> + if (priv->r.v7.rest_right ||
> + priv->pt_attr[0].zone & ZONE_RIGHT_BTN ||
> + priv->pt_attr[1].zone & ZONE_RIGHT_BTN) {
> + f->btn.right = 1;
> + priv->pressed_btn_bits |= RIGHT_BUTTON_BIT;
> + } else {
> + f->btn.left = 1;
> + priv->pressed_btn_bits |= LEFT_BUTTON_BIT;
> + }
> + } else {
> + if (priv->pressed_btn_bits & RIGHT_BUTTON_BIT)
> + f->btn.right = 1;
> + if (priv->pressed_btn_bits & LEFT_BUTTON_BIT)
> + f->btn.left = 1;
> + }
> + } else {
> + priv->pressed_btn_bits = 0;
> + f->btn.right = 0;
> + f->btn.left = 0;
> + }
> +
> + alps_button_dead_zone_filter(psmouse, f, prev_f);
> +
> + priv->prev_phy_btn = priv->phy_btn;
> +}
> +
> +static void alps_process_packet_v7(struct psmouse *psmouse)
> +{
> + struct alps_data *priv = psmouse->private;
> + struct alps_fields f = {0};
> + static struct alps_fields prev_f;
> + unsigned char *packet = psmouse->packet;
> +
> + priv->decode_fields(&f, packet, psmouse);
> +
> + if (alps_drop_unsupported_packet_v7(psmouse))
> + return;
> +
> + alps_set_pt_attr_v7(psmouse, &f);
> +
> + alps_cal_output_finger_num_v7(psmouse, &f);
> +
> + alps_assign_buttons_v7(psmouse, &f, &prev_f);
> +
> + alps_report_coord_and_btn(psmouse, &f);
> +
> + memcpy(&prev_f, &f, sizeof(struct alps_fields));
> +}
> +
> static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
> unsigned char packet[],
> bool report_buttons)
> @@ -1080,6 +1492,14 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
> return PSMOUSE_BAD_DATA;
> }
>
> + if ((priv->proto_version == ALPS_PROTO_V7 &&
> + !alps_is_valid_package_v7(psmouse))) {
> + psmouse_dbg(psmouse, "refusing packet[%i] = %x\n",
> + psmouse->pktcnt - 1,
> + psmouse->packet[psmouse->pktcnt - 1]);
> + return PSMOUSE_BAD_DATA;
> + }
> +
> if (psmouse->pktcnt == psmouse->pktsize) {
> priv->process_packet(psmouse);
> return PSMOUSE_FULL_PACKET;
> @@ -1192,6 +1612,22 @@ static int alps_rpt_cmd(struct psmouse *psmouse, int init_command,
> return 0;
> }
>
> +static int alps_check_valid_firmware_id(unsigned char id[])
> +{
> + int valid = 1;
bool valid = true;
> +
> + if (id[0] == 0x73)
> + valid = 1;
> + else if (id[0] == 0x88) {
> + if ((id[1] == 0x07) ||
> + (id[1] == 0x08) ||
> + ((id[1] & 0xf0) == 0xB0))
> + valid = 1;
> + }
> +
> + return valid;
> +}
> +
> static int alps_enter_command_mode(struct psmouse *psmouse)
> {
> unsigned char param[4];
> @@ -1201,8 +1637,7 @@ static int alps_enter_command_mode(struct psmouse *psmouse)
> return -1;
> }
>
> - if ((param[0] != 0x88 || (param[1] != 0x07 && param[1] != 0x08)) &&
> - param[0] != 0x73) {
> + if (!alps_check_valid_firmware_id(param)) {
> psmouse_dbg(psmouse,
> "unknown response while entering command mode\n");
> return -1;
> @@ -1704,6 +2139,36 @@ error:
> return ret;
> }
>
> +static int alps_hw_init_v7(struct psmouse *psmouse)
> +{
> + struct ps2dev *ps2dev = &psmouse->ps2dev;
> + int reg_val, ret = -1;
> +
> + if (alps_enter_command_mode(psmouse))
> + goto error;
> +
> + reg_val = alps_command_mode_read_reg(psmouse, 0xc2d9);
> + if (reg_val == -1)
> + goto error;
> +
> + if (alps_command_mode_write_reg(psmouse, 0xc2c9, 0x64))
> + goto error;
> +
> + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4);
> + if (reg_val == -1)
> + goto error;
> +
> + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x02))
> + goto error;
> +
> + alps_exit_command_mode(psmouse);
> + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
> +
> +error:
> + alps_exit_command_mode(psmouse);
> + return ret;
> +}
> +
> /* Must be in command mode when calling this function */
> static int alps_absolute_mode_v4(struct psmouse *psmouse)
> {
> @@ -1875,6 +2340,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->set_abs_params = alps_set_abs_params_st;
> priv->x_max = 1023;
> priv->y_max = 767;
> + priv->slot_number = 1;
> break;
> case ALPS_PROTO_V3:
> priv->hw_init = alps_hw_init_v3;
> @@ -1883,6 +2349,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->decode_fields = alps_decode_pinnacle;
> priv->nibble_commands = alps_v3_nibble_commands;
> priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> + priv->slot_number = 2;
> break;
> case ALPS_PROTO_V4:
> priv->hw_init = alps_hw_init_v4;
> @@ -1890,6 +2357,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->set_abs_params = alps_set_abs_params_mt;
> priv->nibble_commands = alps_v4_nibble_commands;
> priv->addr_command = PSMOUSE_CMD_DISABLE;
> + priv->slot_number = 2;
> break;
> case ALPS_PROTO_V5:
> priv->hw_init = alps_hw_init_dolphin_v1;
> @@ -1905,6 +2373,7 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->y_max = 660;
> priv->x_bits = 23;
> priv->y_bits = 12;
> + priv->slot_number = 2;
> break;
> case ALPS_PROTO_V6:
> priv->hw_init = alps_hw_init_v6;
> @@ -1913,6 +2382,28 @@ static void alps_set_defaults(struct alps_data *priv)
> priv->nibble_commands = alps_v6_nibble_commands;
> priv->x_max = 2047;
> priv->y_max = 1535;
> + priv->slot_number = 2;
> + break;
> + case ALPS_PROTO_V7:
> + priv->hw_init = alps_hw_init_v7;
> + priv->process_packet = alps_process_packet_v7;
> + priv->decode_fields = alps_decode_packet_v7;
> + priv->set_abs_params = alps_set_abs_params_mt;
> + priv->nibble_commands = alps_v3_nibble_commands;
> + priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> + priv->x_max = 0xfff;
> + priv->y_max = 0x7ff;
> + priv->resting_zone_y_min = 0x654;
> + priv->byte0 = 0x48;
> + priv->mask0 = 0x48;
> + priv->flags = 0;
> + priv->slot_number = 2;
> +
> + priv->phy_btn = 0;
> + priv->prev_phy_btn = 0;
> + priv->btn_delay_cnt = 0;
> + priv->pressed_btn_bits = 0;
> + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> break;
> }
> }
> @@ -1982,6 +2473,11 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)
> return -EIO;
> else
> return 0;
> + } else if (ec[0] == 0x88 && (ec[1] & 0xf0) == 0xB0) {
> + priv->proto_version = ALPS_PROTO_V7;
> + alps_set_defaults(priv);
> +
> + return 0;
> } else if (ec[0] == 0x88 && ec[1] == 0x08) {
> priv->proto_version = ALPS_PROTO_V3;
> alps_set_defaults(priv);
> @@ -2045,7 +2541,7 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> struct input_dev *dev1)
> {
> set_bit(INPUT_PROP_SEMI_MT, dev1->propbit);
> - input_mt_init_slots(dev1, 2, 0);
> + input_mt_init_slots(dev1, priv->slot_number, 0);
> input_set_abs_params(dev1, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0);
> input_set_abs_params(dev1, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0);
>
> diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h
> index 03f88b6..dedbd27 100644
> --- a/drivers/input/mouse/alps.h
> +++ b/drivers/input/mouse/alps.h
> @@ -18,11 +18,36 @@
> #define ALPS_PROTO_V4 4
> #define ALPS_PROTO_V5 5
> #define ALPS_PROTO_V6 6
> +#define ALPS_PROTO_V7 7
> +
> +#define MAX_IMG_PT_NUM 5
> +#define V7_IMG_PT_NUM 2
> +
> +#define ZONE_NORMAL 0x01
> +#define ZONE_RESTING 0x02
> +#define ZONE_LEFT_BTN 0x04
> +#define ZONE_RIGHT_BTN 0x08
>
> #define DOLPHIN_COUNT_PER_ELECTRODE 64
> #define DOLPHIN_PROFILE_XOFFSET 8 /* x-electrode offset */
> #define DOLPHIN_PROFILE_YOFFSET 1 /* y-electrode offset */
>
> +/*
> + * enum V7_PACKET_ID - defines the packet type for V7
> + * V7_PACKET_ID_IDLE: There's no finger and no button activity.
> + * V7_PACKET_ID_TWO: There's one or two non-resting fingers on touchpad
> + * or there's button activities.
> + * V7_PACKET_ID_MULTI: There are at least three non-resting fingers.
> + * V7_PACKET_ID_NEW: The finger position in slot is not continues from
> + * previous packet.
> +*/
> +enum V7_PACKET_ID {
> + V7_PACKET_ID_IDLE,
> + V7_PACKET_ID_TWO,
> + V7_PACKET_ID_MULTI,
> + V7_PACKET_ID_NEW,
> +};
> +
> /**
> * struct alps_model_info - touchpad ID table
> * @signature: E7 response string to match.
> @@ -66,15 +91,7 @@ struct alps_nibble_commands {
> };
>
> /**
> - * struct alps_fields - decoded version of the report packet
> - * @x_map: Bitmap of active X positions for MT.
> - * @y_map: Bitmap of active Y positions for MT.
> - * @fingers: Number of fingers for MT.
> - * @x: X position for ST.
> - * @y: Y position for ST.
> - * @z: Z position for ST.
> - * @first_mp: Packet is the first of a multi-packet report.
> - * @is_mp: Packet is part of a multi-packet report.
> + * struct alps_btn - decoded version of the button status
> * @left: Left touchpad button is active.
> * @right: Right touchpad button is active.
> * @middle: Middle touchpad button is active.
> @@ -82,16 +99,7 @@ struct alps_nibble_commands {
> * @ts_right: Right trackstick button is active.
> * @ts_middle: Middle trackstick button is active.
> */
> -struct alps_fields {
> - unsigned int x_map;
> - unsigned int y_map;
> - unsigned int fingers;
> - unsigned int x;
> - unsigned int y;
> - unsigned int z;
> - unsigned int first_mp:1;
> - unsigned int is_mp:1;
> -
> +struct alps_btn {
> unsigned int left:1;
> unsigned int right:1;
> unsigned int middle:1;
> @@ -102,6 +110,73 @@ struct alps_fields {
> };
>
> /**
> + * struct alps_btn - decoded version of the X Y Z postion for ST.
> + * @x: X position for ST.
> + * @y: Y position for ST.
> + * @z: Z position for ST.
> + */
> +struct alps_abs_data {
> + unsigned int x;
> + unsigned int y;
> + unsigned int z;
> +};
> +
> +/**
> + * struct alps_fields - decoded version of the report packet
> + * @fingers: Number of fingers for MT.
> + * @pt: X Y Z postion for ST.
> + * @pt: X Y Z postion for image MT.
> + * @x_map: Bitmap of active X positions for MT.
> + * @y_map: Bitmap of active Y positions for MT.
> + * @first_mp: Packet is the first of a multi-packet report.
> + * @is_mp: Packet is part of a multi-packet report.
> + * @btn: Button activity status
> + */
> +struct alps_fields {
> + unsigned int fingers;
> + struct alps_abs_data pt;
> + struct alps_abs_data pt_img[MAX_IMG_PT_NUM];
> + unsigned int x_map;
> + unsigned int y_map;
> + unsigned int first_mp:1;
> + unsigned int is_mp:1;
> + struct alps_btn btn;
Splitting button data and abs data into separate structures might make
sense, but please split off these changes into a separate patch so that
they are not obscuring changes necessary for v7 support.
> +};
> +
> +/**
> + * struct v7_raw - data decoded from raw packet for V7.
> + * @pkt_id: An id that specifies the type of packet.
> + * @additional_fingers: Number of additional finger that is neighter included
> + * in pt slot nor reflected in rest_left and rest_right flag of data packet.
> + * @rest_left: There are fingers on left resting zone.
> + * @rest_right: There are fingers on right resting zone.
> + * @raw_fn: The number of finger on touchpad.
> + */
> +struct v7_raw {
> + unsigned char pkt_id;
> + unsigned int additional_fingers;
> + unsigned char rest_left;
> + unsigned char rest_right;
> + unsigned char raw_fn;
> +};
> +
> +/**
> + * struct alps_bl_pt_attr - generic attributes of touch points for buttonless device
> + * @zone: The part of touchpad that the touch point locates
> + * @is_counted: The touch point is not a resting finger.
> + * @is_init_pt_got: The touch down point is got.
> + * @init_pt: The X Y Z position of the touch down point.
> + * @init_dead_pt: The touch down point of a finger used by dead zone process.
> + */
> +struct alps_bl_pt_attr {
> + unsigned char zone;
> + unsigned char is_counted;
> + unsigned char is_init_pt_got;
> + struct alps_abs_data init_pt;
> + struct alps_abs_data init_dead_pt;
> +};
> +
> +/**
> * struct alps_data - private data structure for the ALPS driver
> * @dev2: "Relative" device used to report trackstick or mouse activity.
> * @phys: Physical path for the relative device.
> @@ -116,8 +191,10 @@ struct alps_fields {
> * @flags: Additional device capabilities (passthrough port, trackstick, etc.).
> * @x_max: Largest possible X position value.
> * @y_max: Largest possible Y position value.
> + * @resting_zone_y_min: Smallest Y postion value of the bottom resting zone.
> * @x_bits: Number of X bits in the MT bitmap.
> * @y_bits: Number of Y bits in the MT bitmap.
> + * @img_fingers: Number of image fingers.
> * @hw_init: Protocol-specific hardware init function.
> * @process_packet: Protocol-specific function to process a report packet.
> * @decode_fields: Protocol-specific function to read packet bitfields.
> @@ -132,6 +209,11 @@ struct alps_fields {
> * @fingers: Number of fingers from last MT report.
> * @quirks: Bitmap of ALPS_QUIRK_*.
> * @timer: Timer for flushing out the final report packet in the stream.
> + * @v7: Data decoded from raw packet for V7
> + * @phy_btn: Physical button is active.
> + * @prev_phy_btn: Physical button of previous packet is active.
> + * @pressed_btn_bits: Pressed positon of button zone
> + * @pt_attr: Generic attributes of touch points for buttonless device.
> */
> struct alps_data {
> struct input_dev *dev2;
> @@ -145,8 +227,10 @@ struct alps_data {
> unsigned char flags;
> int x_max;
> int y_max;
> + int resting_zone_y_min;
> int x_bits;
> int y_bits;
> + unsigned char slot_number;
>
> int (*hw_init)(struct psmouse *psmouse);
> void (*process_packet)(struct psmouse *psmouse);
> @@ -161,6 +245,16 @@ struct alps_data {
> int fingers;
> u8 quirks;
> struct timer_list timer;
> +
> + /* these are used for buttonless touchpad*/
> + union {
> + struct v7_raw v7;
> + } r;
Why do you need a union here? A single-field union does not seem to be
terribly useful.
> + unsigned char phy_btn;
> + unsigned char prev_phy_btn;
> + unsigned char btn_delay_cnt;
> + unsigned char pressed_btn_bits;
> + struct alps_bl_pt_attr pt_attr[MAX_IMG_PT_NUM];
> };
>
> #define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /* trakcstick buttons in trackstick packet */
> --
> 1.8.3.2
>
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] input: add support for ALPS v7 protocol device
From: Peter Hutterer @ 2014-04-22 5:42 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Qiting Chen, cernekee, dturvene, linux-input, jclift, Qiting Chen,
Hans de Goede
In-Reply-To: <20140422052604.GB32749@core.coreip.homeip.net>
On Mon, Apr 21, 2014 at 10:26:04PM -0700, Dmitry Torokhov wrote:
> Hi Qiting,
>
> On Wed, Mar 19, 2014 at 04:55:53PM +0800, Qiting Chen wrote:
> > Here is a patch of supporting ALPS v7 protocol device.
> > ALPS v7 protocol device is a clickpad that is currently used on
> > Lenovo S430/S435/S530, Lenovo Z410/Z510, HP Odie, HP Revolve 810 G1,
> > as well as other machines with ALPS Touchpad of following infomation:
> > Device ID = 0x73, 0x03, 0x0a
> > Firmware ID = 0x88, 0xb*, 0x**
> >
> > A v7 protocol support patch is first relesed 2 months ago:
> > http://www.spinics.net/lists/linux-input/msg29084.html
> > After that some feedbacks were received from end user. Now this patch fixed the bugs
> > reported by them:
> > 1) Fix cursor jump when doing a right click drag
> > 2) Fix cursor jitter when button clicking
>
> My biggest question is whether the soft buttons should be processed in
> kernel driver or in userspace.
>
> Peter, don't Synaptics clickpads need similar functionality? I thought X
> driver already handles soft button areas... Am I mistaken?
yeah, we do handle those software button areas in the xorg synaptics driver
(and libinput very soon, for that matter). I'd prefer this to be handled in
userspace so we can get some sort of unified behaviour. There is one
touchpad series that handles it in firmware though (see Hans' recent patch
for the Cypress touchpads) but on the whole it's better to leave it to
userspace.
Cheers,
Peter
> > Signed-off-by: Qiting Chen <qiting.chen@cn.alps.com>
> > ---
> > drivers/input/mouse/alps.c | 560 ++++++++++++++++++++++++++++++++++++++++++---
> > drivers/input/mouse/alps.h | 132 +++++++++--
> > 2 files changed, 641 insertions(+), 51 deletions(-)
> >
> > diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
> > index fb15c64..383281f 100644
> > --- a/drivers/input/mouse/alps.c
> > +++ b/drivers/input/mouse/alps.c
> > @@ -32,6 +32,13 @@
> > #define ALPS_REG_BASE_RUSHMORE 0xc2c0
> > #define ALPS_REG_BASE_PINNACLE 0x0000
> >
> > +#define LEFT_BUTTON_BIT 0x01
> > +#define RIGHT_BUTTON_BIT 0x02
> > +
> > +#define V7_LARGE_MOVEMENT 130
> > +#define V7_DEAD_ZONE_OFFSET_X 72
> > +#define V7_DEAD_ZONE_OFFSET_Y 72
> > +
> > static const struct alps_nibble_commands alps_v3_nibble_commands[] = {
> > { PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */
> > { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
> > @@ -99,6 +106,7 @@ static const struct alps_nibble_commands alps_v6_nibble_commands[] = {
> > #define ALPS_FOUR_BUTTONS 0x40 /* 4 direction button present */
> > #define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet interleaved with
> > 6-byte ALPS packet */
> > +#define ALPS_BTNLESS 0x100 /* ALPS ClickPad flag */
> >
> > static const struct alps_model_info alps_model_data[] = {
> > { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
> > @@ -140,6 +148,20 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> > * isn't valid per PS/2 spec.
> > */
> >
> > +static unsigned int alps_pt_distance(struct alps_abs_data *pt0,
> > + struct alps_abs_data *pt1)
> > +{
> > + int vect_x, vect_y;
> > +
> > + if (!pt0 || !pt1)
> > + return 0;
>
> In which case can either of this pointers being NULL?
>
> > +
> > + vect_x = pt0->x - pt1->x;
> > + vect_y = pt0->y - pt1->y;
> > +
> > + return int_sqrt(vect_x * vect_x + vect_y * vect_y);
> > +}
> > +
> > /* Packet formats are described in Documentation/input/alps.txt */
> >
> > static bool alps_is_valid_first_byte(struct alps_data *priv,
> > @@ -320,8 +342,8 @@ static void alps_process_bitmap_dolphin(struct alps_data *priv,
> > end_bit = y_msb - 1;
> > box_middle_y = (priv->y_max * (start_bit + end_bit)) /
> > (2 * (priv->y_bits - 1));
> > - *x1 = fields->x;
> > - *y1 = fields->y;
> > + *x1 = fields->pt.x;
> > + *y1 = fields->pt.y;
> > *x2 = 2 * box_middle_x - *x1;
> > *y2 = 2 * box_middle_y - *y1;
> > }
> > @@ -461,6 +483,38 @@ static void alps_report_semi_mt_data(struct input_dev *dev, int num_fingers,
> > alps_set_slot(dev, 1, num_fingers == 2, x2, y2);
> > }
> >
> > +static void alps_report_coord_and_btn(struct psmouse *psmouse,
> > + struct alps_fields *f)
> > +{
> > + struct input_dev *dev;
> > +
> > + if (!psmouse || !f)
> > + return;
>
> Can either of these 2 pointers ever be NULL?
>
> > +
> > + dev = psmouse->dev;
> > +
> > + if (f->fingers) {
> > + input_report_key(dev, BTN_TOUCH, 1);
> > + alps_report_semi_mt_data(dev, f->fingers,
> > + f->pt_img[0].x, f->pt_img[0].y,
> > + f->pt_img[1].x, f->pt_img[1].y);
> > + input_mt_report_finger_count(dev, f->fingers);
> > +
> > + input_report_abs(dev, ABS_X, f->pt_img[0].x);
> > + input_report_abs(dev, ABS_Y, f->pt_img[0].y);
> > + input_report_abs(dev, ABS_PRESSURE, f->pt_img[0].z);
> > + } else {
> > + input_report_key(dev, BTN_TOUCH, 0);
> > + input_mt_report_finger_count(dev, 0);
> > + input_report_abs(dev, ABS_PRESSURE, 0);
> > + }
> > +
> > + input_report_key(dev, BTN_LEFT, f->btn.left);
> > + input_report_key(dev, BTN_RIGHT, f->btn.right);
> > +
> > + input_sync(dev);
> > +}
> > +
> > static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
> > {
> > struct alps_data *priv = psmouse->private;
> > @@ -523,13 +577,13 @@ static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
> >
> > static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p)
> > {
> > - f->left = !!(p[3] & 0x01);
> > - f->right = !!(p[3] & 0x02);
> > - f->middle = !!(p[3] & 0x04);
> > + f->btn.left = !!(p[3] & 0x01);
> > + f->btn.right = !!(p[3] & 0x02);
> > + f->btn.middle = !!(p[3] & 0x04);
> >
> > - f->ts_left = !!(p[3] & 0x10);
> > - f->ts_right = !!(p[3] & 0x20);
> > - f->ts_middle = !!(p[3] & 0x40);
> > + f->btn.ts_left = !!(p[3] & 0x10);
> > + f->btn.ts_right = !!(p[3] & 0x20);
> > + f->btn.ts_middle = !!(p[3] & 0x40);
> > }
> >
> > static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> > @@ -546,10 +600,10 @@ static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> > ((p[2] & 0x7f) << 1) |
> > (p[4] & 0x01);
> >
> > - f->x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> > + f->pt.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> > ((p[0] & 0x30) >> 4);
> > - f->y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> > - f->z = p[5] & 0x7f;
> > + f->pt.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> > + f->pt.z = p[5] & 0x7f;
> >
> > alps_decode_buttons_v3(f, p);
> > }
> > @@ -573,9 +627,9 @@ static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p,
> > f->is_mp = !!(p[0] & 0x20);
> >
> > if (!f->is_mp) {
> > - f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> > - f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> > - f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> > + f->pt.x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> > + f->pt.y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> > + f->pt.z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> > alps_decode_buttons_v3(f, p);
> > } else {
> > f->fingers = ((p[0] & 0x6) >> 1 |
> > @@ -687,7 +741,7 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> > * with x, y, and z all zero, so these seem to be flukes.
> > * Ignore them.
> > */
> > - if (f.x && f.y && !f.z)
> > + if (f.pt.x && f.pt.y && !f.pt.z)
> > return;
> >
> > /*
> > @@ -695,12 +749,12 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> > * to rely on ST data.
> > */
> > if (!fingers) {
> > - x1 = f.x;
> > - y1 = f.y;
> > - fingers = f.z > 0 ? 1 : 0;
> > + x1 = f.pt.x;
> > + y1 = f.pt.y;
> > + fingers = f.pt.z > 0 ? 1 : 0;
> > }
> >
> > - if (f.z >= 64)
> > + if (f.pt.z >= 64)
> > input_report_key(dev, BTN_TOUCH, 1);
> > else
> > input_report_key(dev, BTN_TOUCH, 0);
> > @@ -709,22 +763,22 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> >
> > input_mt_report_finger_count(dev, fingers);
> >
> > - input_report_key(dev, BTN_LEFT, f.left);
> > - input_report_key(dev, BTN_RIGHT, f.right);
> > - input_report_key(dev, BTN_MIDDLE, f.middle);
> > + input_report_key(dev, BTN_LEFT, f.btn.left);
> > + input_report_key(dev, BTN_RIGHT, f.btn.right);
> > + input_report_key(dev, BTN_MIDDLE, f.btn.middle);
> >
> > - if (f.z > 0) {
> > - input_report_abs(dev, ABS_X, f.x);
> > - input_report_abs(dev, ABS_Y, f.y);
> > + if (f.pt.z > 0) {
> > + input_report_abs(dev, ABS_X, f.pt.x);
> > + input_report_abs(dev, ABS_Y, f.pt.y);
> > }
> > - input_report_abs(dev, ABS_PRESSURE, f.z);
> > + input_report_abs(dev, ABS_PRESSURE, f.pt.z);
> >
> > input_sync(dev);
> >
> > if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
> > - input_report_key(dev2, BTN_LEFT, f.ts_left);
> > - input_report_key(dev2, BTN_RIGHT, f.ts_right);
> > - input_report_key(dev2, BTN_MIDDLE, f.ts_middle);
> > + input_report_key(dev2, BTN_LEFT, f.btn.ts_left);
> > + input_report_key(dev2, BTN_RIGHT, f.btn.ts_right);
> > + input_report_key(dev2, BTN_MIDDLE, f.btn.ts_middle);
> > input_sync(dev2);
> > }
> > }
> > @@ -916,6 +970,364 @@ static void alps_process_packet_v4(struct psmouse *psmouse)
> > input_sync(dev);
> > }
> >
> > +static bool alps_is_valid_package_v7(struct psmouse *psmouse)
> > +{
> > + if ((psmouse->pktcnt == 3) && ((psmouse->packet[2] & 0x40) != 0x40))
> > + return false;
> > + if ((psmouse->pktcnt == 4) && ((psmouse->packet[3] & 0x48) != 0x48))
> > + return false;
> > + if ((psmouse->pktcnt == 6) && ((psmouse->packet[5] & 0x40) != 0x0))
> > + return false;
>
> Maybe do:
>
> switch (psmouse->pktcnt) {
> case 3:
> if ((psmouse->packet[2] & 0x40) != 0x40)
> return false;
> break;
> ...
> }
>
> > + return true;
> > +}
> > +
> > +static int alps_drop_unsupported_packet_v7(struct psmouse *psmouse)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + int drop = 1;
>
> bool drop = true;
>
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> > + drop = 0;
>
> if (... ||
> ...) {
> drop = false;
> }
> > +
> > + return drop;
> > +}
> > +
> > +static unsigned char alps_get_packet_id_v7(char *byte)
> > +{
> > + unsigned char packet_id;
> > +
> > + if (byte[4] & 0x40)
> > + packet_id = V7_PACKET_ID_TWO;
> > + else if (byte[4] & 0x01)
> > + packet_id = V7_PACKET_ID_MULTI;
> > + else if ((byte[0] & 0x10) && !(byte[4] & 0x43))
> > + packet_id = V7_PACKET_ID_NEW;
> > + else
> > + packet_id = V7_PACKET_ID_IDLE;
> > +
> > + return packet_id;
> > +}
> > +
> > +static void alps_get_finger_coordinate_v7(struct alps_abs_data *pt,
> > + unsigned char *pkt,
> > + unsigned char pkt_id)
> > +{
> > + if ((pkt_id == V7_PACKET_ID_TWO) ||
> > + (pkt_id == V7_PACKET_ID_MULTI) ||
> > + (pkt_id == V7_PACKET_ID_NEW)) {
> > + pt[0].x = ((pkt[2] & 0x80) << 4);
> > + pt[0].x |= ((pkt[2] & 0x3F) << 5);
> > + pt[0].x |= ((pkt[3] & 0x30) >> 1);
> > + pt[0].x |= (pkt[3] & 0x07);
> > + pt[0].y = (pkt[1] << 3) | (pkt[0] & 0x07);
> > +
> > + pt[1].x = ((pkt[3] & 0x80) << 4);
> > + pt[1].x |= ((pkt[4] & 0x80) << 3);
> > + pt[1].x |= ((pkt[4] & 0x3F) << 4);
> > + pt[1].y = ((pkt[5] & 0x80) << 3);
> > + pt[1].y |= ((pkt[5] & 0x3F) << 4);
> > +
> > + if (pkt_id == V7_PACKET_ID_TWO) {
> > + pt[1].x &= ~0x000F;
> > + pt[1].y |= 0x000F;
> > + } else if (pkt_id == V7_PACKET_ID_MULTI) {
> > + pt[1].x &= ~0x003F;
> > + pt[1].y &= ~0x0020;
> > + pt[1].y |= ((pkt[4] & 0x02) << 4);
> > + pt[1].y |= 0x001F;
> > + } else if (pkt_id == V7_PACKET_ID_NEW) {
> > + pt[1].x &= ~0x003F;
> > + pt[1].x |= (pkt[0] & 0x20);
> > + pt[1].y |= 0x000F;
> > + }
> > +
> > + pt[0].y = 0x7FF - pt[0].y;
> > + pt[1].y = 0x7FF - pt[1].y;
> > +
> > + pt[0].z = (pt[0].x && pt[0].y) ? 62 : 0;
> > + pt[1].z = (pt[1].x && pt[1].y) ? 62 : 0;
> > + }
> > +}
> > +
> > +static void alps_decode_packet_v7(struct alps_fields *f,
> > + unsigned char *p,
> > + struct psmouse *psmouse)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + static struct v7_raw prev_r;
> > +
> > + priv->r.v7.pkt_id = alps_get_packet_id_v7(p);
> > +
> > + alps_get_finger_coordinate_v7(f->pt_img, p, priv->r.v7.pkt_id);
> > +
> > + priv->r.v7.rest_left = 0;
> > + priv->r.v7.rest_right = 0;
> > + priv->r.v7.additional_fingers = 0;
> > + priv->phy_btn = 0;
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI) {
> > + priv->r.v7.rest_left = (p[0] & 0x10) >> 4;
> > + priv->r.v7.rest_right = (p[0] & 0x20) >> 5;
> > + }
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> > + priv->r.v7.additional_fingers = p[5] & 0x03;
> > +
> > + priv->phy_btn = (p[0] & 0x80) >> 7;
> > +
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO) {
> > + if (f->pt_img[0].z != 0 && f->pt_img[1].z != 0)
> > + priv->r.v7.raw_fn = 2;
> > + else
> > + priv->r.v7.raw_fn = 1;
> > + } else if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> > + priv->r.v7.raw_fn = 3 + priv->r.v7.additional_fingers;
> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> > + priv->r.v7.raw_fn = 0;
> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW)
> > + priv->r.v7.raw_fn = prev_r.raw_fn;
> > +
> > + /* It is a trick to bypass firmware bug of older version
> > + that 'New' Packet is missed when finger number changed.
> > + We fake a 'New' Packet in such cases.*/
>
> Multi-line comments should be formatted as follows:
>
> /*
> * This is a multi
> * line comment.
> */
>
>
> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE) {
> > + if (priv->r.v7.raw_fn != prev_r.raw_fn)
> > + priv->r.v7.pkt_id = V7_PACKET_ID_NEW;
> > + }
> > +
> > + memcpy(&prev_r, &priv->r.v7, sizeof(struct v7_raw));
> > +}
> > +
> > +static void alps_set_each_pt_attr_v7(struct psmouse *psmouse,
> > + struct alps_abs_data *pt,
> > + struct alps_bl_pt_attr *pt_attr)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + unsigned int dist;
> > +
> > + if (!pt_attr->is_init_pt_got && pt->z != 0) {
> > + pt_attr->is_init_pt_got = 1;
> > + pt_attr->is_counted = 0;
> > + memcpy(&pt_attr->init_pt, pt, sizeof(pt_attr->init_pt));
> > + }
> > +
> > + if (pt->z != 0) {
> > + if (pt->y < priv->resting_zone_y_min) {
> > + /* A finger is recognized as a non-resting finger
> > + if it's position is outside the resting finger zone.*/
> > + pt_attr->zone = ZONE_NORMAL;
> > + pt_attr->is_counted = 1;
> > + } else {
> > + /* A finger is recognized as a resting finger if it's
> > + position is inside the resting finger zone and there's
> > + no large movement from it's touch down position.*/
> > + pt_attr->zone = ZONE_RESTING;
> > +
> > + if (pt->x > priv->x_max / 2)
> > + pt_attr->zone |= ZONE_RIGHT_BTN;
> > + else
> > + pt_attr->zone |= ZONE_LEFT_BTN;
> > +
> > + /* A resting finger will turn to be a non-resting
> > + finger if it has made large movement from it's touch
> > + down position. A non-resting finger will never turn
> > + to a resting finger before it leaves the touchpad
> > + surface.*/
> > + if (pt_attr->is_init_pt_got) {
> > + dist = alps_pt_distance(pt, &pt_attr->init_pt);
> > +
> > + if (dist > V7_LARGE_MOVEMENT)
> > + pt_attr->is_counted = 1;
> > + }
> > + }
> > + }
> > +}
> > +
> > +static void alps_set_pt_attr_v7(struct psmouse *psmouse,
> > + struct alps_fields *f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + int i;
> > +
> > + switch (priv->r.v7.pkt_id) {
> > + case V7_PACKET_ID_TWO:
> > + case V7_PACKET_ID_MULTI:
> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> > + alps_set_each_pt_attr_v7(psmouse,
> > + &f->pt_img[i],
> > + &priv->pt_attr[i]);
> > + }
> > + break;
> > + default:
> > + /*All finger attributes are cleared when packet ID is
> > + 'IDLE', 'New'or other unknown IDs. An 'IDLE' packet
> > + indicates that there's no finger and no button activity.
> > + A 'NEW' packet indicates the finger position in packet
> > + is not continues from previous packet. Such as the
> > + condition there's finger placed or lifted. In these cases,
> > + finger attributes will be reset.*/
> > + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> > + break;
> > + }
> > +}
> > +
> > +static void alps_cal_output_finger_num_v7(struct psmouse *psmouse,
> > + struct alps_fields *f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + unsigned int fn = 0;
> > + int i;
> > +
> > + switch (priv->r.v7.pkt_id) {
> > + case V7_PACKET_ID_IDLE:
> > + case V7_PACKET_ID_NEW:
> > + /*No finger is reported when packet ID is 'IDLE' or 'New'.
> > + An 'IDLE' packet indicates that there's no finger on touchpad.
> > + A 'NEW' packet indicates there's finger placed or lifted.
> > + Finger position of 'New' packet is not continues from the
> > + previous packet.*/
> > + fn = 0;
> > + break;
> > + case V7_PACKET_ID_TWO:
> > + if (f->pt_img[0].z == 0) {
> > + /*The first finger slot is zero when a non-resting
> > + finger lifted and remaining only one resting finger
> > + on touchpad. Hardware report the remaining resting
> > + finger in second slot. This resting is ignored*/
> > + fn = 0;
> > + } else if (f->pt_img[1].z == 0) {
> > + /* The second finger slot is zero if there's
> > + only one finger*/
> > + fn = 1;
> > + } else {
> > + /*All non-resting fingers will be counted to report*/
> > + fn = 0;
> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> > + if (priv->pt_attr[i].is_counted)
> > + fn++;
> > + }
> > +
> > + /*In the case that both fingers are
> > + resting fingers, report the first one*/
> > + if (!priv->pt_attr[0].is_counted &&
> > + !priv->pt_attr[1].is_counted) {
> > + fn = 1;
> > + }
> > + }
> > + break;
> > + case V7_PACKET_ID_MULTI:
> > + /*A packet ID 'MULTI' indicats that at least 3 non-resting
> > + finger exist.*/
> > + fn = 3 + priv->r.v7.additional_fingers;
> > + break;
> > + }
> > +
> > + f->fingers = fn;
> > +}
> > +
> > +static void alps_button_dead_zone_filter(struct psmouse *psmouse,
> > + struct alps_fields *f,
> > + struct alps_fields *prev_f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + int dx, dy;
> > +
> > + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0) {
> > + memcpy(&priv->pt_attr[0].init_dead_pt,
> > + &f->pt_img[0],
> > + sizeof(struct alps_abs_data));
> > + }
> > +
> > + if (priv->pt_attr[0].init_dead_pt.x != 0 &&
> > + priv->pt_attr[0].init_dead_pt.x != 0) {
> > + dx = f->pt_img[0].x - priv->pt_attr[0].init_dead_pt.x;
> > + dy = f->pt_img[0].y - priv->pt_attr[0].init_dead_pt.y;
> > + if ((abs(dx) > V7_DEAD_ZONE_OFFSET_X) ||
> > + (abs(dy) > V7_DEAD_ZONE_OFFSET_Y)) {
> > + memset(&priv->pt_attr[0].init_dead_pt, 0,
> > + sizeof(struct alps_abs_data));
> > + priv->btn_delay_cnt = 0;
> > + } else {
> > + memcpy(&f->pt_img[0],
> > + &prev_f->pt_img[0],
> > + sizeof(struct alps_abs_data));
> > + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0)
> > + priv->btn_delay_cnt = 2;
> > + }
> > + }
> > +
> > + if (priv->btn_delay_cnt > 0) {
> > + f->btn.left = 0;
> > + f->btn.right = 0;
> > + priv->btn_delay_cnt--;
> > + }
> > +}
> > +
> > +static void alps_assign_buttons_v7(struct psmouse *psmouse,
> > + struct alps_fields *f,
> > + struct alps_fields *prev_f)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > +
> > + if (priv->phy_btn) {
> > + if (!priv->prev_phy_btn) {
> > + /* Report a right click as long as there's finger on
> > + right button zone. Othrewise, report a left click.*/
> > + if (priv->r.v7.rest_right ||
> > + priv->pt_attr[0].zone & ZONE_RIGHT_BTN ||
> > + priv->pt_attr[1].zone & ZONE_RIGHT_BTN) {
> > + f->btn.right = 1;
> > + priv->pressed_btn_bits |= RIGHT_BUTTON_BIT;
> > + } else {
> > + f->btn.left = 1;
> > + priv->pressed_btn_bits |= LEFT_BUTTON_BIT;
> > + }
> > + } else {
> > + if (priv->pressed_btn_bits & RIGHT_BUTTON_BIT)
> > + f->btn.right = 1;
> > + if (priv->pressed_btn_bits & LEFT_BUTTON_BIT)
> > + f->btn.left = 1;
> > + }
> > + } else {
> > + priv->pressed_btn_bits = 0;
> > + f->btn.right = 0;
> > + f->btn.left = 0;
> > + }
> > +
> > + alps_button_dead_zone_filter(psmouse, f, prev_f);
> > +
> > + priv->prev_phy_btn = priv->phy_btn;
> > +}
> > +
> > +static void alps_process_packet_v7(struct psmouse *psmouse)
> > +{
> > + struct alps_data *priv = psmouse->private;
> > + struct alps_fields f = {0};
> > + static struct alps_fields prev_f;
> > + unsigned char *packet = psmouse->packet;
> > +
> > + priv->decode_fields(&f, packet, psmouse);
> > +
> > + if (alps_drop_unsupported_packet_v7(psmouse))
> > + return;
> > +
> > + alps_set_pt_attr_v7(psmouse, &f);
> > +
> > + alps_cal_output_finger_num_v7(psmouse, &f);
> > +
> > + alps_assign_buttons_v7(psmouse, &f, &prev_f);
> > +
> > + alps_report_coord_and_btn(psmouse, &f);
> > +
> > + memcpy(&prev_f, &f, sizeof(struct alps_fields));
> > +}
> > +
> > static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
> > unsigned char packet[],
> > bool report_buttons)
> > @@ -1080,6 +1492,14 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
> > return PSMOUSE_BAD_DATA;
> > }
> >
> > + if ((priv->proto_version == ALPS_PROTO_V7 &&
> > + !alps_is_valid_package_v7(psmouse))) {
> > + psmouse_dbg(psmouse, "refusing packet[%i] = %x\n",
> > + psmouse->pktcnt - 1,
> > + psmouse->packet[psmouse->pktcnt - 1]);
> > + return PSMOUSE_BAD_DATA;
> > + }
> > +
> > if (psmouse->pktcnt == psmouse->pktsize) {
> > priv->process_packet(psmouse);
> > return PSMOUSE_FULL_PACKET;
> > @@ -1192,6 +1612,22 @@ static int alps_rpt_cmd(struct psmouse *psmouse, int init_command,
> > return 0;
> > }
> >
> > +static int alps_check_valid_firmware_id(unsigned char id[])
> > +{
> > + int valid = 1;
>
> bool valid = true;
>
> > +
> > + if (id[0] == 0x73)
> > + valid = 1;
> > + else if (id[0] == 0x88) {
> > + if ((id[1] == 0x07) ||
> > + (id[1] == 0x08) ||
> > + ((id[1] & 0xf0) == 0xB0))
> > + valid = 1;
> > + }
> > +
> > + return valid;
> > +}
> > +
> > static int alps_enter_command_mode(struct psmouse *psmouse)
> > {
> > unsigned char param[4];
> > @@ -1201,8 +1637,7 @@ static int alps_enter_command_mode(struct psmouse *psmouse)
> > return -1;
> > }
> >
> > - if ((param[0] != 0x88 || (param[1] != 0x07 && param[1] != 0x08)) &&
> > - param[0] != 0x73) {
> > + if (!alps_check_valid_firmware_id(param)) {
> > psmouse_dbg(psmouse,
> > "unknown response while entering command mode\n");
> > return -1;
> > @@ -1704,6 +2139,36 @@ error:
> > return ret;
> > }
> >
> > +static int alps_hw_init_v7(struct psmouse *psmouse)
> > +{
> > + struct ps2dev *ps2dev = &psmouse->ps2dev;
> > + int reg_val, ret = -1;
> > +
> > + if (alps_enter_command_mode(psmouse))
> > + goto error;
> > +
> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2d9);
> > + if (reg_val == -1)
> > + goto error;
> > +
> > + if (alps_command_mode_write_reg(psmouse, 0xc2c9, 0x64))
> > + goto error;
> > +
> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4);
> > + if (reg_val == -1)
> > + goto error;
> > +
> > + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x02))
> > + goto error;
> > +
> > + alps_exit_command_mode(psmouse);
> > + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
> > +
> > +error:
> > + alps_exit_command_mode(psmouse);
> > + return ret;
> > +}
> > +
> > /* Must be in command mode when calling this function */
> > static int alps_absolute_mode_v4(struct psmouse *psmouse)
> > {
> > @@ -1875,6 +2340,7 @@ static void alps_set_defaults(struct alps_data *priv)
> > priv->set_abs_params = alps_set_abs_params_st;
> > priv->x_max = 1023;
> > priv->y_max = 767;
> > + priv->slot_number = 1;
> > break;
> > case ALPS_PROTO_V3:
> > priv->hw_init = alps_hw_init_v3;
> > @@ -1883,6 +2349,7 @@ static void alps_set_defaults(struct alps_data *priv)
> > priv->decode_fields = alps_decode_pinnacle;
> > priv->nibble_commands = alps_v3_nibble_commands;
> > priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> > + priv->slot_number = 2;
> > break;
> > case ALPS_PROTO_V4:
> > priv->hw_init = alps_hw_init_v4;
> > @@ -1890,6 +2357,7 @@ static void alps_set_defaults(struct alps_data *priv)
> > priv->set_abs_params = alps_set_abs_params_mt;
> > priv->nibble_commands = alps_v4_nibble_commands;
> > priv->addr_command = PSMOUSE_CMD_DISABLE;
> > + priv->slot_number = 2;
> > break;
> > case ALPS_PROTO_V5:
> > priv->hw_init = alps_hw_init_dolphin_v1;
> > @@ -1905,6 +2373,7 @@ static void alps_set_defaults(struct alps_data *priv)
> > priv->y_max = 660;
> > priv->x_bits = 23;
> > priv->y_bits = 12;
> > + priv->slot_number = 2;
> > break;
> > case ALPS_PROTO_V6:
> > priv->hw_init = alps_hw_init_v6;
> > @@ -1913,6 +2382,28 @@ static void alps_set_defaults(struct alps_data *priv)
> > priv->nibble_commands = alps_v6_nibble_commands;
> > priv->x_max = 2047;
> > priv->y_max = 1535;
> > + priv->slot_number = 2;
> > + break;
> > + case ALPS_PROTO_V7:
> > + priv->hw_init = alps_hw_init_v7;
> > + priv->process_packet = alps_process_packet_v7;
> > + priv->decode_fields = alps_decode_packet_v7;
> > + priv->set_abs_params = alps_set_abs_params_mt;
> > + priv->nibble_commands = alps_v3_nibble_commands;
> > + priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> > + priv->x_max = 0xfff;
> > + priv->y_max = 0x7ff;
> > + priv->resting_zone_y_min = 0x654;
> > + priv->byte0 = 0x48;
> > + priv->mask0 = 0x48;
> > + priv->flags = 0;
> > + priv->slot_number = 2;
> > +
> > + priv->phy_btn = 0;
> > + priv->prev_phy_btn = 0;
> > + priv->btn_delay_cnt = 0;
> > + priv->pressed_btn_bits = 0;
> > + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> > break;
> > }
> > }
> > @@ -1982,6 +2473,11 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)
> > return -EIO;
> > else
> > return 0;
> > + } else if (ec[0] == 0x88 && (ec[1] & 0xf0) == 0xB0) {
> > + priv->proto_version = ALPS_PROTO_V7;
> > + alps_set_defaults(priv);
> > +
> > + return 0;
> > } else if (ec[0] == 0x88 && ec[1] == 0x08) {
> > priv->proto_version = ALPS_PROTO_V3;
> > alps_set_defaults(priv);
> > @@ -2045,7 +2541,7 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> > struct input_dev *dev1)
> > {
> > set_bit(INPUT_PROP_SEMI_MT, dev1->propbit);
> > - input_mt_init_slots(dev1, 2, 0);
> > + input_mt_init_slots(dev1, priv->slot_number, 0);
> > input_set_abs_params(dev1, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0);
> > input_set_abs_params(dev1, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0);
> >
> > diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h
> > index 03f88b6..dedbd27 100644
> > --- a/drivers/input/mouse/alps.h
> > +++ b/drivers/input/mouse/alps.h
> > @@ -18,11 +18,36 @@
> > #define ALPS_PROTO_V4 4
> > #define ALPS_PROTO_V5 5
> > #define ALPS_PROTO_V6 6
> > +#define ALPS_PROTO_V7 7
> > +
> > +#define MAX_IMG_PT_NUM 5
> > +#define V7_IMG_PT_NUM 2
> > +
> > +#define ZONE_NORMAL 0x01
> > +#define ZONE_RESTING 0x02
> > +#define ZONE_LEFT_BTN 0x04
> > +#define ZONE_RIGHT_BTN 0x08
> >
> > #define DOLPHIN_COUNT_PER_ELECTRODE 64
> > #define DOLPHIN_PROFILE_XOFFSET 8 /* x-electrode offset */
> > #define DOLPHIN_PROFILE_YOFFSET 1 /* y-electrode offset */
> >
> > +/*
> > + * enum V7_PACKET_ID - defines the packet type for V7
> > + * V7_PACKET_ID_IDLE: There's no finger and no button activity.
> > + * V7_PACKET_ID_TWO: There's one or two non-resting fingers on touchpad
> > + * or there's button activities.
> > + * V7_PACKET_ID_MULTI: There are at least three non-resting fingers.
> > + * V7_PACKET_ID_NEW: The finger position in slot is not continues from
> > + * previous packet.
> > +*/
> > +enum V7_PACKET_ID {
> > + V7_PACKET_ID_IDLE,
> > + V7_PACKET_ID_TWO,
> > + V7_PACKET_ID_MULTI,
> > + V7_PACKET_ID_NEW,
> > +};
> > +
> > /**
> > * struct alps_model_info - touchpad ID table
> > * @signature: E7 response string to match.
> > @@ -66,15 +91,7 @@ struct alps_nibble_commands {
> > };
> >
> > /**
> > - * struct alps_fields - decoded version of the report packet
> > - * @x_map: Bitmap of active X positions for MT.
> > - * @y_map: Bitmap of active Y positions for MT.
> > - * @fingers: Number of fingers for MT.
> > - * @x: X position for ST.
> > - * @y: Y position for ST.
> > - * @z: Z position for ST.
> > - * @first_mp: Packet is the first of a multi-packet report.
> > - * @is_mp: Packet is part of a multi-packet report.
> > + * struct alps_btn - decoded version of the button status
> > * @left: Left touchpad button is active.
> > * @right: Right touchpad button is active.
> > * @middle: Middle touchpad button is active.
> > @@ -82,16 +99,7 @@ struct alps_nibble_commands {
> > * @ts_right: Right trackstick button is active.
> > * @ts_middle: Middle trackstick button is active.
> > */
> > -struct alps_fields {
> > - unsigned int x_map;
> > - unsigned int y_map;
> > - unsigned int fingers;
> > - unsigned int x;
> > - unsigned int y;
> > - unsigned int z;
> > - unsigned int first_mp:1;
> > - unsigned int is_mp:1;
> > -
> > +struct alps_btn {
> > unsigned int left:1;
> > unsigned int right:1;
> > unsigned int middle:1;
> > @@ -102,6 +110,73 @@ struct alps_fields {
> > };
> >
> > /**
> > + * struct alps_btn - decoded version of the X Y Z postion for ST.
> > + * @x: X position for ST.
> > + * @y: Y position for ST.
> > + * @z: Z position for ST.
> > + */
> > +struct alps_abs_data {
> > + unsigned int x;
> > + unsigned int y;
> > + unsigned int z;
> > +};
> > +
> > +/**
> > + * struct alps_fields - decoded version of the report packet
> > + * @fingers: Number of fingers for MT.
> > + * @pt: X Y Z postion for ST.
> > + * @pt: X Y Z postion for image MT.
> > + * @x_map: Bitmap of active X positions for MT.
> > + * @y_map: Bitmap of active Y positions for MT.
> > + * @first_mp: Packet is the first of a multi-packet report.
> > + * @is_mp: Packet is part of a multi-packet report.
> > + * @btn: Button activity status
> > + */
> > +struct alps_fields {
> > + unsigned int fingers;
> > + struct alps_abs_data pt;
> > + struct alps_abs_data pt_img[MAX_IMG_PT_NUM];
> > + unsigned int x_map;
> > + unsigned int y_map;
> > + unsigned int first_mp:1;
> > + unsigned int is_mp:1;
> > + struct alps_btn btn;
>
> Splitting button data and abs data into separate structures might make
> sense, but please split off these changes into a separate patch so that
> they are not obscuring changes necessary for v7 support.
>
> > +};
> > +
> > +/**
> > + * struct v7_raw - data decoded from raw packet for V7.
> > + * @pkt_id: An id that specifies the type of packet.
> > + * @additional_fingers: Number of additional finger that is neighter included
> > + * in pt slot nor reflected in rest_left and rest_right flag of data packet.
> > + * @rest_left: There are fingers on left resting zone.
> > + * @rest_right: There are fingers on right resting zone.
> > + * @raw_fn: The number of finger on touchpad.
> > + */
> > +struct v7_raw {
> > + unsigned char pkt_id;
> > + unsigned int additional_fingers;
> > + unsigned char rest_left;
> > + unsigned char rest_right;
> > + unsigned char raw_fn;
> > +};
> > +
> > +/**
> > + * struct alps_bl_pt_attr - generic attributes of touch points for buttonless device
> > + * @zone: The part of touchpad that the touch point locates
> > + * @is_counted: The touch point is not a resting finger.
> > + * @is_init_pt_got: The touch down point is got.
> > + * @init_pt: The X Y Z position of the touch down point.
> > + * @init_dead_pt: The touch down point of a finger used by dead zone process.
> > + */
> > +struct alps_bl_pt_attr {
> > + unsigned char zone;
> > + unsigned char is_counted;
> > + unsigned char is_init_pt_got;
> > + struct alps_abs_data init_pt;
> > + struct alps_abs_data init_dead_pt;
> > +};
> > +
> > +/**
> > * struct alps_data - private data structure for the ALPS driver
> > * @dev2: "Relative" device used to report trackstick or mouse activity.
> > * @phys: Physical path for the relative device.
> > @@ -116,8 +191,10 @@ struct alps_fields {
> > * @flags: Additional device capabilities (passthrough port, trackstick, etc.).
> > * @x_max: Largest possible X position value.
> > * @y_max: Largest possible Y position value.
> > + * @resting_zone_y_min: Smallest Y postion value of the bottom resting zone.
> > * @x_bits: Number of X bits in the MT bitmap.
> > * @y_bits: Number of Y bits in the MT bitmap.
> > + * @img_fingers: Number of image fingers.
> > * @hw_init: Protocol-specific hardware init function.
> > * @process_packet: Protocol-specific function to process a report packet.
> > * @decode_fields: Protocol-specific function to read packet bitfields.
> > @@ -132,6 +209,11 @@ struct alps_fields {
> > * @fingers: Number of fingers from last MT report.
> > * @quirks: Bitmap of ALPS_QUIRK_*.
> > * @timer: Timer for flushing out the final report packet in the stream.
> > + * @v7: Data decoded from raw packet for V7
> > + * @phy_btn: Physical button is active.
> > + * @prev_phy_btn: Physical button of previous packet is active.
> > + * @pressed_btn_bits: Pressed positon of button zone
> > + * @pt_attr: Generic attributes of touch points for buttonless device.
> > */
> > struct alps_data {
> > struct input_dev *dev2;
> > @@ -145,8 +227,10 @@ struct alps_data {
> > unsigned char flags;
> > int x_max;
> > int y_max;
> > + int resting_zone_y_min;
> > int x_bits;
> > int y_bits;
> > + unsigned char slot_number;
> >
> > int (*hw_init)(struct psmouse *psmouse);
> > void (*process_packet)(struct psmouse *psmouse);
> > @@ -161,6 +245,16 @@ struct alps_data {
> > int fingers;
> > u8 quirks;
> > struct timer_list timer;
> > +
> > + /* these are used for buttonless touchpad*/
> > + union {
> > + struct v7_raw v7;
> > + } r;
>
> Why do you need a union here? A single-field union does not seem to be
> terribly useful.
>
>
> > + unsigned char phy_btn;
> > + unsigned char prev_phy_btn;
> > + unsigned char btn_delay_cnt;
> > + unsigned char pressed_btn_bits;
> > + struct alps_bl_pt_attr pt_attr[MAX_IMG_PT_NUM];
> > };
> >
> > #define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /* trakcstick buttons in trackstick packet */
> > --
> > 1.8.3.2
> >
>
> Thanks.
>
> --
> Dmitry
^ permalink raw reply
* Re: [PATCH] input: add support for ALPS v7 protocol device
From: Elaine Chen @ 2014-04-22 6:11 UTC (permalink / raw)
To: Peter Hutterer
Cc: Dmitry Torokhov, Kevin Cernekee, david turvene, linux-input,
Justin Clift, Qiting Chen, Hans de Goede
In-Reply-To: <20140422054244.GB21023@yabbi.redhat.com>
Hi Peter,
Thank you! I know there's a flag to register our device as clickpad to
X system. But it seems the clickpad attribution only
deal with soft button. It won't handle "Resting Finger" function,
isn't it? (Resting Finger function: place one or more fingers still in
soft button zone, these fingers
won't affact other finger's cursoring and gestures.) If the X system
doesn't support this function, our driver has to implement it in
kernel. This is why we didn't use
the clickpad flag. If I'm wrong, please tell me.
Thanks.
2014-04-22 13:42 GMT+08:00 Peter Hutterer <peter.hutterer@who-t.net>:
> On Mon, Apr 21, 2014 at 10:26:04PM -0700, Dmitry Torokhov wrote:
>> Hi Qiting,
>>
>> On Wed, Mar 19, 2014 at 04:55:53PM +0800, Qiting Chen wrote:
>> > Here is a patch of supporting ALPS v7 protocol device.
>> > ALPS v7 protocol device is a clickpad that is currently used on
>> > Lenovo S430/S435/S530, Lenovo Z410/Z510, HP Odie, HP Revolve 810 G1,
>> > as well as other machines with ALPS Touchpad of following infomation:
>> > Device ID = 0x73, 0x03, 0x0a
>> > Firmware ID = 0x88, 0xb*, 0x**
>> >
>> > A v7 protocol support patch is first relesed 2 months ago:
>> > http://www.spinics.net/lists/linux-input/msg29084.html
>> > After that some feedbacks were received from end user. Now this patch fixed the bugs
>> > reported by them:
>> > 1) Fix cursor jump when doing a right click drag
>> > 2) Fix cursor jitter when button clicking
>>
>> My biggest question is whether the soft buttons should be processed in
>> kernel driver or in userspace.
>>
>> Peter, don't Synaptics clickpads need similar functionality? I thought X
>> driver already handles soft button areas... Am I mistaken?
>
> yeah, we do handle those software button areas in the xorg synaptics driver
> (and libinput very soon, for that matter). I'd prefer this to be handled in
> userspace so we can get some sort of unified behaviour. There is one
> touchpad series that handles it in firmware though (see Hans' recent patch
> for the Cypress touchpads) but on the whole it's better to leave it to
> userspace.
>
> Cheers,
> Peter
>
>> > Signed-off-by: Qiting Chen <qiting.chen@cn.alps.com>
>> > ---
>> > drivers/input/mouse/alps.c | 560 ++++++++++++++++++++++++++++++++++++++++++---
>> > drivers/input/mouse/alps.h | 132 +++++++++--
>> > 2 files changed, 641 insertions(+), 51 deletions(-)
>> >
>> > diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
>> > index fb15c64..383281f 100644
>> > --- a/drivers/input/mouse/alps.c
>> > +++ b/drivers/input/mouse/alps.c
>> > @@ -32,6 +32,13 @@
>> > #define ALPS_REG_BASE_RUSHMORE 0xc2c0
>> > #define ALPS_REG_BASE_PINNACLE 0x0000
>> >
>> > +#define LEFT_BUTTON_BIT 0x01
>> > +#define RIGHT_BUTTON_BIT 0x02
>> > +
>> > +#define V7_LARGE_MOVEMENT 130
>> > +#define V7_DEAD_ZONE_OFFSET_X 72
>> > +#define V7_DEAD_ZONE_OFFSET_Y 72
>> > +
>> > static const struct alps_nibble_commands alps_v3_nibble_commands[] = {
>> > { PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */
>> > { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
>> > @@ -99,6 +106,7 @@ static const struct alps_nibble_commands alps_v6_nibble_commands[] = {
>> > #define ALPS_FOUR_BUTTONS 0x40 /* 4 direction button present */
>> > #define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet interleaved with
>> > 6-byte ALPS packet */
>> > +#define ALPS_BTNLESS 0x100 /* ALPS ClickPad flag */
>> >
>> > static const struct alps_model_info alps_model_data[] = {
>> > { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
>> > @@ -140,6 +148,20 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
>> > * isn't valid per PS/2 spec.
>> > */
>> >
>> > +static unsigned int alps_pt_distance(struct alps_abs_data *pt0,
>> > + struct alps_abs_data *pt1)
>> > +{
>> > + int vect_x, vect_y;
>> > +
>> > + if (!pt0 || !pt1)
>> > + return 0;
>>
>> In which case can either of this pointers being NULL?
>>
>> > +
>> > + vect_x = pt0->x - pt1->x;
>> > + vect_y = pt0->y - pt1->y;
>> > +
>> > + return int_sqrt(vect_x * vect_x + vect_y * vect_y);
>> > +}
>> > +
>> > /* Packet formats are described in Documentation/input/alps.txt */
>> >
>> > static bool alps_is_valid_first_byte(struct alps_data *priv,
>> > @@ -320,8 +342,8 @@ static void alps_process_bitmap_dolphin(struct alps_data *priv,
>> > end_bit = y_msb - 1;
>> > box_middle_y = (priv->y_max * (start_bit + end_bit)) /
>> > (2 * (priv->y_bits - 1));
>> > - *x1 = fields->x;
>> > - *y1 = fields->y;
>> > + *x1 = fields->pt.x;
>> > + *y1 = fields->pt.y;
>> > *x2 = 2 * box_middle_x - *x1;
>> > *y2 = 2 * box_middle_y - *y1;
>> > }
>> > @@ -461,6 +483,38 @@ static void alps_report_semi_mt_data(struct input_dev *dev, int num_fingers,
>> > alps_set_slot(dev, 1, num_fingers == 2, x2, y2);
>> > }
>> >
>> > +static void alps_report_coord_and_btn(struct psmouse *psmouse,
>> > + struct alps_fields *f)
>> > +{
>> > + struct input_dev *dev;
>> > +
>> > + if (!psmouse || !f)
>> > + return;
>>
>> Can either of these 2 pointers ever be NULL?
>>
>> > +
>> > + dev = psmouse->dev;
>> > +
>> > + if (f->fingers) {
>> > + input_report_key(dev, BTN_TOUCH, 1);
>> > + alps_report_semi_mt_data(dev, f->fingers,
>> > + f->pt_img[0].x, f->pt_img[0].y,
>> > + f->pt_img[1].x, f->pt_img[1].y);
>> > + input_mt_report_finger_count(dev, f->fingers);
>> > +
>> > + input_report_abs(dev, ABS_X, f->pt_img[0].x);
>> > + input_report_abs(dev, ABS_Y, f->pt_img[0].y);
>> > + input_report_abs(dev, ABS_PRESSURE, f->pt_img[0].z);
>> > + } else {
>> > + input_report_key(dev, BTN_TOUCH, 0);
>> > + input_mt_report_finger_count(dev, 0);
>> > + input_report_abs(dev, ABS_PRESSURE, 0);
>> > + }
>> > +
>> > + input_report_key(dev, BTN_LEFT, f->btn.left);
>> > + input_report_key(dev, BTN_RIGHT, f->btn.right);
>> > +
>> > + input_sync(dev);
>> > +}
>> > +
>> > static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
>> > {
>> > struct alps_data *priv = psmouse->private;
>> > @@ -523,13 +577,13 @@ static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
>> >
>> > static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p)
>> > {
>> > - f->left = !!(p[3] & 0x01);
>> > - f->right = !!(p[3] & 0x02);
>> > - f->middle = !!(p[3] & 0x04);
>> > + f->btn.left = !!(p[3] & 0x01);
>> > + f->btn.right = !!(p[3] & 0x02);
>> > + f->btn.middle = !!(p[3] & 0x04);
>> >
>> > - f->ts_left = !!(p[3] & 0x10);
>> > - f->ts_right = !!(p[3] & 0x20);
>> > - f->ts_middle = !!(p[3] & 0x40);
>> > + f->btn.ts_left = !!(p[3] & 0x10);
>> > + f->btn.ts_right = !!(p[3] & 0x20);
>> > + f->btn.ts_middle = !!(p[3] & 0x40);
>> > }
>> >
>> > static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
>> > @@ -546,10 +600,10 @@ static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
>> > ((p[2] & 0x7f) << 1) |
>> > (p[4] & 0x01);
>> >
>> > - f->x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
>> > + f->pt.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
>> > ((p[0] & 0x30) >> 4);
>> > - f->y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
>> > - f->z = p[5] & 0x7f;
>> > + f->pt.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
>> > + f->pt.z = p[5] & 0x7f;
>> >
>> > alps_decode_buttons_v3(f, p);
>> > }
>> > @@ -573,9 +627,9 @@ static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p,
>> > f->is_mp = !!(p[0] & 0x20);
>> >
>> > if (!f->is_mp) {
>> > - f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
>> > - f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
>> > - f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
>> > + f->pt.x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
>> > + f->pt.y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
>> > + f->pt.z = (p[0] & 4) ? 0 : p[5] & 0x7f;
>> > alps_decode_buttons_v3(f, p);
>> > } else {
>> > f->fingers = ((p[0] & 0x6) >> 1 |
>> > @@ -687,7 +741,7 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
>> > * with x, y, and z all zero, so these seem to be flukes.
>> > * Ignore them.
>> > */
>> > - if (f.x && f.y && !f.z)
>> > + if (f.pt.x && f.pt.y && !f.pt.z)
>> > return;
>> >
>> > /*
>> > @@ -695,12 +749,12 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
>> > * to rely on ST data.
>> > */
>> > if (!fingers) {
>> > - x1 = f.x;
>> > - y1 = f.y;
>> > - fingers = f.z > 0 ? 1 : 0;
>> > + x1 = f.pt.x;
>> > + y1 = f.pt.y;
>> > + fingers = f.pt.z > 0 ? 1 : 0;
>> > }
>> >
>> > - if (f.z >= 64)
>> > + if (f.pt.z >= 64)
>> > input_report_key(dev, BTN_TOUCH, 1);
>> > else
>> > input_report_key(dev, BTN_TOUCH, 0);
>> > @@ -709,22 +763,22 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
>> >
>> > input_mt_report_finger_count(dev, fingers);
>> >
>> > - input_report_key(dev, BTN_LEFT, f.left);
>> > - input_report_key(dev, BTN_RIGHT, f.right);
>> > - input_report_key(dev, BTN_MIDDLE, f.middle);
>> > + input_report_key(dev, BTN_LEFT, f.btn.left);
>> > + input_report_key(dev, BTN_RIGHT, f.btn.right);
>> > + input_report_key(dev, BTN_MIDDLE, f.btn.middle);
>> >
>> > - if (f.z > 0) {
>> > - input_report_abs(dev, ABS_X, f.x);
>> > - input_report_abs(dev, ABS_Y, f.y);
>> > + if (f.pt.z > 0) {
>> > + input_report_abs(dev, ABS_X, f.pt.x);
>> > + input_report_abs(dev, ABS_Y, f.pt.y);
>> > }
>> > - input_report_abs(dev, ABS_PRESSURE, f.z);
>> > + input_report_abs(dev, ABS_PRESSURE, f.pt.z);
>> >
>> > input_sync(dev);
>> >
>> > if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
>> > - input_report_key(dev2, BTN_LEFT, f.ts_left);
>> > - input_report_key(dev2, BTN_RIGHT, f.ts_right);
>> > - input_report_key(dev2, BTN_MIDDLE, f.ts_middle);
>> > + input_report_key(dev2, BTN_LEFT, f.btn.ts_left);
>> > + input_report_key(dev2, BTN_RIGHT, f.btn.ts_right);
>> > + input_report_key(dev2, BTN_MIDDLE, f.btn.ts_middle);
>> > input_sync(dev2);
>> > }
>> > }
>> > @@ -916,6 +970,364 @@ static void alps_process_packet_v4(struct psmouse *psmouse)
>> > input_sync(dev);
>> > }
>> >
>> > +static bool alps_is_valid_package_v7(struct psmouse *psmouse)
>> > +{
>> > + if ((psmouse->pktcnt == 3) && ((psmouse->packet[2] & 0x40) != 0x40))
>> > + return false;
>> > + if ((psmouse->pktcnt == 4) && ((psmouse->packet[3] & 0x48) != 0x48))
>> > + return false;
>> > + if ((psmouse->pktcnt == 6) && ((psmouse->packet[5] & 0x40) != 0x0))
>> > + return false;
>>
>> Maybe do:
>>
>> switch (psmouse->pktcnt) {
>> case 3:
>> if ((psmouse->packet[2] & 0x40) != 0x40)
>> return false;
>> break;
>> ...
>> }
>>
>> > + return true;
>> > +}
>> > +
>> > +static int alps_drop_unsupported_packet_v7(struct psmouse *psmouse)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > + int drop = 1;
>>
>> bool drop = true;
>>
>> > +
>> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW ||
>> > + priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
>> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
>> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
>> > + drop = 0;
>>
>> if (... ||
>> ...) {
>> drop = false;
>> }
>> > +
>> > + return drop;
>> > +}
>> > +
>> > +static unsigned char alps_get_packet_id_v7(char *byte)
>> > +{
>> > + unsigned char packet_id;
>> > +
>> > + if (byte[4] & 0x40)
>> > + packet_id = V7_PACKET_ID_TWO;
>> > + else if (byte[4] & 0x01)
>> > + packet_id = V7_PACKET_ID_MULTI;
>> > + else if ((byte[0] & 0x10) && !(byte[4] & 0x43))
>> > + packet_id = V7_PACKET_ID_NEW;
>> > + else
>> > + packet_id = V7_PACKET_ID_IDLE;
>> > +
>> > + return packet_id;
>> > +}
>> > +
>> > +static void alps_get_finger_coordinate_v7(struct alps_abs_data *pt,
>> > + unsigned char *pkt,
>> > + unsigned char pkt_id)
>> > +{
>> > + if ((pkt_id == V7_PACKET_ID_TWO) ||
>> > + (pkt_id == V7_PACKET_ID_MULTI) ||
>> > + (pkt_id == V7_PACKET_ID_NEW)) {
>> > + pt[0].x = ((pkt[2] & 0x80) << 4);
>> > + pt[0].x |= ((pkt[2] & 0x3F) << 5);
>> > + pt[0].x |= ((pkt[3] & 0x30) >> 1);
>> > + pt[0].x |= (pkt[3] & 0x07);
>> > + pt[0].y = (pkt[1] << 3) | (pkt[0] & 0x07);
>> > +
>> > + pt[1].x = ((pkt[3] & 0x80) << 4);
>> > + pt[1].x |= ((pkt[4] & 0x80) << 3);
>> > + pt[1].x |= ((pkt[4] & 0x3F) << 4);
>> > + pt[1].y = ((pkt[5] & 0x80) << 3);
>> > + pt[1].y |= ((pkt[5] & 0x3F) << 4);
>> > +
>> > + if (pkt_id == V7_PACKET_ID_TWO) {
>> > + pt[1].x &= ~0x000F;
>> > + pt[1].y |= 0x000F;
>> > + } else if (pkt_id == V7_PACKET_ID_MULTI) {
>> > + pt[1].x &= ~0x003F;
>> > + pt[1].y &= ~0x0020;
>> > + pt[1].y |= ((pkt[4] & 0x02) << 4);
>> > + pt[1].y |= 0x001F;
>> > + } else if (pkt_id == V7_PACKET_ID_NEW) {
>> > + pt[1].x &= ~0x003F;
>> > + pt[1].x |= (pkt[0] & 0x20);
>> > + pt[1].y |= 0x000F;
>> > + }
>> > +
>> > + pt[0].y = 0x7FF - pt[0].y;
>> > + pt[1].y = 0x7FF - pt[1].y;
>> > +
>> > + pt[0].z = (pt[0].x && pt[0].y) ? 62 : 0;
>> > + pt[1].z = (pt[1].x && pt[1].y) ? 62 : 0;
>> > + }
>> > +}
>> > +
>> > +static void alps_decode_packet_v7(struct alps_fields *f,
>> > + unsigned char *p,
>> > + struct psmouse *psmouse)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > + static struct v7_raw prev_r;
>> > +
>> > + priv->r.v7.pkt_id = alps_get_packet_id_v7(p);
>> > +
>> > + alps_get_finger_coordinate_v7(f->pt_img, p, priv->r.v7.pkt_id);
>> > +
>> > + priv->r.v7.rest_left = 0;
>> > + priv->r.v7.rest_right = 0;
>> > + priv->r.v7.additional_fingers = 0;
>> > + priv->phy_btn = 0;
>> > +
>> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
>> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI) {
>> > + priv->r.v7.rest_left = (p[0] & 0x10) >> 4;
>> > + priv->r.v7.rest_right = (p[0] & 0x20) >> 5;
>> > + }
>> > +
>> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
>> > + priv->r.v7.additional_fingers = p[5] & 0x03;
>> > +
>> > + priv->phy_btn = (p[0] & 0x80) >> 7;
>> > +
>> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO) {
>> > + if (f->pt_img[0].z != 0 && f->pt_img[1].z != 0)
>> > + priv->r.v7.raw_fn = 2;
>> > + else
>> > + priv->r.v7.raw_fn = 1;
>> > + } else if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
>> > + priv->r.v7.raw_fn = 3 + priv->r.v7.additional_fingers;
>> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
>> > + priv->r.v7.raw_fn = 0;
>> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW)
>> > + priv->r.v7.raw_fn = prev_r.raw_fn;
>> > +
>> > + /* It is a trick to bypass firmware bug of older version
>> > + that 'New' Packet is missed when finger number changed.
>> > + We fake a 'New' Packet in such cases.*/
>>
>> Multi-line comments should be formatted as follows:
>>
>> /*
>> * This is a multi
>> * line comment.
>> */
>>
>>
>> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
>> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
>> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE) {
>> > + if (priv->r.v7.raw_fn != prev_r.raw_fn)
>> > + priv->r.v7.pkt_id = V7_PACKET_ID_NEW;
>> > + }
>> > +
>> > + memcpy(&prev_r, &priv->r.v7, sizeof(struct v7_raw));
>> > +}
>> > +
>> > +static void alps_set_each_pt_attr_v7(struct psmouse *psmouse,
>> > + struct alps_abs_data *pt,
>> > + struct alps_bl_pt_attr *pt_attr)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > + unsigned int dist;
>> > +
>> > + if (!pt_attr->is_init_pt_got && pt->z != 0) {
>> > + pt_attr->is_init_pt_got = 1;
>> > + pt_attr->is_counted = 0;
>> > + memcpy(&pt_attr->init_pt, pt, sizeof(pt_attr->init_pt));
>> > + }
>> > +
>> > + if (pt->z != 0) {
>> > + if (pt->y < priv->resting_zone_y_min) {
>> > + /* A finger is recognized as a non-resting finger
>> > + if it's position is outside the resting finger zone.*/
>> > + pt_attr->zone = ZONE_NORMAL;
>> > + pt_attr->is_counted = 1;
>> > + } else {
>> > + /* A finger is recognized as a resting finger if it's
>> > + position is inside the resting finger zone and there's
>> > + no large movement from it's touch down position.*/
>> > + pt_attr->zone = ZONE_RESTING;
>> > +
>> > + if (pt->x > priv->x_max / 2)
>> > + pt_attr->zone |= ZONE_RIGHT_BTN;
>> > + else
>> > + pt_attr->zone |= ZONE_LEFT_BTN;
>> > +
>> > + /* A resting finger will turn to be a non-resting
>> > + finger if it has made large movement from it's touch
>> > + down position. A non-resting finger will never turn
>> > + to a resting finger before it leaves the touchpad
>> > + surface.*/
>> > + if (pt_attr->is_init_pt_got) {
>> > + dist = alps_pt_distance(pt, &pt_attr->init_pt);
>> > +
>> > + if (dist > V7_LARGE_MOVEMENT)
>> > + pt_attr->is_counted = 1;
>> > + }
>> > + }
>> > + }
>> > +}
>> > +
>> > +static void alps_set_pt_attr_v7(struct psmouse *psmouse,
>> > + struct alps_fields *f)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > + int i;
>> > +
>> > + switch (priv->r.v7.pkt_id) {
>> > + case V7_PACKET_ID_TWO:
>> > + case V7_PACKET_ID_MULTI:
>> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
>> > + alps_set_each_pt_attr_v7(psmouse,
>> > + &f->pt_img[i],
>> > + &priv->pt_attr[i]);
>> > + }
>> > + break;
>> > + default:
>> > + /*All finger attributes are cleared when packet ID is
>> > + 'IDLE', 'New'or other unknown IDs. An 'IDLE' packet
>> > + indicates that there's no finger and no button activity.
>> > + A 'NEW' packet indicates the finger position in packet
>> > + is not continues from previous packet. Such as the
>> > + condition there's finger placed or lifted. In these cases,
>> > + finger attributes will be reset.*/
>> > + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
>> > + break;
>> > + }
>> > +}
>> > +
>> > +static void alps_cal_output_finger_num_v7(struct psmouse *psmouse,
>> > + struct alps_fields *f)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > + unsigned int fn = 0;
>> > + int i;
>> > +
>> > + switch (priv->r.v7.pkt_id) {
>> > + case V7_PACKET_ID_IDLE:
>> > + case V7_PACKET_ID_NEW:
>> > + /*No finger is reported when packet ID is 'IDLE' or 'New'.
>> > + An 'IDLE' packet indicates that there's no finger on touchpad.
>> > + A 'NEW' packet indicates there's finger placed or lifted.
>> > + Finger position of 'New' packet is not continues from the
>> > + previous packet.*/
>> > + fn = 0;
>> > + break;
>> > + case V7_PACKET_ID_TWO:
>> > + if (f->pt_img[0].z == 0) {
>> > + /*The first finger slot is zero when a non-resting
>> > + finger lifted and remaining only one resting finger
>> > + on touchpad. Hardware report the remaining resting
>> > + finger in second slot. This resting is ignored*/
>> > + fn = 0;
>> > + } else if (f->pt_img[1].z == 0) {
>> > + /* The second finger slot is zero if there's
>> > + only one finger*/
>> > + fn = 1;
>> > + } else {
>> > + /*All non-resting fingers will be counted to report*/
>> > + fn = 0;
>> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
>> > + if (priv->pt_attr[i].is_counted)
>> > + fn++;
>> > + }
>> > +
>> > + /*In the case that both fingers are
>> > + resting fingers, report the first one*/
>> > + if (!priv->pt_attr[0].is_counted &&
>> > + !priv->pt_attr[1].is_counted) {
>> > + fn = 1;
>> > + }
>> > + }
>> > + break;
>> > + case V7_PACKET_ID_MULTI:
>> > + /*A packet ID 'MULTI' indicats that at least 3 non-resting
>> > + finger exist.*/
>> > + fn = 3 + priv->r.v7.additional_fingers;
>> > + break;
>> > + }
>> > +
>> > + f->fingers = fn;
>> > +}
>> > +
>> > +static void alps_button_dead_zone_filter(struct psmouse *psmouse,
>> > + struct alps_fields *f,
>> > + struct alps_fields *prev_f)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > + int dx, dy;
>> > +
>> > + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0) {
>> > + memcpy(&priv->pt_attr[0].init_dead_pt,
>> > + &f->pt_img[0],
>> > + sizeof(struct alps_abs_data));
>> > + }
>> > +
>> > + if (priv->pt_attr[0].init_dead_pt.x != 0 &&
>> > + priv->pt_attr[0].init_dead_pt.x != 0) {
>> > + dx = f->pt_img[0].x - priv->pt_attr[0].init_dead_pt.x;
>> > + dy = f->pt_img[0].y - priv->pt_attr[0].init_dead_pt.y;
>> > + if ((abs(dx) > V7_DEAD_ZONE_OFFSET_X) ||
>> > + (abs(dy) > V7_DEAD_ZONE_OFFSET_Y)) {
>> > + memset(&priv->pt_attr[0].init_dead_pt, 0,
>> > + sizeof(struct alps_abs_data));
>> > + priv->btn_delay_cnt = 0;
>> > + } else {
>> > + memcpy(&f->pt_img[0],
>> > + &prev_f->pt_img[0],
>> > + sizeof(struct alps_abs_data));
>> > + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0)
>> > + priv->btn_delay_cnt = 2;
>> > + }
>> > + }
>> > +
>> > + if (priv->btn_delay_cnt > 0) {
>> > + f->btn.left = 0;
>> > + f->btn.right = 0;
>> > + priv->btn_delay_cnt--;
>> > + }
>> > +}
>> > +
>> > +static void alps_assign_buttons_v7(struct psmouse *psmouse,
>> > + struct alps_fields *f,
>> > + struct alps_fields *prev_f)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > +
>> > + if (priv->phy_btn) {
>> > + if (!priv->prev_phy_btn) {
>> > + /* Report a right click as long as there's finger on
>> > + right button zone. Othrewise, report a left click.*/
>> > + if (priv->r.v7.rest_right ||
>> > + priv->pt_attr[0].zone & ZONE_RIGHT_BTN ||
>> > + priv->pt_attr[1].zone & ZONE_RIGHT_BTN) {
>> > + f->btn.right = 1;
>> > + priv->pressed_btn_bits |= RIGHT_BUTTON_BIT;
>> > + } else {
>> > + f->btn.left = 1;
>> > + priv->pressed_btn_bits |= LEFT_BUTTON_BIT;
>> > + }
>> > + } else {
>> > + if (priv->pressed_btn_bits & RIGHT_BUTTON_BIT)
>> > + f->btn.right = 1;
>> > + if (priv->pressed_btn_bits & LEFT_BUTTON_BIT)
>> > + f->btn.left = 1;
>> > + }
>> > + } else {
>> > + priv->pressed_btn_bits = 0;
>> > + f->btn.right = 0;
>> > + f->btn.left = 0;
>> > + }
>> > +
>> > + alps_button_dead_zone_filter(psmouse, f, prev_f);
>> > +
>> > + priv->prev_phy_btn = priv->phy_btn;
>> > +}
>> > +
>> > +static void alps_process_packet_v7(struct psmouse *psmouse)
>> > +{
>> > + struct alps_data *priv = psmouse->private;
>> > + struct alps_fields f = {0};
>> > + static struct alps_fields prev_f;
>> > + unsigned char *packet = psmouse->packet;
>> > +
>> > + priv->decode_fields(&f, packet, psmouse);
>> > +
>> > + if (alps_drop_unsupported_packet_v7(psmouse))
>> > + return;
>> > +
>> > + alps_set_pt_attr_v7(psmouse, &f);
>> > +
>> > + alps_cal_output_finger_num_v7(psmouse, &f);
>> > +
>> > + alps_assign_buttons_v7(psmouse, &f, &prev_f);
>> > +
>> > + alps_report_coord_and_btn(psmouse, &f);
>> > +
>> > + memcpy(&prev_f, &f, sizeof(struct alps_fields));
>> > +}
>> > +
>> > static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
>> > unsigned char packet[],
>> > bool report_buttons)
>> > @@ -1080,6 +1492,14 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
>> > return PSMOUSE_BAD_DATA;
>> > }
>> >
>> > + if ((priv->proto_version == ALPS_PROTO_V7 &&
>> > + !alps_is_valid_package_v7(psmouse))) {
>> > + psmouse_dbg(psmouse, "refusing packet[%i] = %x\n",
>> > + psmouse->pktcnt - 1,
>> > + psmouse->packet[psmouse->pktcnt - 1]);
>> > + return PSMOUSE_BAD_DATA;
>> > + }
>> > +
>> > if (psmouse->pktcnt == psmouse->pktsize) {
>> > priv->process_packet(psmouse);
>> > return PSMOUSE_FULL_PACKET;
>> > @@ -1192,6 +1612,22 @@ static int alps_rpt_cmd(struct psmouse *psmouse, int init_command,
>> > return 0;
>> > }
>> >
>> > +static int alps_check_valid_firmware_id(unsigned char id[])
>> > +{
>> > + int valid = 1;
>>
>> bool valid = true;
>>
>> > +
>> > + if (id[0] == 0x73)
>> > + valid = 1;
>> > + else if (id[0] == 0x88) {
>> > + if ((id[1] == 0x07) ||
>> > + (id[1] == 0x08) ||
>> > + ((id[1] & 0xf0) == 0xB0))
>> > + valid = 1;
>> > + }
>> > +
>> > + return valid;
>> > +}
>> > +
>> > static int alps_enter_command_mode(struct psmouse *psmouse)
>> > {
>> > unsigned char param[4];
>> > @@ -1201,8 +1637,7 @@ static int alps_enter_command_mode(struct psmouse *psmouse)
>> > return -1;
>> > }
>> >
>> > - if ((param[0] != 0x88 || (param[1] != 0x07 && param[1] != 0x08)) &&
>> > - param[0] != 0x73) {
>> > + if (!alps_check_valid_firmware_id(param)) {
>> > psmouse_dbg(psmouse,
>> > "unknown response while entering command mode\n");
>> > return -1;
>> > @@ -1704,6 +2139,36 @@ error:
>> > return ret;
>> > }
>> >
>> > +static int alps_hw_init_v7(struct psmouse *psmouse)
>> > +{
>> > + struct ps2dev *ps2dev = &psmouse->ps2dev;
>> > + int reg_val, ret = -1;
>> > +
>> > + if (alps_enter_command_mode(psmouse))
>> > + goto error;
>> > +
>> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2d9);
>> > + if (reg_val == -1)
>> > + goto error;
>> > +
>> > + if (alps_command_mode_write_reg(psmouse, 0xc2c9, 0x64))
>> > + goto error;
>> > +
>> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4);
>> > + if (reg_val == -1)
>> > + goto error;
>> > +
>> > + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x02))
>> > + goto error;
>> > +
>> > + alps_exit_command_mode(psmouse);
>> > + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
>> > +
>> > +error:
>> > + alps_exit_command_mode(psmouse);
>> > + return ret;
>> > +}
>> > +
>> > /* Must be in command mode when calling this function */
>> > static int alps_absolute_mode_v4(struct psmouse *psmouse)
>> > {
>> > @@ -1875,6 +2340,7 @@ static void alps_set_defaults(struct alps_data *priv)
>> > priv->set_abs_params = alps_set_abs_params_st;
>> > priv->x_max = 1023;
>> > priv->y_max = 767;
>> > + priv->slot_number = 1;
>> > break;
>> > case ALPS_PROTO_V3:
>> > priv->hw_init = alps_hw_init_v3;
>> > @@ -1883,6 +2349,7 @@ static void alps_set_defaults(struct alps_data *priv)
>> > priv->decode_fields = alps_decode_pinnacle;
>> > priv->nibble_commands = alps_v3_nibble_commands;
>> > priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
>> > + priv->slot_number = 2;
>> > break;
>> > case ALPS_PROTO_V4:
>> > priv->hw_init = alps_hw_init_v4;
>> > @@ -1890,6 +2357,7 @@ static void alps_set_defaults(struct alps_data *priv)
>> > priv->set_abs_params = alps_set_abs_params_mt;
>> > priv->nibble_commands = alps_v4_nibble_commands;
>> > priv->addr_command = PSMOUSE_CMD_DISABLE;
>> > + priv->slot_number = 2;
>> > break;
>> > case ALPS_PROTO_V5:
>> > priv->hw_init = alps_hw_init_dolphin_v1;
>> > @@ -1905,6 +2373,7 @@ static void alps_set_defaults(struct alps_data *priv)
>> > priv->y_max = 660;
>> > priv->x_bits = 23;
>> > priv->y_bits = 12;
>> > + priv->slot_number = 2;
>> > break;
>> > case ALPS_PROTO_V6:
>> > priv->hw_init = alps_hw_init_v6;
>> > @@ -1913,6 +2382,28 @@ static void alps_set_defaults(struct alps_data *priv)
>> > priv->nibble_commands = alps_v6_nibble_commands;
>> > priv->x_max = 2047;
>> > priv->y_max = 1535;
>> > + priv->slot_number = 2;
>> > + break;
>> > + case ALPS_PROTO_V7:
>> > + priv->hw_init = alps_hw_init_v7;
>> > + priv->process_packet = alps_process_packet_v7;
>> > + priv->decode_fields = alps_decode_packet_v7;
>> > + priv->set_abs_params = alps_set_abs_params_mt;
>> > + priv->nibble_commands = alps_v3_nibble_commands;
>> > + priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
>> > + priv->x_max = 0xfff;
>> > + priv->y_max = 0x7ff;
>> > + priv->resting_zone_y_min = 0x654;
>> > + priv->byte0 = 0x48;
>> > + priv->mask0 = 0x48;
>> > + priv->flags = 0;
>> > + priv->slot_number = 2;
>> > +
>> > + priv->phy_btn = 0;
>> > + priv->prev_phy_btn = 0;
>> > + priv->btn_delay_cnt = 0;
>> > + priv->pressed_btn_bits = 0;
>> > + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
>> > break;
>> > }
>> > }
>> > @@ -1982,6 +2473,11 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)
>> > return -EIO;
>> > else
>> > return 0;
>> > + } else if (ec[0] == 0x88 && (ec[1] & 0xf0) == 0xB0) {
>> > + priv->proto_version = ALPS_PROTO_V7;
>> > + alps_set_defaults(priv);
>> > +
>> > + return 0;
>> > } else if (ec[0] == 0x88 && ec[1] == 0x08) {
>> > priv->proto_version = ALPS_PROTO_V3;
>> > alps_set_defaults(priv);
>> > @@ -2045,7 +2541,7 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
>> > struct input_dev *dev1)
>> > {
>> > set_bit(INPUT_PROP_SEMI_MT, dev1->propbit);
>> > - input_mt_init_slots(dev1, 2, 0);
>> > + input_mt_init_slots(dev1, priv->slot_number, 0);
>> > input_set_abs_params(dev1, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0);
>> > input_set_abs_params(dev1, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0);
>> >
>> > diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h
>> > index 03f88b6..dedbd27 100644
>> > --- a/drivers/input/mouse/alps.h
>> > +++ b/drivers/input/mouse/alps.h
>> > @@ -18,11 +18,36 @@
>> > #define ALPS_PROTO_V4 4
>> > #define ALPS_PROTO_V5 5
>> > #define ALPS_PROTO_V6 6
>> > +#define ALPS_PROTO_V7 7
>> > +
>> > +#define MAX_IMG_PT_NUM 5
>> > +#define V7_IMG_PT_NUM 2
>> > +
>> > +#define ZONE_NORMAL 0x01
>> > +#define ZONE_RESTING 0x02
>> > +#define ZONE_LEFT_BTN 0x04
>> > +#define ZONE_RIGHT_BTN 0x08
>> >
>> > #define DOLPHIN_COUNT_PER_ELECTRODE 64
>> > #define DOLPHIN_PROFILE_XOFFSET 8 /* x-electrode offset */
>> > #define DOLPHIN_PROFILE_YOFFSET 1 /* y-electrode offset */
>> >
>> > +/*
>> > + * enum V7_PACKET_ID - defines the packet type for V7
>> > + * V7_PACKET_ID_IDLE: There's no finger and no button activity.
>> > + * V7_PACKET_ID_TWO: There's one or two non-resting fingers on touchpad
>> > + * or there's button activities.
>> > + * V7_PACKET_ID_MULTI: There are at least three non-resting fingers.
>> > + * V7_PACKET_ID_NEW: The finger position in slot is not continues from
>> > + * previous packet.
>> > +*/
>> > +enum V7_PACKET_ID {
>> > + V7_PACKET_ID_IDLE,
>> > + V7_PACKET_ID_TWO,
>> > + V7_PACKET_ID_MULTI,
>> > + V7_PACKET_ID_NEW,
>> > +};
>> > +
>> > /**
>> > * struct alps_model_info - touchpad ID table
>> > * @signature: E7 response string to match.
>> > @@ -66,15 +91,7 @@ struct alps_nibble_commands {
>> > };
>> >
>> > /**
>> > - * struct alps_fields - decoded version of the report packet
>> > - * @x_map: Bitmap of active X positions for MT.
>> > - * @y_map: Bitmap of active Y positions for MT.
>> > - * @fingers: Number of fingers for MT.
>> > - * @x: X position for ST.
>> > - * @y: Y position for ST.
>> > - * @z: Z position for ST.
>> > - * @first_mp: Packet is the first of a multi-packet report.
>> > - * @is_mp: Packet is part of a multi-packet report.
>> > + * struct alps_btn - decoded version of the button status
>> > * @left: Left touchpad button is active.
>> > * @right: Right touchpad button is active.
>> > * @middle: Middle touchpad button is active.
>> > @@ -82,16 +99,7 @@ struct alps_nibble_commands {
>> > * @ts_right: Right trackstick button is active.
>> > * @ts_middle: Middle trackstick button is active.
>> > */
>> > -struct alps_fields {
>> > - unsigned int x_map;
>> > - unsigned int y_map;
>> > - unsigned int fingers;
>> > - unsigned int x;
>> > - unsigned int y;
>> > - unsigned int z;
>> > - unsigned int first_mp:1;
>> > - unsigned int is_mp:1;
>> > -
>> > +struct alps_btn {
>> > unsigned int left:1;
>> > unsigned int right:1;
>> > unsigned int middle:1;
>> > @@ -102,6 +110,73 @@ struct alps_fields {
>> > };
>> >
>> > /**
>> > + * struct alps_btn - decoded version of the X Y Z postion for ST.
>> > + * @x: X position for ST.
>> > + * @y: Y position for ST.
>> > + * @z: Z position for ST.
>> > + */
>> > +struct alps_abs_data {
>> > + unsigned int x;
>> > + unsigned int y;
>> > + unsigned int z;
>> > +};
>> > +
>> > +/**
>> > + * struct alps_fields - decoded version of the report packet
>> > + * @fingers: Number of fingers for MT.
>> > + * @pt: X Y Z postion for ST.
>> > + * @pt: X Y Z postion for image MT.
>> > + * @x_map: Bitmap of active X positions for MT.
>> > + * @y_map: Bitmap of active Y positions for MT.
>> > + * @first_mp: Packet is the first of a multi-packet report.
>> > + * @is_mp: Packet is part of a multi-packet report.
>> > + * @btn: Button activity status
>> > + */
>> > +struct alps_fields {
>> > + unsigned int fingers;
>> > + struct alps_abs_data pt;
>> > + struct alps_abs_data pt_img[MAX_IMG_PT_NUM];
>> > + unsigned int x_map;
>> > + unsigned int y_map;
>> > + unsigned int first_mp:1;
>> > + unsigned int is_mp:1;
>> > + struct alps_btn btn;
>>
>> Splitting button data and abs data into separate structures might make
>> sense, but please split off these changes into a separate patch so that
>> they are not obscuring changes necessary for v7 support.
>>
>> > +};
>> > +
>> > +/**
>> > + * struct v7_raw - data decoded from raw packet for V7.
>> > + * @pkt_id: An id that specifies the type of packet.
>> > + * @additional_fingers: Number of additional finger that is neighter included
>> > + * in pt slot nor reflected in rest_left and rest_right flag of data packet.
>> > + * @rest_left: There are fingers on left resting zone.
>> > + * @rest_right: There are fingers on right resting zone.
>> > + * @raw_fn: The number of finger on touchpad.
>> > + */
>> > +struct v7_raw {
>> > + unsigned char pkt_id;
>> > + unsigned int additional_fingers;
>> > + unsigned char rest_left;
>> > + unsigned char rest_right;
>> > + unsigned char raw_fn;
>> > +};
>> > +
>> > +/**
>> > + * struct alps_bl_pt_attr - generic attributes of touch points for buttonless device
>> > + * @zone: The part of touchpad that the touch point locates
>> > + * @is_counted: The touch point is not a resting finger.
>> > + * @is_init_pt_got: The touch down point is got.
>> > + * @init_pt: The X Y Z position of the touch down point.
>> > + * @init_dead_pt: The touch down point of a finger used by dead zone process.
>> > + */
>> > +struct alps_bl_pt_attr {
>> > + unsigned char zone;
>> > + unsigned char is_counted;
>> > + unsigned char is_init_pt_got;
>> > + struct alps_abs_data init_pt;
>> > + struct alps_abs_data init_dead_pt;
>> > +};
>> > +
>> > +/**
>> > * struct alps_data - private data structure for the ALPS driver
>> > * @dev2: "Relative" device used to report trackstick or mouse activity.
>> > * @phys: Physical path for the relative device.
>> > @@ -116,8 +191,10 @@ struct alps_fields {
>> > * @flags: Additional device capabilities (passthrough port, trackstick, etc.).
>> > * @x_max: Largest possible X position value.
>> > * @y_max: Largest possible Y position value.
>> > + * @resting_zone_y_min: Smallest Y postion value of the bottom resting zone.
>> > * @x_bits: Number of X bits in the MT bitmap.
>> > * @y_bits: Number of Y bits in the MT bitmap.
>> > + * @img_fingers: Number of image fingers.
>> > * @hw_init: Protocol-specific hardware init function.
>> > * @process_packet: Protocol-specific function to process a report packet.
>> > * @decode_fields: Protocol-specific function to read packet bitfields.
>> > @@ -132,6 +209,11 @@ struct alps_fields {
>> > * @fingers: Number of fingers from last MT report.
>> > * @quirks: Bitmap of ALPS_QUIRK_*.
>> > * @timer: Timer for flushing out the final report packet in the stream.
>> > + * @v7: Data decoded from raw packet for V7
>> > + * @phy_btn: Physical button is active.
>> > + * @prev_phy_btn: Physical button of previous packet is active.
>> > + * @pressed_btn_bits: Pressed positon of button zone
>> > + * @pt_attr: Generic attributes of touch points for buttonless device.
>> > */
>> > struct alps_data {
>> > struct input_dev *dev2;
>> > @@ -145,8 +227,10 @@ struct alps_data {
>> > unsigned char flags;
>> > int x_max;
>> > int y_max;
>> > + int resting_zone_y_min;
>> > int x_bits;
>> > int y_bits;
>> > + unsigned char slot_number;
>> >
>> > int (*hw_init)(struct psmouse *psmouse);
>> > void (*process_packet)(struct psmouse *psmouse);
>> > @@ -161,6 +245,16 @@ struct alps_data {
>> > int fingers;
>> > u8 quirks;
>> > struct timer_list timer;
>> > +
>> > + /* these are used for buttonless touchpad*/
>> > + union {
>> > + struct v7_raw v7;
>> > + } r;
>>
>> Why do you need a union here? A single-field union does not seem to be
>> terribly useful.
>>
>>
>> > + unsigned char phy_btn;
>> > + unsigned char prev_phy_btn;
>> > + unsigned char btn_delay_cnt;
>> > + unsigned char pressed_btn_bits;
>> > + struct alps_bl_pt_attr pt_attr[MAX_IMG_PT_NUM];
>> > };
>> >
>> > #define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /* trakcstick buttons in trackstick packet */
>> > --
>> > 1.8.3.2
>> >
>>
>> Thanks.
>>
>> --
>> Dmitry
^ permalink raw reply
* Re: [PATCH] evdev: flush ABS_* events during EVIOCGABS
From: David Herrmann @ 2014-04-22 6:21 UTC (permalink / raw)
To: Peter Hutterer
Cc: open list:HID CORE LAYER, Dmitry Torokhov, Benjamin Tissoires
In-Reply-To: <20140422041535.GA10735@yabbi.redhat.com>
Hi Peter
On Tue, Apr 22, 2014 at 6:15 AM, Peter Hutterer
<peter.hutterer@who-t.net> wrote:
> How are you planning to handle the slot-based events? We'd either need to
> add something similar (but more complex) to evdev_handle_mt_request or rely
> on the caller to call the whole EV_ABS range and ditch anything ABS_MT_.
> I'd prefer the former, the latter is yet more behaviour that's easy to get
> wrong.
This is all racy..
We _really_ need an ioctl to receive _all_ ABS information atomically.
I mean, there's no way we can know the user's state from the kernel.
Even if the user resyncs via EVIOCGMTSLOTS, we can never flush the
whole ABS queue. Problem is, the user has to call the ioctl for _each_
available MT code and events might get queued in between. So yeah,
this patch doesn't help much..
I have no better idea than adding a new EVIOCGABS call that retrieves
ABS values for all slots atomically (and for all other axes..). No
idea how to properly fix the old ioctls.
Thanks
David
^ permalink raw reply
* Re: [PATCH] Input: evdev - add event-mask API
From: David Herrmann @ 2014-04-22 6:25 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: open list:HID CORE LAYER, Benjamin Tissoires, Peter Hutterer,
Lennart Poettering
In-Reply-To: <20140419211601.GB8343@core.coreip.homeip.net>
Hi
On Sat, Apr 19, 2014 at 11:16 PM, Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
>> +static size_t evdev_get_mask_cnt(unsigned int type)
>> +{
>> + switch (type) {
>> + case 0:
>> + /* 0 is special (EV-bits instead of EV_SYN) like EVIOCGBIT */
>> + return EV_CNT;
>> + case EV_KEY:
>> + return KEY_CNT;
>> + case EV_REL:
>> + return REL_CNT;
>> + case EV_ABS:
>> + return ABS_CNT;
>> + case EV_MSC:
>> + return MSC_CNT;
>> + case EV_SW:
>> + return SW_CNT;
>> + case EV_LED:
>> + return LED_CNT;
>> + case EV_SND:
>> + return SND_CNT;
>> + case EV_FF:
>> + return FF_CNT;
>> + }
>
> Maybe we need a static array of code->count mapping instead of a switch?
Sure, I can change that.
>> +
>> + return 0;
>> +}
>> +
>> +/* must be called with evdev-mutex held */
>> +static int evdev_set_mask(struct evdev_client *client,
>> + unsigned int type,
>> + const void __user *codes,
>> + u32 codes_size)
>> +{
>> + unsigned long flags, *mask, *oldmask;
>> + size_t cnt, size;
>> +
>> + /* unknown masks are simply ignored for forward-compat */
>> + cnt = evdev_get_mask_cnt(type);
>> + if (!cnt)
>> + return 0;
>> +
>> + /* we allow 'codes_size > size' for forward-compat */
>> + size = sizeof(unsigned long) * BITS_TO_LONGS(cnt);
>> +
>> + mask = kzalloc(size, GFP_KERNEL);
>> + if (!mask)
>> + return -ENOMEM;
>> +
>> + if (copy_from_user(mask, codes, min_t(size_t, codes_size, size))) {
>> + kfree(mask);
>> + return -EFAULT;
>> + }
>> +
>> + spin_lock_irqsave(&client->buffer_lock, flags);
>> + oldmask = client->evmasks[type];
>> + client->evmasks[type] = mask;
>> + spin_unlock_irqrestore(&client->buffer_lock, flags);
>> +
>> + kfree(oldmask);
>> +
>> + return 0;
>> +}
>> +
>> +/* must be called with evdev-mutex held */
>> +static int evdev_get_mask(struct evdev_client *client,
>> + unsigned int type,
>> + void __user *codes,
>> + u32 codes_size)
>> +{
>> + unsigned long *mask;
>> + size_t cnt, size, min, i;
>> + u8 __user *out;
>> +
>> + /* we allow unknown types and 'codes_size > size' for forward-compat */
>> + cnt = evdev_get_mask_cnt(type);
>> + size = sizeof(unsigned long) * BITS_TO_LONGS(cnt);
>> + min = min_t(size_t, codes_size, size);
>> +
>> + if (cnt > 0) {
>> + mask = client->evmasks[type];
>> + if (mask) {
>> + if (copy_to_user(codes, mask, min))
>> + return -EFAULT;
>> + } else {
>> + /* fake mask with all bits set */
>> + out = (u8 __user*)codes;
>> + for (i = 0; i < min; ++i) {
>> + if (put_user((u8)0xff, out + i))
>> + return -EFAULT;
>> + }
>> + }
>> + }
>> +
>> + codes = (u8 __user*)codes + min;
>> + codes_size -= min;
>> +
>> + if (codes_size > 0 && clear_user(codes, codes_size))
>> + return -EFAULT;
>> +
>> + return 0;
>> +}
>> +
>> +/* requires the buffer lock to be held */
>> +static bool __evdev_is_masked(struct evdev_client *client,
>> + unsigned int type,
>> + unsigned int code)
>> +{
>> + unsigned long *mask;
>> + size_t cnt;
>> +
>> + /* EV_SYN and unknown codes are never masked */
>
> So won't this mean that client is still woken up by "empty" packet if we
> filter out everything but EV_SYN?
Whoops, indeed. I will skip SYN_REPORT events if the queue is empty or
if the previous event was already a SYN_REPORT.
Thanks
David
^ permalink raw reply
* Re: [PATCH] Input: evdev - add event-mask API
From: David Herrmann @ 2014-04-22 6:31 UTC (permalink / raw)
To: Peter Hutterer
Cc: open list:HID CORE LAYER, Dmitry Torokhov, Benjamin Tissoires,
Lennart Poettering
In-Reply-To: <20140422042947.GB10735@yabbi.redhat.com>
Hi
On Tue, Apr 22, 2014 at 6:29 AM, Peter Hutterer
<peter.hutterer@who-t.net> wrote:
>> +/* requires the buffer lock to be held */
>> +static bool __evdev_is_masked(struct evdev_client *client,
>> + unsigned int type,
>> + unsigned int code)
>> +{
>> + unsigned long *mask;
>> + size_t cnt;
>> +
>> + /* EV_SYN and unknown codes are never masked */
>> + if (!type || type >= EV_CNT)
>
> why not use type == EV_SYN?
You mean instead of "!type"? Yeah, probably easier to read.
>> + return false;
>> +
>> + /* first test whether the type is masked */
>> + mask = client->evmasks[0];
>
> if mask is NULL, you already know it's not mask, you can return early.
Nope. If evmasks[0] is NULL, then no type-mask has been set. That
doesn't mean the given code-mask is NULL.
For instance:
evmasks[0] == NULL
evmasks[EV_KEY][KEY_A] == 0
In that case, the user wants all events but KEY_A (even though
evmasks[0] is NULL).
>> #define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */
>> #define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */
>> +#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) /* Get event-masks */
>> +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */
>
> This is missing from all other ioctls but while you're adding a new one
> anyway: please add documentation on what the ioctl does, the input and
> return value/output expected, side-effects etc. right now, understanding the
> evdev ioctls requires either reading the kernel code or existing user-space
> code, with the usual risk of getting it wrong.
Meh.. I thought no-one will notice.. Clearly, I was wrong. I will add
proper docs in v2.
Thanks a lot!
David
^ permalink raw reply
* [PATCH] soc_button_array: fix a crash during rmmod
From: Zhu, Lejun @ 2014-04-22 6:38 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel
In-Reply-To: <53560DD1.5050805@linux.intel.com>
When the system has zero or one button available, trying to rmmod
soc_button_array will cause crash. Fix this by properly handling -ENODEV
in probe().
Signed-off-by: Lejun Zhu <lejun.zhu@linux.intel.com>
---
drivers/input/misc/soc_button_array.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/input/misc/soc_button_array.c
b/drivers/input/misc/soc_button_array.c
index 08ead2a..20c80f5 100644
--- a/drivers/input/misc/soc_button_array.c
+++ b/drivers/input/misc/soc_button_array.c
@@ -169,6 +169,7 @@ static int soc_button_pnp_probe(struct pnp_dev *pdev,
soc_button_remove(pdev);
return error;
}
+ continue;
}
priv->children[i] = pd;
--
1.8.3.2
^ permalink raw reply related
* Re: [PATCH] input: add support for ALPS v7 protocol device
From: Peter Hutterer @ 2014-04-22 6:55 UTC (permalink / raw)
To: Elaine Chen
Cc: Dmitry Torokhov, Kevin Cernekee, david turvene, linux-input,
Justin Clift, Qiting Chen, Hans de Goede
In-Reply-To: <CAKvfdt+vztgQoys-u1i-4gwOYqYQuuu5f4bgcQyOtjrz3i-cZQ@mail.gmail.com>
On Tue, Apr 22, 2014 at 02:11:18PM +0800, Elaine Chen wrote:
> Hi Peter,
>
> Thank you! I know there's a flag to register our device as clickpad to
> X system. But it seems the clickpad attribution only
> deal with soft button. It won't handle "Resting Finger" function,
> isn't it? (Resting Finger function: place one or more fingers still in
> soft button zone, these fingers
> won't affact other finger's cursoring and gestures.) If the X system
> doesn't support this function, our driver has to implement it in
> kernel. This is why we didn't use
> the clickpad flag. If I'm wrong, please tell me.
we've added a few patches recently that amongst other things ignore finger
events in the softbutton area, so that should fix that issue and stop
messing up pointer movement. I'm the first to admit that the xorg driver
hasn't been up to scratch in many regards, but much of that was for lack of
developer time. Hans has recently improved the behaviour quite a bit. For
the future, it's probably easier (or better) to fix up the xorg driver to
get rid of the specific problems you're seeing than hacking up the kernel
driver.
Cheers,
Peter
> 2014-04-22 13:42 GMT+08:00 Peter Hutterer <peter.hutterer@who-t.net>:
> > On Mon, Apr 21, 2014 at 10:26:04PM -0700, Dmitry Torokhov wrote:
> >> Hi Qiting,
> >>
> >> On Wed, Mar 19, 2014 at 04:55:53PM +0800, Qiting Chen wrote:
> >> > Here is a patch of supporting ALPS v7 protocol device.
> >> > ALPS v7 protocol device is a clickpad that is currently used on
> >> > Lenovo S430/S435/S530, Lenovo Z410/Z510, HP Odie, HP Revolve 810 G1,
> >> > as well as other machines with ALPS Touchpad of following infomation:
> >> > Device ID = 0x73, 0x03, 0x0a
> >> > Firmware ID = 0x88, 0xb*, 0x**
> >> >
> >> > A v7 protocol support patch is first relesed 2 months ago:
> >> > http://www.spinics.net/lists/linux-input/msg29084.html
> >> > After that some feedbacks were received from end user. Now this patch fixed the bugs
> >> > reported by them:
> >> > 1) Fix cursor jump when doing a right click drag
> >> > 2) Fix cursor jitter when button clicking
> >>
> >> My biggest question is whether the soft buttons should be processed in
> >> kernel driver or in userspace.
> >>
> >> Peter, don't Synaptics clickpads need similar functionality? I thought X
> >> driver already handles soft button areas... Am I mistaken?
> >
> > yeah, we do handle those software button areas in the xorg synaptics driver
> > (and libinput very soon, for that matter). I'd prefer this to be handled in
> > userspace so we can get some sort of unified behaviour. There is one
> > touchpad series that handles it in firmware though (see Hans' recent patch
> > for the Cypress touchpads) but on the whole it's better to leave it to
> > userspace.
> >
> > Cheers,
> > Peter
> >
> >> > Signed-off-by: Qiting Chen <qiting.chen@cn.alps.com>
> >> > ---
> >> > drivers/input/mouse/alps.c | 560 ++++++++++++++++++++++++++++++++++++++++++---
> >> > drivers/input/mouse/alps.h | 132 +++++++++--
> >> > 2 files changed, 641 insertions(+), 51 deletions(-)
> >> >
> >> > diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
> >> > index fb15c64..383281f 100644
> >> > --- a/drivers/input/mouse/alps.c
> >> > +++ b/drivers/input/mouse/alps.c
> >> > @@ -32,6 +32,13 @@
> >> > #define ALPS_REG_BASE_RUSHMORE 0xc2c0
> >> > #define ALPS_REG_BASE_PINNACLE 0x0000
> >> >
> >> > +#define LEFT_BUTTON_BIT 0x01
> >> > +#define RIGHT_BUTTON_BIT 0x02
> >> > +
> >> > +#define V7_LARGE_MOVEMENT 130
> >> > +#define V7_DEAD_ZONE_OFFSET_X 72
> >> > +#define V7_DEAD_ZONE_OFFSET_Y 72
> >> > +
> >> > static const struct alps_nibble_commands alps_v3_nibble_commands[] = {
> >> > { PSMOUSE_CMD_SETPOLL, 0x00 }, /* 0 */
> >> > { PSMOUSE_CMD_RESET_DIS, 0x00 }, /* 1 */
> >> > @@ -99,6 +106,7 @@ static const struct alps_nibble_commands alps_v6_nibble_commands[] = {
> >> > #define ALPS_FOUR_BUTTONS 0x40 /* 4 direction button present */
> >> > #define ALPS_PS2_INTERLEAVED 0x80 /* 3-byte PS/2 packet interleaved with
> >> > 6-byte ALPS packet */
> >> > +#define ALPS_BTNLESS 0x100 /* ALPS ClickPad flag */
> >> >
> >> > static const struct alps_model_info alps_model_data[] = {
> >> > { { 0x32, 0x02, 0x14 }, 0x00, ALPS_PROTO_V2, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
> >> > @@ -140,6 +148,20 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> >> > * isn't valid per PS/2 spec.
> >> > */
> >> >
> >> > +static unsigned int alps_pt_distance(struct alps_abs_data *pt0,
> >> > + struct alps_abs_data *pt1)
> >> > +{
> >> > + int vect_x, vect_y;
> >> > +
> >> > + if (!pt0 || !pt1)
> >> > + return 0;
> >>
> >> In which case can either of this pointers being NULL?
> >>
> >> > +
> >> > + vect_x = pt0->x - pt1->x;
> >> > + vect_y = pt0->y - pt1->y;
> >> > +
> >> > + return int_sqrt(vect_x * vect_x + vect_y * vect_y);
> >> > +}
> >> > +
> >> > /* Packet formats are described in Documentation/input/alps.txt */
> >> >
> >> > static bool alps_is_valid_first_byte(struct alps_data *priv,
> >> > @@ -320,8 +342,8 @@ static void alps_process_bitmap_dolphin(struct alps_data *priv,
> >> > end_bit = y_msb - 1;
> >> > box_middle_y = (priv->y_max * (start_bit + end_bit)) /
> >> > (2 * (priv->y_bits - 1));
> >> > - *x1 = fields->x;
> >> > - *y1 = fields->y;
> >> > + *x1 = fields->pt.x;
> >> > + *y1 = fields->pt.y;
> >> > *x2 = 2 * box_middle_x - *x1;
> >> > *y2 = 2 * box_middle_y - *y1;
> >> > }
> >> > @@ -461,6 +483,38 @@ static void alps_report_semi_mt_data(struct input_dev *dev, int num_fingers,
> >> > alps_set_slot(dev, 1, num_fingers == 2, x2, y2);
> >> > }
> >> >
> >> > +static void alps_report_coord_and_btn(struct psmouse *psmouse,
> >> > + struct alps_fields *f)
> >> > +{
> >> > + struct input_dev *dev;
> >> > +
> >> > + if (!psmouse || !f)
> >> > + return;
> >>
> >> Can either of these 2 pointers ever be NULL?
> >>
> >> > +
> >> > + dev = psmouse->dev;
> >> > +
> >> > + if (f->fingers) {
> >> > + input_report_key(dev, BTN_TOUCH, 1);
> >> > + alps_report_semi_mt_data(dev, f->fingers,
> >> > + f->pt_img[0].x, f->pt_img[0].y,
> >> > + f->pt_img[1].x, f->pt_img[1].y);
> >> > + input_mt_report_finger_count(dev, f->fingers);
> >> > +
> >> > + input_report_abs(dev, ABS_X, f->pt_img[0].x);
> >> > + input_report_abs(dev, ABS_Y, f->pt_img[0].y);
> >> > + input_report_abs(dev, ABS_PRESSURE, f->pt_img[0].z);
> >> > + } else {
> >> > + input_report_key(dev, BTN_TOUCH, 0);
> >> > + input_mt_report_finger_count(dev, 0);
> >> > + input_report_abs(dev, ABS_PRESSURE, 0);
> >> > + }
> >> > +
> >> > + input_report_key(dev, BTN_LEFT, f->btn.left);
> >> > + input_report_key(dev, BTN_RIGHT, f->btn.right);
> >> > +
> >> > + input_sync(dev);
> >> > +}
> >> > +
> >> > static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
> >> > {
> >> > struct alps_data *priv = psmouse->private;
> >> > @@ -523,13 +577,13 @@ static void alps_process_trackstick_packet_v3(struct psmouse *psmouse)
> >> >
> >> > static void alps_decode_buttons_v3(struct alps_fields *f, unsigned char *p)
> >> > {
> >> > - f->left = !!(p[3] & 0x01);
> >> > - f->right = !!(p[3] & 0x02);
> >> > - f->middle = !!(p[3] & 0x04);
> >> > + f->btn.left = !!(p[3] & 0x01);
> >> > + f->btn.right = !!(p[3] & 0x02);
> >> > + f->btn.middle = !!(p[3] & 0x04);
> >> >
> >> > - f->ts_left = !!(p[3] & 0x10);
> >> > - f->ts_right = !!(p[3] & 0x20);
> >> > - f->ts_middle = !!(p[3] & 0x40);
> >> > + f->btn.ts_left = !!(p[3] & 0x10);
> >> > + f->btn.ts_right = !!(p[3] & 0x20);
> >> > + f->btn.ts_middle = !!(p[3] & 0x40);
> >> > }
> >> >
> >> > static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> >> > @@ -546,10 +600,10 @@ static void alps_decode_pinnacle(struct alps_fields *f, unsigned char *p,
> >> > ((p[2] & 0x7f) << 1) |
> >> > (p[4] & 0x01);
> >> >
> >> > - f->x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> >> > + f->pt.x = ((p[1] & 0x7f) << 4) | ((p[4] & 0x30) >> 2) |
> >> > ((p[0] & 0x30) >> 4);
> >> > - f->y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> >> > - f->z = p[5] & 0x7f;
> >> > + f->pt.y = ((p[2] & 0x7f) << 4) | (p[4] & 0x0f);
> >> > + f->pt.z = p[5] & 0x7f;
> >> >
> >> > alps_decode_buttons_v3(f, p);
> >> > }
> >> > @@ -573,9 +627,9 @@ static void alps_decode_dolphin(struct alps_fields *f, unsigned char *p,
> >> > f->is_mp = !!(p[0] & 0x20);
> >> >
> >> > if (!f->is_mp) {
> >> > - f->x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> >> > - f->y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> >> > - f->z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> >> > + f->pt.x = ((p[1] & 0x7f) | ((p[4] & 0x0f) << 7));
> >> > + f->pt.y = ((p[2] & 0x7f) | ((p[4] & 0xf0) << 3));
> >> > + f->pt.z = (p[0] & 4) ? 0 : p[5] & 0x7f;
> >> > alps_decode_buttons_v3(f, p);
> >> > } else {
> >> > f->fingers = ((p[0] & 0x6) >> 1 |
> >> > @@ -687,7 +741,7 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> >> > * with x, y, and z all zero, so these seem to be flukes.
> >> > * Ignore them.
> >> > */
> >> > - if (f.x && f.y && !f.z)
> >> > + if (f.pt.x && f.pt.y && !f.pt.z)
> >> > return;
> >> >
> >> > /*
> >> > @@ -695,12 +749,12 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> >> > * to rely on ST data.
> >> > */
> >> > if (!fingers) {
> >> > - x1 = f.x;
> >> > - y1 = f.y;
> >> > - fingers = f.z > 0 ? 1 : 0;
> >> > + x1 = f.pt.x;
> >> > + y1 = f.pt.y;
> >> > + fingers = f.pt.z > 0 ? 1 : 0;
> >> > }
> >> >
> >> > - if (f.z >= 64)
> >> > + if (f.pt.z >= 64)
> >> > input_report_key(dev, BTN_TOUCH, 1);
> >> > else
> >> > input_report_key(dev, BTN_TOUCH, 0);
> >> > @@ -709,22 +763,22 @@ static void alps_process_touchpad_packet_v3_v5(struct psmouse *psmouse)
> >> >
> >> > input_mt_report_finger_count(dev, fingers);
> >> >
> >> > - input_report_key(dev, BTN_LEFT, f.left);
> >> > - input_report_key(dev, BTN_RIGHT, f.right);
> >> > - input_report_key(dev, BTN_MIDDLE, f.middle);
> >> > + input_report_key(dev, BTN_LEFT, f.btn.left);
> >> > + input_report_key(dev, BTN_RIGHT, f.btn.right);
> >> > + input_report_key(dev, BTN_MIDDLE, f.btn.middle);
> >> >
> >> > - if (f.z > 0) {
> >> > - input_report_abs(dev, ABS_X, f.x);
> >> > - input_report_abs(dev, ABS_Y, f.y);
> >> > + if (f.pt.z > 0) {
> >> > + input_report_abs(dev, ABS_X, f.pt.x);
> >> > + input_report_abs(dev, ABS_Y, f.pt.y);
> >> > }
> >> > - input_report_abs(dev, ABS_PRESSURE, f.z);
> >> > + input_report_abs(dev, ABS_PRESSURE, f.pt.z);
> >> >
> >> > input_sync(dev);
> >> >
> >> > if (!(priv->quirks & ALPS_QUIRK_TRACKSTICK_BUTTONS)) {
> >> > - input_report_key(dev2, BTN_LEFT, f.ts_left);
> >> > - input_report_key(dev2, BTN_RIGHT, f.ts_right);
> >> > - input_report_key(dev2, BTN_MIDDLE, f.ts_middle);
> >> > + input_report_key(dev2, BTN_LEFT, f.btn.ts_left);
> >> > + input_report_key(dev2, BTN_RIGHT, f.btn.ts_right);
> >> > + input_report_key(dev2, BTN_MIDDLE, f.btn.ts_middle);
> >> > input_sync(dev2);
> >> > }
> >> > }
> >> > @@ -916,6 +970,364 @@ static void alps_process_packet_v4(struct psmouse *psmouse)
> >> > input_sync(dev);
> >> > }
> >> >
> >> > +static bool alps_is_valid_package_v7(struct psmouse *psmouse)
> >> > +{
> >> > + if ((psmouse->pktcnt == 3) && ((psmouse->packet[2] & 0x40) != 0x40))
> >> > + return false;
> >> > + if ((psmouse->pktcnt == 4) && ((psmouse->packet[3] & 0x48) != 0x48))
> >> > + return false;
> >> > + if ((psmouse->pktcnt == 6) && ((psmouse->packet[5] & 0x40) != 0x0))
> >> > + return false;
> >>
> >> Maybe do:
> >>
> >> switch (psmouse->pktcnt) {
> >> case 3:
> >> if ((psmouse->packet[2] & 0x40) != 0x40)
> >> return false;
> >> break;
> >> ...
> >> }
> >>
> >> > + return true;
> >> > +}
> >> > +
> >> > +static int alps_drop_unsupported_packet_v7(struct psmouse *psmouse)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > + int drop = 1;
> >>
> >> bool drop = true;
> >>
> >> > +
> >> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW ||
> >> > + priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> >> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> >> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> >> > + drop = 0;
> >>
> >> if (... ||
> >> ...) {
> >> drop = false;
> >> }
> >> > +
> >> > + return drop;
> >> > +}
> >> > +
> >> > +static unsigned char alps_get_packet_id_v7(char *byte)
> >> > +{
> >> > + unsigned char packet_id;
> >> > +
> >> > + if (byte[4] & 0x40)
> >> > + packet_id = V7_PACKET_ID_TWO;
> >> > + else if (byte[4] & 0x01)
> >> > + packet_id = V7_PACKET_ID_MULTI;
> >> > + else if ((byte[0] & 0x10) && !(byte[4] & 0x43))
> >> > + packet_id = V7_PACKET_ID_NEW;
> >> > + else
> >> > + packet_id = V7_PACKET_ID_IDLE;
> >> > +
> >> > + return packet_id;
> >> > +}
> >> > +
> >> > +static void alps_get_finger_coordinate_v7(struct alps_abs_data *pt,
> >> > + unsigned char *pkt,
> >> > + unsigned char pkt_id)
> >> > +{
> >> > + if ((pkt_id == V7_PACKET_ID_TWO) ||
> >> > + (pkt_id == V7_PACKET_ID_MULTI) ||
> >> > + (pkt_id == V7_PACKET_ID_NEW)) {
> >> > + pt[0].x = ((pkt[2] & 0x80) << 4);
> >> > + pt[0].x |= ((pkt[2] & 0x3F) << 5);
> >> > + pt[0].x |= ((pkt[3] & 0x30) >> 1);
> >> > + pt[0].x |= (pkt[3] & 0x07);
> >> > + pt[0].y = (pkt[1] << 3) | (pkt[0] & 0x07);
> >> > +
> >> > + pt[1].x = ((pkt[3] & 0x80) << 4);
> >> > + pt[1].x |= ((pkt[4] & 0x80) << 3);
> >> > + pt[1].x |= ((pkt[4] & 0x3F) << 4);
> >> > + pt[1].y = ((pkt[5] & 0x80) << 3);
> >> > + pt[1].y |= ((pkt[5] & 0x3F) << 4);
> >> > +
> >> > + if (pkt_id == V7_PACKET_ID_TWO) {
> >> > + pt[1].x &= ~0x000F;
> >> > + pt[1].y |= 0x000F;
> >> > + } else if (pkt_id == V7_PACKET_ID_MULTI) {
> >> > + pt[1].x &= ~0x003F;
> >> > + pt[1].y &= ~0x0020;
> >> > + pt[1].y |= ((pkt[4] & 0x02) << 4);
> >> > + pt[1].y |= 0x001F;
> >> > + } else if (pkt_id == V7_PACKET_ID_NEW) {
> >> > + pt[1].x &= ~0x003F;
> >> > + pt[1].x |= (pkt[0] & 0x20);
> >> > + pt[1].y |= 0x000F;
> >> > + }
> >> > +
> >> > + pt[0].y = 0x7FF - pt[0].y;
> >> > + pt[1].y = 0x7FF - pt[1].y;
> >> > +
> >> > + pt[0].z = (pt[0].x && pt[0].y) ? 62 : 0;
> >> > + pt[1].z = (pt[1].x && pt[1].y) ? 62 : 0;
> >> > + }
> >> > +}
> >> > +
> >> > +static void alps_decode_packet_v7(struct alps_fields *f,
> >> > + unsigned char *p,
> >> > + struct psmouse *psmouse)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > + static struct v7_raw prev_r;
> >> > +
> >> > + priv->r.v7.pkt_id = alps_get_packet_id_v7(p);
> >> > +
> >> > + alps_get_finger_coordinate_v7(f->pt_img, p, priv->r.v7.pkt_id);
> >> > +
> >> > + priv->r.v7.rest_left = 0;
> >> > + priv->r.v7.rest_right = 0;
> >> > + priv->r.v7.additional_fingers = 0;
> >> > + priv->phy_btn = 0;
> >> > +
> >> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> >> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI) {
> >> > + priv->r.v7.rest_left = (p[0] & 0x10) >> 4;
> >> > + priv->r.v7.rest_right = (p[0] & 0x20) >> 5;
> >> > + }
> >> > +
> >> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> >> > + priv->r.v7.additional_fingers = p[5] & 0x03;
> >> > +
> >> > + priv->phy_btn = (p[0] & 0x80) >> 7;
> >> > +
> >> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO) {
> >> > + if (f->pt_img[0].z != 0 && f->pt_img[1].z != 0)
> >> > + priv->r.v7.raw_fn = 2;
> >> > + else
> >> > + priv->r.v7.raw_fn = 1;
> >> > + } else if (priv->r.v7.pkt_id == V7_PACKET_ID_MULTI)
> >> > + priv->r.v7.raw_fn = 3 + priv->r.v7.additional_fingers;
> >> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_IDLE)
> >> > + priv->r.v7.raw_fn = 0;
> >> > + else if (priv->r.v7.pkt_id == V7_PACKET_ID_NEW)
> >> > + priv->r.v7.raw_fn = prev_r.raw_fn;
> >> > +
> >> > + /* It is a trick to bypass firmware bug of older version
> >> > + that 'New' Packet is missed when finger number changed.
> >> > + We fake a 'New' Packet in such cases.*/
> >>
> >> Multi-line comments should be formatted as follows:
> >>
> >> /*
> >> * This is a multi
> >> * line comment.
> >> */
> >>
> >>
> >> > + if (priv->r.v7.pkt_id == V7_PACKET_ID_TWO ||
> >> > + priv->r.v7.pkt_id == V7_PACKET_ID_MULTI ||
> >> > + priv->r.v7.pkt_id == V7_PACKET_ID_IDLE) {
> >> > + if (priv->r.v7.raw_fn != prev_r.raw_fn)
> >> > + priv->r.v7.pkt_id = V7_PACKET_ID_NEW;
> >> > + }
> >> > +
> >> > + memcpy(&prev_r, &priv->r.v7, sizeof(struct v7_raw));
> >> > +}
> >> > +
> >> > +static void alps_set_each_pt_attr_v7(struct psmouse *psmouse,
> >> > + struct alps_abs_data *pt,
> >> > + struct alps_bl_pt_attr *pt_attr)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > + unsigned int dist;
> >> > +
> >> > + if (!pt_attr->is_init_pt_got && pt->z != 0) {
> >> > + pt_attr->is_init_pt_got = 1;
> >> > + pt_attr->is_counted = 0;
> >> > + memcpy(&pt_attr->init_pt, pt, sizeof(pt_attr->init_pt));
> >> > + }
> >> > +
> >> > + if (pt->z != 0) {
> >> > + if (pt->y < priv->resting_zone_y_min) {
> >> > + /* A finger is recognized as a non-resting finger
> >> > + if it's position is outside the resting finger zone.*/
> >> > + pt_attr->zone = ZONE_NORMAL;
> >> > + pt_attr->is_counted = 1;
> >> > + } else {
> >> > + /* A finger is recognized as a resting finger if it's
> >> > + position is inside the resting finger zone and there's
> >> > + no large movement from it's touch down position.*/
> >> > + pt_attr->zone = ZONE_RESTING;
> >> > +
> >> > + if (pt->x > priv->x_max / 2)
> >> > + pt_attr->zone |= ZONE_RIGHT_BTN;
> >> > + else
> >> > + pt_attr->zone |= ZONE_LEFT_BTN;
> >> > +
> >> > + /* A resting finger will turn to be a non-resting
> >> > + finger if it has made large movement from it's touch
> >> > + down position. A non-resting finger will never turn
> >> > + to a resting finger before it leaves the touchpad
> >> > + surface.*/
> >> > + if (pt_attr->is_init_pt_got) {
> >> > + dist = alps_pt_distance(pt, &pt_attr->init_pt);
> >> > +
> >> > + if (dist > V7_LARGE_MOVEMENT)
> >> > + pt_attr->is_counted = 1;
> >> > + }
> >> > + }
> >> > + }
> >> > +}
> >> > +
> >> > +static void alps_set_pt_attr_v7(struct psmouse *psmouse,
> >> > + struct alps_fields *f)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > + int i;
> >> > +
> >> > + switch (priv->r.v7.pkt_id) {
> >> > + case V7_PACKET_ID_TWO:
> >> > + case V7_PACKET_ID_MULTI:
> >> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> >> > + alps_set_each_pt_attr_v7(psmouse,
> >> > + &f->pt_img[i],
> >> > + &priv->pt_attr[i]);
> >> > + }
> >> > + break;
> >> > + default:
> >> > + /*All finger attributes are cleared when packet ID is
> >> > + 'IDLE', 'New'or other unknown IDs. An 'IDLE' packet
> >> > + indicates that there's no finger and no button activity.
> >> > + A 'NEW' packet indicates the finger position in packet
> >> > + is not continues from previous packet. Such as the
> >> > + condition there's finger placed or lifted. In these cases,
> >> > + finger attributes will be reset.*/
> >> > + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> >> > + break;
> >> > + }
> >> > +}
> >> > +
> >> > +static void alps_cal_output_finger_num_v7(struct psmouse *psmouse,
> >> > + struct alps_fields *f)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > + unsigned int fn = 0;
> >> > + int i;
> >> > +
> >> > + switch (priv->r.v7.pkt_id) {
> >> > + case V7_PACKET_ID_IDLE:
> >> > + case V7_PACKET_ID_NEW:
> >> > + /*No finger is reported when packet ID is 'IDLE' or 'New'.
> >> > + An 'IDLE' packet indicates that there's no finger on touchpad.
> >> > + A 'NEW' packet indicates there's finger placed or lifted.
> >> > + Finger position of 'New' packet is not continues from the
> >> > + previous packet.*/
> >> > + fn = 0;
> >> > + break;
> >> > + case V7_PACKET_ID_TWO:
> >> > + if (f->pt_img[0].z == 0) {
> >> > + /*The first finger slot is zero when a non-resting
> >> > + finger lifted and remaining only one resting finger
> >> > + on touchpad. Hardware report the remaining resting
> >> > + finger in second slot. This resting is ignored*/
> >> > + fn = 0;
> >> > + } else if (f->pt_img[1].z == 0) {
> >> > + /* The second finger slot is zero if there's
> >> > + only one finger*/
> >> > + fn = 1;
> >> > + } else {
> >> > + /*All non-resting fingers will be counted to report*/
> >> > + fn = 0;
> >> > + for (i = 0; i < V7_IMG_PT_NUM; i++) {
> >> > + if (priv->pt_attr[i].is_counted)
> >> > + fn++;
> >> > + }
> >> > +
> >> > + /*In the case that both fingers are
> >> > + resting fingers, report the first one*/
> >> > + if (!priv->pt_attr[0].is_counted &&
> >> > + !priv->pt_attr[1].is_counted) {
> >> > + fn = 1;
> >> > + }
> >> > + }
> >> > + break;
> >> > + case V7_PACKET_ID_MULTI:
> >> > + /*A packet ID 'MULTI' indicats that at least 3 non-resting
> >> > + finger exist.*/
> >> > + fn = 3 + priv->r.v7.additional_fingers;
> >> > + break;
> >> > + }
> >> > +
> >> > + f->fingers = fn;
> >> > +}
> >> > +
> >> > +static void alps_button_dead_zone_filter(struct psmouse *psmouse,
> >> > + struct alps_fields *f,
> >> > + struct alps_fields *prev_f)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > + int dx, dy;
> >> > +
> >> > + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0) {
> >> > + memcpy(&priv->pt_attr[0].init_dead_pt,
> >> > + &f->pt_img[0],
> >> > + sizeof(struct alps_abs_data));
> >> > + }
> >> > +
> >> > + if (priv->pt_attr[0].init_dead_pt.x != 0 &&
> >> > + priv->pt_attr[0].init_dead_pt.x != 0) {
> >> > + dx = f->pt_img[0].x - priv->pt_attr[0].init_dead_pt.x;
> >> > + dy = f->pt_img[0].y - priv->pt_attr[0].init_dead_pt.y;
> >> > + if ((abs(dx) > V7_DEAD_ZONE_OFFSET_X) ||
> >> > + (abs(dy) > V7_DEAD_ZONE_OFFSET_Y)) {
> >> > + memset(&priv->pt_attr[0].init_dead_pt, 0,
> >> > + sizeof(struct alps_abs_data));
> >> > + priv->btn_delay_cnt = 0;
> >> > + } else {
> >> > + memcpy(&f->pt_img[0],
> >> > + &prev_f->pt_img[0],
> >> > + sizeof(struct alps_abs_data));
> >> > + if (priv->prev_phy_btn == 0 && priv->phy_btn != 0)
> >> > + priv->btn_delay_cnt = 2;
> >> > + }
> >> > + }
> >> > +
> >> > + if (priv->btn_delay_cnt > 0) {
> >> > + f->btn.left = 0;
> >> > + f->btn.right = 0;
> >> > + priv->btn_delay_cnt--;
> >> > + }
> >> > +}
> >> > +
> >> > +static void alps_assign_buttons_v7(struct psmouse *psmouse,
> >> > + struct alps_fields *f,
> >> > + struct alps_fields *prev_f)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > +
> >> > + if (priv->phy_btn) {
> >> > + if (!priv->prev_phy_btn) {
> >> > + /* Report a right click as long as there's finger on
> >> > + right button zone. Othrewise, report a left click.*/
> >> > + if (priv->r.v7.rest_right ||
> >> > + priv->pt_attr[0].zone & ZONE_RIGHT_BTN ||
> >> > + priv->pt_attr[1].zone & ZONE_RIGHT_BTN) {
> >> > + f->btn.right = 1;
> >> > + priv->pressed_btn_bits |= RIGHT_BUTTON_BIT;
> >> > + } else {
> >> > + f->btn.left = 1;
> >> > + priv->pressed_btn_bits |= LEFT_BUTTON_BIT;
> >> > + }
> >> > + } else {
> >> > + if (priv->pressed_btn_bits & RIGHT_BUTTON_BIT)
> >> > + f->btn.right = 1;
> >> > + if (priv->pressed_btn_bits & LEFT_BUTTON_BIT)
> >> > + f->btn.left = 1;
> >> > + }
> >> > + } else {
> >> > + priv->pressed_btn_bits = 0;
> >> > + f->btn.right = 0;
> >> > + f->btn.left = 0;
> >> > + }
> >> > +
> >> > + alps_button_dead_zone_filter(psmouse, f, prev_f);
> >> > +
> >> > + priv->prev_phy_btn = priv->phy_btn;
> >> > +}
> >> > +
> >> > +static void alps_process_packet_v7(struct psmouse *psmouse)
> >> > +{
> >> > + struct alps_data *priv = psmouse->private;
> >> > + struct alps_fields f = {0};
> >> > + static struct alps_fields prev_f;
> >> > + unsigned char *packet = psmouse->packet;
> >> > +
> >> > + priv->decode_fields(&f, packet, psmouse);
> >> > +
> >> > + if (alps_drop_unsupported_packet_v7(psmouse))
> >> > + return;
> >> > +
> >> > + alps_set_pt_attr_v7(psmouse, &f);
> >> > +
> >> > + alps_cal_output_finger_num_v7(psmouse, &f);
> >> > +
> >> > + alps_assign_buttons_v7(psmouse, &f, &prev_f);
> >> > +
> >> > + alps_report_coord_and_btn(psmouse, &f);
> >> > +
> >> > + memcpy(&prev_f, &f, sizeof(struct alps_fields));
> >> > +}
> >> > +
> >> > static void alps_report_bare_ps2_packet(struct psmouse *psmouse,
> >> > unsigned char packet[],
> >> > bool report_buttons)
> >> > @@ -1080,6 +1492,14 @@ static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
> >> > return PSMOUSE_BAD_DATA;
> >> > }
> >> >
> >> > + if ((priv->proto_version == ALPS_PROTO_V7 &&
> >> > + !alps_is_valid_package_v7(psmouse))) {
> >> > + psmouse_dbg(psmouse, "refusing packet[%i] = %x\n",
> >> > + psmouse->pktcnt - 1,
> >> > + psmouse->packet[psmouse->pktcnt - 1]);
> >> > + return PSMOUSE_BAD_DATA;
> >> > + }
> >> > +
> >> > if (psmouse->pktcnt == psmouse->pktsize) {
> >> > priv->process_packet(psmouse);
> >> > return PSMOUSE_FULL_PACKET;
> >> > @@ -1192,6 +1612,22 @@ static int alps_rpt_cmd(struct psmouse *psmouse, int init_command,
> >> > return 0;
> >> > }
> >> >
> >> > +static int alps_check_valid_firmware_id(unsigned char id[])
> >> > +{
> >> > + int valid = 1;
> >>
> >> bool valid = true;
> >>
> >> > +
> >> > + if (id[0] == 0x73)
> >> > + valid = 1;
> >> > + else if (id[0] == 0x88) {
> >> > + if ((id[1] == 0x07) ||
> >> > + (id[1] == 0x08) ||
> >> > + ((id[1] & 0xf0) == 0xB0))
> >> > + valid = 1;
> >> > + }
> >> > +
> >> > + return valid;
> >> > +}
> >> > +
> >> > static int alps_enter_command_mode(struct psmouse *psmouse)
> >> > {
> >> > unsigned char param[4];
> >> > @@ -1201,8 +1637,7 @@ static int alps_enter_command_mode(struct psmouse *psmouse)
> >> > return -1;
> >> > }
> >> >
> >> > - if ((param[0] != 0x88 || (param[1] != 0x07 && param[1] != 0x08)) &&
> >> > - param[0] != 0x73) {
> >> > + if (!alps_check_valid_firmware_id(param)) {
> >> > psmouse_dbg(psmouse,
> >> > "unknown response while entering command mode\n");
> >> > return -1;
> >> > @@ -1704,6 +2139,36 @@ error:
> >> > return ret;
> >> > }
> >> >
> >> > +static int alps_hw_init_v7(struct psmouse *psmouse)
> >> > +{
> >> > + struct ps2dev *ps2dev = &psmouse->ps2dev;
> >> > + int reg_val, ret = -1;
> >> > +
> >> > + if (alps_enter_command_mode(psmouse))
> >> > + goto error;
> >> > +
> >> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2d9);
> >> > + if (reg_val == -1)
> >> > + goto error;
> >> > +
> >> > + if (alps_command_mode_write_reg(psmouse, 0xc2c9, 0x64))
> >> > + goto error;
> >> > +
> >> > + reg_val = alps_command_mode_read_reg(psmouse, 0xc2c4);
> >> > + if (reg_val == -1)
> >> > + goto error;
> >> > +
> >> > + if (__alps_command_mode_write_reg(psmouse, reg_val | 0x02))
> >> > + goto error;
> >> > +
> >> > + alps_exit_command_mode(psmouse);
> >> > + return ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE);
> >> > +
> >> > +error:
> >> > + alps_exit_command_mode(psmouse);
> >> > + return ret;
> >> > +}
> >> > +
> >> > /* Must be in command mode when calling this function */
> >> > static int alps_absolute_mode_v4(struct psmouse *psmouse)
> >> > {
> >> > @@ -1875,6 +2340,7 @@ static void alps_set_defaults(struct alps_data *priv)
> >> > priv->set_abs_params = alps_set_abs_params_st;
> >> > priv->x_max = 1023;
> >> > priv->y_max = 767;
> >> > + priv->slot_number = 1;
> >> > break;
> >> > case ALPS_PROTO_V3:
> >> > priv->hw_init = alps_hw_init_v3;
> >> > @@ -1883,6 +2349,7 @@ static void alps_set_defaults(struct alps_data *priv)
> >> > priv->decode_fields = alps_decode_pinnacle;
> >> > priv->nibble_commands = alps_v3_nibble_commands;
> >> > priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> >> > + priv->slot_number = 2;
> >> > break;
> >> > case ALPS_PROTO_V4:
> >> > priv->hw_init = alps_hw_init_v4;
> >> > @@ -1890,6 +2357,7 @@ static void alps_set_defaults(struct alps_data *priv)
> >> > priv->set_abs_params = alps_set_abs_params_mt;
> >> > priv->nibble_commands = alps_v4_nibble_commands;
> >> > priv->addr_command = PSMOUSE_CMD_DISABLE;
> >> > + priv->slot_number = 2;
> >> > break;
> >> > case ALPS_PROTO_V5:
> >> > priv->hw_init = alps_hw_init_dolphin_v1;
> >> > @@ -1905,6 +2373,7 @@ static void alps_set_defaults(struct alps_data *priv)
> >> > priv->y_max = 660;
> >> > priv->x_bits = 23;
> >> > priv->y_bits = 12;
> >> > + priv->slot_number = 2;
> >> > break;
> >> > case ALPS_PROTO_V6:
> >> > priv->hw_init = alps_hw_init_v6;
> >> > @@ -1913,6 +2382,28 @@ static void alps_set_defaults(struct alps_data *priv)
> >> > priv->nibble_commands = alps_v6_nibble_commands;
> >> > priv->x_max = 2047;
> >> > priv->y_max = 1535;
> >> > + priv->slot_number = 2;
> >> > + break;
> >> > + case ALPS_PROTO_V7:
> >> > + priv->hw_init = alps_hw_init_v7;
> >> > + priv->process_packet = alps_process_packet_v7;
> >> > + priv->decode_fields = alps_decode_packet_v7;
> >> > + priv->set_abs_params = alps_set_abs_params_mt;
> >> > + priv->nibble_commands = alps_v3_nibble_commands;
> >> > + priv->addr_command = PSMOUSE_CMD_RESET_WRAP;
> >> > + priv->x_max = 0xfff;
> >> > + priv->y_max = 0x7ff;
> >> > + priv->resting_zone_y_min = 0x654;
> >> > + priv->byte0 = 0x48;
> >> > + priv->mask0 = 0x48;
> >> > + priv->flags = 0;
> >> > + priv->slot_number = 2;
> >> > +
> >> > + priv->phy_btn = 0;
> >> > + priv->prev_phy_btn = 0;
> >> > + priv->btn_delay_cnt = 0;
> >> > + priv->pressed_btn_bits = 0;
> >> > + memset(priv->pt_attr, 0, sizeof(priv->pt_attr[0]) * 2);
> >> > break;
> >> > }
> >> > }
> >> > @@ -1982,6 +2473,11 @@ static int alps_identify(struct psmouse *psmouse, struct alps_data *priv)
> >> > return -EIO;
> >> > else
> >> > return 0;
> >> > + } else if (ec[0] == 0x88 && (ec[1] & 0xf0) == 0xB0) {
> >> > + priv->proto_version = ALPS_PROTO_V7;
> >> > + alps_set_defaults(priv);
> >> > +
> >> > + return 0;
> >> > } else if (ec[0] == 0x88 && ec[1] == 0x08) {
> >> > priv->proto_version = ALPS_PROTO_V3;
> >> > alps_set_defaults(priv);
> >> > @@ -2045,7 +2541,7 @@ static void alps_set_abs_params_mt(struct alps_data *priv,
> >> > struct input_dev *dev1)
> >> > {
> >> > set_bit(INPUT_PROP_SEMI_MT, dev1->propbit);
> >> > - input_mt_init_slots(dev1, 2, 0);
> >> > + input_mt_init_slots(dev1, priv->slot_number, 0);
> >> > input_set_abs_params(dev1, ABS_MT_POSITION_X, 0, priv->x_max, 0, 0);
> >> > input_set_abs_params(dev1, ABS_MT_POSITION_Y, 0, priv->y_max, 0, 0);
> >> >
> >> > diff --git a/drivers/input/mouse/alps.h b/drivers/input/mouse/alps.h
> >> > index 03f88b6..dedbd27 100644
> >> > --- a/drivers/input/mouse/alps.h
> >> > +++ b/drivers/input/mouse/alps.h
> >> > @@ -18,11 +18,36 @@
> >> > #define ALPS_PROTO_V4 4
> >> > #define ALPS_PROTO_V5 5
> >> > #define ALPS_PROTO_V6 6
> >> > +#define ALPS_PROTO_V7 7
> >> > +
> >> > +#define MAX_IMG_PT_NUM 5
> >> > +#define V7_IMG_PT_NUM 2
> >> > +
> >> > +#define ZONE_NORMAL 0x01
> >> > +#define ZONE_RESTING 0x02
> >> > +#define ZONE_LEFT_BTN 0x04
> >> > +#define ZONE_RIGHT_BTN 0x08
> >> >
> >> > #define DOLPHIN_COUNT_PER_ELECTRODE 64
> >> > #define DOLPHIN_PROFILE_XOFFSET 8 /* x-electrode offset */
> >> > #define DOLPHIN_PROFILE_YOFFSET 1 /* y-electrode offset */
> >> >
> >> > +/*
> >> > + * enum V7_PACKET_ID - defines the packet type for V7
> >> > + * V7_PACKET_ID_IDLE: There's no finger and no button activity.
> >> > + * V7_PACKET_ID_TWO: There's one or two non-resting fingers on touchpad
> >> > + * or there's button activities.
> >> > + * V7_PACKET_ID_MULTI: There are at least three non-resting fingers.
> >> > + * V7_PACKET_ID_NEW: The finger position in slot is not continues from
> >> > + * previous packet.
> >> > +*/
> >> > +enum V7_PACKET_ID {
> >> > + V7_PACKET_ID_IDLE,
> >> > + V7_PACKET_ID_TWO,
> >> > + V7_PACKET_ID_MULTI,
> >> > + V7_PACKET_ID_NEW,
> >> > +};
> >> > +
> >> > /**
> >> > * struct alps_model_info - touchpad ID table
> >> > * @signature: E7 response string to match.
> >> > @@ -66,15 +91,7 @@ struct alps_nibble_commands {
> >> > };
> >> >
> >> > /**
> >> > - * struct alps_fields - decoded version of the report packet
> >> > - * @x_map: Bitmap of active X positions for MT.
> >> > - * @y_map: Bitmap of active Y positions for MT.
> >> > - * @fingers: Number of fingers for MT.
> >> > - * @x: X position for ST.
> >> > - * @y: Y position for ST.
> >> > - * @z: Z position for ST.
> >> > - * @first_mp: Packet is the first of a multi-packet report.
> >> > - * @is_mp: Packet is part of a multi-packet report.
> >> > + * struct alps_btn - decoded version of the button status
> >> > * @left: Left touchpad button is active.
> >> > * @right: Right touchpad button is active.
> >> > * @middle: Middle touchpad button is active.
> >> > @@ -82,16 +99,7 @@ struct alps_nibble_commands {
> >> > * @ts_right: Right trackstick button is active.
> >> > * @ts_middle: Middle trackstick button is active.
> >> > */
> >> > -struct alps_fields {
> >> > - unsigned int x_map;
> >> > - unsigned int y_map;
> >> > - unsigned int fingers;
> >> > - unsigned int x;
> >> > - unsigned int y;
> >> > - unsigned int z;
> >> > - unsigned int first_mp:1;
> >> > - unsigned int is_mp:1;
> >> > -
> >> > +struct alps_btn {
> >> > unsigned int left:1;
> >> > unsigned int right:1;
> >> > unsigned int middle:1;
> >> > @@ -102,6 +110,73 @@ struct alps_fields {
> >> > };
> >> >
> >> > /**
> >> > + * struct alps_btn - decoded version of the X Y Z postion for ST.
> >> > + * @x: X position for ST.
> >> > + * @y: Y position for ST.
> >> > + * @z: Z position for ST.
> >> > + */
> >> > +struct alps_abs_data {
> >> > + unsigned int x;
> >> > + unsigned int y;
> >> > + unsigned int z;
> >> > +};
> >> > +
> >> > +/**
> >> > + * struct alps_fields - decoded version of the report packet
> >> > + * @fingers: Number of fingers for MT.
> >> > + * @pt: X Y Z postion for ST.
> >> > + * @pt: X Y Z postion for image MT.
> >> > + * @x_map: Bitmap of active X positions for MT.
> >> > + * @y_map: Bitmap of active Y positions for MT.
> >> > + * @first_mp: Packet is the first of a multi-packet report.
> >> > + * @is_mp: Packet is part of a multi-packet report.
> >> > + * @btn: Button activity status
> >> > + */
> >> > +struct alps_fields {
> >> > + unsigned int fingers;
> >> > + struct alps_abs_data pt;
> >> > + struct alps_abs_data pt_img[MAX_IMG_PT_NUM];
> >> > + unsigned int x_map;
> >> > + unsigned int y_map;
> >> > + unsigned int first_mp:1;
> >> > + unsigned int is_mp:1;
> >> > + struct alps_btn btn;
> >>
> >> Splitting button data and abs data into separate structures might make
> >> sense, but please split off these changes into a separate patch so that
> >> they are not obscuring changes necessary for v7 support.
> >>
> >> > +};
> >> > +
> >> > +/**
> >> > + * struct v7_raw - data decoded from raw packet for V7.
> >> > + * @pkt_id: An id that specifies the type of packet.
> >> > + * @additional_fingers: Number of additional finger that is neighter included
> >> > + * in pt slot nor reflected in rest_left and rest_right flag of data packet.
> >> > + * @rest_left: There are fingers on left resting zone.
> >> > + * @rest_right: There are fingers on right resting zone.
> >> > + * @raw_fn: The number of finger on touchpad.
> >> > + */
> >> > +struct v7_raw {
> >> > + unsigned char pkt_id;
> >> > + unsigned int additional_fingers;
> >> > + unsigned char rest_left;
> >> > + unsigned char rest_right;
> >> > + unsigned char raw_fn;
> >> > +};
> >> > +
> >> > +/**
> >> > + * struct alps_bl_pt_attr - generic attributes of touch points for buttonless device
> >> > + * @zone: The part of touchpad that the touch point locates
> >> > + * @is_counted: The touch point is not a resting finger.
> >> > + * @is_init_pt_got: The touch down point is got.
> >> > + * @init_pt: The X Y Z position of the touch down point.
> >> > + * @init_dead_pt: The touch down point of a finger used by dead zone process.
> >> > + */
> >> > +struct alps_bl_pt_attr {
> >> > + unsigned char zone;
> >> > + unsigned char is_counted;
> >> > + unsigned char is_init_pt_got;
> >> > + struct alps_abs_data init_pt;
> >> > + struct alps_abs_data init_dead_pt;
> >> > +};
> >> > +
> >> > +/**
> >> > * struct alps_data - private data structure for the ALPS driver
> >> > * @dev2: "Relative" device used to report trackstick or mouse activity.
> >> > * @phys: Physical path for the relative device.
> >> > @@ -116,8 +191,10 @@ struct alps_fields {
> >> > * @flags: Additional device capabilities (passthrough port, trackstick, etc.).
> >> > * @x_max: Largest possible X position value.
> >> > * @y_max: Largest possible Y position value.
> >> > + * @resting_zone_y_min: Smallest Y postion value of the bottom resting zone.
> >> > * @x_bits: Number of X bits in the MT bitmap.
> >> > * @y_bits: Number of Y bits in the MT bitmap.
> >> > + * @img_fingers: Number of image fingers.
> >> > * @hw_init: Protocol-specific hardware init function.
> >> > * @process_packet: Protocol-specific function to process a report packet.
> >> > * @decode_fields: Protocol-specific function to read packet bitfields.
> >> > @@ -132,6 +209,11 @@ struct alps_fields {
> >> > * @fingers: Number of fingers from last MT report.
> >> > * @quirks: Bitmap of ALPS_QUIRK_*.
> >> > * @timer: Timer for flushing out the final report packet in the stream.
> >> > + * @v7: Data decoded from raw packet for V7
> >> > + * @phy_btn: Physical button is active.
> >> > + * @prev_phy_btn: Physical button of previous packet is active.
> >> > + * @pressed_btn_bits: Pressed positon of button zone
> >> > + * @pt_attr: Generic attributes of touch points for buttonless device.
> >> > */
> >> > struct alps_data {
> >> > struct input_dev *dev2;
> >> > @@ -145,8 +227,10 @@ struct alps_data {
> >> > unsigned char flags;
> >> > int x_max;
> >> > int y_max;
> >> > + int resting_zone_y_min;
> >> > int x_bits;
> >> > int y_bits;
> >> > + unsigned char slot_number;
> >> >
> >> > int (*hw_init)(struct psmouse *psmouse);
> >> > void (*process_packet)(struct psmouse *psmouse);
> >> > @@ -161,6 +245,16 @@ struct alps_data {
> >> > int fingers;
> >> > u8 quirks;
> >> > struct timer_list timer;
> >> > +
> >> > + /* these are used for buttonless touchpad*/
> >> > + union {
> >> > + struct v7_raw v7;
> >> > + } r;
> >>
> >> Why do you need a union here? A single-field union does not seem to be
> >> terribly useful.
> >>
> >>
> >> > + unsigned char phy_btn;
> >> > + unsigned char prev_phy_btn;
> >> > + unsigned char btn_delay_cnt;
> >> > + unsigned char pressed_btn_bits;
> >> > + struct alps_bl_pt_attr pt_attr[MAX_IMG_PT_NUM];
> >> > };
> >> >
> >> > #define ALPS_QUIRK_TRACKSTICK_BUTTONS 1 /* trakcstick buttons in trackstick packet */
> >> > --
> >> > 1.8.3.2
> >> >
> >>
> >> Thanks.
> >>
> >> --
> >> Dmitry
^ permalink raw reply
* RE: [PATCH 0/6] input: cyapa: integrated with gen5 trackpad supported in one driver.
From: Dudley Du @ 2014-04-22 8:39 UTC (permalink / raw)
To: Dmitry Torokhov (dmitry.torokhov@gmail.com)
Cc: Benson Leung, Daniel Kurtz, David Solda,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <77BC725C9062764F874D79F51E1F1A8F40C1404A@S04-MBX01-01.s04.local>
Hi Dmitry,
Could you help review the patches of input: cyapa for re-architecture and supporting new trackpad devices, I'm really looking forward your responses.
Thanks,
Dudley
> This patch set is made based on kernel 3.14.0. It's aimed to re-architecture the cyapa driver to support the old gen3 trackpad device and new gen5 trackpad device in one cyapa driver for easily products support based on customers' requirements, and add sysfs functions and interfaces supported that required by users and customers.
>
> Beside this patch, it has 6 patches listed as below.
> For these patches each one is patched based on previous one.
>
> patch 1/6: change the architecture of cyapa driver to support function pointers and applying the device proble function in async thread to speed up system boot time.
>
> patch 2/6: add gen5 trackpad devices supported in cyapa driver.
>
> patch 3/6: add full power mode and runtime power mode supported.
>
> patch 4/6: enable/disable trackpad device based on LID state.
>
> patch 5/6: add sysfs interfaces supported for gen3 trackpad device.
>
> patch 6/6: add sysfs interfaces supported for gen5 trackpad device.
This message and any attachments may contain Cypress (or its subsidiaries) confidential information. If it has been received in error, please advise the sender and immediately delete this message.
^ permalink raw reply
* [PATCH 02/24] Port arizona-haptics to ff-memless-next
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/arizona-haptics.c | 39 +++++++++++++++++++++++-------------
2 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7904ab0..33e0f5d 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -75,7 +75,7 @@ config INPUT_AD714X_SPI
config INPUT_ARIZONA_HAPTICS
tristate "Arizona haptics support"
depends on MFD_ARIZONA && SND_SOC
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
help
Say Y to enable support for the haptics module in Arizona CODECs.
diff --git a/drivers/input/misc/arizona-haptics.c b/drivers/input/misc/arizona-haptics.c
index ef2e281..b4707cc 100644
--- a/drivers/input/misc/arizona-haptics.c
+++ b/drivers/input/misc/arizona-haptics.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/input.h>
+#include <linux/input/ff-memless-next.h>
#include <linux/slab.h>
#include <sound/soc.h>
@@ -22,6 +23,8 @@
#include <linux/mfd/arizona/pdata.h>
#include <linux/mfd/arizona/registers.h>
+#define FF_UPDATE_RATE 50
+
struct arizona_haptics {
struct arizona *arizona;
struct input_dev *input_dev;
@@ -108,29 +111,37 @@ static void arizona_haptics_work(struct work_struct *work)
}
static int arizona_haptics_play(struct input_dev *input, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
struct arizona_haptics *haptics = input_get_drvdata(input);
struct arizona *arizona = haptics->arizona;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
if (!arizona->dapm) {
dev_err(arizona->dev, "No DAPM context\n");
return -EBUSY;
}
- if (effect->u.rumble.strong_magnitude) {
- /* Scale the magnitude into the range the device supports */
- if (arizona->pdata.hap_act) {
- haptics->intensity =
- effect->u.rumble.strong_magnitude >> 9;
- if (effect->direction < 0x8000)
- haptics->intensity += 0x7f;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ if (rumble_force->strong) {
+ /* Scale the magnitude into the range the device supports */
+ if (arizona->pdata.hap_act) {
+ haptics->intensity = rumble_force->strong >> 9;
+ if (rumble_force->strong_dir < 0x8000)
+ haptics->intensity += 0x7f;
+ } else {
+ haptics->intensity = rumble_force->strong >> 8;
+ }
} else {
- haptics->intensity =
- effect->u.rumble.strong_magnitude >> 8;
+ haptics->intensity = 0;
}
- } else {
+ break;
+ case MLNX_STOP_RUMBLE:
haptics->intensity = 0;
+ break;
+ default:
+ return -EINVAL;
}
schedule_work(&haptics->work);
@@ -183,10 +194,10 @@ static int arizona_haptics_probe(struct platform_device *pdev)
haptics->input_dev->close = arizona_haptics_close;
__set_bit(FF_RUMBLE, haptics->input_dev->ffbit);
- ret = input_ff_create_memless(haptics->input_dev, NULL,
- arizona_haptics_play);
+ ret = input_ff_create_mlnx(haptics->input_dev, NULL,
+ arizona_haptics_play, FF_UPDATE_RATE);
if (ret < 0) {
- dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n",
+ dev_err(arizona->dev, "input_ff_create_mlnx() failed: %d\n",
ret);
goto err_ialloc;
}
--
1.9.2
^ permalink raw reply related
* [PATCH 03/24] Port twl4030-vibra to ff-memless-next
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/twl4030-vibra.c | 31 +++++++++++++++++++++++++------
2 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 33e0f5d..1016a1b 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -407,7 +407,7 @@ config INPUT_TWL4030_VIBRA
tristate "Support for TWL4030 Vibrator"
depends on TWL4030_CORE
select MFD_TWL4030_AUDIO
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
help
This option enables support for TWL4030 Vibrator Driver.
diff --git a/drivers/input/misc/twl4030-vibra.c b/drivers/input/misc/twl4030-vibra.c
index 960ef2a..5274ad2 100644
--- a/drivers/input/misc/twl4030-vibra.c
+++ b/drivers/input/misc/twl4030-vibra.c
@@ -32,9 +32,11 @@
#include <linux/mfd/twl4030-audio.h>
#include <linux/input.h>
#include <linux/slab.h>
+#include <linux/input/ff-memless-next.h>
/* MODULE ID2 */
#define LEDEN 0x00
+#define FF_UPDATE_RATE 50
/* ForceFeedback */
#define EFFECT_DIR_180_DEG 0x8000 /* range is 0 - 0xFFFF */
@@ -134,14 +136,31 @@ static void vibra_play_work(struct work_struct *work)
/*** Input/ForceFeedback ***/
static int vibra_play(struct input_dev *input, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
struct vibra_info *info = input_get_drvdata(input);
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ if (command->u.rumble_force.strong) {
+ info->speed = rumble_force->strong >> 8;
+ info->direction = rumble_force->strong_dir < EFFECT_DIR_180_DEG ?
+ 0 : 1;
+ } else {
+ info->speed = rumble_force->weak >> 9;
+ info->direction = rumble_force->weak_dir < EFFECT_DIR_180_DEG ?
+ 0 : 1;
+ }
+ break;
+ case MLNX_STOP_RUMBLE:
+ info->speed = 0;
+ info->direction = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
- info->speed = effect->u.rumble.strong_magnitude >> 8;
- if (!info->speed)
- info->speed = effect->u.rumble.weak_magnitude >> 9;
- info->direction = effect->direction < EFFECT_DIR_180_DEG ? 0 : 1;
schedule_work(&info->play_work);
return 0;
}
@@ -227,7 +246,7 @@ static int twl4030_vibra_probe(struct platform_device *pdev)
info->input_dev->close = twl4030_vibra_close;
__set_bit(FF_RUMBLE, info->input_dev->ffbit);
- ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
+ ret = input_ff_create_mlnx(info->input_dev, NULL, vibra_play, FF_UPDATE_RATE);
if (ret < 0) {
dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n");
return ret;
--
1.9.2
^ permalink raw reply related
* [PATCH 04/24] Port twl6040-vibra to ff-memless-next
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/twl6040-vibra.c | 27 ++++++++++++++++++++++-----
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 1016a1b..91dcae8 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -417,7 +417,7 @@ config INPUT_TWL4030_VIBRA
config INPUT_TWL6040_VIBRA
tristate "Support for TWL6040 Vibrator"
depends on TWL6040_CORE
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
help
This option enables support for TWL6040 Vibrator Driver.
diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c
index 77dc23b..7440a74 100644
--- a/drivers/input/misc/twl6040-vibra.c
+++ b/drivers/input/misc/twl6040-vibra.c
@@ -30,12 +30,14 @@
#include <linux/of.h>
#include <linux/workqueue.h>
#include <linux/input.h>
+#include <linux/input/ff-memless-next.h>
#include <linux/mfd/twl6040.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/regulator/consumer.h>
#define EFFECT_DIR_180_DEG 0x8000
+#define FF_UPDATE_RATE 50
/* Recommended modulation index 85% */
#define TWL6040_VIBRA_MOD 85
@@ -197,9 +199,10 @@ static void vibra_play_work(struct work_struct *work)
}
static int vibra_play(struct input_dev *input, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
struct vibra_info *info = input_get_drvdata(input);
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
int ret;
/* Do not allow effect, while the routing is set to use audio */
@@ -209,9 +212,23 @@ static int vibra_play(struct input_dev *input, void *data,
return -EBUSY;
}
- info->weak_speed = effect->u.rumble.weak_magnitude;
- info->strong_speed = effect->u.rumble.strong_magnitude;
- info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ info->weak_speed = rumble_force->weak;
+ info->strong_speed = rumble_force->strong;
+ if (info->strong_speed >= info->weak_speed)
+ info->direction = rumble_force->strong_dir < EFFECT_DIR_180_DEG ? 1 : -1;
+ else
+ info->direction = rumble_force->weak_dir < EFFECT_DIR_180_DEG ? 1 : -1;
+ break;
+ case MLNX_STOP_RUMBLE:
+ info->weak_speed = 0;
+ info->strong_speed = 0;
+ info->direction = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
ret = queue_work(info->workqueue, &info->play_work);
if (!ret) {
@@ -367,7 +384,7 @@ static int twl6040_vibra_probe(struct platform_device *pdev)
info->input_dev->close = twl6040_vibra_close;
__set_bit(FF_RUMBLE, info->input_dev->ffbit);
- ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
+ ret = input_ff_create_mlnx(info->input_dev, NULL, vibra_play, FF_UPDATE_RATE);
if (ret < 0) {
dev_err(info->dev, "couldn't register vibrator to FF\n");
goto err_ialloc;
--
1.9.2
^ permalink raw reply related
* [PATCH 05/24] Port max8997_haptic to ff-memless-next
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/max8997_haptic.c | 25 +++++++++++++++++++------
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 91dcae8..6ba327f 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -157,7 +157,7 @@ config INPUT_MAX8925_ONKEY
config INPUT_MAX8997_HAPTIC
tristate "MAXIM MAX8997 haptic controller support"
depends on PWM && HAVE_PWM && MFD_MAX8997
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
help
This option enables device driver support for the haptic controller
on MAXIM MAX8997 chip. This driver supports ff-memless interface
diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c
index 1fea548..029ac8b 100644
--- a/drivers/input/misc/max8997_haptic.c
+++ b/drivers/input/misc/max8997_haptic.c
@@ -31,6 +31,7 @@
#include <linux/mfd/max8997-private.h>
#include <linux/mfd/max8997.h>
#include <linux/regulator/consumer.h>
+#include <linux/input/ff-memless-next.h>
/* Haptic configuration 2 register */
#define MAX8997_MOTOR_TYPE_SHIFT 7
@@ -43,6 +44,8 @@
#define MAX8997_SIG_DUTY_SHIFT 2
#define MAX8997_PWM_DUTY_SHIFT 0
+#define FF_UPDATE_RATE 50
+
struct max8997_haptic {
struct device *dev;
struct i2c_client *client;
@@ -219,13 +222,23 @@ static void max8997_haptic_play_effect_work(struct work_struct *work)
}
static int max8997_haptic_play_effect(struct input_dev *dev, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
struct max8997_haptic *chip = input_get_drvdata(dev);
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
- chip->level = effect->u.rumble.strong_magnitude;
- if (!chip->level)
- chip->level = effect->u.rumble.weak_magnitude;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ chip->level = rumble_force->strong;
+ if (!chip->level)
+ chip->level = rumble_force->weak;
+ break;
+ case MLNX_STOP_RUMBLE:
+ chip->level = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
schedule_work(&chip->work);
@@ -319,8 +332,8 @@ static int max8997_haptic_probe(struct platform_device *pdev)
input_set_drvdata(input_dev, chip);
input_set_capability(input_dev, EV_FF, FF_RUMBLE);
- error = input_ff_create_memless(input_dev, NULL,
- max8997_haptic_play_effect);
+ error = input_ff_create_mlnx(input_dev, NULL,
+ max8997_haptic_play_effect, FF_UPDATE_RATE);
if (error) {
dev_err(&pdev->dev,
"unable to create FF device, error: %d\n",
--
1.9.2
^ permalink raw reply related
* [PATCH 07/24] Port hid-axff to ff-memless-next
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-axff.c | 32 +++++++++++++++++++++++---------
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index f722001..68c19a0 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -106,7 +106,7 @@ config HID_ACRUX
config HID_ACRUX_FF
bool "ACRUX force feedback support"
depends on HID_ACRUX
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you want to enable force feedback support for ACRUX
game controllers.
diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c
index a594e47..7fbfcbc 100644
--- a/drivers/hid/hid-axff.c
+++ b/drivers/hid/hid-axff.c
@@ -31,31 +31,45 @@
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-ids.h"
+#define FF_UPDATE_RATE 50
#ifdef CONFIG_HID_ACRUX_FF
struct axff_device {
struct hid_report *report;
};
-static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+static int axff_play(struct input_dev *dev, void *data,
+ const struct mlnx_effect_command *command)
{
struct hid_device *hid = input_get_drvdata(dev);
struct axff_device *axff = data;
struct hid_report *report = axff->report;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
int field_count = 0;
int left, right;
int i, j;
- left = effect->u.rumble.strong_magnitude;
- right = effect->u.rumble.weak_magnitude;
-
- dbg_hid("called with 0x%04x 0x%04x", left, right);
-
- left = left * 0xff / 0xffff;
- right = right * 0xff / 0xffff;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ left = rumble_force->strong;
+ right = rumble_force->weak;
+
+ dbg_hid("called with 0x%04x 0x%04x", left, right);
+
+ left = left * 0xff / 0xffff;
+ right = right * 0xff / 0xffff;
+ break;
+ case MLNX_STOP_RUMBLE:
+ left = 0;
+ right = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
for (i = 0; i < report->maxfield; i++) {
for (j = 0; j < report->field[i]->report_count; j++) {
@@ -107,7 +121,7 @@ static int axff_init(struct hid_device *hid)
set_bit(FF_RUMBLE, dev->ffbit);
- error = input_ff_create_memless(dev, axff, axff_play);
+ error = input_ff_create_mlnx(dev, axff, axff_play, FF_UPDATE_RATE);
if (error)
goto err_free_mem;
--
1.9.2
^ permalink raw reply related
* [PATCH 09/24] Port hid-dr to ff-memless-next
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-dr.c | 59 ++++++++++++++++++++++++++++++++--------------------
2 files changed, 37 insertions(+), 24 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 6e233d2..0ba1962 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -196,7 +196,7 @@ config HID_DRAGONRISE
config DRAGONRISE_FF
bool "DragonRise Inc. force feedback"
depends on HID_DRAGONRISE
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you want to enable force feedback support for DragonRise Inc.
game controllers.
diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c
index ce06444..b95c676 100644
--- a/drivers/hid/hid-dr.c
+++ b/drivers/hid/hid-dr.c
@@ -31,8 +31,10 @@
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-ids.h"
+#define FF_UPDATE_RATE 50
#ifdef CONFIG_DRAGONRISE_FF
@@ -41,38 +43,49 @@ struct drff_device {
};
static int drff_play(struct input_dev *dev, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
struct hid_device *hid = input_get_drvdata(dev);
struct drff_device *drff = data;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
int strong, weak;
- strong = effect->u.rumble.strong_magnitude;
- weak = effect->u.rumble.weak_magnitude;
+ strong = rumble_force->strong;
+ weak = rumble_force->weak;
dbg_hid("called with 0x%04x 0x%04x", strong, weak);
- if (strong || weak) {
- strong = strong * 0xff / 0xffff;
- weak = weak * 0xff / 0xffff;
-
- /* While reverse engineering this device, I found that when
- this value is set, it causes the strong rumble to function
- at a near maximum speed, so we'll bypass it. */
- if (weak == 0x0a)
- weak = 0x0b;
-
- drff->report->field[0]->value[0] = 0x51;
- drff->report->field[0]->value[1] = 0x00;
- drff->report->field[0]->value[2] = weak;
- drff->report->field[0]->value[4] = strong;
- hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
-
- drff->report->field[0]->value[0] = 0xfa;
- drff->report->field[0]->value[1] = 0xfe;
- } else {
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ if (strong || weak) {
+ strong = strong * 0xff / 0xffff;
+ weak = weak * 0xff / 0xffff;
+
+ /* While reverse engineering this device, I found that when
+ this value is set, it causes the strong rumble to function
+ at a near maximum speed, so we'll bypass it. */
+ if (weak == 0x0a)
+ weak = 0x0b;
+
+ drff->report->field[0]->value[0] = 0x51;
+ drff->report->field[0]->value[1] = 0x00;
+ drff->report->field[0]->value[2] = weak;
+ drff->report->field[0]->value[4] = strong;
+ hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
+
+ drff->report->field[0]->value[0] = 0xfa;
+ drff->report->field[0]->value[1] = 0xfe;
+ } else {
+ drff->report->field[0]->value[0] = 0xf3;
+ drff->report->field[0]->value[1] = 0x00;
+ }
+ break;
+ case MLNX_STOP_RUMBLE:
drff->report->field[0]->value[0] = 0xf3;
drff->report->field[0]->value[1] = 0x00;
+ break;
+ default:
+ return -EINVAL;
}
drff->report->field[0]->value[2] = 0x00;
@@ -116,7 +129,7 @@ static int drff_init(struct hid_device *hid)
set_bit(FF_RUMBLE, dev->ffbit);
- error = input_ff_create_memless(dev, drff, drff_play);
+ error = input_ff_create_mlnx(dev, drff, drff_play, FF_UPDATE_RATE);
if (error) {
kfree(drff);
return error;
--
1.9.2
^ permalink raw reply related
* [PATCH 10/24] Port hid-gaff to ff-memless-next
From: Michal Malý @ 2014-04-22 13:46 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-gaff.c | 32 +++++++++++++++++++++++---------
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 0ba1962..24b336e 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -656,7 +656,7 @@ config HID_GREENASIA
config GREENASIA_FF
bool "GreenAsia (Product ID 0x12) force feedback support"
depends on HID_GREENASIA
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you have a GreenAsia (Product ID 0x12) based game controller
(like MANTA Warrior MM816 and SpeedLink Strike2 SL-6635) or adapter
diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c
index 2d8cead..f2f70c1 100644
--- a/drivers/hid/hid-gaff.c
+++ b/drivers/hid/hid-gaff.c
@@ -31,8 +31,11 @@
#include <linux/slab.h>
#include <linux/hid.h>
#include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-ids.h"
+#define FF_UPDATE_RATE 50
+
#ifdef CONFIG_GREENASIA_FF
struct gaff_device {
@@ -40,19 +43,30 @@ struct gaff_device {
};
static int hid_gaff_play(struct input_dev *dev, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
struct hid_device *hid = input_get_drvdata(dev);
struct gaff_device *gaff = data;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
int left, right;
- left = effect->u.rumble.strong_magnitude;
- right = effect->u.rumble.weak_magnitude;
-
- dbg_hid("called with 0x%04x 0x%04x", left, right);
-
- left = left * 0xfe / 0xffff;
- right = right * 0xfe / 0xffff;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ left = rumble_force->strong;
+ right = rumble_force->weak;
+
+ dbg_hid("called with 0x%04x 0x%04x", left, right);
+
+ left = left * 0xfe / 0xffff;
+ right = right * 0xfe / 0xffff;
+ break;
+ case MLNX_STOP_RUMBLE:
+ left = 0;
+ right = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
gaff->report->field[0]->value[0] = 0x51;
gaff->report->field[0]->value[1] = 0x0;
@@ -109,7 +123,7 @@ static int gaff_init(struct hid_device *hid)
set_bit(FF_RUMBLE, dev->ffbit);
- error = input_ff_create_memless(dev, gaff, hid_gaff_play);
+ error = input_ff_create_mlnx(dev, gaff, hid_gaff_play, FF_UPDATE_RATE);
if (error) {
kfree(gaff);
return error;
--
1.9.2
^ permalink raw reply related
* [PATCH 12/24] Port hid-lgff to ff-memless-next
From: Michal Malý @ 2014-04-22 13:46 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-lgff.c | 70 +++++++++++++++++++++++++++++++++++---------------
2 files changed, 51 insertions(+), 21 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index d1d4e77..c7794ae 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -356,7 +356,7 @@ config HID_LOGITECH_DJ
config LOGITECH_FF
bool "Logitech force feedback support"
depends on HID_LOGITECH
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
help
Say Y here if you have one of these devices:
- Logitech WingMan Cordless RumblePad
diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c
index e1394af..96d418c 100644
--- a/drivers/hid/hid-lgff.c
+++ b/drivers/hid/hid-lgff.c
@@ -31,9 +31,12 @@
#include <linux/input.h>
#include <linux/hid.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-lg.h"
+#define FF_UPDATE_RATE 50
+
struct dev_type {
u16 idVendor;
u16 idProduct;
@@ -47,11 +50,25 @@ static const signed short ff_rumble[] = {
static const signed short ff_joystick[] = {
FF_CONSTANT,
+ FF_RAMP,
+ FF_PERIODIC,
+ FF_SQUARE,
+ FF_TRIANGLE,
+ FF_SINE,
+ FF_SAW_UP,
+ FF_SAW_DOWN,
-1
};
static const signed short ff_joystick_ac[] = {
FF_CONSTANT,
+ FF_RAMP,
+ FF_PERIODIC,
+ FF_SQUARE,
+ FF_TRIANGLE,
+ FF_SINE,
+ FF_SAW_UP,
+ FF_SAW_DOWN,
FF_AUTOCENTER,
-1
};
@@ -66,45 +83,58 @@ static const struct dev_type devices[] = {
{ 0x046d, 0xc295, ff_joystick },
};
-static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+static int hid_lgff_play(struct input_dev *dev, void *data,
+ const struct mlnx_effect_command *command)
{
struct hid_device *hid = input_get_drvdata(dev);
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
- int x, y;
- unsigned int left, right;
-#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
+ switch (command->cmd) {
+ case MLNX_START_COMBINED: {
+ const struct mlnx_simple_force *simple_force = &command->u.simple_force;
+ /* Scale down from MLNX range */
+ const int x = 0x80 - (simple_force->x * 0xff / 0xffff);
+ const int y = 0x80 - simple_force->y * 0xff / 0xffff;
- switch (effect->type) {
- case FF_CONSTANT:
- x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */
- y = effect->u.ramp.end_level + 0x7f;
- CLAMP(x);
- CLAMP(y);
report->field[0]->value[0] = 0x51;
report->field[0]->value[1] = 0x08;
report->field[0]->value[2] = x;
report->field[0]->value[3] = y;
dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
- hid_hw_request(hid, report, HID_REQ_SET_REPORT);
break;
+ }
+ case MLNX_STOP_COMBINED:
+ report->field[0]->value[0] = 0x51;
+ report->field[0]->value[1] = 0x08;
+ report->field[0]->value[2] = 0x80;
+ report->field[0]->value[3] = 0x80;
+ break;
+ case MLNX_START_RUMBLE: {
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+ /* Scale down from MLNX range */
+ const unsigned int right = rumble_force->weak * 0xff / 0xffff;
+ const unsigned int left = rumble_force->strong * 0xff / 0xffff;
- case FF_RUMBLE:
- right = effect->u.rumble.strong_magnitude;
- left = effect->u.rumble.weak_magnitude;
- right = right * 0xff / 0xffff;
- left = left * 0xff / 0xffff;
- CLAMP(left);
- CLAMP(right);
report->field[0]->value[0] = 0x42;
report->field[0]->value[1] = 0x00;
report->field[0]->value[2] = left;
report->field[0]->value[3] = right;
dbg_hid("(left, right)=(%04x, %04x)\n", left, right);
- hid_hw_request(hid, report, HID_REQ_SET_REPORT);
break;
}
+ case MLNX_STOP_RUMBLE:
+ report->field[0]->value[0] = 0x42;
+ report->field[0]->value[1] = 0x00;
+ report->field[0]->value[2] = 0;
+ report->field[0]->value[3] = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
return 0;
}
@@ -148,7 +178,7 @@ int lgff_init(struct hid_device* hid)
for (i = 0; ff_bits[i] >= 0; i++)
set_bit(ff_bits[i], dev->ffbit);
- error = input_ff_create_memless(dev, NULL, hid_lgff_play);
+ error = input_ff_create_mlnx(dev, NULL, hid_lgff_play, FF_UPDATE_RATE);
if (error)
return error;
--
1.9.2
^ permalink raw reply related
* [PATCH 13/24] Port hid-lg3ff to ff-memless-next
From: Michal Malý @ 2014-04-22 13:46 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-lg3ff.c | 60 +++++++++++++++++++++++++++++++------------------
2 files changed, 39 insertions(+), 23 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index c7794ae..6f2941a 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -382,7 +382,7 @@ config LOGIRUMBLEPAD2_FF
config LOGIG940_FF
bool "Logitech Flight System G940 force feedback support"
depends on HID_LOGITECH
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
help
Say Y here if you want to enable force feedback support for Logitech
Flight System G940 devices.
diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c
index 8c2da18..c49b374 100644
--- a/drivers/hid/hid-lg3ff.c
+++ b/drivers/hid/hid-lg3ff.c
@@ -23,9 +23,12 @@
#include <linux/input.h>
#include <linux/hid.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-lg.h"
+#define FF_UPDATE_RATE 50
+
/*
* G940 Theory of Operation (from experimentation)
*
@@ -58,12 +61,11 @@ struct lg3ff_device {
};
static int hid_lg3ff_play(struct input_dev *dev, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
struct hid_device *hid = input_get_drvdata(dev);
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
- int x, y;
/*
* Available values in the field should always be 63, but we only use up to
@@ -72,30 +74,37 @@ static int hid_lg3ff_play(struct input_dev *dev, void *data,
memset(report->field[0]->value, 0,
sizeof(__s32) * report->field[0]->report_count);
- switch (effect->type) {
- case FF_CONSTANT:
-/*
- * Already clamped in ff_memless
- * 0 is center (different then other logitech)
- */
- x = effect->u.ramp.start_level;
- y = effect->u.ramp.end_level;
-
- /* send command byte */
- report->field[0]->value[0] = 0x51;
-
-/*
- * Sign backwards from other Force3d pro
- * which get recast here in two's complement 8 bits
- */
- report->field[0]->value[1] = (unsigned char)(-x);
- report->field[0]->value[31] = (unsigned char)(-y);
+ /* send command byte */
+ report->field[0]->value[0] = 0x51;
- hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+ switch (command->cmd) {
+ case MLNX_START_COMBINED: {
+ const struct mlnx_simple_force *simple_force = &command->u.simple_force;
+ /* Scale down from MLNX range */
+ const int x = simple_force->x * 0xff / 0xffff;
+ const int y = simple_force->y * 0xff / 0xffff;
+
+ /*
+ * Sign backwards from other Force3d pro
+ * which get recast here in two's complement 8 bits
+ */
+ report->field[0]->value[1] = (unsigned char)x;
+ report->field[0]->value[31] = (unsigned char)y;
+ break;
+ }
+ case MLNX_STOP_COMBINED:
+ report->field[0]->value[1] = 0;
+ report->field[0]->value[31] = 0;
break;
+ default:
+ return -EINVAL;
}
+
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
return 0;
}
+
static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude)
{
struct hid_device *hid = input_get_drvdata(dev);
@@ -123,6 +132,13 @@ static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude)
static const signed short ff3_joystick_ac[] = {
FF_CONSTANT,
+ FF_RAMP,
+ FF_PERIODIC,
+ FF_SQUARE,
+ FF_TRIANGLE,
+ FF_SINE,
+ FF_SAW_UP,
+ FF_SAW_DOWN,
FF_AUTOCENTER,
-1
};
@@ -143,7 +159,7 @@ int lg3ff_init(struct hid_device *hid)
for (i = 0; ff_bits[i] >= 0; i++)
set_bit(ff_bits[i], dev->ffbit);
- error = input_ff_create_memless(dev, NULL, hid_lg3ff_play);
+ error = input_ff_create_mlnx(dev, NULL, hid_lg3ff_play, FF_UPDATE_RATE);
if (error)
return error;
--
1.9.2
^ permalink raw reply related
* [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon
ff-memless-next (MLNX) is a largely improved version of the current ff-memless
(FFML) driver. MLNX supports all force feedback effects currently available in
the Linux force feedback userspace API. All effects are handled in accordance
with Microsoft's DirectInput/XInput. Most notable changes include support for
conditional effects, proper handling of all periodic waveforms and improved
emulation of rumble effects through periodic effects. MLNX also uses its own
kernel API to pass processed effects to hardware-specific drivers instead of
abusing "ff_effect" struct. The API is documented in the respective header
file.
MLNX has been expanded to be a direct replacement for FFML.
Support for FF_PERIODIC and FF_RAMP has been added to all devices that
support FF_CONSTANT as a part of the port to the new API.
This patch series:
1) Adds "ff-memless-next" module [1]
2) Ports all hardware-specific drivers to MLNX's API [2-23]
3) Removes FFML and replaces it with MLNX [24]
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
v2:
- Add missing msecs to jiffies conversion in ff-memless-next
- lgff: Properly convert force on Y axis from MLNX to device range
Support periodic effects for "joystick_ac" device class
- lg3ff: Properly convert forces from MLNX to device range
- Very minor coding style issues fixed
Michal Malý (24):
Add ff-memless-next module
Port arizona-haptics to ff-memless-next
Port twl4030-vibra to ff-memless-next
Port twl6040-vibra to ff-memless-next
Port max8997_haptic to ff-memless-next
Port pm8xxx-vibrator to ff-memless-next
Port hid-axff to ff-memless-next
Port hid-emsff to ff-memless-next
Port hid-dr to ff-memless-next
Port hid-gaff to ff-memless-next
Port hid-holtekff to ff-memless-next
Port hid-lgff to ff-memless-next
Port hid-lg3ff to ff-memless-next
Port hid-pl to ff-memless-next
Port hid-sjoy to ff-memless-next
Port hid-sony to ff-memless-next
Port hid-tmff to ff-memless-next
Port hid-wiimote-modules to ff-memless-next
Port hid-zpff to ff-memless-next
Port gamecon to ff-memless-next
Port xpad to ff-memless-next
Port hid-lg4ff to ff-memless-next
Replace ff-memless with ff-memless-next
drivers/hid/Kconfig | 30 +-
drivers/hid/hid-axff.c | 32 +-
drivers/hid/hid-dr.c | 59 +-
drivers/hid/hid-emsff.c | 38 +-
drivers/hid/hid-gaff.c | 32 +-
drivers/hid/hid-holtekff.c | 47 +-
drivers/hid/hid-lg2ff.c | 65 ++-
drivers/hid/hid-lg3ff.c | 60 +-
drivers/hid/hid-lg4ff.c | 93 +--
drivers/hid/hid-lgff.c | 70 ++-
drivers/hid/hid-pl.c | 38 +-
drivers/hid/hid-sjoy.c | 35 +-
drivers/hid/hid-sony.c | 24 +-
drivers/hid/hid-tmff.c | 83 +--
drivers/hid/hid-wiimote-modules.c | 74 ++-
drivers/hid/hid-zpff.c | 30 +-
drivers/input/Kconfig | 4 +-
drivers/input/Makefile | 2 +-
drivers/input/ff-memless-next.c | 1037 +++++++++++++++++++++++++++++++++
drivers/input/ff-memless.c | 547 -----------------
drivers/input/joystick/Kconfig | 4 +-
drivers/input/joystick/gamecon.c | 57 +-
drivers/input/joystick/xpad.c | 125 ++--
drivers/input/misc/Kconfig | 10 +-
drivers/input/misc/arizona-haptics.c | 39 +-
drivers/input/misc/max8997_haptic.c | 25 +-
drivers/input/misc/pm8xxx-vibrator.c | 28 +-
drivers/input/misc/twl4030-vibra.c | 31 +-
drivers/input/misc/twl6040-vibra.c | 27 +-
include/linux/input.h | 3 -
include/linux/input/ff-memless-next.h | 149 +++++
31 files changed, 1939 insertions(+), 959 deletions(-)
create mode 100644 drivers/input/ff-memless-next.c
delete mode 100644 drivers/input/ff-memless.c
create mode 100644 include/linux/input/ff-memless-next.h
--
1.9.2
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH v2 01/24] input: Add ff-memless-next module
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398174374-9501-1-git-send-email-madcatxster@devoid-pointer.net>
From: Michal Malý <madcatxster@prifuk.cz>
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
---
drivers/input/Kconfig | 11 +
drivers/input/Makefile | 1 +
drivers/input/ff-memless-next.c | 1037 +++++++++++++++++++++++++++++++++
include/linux/input/ff-memless-next.h | 149 +++++
4 files changed, 1198 insertions(+)
create mode 100644 drivers/input/ff-memless-next.c
create mode 100644 include/linux/input/ff-memless-next.h
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index a11ff74..3780962 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -38,6 +38,17 @@ config INPUT_FF_MEMLESS
To compile this driver as a module, choose M here: the
module will be called ff-memless.
+config INPUT_FF_MEMLESS_NEXT
+ tristate "New version of support for memoryless force-feedback devices"
+ help
+ Say Y here to enable a new version of support for memoryless force
+ feedback devices.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ff-memless-next.
+
config INPUT_POLLDEV
tristate "Polled input device skeleton"
help
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 5ca3f63..b4f11f5 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_INPUT) += input-core.o
input-core-y := input.o input-compat.o input-mt.o ff-core.o
obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o
+obj-$(CONFIG_INPUT_FF_MEMLESS_NEXT) += ff-memless-next.o
obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o
obj-$(CONFIG_INPUT_SPARSEKMAP) += sparse-keymap.o
obj-$(CONFIG_INPUT_MATRIXKMAP) += matrix-keymap.o
diff --git a/drivers/input/ff-memless-next.c b/drivers/input/ff-memless-next.c
new file mode 100644
index 0000000..24619e9
--- /dev/null
+++ b/drivers/input/ff-memless-next.c
@@ -0,0 +1,1037 @@
+/*
+ * Force feedback support for memoryless devices
+ *
+ * This module is based on "ff-memless" orignally written by Anssi Hannula.
+ * It is extended to support all force feedback effects currently supported
+ * by the Linux input stack.
+ * Logic of emulation of FF_RUMBLE through FF_PERIODIC provided by
+ * Elias Vanderstuyft <elias.vds@gmail.com>
+ *
+ * Copyright(c) 2014 Michal "MadCatX" Maly <madcatxster@devoid-pointer.net>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/jiffies.h>
+#include <linux/fixp-arith.h>
+#include <linux/input/ff-memless-next.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal \"MadCatX\" Maly");
+MODULE_DESCRIPTION("Force feedback support for memoryless force feedback devices");
+
+#define FF_MAX_EFFECTS 16
+#define FF_MIN_EFFECT_LENGTH ((MSEC_PER_SEC / CONFIG_HZ) + 1)
+#define FF_EFFECT_STARTED 1
+#define FF_EFFECT_PLAYING 2
+#define FF_EFFECT_EMULATED 3
+
+enum mlnx_emulate {
+ EMUL_NOTHING, /* Do not emulate anything */
+ EMUL_RUMBLE, /* Emulate FF_RUMBLE with FF_PERIODIC */
+ EMUL_PERIODIC /* Emulate FF_PERIODIC with FF_RUMBLE */
+};
+
+struct mlnx_effect {
+ struct ff_effect effect;
+ unsigned long flags;
+ unsigned long begin_at;
+ unsigned long stop_at;
+ unsigned long updated_at;
+ unsigned long attack_stop;
+ unsigned long fade_begin;
+ int repeat;
+};
+
+struct mlnx_device {
+ u8 combinable_playing;
+ u8 rumble_playing;
+ unsigned long update_rate_jiffies;
+ void *private;
+ struct mlnx_effect effects[FF_MAX_EFFECTS];
+ u16 gain;
+ struct timer_list timer;
+ struct input_dev *dev;
+ enum mlnx_emulate emul;
+
+ int (*control_effect)(struct input_dev *, void *,
+ const struct mlnx_effect_command *);
+};
+
+static s32 mlnx_calculate_x_force(const s32 level,
+ const u16 direction)
+{
+ s32 new = (level * -fixp_sin(direction)) >> FRAC_N;
+ pr_debug("x force: %d\n", new);
+ return new;
+}
+
+static s32 mlnx_calculate_y_force(const s32 level,
+ const u16 direction)
+{
+ s32 new = (level * fixp_cos(direction)) >> FRAC_N;
+ pr_debug("y force: %d\n", new);
+ return new;
+}
+
+static bool mlnx_is_combinable(const struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_CONSTANT:
+ case FF_PERIODIC:
+ case FF_RAMP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mlnx_is_conditional(const struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void mlnx_clr_emulated(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static void mlnx_clr_playing(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static void mlnx_clr_started(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_emulated(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_rumble(const struct ff_effect *effect)
+{
+ return effect->type == FF_RUMBLE;
+}
+
+static bool mlnx_is_playing(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static bool mlnx_is_started(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_test_set_playing(struct mlnx_effect *mlnxeff)
+{
+ return test_and_set_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static const struct ff_envelope *mlnx_get_envelope(const struct ff_effect *effect)
+{
+ static const struct ff_envelope empty;
+
+ switch (effect->type) {
+ case FF_CONSTANT:
+ return &effect->u.constant.envelope;
+ case FF_PERIODIC:
+ return &effect->u.periodic.envelope;
+ case FF_RAMP:
+ return &effect->u.ramp.envelope;
+ default:
+ return ∅
+ }
+}
+
+/* Some devices might have a limit on how many uncombinable effects
+ * can be played at once */
+static int mlnx_upload_conditional(struct mlnx_device *mlnxdev,
+ const struct ff_effect *effect)
+{
+ struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_UPLOAD_UNCOMB,
+ .u.uncomb.id = effect->id,
+ .u.uncomb.effect = effect
+ };
+ return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+}
+
+static int mlnx_erase_conditional(struct mlnx_device *mlnxdev,
+ const struct ff_effect *effect)
+{
+ struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_ERASE_UNCOMB,
+ .u.uncomb.id = effect->id,
+ .u.uncomb.effect = effect
+ };
+ return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+}
+
+static void mlnx_set_envelope_times(struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+ if (envelope->attack_length) {
+ unsigned long j = msecs_to_jiffies(envelope->attack_length);
+ mlnxeff->attack_stop = mlnxeff->begin_at + j;
+ }
+ if (effect->replay.length && envelope->fade_length) {
+ unsigned long j = msecs_to_jiffies(envelope->fade_length);
+ mlnxeff->fade_begin = mlnxeff->stop_at - j;
+ }
+}
+
+static void mlnx_set_trip_times(struct mlnx_effect *mlnxeff,
+ const unsigned long now)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const u16 replay_delay = effect->replay.delay;
+ const u16 replay_length = effect->replay.length;
+
+ mlnxeff->begin_at = now + msecs_to_jiffies(replay_delay);
+ mlnxeff->stop_at = mlnxeff->begin_at + msecs_to_jiffies(replay_length);
+ mlnxeff->updated_at = mlnxeff->begin_at;
+}
+
+static void mlnx_start_effect(struct mlnx_effect *mlnxeff)
+{
+ const unsigned long now = jiffies;
+
+ mlnx_set_trip_times(mlnxeff, now);
+ mlnx_set_envelope_times(mlnxeff);
+ __set_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static void mlnx_stop_effect(struct mlnx_device *mlnxdev,
+ const struct mlnx_effect *mlnxeff)
+{
+ switch (mlnxeff->effect.type) {
+ case FF_PERIODIC:
+ if (mlnx_is_emulated(mlnxeff)) {
+ if (--mlnxdev->rumble_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_RUMBLE
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &c);
+ }
+ return;
+ }
+ case FF_CONSTANT:
+ case FF_RAMP:
+ if (--mlnxdev->combinable_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_COMBINED
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private,
+ &c);
+ }
+ return;
+ case FF_RUMBLE:
+ if (mlnx_is_emulated(mlnxeff)) {
+ if (--mlnxdev->combinable_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_COMBINED
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &c);
+ }
+ } else {
+ if (--mlnxdev->rumble_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_RUMBLE
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &c);
+ }
+ }
+ return;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_UNCOMB,
+ .u.uncomb.id = mlnxeff->effect.id,
+ .u.uncomb.effect = &mlnxeff->effect
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &c);
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+static int mlnx_restart_effect(struct mlnx_device *mlnxdev,
+ struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const unsigned long now = jiffies;
+
+ if (mlnx_is_combinable(effect)) {
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ else {
+ if (mlnx_is_emulated(mlnxeff))
+ mlnxdev->rumble_playing--;
+ else
+ mlnxdev->combinable_playing--;
+ }
+ } else if (mlnx_is_rumble(effect)) {
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ else {
+ if (mlnx_is_emulated(mlnxeff))
+ mlnxdev->combinable_playing--;
+ else
+ mlnxdev->rumble_playing--;
+ }
+ } else if (mlnx_is_conditional(effect)) {
+ int ret;
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+
+ ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+ if (ret)
+ return ret;
+ }
+
+ mlnx_set_trip_times(mlnxeff, now);
+ mlnx_set_envelope_times(mlnxeff);
+
+ return 0;
+}
+
+static s32 mlnx_apply_envelope(const struct mlnx_effect *mlnxeff,
+ const s32 level)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ const s32 alevel = abs(level);
+
+ /* Effect has an envelope with nonzero attack time */
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+ const s32 alength = envelope->attack_length;
+ const s32 dlevel = (alevel - envelope->attack_level)
+ * clength / alength;
+ return level < 0 ? -(dlevel + envelope->attack_level) :
+ (dlevel + envelope->attack_level);
+ } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+ const s32 flength = envelope->fade_length;
+ const s32 dlevel = (envelope->fade_level - alevel)
+ * clength / flength;
+ return level < 0 ? -(dlevel + alevel) : (dlevel + alevel);
+ }
+
+ return level;
+}
+
+static s32 mlnx_calculate_periodic(struct mlnx_effect *mlnxeff, const s32 level)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const unsigned long now = jiffies;
+ const u16 period = effect->u.periodic.period;
+ const u16 phase = effect->u.periodic.phase;
+ const s16 offset = effect->u.periodic.offset;
+ s32 new = level;
+ u16 t = (jiffies_to_msecs(now - mlnxeff->begin_at) + phase) % period;
+
+ switch (effect->u.periodic.waveform) {
+ case FF_SINE:
+ {
+ u16 degrees = (360 * t) / period;
+ new = ((level * fixp_sin(degrees)) >> FRAC_N) + offset;
+ break;
+ }
+ case FF_SQUARE:
+ {
+ u16 degrees = (360 * t) / period;
+ new = level * (degrees < 180 ? 1 : -1) + offset;
+ break;
+ }
+ case FF_SAW_UP:
+ new = 2 * level * t / period - level + offset;
+ break;
+ case FF_SAW_DOWN:
+ new = level - 2 * level * t / period + offset;
+ break;
+ case FF_TRIANGLE:
+ {
+ new = (2 * abs(level - (2 * level * t) / period));
+ new = new - abs(level) + offset;
+ break;
+ }
+ case FF_CUSTOM:
+ pr_debug("Custom waveform is not handled by this driver\n");
+ return level;
+ default:
+ pr_err("Invalid waveform\n");
+ return level;
+ }
+
+ /* Ensure that the offset did not make the value exceed s16 range */
+ new = clamp(new, -0x7fff, 0x7fff);
+ pr_debug("level: %d, t: %u\n", new, t);
+ return new;
+}
+
+static s32 mlnx_calculate_ramp(const struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ const u16 length = effect->replay.length;
+ const s16 mean = (effect->u.ramp.start_level + effect->u.ramp.end_level) / 2;
+ const u16 t = jiffies_to_msecs(now - mlnxeff->begin_at);
+ s32 start = effect->u.ramp.start_level;
+ s32 end = effect->u.ramp.end_level;
+ s32 new;
+
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+ const s32 alength = envelope->attack_length;
+ s32 attack_level;
+ if (end > start)
+ attack_level = mean - envelope->attack_level;
+ else
+ attack_level = mean + envelope->attack_level;
+ start = (start - attack_level) * clength / alength
+ + attack_level;
+ } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+ const s32 flength = envelope->fade_length;
+ s32 fade_level;
+ if (end > start)
+ fade_level = mean + envelope->fade_level;
+ else
+ fade_level = mean - envelope->fade_level;
+ end = (fade_level - end) * clength / flength + end;
+ }
+
+ new = ((end - start) * t) / length + start;
+ new = clamp(new, -0x7fff, 0x7fff);
+ pr_debug("RAMP level: %d, t: %u\n", new, t);
+ return new;
+}
+
+static void mlnx_destroy(struct ff_device *dev)
+{
+ struct mlnx_device *mlnxdev = dev->private;
+ del_timer_sync(&mlnxdev->timer);
+
+ kfree(mlnxdev->private);
+}
+
+static unsigned long mlnx_get_envelope_update_time(const struct mlnx_effect *mlnxeff,
+ const unsigned long update_rate_jiffies)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ unsigned long fade_next;
+
+ /* Effect has an envelope with nonzero attack time */
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ if (time_before(mlnxeff->updated_at + update_rate_jiffies, mlnxeff->attack_stop))
+ return mlnxeff->updated_at + update_rate_jiffies;
+
+ return mlnxeff->attack_stop;
+ }
+
+ /* Effect has an envelope with nonzero fade time */
+ if (mlnxeff->effect.replay.length) {
+ if (!envelope->fade_length)
+ return mlnxeff->stop_at;
+
+ /* Schedule the next update when the fade begins */
+ if (time_before(mlnxeff->updated_at, mlnxeff->fade_begin))
+ return mlnxeff->fade_begin;
+
+ /* Already fading */
+ fade_next = mlnxeff->updated_at + update_rate_jiffies;
+
+ /* Schedule update when the effect stops */
+ if (time_after(fade_next, mlnxeff->stop_at))
+ return mlnxeff->stop_at;
+ /* Schedule update at the next checkpoint */
+ return fade_next;
+ }
+
+ /* There is no envelope */
+
+ /* Prevent the effect from being started twice */
+ if (mlnxeff->begin_at == now && mlnx_is_playing(mlnxeff))
+ return now - 1;
+
+ return mlnxeff->begin_at;
+}
+
+static unsigned long mlnx_get_update_time(struct mlnx_effect *mlnxeff,
+ const unsigned long update_rate_jiffies)
+{
+ const unsigned long now = jiffies;
+ unsigned long time, update_periodic;
+
+ switch (mlnxeff->effect.type) {
+ /* Constant effect does not change with time, but it can have
+ * an envelope and a duration */
+ case FF_CONSTANT:
+ return mlnx_get_envelope_update_time(mlnxeff,
+ update_rate_jiffies);
+ /* Periodic and ramp effects have to be periodically updated */
+ case FF_PERIODIC:
+ case FF_RAMP:
+ time = mlnx_get_envelope_update_time(mlnxeff,
+ update_rate_jiffies);
+ if (mlnx_is_emulated(mlnxeff))
+ update_periodic = mlnxeff->stop_at;
+ else
+ update_periodic = mlnxeff->updated_at +
+ update_rate_jiffies;
+
+ /* Periodic effect has to be updated earlier than envelope
+ * or envelope update time is in the past */
+ if (time_before(update_periodic, time) || time_before(time, now))
+ return update_periodic;
+ /* Envelope needs to be updated */
+ return time;
+ case FF_RUMBLE:
+ if (mlnx_is_emulated(mlnxeff))
+ return mlnxeff->updated_at + update_rate_jiffies;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ default:
+ if (time_after_eq(mlnxeff->begin_at, now))
+ return mlnxeff->begin_at;
+
+ return mlnxeff->stop_at;
+ }
+}
+
+static void mlnx_schedule_playback(struct mlnx_device *mlnxdev)
+{
+ struct mlnx_effect *mlnxeff;
+ const unsigned long now = jiffies;
+ int events = 0;
+ int i;
+ unsigned long earliest = 0;
+ unsigned long time;
+
+ /* Iterate over all effects and determine the earliest
+ * time when we have to attend to any */
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ mlnxeff = &mlnxdev->effects[i];
+
+ if (!mlnx_is_started(mlnxeff))
+ continue; /* Effect is not started, skip it */
+
+ if (mlnx_is_playing(mlnxeff))
+ time = mlnx_get_update_time(mlnxeff,
+ mlnxdev->update_rate_jiffies);
+ else
+ time = mlnxeff->begin_at;
+
+ pr_debug("Update time for effect %d: %lu\n", i, time);
+
+ /* Scheduled time is in the future and is either
+ * before the current earliest time or it is
+ * the first valid time value in this pass */
+ if (time_before_eq(now, time) &&
+ (++events == 1 || time_before(time, earliest)))
+ earliest = time;
+ }
+
+ if (events) {
+ pr_debug("Events: %d, earliest: %lu\n", events, earliest);
+ mod_timer(&mlnxdev->timer, earliest);
+ } else {
+ pr_debug("No events, deactivating timer\n");
+ del_timer(&mlnxdev->timer);
+ }
+}
+
+static u16 mlnx_calculate_rumble_direction(const u32 total_mag, const u16 total_dir,
+ const u32 new_mag, const u16 new_dir)
+{
+ if (!new_mag)
+ return total_dir;
+ if (!total_mag)
+ return new_dir;
+ return (((total_dir >> 1) * total_mag +
+ (new_dir >> 1) * new_mag) /
+ (total_mag + new_mag)) << 1;
+}
+
+static void mlnx_add_force(struct mlnx_effect *mlnxeff, s32 *cfx, s32 *cfy,
+ const u16 gain)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ u16 direction;
+ s32 level;
+
+ pr_debug("Processing effect type %d, ID %d\n",
+ mlnxeff->effect.type, mlnxeff->effect.id);
+
+ direction = mlnxeff->effect.direction * 360 / 0xffff;
+ pr_debug("Direction deg: %u\n", direction);
+
+ switch (mlnxeff->effect.type) {
+ case FF_CONSTANT:
+ level = mlnx_apply_envelope(mlnxeff, effect->u.constant.level);
+ break;
+ case FF_PERIODIC:
+ level = mlnx_apply_envelope(mlnxeff,
+ effect->u.periodic.magnitude);
+ level = mlnx_calculate_periodic(mlnxeff, level);
+ break;
+ case FF_RAMP:
+ level = mlnx_calculate_ramp(mlnxeff);
+ break;
+ default:
+ pr_err("Effect %d not handled by mlnx_add_force\n",
+ mlnxeff->effect.type);
+ return;
+ }
+
+ *cfx += mlnx_calculate_x_force(level, direction) * gain / 0xffff;
+ *cfy += mlnx_calculate_y_force(level, direction) * gain / 0xffff;
+}
+
+static void mlnx_add_rumble(const struct mlnx_effect *mlnxeff, u32 *strong_mag,
+ u32 *weak_mag, u16 *strong_dir,
+ u16 *weak_dir, const u16 gain)
+{
+ const struct ff_effect *eff = &mlnxeff->effect;
+ const struct ff_rumble_effect *reff = &mlnxeff->effect.u.rumble;
+ const u32 new_strong_mag = (u32)reff->strong_magnitude * gain / 0xffffU;
+ const u32 new_weak_mag = (u32)reff->weak_magnitude * gain / 0xffffU;
+
+ *strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
+ new_strong_mag,
+ eff->direction);
+ *weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
+ new_weak_mag,
+ eff->direction);
+ *strong_mag += new_strong_mag;
+ *weak_mag += new_weak_mag;
+}
+
+static void mlnx_add_emul_periodic(const struct mlnx_effect *mlnxeff,
+ u32 *strong_mag, u32 *weak_mag,
+ u16 *strong_dir, u16 *weak_dir,
+ const u16 gain)
+{
+ const struct ff_effect *eff = &mlnxeff->effect;
+ const u32 level = (u32)abs(mlnx_apply_envelope(mlnxeff,
+ eff->u.periodic.magnitude)) * gain / 0x7fffU;
+
+ *strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
+ level, eff->direction);
+ *weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
+ level, eff->direction);
+
+ *strong_mag += level;
+ *weak_mag += level;
+}
+
+static void mlnx_add_emul_rumble(const struct mlnx_effect *mlnxeff, s32 *cfx,
+ s32 *cfy, const u16 gain,
+ const unsigned long now,
+ const unsigned long update_rate_jiffies)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const u16 strong = effect->u.rumble.strong_magnitude;
+ const u16 weak = effect->u.rumble.weak_magnitude;
+ /* To calculate 't', we pretend that mlnxeff->begin_at == 0, thus t == now. */
+ /* This will synchronise all simultaneously playing emul rumble effects, */
+ /* otherwise non-deterministic phase-inversions could occur depending on */
+ /* upload time, which could lead to undesired cancellation of these effects. */
+ const unsigned long t = now % (4UL * update_rate_jiffies);
+ s32 level = 0;
+ bool direction_up;
+ bool direction_left;
+
+ if (strong)
+ level += (strong / 4) * (t < 2UL * update_rate_jiffies ? 1 : -1);
+ if (weak)
+ level += (weak / 4) * (t < 2UL * update_rate_jiffies ?
+ (t < 1UL * update_rate_jiffies ? 1 : -1) :
+ (t < 3UL * update_rate_jiffies ? 1 : -1));
+ direction_up = (effect->direction > 0x3fffU && effect->direction <= 0xbfffU);
+ direction_left = (effect->direction <= 0x7fffU);
+
+ pr_debug("Emulated cf: %d, t: %lu, n: %lu, begin: %lu, diff: %lu j: %lu\n",
+ level, t, now, mlnxeff->begin_at, now - mlnxeff->begin_at,
+ update_rate_jiffies);
+ level = (level * gain) / 0xffff;
+ *cfx += direction_left ? -level : level;
+ *cfy += direction_up ? -level : level;
+}
+
+static void mlnx_play_effects(struct mlnx_device *mlnxdev)
+{
+ const u16 gain = mlnxdev->gain;
+ const unsigned long now = jiffies;
+ int i;
+ s32 cfx = 0;
+ s32 cfy = 0;
+ u32 strong_mag = 0;
+ u32 weak_mag = 0;
+ u16 strong_dir = 0;
+ u16 weak_dir = 0;
+
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[i];
+
+ if (!mlnx_is_started(mlnxeff)) {
+ pr_debug("Effect %hd/%d not started\n",
+ mlnxeff->effect.id, i);
+ continue;
+ }
+
+ if (time_before(now, mlnxeff->begin_at)) {
+ pr_debug("Effect %hd/%d begins at a later time\n",
+ mlnxeff->effect.id, i);
+ continue;
+ }
+
+ if (time_before_eq(mlnxeff->stop_at, now) && mlnxeff->effect.replay.length) {
+ pr_debug("Effect %hd/%d has to be stopped\n",
+ mlnxeff->effect.id, i);
+
+ mlnx_clr_playing(mlnxeff);
+ if (--mlnxeff->repeat > 0)
+ mlnx_restart_effect(mlnxdev, mlnxeff);
+ else {
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ mlnx_clr_started(mlnxeff);
+ mlnx_clr_emulated(mlnxeff);
+ if (mlnx_is_conditional(&mlnxeff->effect))
+ mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
+ }
+
+ continue;
+ }
+
+ switch (mlnxeff->effect.type) {
+ case FF_PERIODIC:
+ if (mlnxdev->emul == EMUL_PERIODIC) {
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->rumble_playing++;
+ pr_debug("Starting emul periodic, total rumble %u\n",
+ mlnxdev->rumble_playing);
+ }
+ __set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+ mlnx_add_emul_periodic(mlnxeff, &strong_mag, &weak_mag,
+ &strong_dir, &weak_dir, gain);
+ break;
+ }
+ case FF_CONSTANT:
+ case FF_RAMP:
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->combinable_playing++;
+ pr_debug("Starting combinable effect, total %u\n",
+ mlnxdev->combinable_playing);
+ }
+ mlnx_add_force(mlnxeff, &cfx, &cfy, gain);
+ break;
+ case FF_RUMBLE:
+ if (mlnxdev->emul == EMUL_RUMBLE) {
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->combinable_playing++;
+ pr_debug("Starting emul rumble, total comb %u\n",
+ mlnxdev->combinable_playing);
+ }
+ __set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+ mlnx_add_emul_rumble(mlnxeff, &cfx, &cfy, gain, now,
+ mlnxdev->update_rate_jiffies);
+ } else {
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->rumble_playing++;
+ pr_debug("Starting rumble effect, total %u\n",
+ mlnxdev->rumble_playing);
+ }
+ mlnx_add_rumble(mlnxeff, &strong_mag, &weak_mag,
+ &strong_dir, &weak_dir, gain);
+ }
+ break;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_UNCOMB,
+ .u.uncomb.id = i,
+ .u.uncomb.effect = &mlnxeff->effect
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &ecmd);
+ }
+ break;
+ default:
+ pr_debug("Unhandled type of effect\n");
+ }
+ mlnxeff->updated_at = now;
+ }
+
+ if (mlnxdev->combinable_playing) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_COMBINED,
+ .u.simple_force = {
+ .x = clamp(cfx, -0x7fff, 0x7fff),
+ .y = clamp(cfy, -0x7fff, 0x7fff)
+ }
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+ }
+ if (mlnxdev->rumble_playing) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_RUMBLE,
+ .u.rumble_force = {
+ .strong = clamp(strong_mag, (u32)0, (u32)0xffffU),
+ .weak = clamp(weak_mag, (u32)0, (u32)0xffffU),
+ .strong_dir = clamp(strong_dir, (u16)0, (u16)0xffffU),
+ .weak_dir = clamp(weak_dir, (u16)0, (u16)0xffffU)
+ }
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+ }
+
+ mlnx_schedule_playback(mlnxdev);
+}
+
+static void mlnx_set_gain(struct input_dev *dev, u16 gain)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ int i;
+
+ mlnxdev->gain = gain;
+
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ struct mlnx_effect *eff = &mlnxdev->effects[i];
+ if (eff == NULL)
+ continue;
+ if (mlnx_is_playing(eff)) {
+ if (mlnx_is_combinable(&eff->effect)) {
+ mlnx_clr_playing(eff);
+ if (mlnx_is_emulated(eff))
+ --mlnxdev->rumble_playing;
+ else
+ --mlnxdev->combinable_playing;
+ } else if (mlnx_is_rumble(&eff->effect)) {
+ mlnx_clr_playing(eff);
+ if (mlnx_is_emulated(eff))
+ --mlnxdev->combinable_playing;
+ else
+ --mlnxdev->rumble_playing;
+ }
+ }
+ }
+
+ mlnx_play_effects(mlnxdev);
+}
+
+static int mlnx_startstop(struct input_dev *dev, int effect_id, int repeat)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect_id];
+ int ret;
+
+ if (repeat > 0) {
+ pr_debug("Starting effect ID %d\n", effect_id);
+ mlnxeff->repeat = repeat;
+
+ if (!mlnx_is_started(mlnxeff)) {
+ /* Check that device has a free effect slot */
+ if (mlnx_is_conditional(&mlnxeff->effect)) {
+ ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+ if (ret) {
+ /* Device effect slots are all occupied */
+ pr_debug("No free effect slot for EID %d\n", effect_id);
+ return ret;
+ }
+ }
+ mlnx_start_effect(mlnxeff);
+ }
+ } else {
+ pr_debug("Stopping effect ID %d\n", effect_id);
+ if (mlnx_is_started(mlnxeff)) {
+ if (mlnx_is_playing(mlnxeff)) {
+ mlnx_clr_playing(mlnxeff);
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ }
+ mlnx_clr_started(mlnxeff);
+ mlnx_clr_emulated(mlnxeff);
+
+ if (mlnx_is_conditional(&mlnxeff->effect))
+ return mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
+ } else {
+ pr_debug("Effect ID %d already stopped\n", effect_id);
+ return 0;
+ }
+ }
+ mlnx_play_effects(mlnxdev);
+
+ return 0;
+}
+
+static void mlnx_timer_fired(unsigned long data)
+{
+ struct input_dev *dev = (struct input_dev *)data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ mlnx_play_effects(dev->ff->private);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static int mlnx_upload(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect->id];
+ const u16 length = effect->replay.length;
+ const u16 delay = effect->replay.delay;
+ int ret, fade_from;
+
+ /* Effect's timing is below kernel timer resolution */
+ if (length && length < FF_MIN_EFFECT_LENGTH)
+ effect->replay.length = FF_MIN_EFFECT_LENGTH;
+ if (delay && delay < FF_MIN_EFFECT_LENGTH)
+ effect->replay.delay = FF_MIN_EFFECT_LENGTH;
+
+ /* Periodic effects must have a non-zero period */
+ if (effect->type == FF_PERIODIC) {
+ if (!effect->u.periodic.period)
+ return -EINVAL;
+ }
+ /* Ramp effects cannot be infinite */
+ if (effect->type == FF_RAMP && !length)
+ return -EINVAL;
+
+ if (mlnx_is_combinable(effect)) {
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+ /* Infinite effects cannot fade */
+ if (!length && envelope->fade_length > 0)
+ return -EINVAL;
+ /* Fade length cannot be greater than effect duration */
+ fade_from = (int)length - (int)envelope->fade_length;
+ if (fade_from < 0)
+ return -EINVAL;
+ /* Envelope cannot start fading before it finishes attacking */
+ if (fade_from < envelope->attack_length && fade_from > 0)
+ return -EINVAL;
+ }
+
+
+ spin_lock_irq(&dev->event_lock);
+ mlnxeff->effect = *effect; /* Keep internal copy of the effect */
+ /* Check if the effect being modified is playing */
+ if (mlnx_is_started(mlnxeff)) {
+ if (mlnx_is_playing(mlnxeff)) {
+ mlnx_clr_playing(mlnxeff);
+ ret = mlnx_restart_effect(mlnxdev, mlnxeff);
+
+ if (ret) {
+ /* Restore the original effect */
+ if (old)
+ mlnxeff->effect = *old;
+ spin_unlock_irq(&dev->event_lock);
+ return ret;
+ }
+ }
+
+ mlnx_schedule_playback(mlnxdev);
+ }
+
+ spin_unlock_irq(&dev->event_lock);
+
+ return 0;
+}
+
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+ int (*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+ const u16 update_rate)
+{
+ struct mlnx_device *mlnxdev;
+ int ret;
+ const u16 min_update_rate = update_rate < FF_MIN_EFFECT_LENGTH ?
+ FF_MIN_EFFECT_LENGTH : update_rate;
+
+ mlnxdev = kzalloc(sizeof(*mlnxdev), GFP_KERNEL);
+ if (!mlnxdev)
+ return -ENOMEM;
+
+ mlnxdev->dev = dev;
+ mlnxdev->private = data;
+ mlnxdev->control_effect = control_effect;
+ mlnxdev->gain = 0xffff;
+ mlnxdev->update_rate_jiffies = msecs_to_jiffies(min_update_rate);
+ input_set_capability(dev, EV_FF, FF_GAIN);
+ setup_timer(&mlnxdev->timer, mlnx_timer_fired, (unsigned long)dev);
+
+ /* Set up effect emulation if needed */
+ if (test_bit(FF_PERIODIC, dev->ffbit) &&
+ !test_bit(FF_RUMBLE, dev->ffbit)) {
+ set_bit(FF_RUMBLE, dev->ffbit);
+ mlnxdev->emul = EMUL_RUMBLE;
+ pr_debug("Emulating RUMBLE with PERIODIC\n");
+ } else if (test_bit(FF_RUMBLE, dev->ffbit) &&
+ !test_bit(FF_PERIODIC, dev->ffbit)) {
+ set_bit(FF_PERIODIC, dev->ffbit);
+ set_bit(FF_SINE, dev->ffbit);
+ set_bit(FF_SQUARE, dev->ffbit);
+ set_bit(FF_TRIANGLE, dev->ffbit);
+ set_bit(FF_SAW_DOWN, dev->ffbit);
+ set_bit(FF_SAW_UP, dev->ffbit);
+ mlnxdev->emul = EMUL_PERIODIC;
+ pr_debug("Emulating PERIODIC with RUMBLE\n");
+ } else {
+ mlnxdev->emul = EMUL_NOTHING;
+ pr_debug("No effect emulation is necessary\n");
+ }
+
+ ret = input_ff_create(dev, FF_MAX_EFFECTS);
+ if (ret) {
+ kfree(mlnxdev);
+ return ret;
+ }
+
+
+ dev->ff->private = mlnxdev;
+ dev->ff->upload = mlnx_upload;
+ dev->ff->set_gain = mlnx_set_gain;
+ dev->ff->destroy = mlnx_destroy;
+ dev->ff->playback = mlnx_startstop;
+
+ pr_debug("Device successfully registered.\n");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(input_ff_create_mlnx);
diff --git a/include/linux/input/ff-memless-next.h b/include/linux/input/ff-memless-next.h
new file mode 100644
index 0000000..2f15897
--- /dev/null
+++ b/include/linux/input/ff-memless-next.h
@@ -0,0 +1,149 @@
+#include <linux/input.h>
+
+/** DEFINITION OF TERMS
+ *
+ * Combined effect - An effect whose force is a superposition of forces
+ * generated by all effects that can be added together.
+ * Only one combined effect can be playing at a time.
+ * Effects that can be added together to create a combined
+ * effect are FF_CONSTANT, FF_PERIODIC and FF_RAMP.
+ * Uncombinable effect - An effect that cannot be combined with another effect.
+ * All conditional effects - FF_DAMPER, FF_FRICTION,
+ * FF_INERTIA and FF_SPRING are uncombinable.
+ * Number of uncombinable effects playing simultaneously
+ * depends on the capabilities of the hardware.
+ * Rumble effect - An effect generated by device's rumble motors instead of
+ * force feedback actuators.
+ *
+ *
+ * HANDLING OF UNCOMBINABLE EFFECTS
+ *
+ * Uncombinable effects cannot be combined together into just one effect, at
+ * least not in a clear and obvious manner. Therefore these effects have to
+ * be handled individually by ff-memless-next. Handling of these effects is
+ * left entirely to the hardware-specific driver, ff-memless-next merely
+ * passes these effects to the hardware-specific driver at appropriate time.
+ * ff-memless-next provides the UPLOAD command to notify the hardware-specific
+ * driver that the userspace is about to request playback of an uncombinable
+ * effect. The hardware-specific driver shall take all steps needed to make
+ * the device ready to play the effect when it receives the UPLOAD command.
+ * The actual playback shall commence when START_UNCOMB command is received.
+ * Opposite to the UPLOAD command is the ERASE command which tells
+ * the hardware-specific driver that the playback has finished and that
+ * the effect will not be restarted. STOP_UNCOMB command tells
+ * the hardware-specific driver that the playback shall stop but the device
+ * shall still be ready to resume the playback immediately.
+ *
+ * In case it is not possible to make the device ready to play an uncombinable
+ * effect (all hardware effect slots are occupied), the hardware-specific
+ * driver may return an error when it receives an UPLOAD command. If the
+ * hardware-specific driver returns 0, the upload is considered successful.
+ * START_UNCOMB and STOP_UNCOMB commands cannot fail and the device must always
+ * start the playback of the requested effect if the UPLOAD command of the
+ * respective effect has been successful. ff-memless-next will never send
+ * a START/STOP_UNCOMB command for an effect that has not been uploaded
+ * successfully, nor will it send an ERASE command for an effect that is
+ * playing (= has been started with START_UNCOMB command).
+ */
+
+enum mlnx_commands {
+ /* Start or update a combined effect. This command is sent whenever
+ * a FF_CONSTANT, FF_PERIODIC or FF_RAMP is started, stopped or
+ * updated by userspace, when the applied envelopes are recalculated
+ * or when periodic effects are recalculated. */
+ MLNX_START_COMBINED,
+ /* Stop combined effect. This command is sent when all combinable
+ * effects are stopped. */
+ MLNX_STOP_COMBINED,
+ /* Start or update a rumble effect. This command is sent whenever
+ * a FF_RUMBLE effect is started or when its magnitudes or directions
+ * change. */
+ MLNX_START_RUMBLE,
+ /* Stop a rumble effect. This command is sent when all FF_RUMBLE
+ * effects are stopped. */
+ MLNX_STOP_RUMBLE,
+ /* Start or update an uncombinable effect. This command is sent
+ * whenever an uncombinable effect is started or updated. */
+ MLNX_START_UNCOMB,
+ /* Stop uncombinable effect. This command is sent when an uncombinable
+ * effect is stopped. */
+ MLNX_STOP_UNCOMB,
+ /* Upload uncombinable effect to device. This command is sent when the
+ * effect is started from userspace. It is up to the hardware-specific
+ * driver to handle this situation.
+ */
+ MLNX_UPLOAD_UNCOMB,
+ /* Remove uncombinable effect from device, This command is sent when
+ * and uncombinable effect has finished playing and will not be
+ * restarted.
+ */
+ MLNX_ERASE_UNCOMB
+};
+
+/** struct mlnx_simple_force - holds constant forces along X and Y axis
+ * @x: Force along X axis. Negative value denotes force pulling to the left,
+ * positive value denotes force pulling to the right.
+ * @y: Force along Y axis. Negative value denotes force denotes force pulling
+ * away from the user, positive value denotes force pulling towards
+ * the user.
+ */
+struct mlnx_simple_force {
+ const s32 x;
+ const s32 y;
+};
+
+/** struct mlnx_rumble_force - holds information about rumble effect
+ * @strong: Magnitude of the strong vibration.
+ * @weak: Magnitude of the weak vibration.
+ * @strong_dir: Direction of the strong vibration expressed in the same way
+ * as the direction of force feedback effect in struct ff_effect.
+ * @weak_dir: Direction of the weak vibration, same as above applies.
+ */
+struct mlnx_rumble_force {
+ const u32 strong;
+ const u32 weak;
+ const u16 strong_dir;
+ const u16 weak_dir;
+};
+
+/** struct mlnx_uncomb_effect - holds information about uncombinable effect
+ * @id: Id of the effect assigned by ff-core.
+ * @effect: Pointer to the uncombinable effect stored in ff-memless-next module
+ * Hardware-specific driver must not alter this.
+ */
+struct mlnx_uncomb_effect {
+ const int id;
+ const struct ff_effect *effect;
+};
+
+/** struct mlnx_commands - describes what action shall the force feedback
+ * device perform
+ * @cmd: Type of the action.
+ * @u: Data associated with the action.
+ */
+struct mlnx_effect_command {
+ const enum mlnx_commands cmd;
+ union {
+ const struct mlnx_simple_force simple_force;
+ const struct mlnx_rumble_force rumble_force;
+ const struct mlnx_uncomb_effect uncomb;
+ } u;
+};
+
+/** input_ff_create_mlnx() - Register a device within ff-memless-next and
+ * the kernel force feedback system
+ * @dev: Pointer to the struct input_dev associated with the device.
+ * @data: Any device-specific data that shall be passed to the callback.
+ * function called by ff-memless-next when a force feedback action
+ * shall be performed.
+ * @control_effect: Pointer to the callback function.
+ * @update_date: Delay in milliseconds between two recalculations of periodic
+ * effects, ramp effects and envelopes. Note that this value will
+ * never be lower than (CONFIG_HZ / 1000) + 1 regardless of the
+ * value specified here. This is not a "hard" rate limiter.
+ * Userspace still can submit effects at a rate faster than
+ * this value.
+ */
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+ int (*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+ const u16 update_rate);
--
1.9.2
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox