* Re: [PATCH v4 7/7] HID: sony: Add blink support to the Sixaxis and DualShock 4 LEDs
From: Frank Praznik @ 2014-04-04 18:37 UTC (permalink / raw)
To: simon, Frank Praznik; +Cc: HID CORE LAYER, Jiri Kosina
In-Reply-To: <9eabb4ae4fb93b4c51816953fc453e4f.squirrel@mungewell.org>
On 4/4/2014 12:11, simon@mungewell.org wrote:
> Why not make the triggers/etc apply across the board, so it doesn't matter
> which led the command is sent to - it's just registered/copied to the
> first (red?) led data[].
Unfortunately, it's not that simple. There is no API for setting
triggers from within the module and from looking at the code for these
triggers, some set the hardware blink rate which will blink the whole
light bar and some just use their own software timers and send
brightness commands which will only effect the LED they are set on which
results in unpredictable behavior depending on how the trigger is
implemented.
These software-only triggers are why I'm thinking it would be good to
have an extra global control for controlling the whole light bar
synchronously. Even if you set all the LEDs to the same software
trigger the colors may not flash in sync if the trigger starts a
separate timer for each LED and in some cases the trigger may mangle the
user defined brightness settings (ie. the heartbeat trigger doesn't read
the current brightness and just uses the maximum).
With an extra global switch, you can set the desired brightness levels
for each color, then just use the global control to set the on/off state
and get consistent blink behavior even if the trigger uses software
timers or doesn't care about the user's brightness settings. Plus, if
for some reason you do want to flash individual colors, setting the
blink rate of an individual color will always blink only that color
instead of potentially blinking the whole light bar if the trigger tries
to set the hardware blink rate.
I think it will be more functional, more predictable and less confusing.
>
>>> 3rd Party Intec - Was unable to get any controlled blinking.
>> It sounds like this controller just doesn't implement all of the
>> behavior of the official controller. I'm not sure how to fix it if it's
>> not obeying the instructions in valid output reports and I don't have
>> one to test personally. Do the lights flash properly when the
>> controller is used with a PS3?
> I presume that they just reversed engineered what they thought to be
> correct, I don't have a Playstation so can't confirm for certain.
>
> In my testing from a year ago or so, I could make the blink work via
> sending data with python. The script can be found here:
> http://www.spinics.net/lists/linux-input/msg28271.html
>
> Note: "Which LED to enable(LED1..4 << 1, 0x20=none)"
>
> Cheers,
> Simon
>
I'll take a look, but without having that particular controller all I
can do is take a shot in the dark.
^ permalink raw reply
* Re: hid-lg2ff handling of zero/low magnitude rumble effects
From: Elias Vanderstuyft @ 2014-04-04 20:10 UTC (permalink / raw)
To: Hendrik_Iben, Edgar Simo-Serra, Anssi Hannula
Cc: linux-input, Simon Wood, Michal Malý
In-Reply-To: <CADbOyBRGha3+X8RSjvOU=ftTGuDGYAyuFW_b7==0HeF1uBLV-g@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 3160 bytes --]
Hi guys,
I'm about to release a patch (see attachment
"0001-HID-lg2ff-add-rumble-magnitude-clamping-quirk.patch") for the
lg2ff driver to account for some weird quirks concerning the magnitude
of a rumble effect.
You can read the description in that patch too get more details on it.
The reason I sent this to you, is because I only own and tested this
on a Logitech Vibration Feedback wheel, and not on a Rumblepad (2). I
saw that Hendrik might have a Wingman Rumblepad, and Edgar a Rumblepad
2 device. But if other people have such lg2ff-compatible device as
well, feel free to cooperate.
May I ask you to perform some simple tests (see next paragraph) to
verify that these quirks are also valid on those devices?
To test sending some USB commands, I attached "USBCommander.zip" which
contains a program made with Qt4 by Michal (Cc'd; thanks Michal!) To
build it, you will need to run 'qmake-qt4' and 'make' in the top-level
directory.
It's simple to use USBCommander: enter the Vendor ID ("046d") and
Product ID ("c20a" for Rumblepad, or "c218" for Rumblepad 2), then
press 'Open', enter the to-be-tested command in the 'Command:' field,
and finally press 'Send command'.
After testing, press 'Release' and then quit the application.
You will need to run 'USBCommander' as root to send the USB commands.
Commands to test:
==============
Send the following commands (without parentheses):
"51 00 xx 00 yy 00 00"
with yy (strong rumble) = "02" and:
xx = "00" => You should feel an irregular weak rumble
xx = "01" => You should feel an irregular weak rumble
xx = "02" => You should feel (almost) no rumble
xx = "03" => You should feel (almost) no rumble
xx = "FC" => You should feel much weak rumble
xx = "FD" => You should feel much weak rumble
xx = "FE" => You should feel much irregular weak rumble,
but less than with xx = FD
xx = "FF" => You should feel much irregular weak rumble,
but less than with xx = FD
Test the same for the other rumble motor type (swap xx with yy).
To stop the rumble, send the following command:
"F3 00 00 00 00 00 00"
If you discover that the results do not match my assumptions, please
tell what happens in that case.
Thank you very much,
Elias
On Thu, Mar 27, 2014 at 11:13 PM, Elias Vanderstuyft
<elias.vds@gmail.com> wrote:
> Hi,
>
>
> I noticed that my rumble wheel (Logitech Formula Vibration Feedback)
> reacts in a strange way when sending low rumble magnitudes to the
> device:
>
> Assume the following USB command to be send to emit rumble:
> report->field[0]->value[0] = 0x51;
> report->field[0]->value[2] = weak;
> report->field[0]->value[4] = strong;
> When 'weak' or 'strong' is lower than 0x02 (i.e. 0x01 or 0x00), then
> the corresponding rumble motor begins to rumble intermittently, this
> resembles a bit to forcing a 2-state light-switch to be in the middle
> position.
>
> Now my question is whether all other devices (e.g. "Logitech
> RumblePad", "Rumblepad 2") experience this behaviour?
> (If you own such a device, please verify this.)
>
>
> Best regards,
>
> Elias
[-- Attachment #2: USBCommander.zip --]
[-- Type: application/zip, Size: 9947 bytes --]
[-- Attachment #3: 0001-HID-lg2ff-add-rumble-magnitude-clamping-quirk.patch --]
[-- Type: text/x-patch, Size: 1897 bytes --]
From 528b4c7e78429672538a65287160ad5498aafd05 Mon Sep 17 00:00:00 2001
From: Elias Vanderstuyft <elias.vds@gmail.com>
Date: Thu, 3 Apr 2014 19:25:58 +0200
Subject: [PATCH] HID: lg2ff: add rumble magnitude clamping quirk
If a magnitude in the output report is lower than 2, i.e. 1 or 0,
the corresponding rumble motor shakes irregularly,
instead of being turned (almost) off like when magnitude 2 is used.
On the other hand, if a magnitude is higher than 0xfd, i.e 0xfe or 0xff,
the corresponding rumble motor shakes irregularly with a rotation speed
lower than when magnitude 0xfd is used.
From 0x02 to 0xfd, the device behaves well:
a monotonic increase of rotation speed.
This applies to both weak and strong rumble motor types.
This patch fixes this issue by clamping magnitudes from 0x02 to 0xfd.
Note: The same thing appears to happen in the Windows Logitech driver,
except the max clamping bound is not 0xfd, but 0xfe.
Experimentally, I proved this to be wrong.
Signed-off-by: Elias Vanderstuyft <elias.vds@gmail.com>
Cc: linux-input@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
drivers/hid/hid-lg2ff.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c
index 0e3fb1a..e180e1e 100644
--- a/drivers/hid/hid-lg2ff.c
+++ b/drivers/hid/hid-lg2ff.c
@@ -38,12 +38,17 @@ static int play_effect(struct input_dev *dev, void *data,
struct lg2ff_device *lg2ff = data;
int weak, strong;
+#define CLAMP_QUIRK(x) do { if (x < 2) x = 2; else if (x > 0xfd) x = 0xfd; } \
+ while (0)
+
strong = effect->u.rumble.strong_magnitude;
weak = effect->u.rumble.weak_magnitude;
if (weak || strong) {
weak = weak * 0xff / 0xffff;
strong = strong * 0xff / 0xffff;
+ CLAMP_QUIRK(weak);
+ CLAMP_QUIRK(strong);
lg2ff->report->field[0]->value[0] = 0x51;
lg2ff->report->field[0]->value[2] = weak;
--
1.8.3.1
^ permalink raw reply related
* Re: [PATCH v4 7/7] HID: sony: Add blink support to the Sixaxis and DualShock 4 LEDs
From: simon @ 2014-04-04 21:13 UTC (permalink / raw)
To: Frank Praznik; +Cc: Frank Praznik, HID CORE LAYER, Jiri Kosina
In-Reply-To: <533EFBFC.1000105@gmail.com>
> On 4/4/2014 12:11, simon@mungewell.org wrote:
>> Why not make the triggers/etc apply across the board, so it doesn't
>> matter
>> which led the command is sent to - it's just registered/copied to the
>> first (red?) led data[].
>
> Unfortunately, it's not that simple.
It never is.... :-(
> There is no API for setting
> triggers from within the module and from looking at the code for these
> triggers, some set the hardware blink rate which will blink the whole
> light bar and some just use their own software timers and send
> brightness commands which will only effect the LED they are set on which
> results in unpredictable behavior depending on how the trigger is
> implemented.
I see the driver has a special case/function for setting blink with
'sony_led_blink_set()' which could be made to do funky stuff but that
might very rapidly become messy.
> These software-only triggers are why I'm thinking it would be good to
> have an extra global control for controlling the whole light bar
> synchronously.
A while ago (summer 2012) I messed around with the idea of 'chaining'
(grouping) leds together into a linked list, such that any actions on the
head of the list were duplicated to all leds in the chain.
The chain was created by a new trigger 'chain' and given the parameter of
the led to follow. I got as far as some code (wanted to learn about linked
lists in the kernel), but don't remember whether it worked properly on
not.
In this use case we could 'chain' the red->blue, and then have the blue
trigger set as 'timer' (or 'heartbeat') to cause a 'off->purple' blinking.
This would remove the contention in your scheme of having two places/leds
('red' and 'all') which could be set differently.
I can see if there's still some code on my disk if others are interested
in the idea.
Simon.
^ permalink raw reply
* Re: hid-lg2ff handling of zero/low magnitude rumble effects
From: Hendrik Iben @ 2014-04-04 23:02 UTC (permalink / raw)
To: Elias Vanderstuyft, Edgar Simo-Serra, Anssi Hannula
Cc: linux-input, Simon Wood, Michal Malý
In-Reply-To: <CADbOyBQkSGkDYcfpRUZR-h=gz-3S-NGLg4meuFYAP4zZQ7d2fQ@mail.gmail.com>
Hi Elias,
First of all - thanks for caring about compatibility with old devices.
It would be really annoying if something that worked once stopped
working on an update.
I just dug out my old Wingman Rumblepad (VID 046d PID c20a) and did the
test cases (thanks for the tool by the way - might come in handy for
other things). The first motor for this device controls a weak/smooth
rumble effect and the second one a strong/coarse one. For both motors,
values from 00 to 03 have no effect on the haptic side. You can hear the
motors spinning a bit higher if you hold the pad to your ear... :-) -
but it sounds like a linear increase. It similar for the high values.
FC-FF produce the same high amount of rumble. There might be a small
increase but not really any difference.
>From these observations I would say that clamping is fine for the
Rumblepad although not needed. But a special case handling would have no
positive effect.
Best regards,
Hendrik
Am 04.04.2014 22:10, schrieb Elias Vanderstuyft:
> Hi guys,
>
>
> I'm about to release a patch (see attachment
> "0001-HID-lg2ff-add-rumble-magnitude-clamping-quirk.patch") for the
> lg2ff driver to account for some weird quirks concerning the magnitude
> of a rumble effect.
> You can read the description in that patch too get more details on it.
>
> The reason I sent this to you, is because I only own and tested this
> on a Logitech Vibration Feedback wheel, and not on a Rumblepad (2). I
> saw that Hendrik might have a Wingman Rumblepad, and Edgar a Rumblepad
> 2 device. But if other people have such lg2ff-compatible device as
> well, feel free to cooperate.
> May I ask you to perform some simple tests (see next paragraph) to
> verify that these quirks are also valid on those devices?
>
> To test sending some USB commands, I attached "USBCommander.zip" which
> contains a program made with Qt4 by Michal (Cc'd; thanks Michal!) To
> build it, you will need to run 'qmake-qt4' and 'make' in the top-level
> directory.
> It's simple to use USBCommander: enter the Vendor ID ("046d") and
> Product ID ("c20a" for Rumblepad, or "c218" for Rumblepad 2), then
> press 'Open', enter the to-be-tested command in the 'Command:' field,
> and finally press 'Send command'.
> After testing, press 'Release' and then quit the application.
> You will need to run 'USBCommander' as root to send the USB commands.
>
> Commands to test:
> ==============
>
> Send the following commands (without parentheses):
> "51 00 xx 00 yy 00 00"
> with yy (strong rumble) = "02" and:
> xx = "00" => You should feel an irregular weak rumble
> xx = "01" => You should feel an irregular weak rumble
> xx = "02" => You should feel (almost) no rumble
> xx = "03" => You should feel (almost) no rumble
> xx = "FC" => You should feel much weak rumble
> xx = "FD" => You should feel much weak rumble
> xx = "FE" => You should feel much irregular weak rumble,
> but less than with xx = FD
> xx = "FF" => You should feel much irregular weak rumble,
> but less than with xx = FD
>
> Test the same for the other rumble motor type (swap xx with yy).
>
> To stop the rumble, send the following command:
> "F3 00 00 00 00 00 00"
>
> If you discover that the results do not match my assumptions, please
> tell what happens in that case.
>
>
> Thank you very much,
>
> Elias
>
> On Thu, Mar 27, 2014 at 11:13 PM, Elias Vanderstuyft
> <elias.vds@gmail.com> wrote:
>> Hi,
>>
>>
>> I noticed that my rumble wheel (Logitech Formula Vibration Feedback)
>> reacts in a strange way when sending low rumble magnitudes to the
>> device:
>>
>> Assume the following USB command to be send to emit rumble:
>> report->field[0]->value[0] = 0x51;
>> report->field[0]->value[2] = weak;
>> report->field[0]->value[4] = strong;
>> When 'weak' or 'strong' is lower than 0x02 (i.e. 0x01 or 0x00), then
>> the corresponding rumble motor begins to rumble intermittently, this
>> resembles a bit to forcing a 2-state light-switch to be in the middle
>> position.
>>
>> Now my question is whether all other devices (e.g. "Logitech
>> RumblePad", "Rumblepad 2") experience this behaviour?
>> (If you own such a device, please verify this.)
>>
>>
>> Best regards,
>>
>> Elias
^ permalink raw reply
* Re: [PATCH v4 7/7] HID: sony: Add blink support to the Sixaxis and DualShock 4 LEDs
From: Frank Praznik @ 2014-04-04 23:41 UTC (permalink / raw)
To: simon; +Cc: Frank Praznik, HID CORE LAYER, Jiri Kosina
In-Reply-To: <a7a124f17fadaebb5c6e42337a25421a.squirrel@mungewell.org>
On 4/4/2014 17:13, simon@mungewell.org wrote:
> I see the driver has a special case/function for setting blink with
> 'sony_led_blink_set()' which could be made to do funky stuff but that
> might very rapidly become messy.
>> These software-only triggers are why I'm thinking it would be good to
>> have an extra global control for controlling the whole light bar
>> synchronously.
> A while ago (summer 2012) I messed around with the idea of 'chaining'
> (grouping) leds together into a linked list, such that any actions on the
> head of the list were duplicated to all leds in the chain.
>
> The chain was created by a new trigger 'chain' and given the parameter of
> the led to follow. I got as far as some code (wanted to learn about linked
> lists in the kernel), but don't remember whether it worked properly on
> not.
>
> In this use case we could 'chain' the red->blue, and then have the blue
> trigger set as 'timer' (or 'heartbeat') to cause a 'off->purple' blinking.
> This would remove the contention in your scheme of having two places/leds
> ('red' and 'all') which could be set differently.
>
> I can see if there's still some code on my disk if others are interested
> in the idea.
> Simon.
This sounds kind of like how things work now: The blink function maps
all blink requests to any of the LEDs to one global value and uses that
to set the hardware delay on/off values.
I'd be interested in seeing your solution if you still have it, but that
still sounds like it still wouldn't work in the case of something like
the heartbeat trigger which doesn't actually use the blink function,
just runs it's own internal timer and just changes the brightness every
so often. The LED code in the module doesn't know where the commands
are coming from, just that it's being told to change brightness so it
wouldn't know which commands come from a trigger and should effect
everything vs which are user commands and should just change the
brightness of that specific LED.
^ permalink raw reply
* Re: hid-lg2ff handling of zero/low magnitude rumble effects
From: Elias Vanderstuyft @ 2014-04-05 7:31 UTC (permalink / raw)
To: Hendrik Iben
Cc: Edgar Simo-Serra, Anssi Hannula, linux-input, Simon Wood,
Michal Malý
In-Reply-To: <533F39F5.3020909@web.de>
On Sat, Apr 5, 2014 at 1:02 AM, Hendrik Iben <Hendrik_Iben@web.de> wrote:
> Hi Elias,
>
> First of all - thanks for caring about compatibility with old devices.
> It would be really annoying if something that worked once stopped
> working on an update.
>
> I just dug out my old Wingman Rumblepad (VID 046d PID c20a) and did the
> test cases (thanks for the tool by the way - might come in handy for
> other things). The first motor for this device controls a weak/smooth
> rumble effect and the second one a strong/coarse one. For both motors,
> values from 00 to 03 have no effect on the haptic side. You can hear the
> motors spinning a bit higher if you hold the pad to your ear... :-) -
> but it sounds like a linear increase. It similar for the high values.
> FC-FF produce the same high amount of rumble. There might be a small
> increase but not really any difference.
> From these observations I would say that clamping is fine for the
> Rumblepad although not needed. But a special case handling would have no
> positive effect.
OK, thanks for testing! I agree that a special case handling would
have no positive effect.
I tried to contact Edgar earlier past month, but until now I can't
seem to get in touch with him.
If there is no reply in a reasonable amount of time, I'll release the
patch, it won't hurt anyone ;)
Again thanks,
Elias
^ permalink raw reply
* [PATCH] input: pcap2: avoid calling mutex_lock() in irq handler
From: Alexey Khoroshilov @ 2014-04-05 20:54 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input, linux-kernel, ldv-project, Alexey Khoroshilov
pcap_keys_handler() calls ezx_pcap_read() that calls mutex_lock().
pcap_keys_handler() is registered as nonthreaded irq handler,
that means sleeping function is called in irq handler.
The patch makes a switch to threaded irq handling.
Compile tested only.
Found by Linux Driver Verification project (linuxtesting.org).
Signed-off-by: Alexey Khoroshilov <khoroshilov@ispras.ru>
---
drivers/input/misc/pcap_keys.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/input/misc/pcap_keys.c b/drivers/input/misc/pcap_keys.c
index cd230365166e..2a10f3a30969 100644
--- a/drivers/input/misc/pcap_keys.c
+++ b/drivers/input/misc/pcap_keys.c
@@ -79,13 +79,15 @@ static int pcap_keys_probe(struct platform_device *pdev)
if (err)
goto fail_allocate;
- err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
- pcap_keys_handler, 0, "Power key", pcap_keys);
+ err = request_threaded_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
+ NULL, pcap_keys_handler, 0,
+ "Power key", pcap_keys);
if (err)
goto fail_register;
- err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
- pcap_keys_handler, 0, "Headphone button", pcap_keys);
+ err = request_threaded_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
+ NULL, pcap_keys_handler, 0,
+ "Headphone button", pcap_keys);
if (err)
goto fail_pwrkey;
--
1.8.3.2
^ permalink raw reply related
* [PATCHv2 1/5] Input: add common DT binding for touchscreens
From: Sebastian Reichel @ 2014-04-05 22:26 UTC (permalink / raw)
To: Sebastian Reichel, Dmitry Torokhov, Dmitry Torokhov,
linux-input-u79uwXL29TY76Z2rM5mHXA, Tony Lindgren
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-omap-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Sebastian Reichel
In-Reply-To: <1396736821-14395-1-git-send-email-sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Add common DT binding documentation for touchscreen devices and
implement input_parse_touchscreen_of_params, which parses the common
properties and configures the input device accordingly.
Signed-off-by: Sebastian Reichel <sre-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
.../bindings/input/touchscreen/touchscreen.txt | 9 ++++++
drivers/input/input.c | 34 ++++++++++++++++++++++
include/linux/input.h | 8 +++++
3 files changed, 51 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
diff --git a/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
new file mode 100644
index 0000000..a2ff0a0
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
@@ -0,0 +1,9 @@
+General Touchscreen Properties:
+
+Optional properties for Touchscreens:
+ - touchscreen-size-x : horizontal resolution of touchscreen
+ - touchscreen-size-y : vertical resolution of touchscreen
+ - touchscreen-max-pressure : maximum reported pressure
+ - touchscreen-fuzz-x : horizontal noise value of the absolute input device
+ - touchscreen-fuzz-y : vertical noise value of the absolute input device
+ - touchscreen-fuzz-pressure : pressure noise value of the absolute input device
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 1c4c0db..97966d93 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -27,6 +27,7 @@
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/rcupdate.h>
+#include <linux/of.h>
#include "input-compat.h"
MODULE_AUTHOR("Vojtech Pavlik <vojtech-AlSwsSmVLrQ@public.gmane.org>");
@@ -2398,6 +2399,39 @@ void input_free_minor(unsigned int minor)
}
EXPORT_SYMBOL(input_free_minor);
+#if defined(CONFIG_OF)
+/**
+ * input_parse_touchscreen_of_params - parse common touchscreen DT properties
+ * @dev: device that should be parsed
+ *
+ * This function parses common DT properties for touchscreens and setups the
+ * input device accordingly. The function keeps previously setuped default
+ * values if no value is specified via DT.
+ */
+void input_parse_touchscreen_of_params(struct input_dev *dev)
+{
+ struct device_node *np = dev->dev.parent->of_node;
+ struct input_absinfo *absinfo;
+
+ input_alloc_absinfo(dev);
+ if (!dev->absinfo)
+ return;
+
+ absinfo = &dev->absinfo[ABS_X];
+ of_property_read_u32(np, "touchscreen-size-x", &absinfo->maximum);
+ of_property_read_u32(np, "touchscreen-fuzz-x", &absinfo->fuzz);
+
+ absinfo = &dev->absinfo[ABS_Y];
+ of_property_read_u32(np, "touchscreen-size-y", &absinfo->maximum);
+ of_property_read_u32(np, "touchscreen-fuzz-y", &absinfo->fuzz);
+
+ absinfo = &dev->absinfo[ABS_PRESSURE];
+ of_property_read_u32(np, "touchscreen-max-pressure", &absinfo->maximum);
+ of_property_read_u32(np, "touchscreen-fuzz-pressure", &absinfo->fuzz);
+}
+EXPORT_SYMBOL(input_parse_touchscreen_of_params);
+#endif
+
static int __init input_init(void)
{
int err;
diff --git a/include/linux/input.h b/include/linux/input.h
index 82ce323..3dc3b1e 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -531,4 +531,12 @@ int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file);
int input_ff_create_memless(struct input_dev *dev, void *data,
int (*play_effect)(struct input_dev *, void *, struct ff_effect *));
+#if defined(CONFIG_OF)
+void input_parse_touchscreen_of_params(struct input_dev *dev);
+#else
+static inline void input_parse_touchscreen_of_params(struct input_dev *dev) {
+ return;
+}
+#endif
+
#endif
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCHv2 2/5] Input: tsc2005: use dev_err for error messages
From: Sebastian Reichel @ 2014-04-05 22:26 UTC (permalink / raw)
To: Sebastian Reichel, Dmitry Torokhov, Dmitry Torokhov, linux-input,
Tony Lindgren
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
devicetree, linux-omap, linux-kernel, Sebastian Reichel
In-Reply-To: <1396736821-14395-1-git-send-email-sre@kernel.org>
Change some dev_dbg() invocations to dev_err() ones, because they
are supposed to output error messages.
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
drivers/input/touchscreen/tsc2005.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c
index 550adcb..520e673 100644
--- a/drivers/input/touchscreen/tsc2005.c
+++ b/drivers/input/touchscreen/tsc2005.c
@@ -579,7 +579,7 @@ static int tsc2005_probe(struct spi_device *spi)
int error;
if (!pdata) {
- dev_dbg(&spi->dev, "no platform data\n");
+ dev_err(&spi->dev, "no platform data\n");
return -ENODEV;
}
@@ -591,7 +591,7 @@ static int tsc2005_probe(struct spi_device *spi)
max_p = pdata->ts_pressure_max ? : MAX_12BIT;
if (spi->irq <= 0) {
- dev_dbg(&spi->dev, "no irq\n");
+ dev_err(&spi->dev, "no irq\n");
return -ENODEV;
}
--
1.9.1
^ permalink raw reply related
* [PATCHv2 3/5] Input: tsc2005: convert driver to use devm_*
From: Sebastian Reichel @ 2014-04-05 22:26 UTC (permalink / raw)
To: Sebastian Reichel, Dmitry Torokhov, Dmitry Torokhov, linux-input,
Tony Lindgren
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
devicetree, linux-omap, linux-kernel, Sebastian Reichel
In-Reply-To: <1396736821-14395-1-git-send-email-sre@kernel.org>
Simplify the driver by using managed resources for memory allocation of
internal struct, input device allocation and irq request.
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
drivers/input/touchscreen/tsc2005.c | 30 ++++++++++--------------------
1 file changed, 10 insertions(+), 20 deletions(-)
diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c
index 520e673..9daaddd 100644
--- a/drivers/input/touchscreen/tsc2005.c
+++ b/drivers/input/touchscreen/tsc2005.c
@@ -604,12 +604,10 @@ static int tsc2005_probe(struct spi_device *spi)
if (error)
return error;
- ts = kzalloc(sizeof(*ts), GFP_KERNEL);
- input_dev = input_allocate_device();
- if (!ts || !input_dev) {
- error = -ENOMEM;
- goto err_free_mem;
- }
+ ts = devm_kzalloc(&spi->dev, sizeof(*ts), GFP_KERNEL);
+ input_dev = devm_input_allocate_device(&spi->dev);
+ if (!ts || !input_dev)
+ return -ENOMEM;
ts->spi = spi;
ts->idev = input_dev;
@@ -649,12 +647,13 @@ static int tsc2005_probe(struct spi_device *spi)
/* Ensure the touchscreen is off */
tsc2005_stop_scan(ts);
- error = request_threaded_irq(spi->irq, NULL, tsc2005_irq_thread,
- IRQF_TRIGGER_RISING | IRQF_ONESHOT,
- "tsc2005", ts);
+ error = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
+ tsc2005_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "tsc2005", ts);
if (error) {
dev_err(&spi->dev, "Failed to request irq, err: %d\n", error);
- goto err_free_mem;
+ return error;
}
spi_set_drvdata(spi, ts);
@@ -662,7 +661,7 @@ static int tsc2005_probe(struct spi_device *spi)
if (error) {
dev_err(&spi->dev,
"Failed to create sysfs attributes, err: %d\n", error);
- goto err_clear_drvdata;
+ return error;
}
error = input_register_device(ts->idev);
@@ -677,11 +676,6 @@ static int tsc2005_probe(struct spi_device *spi)
err_remove_sysfs:
sysfs_remove_group(&spi->dev.kobj, &tsc2005_attr_group);
-err_clear_drvdata:
- free_irq(spi->irq, ts);
-err_free_mem:
- input_free_device(input_dev);
- kfree(ts);
return error;
}
@@ -691,10 +685,6 @@ static int tsc2005_remove(struct spi_device *spi)
sysfs_remove_group(&ts->spi->dev.kobj, &tsc2005_attr_group);
- free_irq(ts->spi->irq, ts);
- input_unregister_device(ts->idev);
- kfree(ts);
-
return 0;
}
--
1.9.1
^ permalink raw reply related
* [PATCHv2 4/5] Input: tsc2005: add DT support
From: Sebastian Reichel @ 2014-04-05 22:27 UTC (permalink / raw)
To: Sebastian Reichel, Dmitry Torokhov, Dmitry Torokhov, linux-input,
Tony Lindgren
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
devicetree, linux-omap, linux-kernel, Sebastian Reichel
In-Reply-To: <1396736821-14395-1-git-send-email-sre@kernel.org>
This adds DT support to the tsc2005 touchscreen
driver.
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
drivers/input/touchscreen/tsc2005.c | 96 +++++++++++++++++++++++++++++--------
1 file changed, 77 insertions(+), 19 deletions(-)
diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c
index 9daaddd..a83d1be 100644
--- a/drivers/input/touchscreen/tsc2005.c
+++ b/drivers/input/touchscreen/tsc2005.c
@@ -28,6 +28,8 @@
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/pm.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
#include <linux/spi/spi.h>
#include <linux/spi/tsc2005.h>
@@ -100,6 +102,11 @@
TSC2005_CFR2_AVG_7)
#define MAX_12BIT 0xfff
+#define TSC2005_DEF_X_FUZZ 4
+#define TSC2005_DEF_Y_FUZZ 8
+#define TSC2005_DEF_P_FUZZ 2
+#define TSC2005_DEF_RESISTOR 280
+
#define TSC2005_SPI_MAX_SPEED_HZ 10000000
#define TSC2005_PENUP_TIME_MS 40
@@ -143,6 +150,7 @@ struct tsc2005 {
bool pen_down;
+ int reset_gpio;
void (*set_reset)(bool enable);
};
@@ -337,6 +345,14 @@ static void tsc2005_stop_scan(struct tsc2005 *ts)
tsc2005_cmd(ts, TSC2005_CMD_STOP);
}
+static void tsc2005_set_reset(struct tsc2005 *ts, bool enable)
+{
+ if (ts->reset_gpio >= 0)
+ gpio_set_value(ts->reset_gpio, enable);
+ else if (ts->set_reset)
+ ts->set_reset(enable);
+}
+
/* must be called with ts->mutex held */
static void __tsc2005_disable(struct tsc2005 *ts)
{
@@ -355,7 +371,7 @@ static void __tsc2005_enable(struct tsc2005 *ts)
{
tsc2005_start_scan(ts);
- if (ts->esd_timeout && ts->set_reset) {
+ if (ts->esd_timeout && (ts->set_reset || ts->reset_gpio)) {
ts->last_valid_interrupt = jiffies;
schedule_delayed_work(&ts->esd_work,
round_jiffies_relative(
@@ -414,9 +430,9 @@ static ssize_t tsc2005_selftest_show(struct device *dev,
}
/* hardware reset */
- ts->set_reset(false);
+ tsc2005_set_reset(ts, false);
usleep_range(100, 500); /* only 10us required */
- ts->set_reset(true);
+ tsc2005_set_reset(ts, true);
if (!success)
goto out;
@@ -459,7 +475,7 @@ static umode_t tsc2005_attr_is_visible(struct kobject *kobj,
umode_t mode = attr->mode;
if (attr == &dev_attr_selftest.attr) {
- if (!ts->set_reset)
+ if (!ts->set_reset && !ts->reset_gpio)
mode = 0;
}
@@ -509,9 +525,9 @@ static void tsc2005_esd_work(struct work_struct *work)
tsc2005_update_pen_state(ts, 0, 0, 0);
- ts->set_reset(false);
+ tsc2005_set_reset(ts, false);
usleep_range(100, 500); /* only 10us required */
- ts->set_reset(true);
+ tsc2005_set_reset(ts, true);
enable_irq(ts->spi->irq);
tsc2005_start_scan(ts);
@@ -572,29 +588,47 @@ static void tsc2005_setup_spi_xfer(struct tsc2005 *ts)
static int tsc2005_probe(struct spi_device *spi)
{
const struct tsc2005_platform_data *pdata = dev_get_platdata(&spi->dev);
+ struct device_node *np = spi->dev.of_node;
+
struct tsc2005 *ts;
struct input_dev *input_dev;
- unsigned int max_x, max_y, max_p;
- unsigned int fudge_x, fudge_y, fudge_p;
+ unsigned int max_x = MAX_12BIT;
+ unsigned int max_y = MAX_12BIT;
+ unsigned int max_p = MAX_12BIT;
+ unsigned int fudge_x = TSC2005_DEF_X_FUZZ;
+ unsigned int fudge_y = TSC2005_DEF_Y_FUZZ;
+ unsigned int fudge_p = TSC2005_DEF_P_FUZZ;
+ unsigned int x_plate_ohm = TSC2005_DEF_RESISTOR;
+ unsigned int esd_timeout;
int error;
- if (!pdata) {
+ if (!np && !pdata) {
dev_err(&spi->dev, "no platform data\n");
return -ENODEV;
}
- fudge_x = pdata->ts_x_fudge ? : 4;
- fudge_y = pdata->ts_y_fudge ? : 8;
- fudge_p = pdata->ts_pressure_fudge ? : 2;
- max_x = pdata->ts_x_max ? : MAX_12BIT;
- max_y = pdata->ts_y_max ? : MAX_12BIT;
- max_p = pdata->ts_pressure_max ? : MAX_12BIT;
-
if (spi->irq <= 0) {
dev_err(&spi->dev, "no irq\n");
return -ENODEV;
}
+ if (pdata) {
+ fudge_x = pdata->ts_x_fudge;
+ fudge_y = pdata->ts_y_fudge;
+ fudge_p = pdata->ts_pressure_fudge;
+ max_x = pdata->ts_x_max;
+ max_y = pdata->ts_y_max;
+ max_p = pdata->ts_pressure_max;
+ x_plate_ohm = pdata->ts_x_plate_ohm;
+ esd_timeout = pdata->esd_timeout_ms;
+ } else {
+ x_plate_ohm = TSC2005_DEF_RESISTOR;
+ of_property_read_u32(np, "ti,x-plate-ohms", &x_plate_ohm);
+ esd_timeout = 0;
+ of_property_read_u32(np, "ti,esd-recovery-timeout-ms",
+ &esd_timeout);
+ }
+
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
if (!spi->max_speed_hz)
@@ -612,9 +646,30 @@ static int tsc2005_probe(struct spi_device *spi)
ts->spi = spi;
ts->idev = input_dev;
- ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280;
- ts->esd_timeout = pdata->esd_timeout_ms;
- ts->set_reset = pdata->set_reset;
+ ts->x_plate_ohm = x_plate_ohm;
+ ts->esd_timeout = esd_timeout;
+
+ if (np) {
+ ts->reset_gpio = of_get_named_gpio(np, "reset-gpios", 0);
+ if (ts->reset_gpio == -EPROBE_DEFER)
+ return ts->reset_gpio;
+ if (ts->reset_gpio < 0) {
+ dev_err(&spi->dev, "error acquiring reset gpio: %d\n",
+ ts->reset_gpio);
+ return ts->reset_gpio;
+ }
+
+ error = devm_gpio_request_one(&spi->dev, ts->reset_gpio, 0,
+ "reset-gpios");
+ if (error) {
+ dev_err(&spi->dev, "error requesting reset gpio: %d\n",
+ error);
+ return error;
+ }
+ } else {
+ ts->reset_gpio = -1;
+ ts->set_reset = pdata->set_reset;
+ }
mutex_init(&ts->mutex);
@@ -639,6 +694,9 @@ static int tsc2005_probe(struct spi_device *spi)
input_set_abs_params(input_dev, ABS_Y, 0, max_y, fudge_y, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, max_p, fudge_p, 0);
+ if (np)
+ input_parse_touchscreen_of_params(input_dev);
+
input_dev->open = tsc2005_open;
input_dev->close = tsc2005_close;
--
1.9.1
^ permalink raw reply related
* [PATCHv2 5/5] Documentation: dt: Document TSC2005 DT binding
From: Sebastian Reichel @ 2014-04-05 22:27 UTC (permalink / raw)
To: Sebastian Reichel, Dmitry Torokhov, Dmitry Torokhov, linux-input,
Tony Lindgren
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
devicetree, linux-omap, linux-kernel, Sebastian Reichel
In-Reply-To: <1396736821-14395-1-git-send-email-sre@kernel.org>
Add devicetree binding documentation for TSC2005 touchscreen.
Signed-off-by: Sebastian Reichel <sre@kernel.org>
---
.../bindings/input/touchscreen/tsc2005.txt | 39 ++++++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/tsc2005.txt
diff --git a/Documentation/devicetree/bindings/input/touchscreen/tsc2005.txt b/Documentation/devicetree/bindings/input/touchscreen/tsc2005.txt
new file mode 100644
index 0000000..a0e5bf5
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/tsc2005.txt
@@ -0,0 +1,39 @@
+* Texas Instruments tsc2005 touchscreen controller
+
+Required properties:
+ - compatible : "ti,tsc2005"
+ - reg : SPI device address
+ - spi-max-frequency : Maximal SPI speed
+ - interrupts : IRQ specifier
+ - reset-gpios : GPIO specifier
+
+Optional properties:
+ - ti,x-plate-resistance : integer, resistance of the touchscreen's X plates
+ in ohm (defaults to 280)
+ - ti,esd-recovery-timeout-ms : integer, if the touchscreen does not respond after
+ the configured time (in milli seconds), the driver
+ will reset it. This is disabled by default.
+ - properties defined in touchscreen.txt
+
+Example:
+
+&mcspi1 {
+ tsc2005@0 {
+ compatible = "ti,tsc2005";
+ spi-max-frequency = <6000000>;
+ reg = <0>;
+
+ reset-gpios = <&gpio4 8 GPIO_ACTIVE_HIGH>; /* 104 */
+ interrupts-extended = <&gpio4 4 IRQ_TYPE_NONE>; /* 100 */
+
+ touchscreen-fuzz-x = <4>;
+ touchscreen-fuzz-y = <7>;
+ touchscreen-fuzz-pressure = <2>;
+ touchscreen-max-x = <4096>;
+ touchscreen-max-y = <4096>;
+ touchscreen-max-pressure = <2048>;
+
+ ti,x-plate-resistance = <280>;
+ ti,esd-recovery-timeout-ms = <8000>;
+ };
+}
--
1.9.1
^ permalink raw reply related
* [PATCHv2 0/5] tsc2005 DT binding
From: Sebastian Reichel @ 2014-04-05 22:26 UTC (permalink / raw)
To: Sebastian Reichel, Dmitry Torokhov, Dmitry Torokhov, linux-input,
Tony Lindgren
Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
devicetree, linux-omap, linux-kernel, Sebastian Reichel
Hi,
This adds device tree support for the tsc2005 touchscreen
controller, which is currently only used by the Nokia N900
board.
The patch does not update the reset pin handling for platform
data based probe to avoid merge conflicts. The n900 platform
code will be removed in the near future (3.16?) and the driver
can be simplified once that has happened.
Changes since v1 [0]:
* rename "reset-gpio" to "reset-gpios", so that the code can easily
switch to the gpiod interface (once the boardcode is gone).
* rename ti,fuzz-* and ti,max-* to touchscreen-fuzz-* and
touchscreen-size-* and specify them as common touchscreen binding.
* implement input_parse_touchscreen_of_params(), which parses the
common touchscreen binding.
[0] https://lkml.org/lkml/2013/12/5/607
-- Sebastian
Sebastian Reichel (5):
Input: add common DT binding for touchscreens
Input: tsc2005: use dev_err for error messages
Input: tsc2005: convert driver to use devm_*
Input: tsc2005: add DT support
Documentation: dt: Document TSC2005 DT binding
.../bindings/input/touchscreen/touchscreen.txt | 9 ++
.../bindings/input/touchscreen/tsc2005.txt | 39 +++++++
drivers/input/input.c | 34 ++++++
drivers/input/touchscreen/tsc2005.c | 130 ++++++++++++++-------
include/linux/input.h | 8 ++
5 files changed, 179 insertions(+), 41 deletions(-)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/tsc2005.txt
--
1.9.1
^ permalink raw reply
* Re: 8 months to review a patch (was Re: [PATCH] Route kbd LEDs through the generic LEDs layer)
From: Pali Rohár @ 2014-04-06 9:43 UTC (permalink / raw)
To: Greg KH
Cc: Pavel Machek, dtor, linux-input, Jiri Kosina, Samuel Thibault,
Dmitry Torokhov, Andrew Morton, jslaby, Richard Purdie, LKML,
Evan Broder, Arnaud Patard, Linus Torvalds
In-Reply-To: <20140328071743.GA17588@kroah.com>
2014-03-28 8:17 GMT+01:00 Greg KH <greg@kroah.com>:
> On Fri, Mar 28, 2014 at 08:01:36AM +0100, Pavel Machek wrote:
>> On Thu 2014-03-27 02:08:17, Pali Rohár wrote:
>> > 2014-03-16 11:19 GMT+01:00 Samuel Thibault <samuel.thibault@ens-lyon.org>:
>> > > Pali Rohár, le Sun 16 Mar 2014 11:16:25 +0100, a écrit :
>> > >> Hello, what happened with this patch? Is there any problem with accepting it?
>> > >
>> > > Dmitry finding time to review it, I guess.
>>
>> > Dmitry, can you look and review this patch?
>>
>> Dmitry, what happened to you, are you there? This patch is like 8
>> months old, and not too scary, either. Can you at least pinpoint some
>> typos in it, or run in through the checkpatch?
>>
>> If Dmitry does not respond, what are the next steps. Greg, could you
>> just take the patch? Linus?
>
> No, work with Dmitry please.
>
Greg, problem is that Dmitry does not respond and has not reviewed
this patch for more that 8 months. From my point of view this looks
like Dmitry does not have time for review anymore...
What to do if maintainer is busy for a long time?
Really could not you look at this patch?
https://lkml.org/lkml/2014/3/31/141
--
Pali Rohár
pali.rohar@gmail.com
--
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
* Re: 8 months to review a patch (was Re: [PATCH] Route kbd LEDs through the generic LEDs layer)
From: Sebastian Reichel @ 2014-04-06 9:55 UTC (permalink / raw)
To: Pali Rohár
Cc: Greg KH, Pavel Machek, dtor, linux-input, Jiri Kosina,
Samuel Thibault, Dmitry Torokhov, Andrew Morton, jslaby,
Richard Purdie, LKML, Evan Broder, Arnaud Patard, Linus Torvalds
In-Reply-To: <CAHYPw2FAVtxxt_6=LArC=T_Ca=+tTsE37U29mrY-=OViHXf_2g@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 922 bytes --]
Hi Pali,
On Sun, Apr 06, 2014 at 11:43:13AM +0200, Pali Rohár wrote:
> Greg, problem is that Dmitry does not respond and has not reviewed
> this patch for more that 8 months. From my point of view this looks
> like Dmitry does not have time for review anymore...
>
> What to do if maintainer is busy for a long time?
> Really could not you look at this patch?
>
> https://lkml.org/lkml/2014/3/31/141
I don't think that he doesn't have time for review anymore, since
he is active in his git tree [0] and has sent a pull request to
torvalds recently [1].
Maybe this thread is blocked by gmail? Or he didn't notice, that
this patchset involves input.git, since the subject is mainly about
LEDs? Have you tried contacting him directly, possibly without
referencing the thread?
[0] https://git.kernel.org/cgit/linux/kernel/git/dtor/input.git/
[1] https://lkml.org/lkml/2014/4/3/154
-- Sebastian
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]
^ permalink raw reply
* Re: [PATCH] INPUT: Route keyboard LEDs through the generic LEDs layer.
From: Pali Rohár @ 2014-04-06 10:02 UTC (permalink / raw)
To: Samuel Thibault, Dmitry Torokhov, Pavel Machek, David Herrmann,
Andrew Morton, jslaby, Bryan Wu, Richard Purdie, LKML,
Evan Broder, Arnaud Patard, Peter Korsgaard, Sascha Hauer,
Rob Clark, Niels de Vos, linux-arm-kernel, blogic,
Pali Rohár, linux-input, Sebastian Reichel
2014-04-01 9:02 GMT+02:00 Pali Rohár <pali.rohar@gmail.com>:
> 2014-03-31 14:23 GMT+02:00 Samuel Thibault <samuel.thibault@ens-lyon.org>:
>> This permits to reassign keyboard LEDs to something else than keyboard "leds"
>> state, by adding keyboard led and modifier triggers connected to a series
>> of VT input LEDs, themselves connected to VT input triggers, which
>> per-input device LEDs use by default. Userland can thus easily change the LED
>> behavior of (a priori) all input devices, or of particular input devices.
>>
>> This also permits to fix #7063 from userland by using a modifier to implement
>> proper CapsLock behavior and have the keyboard caps lock led show that modifier
>> state.
>>
>> [ebroder@mokafive.com: Rebased to 3.2-rc1 or so, cleaned up some includes, and fixed some constants]
>> [blogic@openwrt.org: CONFIG_INPUT_LEDS stubs should be static inline]
>> [akpm@linux-foundation.org: remove unneeded `extern', fix comment layout]
>> Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
>> Signed-off-by: Evan Broder <evan@ebroder.net>
>> Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
>> Tested-by: Pavel Machek <pavel@ucw.cz>
>> Acked-by: Peter Korsgaard <jacmet@sunsite.dk>
>> Signed-off-by: John Crispin <blogic@openwrt.org>
>> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
>> ---
>> Changed in this version:
>> - fixes symbol dependencies between input.c and leds.c (notably
>> input_led_connect/disconnect) by stuffing them together in input.ko.
>> - documents the new leds field of struct input_dev.
>>
>> --- a/Documentation/leds/leds-class.txt
>> +++ b/Documentation/leds/leds-class.txt
>> @@ -2,9 +2,6 @@
>> LED handling under Linux
>> ========================
>>
>> -If you're reading this and thinking about keyboard leds, these are
>> -handled by the input subsystem and the led class is *not* needed.
>> -
>> In its simplest form, the LED class just allows control of LEDs from
>> userspace. LEDs appear in /sys/class/leds/. The maximum brightness of the
>> LED is defined in max_brightness file. The brightness file will set the brightness
>> --- a/drivers/input/input.c
>> +++ b/drivers/input/input.c
>> @@ -708,6 +708,9 @@ static void input_disconnect_device(stru
>> handle->open = 0;
>>
>> spin_unlock_irq(&dev->event_lock);
>> +
>> + if (is_event_supported(EV_LED, dev->evbit, EV_MAX))
>> + input_led_disconnect(dev);
>> }
>>
>> /**
>> @@ -2134,6 +2137,9 @@ int input_register_device(struct input_d
>>
>> list_add_tail(&dev->node, &input_dev_list);
>>
>> + if (is_event_supported(EV_LED, dev->evbit, EV_MAX))
>> + input_led_connect(dev);
>> +
>> list_for_each_entry(handler, &input_handler_list, node)
>> input_attach_handler(dev, handler);
>>
>> --- a/drivers/input/Kconfig
>> +++ b/drivers/input/Kconfig
>> @@ -178,6 +178,15 @@ comment "Input Device Drivers"
>>
>> source "drivers/input/keyboard/Kconfig"
>>
>> +config INPUT_LEDS
>> + bool "LED Support"
>> + depends on LEDS_CLASS = INPUT || LEDS_CLASS = y
>> + select LEDS_TRIGGERS
>> + default y
>> + help
>> + This option enables support for LEDs on keyboards managed
>> + by the input layer.
>> +
>> source "drivers/input/mouse/Kconfig"
>>
>> source "drivers/input/joystick/Kconfig"
>> --- a/drivers/input/Makefile
>> +++ b/drivers/input/Makefile
>> @@ -6,6 +6,9 @@
>>
>> obj-$(CONFIG_INPUT) += input-core.o
>> input-core-y := input.o input-compat.o input-mt.o ff-core.o
>> +ifeq ($(CONFIG_INPUT_LEDS),y)
>> +input-core-y += leds.o
>> +endif
>>
>> obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o
>> obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -11,9 +11,6 @@ menuconfig NEW_LEDS
>> Say Y to enable Linux LED support. This allows control of supported
>> LEDs from both userspace and optionally, by kernel events (triggers).
>>
>> - This is not related to standard keyboard LEDs which are controlled
>> - via the input system.
>> -
>> if NEW_LEDS
>>
>> config LEDS_CLASS
>> --- a/drivers/tty/Kconfig
>> +++ b/drivers/tty/Kconfig
>> @@ -13,6 +13,10 @@ config VT
>> bool "Virtual terminal" if EXPERT
>> depends on !S390 && !UML
>> select INPUT
>> + select NEW_LEDS
>> + select LEDS_CLASS
>> + select LEDS_TRIGGERS
>> + select INPUT_LEDS
>> default y
>> ---help---
>> If you say Y here, you will get support for terminal devices with
>> --- a/drivers/tty/vt/keyboard.c
>> +++ b/drivers/tty/vt/keyboard.c
>> @@ -33,6 +33,7 @@
>> #include <linux/string.h>
>> #include <linux/init.h>
>> #include <linux/slab.h>
>> +#include <linux/leds.h>
>>
>> #include <linux/kbd_kern.h>
>> #include <linux/kbd_diacr.h>
>> @@ -130,6 +131,7 @@ static char rep; /* flag telling cha
>> static int shift_state = 0;
>>
>> static unsigned char ledstate = 0xff; /* undefined */
>> +static unsigned char lockstate = 0xff; /* undefined */
>> static unsigned char ledioctl;
>>
>> /*
>> @@ -961,6 +963,41 @@ static void k_brl(struct vc_data *vc, un
>> }
>> }
>>
>> +/* We route VT keyboard "leds" through triggers */
>> +static void kbd_ledstate_trigger_activate(struct led_classdev *cdev);
>> +
>> +static struct led_trigger ledtrig_ledstate[] = {
>> +#define DEFINE_LEDSTATE_TRIGGER(kbd_led, nam) \
>> + [kbd_led] = { \
>> + .name = nam, \
>> + .activate = kbd_ledstate_trigger_activate, \
>> + }
>> + DEFINE_LEDSTATE_TRIGGER(VC_SCROLLOCK, "kbd-scrollock"),
>> + DEFINE_LEDSTATE_TRIGGER(VC_NUMLOCK, "kbd-numlock"),
>> + DEFINE_LEDSTATE_TRIGGER(VC_CAPSLOCK, "kbd-capslock"),
>> + DEFINE_LEDSTATE_TRIGGER(VC_KANALOCK, "kbd-kanalock"),
>> +#undef DEFINE_LEDSTATE_TRIGGER
>> +};
>> +
>> +static void kbd_lockstate_trigger_activate(struct led_classdev *cdev);
>> +
>> +static struct led_trigger ledtrig_lockstate[] = {
>> +#define DEFINE_LOCKSTATE_TRIGGER(kbd_led, nam) \
>> + [kbd_led] = { \
>> + .name = nam, \
>> + .activate = kbd_lockstate_trigger_activate, \
>> + }
>> + DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTLOCK, "kbd-shiftlock"),
>> + DEFINE_LOCKSTATE_TRIGGER(VC_ALTGRLOCK, "kbd-altgrlock"),
>> + DEFINE_LOCKSTATE_TRIGGER(VC_CTRLLOCK, "kbd-ctrllock"),
>> + DEFINE_LOCKSTATE_TRIGGER(VC_ALTLOCK, "kbd-altlock"),
>> + DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTLLOCK, "kbd-shiftllock"),
>> + DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTRLOCK, "kbd-shiftrlock"),
>> + DEFINE_LOCKSTATE_TRIGGER(VC_CTRLLLOCK, "kbd-ctrlllock"),
>> + DEFINE_LOCKSTATE_TRIGGER(VC_CTRLRLOCK, "kbd-ctrlrlock"),
>> +#undef DEFINE_LOCKSTATE_TRIGGER
>> +};
>> +
>> /*
>> * The leds display either (i) the status of NumLock, CapsLock, ScrollLock,
>> * or (ii) whatever pattern of lights people want to show using KDSETLED,
>> @@ -995,18 +1032,25 @@ static inline unsigned char getleds(void
>> return kbd->ledflagstate;
>> }
>>
>> -static int kbd_update_leds_helper(struct input_handle *handle, void *data)
>> +/* Called on trigger connection, to set initial state */
>> +static void kbd_ledstate_trigger_activate(struct led_classdev *cdev)
>> {
>> - unsigned char leds = *(unsigned char *)data;
>> + struct led_trigger *trigger = cdev->trigger;
>> + int led = trigger - ledtrig_ledstate;
>>
>> - if (test_bit(EV_LED, handle->dev->evbit)) {
>> - input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & 0x01));
>> - input_inject_event(handle, EV_LED, LED_NUML, !!(leds & 0x02));
>> - input_inject_event(handle, EV_LED, LED_CAPSL, !!(leds & 0x04));
>> - input_inject_event(handle, EV_SYN, SYN_REPORT, 0);
>> - }
>> + tasklet_disable(&keyboard_tasklet);
>> + led_trigger_event(trigger, ledstate & (1 << led) ? LED_FULL : LED_OFF);
>> + tasklet_enable(&keyboard_tasklet);
>> +}
>>
>> - return 0;
>> +static void kbd_lockstate_trigger_activate(struct led_classdev *cdev)
>> +{
>> + struct led_trigger *trigger = cdev->trigger;
>> + int led = trigger - ledtrig_lockstate;
>> +
>> + tasklet_disable(&keyboard_tasklet);
>> + led_trigger_event(trigger, lockstate & (1 << led) ? LED_FULL : LED_OFF);
>> + tasklet_enable(&keyboard_tasklet);
>> }
>>
>> /**
>> @@ -1095,16 +1139,29 @@ static void kbd_bh(unsigned long dummy)
>> {
>> unsigned char leds;
>> unsigned long flags;
>> -
>> + int i;
>> +
>> spin_lock_irqsave(&led_lock, flags);
>> leds = getleds();
>> spin_unlock_irqrestore(&led_lock, flags);
>>
>> if (leds != ledstate) {
>> - input_handler_for_each_handle(&kbd_handler, &leds,
>> - kbd_update_leds_helper);
>> + for (i = 0; i < ARRAY_SIZE(ledtrig_ledstate); i++)
>> + if ((leds ^ ledstate) & (1 << i))
>> + led_trigger_event(&ledtrig_ledstate[i],
>> + leds & (1 << i)
>> + ? LED_FULL : LED_OFF);
>> ledstate = leds;
>> }
>> +
>> + if (kbd->lockstate != lockstate) {
>> + for (i = 0; i < ARRAY_SIZE(ledtrig_lockstate); i++)
>> + if ((kbd->lockstate ^ lockstate) & (1 << i))
>> + led_trigger_event(&ledtrig_lockstate[i],
>> + kbd->lockstate & (1 << i)
>> + ? LED_FULL : LED_OFF);
>> + lockstate = kbd->lockstate;
>> + }
>> }
>>
>> DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0);
>> @@ -1442,20 +1499,6 @@ static void kbd_disconnect(struct input_
>> kfree(handle);
>> }
>>
>> -/*
>> - * Start keyboard handler on the new keyboard by refreshing LED state to
>> - * match the rest of the system.
>> - */
>> -static void kbd_start(struct input_handle *handle)
>> -{
>> - tasklet_disable(&keyboard_tasklet);
>> -
>> - if (ledstate != 0xff)
>> - kbd_update_leds_helper(handle, &ledstate);
>> -
>> - tasklet_enable(&keyboard_tasklet);
>> -}
>> -
>> static const struct input_device_id kbd_ids[] = {
>> {
>> .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
>> @@ -1477,7 +1520,6 @@ static struct input_handler kbd_handler
>> .match = kbd_match,
>> .connect = kbd_connect,
>> .disconnect = kbd_disconnect,
>> - .start = kbd_start,
>> .name = "kbd",
>> .id_table = kbd_ids,
>> };
>> @@ -1501,6 +1543,20 @@ int __init kbd_init(void)
>> if (error)
>> return error;
>>
>> + for (i = 0; i < ARRAY_SIZE(ledtrig_ledstate); i++) {
>> + error = led_trigger_register(&ledtrig_ledstate[i]);
>> + if (error)
>> + pr_err("error %d while registering trigger %s\n",
>> + error, ledtrig_ledstate[i].name);
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(ledtrig_lockstate); i++) {
>> + error = led_trigger_register(&ledtrig_lockstate[i]);
>> + if (error)
>> + pr_err("error %d while registering trigger %s\n",
>> + error, ledtrig_lockstate[i].name);
>> + }
>> +
>> tasklet_enable(&keyboard_tasklet);
>> tasklet_schedule(&keyboard_tasklet);
>>
>> --- a/include/linux/input.h
>> +++ b/include/linux/input.h
>> @@ -79,6 +79,7 @@ struct input_value {
>> * @led: reflects current state of device's LEDs
>> * @snd: reflects current state of sound effects
>> * @sw: reflects current state of device's switches
>> + * @leds: leds objects for the device's LEDs
>> * @open: this method is called when the very first user calls
>> * input_open_device(). The driver must prepare the device
>> * to start generating events (start polling thread,
>> @@ -164,6 +165,8 @@ struct input_dev {
>> unsigned long snd[BITS_TO_LONGS(SND_CNT)];
>> unsigned long sw[BITS_TO_LONGS(SW_CNT)];
>>
>> + struct led_classdev *leds;
>> +
>> int (*open)(struct input_dev *dev);
>> void (*close)(struct input_dev *dev);
>> int (*flush)(struct input_dev *dev, struct file *file);
>> @@ -531,4 +534,22 @@ int input_ff_erase(struct input_dev *dev
>> int input_ff_create_memless(struct input_dev *dev, void *data,
>> int (*play_effect)(struct input_dev *, void *, struct ff_effect *));
>>
>> +#ifdef CONFIG_INPUT_LEDS
>> +
>> +int input_led_connect(struct input_dev *dev);
>> +void input_led_disconnect(struct input_dev *dev);
>> +
>> +#else
>> +
>> +static inline int input_led_connect(struct input_dev *dev)
>> +{
>> + return 0;
>> +}
>> +
>> +static inline void input_led_disconnect(struct input_dev *dev)
>> +{
>> +}
>> +
>> +#endif
>> +
>> #endif
>> --- /dev/null
>> +++ b/drivers/input/leds.c
>> @@ -0,0 +1,249 @@
>> +/*
>> + * LED support for the input layer
>> + *
>> + * Copyright 2010-2014 Samuel Thibault <samuel.thibault@ens-lyon.org>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/slab.h>
>> +#include <linux/module.h>
>> +#include <linux/init.h>
>> +#include <linux/leds.h>
>> +#include <linux/input.h>
>> +
>> +/*
>> + * Keyboard LEDs are propagated by default like the following example:
>> + *
>> + * VT keyboard numlock trigger
>> + * -> vt::numl VT LED
>> + * -> vt-numl VT trigger
>> + * -> per-device inputX::numl LED
>> + *
>> + * Userland can however choose the trigger for the vt::numl LED, or
>> + * independently choose the trigger for any inputx::numl LED.
>> + *
>> + *
>> + * VT LED classes and triggers are registered on-demand according to
>> + * existing LED devices
>> + */
>> +
>> +/* Handler for VT LEDs, just triggers the corresponding VT trigger. */
>> +static void vt_led_set(struct led_classdev *cdev,
>> + enum led_brightness brightness);
>> +static struct led_classdev vt_leds[LED_CNT] = {
>> +#define DEFINE_INPUT_LED(vt_led, nam, deftrig) \
>> + [vt_led] = { \
>> + .name = "vt::"nam, \
>> + .max_brightness = 1, \
>> + .brightness_set = vt_led_set, \
>> + .default_trigger = deftrig, \
>> + }
>> +/* Default triggers for the VT LEDs just correspond to the legacy
>> + * usage. */
>> + DEFINE_INPUT_LED(LED_NUML, "numl", "kbd-numlock"),
>> + DEFINE_INPUT_LED(LED_CAPSL, "capsl", "kbd-capslock"),
>> + DEFINE_INPUT_LED(LED_SCROLLL, "scrolll", "kbd-scrollock"),
>> + DEFINE_INPUT_LED(LED_COMPOSE, "compose", NULL),
>> + DEFINE_INPUT_LED(LED_KANA, "kana", "kbd-kanalock"),
>> + DEFINE_INPUT_LED(LED_SLEEP, "sleep", NULL),
>> + DEFINE_INPUT_LED(LED_SUSPEND, "suspend", NULL),
>> + DEFINE_INPUT_LED(LED_MUTE, "mute", NULL),
>> + DEFINE_INPUT_LED(LED_MISC, "misc", NULL),
>> + DEFINE_INPUT_LED(LED_MAIL, "mail", NULL),
>> + DEFINE_INPUT_LED(LED_CHARGING, "charging", NULL),
>> +};
>> +static const char *const vt_led_names[LED_CNT] = {
>> + [LED_NUML] = "numl",
>> + [LED_CAPSL] = "capsl",
>> + [LED_SCROLLL] = "scrolll",
>> + [LED_COMPOSE] = "compose",
>> + [LED_KANA] = "kana",
>> + [LED_SLEEP] = "sleep",
>> + [LED_SUSPEND] = "suspend",
>> + [LED_MUTE] = "mute",
>> + [LED_MISC] = "misc",
>> + [LED_MAIL] = "mail",
>> + [LED_CHARGING] = "charging",
>> +};
>> +/* Handler for hotplug initialization */
>> +static void vt_led_trigger_activate(struct led_classdev *cdev);
>> +/* VT triggers */
>> +static struct led_trigger vt_led_triggers[LED_CNT] = {
>> +#define DEFINE_INPUT_LED_TRIGGER(vt_led, nam) \
>> + [vt_led] = { \
>> + .name = "vt-"nam, \
>> + .activate = vt_led_trigger_activate, \
>> + }
>> + DEFINE_INPUT_LED_TRIGGER(LED_NUML, "numl"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_CAPSL, "capsl"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_SCROLLL, "scrolll"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_COMPOSE, "compose"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_KANA, "kana"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_SLEEP, "sleep"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_SUSPEND, "suspend"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_MUTE, "mute"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_MISC, "misc"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_MAIL, "mail"),
>> + DEFINE_INPUT_LED_TRIGGER(LED_CHARGING, "charging"),
>> +};
>> +
>> +/* Lock for registration coherency */
>> +static DEFINE_MUTEX(vt_led_registered_lock);
>> +
>> +/* Which VT LED classes and triggers are registered */
>> +static unsigned long vt_led_registered[BITS_TO_LONGS(LED_CNT)];
>> +
>> +/* Number of input devices having each LED */
>> +static int vt_led_references[LED_CNT];
>> +
>> +/* VT LED state change, tell the VT trigger. */
>> +static void vt_led_set(struct led_classdev *cdev,
>> + enum led_brightness brightness)
>> +{
>> + int led = cdev - vt_leds;
>> +
>> + led_trigger_event(&vt_led_triggers[led], !!brightness);
>> +}
>> +
>> +/* LED state change for some keyboard, notify that keyboard. */
>> +static void perdevice_input_led_set(struct led_classdev *cdev,
>> + enum led_brightness brightness)
>> +{
>> + struct input_dev *dev;
>> + struct led_classdev *leds;
>> + int led;
>> +
>> + dev = cdev->dev->platform_data;
>> + if (!dev)
>> + /* Still initializing */
>> + return;
>> + leds = dev->leds;
>> + led = cdev - leds;
>> +
>> + input_event(dev, EV_LED, led, !!brightness);
>> + input_event(dev, EV_SYN, SYN_REPORT, 0);
>> +}
>> +
>> +/* Keyboard hotplug, initialize its LED status */
>> +static void vt_led_trigger_activate(struct led_classdev *cdev)
>> +{
>> + struct led_trigger *trigger = cdev->trigger;
>> + int led = trigger - vt_led_triggers;
>> +
>> + if (cdev->brightness_set)
>> + cdev->brightness_set(cdev, vt_leds[led].brightness);
>> +}
>> +
>> +/* Free led stuff from input device, used at abortion and disconnection. */
>> +static void input_led_delete(struct input_dev *dev)
>> +{
>> + if (dev) {
>> + struct led_classdev *leds = dev->leds;
>> + if (leds) {
>> + int i;
>> + for (i = 0; i < LED_CNT; i++)
>> + kfree(leds[i].name);
>> + kfree(leds);
>> + dev->leds = NULL;
>> + }
>> + }
>> +}
>> +
>> +/* A new input device with potential LEDs to connect. */
>> +int input_led_connect(struct input_dev *dev)
>> +{
>> + int i, error = 0;
>> + struct led_classdev *leds;
>> +
>> + dev->leds = leds = kcalloc(LED_CNT, sizeof(*leds), GFP_KERNEL);
>> + if (!dev->leds)
>> + return -ENOMEM;
>> +
>> + /* lazily register missing VT LEDs */
>> + mutex_lock(&vt_led_registered_lock);
>> + for (i = 0; i < LED_CNT; i++)
>> + if (vt_leds[i].name && test_bit(i, dev->ledbit)) {
>> + if (!vt_led_references[i]) {
>> + led_trigger_register(&vt_led_triggers[i]);
>> + /* This keyboard is first to have led i,
>> + * try to register it */
>> + if (!led_classdev_register(NULL, &vt_leds[i]))
>> + vt_led_references[i] = 1;
>> + else
>> + led_trigger_unregister(&vt_led_triggers[i]);
>> + } else
>> + vt_led_references[i]++;
>> + }
>> + mutex_unlock(&vt_led_registered_lock);
>> +
>> + /* and register this device's LEDs */
>> + for (i = 0; i < LED_CNT; i++)
>> + if (vt_leds[i].name && test_bit(i, dev->ledbit)) {
>> + leds[i].name = kasprintf(GFP_KERNEL, "%s::%s",
>> + dev_name(&dev->dev),
>> + vt_led_names[i]);
>> + if (!leds[i].name) {
>> + error = -ENOMEM;
>> + goto err;
>> + }
>> + leds[i].max_brightness = 1;
>> + leds[i].brightness_set = perdevice_input_led_set;
>> + leds[i].default_trigger = vt_led_triggers[i].name;
>> + }
>> +
>> + /* No issue so far, we can register for real. */
>> + for (i = 0; i < LED_CNT; i++)
>> + if (leds[i].name) {
>> + led_classdev_register(&dev->dev, &leds[i]);
>> + leds[i].dev->platform_data = dev;
>> + perdevice_input_led_set(&leds[i],
>> + vt_leds[i].brightness);
>> + }
>> +
>> + return 0;
>> +
>> +err:
>> + input_led_delete(dev);
>> + return error;
>> +}
>> +
>> +/*
>> + * Disconnected input device. Clean it, and deregister now-useless VT LEDs
>> + * and triggers.
>> + */
>> +void input_led_disconnect(struct input_dev *dev)
>> +{
>> + int i;
>> + struct led_classdev *leds = dev->leds;
>> +
>> + for (i = 0; i < LED_CNT; i++)
>> + if (leds[i].name)
>> + led_classdev_unregister(&leds[i]);
>> +
>> + input_led_delete(dev);
>> +
>> + mutex_lock(&vt_led_registered_lock);
>> + for (i = 0; i < LED_CNT; i++) {
>> + if (!vt_leds[i].name || !test_bit(i, dev->ledbit))
>> + continue;
>> +
>> + vt_led_references[i]--;
>> + if (vt_led_references[i]) {
>> + /* Still some devices needing it */
>> + continue;
>> + }
>> +
>> + led_classdev_unregister(&vt_leds[i]);
>> + led_trigger_unregister(&vt_led_triggers[i]);
>> + clear_bit(i, vt_led_registered);
>> + }
>> + mutex_unlock(&vt_led_registered_lock);
>> +}
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("User LED support for input layer");
>> +MODULE_AUTHOR("Samuel Thibault <samuel.thibault@ens-lyon.org>");
>>
>
> Dmitry, can you review this patch and include it into 3.15?
>
(Per Pavel and Sebastian suggestion resending email with included
keyword INPUT in subject)
--
Pali Rohár
pali.rohar@gmail.com
--
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
* Re: [PATCH] input: pcap2: avoid calling mutex_lock() in irq handler
From: Dmitry Torokhov @ 2014-04-06 20:24 UTC (permalink / raw)
To: Alexey Khoroshilov
Cc: linux-input, linux-kernel, ldv-project, Daniel Ribeiro,
Ilya Petrov, Antonio Ospite
In-Reply-To: <1396731290-7062-1-git-send-email-khoroshilov@ispras.ru>
On Sun, Apr 06, 2014 at 12:54:50AM +0400, Alexey Khoroshilov wrote:
> pcap_keys_handler() calls ezx_pcap_read() that calls mutex_lock().
> pcap_keys_handler() is registered as nonthreaded irq handler,
> that means sleeping function is called in irq handler.
>
> The patch makes a switch to threaded irq handling.
> Compile tested only.
>
> Found by Linux Driver Verification project (linuxtesting.org).
>
> Signed-off-by: Alexey Khoroshilov <khoroshilov@ispras.ru>
> ---
> drivers/input/misc/pcap_keys.c | 10 ++++++----
> 1 file changed, 6 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/input/misc/pcap_keys.c b/drivers/input/misc/pcap_keys.c
> index cd230365166e..2a10f3a30969 100644
> --- a/drivers/input/misc/pcap_keys.c
> +++ b/drivers/input/misc/pcap_keys.c
> @@ -79,13 +79,15 @@ static int pcap_keys_probe(struct platform_device *pdev)
> if (err)
> goto fail_allocate;
>
> - err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
> - pcap_keys_handler, 0, "Power key", pcap_keys);
> + err = request_threaded_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
> + NULL, pcap_keys_handler, 0,
> + "Power key", pcap_keys);
> if (err)
> goto fail_register;
>
> - err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
> - pcap_keys_handler, 0, "Headphone button", pcap_keys);
> + err = request_threaded_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
> + NULL, pcap_keys_handler, 0,
> + "Headphone button", pcap_keys);
So I guess we do need threaded IRQ here, but I do not see the pcap's
irqchip specifying IRQCHIP_ONESHOT_SAFE so I do not think
request_threaded_irq() without IRQF_ONESHOT would succeed.
Can someone test the change to be sure?
Thanks!
--
Dmitry
^ permalink raw reply
* Re: [PATCH] input: pcap2: avoid calling mutex_lock() in irq handler
From: Antonio Ospite @ 2014-04-06 21:37 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Alexey Khoroshilov, linux-input, linux-kernel, ldv-project,
Daniel Ribeiro, Ilya Petrov, Antonio Ospite
In-Reply-To: <20140406202436.GA16739@core.coreip.homeip.net>
On Sun, 6 Apr 2014 13:24:36 -0700
Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
> On Sun, Apr 06, 2014 at 12:54:50AM +0400, Alexey Khoroshilov wrote:
> > pcap_keys_handler() calls ezx_pcap_read() that calls mutex_lock().
> > pcap_keys_handler() is registered as nonthreaded irq handler,
> > that means sleeping function is called in irq handler.
> >
> > The patch makes a switch to threaded irq handling.
> > Compile tested only.
> >
> > Found by Linux Driver Verification project (linuxtesting.org).
> >
> > Signed-off-by: Alexey Khoroshilov <khoroshilov@ispras.ru>
> > ---
> > drivers/input/misc/pcap_keys.c | 10 ++++++----
> > 1 file changed, 6 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/input/misc/pcap_keys.c b/drivers/input/misc/pcap_keys.c
> > index cd230365166e..2a10f3a30969 100644
> > --- a/drivers/input/misc/pcap_keys.c
> > +++ b/drivers/input/misc/pcap_keys.c
> > @@ -79,13 +79,15 @@ static int pcap_keys_probe(struct platform_device *pdev)
> > if (err)
> > goto fail_allocate;
> >
> > - err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
> > - pcap_keys_handler, 0, "Power key", pcap_keys);
> > + err = request_threaded_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
> > + NULL, pcap_keys_handler, 0,
> > + "Power key", pcap_keys);
> > if (err)
> > goto fail_register;
> >
> > - err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
> > - pcap_keys_handler, 0, "Headphone button", pcap_keys);
> > + err = request_threaded_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
> > + NULL, pcap_keys_handler, 0,
> > + "Headphone button", pcap_keys);
>
> So I guess we do need threaded IRQ here, but I do not see the pcap's
> irqchip specifying IRQCHIP_ONESHOT_SAFE so I do not think
> request_threaded_irq() without IRQF_ONESHOT would succeed.
>
> Can someone test the change to be sure?
>
I can try to test it in a couple of weeks, I don't have the hardware
at hand right now.
Ciao,
Antonio
--
Antonio Ospite
http://ao2.it
A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
^ permalink raw reply
* Re: [PATCH] HID: add missing hid usages
From: Jiri Kosina @ 2014-04-07 7:49 UTC (permalink / raw)
To: Olivier Gay
Cc: linux-input, Nestor Lopez Casado, Mathieu Meisser,
Dmitry Torokhov
In-Reply-To: <1396623746-1416-1-git-send-email-ogay@logitech.com>
[ adding Dmitry to CC ]
On Fri, 4 Apr 2014, Olivier Gay wrote:
> Add some missing hid usages from consumer page, add
> display brightness control usages from approved hid usage
> table request HUTTR41:
> http://www.usb.org/developers/hidpage/HUTRR41.pdf
> and add voice command usage from approved request HUTTR45:
> http://www.usb.org/developers/hidpage/Voice_Command_Usage.pdf
>
> Signed-off-by: Olivier Gay <ogay@logitech.com>
> Signed-off-by: Mathieu Meisser <mmeisser@logitech.com>
> ---
>
> Hi all,
>
> with this patch we add some missing hid usages to the hid system.
> Those missing hid usages are already used by some Logitech
> products as well as some future ones.
>
> (Note: checkpatch.pl shows some errors but I didn't want to
> break the current code style in hid-input.c)
Thanks for the patch, Olivier. All looking good to me, but for the input.h
additions, I'd like to get Ack from Dmitry, hence CCing him.
Dmitry, ok to take this through my tree with your ack?
Thanks.
>
> Best regards,
>
> Olivier
>
> drivers/hid/hid-debug.c | 14 ++++++++++++++
> drivers/hid/hid-input.c | 15 +++++++++++++++
> include/uapi/linux/input.h | 16 ++++++++++++++++
> 3 files changed, 45 insertions(+)
>
> diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
> index 53b771d..742d78b 100644
> --- a/drivers/hid/hid-debug.c
> +++ b/drivers/hid/hid-debug.c
> @@ -855,6 +855,20 @@ static const char *keys[KEY_MAX + 1] = {
> [KEY_KBDILLUMDOWN] = "KbdIlluminationDown",
> [KEY_KBDILLUMUP] = "KbdIlluminationUp",
> [KEY_SWITCHVIDEOMODE] = "SwitchVideoMode",
> + [KEY_BUTTONCONFIG] = "ButtonConfig",
> + [KEY_TASKMANAGER] = "TaskManager",
> + [KEY_JOURNAL] = "Journal",
> + [KEY_CONTROLPANEL] = "ControlPanel",
> + [KEY_APPSELECT] = "AppSelect",
> + [KEY_SCREENSAVER] = "ScreenSaver",
> + [KEY_VOICECOMMAND] = "VoiceCommand",
> + [KEY_BRIGHTNESS_INC] = "BrightnessInc",
> + [KEY_BRIGHTNESS_DEC] = "BrightnessDec",
> + [KEY_BRIGHTNESS] = "Brightness",
> + [KEY_BRIGHTNESS_TOGGLE] = "BrightnessToggle",
> + [KEY_BRIGHTNESS_MIN] = "BrightnessMin",
> + [KEY_BRIGHTNESS_MAX] = "BrightnessMax",
> + [KEY_BRIGHTNESS_AUTO] = "BrightnessAuto",
> };
>
> static const char *relatives[REL_MAX + 1] = {
> diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
> index 930382e..e1409da 100644
> --- a/drivers/hid/hid-input.c
> +++ b/drivers/hid/hid-input.c
> @@ -721,6 +721,14 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
> case 0x06c: map_key_clear(KEY_YELLOW); break;
> case 0x06d: map_key_clear(KEY_ZOOM); break;
>
> + case 0x06f: map_key_clear(KEY_BRIGHTNESS_INC); break;
> + case 0x070: map_key_clear(KEY_BRIGHTNESS_DEC); break;
> + case 0x071: map_key_clear(KEY_BRIGHTNESS); break;
> + case 0x072: map_key_clear(KEY_BRIGHTNESS_TOGGLE); break;
> + case 0x073: map_key_clear(KEY_BRIGHTNESS_MIN); break;
> + case 0x074: map_key_clear(KEY_BRIGHTNESS_MAX); break;
> + case 0x075: map_key_clear(KEY_BRIGHTNESS_AUTO); break;
> +
> case 0x082: map_key_clear(KEY_VIDEO_NEXT); break;
> case 0x083: map_key_clear(KEY_LAST); break;
> case 0x084: map_key_clear(KEY_ENTER); break;
> @@ -761,6 +769,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
> case 0x0bf: map_key_clear(KEY_SLOW); break;
>
> case 0x0cd: map_key_clear(KEY_PLAYPAUSE); break;
> + case 0x0cf: map_key_clear(KEY_VOICECOMMAND); break;
> case 0x0e0: map_abs_clear(ABS_VOLUME); break;
> case 0x0e2: map_key_clear(KEY_MUTE); break;
> case 0x0e5: map_key_clear(KEY_BASSBOOST); break;
> @@ -768,6 +777,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
> case 0x0ea: map_key_clear(KEY_VOLUMEDOWN); break;
> case 0x0f5: map_key_clear(KEY_SLOW); break;
>
> + case 0x181: map_key_clear(KEY_BUTTONCONFIG); break;
> case 0x182: map_key_clear(KEY_BOOKMARKS); break;
> case 0x183: map_key_clear(KEY_CONFIG); break;
> case 0x184: map_key_clear(KEY_WORDPROCESSOR); break;
> @@ -781,6 +791,8 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
> case 0x18c: map_key_clear(KEY_VOICEMAIL); break;
> case 0x18d: map_key_clear(KEY_ADDRESSBOOK); break;
> case 0x18e: map_key_clear(KEY_CALENDAR); break;
> + case 0x18f: map_key_clear(KEY_TASKMANAGER); break;
> + case 0x190: map_key_clear(KEY_JOURNAL); break;
> case 0x191: map_key_clear(KEY_FINANCE); break;
> case 0x192: map_key_clear(KEY_CALC); break;
> case 0x193: map_key_clear(KEY_PLAYER); break;
> @@ -789,12 +801,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
> case 0x199: map_key_clear(KEY_CHAT); break;
> case 0x19c: map_key_clear(KEY_LOGOFF); break;
> case 0x19e: map_key_clear(KEY_COFFEE); break;
> + case 0x19f: map_key_clear(KEY_CONTROLPANEL); break;
> + case 0x1a2: map_key_clear(KEY_APPSELECT); break;
> case 0x1a3: map_key_clear(KEY_NEXT); break;
> case 0x1a4: map_key_clear(KEY_PREVIOUS); break;
> case 0x1a6: map_key_clear(KEY_HELP); break;
> case 0x1a7: map_key_clear(KEY_DOCUMENTS); break;
> case 0x1ab: map_key_clear(KEY_SPELLCHECK); break;
> case 0x1ae: map_key_clear(KEY_KEYBOARD); break;
> + case 0x1b1: map_key_clear(KEY_SCREENSAVER); break;
> case 0x1b4: map_key_clear(KEY_FILE); break;
> case 0x1b6: map_key_clear(KEY_IMAGES); break;
> case 0x1b7: map_key_clear(KEY_AUDIO); break;
> diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
> index bd24470..d82454f 100644
> --- a/include/uapi/linux/input.h
> +++ b/include/uapi/linux/input.h
> @@ -722,6 +722,22 @@ struct input_keymap_entry {
>
> #define KEY_ALS_TOGGLE 0x230 /* Ambient light sensor */
>
> +#define KEY_BUTTONCONFIG 0x240 /* AL Button Configuration */
> +#define KEY_TASKMANAGER 0x241 /* AL Task/Project Manager */
> +#define KEY_JOURNAL 0x242 /* AL Log/Journal/Timecard */
> +#define KEY_CONTROLPANEL 0x243 /* AL Control Panel */
> +#define KEY_APPSELECT 0x244 /* AL Select Task/Application */
> +#define KEY_SCREENSAVER 0x245 /* AL Screen Saver */
> +#define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */
> +
> +#define KEY_BRIGHTNESS_INC KEY_BRIGHTNESSUP
> +#define KEY_BRIGHTNESS_DEC KEY_BRIGHTNESSDOWN
> +#define KEY_BRIGHTNESS 0x250 /* Display Brightness */
> +#define KEY_BRIGHTNESS_TOGGLE 0x251 /* Backlight Toggle */
> +#define KEY_BRIGHTNESS_MIN 0x252 /* Set Brightness to Minimum */
> +#define KEY_BRIGHTNESS_MAX 0x253 /* Set Brightness to Maximum */
> +#define KEY_BRIGHTNESS_AUTO 0x254 /* Set Auto Brightness */
> +
> #define BTN_TRIGGER_HAPPY 0x2c0
> #define BTN_TRIGGER_HAPPY1 0x2c0
> #define BTN_TRIGGER_HAPPY2 0x2c1
> --
> 1.9.0
>
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH 0/2] Microsoft Surface Type/Touch Cover 2 fixes
From: Jiri Kosina @ 2014-04-07 7:57 UTC (permalink / raw)
To: Benjamin Tissoires; +Cc: Reyad Attiyat, Derya, linux-input, linux-kernel
In-Reply-To: <1396286830-2795-1-git-send-email-benjamin.tissoires@redhat.com>
On Mon, 31 Mar 2014, Benjamin Tissoires wrote:
> well, this is a series which should fix the current state of the Surface
> 2 touch cover. The first one has been reported by Derya and the second
> one fixes the problem for this device and the future ones.
Thanks Benjamin, queuing this for 3.15 still.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: HID vendor access from user space
From: Nestor Lopez Casado @ 2014-04-07 16:15 UTC (permalink / raw)
To: Jiri Kosina
Cc: David Herrmann, Dmitry Torokhov, open list:HID CORE LAYER,
Olivier Gay, Benjamin Tissoires, Mathieu Meisser
In-Reply-To: <alpine.LNX.2.00.1404031547580.32122@pobox.suse.cz>
On Thu, Apr 3, 2014 at 3:57 PM, Jiri Kosina <jkosina@suse.cz> wrote:
> On Wed, 2 Apr 2014, Nestor Lopez Casado wrote:
>
>> >> If a user wants to configure/control the device there are two choices,
>> >> either write a hid-specific driver to deal with the vendor specific
>> >> collection, or open the corresponding /hidraw node from userspace.
>> >>
>> >> But a hidraw node that carries system input data requires root priviledges.
>> >>
>> >> I'm interested in hearing your opinions on how to add the capability
>> >> for a normal user process to control/configure a HID device via
>> >> reports exchanged with a vendor collection.
>> >>
>> >> I have one proposal, which is to create, say "/dev/hidvendorX", nodes
>> >> for all top level HID collections which are today ignored by hid-input
>> >> and/or other subsystems.
>> >>
>> >> These nodes would not require root priviledge by default and thus,
>> >> users could control/reconfigure their devices from a standard
>> >> application while keeping the "standard" input functionality intact.
>> >
>> > Why not write a kernel driver? We have a pretty nice infrastructure
>> > for all this.
>>
>> Do you mean a "hid-specific" driver for certain vid & pid devices ?
>
> I indeed believe that it's what David was proposing. Writing in-kernel
> drivers that handle just specific collections/usages, and let the rest
> be handled by the core infrastructure, is super-easy these days.
I don't think that the burden of hid input device status and
configuration should belong to the kernel 'for all devices in all
situations', as it would mandate the proliferation of many hid
specific code and too much kernel bloating that would be best handled
at a higher level. If we take for example the Logitech devices, we
have many features which are accessible via vendor collections, and
the easiest way to use them is via a user space application or
library.
>
> Also, if you need users to be able to trigger particular actions being
> perfomed by the driver, sysfs knobs should be a reasonable way to doing
> this.
>
> As an example of such interface -- creating a sysfs trigger for
> hid-logitech-dj that will send the pairing command to the unified
> receiver, has been on my TODO list for quite some time already (and was
> even suggested by Linus).
>
This is fine, but this solves a very particular case, the
pairing/unpairing of devices with the Unifying receiver. There is much
more to device configuration that just pairing/unpairing, it does not
even need to be Unifying devices, or Logitech devices, for that
matter.
(See also previous comment)
>> > If it's a license-issue, I recommend using udev rules to change
>> > permissions on the requested devices directly during setup. You can
>> > then use hidraw.
>> It is not a license issue, it is a security issue.
>>
>> Every hid class interface, (either usb, bluetooth, i2c, etc) has an
>> associated hidraw node, and that node aggregates all reports sent from
>> the device.
>>
>> Say that for example, the device has a keyboard collection and a
>> vendor collection, then changing permissions on the associated hidraw
>> node (so as to give any desktop user unrestricted access to the vendor
>> collection) would open the door to malicious code that could log the
>> keyboard activity (as both the vendor reports and input reports are
>> seen on the same hidraw node)
>
> I think I am missing the point here. If you own the credentials of the
> session owner, you can log keystrokes anyway even now, just use
>
> xinput list
> xinput test <id>
>
> and see the result :) Admittedly, this is a little bit more difficult when
> you are outside X11 session, but I am not sure how much concerned you are
> about such scenario anyway.
Oh, this I did not know!
Let me try to sketch the scenario we are concerned with:
We want user processes to be able to communicate with hid input
devices, via vendor specific commands. This vendor specific data flows
thru hid vendor reports.
Today, user processes can only access these reports via hidraw. Hidraw
aggregates user input (keyboard, mouse, etc) with vendor reports.
Hidraw nodes are owned by root by default. This means that an
unprivileged user would not be able to send vendor commands to a hid
device.
On the other hand, if an unprivileged user (or group) is given access
to hidraw, then she will also be able to spy the input, even if she is
remotely logged to a computer.
We consider this a potential security problem and we are looking into
a solution that would enable the currently logged local user to access
the vendor collections of a hid device without requiring any special
permissions.
Maybe the solution is not within the kernel itself, but rather on
using a user mode component, maybe the X server or even udev. Do you
get my point now ?
>
> --
> Jiri Kosina
> SUSE Labs
-nestor.
^ permalink raw reply
* Re: HID vendor access from user space
From: David Herrmann @ 2014-04-07 16:27 UTC (permalink / raw)
To: Nestor Lopez Casado
Cc: Jiri Kosina, Dmitry Torokhov, open list:HID CORE LAYER,
Olivier Gay, Benjamin Tissoires, Mathieu Meisser
In-Reply-To: <CAE7qMrqizfupabTciYfo+gGNG16ZM482y8tBbuQdvZYHdBaV=Q@mail.gmail.com>
Hi
On Mon, Apr 7, 2014 at 6:15 PM, Nestor Lopez Casado
<nlopezcasad@logitech.com> wrote:
> We consider this a potential security problem and we are looking into
> a solution that would enable the currently logged local user to access
> the vendor collections of a hid device without requiring any special
> permissions.
>
> Maybe the solution is not within the kernel itself, but rather on
> using a user mode component, maybe the X server or even udev. Do you
> get my point now ?
There is a lot of work going on to make Xorg (and also Wayland
compositors) run as non-root. Many people, however, seem to ignore
that this means the compositor runs with the _same_ privileges as the
applications. At least that is the security model we are going for.
Therefore, if a compositor can access input devices, all applications
can do so, too. Of course, you can introduce new privilege-levels and
or run applications with less privileges than normal user processes.
But I guess you get the point.
Therefore, I really doubt there is much need to split the
access-rights here. What you wanna do is to provide this
privilege-separation on the kernel level. However, I think this is the
wrong way to do it. Imagine an HID device that has several
vendor-commands, some of them rather trivial and un-privileged, others
not. Do you expect the kernel to provide one device-node for each? How
do you expect the kernel to know which vender-extensions are _safe_ to
be grouped together?
Yes, the kernel should provide different interfaces for different
hw-features so user-space can apply fine-grained access-modes.
However, if we don't know what hardware feature a specific command
represents, I don't think we should apply some kind of random
interface separation. This is why Jiri and I recommend writing an
in-kernel driver. That driver can be small and all it does it provide
a separate interface for your vendor-extensions. This can be as easy
as setting HID_QUIRK_MULTI_INPUT for given devices (or an equivalent
HID_QUIRK_MULTI_HIDRAW, which doesn't exist, yet). I still think it
would be nicer if the kernel provides a proper interface-abstraction,
but I'd be fine with such a quirk, too.
Maybe you can describe one specific example? Otherwise, it's hard to
imagine hid devices that provide two totally separate interfaces that
we had the need to split them. All examples I know already provide
different virtual HID devices and thus don't suffer from this problem.
Furthermore, did you do some research how other platforms deal with
it?
Thanks
David
^ permalink raw reply
* Re: [PATCHv2 1/5] Input: add common DT binding for touchscreens
From: Rob Herring @ 2014-04-07 16:38 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Sebastian Reichel, Dmitry Torokhov, Dmitry Torokhov,
linux-input@vger.kernel.org, Tony Lindgren, Rob Herring,
Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
devicetree@vger.kernel.org, linux-omap,
linux-kernel@vger.kernel.org
In-Reply-To: <1396736821-14395-2-git-send-email-sre@kernel.org>
On Sat, Apr 5, 2014 at 5:26 PM, Sebastian Reichel <sre@kernel.org> wrote:
> Add common DT binding documentation for touchscreen devices and
> implement input_parse_touchscreen_of_params, which parses the common
> properties and configures the input device accordingly.
Good.
>
> Signed-off-by: Sebastian Reichel <sre@kernel.org>
> ---
> .../bindings/input/touchscreen/touchscreen.txt | 9 ++++++
> drivers/input/input.c | 34 ++++++++++++++++++++++
> include/linux/input.h | 8 +++++
> 3 files changed, 51 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
>
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
> new file mode 100644
> index 0000000..a2ff0a0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt
> @@ -0,0 +1,9 @@
> +General Touchscreen Properties:
> +
> +Optional properties for Touchscreens:
> + - touchscreen-size-x : horizontal resolution of touchscreen
> + - touchscreen-size-y : vertical resolution of touchscreen
While I like the consistency, x-size and y-size are already commonly
used. Perhaps the common binding should have both and x-size/y-size be
marked deprecated.
> + - touchscreen-max-pressure : maximum reported pressure
> + - touchscreen-fuzz-x : horizontal noise value of the absolute input device
> + - touchscreen-fuzz-y : vertical noise value of the absolute input device
> + - touchscreen-fuzz-pressure : pressure noise value of the absolute input device
What are the units or are they just an arbitrary range dependent on
the controller? Several existing bindings appear to be in pixels, but
that seems wrong to me.
There's also these various properties that should have common versions created:
ti,x-plate-resistance and ti,x-plate-ohms (tsc2007)
- rohm,flip-x : Flip touch coordinates on the X axis
- rohm,flip-y : Flip touch coordinates on the Y axis
- x-invert: invert X axis
- y-invert: invert Y axis
- contact-threshold:
- moving-threshold:
Rob
^ permalink raw reply
* [PATCH] HID: rmi: introduce RMI driver for Synaptics touchpads
From: Benjamin Tissoires @ 2014-04-07 17:39 UTC (permalink / raw)
To: Andrew Duggan, Christopher Heiny, Jiri Kosina, linux-input,
linux-kernel
This driver add support for RMI4 over USB or I2C.
The current state is that it uses its own RMI4 implementation, but once
RMI4 is merged upstream, the driver will be a transport driver for the
RMI4 library.
Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
drivers/hid/Kconfig | 8 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-core.c | 2 +
drivers/hid/hid-rmi.c | 889 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/hid.h | 2 +
5 files changed, 902 insertions(+)
create mode 100644 drivers/hid/hid-rmi.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 7af9d0b..762f15d 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -657,6 +657,14 @@ config HID_SUNPLUS
---help---
Support for Sunplus wireless desktop.
+config HID_RMI
+ tristate "Synaptics RMI4 device support"
+ depends on HID
+ ---help---
+ Support for Synaptics RMI4 touchpads.
+ Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
+ and want support for its special functionalities.
+
config HID_GREENASIA
tristate "GreenAsia (Product ID 0x12) game controller support"
depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index fc712dd..a6fa6ba 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
hid-roccat-koneplus.o hid-roccat-konepure.o hid-roccat-kovaplus.o \
hid-roccat-lua.o hid-roccat-pyra.o hid-roccat-ryos.o hid-roccat-savu.o
+obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index abaa0a5..c97ece8 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1879,6 +1879,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
{ HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) },
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) },
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
new file mode 100644
index 0000000..699d631
--- /dev/null
+++ b/drivers/hid/hid-rmi.c
@@ -0,0 +1,889 @@
+/*
+ * Copyright (c) 2013 Andrew Duggan <aduggan@synaptics.com>
+ * Copyright (c) 2013 Synaptics Incorporated
+ * Copyright (c) 2014 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2014 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include "hid-ids.h"
+
+#define RMI_MOUSE_REPORT_ID 0x01 /* Mouse emulation Report */
+#define RMI_WRITE_REPORT_ID 0x09 /* Output Report */
+#define RMI_READ_ADDR_REPORT_ID 0x0a /* Output Report */
+#define RMI_READ_DATA_REPORT_ID 0x0b /* Input Report */
+#define RMI_ATTN_REPORT_ID 0x0c /* Input Report */
+#define RMI_SET_RMI_MODE_REPORT_ID 0x0f /* Feature Report */
+
+/* flags */
+#define RMI_READ_REQUEST_PENDING BIT(0)
+#define RMI_READ_DATA_PENDING BIT(1)
+#define RMI_STARTED BIT(2)
+
+enum rmi_mode_type {
+ RMI_MODE_OFF = 0,
+ RMI_MODE_ATTN_REPORTS = 1,
+ RMI_MODE_NO_PACKED_ATTN_REPORTS = 2,
+};
+
+struct rmi_function {
+ unsigned page; /* page of the function */
+ u16 query_base_addr; /* base address for queries */
+ u16 command_base_addr; /* base address for commands */
+ u16 control_base_addr; /* base address for controls */
+ u16 data_base_addr; /* base address for datas */
+ unsigned int interrupt_base; /* cross-function interrupt number
+ * (uniq in the device)*/
+ unsigned int interrupt_count; /* number of interrupts */
+ unsigned int report_size; /* size of a report */
+ unsigned long irq_mask; /* mask of the interrupts
+ * (to be applied against ATTN IRQ) */
+};
+
+/**
+ * struct rmi_data - stores information for hid communication
+ *
+ * @page_mutex: Locks current page to avoid changing pages in unexpected ways.
+ * @page: Keeps track of the current virtual page
+ *
+ * @wait: Used for waiting for read data
+ *
+ * @writeReport: output buffer when writing RMI registers
+ * @readReport: input buffer when reading RMI registers
+ *
+ * @input_report_size: size of an input report (advertised by HID)
+ * @output_report_size: size of an output report (advertised by HID)
+ *
+ * @flags: flags for the current device (started, reading, etc...)
+ *
+ * @f11: placeholder of internal RMI function F11 description
+ * @f30: placeholder of internal RMI function F30 description
+ *
+ * @max_fingers: maximum finger count reported by the device
+ * @max_x: maximum x value reported by the device
+ * @max_y: maximum y value reported by the device
+ *
+ * @gpio_led_count: count of GPIOs + LEDs reported by F30
+ * @button_count: actual physical buttons count
+ * @button_mask: button mask used to decode GPIO ATTN reports
+ * @button_state_mask: pull state of the buttons
+ *
+ * @input: pointer to the kernel input device
+ *
+ * @reset_work: worker which will be called in case of a mouse report
+ * @hdev: pointer to the struct hid_device
+ */
+struct rmi_data {
+ struct mutex page_mutex;
+ int page;
+
+ wait_queue_head_t wait;
+
+ u8 *writeReport;
+ u8 *readReport;
+
+ int input_report_size;
+ int output_report_size;
+
+ unsigned long flags;
+
+ struct rmi_function f11;
+ struct rmi_function f30;
+
+ unsigned int max_fingers;
+ unsigned int max_x;
+ unsigned int max_y;
+ unsigned int x_size_mm;
+ unsigned int y_size_mm;
+
+ unsigned int gpio_led_count;
+ unsigned int button_count;
+ unsigned long button_mask;
+ unsigned long button_state_mask;
+
+ struct input_dev *input;
+
+ struct work_struct reset_work;
+ struct hid_device *hdev;
+};
+
+#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
+
+static int rmi_write_report(struct hid_device *hdev, u8 *report, int len);
+
+/**
+ * rmi_set_page - Set RMI page
+ * @hdev: The pointer to the hid_device struct
+ * @page: The new page address.
+ *
+ * RMI devices have 16-bit addressing, but some of the physical
+ * implementations (like SMBus) only have 8-bit addressing. So RMI implements
+ * a page address at 0xff of every page so we can reliable page addresses
+ * every 256 registers.
+ *
+ * The page_mutex lock must be held when this function is entered.
+ *
+ * Returns zero on success, non-zero on failure.
+ */
+static int rmi_set_page(struct hid_device *hdev, u8 page)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int retval;
+
+ data->writeReport[0] = RMI_WRITE_REPORT_ID;
+ data->writeReport[1] = 1;
+ data->writeReport[2] = 0xFF;
+ data->writeReport[4] = page;
+
+ retval = rmi_write_report(hdev, data->writeReport,
+ data->output_report_size);
+ if (retval != data->output_report_size) {
+ dev_err(&hdev->dev,
+ "%s: set page failed: %d.", __func__, retval);
+ return retval;
+ }
+
+ data->page = page;
+ return 0;
+}
+
+static int rmi_set_mode(struct hid_device *hdev, u8 mode)
+{
+ int ret;
+ u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode};
+
+ ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, txbuf,
+ sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "unable to set rmi mode to %d (%d)\n", mode,
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rmi_write_report(struct hid_device *hdev, u8 *report, int len)
+{
+ int ret;
+
+ ret = hid_hw_output_report(hdev, (void *)report, len);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "failed to write hid report (%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int rmi_read_block(struct hid_device *hdev, u16 addr, void *buf,
+ const int len)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ int ret;
+ int bytes_read;
+ int bytes_needed;
+ int retries;
+ int read_input_count;
+
+ mutex_lock(&data->page_mutex);
+
+ if (RMI_PAGE(addr) != data->page) {
+ ret = rmi_set_page(hdev, RMI_PAGE(addr));
+ if (ret < 0)
+ goto exit;
+ }
+
+ for (retries = 5; retries > 0; retries--) {
+ data->writeReport[0] = RMI_READ_ADDR_REPORT_ID;
+ data->writeReport[1] = 0; /* old 1 byte read count */
+ data->writeReport[2] = addr & 0xFF;
+ data->writeReport[3] = (addr >> 8) & 0xFF;
+ data->writeReport[4] = len & 0xFF;
+ data->writeReport[5] = (len >> 8) & 0xFF;
+
+ set_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+
+ ret = rmi_write_report(hdev, data->writeReport,
+ data->output_report_size);
+ if (ret != data->output_report_size) {
+ clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+ dev_err(&hdev->dev,
+ "failed to write request output report (%d)\n",
+ ret);
+ goto exit;
+ }
+
+ bytes_read = 0;
+ bytes_needed = len;
+ while (bytes_read < len) {
+ if (!wait_event_timeout(data->wait,
+ test_bit(RMI_READ_DATA_PENDING, &data->flags),
+ msecs_to_jiffies(1000))) {
+ hid_warn(hdev, "%s: timeout elapsed\n",
+ __func__);
+ ret = -EAGAIN;
+ break;
+ }
+
+ read_input_count = data->readReport[1];
+ memcpy(buf + bytes_read, &data->readReport[2],
+ read_input_count < bytes_needed ?
+ read_input_count : bytes_needed);
+
+ bytes_read += read_input_count;
+ bytes_needed -= read_input_count;
+ clear_bit(RMI_READ_DATA_PENDING, &data->flags);
+ }
+
+ if (ret >= 0) {
+ ret = 0;
+ break;
+ }
+ }
+
+exit:
+ clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+ mutex_unlock(&data->page_mutex);
+ return ret;
+}
+
+static inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
+{
+ return rmi_read_block(hdev, addr, buf, 1);
+}
+
+static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
+ u8 finger_state, u8 *touch_data)
+{
+ int x, y, wx, wy;
+ int wide, major, minor;
+ int z;
+
+ input_mt_slot(hdata->input, slot);
+ input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER,
+ finger_state == 0x01);
+ if (finger_state == 0x01) {
+ x = (touch_data[0] << 4) | (touch_data[2] & 0x07);
+ y = (touch_data[1] << 4) | (touch_data[2] >> 4);
+ wx = touch_data[3] & 0x07;
+ wy = touch_data[3] >> 4;
+ wide = (wx > wy);
+ major = max(wx, wy);
+ minor = min(wx, wy);
+ z = touch_data[4];
+
+ /* y is inverted */
+ y = hdata->max_y - y;
+
+ input_event(hdata->input, EV_ABS, ABS_MT_POSITION_X, x);
+ input_event(hdata->input, EV_ABS, ABS_MT_POSITION_Y, y);
+ input_event(hdata->input, EV_ABS, ABS_MT_ORIENTATION, wide);
+ input_event(hdata->input, EV_ABS, ABS_MT_PRESSURE, z);
+ input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+ input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
+ }
+}
+
+static void rmi_reset_work(struct work_struct *work)
+{
+ struct rmi_data *hdata = container_of(work, struct rmi_data,
+ reset_work);
+
+ /* switch the device to RMI if we receive a generic mouse report */
+ rmi_set_mode(hdata->hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+static inline int rmi_schedule_reset(struct hid_device *hdev)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ return schedule_work(&hdata->reset_work);
+}
+
+static int rmi_f11_input_event(struct hid_device *hdev, u8 irq, u8 *data,
+ int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ int offset;
+ int i;
+
+ if (size < hdata->f11.report_size)
+ return 0;
+
+ if (!(irq & hdata->f11.irq_mask))
+ return 0;
+
+ offset = (hdata->max_fingers >> 2) + 1;
+ for (i = 0; i < hdata->max_fingers; i++) {
+ int fs_byte_position = i >> 2;
+ int fs_bit_position = (i & 0x3) << 1;
+ int finger_state = (data[fs_byte_position] >> fs_bit_position) &
+ 0x03;
+
+ rmi_f11_process_touch(hdata, i, finger_state,
+ &data[offset + 5 * i]);
+ }
+ input_mt_sync_frame(hdata->input);
+ input_sync(hdata->input);
+ return hdata->f11.report_size;
+}
+
+static int rmi_f30_input_event(struct hid_device *hdev, u8 irq, u8 *data,
+ int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ int i;
+ int button = 0;
+ bool value;
+
+ if (!(irq & hdata->f30.irq_mask))
+ return 0;
+
+ for (i = 0; i < hdata->gpio_led_count; i++) {
+ if (test_bit(i, &hdata->button_mask)) {
+ value = (data[i / 8] >> (i & 0x07)) & BIT(0);
+ if (test_bit(i, &hdata->button_state_mask))
+ value = !value;
+ input_event(hdata->input, EV_KEY, BTN_LEFT + button++,
+ value);
+ }
+ }
+ return hdata->f30.report_size;
+}
+
+static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+ unsigned long irq_mask = 0;
+ unsigned index = 2;
+
+ if (!(test_bit(RMI_STARTED, &hdata->flags)))
+ return 0;
+
+ irq_mask |= hdata->f11.irq_mask;
+ irq_mask |= hdata->f30.irq_mask;
+
+ if (data[1] & ~irq_mask)
+ hid_warn(hdev, "unknown intr source:%02lx %s:%d\n",
+ data[1] & ~irq_mask, __FILE__, __LINE__);
+
+ if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
+ index += rmi_f11_input_event(hdev, data[1], &data[index],
+ size - index);
+ index += rmi_f30_input_event(hdev, data[1], &data[index],
+ size - index);
+ } else {
+ index += rmi_f30_input_event(hdev, data[1], &data[index],
+ size - index);
+ index += rmi_f11_input_event(hdev, data[1], &data[index],
+ size - index);
+ }
+
+ return 1;
+}
+
+static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+ if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
+ hid_err(hdev, "no read request pending\n");
+ return 0;
+ }
+
+ memcpy(hdata->readReport, data, size < hdata->input_report_size ?
+ size : hdata->input_report_size);
+ set_bit(RMI_READ_DATA_PENDING, &hdata->flags);
+ wake_up(&hdata->wait);
+
+ return 1;
+}
+
+static int rmi_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ switch (data[0]) {
+ case RMI_READ_DATA_REPORT_ID:
+ return rmi_read_data_event(hdev, data, size);
+ case RMI_ATTN_REPORT_ID:
+ return rmi_input_event(hdev, data, size);
+ case RMI_MOUSE_REPORT_ID:
+ rmi_schedule_reset(hdev);
+ break;
+ }
+
+ return 0;
+}
+
+static int rmi_post_reset(struct hid_device *hdev)
+{
+ return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+static int rmi_post_resume(struct hid_device *hdev)
+{
+ return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+}
+
+#define RMI4_MAX_PAGE 0xff
+#define RMI4_PAGE_SIZE 0x0100
+
+#define PDT_START_SCAN_LOCATION 0x00e9
+#define PDT_END_SCAN_LOCATION 0x0005
+#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
+
+struct pdt_entry {
+ u8 query_base_addr:8;
+ u8 command_base_addr:8;
+ u8 control_base_addr:8;
+ u8 data_base_addr:8;
+ u8 interrupt_source_count:3;
+ u8 bits3and4:2;
+ u8 function_version:2;
+ u8 bit7:1;
+ u8 function_number:8;
+} __attribute__((__packed__));
+
+static inline unsigned long rmi_gen_mask(unsigned irq_base, unsigned irq_count)
+{
+ return GENMASK(irq_count + irq_base - 1, irq_base);
+}
+
+static void rmi_register_function(struct rmi_data *data,
+ struct pdt_entry *pdt_entry, int page, unsigned interrupt_count)
+{
+ struct rmi_function *f = NULL;
+ u16 page_base = page << 8;
+
+ switch (pdt_entry->function_number) {
+ case 0x11:
+ f = &data->f11;
+ break;
+ case 0x30:
+ f = &data->f30;
+ break;
+ }
+
+ if (f) {
+ f->page = page;
+ f->query_base_addr = page_base | pdt_entry->query_base_addr;
+ f->command_base_addr = page_base | pdt_entry->command_base_addr;
+ f->control_base_addr = page_base | pdt_entry->control_base_addr;
+ f->data_base_addr = page_base | pdt_entry->data_base_addr;
+ f->interrupt_base = interrupt_count;
+ f->interrupt_count = pdt_entry->interrupt_source_count;
+ f->irq_mask = rmi_gen_mask(f->interrupt_base,
+ f->interrupt_count);
+ }
+}
+
+static int rmi_scan_pdt(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ struct pdt_entry entry;
+ int page;
+ bool page_has_function;
+ int i;
+ int retval;
+ int interrupt = 0;
+ u16 page_start, pdt_start , pdt_end;
+
+ hid_info(hdev, "Scanning PDT...\n");
+
+ for (page = 0; (page <= RMI4_MAX_PAGE); page++) {
+ page_start = RMI4_PAGE_SIZE * page;
+ pdt_start = page_start + PDT_START_SCAN_LOCATION;
+ pdt_end = page_start + PDT_END_SCAN_LOCATION;
+
+ page_has_function = false;
+ for (i = pdt_start; i >= pdt_end; i -= sizeof(entry)) {
+ retval = rmi_read_block(hdev, i, &entry, sizeof(entry));
+ if (retval) {
+ hid_err(hdev,
+ "Read of PDT entry at %#06x failed.\n",
+ i);
+ goto error_exit;
+ }
+
+ if (RMI4_END_OF_PDT(entry.function_number))
+ break;
+
+ page_has_function = true;
+
+ hid_info(hdev, "Found F%02X on page %#04x\n",
+ entry.function_number, page);
+
+ rmi_register_function(data, &entry, page, interrupt);
+ interrupt += entry.interrupt_source_count;
+ }
+
+ if (!page_has_function)
+ break;
+ }
+
+ hid_info(hdev, "%s: Done with PDT scan.\n", __func__);
+ retval = 0;
+
+error_exit:
+ return retval;
+}
+
+static int rmi_populate_f11(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 buf[20];
+ int ret;
+ bool has_query12;
+ bool has_physical_props;
+ unsigned x_size, y_size;
+
+ if (!data->f11.query_base_addr) {
+ hid_err(hdev, "No 2D sensor found, giving up.\n");
+ return -ENODEV;
+ }
+
+ /* query 0 contains some useful information */
+ ret = rmi_read(hdev, data->f11.query_base_addr, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 0: %d.\n", ret);
+ return ret;
+ }
+ has_query12 = !!(buf[0] & BIT(5));
+
+ /* query 1 to get the max number of fingers */
+ ret = rmi_read(hdev, data->f11.query_base_addr + 1, buf);
+ if (ret) {
+ hid_err(hdev, "can not get NumberOfFingers: %d.\n", ret);
+ return ret;
+ }
+ data->max_fingers = (buf[0] & 0x07) + 1;
+ if (data->max_fingers > 5)
+ data->max_fingers = 10;
+
+ data->f11.report_size = data->max_fingers * 5 +
+ DIV_ROUND_UP(data->max_fingers, 4);
+
+ if (!(buf[0] & BIT(4))) {
+ hid_err(hdev, "No absolute events, giving up.\n");
+ return -ENODEV;
+ }
+
+ /*
+ * query 12 to know if the physical properties are reported
+ * (query 12 is at offset 10 for HID devices)
+ */
+ if (has_query12) {
+ ret = rmi_read(hdev, data->f11.query_base_addr + 10, buf);
+ if (ret) {
+ hid_err(hdev, "can not get query 12: %d.\n", ret);
+ return ret;
+ }
+ has_physical_props = !!(buf[0] & BIT(5));
+
+ if (has_physical_props) {
+ ret = rmi_read_block(hdev,
+ data->f11.query_base_addr + 11, buf, 4);
+ if (ret) {
+ hid_err(hdev, "can not read query 15-18: %d.\n",
+ ret);
+ return ret;
+ }
+
+ x_size = buf[0] | (buf[1] << 8);
+ y_size = buf[2] | (buf[3] << 8);
+
+ data->x_size_mm = DIV_ROUND_CLOSEST(x_size, 10);
+ data->y_size_mm = DIV_ROUND_CLOSEST(y_size, 10);
+
+ hid_info(hdev, "%s: size in mm: %d x %d\n",
+ __func__, data->x_size_mm, data->y_size_mm);
+ }
+ }
+
+ /* retrieve the ctrl registers */
+ ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 20);
+ if (ret) {
+ hid_err(hdev, "can not read ctrl block of size 20: %d.\n", ret);
+ return ret;
+ }
+
+ data->max_x = buf[6] | (buf[7] << 8);
+ data->max_y = buf[8] | (buf[9] << 8);
+
+ return 0;
+}
+
+static int rmi_populate_f30(struct hid_device *hdev)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ u8 buf[20];
+ int ret;
+ bool has_gpio, has_led;
+ unsigned bytes_per_ctrl;
+ u8 ctrl2_addr;
+ int ctrl2_3_length;
+ int i;
+
+ /* function F30 is for physical buttons */
+ if (!data->f30.query_base_addr) {
+ hid_err(hdev, "No GPIO/LEDs found, giving up.\n");
+ return -ENODEV;
+ }
+
+ ret = rmi_read_block(hdev, data->f30.query_base_addr, buf, 2);
+ if (ret) {
+ hid_err(hdev, "can not get F30 query registers: %d.\n", ret);
+ return ret;
+ }
+
+ has_gpio = !!(buf[0] & BIT(3));
+ has_led = !!(buf[0] & BIT(2));
+ data->gpio_led_count = buf[1] & 0x1f;
+
+ /* retrieve ctrl 2 & 3 registers */
+ bytes_per_ctrl = (data->gpio_led_count + 7) / 8;
+ /* Ctrl0 is present only if both has_gpio and has_led are set*/
+ ctrl2_addr = (has_gpio && has_led) ? bytes_per_ctrl : 0;
+ /* Ctrl1 is always be present */
+ ctrl2_addr += bytes_per_ctrl;
+ ctrl2_3_length = 2 * bytes_per_ctrl;
+
+ data->f30.report_size = bytes_per_ctrl;
+
+ ret = rmi_read_block(hdev, data->f30.control_base_addr + ctrl2_addr,
+ buf, ctrl2_3_length);
+ if (ret) {
+ hid_err(hdev, "can not read ctrl 2&3 block of size %d: %d.\n",
+ ctrl2_3_length, ret);
+ return ret;
+ }
+
+ for (i = 0; i < data->gpio_led_count; i++) {
+ int byte_position = i >> 3;
+ int bit_position = i & 0x07;
+ u8 dir_byte = buf[byte_position];
+ u8 data_byte = buf[byte_position + bytes_per_ctrl];
+ bool dir = (dir_byte >> bit_position) & BIT(0);
+ bool dat = (data_byte >> bit_position) & BIT(0);
+
+ if (dir == 0) {
+ /* input mode */
+ if (dat) {
+ /* actual buttons have pull up resistor */
+ data->button_count++;
+ set_bit(i, &data->button_mask);
+ set_bit(i, &data->button_state_mask);
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+static int rmi_populate(struct hid_device *hdev)
+{
+ int ret;
+
+ ret = rmi_scan_pdt(hdev);
+ if (ret) {
+ hid_err(hdev, "PDT scan failed with code %d.\n", ret);
+ return ret;
+ }
+
+ ret = rmi_populate_f11(hdev);
+ if (ret) {
+ hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
+ return ret;
+ }
+
+ ret = rmi_populate_f30(hdev);
+ if (ret)
+ hid_warn(hdev, "Error while initializing F30 (%d).\n", ret);
+
+ return 0;
+}
+
+static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+ struct rmi_data *data = hid_get_drvdata(hdev);
+ struct input_dev *input = hi->input;
+ int ret;
+ int res_x, res_y, i;
+
+ data->input = input;
+
+ hid_dbg(hdev, "Opening low level driver\n");
+ ret = hid_hw_open(hdev);
+ if (ret)
+ return;
+
+ /* Allow incoming hid reports */
+ hid_device_io_start(hdev);
+
+ ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "failed to set rmi mode\n");
+ goto exit;
+ }
+
+ ret = rmi_set_page(hdev, 0);
+ if (ret < 0) {
+ dev_err(&hdev->dev, "failed to set page select to 0.\n");
+ goto exit;
+ }
+
+ ret = rmi_populate(hdev);
+ if (ret)
+ goto exit;
+
+ __set_bit(EV_ABS, input->evbit);
+ input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
+
+ if (data->x_size_mm && data->x_size_mm) {
+ res_x = (data->max_x - 1) / data->x_size_mm;
+ res_y = (data->max_y - 1) / data->x_size_mm;
+
+ input_abs_set_res(input, ABS_MT_POSITION_X, res_x);
+ input_abs_set_res(input, ABS_MT_POSITION_Y, res_y);
+ }
+
+ input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xff, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0x0f, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 0x0f, 0, 0);
+
+ input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
+
+ if (data->button_count) {
+ __set_bit(EV_KEY, input->evbit);
+ for (i = 0; i < data->button_count; i++)
+ __set_bit(BTN_LEFT + i, input->keybit);
+
+ if (data->button_count == 1)
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+ }
+
+ set_bit(RMI_STARTED, &data->flags);
+
+exit:
+ hid_device_io_stop(hdev);
+ hid_hw_close(hdev);
+}
+
+static int rmi_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field,
+ struct hid_usage *usage, unsigned long **bit, int *max)
+{
+ /* we want to make HID ignore the advertised HID collection */
+ return -1;
+}
+
+static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct rmi_data *data = NULL;
+ int ret;
+ size_t alloc_size;
+
+ data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_WORK(&data->reset_work, rmi_reset_work);
+ data->hdev = hdev;
+
+ hid_set_drvdata(hdev, data);
+
+ hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ data->input_report_size = (hdev->report_enum[HID_INPUT_REPORT]
+ .report_id_hash[RMI_ATTN_REPORT_ID]->size >> 3)
+ + 1 /* report id */;
+ data->output_report_size = (hdev->report_enum[HID_OUTPUT_REPORT]
+ .report_id_hash[RMI_WRITE_REPORT_ID]->size >> 3)
+ + 1 /* report id */;
+
+ alloc_size = data->output_report_size + data->input_report_size;
+
+ data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL);
+ if (!data->writeReport) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ data->readReport = data->writeReport + data->output_report_size;
+
+ init_waitqueue_head(&data->wait);
+
+ mutex_init(&data->page_mutex);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ if (!test_bit(RMI_STARTED, &data->flags)) {
+ hid_hw_stop(hdev);
+ return -EIO;
+ }
+
+ hid_hw_stop(hdev);
+ return 0;
+}
+
+static void rmi_remove(struct hid_device *hdev)
+{
+ struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+ clear_bit(RMI_STARTED, &hdata->flags);
+
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id rmi_id[] = {
+ { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, rmi_id);
+
+static struct hid_driver rmi_driver = {
+ .name = "hid-rmi",
+ .id_table = rmi_id,
+ .probe = rmi_probe,
+ .remove = rmi_remove,
+ .raw_event = rmi_raw_event,
+ .input_mapping = rmi_input_mapping,
+ .input_configured = rmi_input_configured,
+#ifdef CONFIG_PM
+ .resume = rmi_post_resume,
+ .reset_resume = rmi_post_reset,
+#endif
+};
+
+module_hid_driver(rmi_driver);
+
+MODULE_AUTHOR("Andrew Duggan <aduggan@synaptics.com>");
+MODULE_DESCRIPTION("RMI HID driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 720e3a1..54f855b 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -570,6 +570,8 @@ struct hid_descriptor {
.bus = BUS_USB, .vendor = (ven), .product = (prod)
#define HID_BLUETOOTH_DEVICE(ven, prod) \
.bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
+#define HID_I2C_DEVICE(ven, prod) \
+ .bus = BUS_I2C, .vendor = (ven), .product = (prod)
#define HID_REPORT_ID(rep) \
.report_type = (rep)
--
1.9.0
^ permalink raw reply related
* Re: [PATCH] HID: rmi: introduce RMI driver for Synaptics touchpads
From: Benjamin Tissoires @ 2014-04-07 17:43 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Andrew Duggan, Christopher Heiny, Jiri Kosina, linux-input,
linux-kernel@vger.kernel.org
In-Reply-To: <1396892373-3330-1-git-send-email-benjamin.tissoires@redhat.com>
On Mon, Apr 7, 2014 at 1:39 PM, Benjamin Tissoires
<benjamin.tissoires@redhat.com> wrote:
> This driver add support for RMI4 over USB or I2C.
> The current state is that it uses its own RMI4 implementation, but once
> RMI4 is merged upstream, the driver will be a transport driver for the
> RMI4 library.
>
> Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
Of course, I did not triple checked things here, and the version I
sent was missing the comments:
Hi,
well, part of this driver should be considered as temporary. Most of the
RMI4 processing and input handling will be deleted at some point.
I based my work on Andrew's regarding its port of RMI4 over HID (see
https://github.com/mightybigcar/synaptics-rmi4/tree/rmihid )
This repo presents how the driver may looks like at the end:
https://github.com/mightybigcar/synaptics-rmi4/blob/rmihid/drivers/input/rmi4/rmi_hid.c
Andrew validated the changes, so I kept his S-o-b and the module author.
Without this temporary solution, the workaround we gave to users
is to disable i2c-hid, which leads to disabling the touchscreen on the
XPS 11 and 12 (Haswell generation).
Cheers,
Benjamin
Related bugs:
https://bugzilla.redhat.com/show_bug.cgi?id=1048314
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1218973
> drivers/hid/Kconfig | 8 +
> drivers/hid/Makefile | 1 +
> drivers/hid/hid-core.c | 2 +
> drivers/hid/hid-rmi.c | 889 +++++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/hid.h | 2 +
> 5 files changed, 902 insertions(+)
> create mode 100644 drivers/hid/hid-rmi.c
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 7af9d0b..762f15d 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -657,6 +657,14 @@ config HID_SUNPLUS
> ---help---
> Support for Sunplus wireless desktop.
>
> +config HID_RMI
> + tristate "Synaptics RMI4 device support"
> + depends on HID
> + ---help---
> + Support for Synaptics RMI4 touchpads.
> + Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
> + and want support for its special functionalities.
> +
> config HID_GREENASIA
> tristate "GreenAsia (Product ID 0x12) game controller support"
> depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index fc712dd..a6fa6ba 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -97,6 +97,7 @@ obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
> hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
> hid-roccat-koneplus.o hid-roccat-konepure.o hid-roccat-kovaplus.o \
> hid-roccat-lua.o hid-roccat-pyra.o hid-roccat-ryos.o hid-roccat-savu.o
> +obj-$(CONFIG_HID_RMI) += hid-rmi.o
> obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
> obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
> obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index abaa0a5..c97ece8 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -1879,6 +1879,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
> { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
> { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
> { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> + { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
> { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) },
> { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) },
> diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
> new file mode 100644
> index 0000000..699d631
> --- /dev/null
> +++ b/drivers/hid/hid-rmi.c
> @@ -0,0 +1,889 @@
> +/*
> + * Copyright (c) 2013 Andrew Duggan <aduggan@synaptics.com>
> + * Copyright (c) 2013 Synaptics Incorporated
> + * Copyright (c) 2014 Benjamin Tissoires <benjamin.tissoires@gmail.com>
> + * Copyright (c) 2014 Red Hat, Inc
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the Free
> + * Software Foundation; either version 2 of the License, or (at your option)
> + * any later version.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/hid.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/module.h>
> +#include <linux/pm.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +#include <linux/sched.h>
> +#include "hid-ids.h"
> +
> +#define RMI_MOUSE_REPORT_ID 0x01 /* Mouse emulation Report */
> +#define RMI_WRITE_REPORT_ID 0x09 /* Output Report */
> +#define RMI_READ_ADDR_REPORT_ID 0x0a /* Output Report */
> +#define RMI_READ_DATA_REPORT_ID 0x0b /* Input Report */
> +#define RMI_ATTN_REPORT_ID 0x0c /* Input Report */
> +#define RMI_SET_RMI_MODE_REPORT_ID 0x0f /* Feature Report */
> +
> +/* flags */
> +#define RMI_READ_REQUEST_PENDING BIT(0)
> +#define RMI_READ_DATA_PENDING BIT(1)
> +#define RMI_STARTED BIT(2)
> +
> +enum rmi_mode_type {
> + RMI_MODE_OFF = 0,
> + RMI_MODE_ATTN_REPORTS = 1,
> + RMI_MODE_NO_PACKED_ATTN_REPORTS = 2,
> +};
> +
> +struct rmi_function {
> + unsigned page; /* page of the function */
> + u16 query_base_addr; /* base address for queries */
> + u16 command_base_addr; /* base address for commands */
> + u16 control_base_addr; /* base address for controls */
> + u16 data_base_addr; /* base address for datas */
> + unsigned int interrupt_base; /* cross-function interrupt number
> + * (uniq in the device)*/
> + unsigned int interrupt_count; /* number of interrupts */
> + unsigned int report_size; /* size of a report */
> + unsigned long irq_mask; /* mask of the interrupts
> + * (to be applied against ATTN IRQ) */
> +};
> +
> +/**
> + * struct rmi_data - stores information for hid communication
> + *
> + * @page_mutex: Locks current page to avoid changing pages in unexpected ways.
> + * @page: Keeps track of the current virtual page
> + *
> + * @wait: Used for waiting for read data
> + *
> + * @writeReport: output buffer when writing RMI registers
> + * @readReport: input buffer when reading RMI registers
> + *
> + * @input_report_size: size of an input report (advertised by HID)
> + * @output_report_size: size of an output report (advertised by HID)
> + *
> + * @flags: flags for the current device (started, reading, etc...)
> + *
> + * @f11: placeholder of internal RMI function F11 description
> + * @f30: placeholder of internal RMI function F30 description
> + *
> + * @max_fingers: maximum finger count reported by the device
> + * @max_x: maximum x value reported by the device
> + * @max_y: maximum y value reported by the device
> + *
> + * @gpio_led_count: count of GPIOs + LEDs reported by F30
> + * @button_count: actual physical buttons count
> + * @button_mask: button mask used to decode GPIO ATTN reports
> + * @button_state_mask: pull state of the buttons
> + *
> + * @input: pointer to the kernel input device
> + *
> + * @reset_work: worker which will be called in case of a mouse report
> + * @hdev: pointer to the struct hid_device
> + */
> +struct rmi_data {
> + struct mutex page_mutex;
> + int page;
> +
> + wait_queue_head_t wait;
> +
> + u8 *writeReport;
> + u8 *readReport;
> +
> + int input_report_size;
> + int output_report_size;
> +
> + unsigned long flags;
> +
> + struct rmi_function f11;
> + struct rmi_function f30;
> +
> + unsigned int max_fingers;
> + unsigned int max_x;
> + unsigned int max_y;
> + unsigned int x_size_mm;
> + unsigned int y_size_mm;
> +
> + unsigned int gpio_led_count;
> + unsigned int button_count;
> + unsigned long button_mask;
> + unsigned long button_state_mask;
> +
> + struct input_dev *input;
> +
> + struct work_struct reset_work;
> + struct hid_device *hdev;
> +};
> +
> +#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
> +
> +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len);
> +
> +/**
> + * rmi_set_page - Set RMI page
> + * @hdev: The pointer to the hid_device struct
> + * @page: The new page address.
> + *
> + * RMI devices have 16-bit addressing, but some of the physical
> + * implementations (like SMBus) only have 8-bit addressing. So RMI implements
> + * a page address at 0xff of every page so we can reliable page addresses
> + * every 256 registers.
> + *
> + * The page_mutex lock must be held when this function is entered.
> + *
> + * Returns zero on success, non-zero on failure.
> + */
> +static int rmi_set_page(struct hid_device *hdev, u8 page)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + int retval;
> +
> + data->writeReport[0] = RMI_WRITE_REPORT_ID;
> + data->writeReport[1] = 1;
> + data->writeReport[2] = 0xFF;
> + data->writeReport[4] = page;
> +
> + retval = rmi_write_report(hdev, data->writeReport,
> + data->output_report_size);
> + if (retval != data->output_report_size) {
> + dev_err(&hdev->dev,
> + "%s: set page failed: %d.", __func__, retval);
> + return retval;
> + }
> +
> + data->page = page;
> + return 0;
> +}
> +
> +static int rmi_set_mode(struct hid_device *hdev, u8 mode)
> +{
> + int ret;
> + u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode};
> +
> + ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, txbuf,
> + sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "unable to set rmi mode to %d (%d)\n", mode,
> + ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len)
> +{
> + int ret;
> +
> + ret = hid_hw_output_report(hdev, (void *)report, len);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "failed to write hid report (%d)\n", ret);
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int rmi_read_block(struct hid_device *hdev, u16 addr, void *buf,
> + const int len)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + int ret;
> + int bytes_read;
> + int bytes_needed;
> + int retries;
> + int read_input_count;
> +
> + mutex_lock(&data->page_mutex);
> +
> + if (RMI_PAGE(addr) != data->page) {
> + ret = rmi_set_page(hdev, RMI_PAGE(addr));
> + if (ret < 0)
> + goto exit;
> + }
> +
> + for (retries = 5; retries > 0; retries--) {
> + data->writeReport[0] = RMI_READ_ADDR_REPORT_ID;
> + data->writeReport[1] = 0; /* old 1 byte read count */
> + data->writeReport[2] = addr & 0xFF;
> + data->writeReport[3] = (addr >> 8) & 0xFF;
> + data->writeReport[4] = len & 0xFF;
> + data->writeReport[5] = (len >> 8) & 0xFF;
> +
> + set_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> +
> + ret = rmi_write_report(hdev, data->writeReport,
> + data->output_report_size);
> + if (ret != data->output_report_size) {
> + clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> + dev_err(&hdev->dev,
> + "failed to write request output report (%d)\n",
> + ret);
> + goto exit;
> + }
> +
> + bytes_read = 0;
> + bytes_needed = len;
> + while (bytes_read < len) {
> + if (!wait_event_timeout(data->wait,
> + test_bit(RMI_READ_DATA_PENDING, &data->flags),
> + msecs_to_jiffies(1000))) {
> + hid_warn(hdev, "%s: timeout elapsed\n",
> + __func__);
> + ret = -EAGAIN;
> + break;
> + }
> +
> + read_input_count = data->readReport[1];
> + memcpy(buf + bytes_read, &data->readReport[2],
> + read_input_count < bytes_needed ?
> + read_input_count : bytes_needed);
> +
> + bytes_read += read_input_count;
> + bytes_needed -= read_input_count;
> + clear_bit(RMI_READ_DATA_PENDING, &data->flags);
> + }
> +
> + if (ret >= 0) {
> + ret = 0;
> + break;
> + }
> + }
> +
> +exit:
> + clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> + mutex_unlock(&data->page_mutex);
> + return ret;
> +}
> +
> +static inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
> +{
> + return rmi_read_block(hdev, addr, buf, 1);
> +}
> +
> +static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
> + u8 finger_state, u8 *touch_data)
> +{
> + int x, y, wx, wy;
> + int wide, major, minor;
> + int z;
> +
> + input_mt_slot(hdata->input, slot);
> + input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER,
> + finger_state == 0x01);
> + if (finger_state == 0x01) {
> + x = (touch_data[0] << 4) | (touch_data[2] & 0x07);
> + y = (touch_data[1] << 4) | (touch_data[2] >> 4);
> + wx = touch_data[3] & 0x07;
> + wy = touch_data[3] >> 4;
> + wide = (wx > wy);
> + major = max(wx, wy);
> + minor = min(wx, wy);
> + z = touch_data[4];
> +
> + /* y is inverted */
> + y = hdata->max_y - y;
> +
> + input_event(hdata->input, EV_ABS, ABS_MT_POSITION_X, x);
> + input_event(hdata->input, EV_ABS, ABS_MT_POSITION_Y, y);
> + input_event(hdata->input, EV_ABS, ABS_MT_ORIENTATION, wide);
> + input_event(hdata->input, EV_ABS, ABS_MT_PRESSURE, z);
> + input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
> + input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
> + }
> +}
> +
> +static void rmi_reset_work(struct work_struct *work)
> +{
> + struct rmi_data *hdata = container_of(work, struct rmi_data,
> + reset_work);
> +
> + /* switch the device to RMI if we receive a generic mouse report */
> + rmi_set_mode(hdata->hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +static inline int rmi_schedule_reset(struct hid_device *hdev)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + return schedule_work(&hdata->reset_work);
> +}
> +
> +static int rmi_f11_input_event(struct hid_device *hdev, u8 irq, u8 *data,
> + int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + int offset;
> + int i;
> +
> + if (size < hdata->f11.report_size)
> + return 0;
> +
> + if (!(irq & hdata->f11.irq_mask))
> + return 0;
> +
> + offset = (hdata->max_fingers >> 2) + 1;
> + for (i = 0; i < hdata->max_fingers; i++) {
> + int fs_byte_position = i >> 2;
> + int fs_bit_position = (i & 0x3) << 1;
> + int finger_state = (data[fs_byte_position] >> fs_bit_position) &
> + 0x03;
> +
> + rmi_f11_process_touch(hdata, i, finger_state,
> + &data[offset + 5 * i]);
> + }
> + input_mt_sync_frame(hdata->input);
> + input_sync(hdata->input);
> + return hdata->f11.report_size;
> +}
> +
> +static int rmi_f30_input_event(struct hid_device *hdev, u8 irq, u8 *data,
> + int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + int i;
> + int button = 0;
> + bool value;
> +
> + if (!(irq & hdata->f30.irq_mask))
> + return 0;
> +
> + for (i = 0; i < hdata->gpio_led_count; i++) {
> + if (test_bit(i, &hdata->button_mask)) {
> + value = (data[i / 8] >> (i & 0x07)) & BIT(0);
> + if (test_bit(i, &hdata->button_state_mask))
> + value = !value;
> + input_event(hdata->input, EV_KEY, BTN_LEFT + button++,
> + value);
> + }
> + }
> + return hdata->f30.report_size;
> +}
> +
> +static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> + unsigned long irq_mask = 0;
> + unsigned index = 2;
> +
> + if (!(test_bit(RMI_STARTED, &hdata->flags)))
> + return 0;
> +
> + irq_mask |= hdata->f11.irq_mask;
> + irq_mask |= hdata->f30.irq_mask;
> +
> + if (data[1] & ~irq_mask)
> + hid_warn(hdev, "unknown intr source:%02lx %s:%d\n",
> + data[1] & ~irq_mask, __FILE__, __LINE__);
> +
> + if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
> + index += rmi_f11_input_event(hdev, data[1], &data[index],
> + size - index);
> + index += rmi_f30_input_event(hdev, data[1], &data[index],
> + size - index);
> + } else {
> + index += rmi_f30_input_event(hdev, data[1], &data[index],
> + size - index);
> + index += rmi_f11_input_event(hdev, data[1], &data[index],
> + size - index);
> + }
> +
> + return 1;
> +}
> +
> +static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> +
> + if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
> + hid_err(hdev, "no read request pending\n");
> + return 0;
> + }
> +
> + memcpy(hdata->readReport, data, size < hdata->input_report_size ?
> + size : hdata->input_report_size);
> + set_bit(RMI_READ_DATA_PENDING, &hdata->flags);
> + wake_up(&hdata->wait);
> +
> + return 1;
> +}
> +
> +static int rmi_raw_event(struct hid_device *hdev,
> + struct hid_report *report, u8 *data, int size)
> +{
> + switch (data[0]) {
> + case RMI_READ_DATA_REPORT_ID:
> + return rmi_read_data_event(hdev, data, size);
> + case RMI_ATTN_REPORT_ID:
> + return rmi_input_event(hdev, data, size);
> + case RMI_MOUSE_REPORT_ID:
> + rmi_schedule_reset(hdev);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static int rmi_post_reset(struct hid_device *hdev)
> +{
> + return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +static int rmi_post_resume(struct hid_device *hdev)
> +{
> + return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +#define RMI4_MAX_PAGE 0xff
> +#define RMI4_PAGE_SIZE 0x0100
> +
> +#define PDT_START_SCAN_LOCATION 0x00e9
> +#define PDT_END_SCAN_LOCATION 0x0005
> +#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
> +
> +struct pdt_entry {
> + u8 query_base_addr:8;
> + u8 command_base_addr:8;
> + u8 control_base_addr:8;
> + u8 data_base_addr:8;
> + u8 interrupt_source_count:3;
> + u8 bits3and4:2;
> + u8 function_version:2;
> + u8 bit7:1;
> + u8 function_number:8;
> +} __attribute__((__packed__));
> +
> +static inline unsigned long rmi_gen_mask(unsigned irq_base, unsigned irq_count)
> +{
> + return GENMASK(irq_count + irq_base - 1, irq_base);
> +}
> +
> +static void rmi_register_function(struct rmi_data *data,
> + struct pdt_entry *pdt_entry, int page, unsigned interrupt_count)
> +{
> + struct rmi_function *f = NULL;
> + u16 page_base = page << 8;
> +
> + switch (pdt_entry->function_number) {
> + case 0x11:
> + f = &data->f11;
> + break;
> + case 0x30:
> + f = &data->f30;
> + break;
> + }
> +
> + if (f) {
> + f->page = page;
> + f->query_base_addr = page_base | pdt_entry->query_base_addr;
> + f->command_base_addr = page_base | pdt_entry->command_base_addr;
> + f->control_base_addr = page_base | pdt_entry->control_base_addr;
> + f->data_base_addr = page_base | pdt_entry->data_base_addr;
> + f->interrupt_base = interrupt_count;
> + f->interrupt_count = pdt_entry->interrupt_source_count;
> + f->irq_mask = rmi_gen_mask(f->interrupt_base,
> + f->interrupt_count);
> + }
> +}
> +
> +static int rmi_scan_pdt(struct hid_device *hdev)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + struct pdt_entry entry;
> + int page;
> + bool page_has_function;
> + int i;
> + int retval;
> + int interrupt = 0;
> + u16 page_start, pdt_start , pdt_end;
> +
> + hid_info(hdev, "Scanning PDT...\n");
> +
> + for (page = 0; (page <= RMI4_MAX_PAGE); page++) {
> + page_start = RMI4_PAGE_SIZE * page;
> + pdt_start = page_start + PDT_START_SCAN_LOCATION;
> + pdt_end = page_start + PDT_END_SCAN_LOCATION;
> +
> + page_has_function = false;
> + for (i = pdt_start; i >= pdt_end; i -= sizeof(entry)) {
> + retval = rmi_read_block(hdev, i, &entry, sizeof(entry));
> + if (retval) {
> + hid_err(hdev,
> + "Read of PDT entry at %#06x failed.\n",
> + i);
> + goto error_exit;
> + }
> +
> + if (RMI4_END_OF_PDT(entry.function_number))
> + break;
> +
> + page_has_function = true;
> +
> + hid_info(hdev, "Found F%02X on page %#04x\n",
> + entry.function_number, page);
> +
> + rmi_register_function(data, &entry, page, interrupt);
> + interrupt += entry.interrupt_source_count;
> + }
> +
> + if (!page_has_function)
> + break;
> + }
> +
> + hid_info(hdev, "%s: Done with PDT scan.\n", __func__);
> + retval = 0;
> +
> +error_exit:
> + return retval;
> +}
> +
> +static int rmi_populate_f11(struct hid_device *hdev)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + u8 buf[20];
> + int ret;
> + bool has_query12;
> + bool has_physical_props;
> + unsigned x_size, y_size;
> +
> + if (!data->f11.query_base_addr) {
> + hid_err(hdev, "No 2D sensor found, giving up.\n");
> + return -ENODEV;
> + }
> +
> + /* query 0 contains some useful information */
> + ret = rmi_read(hdev, data->f11.query_base_addr, buf);
> + if (ret) {
> + hid_err(hdev, "can not get query 0: %d.\n", ret);
> + return ret;
> + }
> + has_query12 = !!(buf[0] & BIT(5));
> +
> + /* query 1 to get the max number of fingers */
> + ret = rmi_read(hdev, data->f11.query_base_addr + 1, buf);
> + if (ret) {
> + hid_err(hdev, "can not get NumberOfFingers: %d.\n", ret);
> + return ret;
> + }
> + data->max_fingers = (buf[0] & 0x07) + 1;
> + if (data->max_fingers > 5)
> + data->max_fingers = 10;
> +
> + data->f11.report_size = data->max_fingers * 5 +
> + DIV_ROUND_UP(data->max_fingers, 4);
> +
> + if (!(buf[0] & BIT(4))) {
> + hid_err(hdev, "No absolute events, giving up.\n");
> + return -ENODEV;
> + }
> +
> + /*
> + * query 12 to know if the physical properties are reported
> + * (query 12 is at offset 10 for HID devices)
> + */
> + if (has_query12) {
> + ret = rmi_read(hdev, data->f11.query_base_addr + 10, buf);
> + if (ret) {
> + hid_err(hdev, "can not get query 12: %d.\n", ret);
> + return ret;
> + }
> + has_physical_props = !!(buf[0] & BIT(5));
> +
> + if (has_physical_props) {
> + ret = rmi_read_block(hdev,
> + data->f11.query_base_addr + 11, buf, 4);
> + if (ret) {
> + hid_err(hdev, "can not read query 15-18: %d.\n",
> + ret);
> + return ret;
> + }
> +
> + x_size = buf[0] | (buf[1] << 8);
> + y_size = buf[2] | (buf[3] << 8);
> +
> + data->x_size_mm = DIV_ROUND_CLOSEST(x_size, 10);
> + data->y_size_mm = DIV_ROUND_CLOSEST(y_size, 10);
> +
> + hid_info(hdev, "%s: size in mm: %d x %d\n",
> + __func__, data->x_size_mm, data->y_size_mm);
> + }
> + }
> +
> + /* retrieve the ctrl registers */
> + ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 20);
> + if (ret) {
> + hid_err(hdev, "can not read ctrl block of size 20: %d.\n", ret);
> + return ret;
> + }
> +
> + data->max_x = buf[6] | (buf[7] << 8);
> + data->max_y = buf[8] | (buf[9] << 8);
> +
> + return 0;
> +}
> +
> +static int rmi_populate_f30(struct hid_device *hdev)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + u8 buf[20];
> + int ret;
> + bool has_gpio, has_led;
> + unsigned bytes_per_ctrl;
> + u8 ctrl2_addr;
> + int ctrl2_3_length;
> + int i;
> +
> + /* function F30 is for physical buttons */
> + if (!data->f30.query_base_addr) {
> + hid_err(hdev, "No GPIO/LEDs found, giving up.\n");
> + return -ENODEV;
> + }
> +
> + ret = rmi_read_block(hdev, data->f30.query_base_addr, buf, 2);
> + if (ret) {
> + hid_err(hdev, "can not get F30 query registers: %d.\n", ret);
> + return ret;
> + }
> +
> + has_gpio = !!(buf[0] & BIT(3));
> + has_led = !!(buf[0] & BIT(2));
> + data->gpio_led_count = buf[1] & 0x1f;
> +
> + /* retrieve ctrl 2 & 3 registers */
> + bytes_per_ctrl = (data->gpio_led_count + 7) / 8;
> + /* Ctrl0 is present only if both has_gpio and has_led are set*/
> + ctrl2_addr = (has_gpio && has_led) ? bytes_per_ctrl : 0;
> + /* Ctrl1 is always be present */
> + ctrl2_addr += bytes_per_ctrl;
> + ctrl2_3_length = 2 * bytes_per_ctrl;
> +
> + data->f30.report_size = bytes_per_ctrl;
> +
> + ret = rmi_read_block(hdev, data->f30.control_base_addr + ctrl2_addr,
> + buf, ctrl2_3_length);
> + if (ret) {
> + hid_err(hdev, "can not read ctrl 2&3 block of size %d: %d.\n",
> + ctrl2_3_length, ret);
> + return ret;
> + }
> +
> + for (i = 0; i < data->gpio_led_count; i++) {
> + int byte_position = i >> 3;
> + int bit_position = i & 0x07;
> + u8 dir_byte = buf[byte_position];
> + u8 data_byte = buf[byte_position + bytes_per_ctrl];
> + bool dir = (dir_byte >> bit_position) & BIT(0);
> + bool dat = (data_byte >> bit_position) & BIT(0);
> +
> + if (dir == 0) {
> + /* input mode */
> + if (dat) {
> + /* actual buttons have pull up resistor */
> + data->button_count++;
> + set_bit(i, &data->button_mask);
> + set_bit(i, &data->button_state_mask);
> + }
> + }
> +
> + }
> +
> + return 0;
> +}
> +
> +static int rmi_populate(struct hid_device *hdev)
> +{
> + int ret;
> +
> + ret = rmi_scan_pdt(hdev);
> + if (ret) {
> + hid_err(hdev, "PDT scan failed with code %d.\n", ret);
> + return ret;
> + }
> +
> + ret = rmi_populate_f11(hdev);
> + if (ret) {
> + hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
> + return ret;
> + }
> +
> + ret = rmi_populate_f30(hdev);
> + if (ret)
> + hid_warn(hdev, "Error while initializing F30 (%d).\n", ret);
> +
> + return 0;
> +}
> +
> +static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
> +{
> + struct rmi_data *data = hid_get_drvdata(hdev);
> + struct input_dev *input = hi->input;
> + int ret;
> + int res_x, res_y, i;
> +
> + data->input = input;
> +
> + hid_dbg(hdev, "Opening low level driver\n");
> + ret = hid_hw_open(hdev);
> + if (ret)
> + return;
> +
> + /* Allow incoming hid reports */
> + hid_device_io_start(hdev);
> +
> + ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "failed to set rmi mode\n");
> + goto exit;
> + }
> +
> + ret = rmi_set_page(hdev, 0);
> + if (ret < 0) {
> + dev_err(&hdev->dev, "failed to set page select to 0.\n");
> + goto exit;
> + }
> +
> + ret = rmi_populate(hdev);
> + if (ret)
> + goto exit;
> +
> + __set_bit(EV_ABS, input->evbit);
> + input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
> + input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
> +
> + if (data->x_size_mm && data->x_size_mm) {
> + res_x = (data->max_x - 1) / data->x_size_mm;
> + res_y = (data->max_y - 1) / data->x_size_mm;
> +
> + input_abs_set_res(input, ABS_MT_POSITION_X, res_x);
> + input_abs_set_res(input, ABS_MT_POSITION_Y, res_y);
> + }
> +
> + input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0);
> + input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xff, 0, 0);
> + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0x0f, 0, 0);
> + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 0x0f, 0, 0);
> +
> + input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
> +
> + if (data->button_count) {
> + __set_bit(EV_KEY, input->evbit);
> + for (i = 0; i < data->button_count; i++)
> + __set_bit(BTN_LEFT + i, input->keybit);
> +
> + if (data->button_count == 1)
> + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> + }
> +
> + set_bit(RMI_STARTED, &data->flags);
> +
> +exit:
> + hid_device_io_stop(hdev);
> + hid_hw_close(hdev);
> +}
> +
> +static int rmi_input_mapping(struct hid_device *hdev,
> + struct hid_input *hi, struct hid_field *field,
> + struct hid_usage *usage, unsigned long **bit, int *max)
> +{
> + /* we want to make HID ignore the advertised HID collection */
> + return -1;
> +}
> +
> +static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> + struct rmi_data *data = NULL;
> + int ret;
> + size_t alloc_size;
> +
> + data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + INIT_WORK(&data->reset_work, rmi_reset_work);
> + data->hdev = hdev;
> +
> + hid_set_drvdata(hdev, data);
> +
> + hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
> +
> + ret = hid_parse(hdev);
> + if (ret) {
> + hid_err(hdev, "parse failed\n");
> + return ret;
> + }
> +
> + data->input_report_size = (hdev->report_enum[HID_INPUT_REPORT]
> + .report_id_hash[RMI_ATTN_REPORT_ID]->size >> 3)
> + + 1 /* report id */;
> + data->output_report_size = (hdev->report_enum[HID_OUTPUT_REPORT]
> + .report_id_hash[RMI_WRITE_REPORT_ID]->size >> 3)
> + + 1 /* report id */;
> +
> + alloc_size = data->output_report_size + data->input_report_size;
> +
> + data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL);
> + if (!data->writeReport) {
> + ret = -ENOMEM;
> + return ret;
> + }
> +
> + data->readReport = data->writeReport + data->output_report_size;
> +
> + init_waitqueue_head(&data->wait);
> +
> + mutex_init(&data->page_mutex);
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> + if (ret) {
> + hid_err(hdev, "hw start failed\n");
> + return ret;
> + }
> +
> + if (!test_bit(RMI_STARTED, &data->flags)) {
> + hid_hw_stop(hdev);
> + return -EIO;
> + }
> +
> + hid_hw_stop(hdev);
> + return 0;
> +}
> +
> +static void rmi_remove(struct hid_device *hdev)
> +{
> + struct rmi_data *hdata = hid_get_drvdata(hdev);
> +
> + clear_bit(RMI_STARTED, &hdata->flags);
> +
> + hid_hw_stop(hdev);
> +}
> +
> +static const struct hid_device_id rmi_id[] = {
> + { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(hid, rmi_id);
> +
> +static struct hid_driver rmi_driver = {
> + .name = "hid-rmi",
> + .id_table = rmi_id,
> + .probe = rmi_probe,
> + .remove = rmi_remove,
> + .raw_event = rmi_raw_event,
> + .input_mapping = rmi_input_mapping,
> + .input_configured = rmi_input_configured,
> +#ifdef CONFIG_PM
> + .resume = rmi_post_resume,
> + .reset_resume = rmi_post_reset,
> +#endif
> +};
> +
> +module_hid_driver(rmi_driver);
> +
> +MODULE_AUTHOR("Andrew Duggan <aduggan@synaptics.com>");
> +MODULE_DESCRIPTION("RMI HID driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index 720e3a1..54f855b 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -570,6 +570,8 @@ struct hid_descriptor {
> .bus = BUS_USB, .vendor = (ven), .product = (prod)
> #define HID_BLUETOOTH_DEVICE(ven, prod) \
> .bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
> +#define HID_I2C_DEVICE(ven, prod) \
> + .bus = BUS_I2C, .vendor = (ven), .product = (prod)
>
> #define HID_REPORT_ID(rep) \
> .report_type = (rep)
> --
> 1.9.0
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
^ permalink raw reply
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