* Re: [PATCH 0/4] Input: ABS2 and friends
From: David Herrmann @ 2013-12-17 16:34 UTC (permalink / raw)
To: open list:HID CORE LAYER
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, Peter Hutterer,
Antonio Ospite, linux-kernel, Input Tools, David Herrmann
In-Reply-To: <1387295334-1744-1-git-send-email-dh.herrmann@gmail.com>
Hi
On Tue, Dec 17, 2013 at 4:48 PM, David Herrmann <dh.herrmann@gmail.com> wrote:
> Hi
>
> This implements the recently discussed ABS2 API. It's working fine on my machine
> with libevdev. Comments welcome!
>
> * Patch #1 fixes some uinput shortcomings and prepares uinput for ABS2
> * Patch #2 adds ABS2
> * Patch #3 is just a small comment-fix for #4
> * Patch #4 adds some new example ABS values in the new range
>
> Note that I have patches pending which make use of the new ABS values, but I'd
> like to get this reduced series in first.
If someone is interested in the libevdev patches, see here:
http://cgit.freedesktop.org/~dvdhrm/libevdev/log/?h=abs2
I tested the libevdev test-suite in all combinations linux+libevdev,
linux-abs2 + libevdev, linux + libevdev-abs2, linux-abs2 +
libevdev-abs2 and all worked fine.
Thanks
David
^ permalink raw reply
* [PATCH 4/4] Input: add motion-tracking ABS_* bits and docs
From: David Herrmann @ 2013-12-17 15:48 UTC (permalink / raw)
To: linux-input
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, Peter Hutterer,
Antonio Ospite, linux-kernel, input-tools, David Herrmann
In-Reply-To: <1387295334-1744-1-git-send-email-dh.herrmann@gmail.com>
Motion sensors are getting quite common in mobile devices. To avoid
returning accelerometer data via ABS_X/Y/Z and irritating the Xorg
mouse-driver, this adds separate ABS_* bits for that.
This is needed if gaming devices want to report their normal data plus
accelerometer/gyro data. Usually, ABS_X/Y are already used by analog
sticks, so need separate definitions, anyway.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
Documentation/input/gamepad.txt | 7 ++
Documentation/input/motion-tracking.txt | 149 ++++++++++++++++++++++++++++++++
include/linux/mod_devicetable.h | 2 +-
include/uapi/linux/input.h | 9 +-
4 files changed, 165 insertions(+), 2 deletions(-)
create mode 100644 Documentation/input/motion-tracking.txt
diff --git a/Documentation/input/gamepad.txt b/Documentation/input/gamepad.txt
index 196dc42..eeda685 100644
--- a/Documentation/input/gamepad.txt
+++ b/Documentation/input/gamepad.txt
@@ -57,6 +57,9 @@ Most gamepads have the following features:
- Rumble
Many devices provide force-feedback features. But are mostly just
simple rumble motors.
+ - Motion-tracking
+ Gamepads may include motion-tracking sensors like accelerometers and
+ gyroscopes.
3. Detection
~~~~~~~~~~~~
@@ -153,5 +156,9 @@ Menu-Pad:
Rumble:
Rumble is adverticed as FF_RUMBLE.
+Motion-tracking:
+ Motion-tracking is defined in ./Documentation/input/motion-tracking.txt and
+ gamepads shall comply to the rules defined there.
+
----------------------------------------------------------------------------
Written 2013 by David Herrmann <dh.herrmann@gmail.com>
diff --git a/Documentation/input/motion-tracking.txt b/Documentation/input/motion-tracking.txt
new file mode 100644
index 0000000..0885c9a
--- /dev/null
+++ b/Documentation/input/motion-tracking.txt
@@ -0,0 +1,149 @@
+ Motion Tracking API
+----------------------------------------------------------------------------
+
+1. Intro
+~~~~~~~~
+Motion tracking devices produce device motion events generated from an
+accelerometer, gyroscope or compass. This data can be returned to user-space
+via input events. This document defines how this data is reported.
+
+2. Devices
+~~~~~~~~~~
+In this document, a "device" is one of:
+ - accelerometer
+ - gyroscope
+ - compass
+
+These devices returned their information via different APIs in the past. To
+unify them and define a common API, a set of input evdev codes was created. Old
+drivers might continue using their API, but developers are encouraged to use
+the input evdev API for new drivers.
+
+2.1 Axes
+~~~~~~~~
+Movement data is usually returned as absolute data for the 3 axes of a device.
+In this context, the three axes are defined as:
+ - X: Axis goes from the left to the right side of the device
+ - Y: Axis goes from the bottom to the top of the device
+ - Z: Axis goes from the back to the front of the device
+
+The front of a device is the side faced to the user. For a mobile-phone it
+would be the screen. For devices without a screen, the front is usually the
+side with the most buttons on it.
+
+ Example: Mobile-Phone
+ +-------------------------------------------------------------------------+
+ | TOP |
+ | |
+ | |
+ | +---------------------------+ |
+ | |\ ________________________ \ .__ |
+ | \ \ \ \ \ |\ |
+ | \ \ \ __ \ \ \ RIGHT|
+ | \ \ \ /| \ \ \__ |
+ | \ \ \ __/ \ \ |\ |
+ | \ \ \ /| \ \ \ (Y Axis) |
+ | \ \ \ __/ (Z axis) \ \ \__ |
+ | \ \ \ /| \ \ |\ |
+ | LEFT \ \ \ / \ \ \ |
+ | \ \ \ FRONT \ \ \ |
+ | \ \ \ \ \ |
+ | \ \ \_______________________\ \ |
+ | \ \ ___ \ |
+ | /\ \ \__\ \ |
+ | __/ \ +---------------------------+ |
+ | /| \|___________________________| |
+ | / BACK |
+ | (X axis) |
+ | ------->------->------->-------> |
+ | |
+ | |
+ | BOTTOM |
+ +-------------------------------------------------------------------------+
+
+Rotation-data is reported as clock-wise rotation on an axis. For a given axes,
+the reported rotation would be:
+ ___
+ /|
+ / | (axis)
+ /
+ .-**-.
+ / / \
+ | / \ | /
+ \ / \|/ (clock-wise rotation)
+ /
+ /
+ /
+
+2.2 Calibration
+~~~~~~~~~~~~~~~
+Motion sensors are often highly sensitive and need precise calibration. Users
+are adviced to perform neutral-point calibration themselves or to implement a
+state-machine to normalize input data automatically.
+
+Kernel devices may perform their own calibration and/or normalization. However,
+this is usually sparse and, if implemented, transparent to the user.
+
+There is currently no way to feed calibration data into the kernel in a generic
+way. Proposals welcome!
+
+2.3 Units
+~~~~~~~~~
+(NOTE: This section describes an experimental API. Currently, no device complies
+to these rules so this might change in the future.)
+
+Reported data shall be returned as:
+ - Acceleration: 1/1000 m per s^2
+ - Rotation: 1/1000 degree per second
+
+However, for most devices the reported units are unknown (more precisely: no
+one has the time to measure them and figure them out). Therefore, user-space
+shall use abs-minimum and abs-maximum to calculate relative data and use that
+instead. Devices which return wrong units may be fixed in the future to comply
+to these rules.
+
+3.1 Accelerometer
+~~~~~~~~~~~~~~~~~
+Accelerometers measure movement acceleration of devices. Any combination of the
+three available axes can be used. Usually, all three are supported.
+
+Data is provided as absolute acceleration. A positive integer defines the
+acceleration in the direction of an axis. A negative integer defines
+acceleration in the opposite direction.
+
+The evdev ABS codes used are:
+ - ABS_ACCEL_X: X axis
+ - ABS_ACCEL_Y: Y axis
+ - ABS_ACCEL_Z: Z axis
+
+3.2 Gyroscope
+~~~~~~~~~~~~~
+A gyroscope measures rotational speed (*not* acceleration!). Any combination of
+the three available axes can be used. Usually, all three are supported.
+
+Data is provided as absolute speed. A positive integer defines the rotational
+speed in clock-wise order around a given axis. A negative integer defines it in
+counter-clock-wise order.
+
+The evdev ABS codes used are:
+ - ABS_GYRO_X: X axis (also: Pitch)
+ - ABS_GYRO_Y: Y axis (also: Roll)
+ - ABS_GYRO_Z: Z axis (also: Azimuth/Yaw)
+
+3.3 Compass
+~~~~~~~~~~~
+(NOTE: No compass device currently uses the evdev input subsystem. Thus, this
+API is only a proposal, it hasn't been implemented, yet.)
+
+A compass measures the ambient magnetic field of the three defined axes. This
+makes the data self-contained and independent of the current device position.
+Any combination of the three axes can be used. Usually all three are supported,
+otherwise, it's not really useful as a compass.
+
+Proposed evdev ABS codes are:
+ - ABS_COMPASS_X: X axis
+ - ABS_COMPASS_Y: Y axis
+ - ABS_COMPASS_Z: Z axis
+
+----------------------------------------------------------------------------
+ Written 2013 by David Herrmann <dh.herrmann@gmail.com>
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 45e9214..329aa30 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -277,7 +277,7 @@ struct pcmcia_device_id {
#define INPUT_DEVICE_ID_KEY_MIN_INTERESTING 0x71
#define INPUT_DEVICE_ID_KEY_MAX 0x2ff
#define INPUT_DEVICE_ID_REL_MAX 0x0f
-#define INPUT_DEVICE_ID_ABS_MAX 0x3f
+#define INPUT_DEVICE_ID_ABS_MAX 0x4f
#define INPUT_DEVICE_ID_MSC_MAX 0x07
#define INPUT_DEVICE_ID_LED_MAX 0x0f
#define INPUT_DEVICE_ID_SND_MAX 0x07
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
index 1856461..e4c3596 100644
--- a/include/uapi/linux/input.h
+++ b/include/uapi/linux/input.h
@@ -869,12 +869,19 @@ struct input_keymap_entry {
#define ABS_MAX 0x3f
#define ABS_CNT (ABS_MAX+1)
+#define ABS_ACCEL_X 0x40 /* Accelerometer X axis */
+#define ABS_ACCEL_Y 0x41 /* Accelerometer Y axis */
+#define ABS_ACCEL_Z 0x42 /* Accelerometer Z axis */
+#define ABS_GYRO_X 0x43 /* Gyroscope X axis */
+#define ABS_GYRO_Y 0x44 /* Gyroscope Y axis */
+#define ABS_GYRO_Z 0x45 /* Gyroscope Z axis */
+
/*
* Due to API restrictions the legacy evdev API only supports ABS values up to
* ABS_MAX/CNT. Use the extended *ABS2 ioctls to operate on any ABS values in
* between ABS_MAX and ABS_MAX2.
*/
-#define ABS_MAX2 0x3f
+#define ABS_MAX2 0x4f
#define ABS_CNT2 (ABS_MAX2+1)
/*
--
1.8.5.1
^ permalink raw reply related
* [PATCH 3/4] Input: remove ambigious gamepad comment
From: David Herrmann @ 2013-12-17 15:48 UTC (permalink / raw)
To: linux-input
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, Peter Hutterer,
Antonio Ospite, linux-kernel, input-tools, David Herrmann
In-Reply-To: <1387295334-1744-1-git-send-email-dh.herrmann@gmail.com>
If only a single trigger combination is present on a device, either can be
reported. If the side of the trigger (left/right) cannot be decided (eg.,
if it's centered), then choose any side. User-space automatically notices
that only a single trigger is reported and thus doesn't care how it is
reported.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
Documentation/input/gamepad.txt | 2 --
1 file changed, 2 deletions(-)
diff --git a/Documentation/input/gamepad.txt b/Documentation/input/gamepad.txt
index 31bb6a4..196dc42 100644
--- a/Documentation/input/gamepad.txt
+++ b/Documentation/input/gamepad.txt
@@ -138,8 +138,6 @@ Triggers:
Upper trigger buttons are reported as BTN_TR or ABS_HAT1X (right) and BTN_TL
or ABS_HAT1Y (left). Lower trigger buttons are reported as BTN_TR2 or
ABS_HAT2X (right/ZR) and BTN_TL2 or ABS_HAT2Y (left/ZL).
- If only one trigger-button combination is present (upper+lower), they are
- reported as "right" triggers (BTN_TR/ABS_HAT1X).
(ABS trigger values start at 0, pressure is reported as positive values)
Menu-Pad:
--
1.8.5.1
^ permalink raw reply related
* [PATCH 0/4] Input: ABS2 and friends
From: David Herrmann @ 2013-12-17 15:48 UTC (permalink / raw)
To: linux-input
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, Peter Hutterer,
Antonio Ospite, linux-kernel, input-tools, David Herrmann
Hi
This implements the recently discussed ABS2 API. It's working fine on my machine
with libevdev. Comments welcome!
* Patch #1 fixes some uinput shortcomings and prepares uinput for ABS2
* Patch #2 adds ABS2
* Patch #3 is just a small comment-fix for #4
* Patch #4 adds some new example ABS values in the new range
Note that I have patches pending which make use of the new ABS values, but I'd
like to get this reduced series in first.
Thanks
David
David Herrmann (4):
Input: uinput: add full absinfo support
Input: introduce ABS_MAX2/CNT2 and friends
Input: remove ambigious gamepad comment
Input: add motion-tracking ABS_* bits and docs
Documentation/input/gamepad.txt | 9 +-
Documentation/input/motion-tracking.txt | 149 +++++++++++++++++++++++++++++++
drivers/hid/hid-debug.c | 2 +-
drivers/hid/hid-input.c | 2 +-
drivers/input/evdev.c | 95 +++++++++++++++++++-
drivers/input/input.c | 14 +--
drivers/input/keyboard/goldfish_events.c | 6 +-
drivers/input/keyboard/hil_kbd.c | 2 +-
drivers/input/misc/uinput.c | 146 ++++++++++++++++++++++--------
include/linux/hid.h | 2 +-
include/linux/input.h | 6 +-
include/linux/mod_devicetable.h | 2 +-
include/uapi/linux/input.h | 49 +++++++++-
include/uapi/linux/uinput.h | 9 ++
14 files changed, 431 insertions(+), 62 deletions(-)
create mode 100644 Documentation/input/motion-tracking.txt
--
1.8.5.1
^ permalink raw reply
* [PATCH 2/4] Input: introduce ABS_MAX2/CNT2 and friends
From: David Herrmann @ 2013-12-17 15:48 UTC (permalink / raw)
To: linux-input
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, Peter Hutterer,
Antonio Ospite, linux-kernel, input-tools, David Herrmann
In-Reply-To: <1387295334-1744-1-git-send-email-dh.herrmann@gmail.com>
As we painfully noticed during the 3.12 merge-window our
EVIOCGABS/EVIOCSABS API is limited to ABS_MAX<=0x3f. We tried several
hacks to work around it but if we ever decide to increase ABS_MAX, the
EVIOCSABS ioctl ABI might overflow into the next byte causing horrible
misinterpretations in the kernel that we cannot catch.
Therefore, we decided to go with ABS_MAX2/CNT2 and introduce two new
ioctls to get/set abs-params. They no longer encode the ABS code in the
ioctl number and thus allow up to 4 billion ABS codes.
The new API also allows to query multiple ABS values with one call. To
allow EVIOCSABS2(code = 0, cnt = ABS_CNT2) we need to silently ignore
writes to ABS_MT_SLOT. Furthermore, for better compatibility with
newer user-space, we ignore writes to unknown codes. Hence, if we ever
increase ABS_MAX2, new user-space will work with code=0,cnt=ABS_CNT2 just
fine even on old kernels.
Note that we also need to increase EV_VERSION so user-space can reliably
know whether ABS2 is supported. Unfortunately, we return EINVAL instead of
ENOSYS for unknown evdev ioctls so it's nearly impossible to catch
reliably without EVIOCGVERSION.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
drivers/hid/hid-debug.c | 2 +-
drivers/hid/hid-input.c | 2 +-
drivers/input/evdev.c | 95 +++++++++++++++++++++++++++++++-
drivers/input/input.c | 14 ++---
drivers/input/keyboard/goldfish_events.c | 6 +-
drivers/input/keyboard/hil_kbd.c | 2 +-
drivers/input/misc/uinput.c | 6 +-
include/linux/hid.h | 2 +-
include/linux/input.h | 6 +-
include/uapi/linux/input.h | 42 +++++++++++++-
include/uapi/linux/uinput.h | 2 +-
11 files changed, 155 insertions(+), 24 deletions(-)
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index 8453214..d32fa30 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -862,7 +862,7 @@ static const char *relatives[REL_MAX + 1] = {
[REL_WHEEL] = "Wheel", [REL_MISC] = "Misc",
};
-static const char *absolutes[ABS_CNT] = {
+static const char *absolutes[ABS_CNT2] = {
[ABS_X] = "X", [ABS_Y] = "Y",
[ABS_Z] = "Z", [ABS_RX] = "Rx",
[ABS_RY] = "Ry", [ABS_RZ] = "Rz",
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index d97f232..a02721c 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -1300,7 +1300,7 @@ static bool hidinput_has_been_populated(struct hid_input *hidinput)
for (i = 0; i < BITS_TO_LONGS(REL_CNT); i++)
r |= hidinput->input->relbit[i];
- for (i = 0; i < BITS_TO_LONGS(ABS_CNT); i++)
+ for (i = 0; i < BITS_TO_LONGS(ABS_CNT2); i++)
r |= hidinput->input->absbit[i];
for (i = 0; i < BITS_TO_LONGS(MSC_CNT); i++)
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index a06e125..32b74e5 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -643,7 +643,7 @@ static int handle_eviocgbit(struct input_dev *dev,
case 0: bits = dev->evbit; len = EV_MAX; break;
case EV_KEY: bits = dev->keybit; len = KEY_MAX; break;
case EV_REL: bits = dev->relbit; len = REL_MAX; break;
- case EV_ABS: bits = dev->absbit; len = ABS_MAX; break;
+ case EV_ABS: bits = dev->absbit; len = ABS_MAX2; break;
case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break;
case EV_LED: bits = dev->ledbit; len = LED_MAX; break;
case EV_SND: bits = dev->sndbit; len = SND_MAX; break;
@@ -671,6 +671,93 @@ static int handle_eviocgbit(struct input_dev *dev,
}
#undef OLD_KEY_MAX
+static int evdev_handle_get_abs2(struct input_dev *dev, void __user *p)
+{
+ u32 code, cnt, valid_cnt, i;
+ struct input_absinfo2 __user *pinfo = p;
+ struct input_absinfo abs;
+
+ if (copy_from_user(&code, &pinfo->code, sizeof(code)))
+ return -EFAULT;
+ if (copy_from_user(&cnt, &pinfo->cnt, sizeof(cnt)))
+ return -EFAULT;
+ if (!cnt)
+ return 0;
+
+ if (!dev->absinfo)
+ valid_cnt = 0;
+ else if (code > ABS_MAX2)
+ valid_cnt = 0;
+ else if (code + cnt <= code || code + cnt > ABS_MAX2)
+ valid_cnt = ABS_MAX2 - code + 1;
+ else
+ valid_cnt = cnt;
+
+ for (i = 0; i < valid_cnt; ++i) {
+ /*
+ * Take event lock to ensure that we are not
+ * copying data while EVIOCSABS2 changes it.
+ * Might be inconsistent, otherwise.
+ */
+ spin_lock_irq(&dev->event_lock);
+ abs = dev->absinfo[code + i];
+ spin_unlock_irq(&dev->event_lock);
+
+ if (copy_to_user(&pinfo->info[i], &abs, sizeof(abs)))
+ return -EFAULT;
+ }
+
+ memset(&abs, 0, sizeof(abs));
+ for (i = valid_cnt; i < cnt; ++i)
+ if (copy_to_user(&pinfo->info[i], &abs, sizeof(abs)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int evdev_handle_set_abs2(struct input_dev *dev, void __user *p)
+{
+ struct input_absinfo2 __user *pinfo = p;
+ struct input_absinfo *abs;
+ u32 code, cnt, i;
+ size_t size;
+
+ if (!dev->absinfo)
+ return 0;
+ if (copy_from_user(&code, &pinfo->code, sizeof(code)))
+ return -EFAULT;
+ if (copy_from_user(&cnt, &pinfo->cnt, sizeof(cnt)))
+ return -EFAULT;
+ if (!cnt || code > ABS_MAX2)
+ return 0;
+
+ if (code + cnt <= code || code + cnt > ABS_MAX2)
+ cnt = ABS_MAX2 - code + 1;
+
+ size = cnt * sizeof(*abs);
+ abs = memdup_user(pinfo->info, size);
+ if (IS_ERR(abs))
+ return PTR_ERR(abs);
+
+ /*
+ * Take event lock to ensure that we are not
+ * changing device parameters in the middle
+ * of event.
+ */
+ spin_lock_irq(&dev->event_lock);
+ for (i = 0; i < cnt; ++i) {
+ /* silently drop ABS_MT_SLOT */
+ if (code + i == ABS_MT_SLOT)
+ continue;
+
+ dev->absinfo[code + i] = abs[i];
+ }
+ spin_unlock_irq(&dev->event_lock);
+
+ kfree(abs);
+ return 0;
+}
+
static int evdev_handle_get_keycode(struct input_dev *dev, void __user *p)
{
struct input_keymap_entry ke = {
@@ -898,6 +985,12 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
client->clkid = i;
return 0;
+ case EVIOCGABS2:
+ return evdev_handle_get_abs2(dev, p);
+
+ case EVIOCSABS2:
+ return evdev_handle_set_abs2(dev, p);
+
case EVIOCGKEYCODE:
return evdev_handle_get_keycode(dev, p);
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 846ccdd..042157c 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -305,7 +305,7 @@ static int input_get_disposition(struct input_dev *dev,
break;
case EV_ABS:
- if (is_event_supported(code, dev->absbit, ABS_MAX))
+ if (is_event_supported(code, dev->absbit, ABS_MAX2))
disposition = input_handle_abs_event(dev, code, &value);
break;
@@ -474,7 +474,7 @@ EXPORT_SYMBOL(input_inject_event);
void input_alloc_absinfo(struct input_dev *dev)
{
if (!dev->absinfo)
- dev->absinfo = kcalloc(ABS_CNT, sizeof(struct input_absinfo),
+ dev->absinfo = kcalloc(ABS_CNT2, sizeof(struct input_absinfo),
GFP_KERNEL);
WARN(!dev->absinfo, "%s(): kcalloc() failed?\n", __func__);
@@ -954,7 +954,7 @@ static const struct input_device_id *input_match_device(struct input_handler *ha
if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
continue;
- if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
+ if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX2))
continue;
if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
@@ -1147,7 +1147,7 @@ static int input_devices_seq_show(struct seq_file *seq, void *v)
if (test_bit(EV_REL, dev->evbit))
input_seq_print_bitmap(seq, "REL", dev->relbit, REL_MAX);
if (test_bit(EV_ABS, dev->evbit))
- input_seq_print_bitmap(seq, "ABS", dev->absbit, ABS_MAX);
+ input_seq_print_bitmap(seq, "ABS", dev->absbit, ABS_MAX2);
if (test_bit(EV_MSC, dev->evbit))
input_seq_print_bitmap(seq, "MSC", dev->mscbit, MSC_MAX);
if (test_bit(EV_LED, dev->evbit))
@@ -1333,7 +1333,7 @@ static int input_print_modalias(char *buf, int size, struct input_dev *id,
len += input_print_modalias_bits(buf + len, size - len,
'r', id->relbit, 0, REL_MAX);
len += input_print_modalias_bits(buf + len, size - len,
- 'a', id->absbit, 0, ABS_MAX);
+ 'a', id->absbit, 0, ABS_MAX2);
len += input_print_modalias_bits(buf + len, size - len,
'm', id->mscbit, 0, MSC_MAX);
len += input_print_modalias_bits(buf + len, size - len,
@@ -1592,7 +1592,7 @@ static int input_dev_uevent(struct device *device, struct kobj_uevent_env *env)
if (test_bit(EV_REL, dev->evbit))
INPUT_ADD_HOTPLUG_BM_VAR("REL=", dev->relbit, REL_MAX);
if (test_bit(EV_ABS, dev->evbit))
- INPUT_ADD_HOTPLUG_BM_VAR("ABS=", dev->absbit, ABS_MAX);
+ INPUT_ADD_HOTPLUG_BM_VAR("ABS=", dev->absbit, ABS_MAX2);
if (test_bit(EV_MSC, dev->evbit))
INPUT_ADD_HOTPLUG_BM_VAR("MSC=", dev->mscbit, MSC_MAX);
if (test_bit(EV_LED, dev->evbit))
@@ -1929,7 +1929,7 @@ static unsigned int input_estimate_events_per_packet(struct input_dev *dev)
events = mt_slots + 1; /* count SYN_MT_REPORT and SYN_REPORT */
- for (i = 0; i < ABS_CNT; i++) {
+ for (i = 0; i < ABS_CNT2; i++) {
if (test_bit(i, dev->absbit)) {
if (input_is_mt_axis(i))
events += mt_slots;
diff --git a/drivers/input/keyboard/goldfish_events.c b/drivers/input/keyboard/goldfish_events.c
index 9f60a2e..9999cea 100644
--- a/drivers/input/keyboard/goldfish_events.c
+++ b/drivers/input/keyboard/goldfish_events.c
@@ -90,8 +90,8 @@ static void events_import_abs_params(struct event_dev *edev)
__raw_writel(PAGE_ABSDATA, addr + REG_SET_PAGE);
count = __raw_readl(addr + REG_LEN) / sizeof(val);
- if (count > ABS_MAX)
- count = ABS_MAX;
+ if (count > ABS_MAX2)
+ count = ABS_MAX2;
for (i = 0; i < count; i++) {
if (!test_bit(i, input_dev->absbit))
@@ -158,7 +158,7 @@ static int events_probe(struct platform_device *pdev)
events_import_bits(edev, input_dev->evbit, EV_SYN, EV_MAX);
events_import_bits(edev, input_dev->keybit, EV_KEY, KEY_MAX);
events_import_bits(edev, input_dev->relbit, EV_REL, REL_MAX);
- events_import_bits(edev, input_dev->absbit, EV_ABS, ABS_MAX);
+ events_import_bits(edev, input_dev->absbit, EV_ABS, ABS_MAX2);
events_import_bits(edev, input_dev->mscbit, EV_MSC, MSC_MAX);
events_import_bits(edev, input_dev->ledbit, EV_LED, LED_MAX);
events_import_bits(edev, input_dev->sndbit, EV_SND, SND_MAX);
diff --git a/drivers/input/keyboard/hil_kbd.c b/drivers/input/keyboard/hil_kbd.c
index 589e3c2..4e4e010 100644
--- a/drivers/input/keyboard/hil_kbd.c
+++ b/drivers/input/keyboard/hil_kbd.c
@@ -387,7 +387,7 @@ static void hil_dev_pointer_setup(struct hil_dev *ptr)
0, HIL_IDD_AXIS_MAX(idd, i - 3), 0, 0);
#ifdef TABLET_AUTOADJUST
- for (i = 0; i < ABS_MAX; i++) {
+ for (i = 0; i < ABS_MAX2; i++) {
int diff = input_abs_get_max(input_dev, ABS_X + i) / 10;
input_abs_set_min(input_dev, ABS_X + i,
input_abs_get_min(input_dev, ABS_X + i) + diff);
diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c
index 927ad9a..4660ed1 100644
--- a/drivers/input/misc/uinput.c
+++ b/drivers/input/misc/uinput.c
@@ -311,7 +311,7 @@ static int uinput_validate_absbits(struct input_dev *dev)
unsigned int cnt;
int retval = 0;
- for (cnt = 0; cnt < ABS_CNT; cnt++) {
+ for (cnt = 0; cnt < ABS_CNT2; cnt++) {
int min, max;
if (!test_bit(cnt, dev->absbit))
continue;
@@ -474,7 +474,7 @@ static int uinput_setup_device2(struct uinput_device *udev,
return -EINVAL;
/* rough check to avoid huge kernel space allocations */
- max = ABS_CNT * sizeof(*user_dev2->abs) + sizeof(*user_dev2);
+ max = ABS_CNT2 * sizeof(*user_dev2->abs) + sizeof(*user_dev2);
if (count > max)
return -EINVAL;
@@ -780,7 +780,7 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
break;
case UI_SET_ABSBIT:
- retval = uinput_set_bit(arg, absbit, ABS_MAX);
+ retval = uinput_set_bit(arg, absbit, ABS_MAX2);
break;
case UI_SET_MSCBIT:
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 31b9d29..c21d8bb 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -828,7 +828,7 @@ static inline void hid_map_usage(struct hid_input *hidinput,
switch (type) {
case EV_ABS:
*bit = input->absbit;
- *max = ABS_MAX;
+ *max = ABS_MAX2;
break;
case EV_REL:
*bit = input->relbit;
diff --git a/include/linux/input.h b/include/linux/input.h
index 82ce323..c6add6f 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -129,7 +129,7 @@ struct input_dev {
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
- unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
+ unsigned long absbit[BITS_TO_LONGS(ABS_CNT2)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
@@ -210,8 +210,8 @@ struct input_dev {
#error "REL_MAX and INPUT_DEVICE_ID_REL_MAX do not match"
#endif
-#if ABS_MAX != INPUT_DEVICE_ID_ABS_MAX
-#error "ABS_MAX and INPUT_DEVICE_ID_ABS_MAX do not match"
+#if ABS_MAX2 != INPUT_DEVICE_ID_ABS_MAX
+#error "ABS_MAX2 and INPUT_DEVICE_ID_ABS_MAX do not match"
#endif
#if MSC_MAX != INPUT_DEVICE_ID_MSC_MAX
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
index bd24470..1856461 100644
--- a/include/uapi/linux/input.h
+++ b/include/uapi/linux/input.h
@@ -32,7 +32,7 @@ struct input_event {
* Protocol version.
*/
-#define EV_VERSION 0x010001
+#define EV_VERSION 0x010002
/*
* IOCTLs (0x00 - 0x7f)
@@ -74,6 +74,30 @@ struct input_absinfo {
};
/**
+ * struct input_absinfo2 - used by EVIOC[G/S]ABS2 ioctls
+ * @code: First ABS code to query
+ * @cnt: Number of ABS codes to query starting at @code
+ * @info: #@cnt absinfo structures to get/set abs parameters for all codes
+ *
+ * This structure is used by the new EVIOC[G/S]ABS2 ioctls which
+ * do the same as the old EVIOC[G/S]ABS ioctls but avoid encoding
+ * the ABS code in the ioctl number. This allows a much wider
+ * range of ABS codes. Furthermore, it allows to query multiple codes with a
+ * single call.
+ *
+ * Note that this silently drops any requests to set ABS_MT_SLOT. Hence, it is
+ * allowed to call this with code=0 cnt=ABS_CNT2. Furthermore, retrieving
+ * invalid codes returns all 0, setting them does nothing. So you must check
+ * with EVIOCGBIT first if you want reliable results. This behavior is needed
+ * to allow forward compatibility to new ABS codes.
+ */
+struct input_absinfo2 {
+ __u32 code;
+ __u32 cnt;
+ struct input_absinfo info[1];
+};
+
+/**
* struct input_keymap_entry - used by EVIOCGKEYCODE/EVIOCSKEYCODE ioctls
* @scancode: scancode represented in machine-endian form.
* @len: length of the scancode that resides in @scancode buffer.
@@ -153,6 +177,8 @@ struct input_keymap_entry {
#define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */
#define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */
+#define EVIOCGABS2 _IOR('E', 0x92, struct input_absinfo2) /* get abs value/limits */
+#define EVIOCSABS2 _IOW('E', 0x93, struct input_absinfo2) /* set abs value/limits */
#define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */
@@ -835,11 +861,23 @@ struct input_keymap_entry {
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */
-
+/*
+ * ABS_MAX/CNT is limited to a maximum of 0x3f due to the design of EVIOCGABS
+ * and EVIOCSABS ioctls. Other kernel APIs like uinput also hardcoded it. Do
+ * not modify this value and instead use the extended ABS_MAX2/CNT2 API.
+ */
#define ABS_MAX 0x3f
#define ABS_CNT (ABS_MAX+1)
/*
+ * Due to API restrictions the legacy evdev API only supports ABS values up to
+ * ABS_MAX/CNT. Use the extended *ABS2 ioctls to operate on any ABS values in
+ * between ABS_MAX and ABS_MAX2.
+ */
+#define ABS_MAX2 0x3f
+#define ABS_CNT2 (ABS_MAX2+1)
+
+/*
* Switch events
*/
diff --git a/include/uapi/linux/uinput.h b/include/uapi/linux/uinput.h
index c2e8710..27ee521 100644
--- a/include/uapi/linux/uinput.h
+++ b/include/uapi/linux/uinput.h
@@ -140,7 +140,7 @@ struct uinput_user_dev2 {
char name[UINPUT_MAX_NAME_SIZE];
struct input_id id;
__u32 ff_effects_max;
- struct input_absinfo abs[ABS_CNT];
+ struct input_absinfo abs[ABS_CNT2];
};
#endif /* _UAPI__UINPUT_H_ */
--
1.8.5.1
^ permalink raw reply related
* [PATCH 1/4] Input: uinput: add full absinfo support
From: David Herrmann @ 2013-12-17 15:48 UTC (permalink / raw)
To: linux-input
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, Peter Hutterer,
Antonio Ospite, linux-kernel, input-tools, David Herrmann
In-Reply-To: <1387295334-1744-1-git-send-email-dh.herrmann@gmail.com>
We currently lack support for abs-resolution and abs-value parameters
during uinput ABS initialization. Furthermore, our parsers don't allow
growing ABS_CNT values. Therefore, introduce uinput_user_dev2.
User-space is free to write uinput_user_dev2 objects instead of
uinput_user_dev legacy objects now. If the kernel lacks support for it,
our comparison for "count != sizeof(struct uinput_user_dev)" will catch
this and return EINVAL. User-space shall retry with the legacy mode then.
Internally, we transform the legacy object into uinput_user_dev2 and then
handle both the same way.
The new uinput_user_dev2 object has multiple new features:
- abs payload now has "value" and "resolution" parameters as part of the
switch to "struct input_absinfo". We simply copy these over.
- Our parser allows growing ABS_CNT. We automatically detect the payload
size of the caller, thus, calculating the ABS_CNT the program was
compiled with.
- A "version" field to denote the uinput-version used. This is required
to properly support changing "struct input_user_dev2" changes in the
future. Due to the dynamic ABS_CNT support, we cannot simply add new
fields, as we cannot deduce the structure size from the user-space
given size. Thus, we need the "version" field to allow changing the
object and properly detecting it in our write() handler.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
drivers/input/misc/uinput.c | 142 ++++++++++++++++++++++++++++++++------------
include/uapi/linux/uinput.h | 9 +++
2 files changed, 114 insertions(+), 37 deletions(-)
diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c
index 7728359..927ad9a 100644
--- a/drivers/input/misc/uinput.c
+++ b/drivers/input/misc/uinput.c
@@ -358,14 +358,16 @@ static int uinput_allocate_device(struct uinput_device *udev)
}
static int uinput_setup_device(struct uinput_device *udev,
- const char __user *buffer, size_t count)
+ struct uinput_user_dev2 *user_dev2,
+ size_t abscnt)
{
- struct uinput_user_dev *user_dev;
struct input_dev *dev;
int i;
int retval;
+ struct input_absinfo *abs;
- if (count != sizeof(struct uinput_user_dev))
+ /* Ensure name is filled in */
+ if (!user_dev2->name[0])
return -EINVAL;
if (!udev->dev) {
@@ -375,37 +377,27 @@ static int uinput_setup_device(struct uinput_device *udev,
}
dev = udev->dev;
-
- user_dev = memdup_user(buffer, sizeof(struct uinput_user_dev));
- if (IS_ERR(user_dev))
- return PTR_ERR(user_dev);
-
- udev->ff_effects_max = user_dev->ff_effects_max;
-
- /* Ensure name is filled in */
- if (!user_dev->name[0]) {
- retval = -EINVAL;
- goto exit;
- }
+ udev->ff_effects_max = user_dev2->ff_effects_max;
kfree(dev->name);
- dev->name = kstrndup(user_dev->name, UINPUT_MAX_NAME_SIZE,
+ dev->name = kstrndup(user_dev2->name, UINPUT_MAX_NAME_SIZE,
GFP_KERNEL);
- if (!dev->name) {
- retval = -ENOMEM;
- goto exit;
- }
-
- dev->id.bustype = user_dev->id.bustype;
- dev->id.vendor = user_dev->id.vendor;
- dev->id.product = user_dev->id.product;
- dev->id.version = user_dev->id.version;
+ if (!dev->name)
+ return -ENOMEM;
- for (i = 0; i < ABS_CNT; i++) {
- input_abs_set_max(dev, i, user_dev->absmax[i]);
- input_abs_set_min(dev, i, user_dev->absmin[i]);
- input_abs_set_fuzz(dev, i, user_dev->absfuzz[i]);
- input_abs_set_flat(dev, i, user_dev->absflat[i]);
+ dev->id.bustype = user_dev2->id.bustype;
+ dev->id.vendor = user_dev2->id.vendor;
+ dev->id.product = user_dev2->id.product;
+ dev->id.version = user_dev2->id.version;
+
+ for (i = 0; i < abscnt; i++) {
+ abs = &user_dev2->abs[i];
+ input_abs_set_val(dev, i, abs->value);
+ input_abs_set_max(dev, i, abs->maximum);
+ input_abs_set_min(dev, i, abs->minimum);
+ input_abs_set_fuzz(dev, i, abs->fuzz);
+ input_abs_set_flat(dev, i, abs->flat);
+ input_abs_set_res(dev, i, abs->resolution);
}
/* check if absmin/absmax/absfuzz/absflat are filled as
@@ -413,7 +405,7 @@ static int uinput_setup_device(struct uinput_device *udev,
if (test_bit(EV_ABS, dev->evbit)) {
retval = uinput_validate_absbits(dev);
if (retval < 0)
- goto exit;
+ return retval;
if (test_bit(ABS_MT_SLOT, dev->absbit)) {
int nslot = input_abs_get_max(dev, ABS_MT_SLOT) + 1;
input_mt_init_slots(dev, nslot, 0);
@@ -423,11 +415,84 @@ static int uinput_setup_device(struct uinput_device *udev,
}
udev->state = UIST_SETUP_COMPLETE;
- retval = count;
+ return 0;
+}
+
+static int uinput_setup_device1(struct uinput_device *udev,
+ const char __user *buffer, size_t count)
+{
+ struct uinput_user_dev *user_dev;
+ struct uinput_user_dev2 *user_dev2;
+ int i;
+ int retval;
+
+ if (count != sizeof(struct uinput_user_dev))
+ return -EINVAL;
+
+ user_dev = memdup_user(buffer, sizeof(struct uinput_user_dev));
+ if (IS_ERR(user_dev))
+ return PTR_ERR(user_dev);
+
+ user_dev2 = kmalloc(sizeof(*user_dev2), GFP_KERNEL);
+ if (!user_dev2) {
+ kfree(user_dev);
+ return -ENOMEM;
+ }
+
+ user_dev2->version = UINPUT_VERSION;
+ memcpy(user_dev2->name, user_dev->name, UINPUT_MAX_NAME_SIZE);
+ memcpy(&user_dev2->id, &user_dev->id, sizeof(struct input_id));
+ user_dev2->ff_effects_max = user_dev->ff_effects_max;
+
+ for (i = 0; i < ABS_CNT; ++i) {
+ user_dev2->abs[i].value = 0;
+ user_dev2->abs[i].maximum = user_dev->absmax[i];
+ user_dev2->abs[i].minimum = user_dev->absmin[i];
+ user_dev2->abs[i].fuzz = user_dev->absfuzz[i];
+ user_dev2->abs[i].flat = user_dev->absflat[i];
+ user_dev2->abs[i].resolution = 0;
+ }
+
+ retval = uinput_setup_device(udev, user_dev2, ABS_CNT);
- exit:
kfree(user_dev);
- return retval;
+ kfree(user_dev2);
+
+ return retval ? retval : count;
+}
+
+static int uinput_setup_device2(struct uinput_device *udev,
+ const char __user *buffer, size_t count)
+{
+ struct uinput_user_dev2 *user_dev2;
+ int retval;
+ size_t off, abscnt, max;
+
+ /* The first revision of "uinput_user_dev2" is bigger than
+ * "uinput_user_dev" and growing. Disallow any smaller payloads. */
+ if (count <= sizeof(struct uinput_user_dev))
+ return -EINVAL;
+
+ /* rough check to avoid huge kernel space allocations */
+ max = ABS_CNT * sizeof(*user_dev2->abs) + sizeof(*user_dev2);
+ if (count > max)
+ return -EINVAL;
+
+ user_dev2 = memdup_user(buffer, count);
+ if (IS_ERR(user_dev2))
+ return PTR_ERR(user_dev2);
+
+ if (user_dev2->version > UINPUT_VERSION) {
+ retval = -EINVAL;
+ } else {
+ off = offsetof(struct uinput_user_dev2, abs);
+ abscnt = (count - off) / sizeof(*user_dev2->abs);
+ retval = uinput_setup_device(udev, user_dev2, abscnt);
+ }
+
+ kfree(user_dev2);
+
+ return retval ? retval : count;
}
static ssize_t uinput_inject_events(struct uinput_device *udev,
@@ -469,9 +534,12 @@ static ssize_t uinput_write(struct file *file, const char __user *buffer,
if (retval)
return retval;
- retval = udev->state == UIST_CREATED ?
- uinput_inject_events(udev, buffer, count) :
- uinput_setup_device(udev, buffer, count);
+ if (udev->state == UIST_CREATED)
+ retval = uinput_inject_events(udev, buffer, count);
+ else if (count <= sizeof(struct uinput_user_dev))
+ retval = uinput_setup_device1(udev, buffer, count);
+ else
+ retval = uinput_setup_device2(udev, buffer, count);
mutex_unlock(&udev->mutex);
diff --git a/include/uapi/linux/uinput.h b/include/uapi/linux/uinput.h
index fe46431..c2e8710 100644
--- a/include/uapi/linux/uinput.h
+++ b/include/uapi/linux/uinput.h
@@ -134,4 +134,13 @@ struct uinput_user_dev {
__s32 absfuzz[ABS_CNT];
__s32 absflat[ABS_CNT];
};
+
+struct uinput_user_dev2 {
+ __u8 version;
+ char name[UINPUT_MAX_NAME_SIZE];
+ struct input_id id;
+ __u32 ff_effects_max;
+ struct input_absinfo abs[ABS_CNT];
+};
+
#endif /* _UAPI__UINPUT_H_ */
--
1.8.5.1
^ permalink raw reply related
* Disable i8042 check on Apple machines
From: Josh Boyer @ 2013-12-17 15:25 UTC (permalink / raw)
To: Bastien Nocera, Dmitry Torokhov; +Cc: linux-input
Hi Bastien and Dmitry,
I was going through some of the patches we're carrying in Fedora and
came across this one that Bastien submitted a long time ago:
http://lkml.indiana.edu/hypermail/linux/kernel/1005.0/00938.html
It works and we're still carrying it, but Dmitry said it needed to be
done differently in i8042-x86ia64io.h. Would that just mean adding a
DMI for Apple machines to the i8042_dmi_nopnp_table struct? Or
exactly what should be done here?
I'd like to get this upstream.
josh
^ permalink raw reply
* Re: kernel panic on gpio-keys
From: Paul Cercueil @ 2013-12-17 15:17 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
In-Reply-To: <20131214093900.GA10799@core.coreip.homeip.net>
On 14/12/2013 10:39, Dmitry Torokhov wrote:
> On Wed, Dec 11, 2013 at 08:17:29PM +0100, Paul Cercueil wrote:
>> Hi there,
>>
>> I am trying to use the gpio-keys driver to inject joystick events.
>> There seems to be some basic support of it, looking at <linux/gpio_keys.h>.
>>
>> However, registering the following will trigger a kernel panic in
>> the kernel:
>>
>> static struct gpio_keys_button my_buttons[] {
>> {
>> .gpio = GPIO_FOO,
>> .type = EV_ABS,
>> .code = ABS_HAT0X,
>> .value = 1,
>> },
>> };
>>
>> (tested on kernel 3.12).
>>
>> I don't know well the input subsystem, so I have no idea of what is
>> going wrong. Could anybody try to at least reproduce the issue?
>
> It woudl be helpful if you poster the stack trace from panic so we'd
> have an idea where the fault happens.
>
> Thanks.
>
Here is the crash log I get: http://pastebin.com/FzTTGxsR
(I did put it on pastebin because it's huge, 200+ lines).
The first OOPS happen as soon as the GPIO button is pressed; the other
ones seem to happen recursively. I included only a part of the log I
get, as the OOPSes continue to flow until the watchdog kicks in.
^ permalink raw reply
* Re: [PATCH -next] Input: zforce - fix error return code in zforce_start()
From: Heiko Stübner @ 2013-12-17 15:06 UTC (permalink / raw)
To: Wei Yongjun; +Cc: dmitry.torokhov, rydberg, yongjun_wei, linux-input
In-Reply-To: <CAPgLHd8WBR38qd9VHfazXVTbuoJ=GPOafHzSz8d9XJ-F61a7nQ@mail.gmail.com>
Am Dienstag, 17. Dezember 2013, 04:27:16 schrieb Wei Yongjun:
> From: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
>
> The error code was not set if unable to set config, so the error
> condition wasn't reflected in the return value. Fix to return a
> negative error code from the error handling case instead of 0.
>
> Signed-off-by: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
Thanks for the catch.
Acked-by: Heiko Stuebner <heiko@sntech.de>
^ permalink raw reply
* Re: [Xen-devel] [PATCH v3 1/2] xen/pvhvm: If xen_platform_pci=0 is set don't blow up (v3).
From: Konrad Rzeszutek Wilk @ 2013-12-17 14:51 UTC (permalink / raw)
To: Fabio Fantoni
Cc: axboe, stefano.stabellini, ian.campbell, xen-devel, linux-kernel,
boris.ostrovsky, david.vrabel, leosilva, ashley, peterhuewe, mail,
tpmdd, dmitry.torokhov, bhelgaas, plagnioj, tomi.valkeinen,
tpmdd-devel, linux-input, netdev, linux-pci, linux-fbdev
In-Reply-To: <52B01F67.1030108@m2r.biz>
On Tue, Dec 17, 2013 at 10:54:47AM +0100, Fabio Fantoni wrote:
> Il 16/12/2013 16:04, Konrad Rzeszutek Wilk ha scritto:
> >The user has the option of disabling the platform driver:
> >00:02.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)
> >
> >which is used to unplug the emulated drivers (IDE, Realtek 8169, etc)
> >and allow the PV drivers to take over. If the user wishes
> >to disable that they can set:
> >
> > xen_platform_pci=0
> > (in the guest config file)
> >
> >or
> > xen_emul_unplug=never
> > (on the Linux command line)
> >
> >except it does not work properly. The PV drivers still try to
> >load and since the Xen platform driver is not run - and it
> >has not initialized the grant tables, most of the PV drivers
> >stumble upon:
> >
> >input: Xen Virtual Keyboard as /devices/virtual/input/input5
> >input: Xen Virtual Pointer as /devices/virtual/input/input6M
> >------------[ cut here ]------------
> >kernel BUG at /home/konrad/ssd/konrad/linux/drivers/xen/grant-table.c:1206!
> >invalid opcode: 0000 [#1] SMP
> >Modules linked in: xen_kbdfront(+) xenfs xen_privcmd
> >CPU: 6 PID: 1389 Comm: modprobe Not tainted 3.13.0-rc1upstream-00021-ga6c892b-dirty #1
> >Hardware name: Xen HVM domU, BIOS 4.4-unstable 11/26/2013
> >RIP: 0010:[<ffffffff813ddc40>] [<ffffffff813ddc40>] get_free_entries+0x2e0/0x300
> >Call Trace:
> > [<ffffffff8150d9a3>] ? evdev_connect+0x1e3/0x240
> > [<ffffffff813ddd0e>] gnttab_grant_foreign_access+0x2e/0x70
> > [<ffffffffa0010081>] xenkbd_connect_backend+0x41/0x290 [xen_kbdfront]
> > [<ffffffffa0010a12>] xenkbd_probe+0x2f2/0x324 [xen_kbdfront]
> > [<ffffffff813e5757>] xenbus_dev_probe+0x77/0x130
> > [<ffffffff813e7217>] xenbus_frontend_dev_probe+0x47/0x50
> > [<ffffffff8145e9a9>] driver_probe_device+0x89/0x230
> > [<ffffffff8145ebeb>] __driver_attach+0x9b/0xa0
> > [<ffffffff8145eb50>] ? driver_probe_device+0x230/0x230
> > [<ffffffff8145eb50>] ? driver_probe_device+0x230/0x230
> > [<ffffffff8145cf1c>] bus_for_each_dev+0x8c/0xb0
> > [<ffffffff8145e7d9>] driver_attach+0x19/0x20
> > [<ffffffff8145e260>] bus_add_driver+0x1a0/0x220
> > [<ffffffff8145f1ff>] driver_register+0x5f/0xf0
> > [<ffffffff813e55c5>] xenbus_register_driver_common+0x15/0x20
> > [<ffffffff813e76b3>] xenbus_register_frontend+0x23/0x40
> > [<ffffffffa0015000>] ? 0xffffffffa0014fff
> > [<ffffffffa001502b>] xenkbd_init+0x2b/0x1000 [xen_kbdfront]
> > [<ffffffff81002049>] do_one_initcall+0x49/0x170
> >
> >.. snip..
> >
> >which is hardly nice. This patch fixes this by having each
> >PV driver check for:
> > - if running in PV, then it is fine to execute (as that is their
> > native environment).
> > - if running in HVM, check if user wanted 'xen_emul_unplug=never',
> > in which case bail out and don't load any PV drivers.
> > - if running in HVM, and if PCI device 5853:0001 (xen_platform_pci)
> > does not exist, then bail out and not load PV drivers.
> > - (v2) if running in HVM, and if the user wanted 'xen_emul_unplug=disks',
> > then bail out for all PV devices _except_ the block one.
> > Ditto for the network one ('nics').
> > - (v2) if running in HVM, and if the user wanted 'xen_emul_unplug=unnecessary'
> > then load block PV driver, and also setup the legacy IDE paths.
> > In (v3) make it actually load PV drivers.
> >
> >Reported-by: Sander Eikelenboom <linux@eikelenboom.it
> >Reported-by: Anthony PERARD <anthony.perard@citrix.com>
> >Reported-by: Fabio Fantoni <fabio.fantoni@m2r.biz>
> >Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
> >[v2: Add extra logic to handle the myrid ways 'xen_emul_unplug'
> >can be used per Ian and Stefano suggestion]
> >[v3: Make the unnecessary case work properly]
> >Reviewed-by: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
>
> I tested this patch with all possible cases that I know, no crash or
> calltrace found.
>
> I don't understand the utility of 'xen_emul_unplug=unnecessary' but
> probably it is working correctly, it shows both pv and not pv for
> blocks and nics.
Great!
>
> About 'xen_emul_unplug=disks' and 'xen_emul_unplug=nics' probably
> there is something wrong.
> With 'xen_emul_unplug=nics' it shows pv nic (this should be correct)
> and about disks it shows the same disk twice, as pv and not pv (xvda
> and sda) with different device number.
Good.
> With 'xen_emul_unplug=disks' it shows pv block (this should be
> correct) and it seems to show the pv nic too (this should be wrong).
Correct. You should see only the PV disk and all other PV devices
should not function. Let me dig in this and see whether there is
another bug. Thank you!
>
> Thanks for any reply.
>
> >---
> > arch/x86/xen/platform-pci-unplug.c | 74 ++++++++++++++++++++++++++++++
> > drivers/block/xen-blkfront.c | 4 +-
> > drivers/char/tpm/xen-tpmfront.c | 4 ++
> > drivers/input/misc/xen-kbdfront.c | 4 ++
> > drivers/net/xen-netfront.c | 2 +-
> > drivers/pci/xen-pcifront.c | 4 ++
> > drivers/video/xen-fbfront.c | 4 ++
> > drivers/xen/xenbus/xenbus_probe_frontend.c | 2 +-
> > include/xen/platform_pci.h | 23 ++++++++++
> > 9 files changed, 117 insertions(+), 4 deletions(-)
> >
> >diff --git a/arch/x86/xen/platform-pci-unplug.c b/arch/x86/xen/platform-pci-unplug.c
> >index 0a78524..ab84ac1 100644
> >--- a/arch/x86/xen/platform-pci-unplug.c
> >+++ b/arch/x86/xen/platform-pci-unplug.c
> >@@ -69,6 +69,80 @@ static int check_platform_magic(void)
> > return 0;
> > }
> >+bool xen_has_pv_devices()
> >+{
> >+ if (!xen_domain())
> >+ return false;
> >+
> >+ /* PV domains always have them. */
> >+ if (xen_pv_domain())
> >+ return true;
> >+
> >+ /* And user has xen_platform_pci=0 set in guest config as
> >+ * driver did not modify the value. */
> >+ if (xen_platform_pci_unplug == 0)
> >+ return false;
> >+
> >+ if (xen_platform_pci_unplug & XEN_UNPLUG_NEVER)
> >+ return false;
> >+
> >+ if (xen_platform_pci_unplug & XEN_UNPLUG_ALL)
> >+ return true;
> >+
> >+ /* This is an odd one - we are going to run legacy
> >+ * and PV drivers at the same time. */
> >+ if (xen_platform_pci_unplug & XEN_UNPLUG_UNNECESSARY)
> >+ return true;
> >+
> >+ /* And the caller has to follow with xen_pv_{disk,nic}_devices
> >+ * to be certain which driver can load. */
> >+ return false;
> >+}
> >+EXPORT_SYMBOL_GPL(xen_has_pv_devices);
> >+
> >+static bool __xen_has_pv_device(int state)
> >+{
> >+ /* HVM domains might or might not */
> >+ if (xen_hvm_domain() && (xen_platform_pci_unplug & state))
> >+ return true;
> >+
> >+ return xen_has_pv_devices();
> >+}
> >+
> >+bool xen_has_pv_nic_devices(void)
> >+{
> >+ return __xen_has_pv_device(XEN_UNPLUG_ALL_NICS | XEN_UNPLUG_ALL);
> >+}
> >+EXPORT_SYMBOL_GPL(xen_has_pv_nic_devices);
> >+
> >+bool xen_has_pv_disk_devices(void)
> >+{
> >+ return __xen_has_pv_device(XEN_UNPLUG_ALL_IDE_DISKS |
> >+ XEN_UNPLUG_AUX_IDE_DISKS | XEN_UNPLUG_ALL);
> >+}
> >+EXPORT_SYMBOL_GPL(xen_has_pv_disk_devices);
> >+
> >+/*
> >+ * This one is odd - it determines whether you want to run PV _and_
> >+ * legacy (IDE) drivers together. This combination is only possible
> >+ * under HVM.
> >+ */
> >+bool xen_has_pv_and_legacy_disk_devices(void)
> >+{
> >+ if (!xen_domain())
> >+ return false;
> >+
> >+ /* N.B. This is only ever used in HVM mode */
> >+ if (xen_pv_domain())
> >+ return false;
> >+
> >+ if (xen_platform_pci_unplug & XEN_UNPLUG_UNNECESSARY)
> >+ return true;
> >+
> >+ return false;
> >+}
> >+EXPORT_SYMBOL_GPL(xen_has_pv_and_legacy_disk_devices);
> >+
> > void xen_unplug_emulated_devices(void)
> > {
> > int r;
> >diff --git a/drivers/block/xen-blkfront.c b/drivers/block/xen-blkfront.c
> >index a4660bb..ed88b3c 100644
> >--- a/drivers/block/xen-blkfront.c
> >+++ b/drivers/block/xen-blkfront.c
> >@@ -1278,7 +1278,7 @@ static int blkfront_probe(struct xenbus_device *dev,
> > char *type;
> > int len;
> > /* no unplug has been done: do not hook devices != xen vbds */
> >- if (xen_platform_pci_unplug & XEN_UNPLUG_UNNECESSARY) {
> >+ if (xen_has_pv_and_legacy_disk_devices()) {
> > int major;
> > if (!VDEV_IS_EXTENDED(vdevice))
> >@@ -2022,7 +2022,7 @@ static int __init xlblk_init(void)
> > if (!xen_domain())
> > return -ENODEV;
> >- if (xen_hvm_domain() && !xen_platform_pci_unplug)
> >+ if (!xen_has_pv_disk_devices())
> > return -ENODEV;
> > if (register_blkdev(XENVBD_MAJOR, DEV_NAME)) {
> >diff --git a/drivers/char/tpm/xen-tpmfront.c b/drivers/char/tpm/xen-tpmfront.c
> >index 06189e5..9c2cbd1 100644
> >--- a/drivers/char/tpm/xen-tpmfront.c
> >+++ b/drivers/char/tpm/xen-tpmfront.c
> >@@ -16,6 +16,7 @@
> > #include <xen/xenbus.h>
> > #include <xen/page.h>
> > #include "tpm.h"
> >+#include <xen/platform_pci.h>
> > struct tpm_private {
> > struct tpm_chip *chip;
> >@@ -422,6 +423,9 @@ static int __init xen_tpmfront_init(void)
> > if (!xen_domain())
> > return -ENODEV;
> >+ if (!xen_has_pv_devices())
> >+ return -ENODEV;
> >+
> > return xenbus_register_frontend(&tpmfront_driver);
> > }
> > module_init(xen_tpmfront_init);
> >diff --git a/drivers/input/misc/xen-kbdfront.c b/drivers/input/misc/xen-kbdfront.c
> >index e21c181..fbfdc10 100644
> >--- a/drivers/input/misc/xen-kbdfront.c
> >+++ b/drivers/input/misc/xen-kbdfront.c
> >@@ -29,6 +29,7 @@
> > #include <xen/interface/io/fbif.h>
> > #include <xen/interface/io/kbdif.h>
> > #include <xen/xenbus.h>
> >+#include <xen/platform_pci.h>
> > struct xenkbd_info {
> > struct input_dev *kbd;
> >@@ -380,6 +381,9 @@ static int __init xenkbd_init(void)
> > if (xen_initial_domain())
> > return -ENODEV;
> >+ if (!xen_has_pv_devices())
> >+ return -ENODEV;
> >+
> > return xenbus_register_frontend(&xenkbd_driver);
> > }
> >diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c
> >index 36808bf..eea2392 100644
> >--- a/drivers/net/xen-netfront.c
> >+++ b/drivers/net/xen-netfront.c
> >@@ -2106,7 +2106,7 @@ static int __init netif_init(void)
> > if (!xen_domain())
> > return -ENODEV;
> >- if (xen_hvm_domain() && !xen_platform_pci_unplug)
> >+ if (!xen_has_pv_nic_devices())
> > return -ENODEV;
> > pr_info("Initialising Xen virtual ethernet driver\n");
> >diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c
> >index f7197a7..eae7cd9 100644
> >--- a/drivers/pci/xen-pcifront.c
> >+++ b/drivers/pci/xen-pcifront.c
> >@@ -20,6 +20,7 @@
> > #include <linux/workqueue.h>
> > #include <linux/bitops.h>
> > #include <linux/time.h>
> >+#include <xen/platform_pci.h>
> > #include <asm/xen/swiotlb-xen.h>
> > #define INVALID_GRANT_REF (0)
> >@@ -1138,6 +1139,9 @@ static int __init pcifront_init(void)
> > if (!xen_pv_domain() || xen_initial_domain())
> > return -ENODEV;
> >+ if (!xen_has_pv_devices())
> >+ return -ENODEV;
> >+
> > pci_frontend_registrar(1 /* enable */);
> > return xenbus_register_frontend(&xenpci_driver);
> >diff --git a/drivers/video/xen-fbfront.c b/drivers/video/xen-fbfront.c
> >index cd005c2..4b2d3ab 100644
> >--- a/drivers/video/xen-fbfront.c
> >+++ b/drivers/video/xen-fbfront.c
> >@@ -35,6 +35,7 @@
> > #include <xen/interface/io/fbif.h>
> > #include <xen/interface/io/protocols.h>
> > #include <xen/xenbus.h>
> >+#include <xen/platform_pci.h>
> > struct xenfb_info {
> > unsigned char *fb;
> >@@ -699,6 +700,9 @@ static int __init xenfb_init(void)
> > if (xen_initial_domain())
> > return -ENODEV;
> >+ if (!xen_has_pv_devices())
> >+ return -ENODEV;
> >+
> > return xenbus_register_frontend(&xenfb_driver);
> > }
> >diff --git a/drivers/xen/xenbus/xenbus_probe_frontend.c b/drivers/xen/xenbus/xenbus_probe_frontend.c
> >index 34b20bf..6244f9c 100644
> >--- a/drivers/xen/xenbus/xenbus_probe_frontend.c
> >+++ b/drivers/xen/xenbus/xenbus_probe_frontend.c
> >@@ -496,7 +496,7 @@ subsys_initcall(xenbus_probe_frontend_init);
> > #ifndef MODULE
> > static int __init boot_wait_for_devices(void)
> > {
> >- if (xen_hvm_domain() && !xen_platform_pci_unplug)
> >+ if (!xen_has_pv_devices())
> > return -ENODEV;
> > ready_to_wait_for_devices = 1;
> >diff --git a/include/xen/platform_pci.h b/include/xen/platform_pci.h
> >index 438c256..b49eeab 100644
> >--- a/include/xen/platform_pci.h
> >+++ b/include/xen/platform_pci.h
> >@@ -48,4 +48,27 @@ static inline int xen_must_unplug_disks(void) {
> > extern int xen_platform_pci_unplug;
> >+#if defined(CONFIG_XEN_PVHVM)
> >+extern bool xen_has_pv_devices(void);
> >+extern bool xen_has_pv_disk_devices(void);
> >+extern bool xen_has_pv_nic_devices(void);
> >+extern bool xen_has_pv_and_legacy_disk_devices(void);
> >+#else
> >+static inline bool xen_has_pv_devices(void)
> >+{
> >+ return IS_ENABLED(CONFIG_XEN);
> >+}
> >+static inline bool xen_has_pv_disk_devices(void)
> >+{
> >+ return IS_ENABLED(CONFIG_XEN);
> >+}
> >+static inline bool xen_has_pv_nic_devices(void)
> >+{
> >+ return IS_ENABLED(CONFIG_XEN);
> >+}
> >+static inline bool xen_has_pv_and_legacy_disk_devices(void)
> >+{
> >+ return false;
> >+}
> >+#endif
> > #endif /* _XEN_PLATFORM_PCI_H */
>
^ permalink raw reply
* Re: [PATCH] HID: debug: add labels for some new buttons
From: Jiri Kosina @ 2013-12-17 12:56 UTC (permalink / raw)
To: Antonio Ospite; +Cc: linux-input
In-Reply-To: <1387281168-13856-1-git-send-email-ospite@studenti.unina.it>
On Tue, 17 Dec 2013, Antonio Ospite wrote:
> Signed-off-by: Antonio Ospite <ospite@studenti.unina.it>
I have added some changelog and applied, thanks.
> ---
>
> I also noticed that BTN_GEAR_DOWN is named "WheelBtn", should this be fixed?
#define BTN_WHEEL 0x150
#define BTN_GEAR_DOWN 0x150
#define BTN_GEAR_UP 0x151
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* [PATCH] HID: debug: add labels for some new buttons
From: Antonio Ospite @ 2013-12-17 11:52 UTC (permalink / raw)
To: linux-input; +Cc: Antonio Ospite, Jiri Kosina
Signed-off-by: Antonio Ospite <ospite@studenti.unina.it>
---
I also noticed that BTN_GEAR_DOWN is named "WheelBtn", should this be fixed?
Thanks,
Antonio
drivers/hid/hid-debug.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index 8453214..53b771d 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -768,6 +768,8 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_ALTERASE] = "AlternateErase", [KEY_CANCEL] = "Cancel",
[KEY_BRIGHTNESSDOWN] = "BrightnessDown", [KEY_BRIGHTNESSUP] = "BrightnessUp",
[KEY_MEDIA] = "Media", [KEY_UNKNOWN] = "Unknown",
+ [BTN_DPAD_UP] = "BtnDPadUp", [BTN_DPAD_DOWN] = "BtnDPadDown",
+ [BTN_DPAD_LEFT] = "BtnDPadLeft", [BTN_DPAD_RIGHT] = "BtnDPadRight",
[BTN_0] = "Btn0", [BTN_1] = "Btn1",
[BTN_2] = "Btn2", [BTN_3] = "Btn3",
[BTN_4] = "Btn4", [BTN_5] = "Btn5",
@@ -797,7 +799,8 @@ static const char *keys[KEY_MAX + 1] = {
[BTN_TOOL_MOUSE] = "ToolMouse", [BTN_TOOL_LENS] = "ToolLens",
[BTN_TOUCH] = "Touch", [BTN_STYLUS] = "Stylus",
[BTN_STYLUS2] = "Stylus2", [BTN_TOOL_DOUBLETAP] = "ToolDoubleTap",
- [BTN_TOOL_TRIPLETAP] = "ToolTripleTap", [BTN_GEAR_DOWN] = "WheelBtn",
+ [BTN_TOOL_TRIPLETAP] = "ToolTripleTap", [BTN_TOOL_QUADTAP] = "ToolQuadrupleTap",
+ [BTN_GEAR_DOWN] = "WheelBtn",
[BTN_GEAR_UP] = "Gear up", [KEY_OK] = "Ok",
[KEY_SELECT] = "Select", [KEY_GOTO] = "Goto",
[KEY_CLEAR] = "Clear", [KEY_POWER2] = "Power2",
--
1.8.5.1
^ permalink raw reply related
* [PATCHv2] Input: fix typos in Documentation/input/gamepad.txt
From: Antonio Ospite @ 2013-12-17 11:33 UTC (permalink / raw)
To: linux-input
Cc: Antonio Ospite, Randy Dunlap, David Herrmann, Dmitry Torokhov,
Jiri Kosina
In-Reply-To: <52AF51EA.8090400@infradead.org>
Fix some typos and while at it also use "PS" as the name for the central
"HOME" button on Sony controllers, this is how Sony itself calls it.
Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: Antonio Ospite <ospite@studenti.unina.it>
---
Changes since v1:
- Fix one of the fixes: use "advise" (verb) in place of "advice" (noun)
Thanks Randy.
Documentation/input/gamepad.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Documentation/input/gamepad.txt b/Documentation/input/gamepad.txt
index 31bb6a4..3f6d8a5 100644
--- a/Documentation/input/gamepad.txt
+++ b/Documentation/input/gamepad.txt
@@ -68,7 +68,7 @@ features that you need, first. How each feature is mapped is described below.
Legacy drivers often don't comply to these rules. As we cannot change them
for backwards-compatibility reasons, you need to provide fixup mappings in
user-space yourself. Some of them might also provide module-options that
-change the mappings so you can adivce users to set these.
+change the mappings so you can advise users to set these.
All new gamepads are supposed to comply with this mapping. Please report any
bugs, if they don't.
@@ -150,10 +150,10 @@ Menu-Pad:
BTN_START
Many pads also have a third button which is branded or has a special symbol
and meaning. Such buttons are mapped as BTN_MODE. Examples are the Nintendo
- "HOME" button, the XBox "X"-button or Sony "P" button.
+ "HOME" button, the XBox "X"-button or Sony "PS" button.
Rumble:
- Rumble is adverticed as FF_RUMBLE.
+ Rumble is advertised as FF_RUMBLE.
----------------------------------------------------------------------------
Written 2013 by David Herrmann <dh.herrmann@gmail.com>
--
1.8.5.1
^ permalink raw reply related
* LG 23ET83V-W multitouch monitor
From: Randolf Rotta @ 2013-12-17 10:35 UTC (permalink / raw)
To: linux-input
Hello,
I got a LG 23ET83V multitouch monitor and needed driver support for
that. I got it working with
options usbhid quirks=0x0457:0x1030:0x20000000
in /etc/modprobe.d/usbhid.conf
and
echo 3 0457 1030 18
/sys/module/hid_multitouch/drivers/hid\:hid-multitouch/new_id
Just saw, that there is a patch for this monitor now from Emanuel Krenz
and Jiri Kosina. So maybe, my experiments do not provide new information.
Randolf
^ permalink raw reply
* Re: [Xen-devel] [PATCH v3 1/2] xen/pvhvm: If xen_platform_pci=0 is set don't blow up (v3).
From: Fabio Fantoni @ 2013-12-17 9:54 UTC (permalink / raw)
To: Konrad Rzeszutek Wilk, axboe, stefano.stabellini, ian.campbell,
xen-devel, linux-kernel, boris.ostrovsky, david.vrabel, leosilva,
ashley, peterhuewe, mail, tpmdd, dmitry.torokhov, bhelgaas,
plagnioj, tomi.valkeinen, tpmdd-devel, linux-input, netdev,
linux-pci, linux-fbdev
In-Reply-To: <1387206250-13963-2-git-send-email-konrad.wilk@oracle.com>
Il 16/12/2013 16:04, Konrad Rzeszutek Wilk ha scritto:
> The user has the option of disabling the platform driver:
> 00:02.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)
>
> which is used to unplug the emulated drivers (IDE, Realtek 8169, etc)
> and allow the PV drivers to take over. If the user wishes
> to disable that they can set:
>
> xen_platform_pci=0
> (in the guest config file)
>
> or
> xen_emul_unplug=never
> (on the Linux command line)
>
> except it does not work properly. The PV drivers still try to
> load and since the Xen platform driver is not run - and it
> has not initialized the grant tables, most of the PV drivers
> stumble upon:
>
> input: Xen Virtual Keyboard as /devices/virtual/input/input5
> input: Xen Virtual Pointer as /devices/virtual/input/input6M
> ------------[ cut here ]------------
> kernel BUG at /home/konrad/ssd/konrad/linux/drivers/xen/grant-table.c:1206!
> invalid opcode: 0000 [#1] SMP
> Modules linked in: xen_kbdfront(+) xenfs xen_privcmd
> CPU: 6 PID: 1389 Comm: modprobe Not tainted 3.13.0-rc1upstream-00021-ga6c892b-dirty #1
> Hardware name: Xen HVM domU, BIOS 4.4-unstable 11/26/2013
> RIP: 0010:[<ffffffff813ddc40>] [<ffffffff813ddc40>] get_free_entries+0x2e0/0x300
> Call Trace:
> [<ffffffff8150d9a3>] ? evdev_connect+0x1e3/0x240
> [<ffffffff813ddd0e>] gnttab_grant_foreign_access+0x2e/0x70
> [<ffffffffa0010081>] xenkbd_connect_backend+0x41/0x290 [xen_kbdfront]
> [<ffffffffa0010a12>] xenkbd_probe+0x2f2/0x324 [xen_kbdfront]
> [<ffffffff813e5757>] xenbus_dev_probe+0x77/0x130
> [<ffffffff813e7217>] xenbus_frontend_dev_probe+0x47/0x50
> [<ffffffff8145e9a9>] driver_probe_device+0x89/0x230
> [<ffffffff8145ebeb>] __driver_attach+0x9b/0xa0
> [<ffffffff8145eb50>] ? driver_probe_device+0x230/0x230
> [<ffffffff8145eb50>] ? driver_probe_device+0x230/0x230
> [<ffffffff8145cf1c>] bus_for_each_dev+0x8c/0xb0
> [<ffffffff8145e7d9>] driver_attach+0x19/0x20
> [<ffffffff8145e260>] bus_add_driver+0x1a0/0x220
> [<ffffffff8145f1ff>] driver_register+0x5f/0xf0
> [<ffffffff813e55c5>] xenbus_register_driver_common+0x15/0x20
> [<ffffffff813e76b3>] xenbus_register_frontend+0x23/0x40
> [<ffffffffa0015000>] ? 0xffffffffa0014fff
> [<ffffffffa001502b>] xenkbd_init+0x2b/0x1000 [xen_kbdfront]
> [<ffffffff81002049>] do_one_initcall+0x49/0x170
>
> .. snip..
>
> which is hardly nice. This patch fixes this by having each
> PV driver check for:
> - if running in PV, then it is fine to execute (as that is their
> native environment).
> - if running in HVM, check if user wanted 'xen_emul_unplug=never',
> in which case bail out and don't load any PV drivers.
> - if running in HVM, and if PCI device 5853:0001 (xen_platform_pci)
> does not exist, then bail out and not load PV drivers.
> - (v2) if running in HVM, and if the user wanted 'xen_emul_unplug=disks',
> then bail out for all PV devices _except_ the block one.
> Ditto for the network one ('nics').
> - (v2) if running in HVM, and if the user wanted 'xen_emul_unplug=unnecessary'
> then load block PV driver, and also setup the legacy IDE paths.
> In (v3) make it actually load PV drivers.
>
> Reported-by: Sander Eikelenboom <linux@eikelenboom.it
> Reported-by: Anthony PERARD <anthony.perard@citrix.com>
> Reported-by: Fabio Fantoni <fabio.fantoni@m2r.biz>
> Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
> [v2: Add extra logic to handle the myrid ways 'xen_emul_unplug'
> can be used per Ian and Stefano suggestion]
> [v3: Make the unnecessary case work properly]
> Reviewed-by: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
I tested this patch with all possible cases that I know, no crash or
calltrace found.
I don't understand the utility of 'xen_emul_unplug=unnecessary' but
probably it is working correctly, it shows both pv and not pv for blocks
and nics.
About 'xen_emul_unplug=disks' and 'xen_emul_unplug=nics' probably there
is something wrong.
With 'xen_emul_unplug=nics' it shows pv nic (this should be correct) and
about disks it shows the same disk twice, as pv and not pv (xvda and
sda) with different device number.
With 'xen_emul_unplug=disks' it shows pv block (this should be correct)
and it seems to show the pv nic too (this should be wrong).
Thanks for any reply.
> ---
> arch/x86/xen/platform-pci-unplug.c | 74 ++++++++++++++++++++++++++++++
> drivers/block/xen-blkfront.c | 4 +-
> drivers/char/tpm/xen-tpmfront.c | 4 ++
> drivers/input/misc/xen-kbdfront.c | 4 ++
> drivers/net/xen-netfront.c | 2 +-
> drivers/pci/xen-pcifront.c | 4 ++
> drivers/video/xen-fbfront.c | 4 ++
> drivers/xen/xenbus/xenbus_probe_frontend.c | 2 +-
> include/xen/platform_pci.h | 23 ++++++++++
> 9 files changed, 117 insertions(+), 4 deletions(-)
>
> diff --git a/arch/x86/xen/platform-pci-unplug.c b/arch/x86/xen/platform-pci-unplug.c
> index 0a78524..ab84ac1 100644
> --- a/arch/x86/xen/platform-pci-unplug.c
> +++ b/arch/x86/xen/platform-pci-unplug.c
> @@ -69,6 +69,80 @@ static int check_platform_magic(void)
> return 0;
> }
>
> +bool xen_has_pv_devices()
> +{
> + if (!xen_domain())
> + return false;
> +
> + /* PV domains always have them. */
> + if (xen_pv_domain())
> + return true;
> +
> + /* And user has xen_platform_pci=0 set in guest config as
> + * driver did not modify the value. */
> + if (xen_platform_pci_unplug == 0)
> + return false;
> +
> + if (xen_platform_pci_unplug & XEN_UNPLUG_NEVER)
> + return false;
> +
> + if (xen_platform_pci_unplug & XEN_UNPLUG_ALL)
> + return true;
> +
> + /* This is an odd one - we are going to run legacy
> + * and PV drivers at the same time. */
> + if (xen_platform_pci_unplug & XEN_UNPLUG_UNNECESSARY)
> + return true;
> +
> + /* And the caller has to follow with xen_pv_{disk,nic}_devices
> + * to be certain which driver can load. */
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(xen_has_pv_devices);
> +
> +static bool __xen_has_pv_device(int state)
> +{
> + /* HVM domains might or might not */
> + if (xen_hvm_domain() && (xen_platform_pci_unplug & state))
> + return true;
> +
> + return xen_has_pv_devices();
> +}
> +
> +bool xen_has_pv_nic_devices(void)
> +{
> + return __xen_has_pv_device(XEN_UNPLUG_ALL_NICS | XEN_UNPLUG_ALL);
> +}
> +EXPORT_SYMBOL_GPL(xen_has_pv_nic_devices);
> +
> +bool xen_has_pv_disk_devices(void)
> +{
> + return __xen_has_pv_device(XEN_UNPLUG_ALL_IDE_DISKS |
> + XEN_UNPLUG_AUX_IDE_DISKS | XEN_UNPLUG_ALL);
> +}
> +EXPORT_SYMBOL_GPL(xen_has_pv_disk_devices);
> +
> +/*
> + * This one is odd - it determines whether you want to run PV _and_
> + * legacy (IDE) drivers together. This combination is only possible
> + * under HVM.
> + */
> +bool xen_has_pv_and_legacy_disk_devices(void)
> +{
> + if (!xen_domain())
> + return false;
> +
> + /* N.B. This is only ever used in HVM mode */
> + if (xen_pv_domain())
> + return false;
> +
> + if (xen_platform_pci_unplug & XEN_UNPLUG_UNNECESSARY)
> + return true;
> +
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(xen_has_pv_and_legacy_disk_devices);
> +
> void xen_unplug_emulated_devices(void)
> {
> int r;
> diff --git a/drivers/block/xen-blkfront.c b/drivers/block/xen-blkfront.c
> index a4660bb..ed88b3c 100644
> --- a/drivers/block/xen-blkfront.c
> +++ b/drivers/block/xen-blkfront.c
> @@ -1278,7 +1278,7 @@ static int blkfront_probe(struct xenbus_device *dev,
> char *type;
> int len;
> /* no unplug has been done: do not hook devices != xen vbds */
> - if (xen_platform_pci_unplug & XEN_UNPLUG_UNNECESSARY) {
> + if (xen_has_pv_and_legacy_disk_devices()) {
> int major;
>
> if (!VDEV_IS_EXTENDED(vdevice))
> @@ -2022,7 +2022,7 @@ static int __init xlblk_init(void)
> if (!xen_domain())
> return -ENODEV;
>
> - if (xen_hvm_domain() && !xen_platform_pci_unplug)
> + if (!xen_has_pv_disk_devices())
> return -ENODEV;
>
> if (register_blkdev(XENVBD_MAJOR, DEV_NAME)) {
> diff --git a/drivers/char/tpm/xen-tpmfront.c b/drivers/char/tpm/xen-tpmfront.c
> index 06189e5..9c2cbd1 100644
> --- a/drivers/char/tpm/xen-tpmfront.c
> +++ b/drivers/char/tpm/xen-tpmfront.c
> @@ -16,6 +16,7 @@
> #include <xen/xenbus.h>
> #include <xen/page.h>
> #include "tpm.h"
> +#include <xen/platform_pci.h>
>
> struct tpm_private {
> struct tpm_chip *chip;
> @@ -422,6 +423,9 @@ static int __init xen_tpmfront_init(void)
> if (!xen_domain())
> return -ENODEV;
>
> + if (!xen_has_pv_devices())
> + return -ENODEV;
> +
> return xenbus_register_frontend(&tpmfront_driver);
> }
> module_init(xen_tpmfront_init);
> diff --git a/drivers/input/misc/xen-kbdfront.c b/drivers/input/misc/xen-kbdfront.c
> index e21c181..fbfdc10 100644
> --- a/drivers/input/misc/xen-kbdfront.c
> +++ b/drivers/input/misc/xen-kbdfront.c
> @@ -29,6 +29,7 @@
> #include <xen/interface/io/fbif.h>
> #include <xen/interface/io/kbdif.h>
> #include <xen/xenbus.h>
> +#include <xen/platform_pci.h>
>
> struct xenkbd_info {
> struct input_dev *kbd;
> @@ -380,6 +381,9 @@ static int __init xenkbd_init(void)
> if (xen_initial_domain())
> return -ENODEV;
>
> + if (!xen_has_pv_devices())
> + return -ENODEV;
> +
> return xenbus_register_frontend(&xenkbd_driver);
> }
>
> diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c
> index 36808bf..eea2392 100644
> --- a/drivers/net/xen-netfront.c
> +++ b/drivers/net/xen-netfront.c
> @@ -2106,7 +2106,7 @@ static int __init netif_init(void)
> if (!xen_domain())
> return -ENODEV;
>
> - if (xen_hvm_domain() && !xen_platform_pci_unplug)
> + if (!xen_has_pv_nic_devices())
> return -ENODEV;
>
> pr_info("Initialising Xen virtual ethernet driver\n");
> diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c
> index f7197a7..eae7cd9 100644
> --- a/drivers/pci/xen-pcifront.c
> +++ b/drivers/pci/xen-pcifront.c
> @@ -20,6 +20,7 @@
> #include <linux/workqueue.h>
> #include <linux/bitops.h>
> #include <linux/time.h>
> +#include <xen/platform_pci.h>
>
> #include <asm/xen/swiotlb-xen.h>
> #define INVALID_GRANT_REF (0)
> @@ -1138,6 +1139,9 @@ static int __init pcifront_init(void)
> if (!xen_pv_domain() || xen_initial_domain())
> return -ENODEV;
>
> + if (!xen_has_pv_devices())
> + return -ENODEV;
> +
> pci_frontend_registrar(1 /* enable */);
>
> return xenbus_register_frontend(&xenpci_driver);
> diff --git a/drivers/video/xen-fbfront.c b/drivers/video/xen-fbfront.c
> index cd005c2..4b2d3ab 100644
> --- a/drivers/video/xen-fbfront.c
> +++ b/drivers/video/xen-fbfront.c
> @@ -35,6 +35,7 @@
> #include <xen/interface/io/fbif.h>
> #include <xen/interface/io/protocols.h>
> #include <xen/xenbus.h>
> +#include <xen/platform_pci.h>
>
> struct xenfb_info {
> unsigned char *fb;
> @@ -699,6 +700,9 @@ static int __init xenfb_init(void)
> if (xen_initial_domain())
> return -ENODEV;
>
> + if (!xen_has_pv_devices())
> + return -ENODEV;
> +
> return xenbus_register_frontend(&xenfb_driver);
> }
>
> diff --git a/drivers/xen/xenbus/xenbus_probe_frontend.c b/drivers/xen/xenbus/xenbus_probe_frontend.c
> index 34b20bf..6244f9c 100644
> --- a/drivers/xen/xenbus/xenbus_probe_frontend.c
> +++ b/drivers/xen/xenbus/xenbus_probe_frontend.c
> @@ -496,7 +496,7 @@ subsys_initcall(xenbus_probe_frontend_init);
> #ifndef MODULE
> static int __init boot_wait_for_devices(void)
> {
> - if (xen_hvm_domain() && !xen_platform_pci_unplug)
> + if (!xen_has_pv_devices())
> return -ENODEV;
>
> ready_to_wait_for_devices = 1;
> diff --git a/include/xen/platform_pci.h b/include/xen/platform_pci.h
> index 438c256..b49eeab 100644
> --- a/include/xen/platform_pci.h
> +++ b/include/xen/platform_pci.h
> @@ -48,4 +48,27 @@ static inline int xen_must_unplug_disks(void) {
>
> extern int xen_platform_pci_unplug;
>
> +#if defined(CONFIG_XEN_PVHVM)
> +extern bool xen_has_pv_devices(void);
> +extern bool xen_has_pv_disk_devices(void);
> +extern bool xen_has_pv_nic_devices(void);
> +extern bool xen_has_pv_and_legacy_disk_devices(void);
> +#else
> +static inline bool xen_has_pv_devices(void)
> +{
> + return IS_ENABLED(CONFIG_XEN);
> +}
> +static inline bool xen_has_pv_disk_devices(void)
> +{
> + return IS_ENABLED(CONFIG_XEN);
> +}
> +static inline bool xen_has_pv_nic_devices(void)
> +{
> + return IS_ENABLED(CONFIG_XEN);
> +}
> +static inline bool xen_has_pv_and_legacy_disk_devices(void)
> +{
> + return false;
> +}
> +#endif
> #endif /* _XEN_PLATFORM_PCI_H */
^ permalink raw reply
* RE: [PATCH] Input: add i2c/smbus driver for elan touchpad
From: duson @ 2013-12-17 8:26 UTC (permalink / raw)
To: 'Dmitry Torokhov'
Cc: linux-input, linux-kernel, phoenix, bleung, agnescheng
In-Reply-To: <20131216210011.GC8884@core.coreip.homeip.net>
Add BLeung in mail loop.
Hi Dmitry:
Thanks for your review and suggestions.
First at all, I should say sorry to you because I had shrink and only reserved necessary basic functions from original source code and re-uploaded it before got your mail yesterday. To avoid your persecution, I pasted the newest patch and added your recommends again in this mail. Please see my responses as below and give me more recommends.
Thank you,
Duson
This driver adds support for elan i2c/smbus touchpad found on some laptops PC
Signed-off-by: Duson Lin <dusonlin@emc.com.tw>
---
drivers/input/mouse/Kconfig | 10 +
drivers/input/mouse/Makefile | 1 +
drivers/input/mouse/elan_i2c.c | 908 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 919 insertions(+)
create mode 100644 drivers/input/mouse/elan_i2c.c
diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig index effa9c5..095eccc 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -215,6 +215,16 @@ config MOUSE_CYAPA
To compile this driver as a module, choose M here: the module will be
called cyapa.
+config MOUSE_ELAN_I2C
+ tristate "ELAN I2C Touchpad support"
+ depends on I2C
+ help
+ This driver adds support for Elan I2C Trackpads.
+ Say Y here if you have a ELAN I2C Touchpad.
+
+ To compile this driver as a module, choose M here: the module will be
+ called elan_i2c.
+
config MOUSE_INPORT
tristate "InPort/MS/ATIXL busmouse"
depends on ISA
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile index c25efdb..24a12a6 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_MOUSE_APPLETOUCH) += appletouch.o
obj-$(CONFIG_MOUSE_ATARI) += atarimouse.o
obj-$(CONFIG_MOUSE_BCM5974) += bcm5974.o
obj-$(CONFIG_MOUSE_CYAPA) += cyapa.o
+obj-$(CONFIG_MOUSE_ELAN_I2C) += elan_i2c.o
obj-$(CONFIG_MOUSE_GPIO) += gpio_mouse.o
obj-$(CONFIG_MOUSE_INPORT) += inport.o
obj-$(CONFIG_MOUSE_LOGIBM) += logibm.o
diff --git a/drivers/input/mouse/elan_i2c.c b/drivers/input/mouse/elan_i2c.c new file mode 100644 index 0000000..a5a11bb
--- /dev/null
+++ b/drivers/input/mouse/elan_i2c.c
@@ -0,0 +1,908 @@
+?/*
+ * Elan I2C/SMBus Touchpad driver
+ *
+ * Copyright (c) 2013 ELAN Microelectronics Corp.
+ *
+ * Author: 林政維 (Duson Lin) <dusonlin@emc.com.tw>
+ * Version: 1.4.6
+ *
+ * Based on cyapa driver:
+ * copyright (c) 2011-2012 Cypress Semiconductor, Inc.
+ * copyright (c) 2011-2012 Google, Inc.
+ *
+ * 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.
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/cdev.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/input.h>
+#include <linux/uaccess.h>
+#include <linux/jiffies.h>
+
+#define DRIVER_NAME "elan_i2c"
+#define ELAN_DRIVER_VERSION "1.4.6"
+#define ETP_PRESSURE_OFFSET 25
+#define ETP_MAX_PRESSURE 255
+#define ETP_FWIDTH_REDUCE 90
+#define ETP_FINGER_WIDTH 15
+
+#define ELAN_ADAPTER_FUNC_NONE 0
+#define ELAN_ADAPTER_FUNC_I2C 1
+#define ELAN_ADAPTER_FUNC_SMBUS 2
+#define ELAN_ADAPTER_FUNC_BOTH 3
+
+/* Length of Elan touchpad information */
+#define ETP_INF_LENGTH 2
+#define ETP_MAX_FINGERS 5
+#define ETP_FINGER_DATA_LEN 5
+#define ETP_REPORT_ID 0x5D
+#define ETP_MAX_REPORT_LEN 34
+#define ETP_ENABLE_ABS 0x0001
+#define ETP_ENABLE_CALIBRATE 0x0002
+#define ETP_DISABLE_CALIBRATE 0x0000
+
+/* Elan smbus command */
+#define ETP_SMBUS_IAP_CMD 0x00
+#define ETP_SMBUS_ENABLE_TP 0x20
+#define ETP_SMBUS_SLEEP_CMD 0x21
+#define ETP_SMBUS_IAP_PASSWORD_WRITE 0x29
+#define ETP_SMBUS_IAP_PASSWORD_READ 0x80
+#define ETP_SMBUS_WRITE_FW_BLOCK 0x2A
+#define ETP_SMBUS_IAP_RESET_CMD 0x2B
+#define ETP_SMBUS_RANGE_CMD 0xA0
+#define ETP_SMBUS_FW_VERSION_CMD 0xA1
+#define ETP_SMBUS_XY_TRACENUM_CMD 0xA2
+#define ETP_SMBUS_SM_VERSION_CMD 0xA3
+#define ETP_SMBUS_UNIQUEID_CMD 0xA3
+#define ETP_SMBUS_RESOLUTION_CMD 0xA4
+#define ETP_SMBUS_HELLOPACKET_CMD 0xA7
+#define ETP_SMBUS_PACKET_QUERY 0xA8
+#define ETP_SMBUS_IAP_VERSION_CMD 0xAC
+#define ETP_SMBUS_IAP_CTRL_CMD 0xAD
+#define ETP_SMBUS_IAP_CHECKSUM_CMD 0xAE
+#define ETP_SMBUS_FW_CHECKSUM_CMD 0xAF
+#define ETP_SMBUS_MAX_BASELINE_CMD 0xC3
+#define ETP_SMBUS_MIN_BASELINE_CMD 0xC4
+#define ETP_SMBUS_CALIBRATE_QUERY 0xC5
+#define ETP_SMBUS_REPORT_LEN 32
+#define ETP_SMBUS_FINGER_DATA_OFFSET 2
+#define ETP_SMBUS_HELLOPACKET_LEN 5
+#define ETP_SMBUS_IAP_PASSWORD 0x1234
+#define ETP_SMBUS_IAP_MODE_ON (1<<6)
+
+/* Elan i2c command */
+#define ETP_I2C_RESET 0x0100
+#define ETP_I2C_WAKE_UP 0x0800
+#define ETP_I2C_SLEEP 0x0801
+#define ETP_I2C_DESC_CMD 0x0001
+#define ETP_I2C_REPORT_DESC_CMD 0x0002
+#define ETP_I2C_STAND_CMD 0x0005
+#define ETP_I2C_UNIQUEID_CMD 0x0101
+#define ETP_I2C_FW_VERSION_CMD 0x0102
+#define ETP_I2C_SM_VERSION_CMD 0x0103
+#define ETP_I2C_XY_TRACENUM_CMD 0x0105
+#define ETP_I2C_MAX_X_AXIS_CMD 0x0106
+#define ETP_I2C_MAX_Y_AXIS_CMD 0x0107
+#define ETP_I2C_RESOLUTION_CMD 0x0108
+#define ETP_I2C_IAP_VERSION_CMD 0x0110
+#define ETP_I2C_SET_CMD 0x0300
+#define ETP_I2C_MAX_BASELINE_CMD 0x0306
+#define ETP_I2C_MIN_BASELINE_CMD 0x0307
+#define ETP_I2C_FW_CHECKSUM_CMD 0x030F
+#define ETP_I2C_IAP_CTRL_CMD 0x0310
+#define ETP_I2C_IAP_CMD 0x0311
+#define ETP_I2C_IAP_RESET_CMD 0x0314
+#define ETP_I2C_IAP_CHECKSUM_CMD 0x0315
+#define ETP_I2C_CALIBRATE_CMD 0x0316
+#define ETP_I2C_REPORT_LEN 34
+#define ETP_I2C_FINGER_DATA_OFFSET 4
+#define ETP_I2C_REPORT_ID_OFFSET 2
+#define ETP_I2C_DESC_LENGTH 30
+#define ETP_I2C_REPORT_DESC_LENGTH 158
+#define ETP_I2C_IAP_PASSWORD 0x1EA5
+#define ETP_I2C_IAP_RESET 0xF0F0
+#define ETP_I2C_MAIN_MODE_ON (1<<9)
+#define ETP_I2C_IAP_REG_L 0x01
+#define ETP_I2C_IAP_REG_H 0x06
+
+/* The main device structure */
+struct elan_tp_data {
+ struct i2c_client *client;
+ struct input_dev *input;
+ unsigned int max_x;
+ unsigned int max_y;
+ unsigned int width_x;
+ unsigned int width_y;
+ unsigned int irq;
+ u16 unique_id;
+ u16 fw_version;
+ u16 sm_version;
+ u16 iap_version;
+ bool smbus;
+};
+
+/*
+ *******************************************************************
+ * Elan smbus interface
+ *******************************************************************
+ */
+static int elan_smbus_initialize(struct i2c_client *client) {
+ u8 check[ETP_SMBUS_HELLOPACKET_LEN] = {0x55, 0x55, 0x55, 0x55, 0x55};
+ u8 values[ETP_SMBUS_HELLOPACKET_LEN] = {0, 0, 0, 0, 0};
+ int ret;
+
+ /* Get hello packet */
+ ret = i2c_smbus_read_block_data(client,
+ ETP_SMBUS_HELLOPACKET_CMD, values);
+ if (ret != ETP_SMBUS_HELLOPACKET_LEN) {
+ dev_err(&client->dev, "hello packet length fail\n");
+ return -1;
+ }
+
+ /* compare hello packet */
+ if (memcmp(values, check, ETP_SMBUS_HELLOPACKET_LEN)) {
+ dev_err(&client->dev, "hello packet fail [%x %x %x %x %x]\n",
+ values[0], values[1], values[2], values[3], values[4]);
+ return -1;
+ }
+
+ /* enable tp */
+ ret = i2c_smbus_write_byte(client, ETP_SMBUS_ENABLE_TP);
+ return ret;
+}
+
+static int elan_smbus_enable_absolute_mode(struct i2c_client *client) {
+ u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS};
+
+ return i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, 4, cmd);
+}
+
+/*
+ ******************************************************************
+ * Elan i2c interface
+ ******************************************************************
+ */
+static int elan_i2c_read_block(struct i2c_client *client,
+ u16 reg, u8 *val, u16 len)
+{
+ struct i2c_msg msgs[2];
+ u8 buf[2];
+ int ret;
+
+ buf[0] = reg & 0xff;
+ buf[1] = (reg >> 8) & 0xff;
+
+ msgs[0].addr = client->addr;
+ msgs[0].flags = client->flags & I2C_M_TEN;
+ msgs[0].len = 2;
+ msgs[0].buf = buf;
+
+ msgs[1].addr = client->addr;
+ msgs[1].flags = client->flags & I2C_M_TEN;
+ msgs[1].flags |= I2C_M_RD;
+ msgs[1].len = len;
+ msgs[1].buf = val;
+
+ ret = i2c_transfer(client->adapter, msgs, 2);
+ return ret != 2 ? -EIO : 0;
+}
+
+static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8
+*val) {
+ int retval;
+
+ retval = elan_i2c_read_block(client, reg, val, ETP_INF_LENGTH);
+ if (retval < 0) {
+ dev_err(&client->dev, "reading cmd (0x%04x) fail.\n", reg);
+ return retval;
+ }
+ return 0;
+}
+
+static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16
+cmd) {
+ struct i2c_msg msg;
+ u8 buf[4];
+ int ret;
+
+ buf[0] = reg & 0xff;
+ buf[1] = (reg >> 8) & 0xff;
+ buf[2] = cmd & 0xff;
+ buf[3] = (cmd >> 8) & 0xff;
+
+ msg.addr = client->addr;
+ msg.flags = client->flags & I2C_M_TEN;
+ msg.len = 4;
+ msg.buf = buf;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ return ret != 1 ? -EIO : 0;
+}
+
+static int elan_i2c_reset(struct i2c_client *client) {
+ return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
+ ETP_I2C_RESET);
+}
+
+static int elan_i2c_wake_up(struct i2c_client *client) {
+ return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
+ ETP_I2C_WAKE_UP);
+}
+
+static int elan_i2c_sleep(struct i2c_client *client) {
+ return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
+ ETP_I2C_SLEEP);
+}
+
+static int elan_i2c_enable_absolute_mode(struct i2c_client *client) {
+ return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
+ ETP_ENABLE_ABS);
+}
+
+static int elan_i2c_get_desc(struct i2c_client *client, u8 *val) {
+ return elan_i2c_read_block(client, ETP_I2C_DESC_CMD, val,
+ ETP_I2C_DESC_LENGTH);
+}
+
+static int elan_i2c_get_report_desc(struct i2c_client *client, u8 *val)
+{
+ return elan_i2c_read_block(client, ETP_I2C_REPORT_DESC_CMD,
+ val, ETP_I2C_REPORT_DESC_LENGTH); }
+
+static int elan_i2c_initialize(struct i2c_client *client) {
+ struct device *dev = &client->dev;
+ int rc;
+ u8 val[256];
+
+ rc = elan_i2c_reset(client);
+ if (rc < 0) {
+ dev_err(dev, "device reset failed.\n");
+ return -1;
+ }
+
+ /* wait for get reset return flag */
+ msleep(100);
+ /* get reset return flag 0000 */
+ rc = i2c_master_recv(client, val, ETP_INF_LENGTH);
+ if (rc < 0) {
+ dev_err(dev, "get device reset return value failed.\n");
+ return -1;
+ }
+
+ rc = elan_i2c_get_desc(client, val);
+ if (rc < 0) {
+ dev_err(dev, "cannot get device descriptor.\n");
+ return -1;
+ }
+
+ rc = elan_i2c_get_report_desc(client, val);
+ if (rc < 0) {
+ dev_err(dev, "fetching report descriptor failed.\n");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ ******************************************************************
+ * General functions
+ ******************************************************************
+ */
+/*
+ * (value from firmware) * 10 + 790 = dpi
+ * we also have to convert dpi to dots/mm (*10/254 to avoid floating
+point) */ static unsigned int elan_convert_res(char val) {
+ int res;
+ if (val & 0x80) {
+ val = ~val + 1;
+ res = (790 - val * 10) * 10 / 254;
+ } else
+ res = (val * 10 + 790) * 10 / 254;
+ return res;
+}
+
+static int elan_get_iap_version(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_IAP_VERSION_CMD, val);
+ ret = val[2];
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_IAP_VERSION_CMD, val);
+ ret = val[0];
+ }
+ return ret;
+}
+
+static int elan_get_x_max(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_RANGE_CMD, val);
+ ret = (0x0f & val[0]) << 8 | val[1];
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_MAX_X_AXIS_CMD, val);
+ ret = (0x0f & val[1]) << 8 | val[0];
+ }
+ return ret;
+}
+
+static int elan_get_y_max(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_RANGE_CMD, val);
+ ret = (0xf0 & val[0]) << 4 | val[2];
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_MAX_Y_AXIS_CMD, val);
+ ret = (0x0f & val[1]) << 8 | val[0];
+ }
+ return ret;
+}
+
+static int elan_get_x_tracenum(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_XY_TRACENUM_CMD, val);
+ ret = (val[1] - 1);
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_XY_TRACENUM_CMD, val);
+ ret = (val[0] - 1);
+ }
+ return ret;
+}
+
+static int elan_get_y_tracenum(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_XY_TRACENUM_CMD, val);
+ ret = (val[2] - 1);
+ } else {
+ ret = elan_i2c_read_cmd(data->client,
+ ETP_I2C_XY_TRACENUM_CMD, val);
+ ret = (val[1] - 1);
+ }
+ return ret;
+}
+
+static int elan_get_fw_version(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_FW_VERSION_CMD, val);
+ ret = val[2];
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_FW_VERSION_CMD, val);
+ ret = val[0];
+ }
+ return ret;
+}
+
+static int elan_get_sm_version(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus)
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_SM_VERSION_CMD, val);
+ else
+ elan_i2c_read_block(data->client,
+ ETP_I2C_SM_VERSION_CMD, val, 1);
+ ret = val[0];
+ return ret;
+}
+
+static int elan_get_unique_id(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_UNIQUEID_CMD, val);
+ ret = val[1];
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_UNIQUEID_CMD, val);
+ ret = val[0];
+ }
+ return ret;
+}
+
+static int elan_get_x_resolution(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_RESOLUTION_CMD, val);
+ ret = elan_convert_res(val[1] & 0x0F);
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_RESOLUTION_CMD, val);
+ ret = elan_convert_res(val[0]);
+ }
+ return ret;
+}
+
+static int elan_get_y_resolution(struct elan_tp_data *data) {
+ int ret;
+ u8 val[3];
+ if (data->smbus) {
+ i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_RESOLUTION_CMD, val);
+ ret = elan_convert_res((val[1] & 0xF0) >> 4);
+ } else {
+ elan_i2c_read_cmd(data->client,
+ ETP_I2C_RESOLUTION_CMD, val);
+ ret = elan_convert_res(val[1]);
+ }
+ return ret;
+}
+
+static int elan_initialize(struct elan_tp_data *data) {
+ int ret;
+ if (data->smbus) {
+ ret = elan_smbus_initialize(data->client);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "device initialize failed.\n");
+ goto err_initialize;
+ }
+
+ ret = elan_smbus_enable_absolute_mode(data->client);
+ if (ret < 0)
+ dev_err(&data->client->dev,
+ "cannot switch to absolute mode.\n");
+ } else {
+ ret = elan_i2c_initialize(data->client);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "device initialize failed.\n");
+ goto err_initialize;
+ }
+
+ ret = elan_i2c_enable_absolute_mode(data->client);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "cannot switch to absolute mode.\n");
+ goto err_initialize;
+ }
+
+ ret = elan_i2c_wake_up(data->client);
+ if (ret < 0)
+ dev_err(&data->client->dev,
+ "device wake up failed.\n");
+ }
+err_initialize:
+ return ret;
+}
+
+
+/*
+ ******************************************************************
+ * Elan isr functions
+ ******************************************************************
+ */
+static int elan_check_packet(struct elan_tp_data *data, u8 *packet) {
+ u8 rid;
+
+ if (data->smbus)
+ rid = packet[0];
+ else
+ rid = packet[ETP_I2C_REPORT_ID_OFFSET];
+
+ /* check report id */
+ if (rid != ETP_REPORT_ID) {
+ dev_err(&data->client->dev, "report id [%x] fail.\n", rid);
+ return -1;
+ }
+ return 0;
+}
+
+static void elan_report_absolute(struct elan_tp_data *data, u8 *packet)
+{
+ struct input_dev *input = data->input;
+ u8 *finger_data;
+ bool finger_on;
+ int pos_x, pos_y;
+ int pressure, mk_x, mk_y;
+ int i, area_x, area_y, major, minor, new_pressure;
+ int finger_count = 0;
+ int btn_click;
+ u8 tp_info;
+
+ if (data->smbus) {
+ finger_data = &packet[ETP_SMBUS_FINGER_DATA_OFFSET];
+ tp_info = packet[1];
+ } else {
+ finger_data = &packet[ETP_I2C_FINGER_DATA_OFFSET];
+ tp_info = packet[3];
+ }
+
+ btn_click = (tp_info & 0x01);
+ for (i = 0; i < ETP_MAX_FINGERS; i++) {
+ finger_on = (tp_info >> (3 + i)) & 0x01;
+
+ /* analyze touched finger raw data*/
+ if (finger_on) {
+ pos_x = ((finger_data[0] & 0xf0) << 4) |
+ finger_data[1];
+ pos_y = ((finger_data[0] & 0x0f) << 8) |
+ finger_data[2];
+ pos_y = data->max_y - pos_y;
+ mk_x = (finger_data[3] & 0x0f);
+ mk_y = (finger_data[3] >> 4);
+ pressure = finger_data[4];
+
+ /*
+ * to avoid fat finger be as palm, so reduce the
+ * width x and y per trace
+ */
+ area_x = mk_x * (data->width_x - ETP_FWIDTH_REDUCE);
+ area_y = mk_y * (data->width_y - ETP_FWIDTH_REDUCE);
+
+ major = max(area_x, area_y);
+ minor = min(area_x, area_y);
+
+ new_pressure = pressure + ETP_PRESSURE_OFFSET;
+ if (new_pressure > ETP_MAX_PRESSURE)
+ new_pressure = ETP_MAX_PRESSURE;
+
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER,
+ true);
+ input_report_abs(input, ABS_MT_POSITION_X, pos_x);
+ input_report_abs(input, ABS_MT_POSITION_Y, pos_y);
+ input_report_abs(input, ABS_MT_PRESSURE, new_pressure);
+ input_report_abs(input, ABS_TOOL_WIDTH, mk_x);
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR, major);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR, minor);
+ finger_data += ETP_FINGER_DATA_LEN;
+ finger_count++;
+ } else {
+ input_mt_slot(input, i);
+ input_mt_report_slot_state(input,
+ MT_TOOL_FINGER, false);
+ }
+ }
+
+ input_report_key(input, BTN_LEFT, (btn_click == 1));
+ input_mt_report_pointer_emulation(input, true);
+ input_sync(input);
+}
+
+static irqreturn_t elan_isr(int irq, void *dev_id) {
+ struct elan_tp_data *data = dev_id;
+ u8 raw[ETP_MAX_REPORT_LEN];
+ int retval;
+ int report_len;
+
+ if (data->smbus) {
+ report_len = ETP_SMBUS_REPORT_LEN;
+ retval = i2c_smbus_read_block_data(data->client,
+ ETP_SMBUS_PACKET_QUERY,
+ raw);
+ } else {
+ report_len = ETP_I2C_REPORT_LEN;
+ retval = i2c_master_recv(data->client, raw, report_len);
+ }
+
+ if (retval != report_len) {
+ dev_err(&data->client->dev, "wrong packet len(%d)", retval);
+ goto elan_isr_end;
+ }
+
+ if (elan_check_packet(data, raw) < 0) {
+ dev_err(&data->client->dev, "wrong packet format.");
+ goto elan_isr_end;
+ }
+ elan_report_absolute(data, raw);
+
+elan_isr_end:
+ return IRQ_HANDLED;
+}
+
+/*
+ ******************************************************************
+ * Elan initial functions
+ ******************************************************************
+ */
+static int elan_input_dev_create(struct elan_tp_data *data) {
+ struct i2c_client *client = data->client;
+ struct input_dev *input;
+ unsigned int x_res, y_res;
+ int ret, max_width, min_width;
+
+ data->input = input = input_allocate_device();
+ if (!input)
+ return -ENOMEM;
+ input->name = "Elan Touchpad";
+ input->id.bustype = BUS_I2C;
+ input->dev.parent = &data->client->dev;
+
+ __set_bit(EV_ABS, input->evbit);
+ __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+ __set_bit(BTN_LEFT, input->keybit);
+
+ data->unique_id = elan_get_unique_id(data);
+ data->fw_version = elan_get_fw_version(data);
+ data->sm_version = elan_get_sm_version(data);
+ data->iap_version = elan_get_iap_version(data);
+ data->max_x = elan_get_x_max(data);
+ data->max_y = elan_get_y_max(data);
+ data->width_x = data->max_x / elan_get_x_tracenum(data);
+ data->width_y = data->max_y / elan_get_y_tracenum(data);
+ x_res = elan_get_x_resolution(data);
+ y_res = elan_get_y_resolution(data);
+ max_width = max(data->width_x, data->width_y);
+ min_width = min(data->width_x, data->width_y);
+
+ dev_dbg(&client->dev,
+ "Elan Touchpad Information:\n"
+ " Module unique ID: 0x%04x\n"
+ " Firmware Version: 0x%04x\n"
+ " Sample Version: 0x%04x\n"
+ " IAP Version: 0x%04x\n"
+ " Max ABS X,Y: %d,%d\n"
+ " Width X,Y: %d,%d\n"
+ " Resolution X,Y: %d,%d (dots/mm)\n",
+ data->unique_id,
+ data->fw_version,
+ data->sm_version,
+ data->iap_version,
+ data->max_x, data->max_y,
+ data->width_x, data->width_y,
+ (char)x_res, (char)y_res);
+
Why cast to char?
Ans: OK. I will remove char type.
+ input_set_abs_params(input, ABS_X, 0, data->max_x, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, data->max_y, 0, 0);
+ input_abs_set_res(input, ABS_X, x_res);
+ input_abs_set_res(input, ABS_Y, y_res);
+ input_set_abs_params(input, ABS_PRESSURE, 0, ETP_MAX_PRESSURE, 0, 0);
+ input_set_abs_params(input, ABS_TOOL_WIDTH, 0, ETP_FINGER_WIDTH, 0,
+0);
+
+ /* handle pointer emulation and unused slots in core */
+ ret = input_mt_init_slots(input, ETP_MAX_FINGERS,
+ INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED);
+ if (ret) {
+ dev_err(&client->dev, "allocate MT slots failed, %d\n", ret);
+ goto err_free_device;
+ }
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, data->max_x, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, data->max_y, 0, 0);
+ input_abs_set_res(input, ABS_MT_POSITION_X, x_res);
+ input_abs_set_res(input, ABS_MT_POSITION_Y, y_res);
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0,
+ ETP_MAX_PRESSURE, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0,
+ ETP_FINGER_WIDTH * max_width, 0, 0);
+ input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0,
+ ETP_FINGER_WIDTH * min_width, 0, 0);
+
+ /* Register the device in input subsystem */
+ ret = input_register_device(input);
+ if (ret) {
+ dev_err(&client->dev, "input_dev register failed, %d\n", ret);
+ goto err_free_device;
+ }
+
+ return 0;
+
+err_free_device:
+ input_free_device(input);
+ return ret;
+}
+
+static u8 elan_check_adapter_functionality(struct i2c_client *client) {
+ u8 ret = ELAN_ADAPTER_FUNC_NONE;
+
+ if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ ret |= ELAN_ADAPTER_FUNC_I2C;
+ if (i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_BLOCK_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK))
+ ret |= ELAN_ADAPTER_FUNC_SMBUS;
+ return ret;
+}
+
+static int elan_probe(struct i2c_client *client,
+ const struct i2c_device_id *dev_id) {
+ struct elan_tp_data *data;
+ int ret;
+ u8 adapter_func;
+ union i2c_smbus_data dummy;
+ struct device *dev = &client->dev;
+
+ adapter_func = elan_check_adapter_functionality(client);
+ if (adapter_func == ELAN_ADAPTER_FUNC_NONE) {
+ dev_err(dev, "not a supported I2C/SMBus adapter\n");
+ return -EIO;
+ }
+
+ /* Make sure there is something at this address */
+ if (i2c_smbus_xfer(client->adapter, client->addr, 0,
+ I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0)
+ return -ENODEV;
+
+ data = kzalloc(sizeof(struct elan_tp_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /* check protocol type */
+ if (adapter_func == ELAN_ADAPTER_FUNC_SMBUS)
+ data->smbus = true;
+ else
+ data->smbus = false;
+ data->client = client;
+ data->irq = client->irq;
+
+ ret = request_threaded_irq(client->irq, NULL, elan_isr,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ client->name, data);
+ if (ret < 0) {
+ dev_err(&client->dev, "cannot register irq=%d\n",
+ client->irq);
+ goto err_irq;
+ }
+
+ /* initial elan touch pad */
+ ret = elan_initialize(data);
+ if (ret < 0)
+ goto err_init;
+
+ /* create input device */
+ ret = elan_input_dev_create(data);
+ if (ret < 0)
+ goto err_input_dev;
+
+ device_init_wakeup(&client->dev, 1);
+ i2c_set_clientdata(client, data);
+ return 0;
+
+err_input_dev:
+err_init:
+ free_irq(data->irq, data);
+err_irq:
+ kfree(data);
+ dev_err(&client->dev, "Elan Trackpad probe fail!\n");
+ return ret;
+}
+
+static int elan_remove(struct i2c_client *client) {
+ struct elan_tp_data *data = i2c_get_clientdata(client);
+ free_irq(data->irq, data);
+ input_free_device(data->input);
Not needed (and harmful here).
Ans: OK. I will remove this.
+ input_unregister_device(data->input);
+ kfree(data);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int elan_suspend(struct device *dev) {
+ int ret = 0;
+ struct elan_tp_data *data = dev_get_drvdata(dev);
+
+ disable_irq(data->irq);
+ if (data->smbus)
+ ret = i2c_smbus_write_byte(data->client,
+ ETP_SMBUS_SLEEP_CMD);
+ else
+ ret = elan_i2c_sleep(data->client);
+
+ if (ret < 0)
+ dev_err(dev, "suspend mode failed, %d\n", ret);
+
+ return ret;
+}
+
+static int elan_resume(struct device *dev) {
+ int ret = 0;
+ struct elan_tp_data *data = dev_get_drvdata(dev);
+
+ ret = elan_initialize(data);
+ if (ret < 0)
+ dev_err(dev, "resume active power failed, %d\n", ret);
+
+ enable_irq(data->irq);
+ return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume);
+
+static const struct i2c_device_id elan_id[] = {
+ { DRIVER_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, elan_id);
+
+static struct i2c_driver elan_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .pm = &elan_pm_ops,
+ },
+ .probe = elan_probe,
+ .remove = elan_remove,
+ .id_table = elan_id,
+};
+
+
+static int __init elan_init(void)
+{
+ int ret;
+ ret = i2c_add_driver(&elan_driver);
+ if (ret)
+ pr_err("elan driver register FAILED.\n");
+
+ return ret;
+}
+
+static void __exit elan_exit(void)
+{
+ i2c_del_driver(&elan_driver);
+}
+
+module_init(elan_init);
+module_exit(elan_exit);
+
module_i2c_driver().
Ans: OK. I will modify this.
+MODULE_AUTHOR("Duson Lin <dusonlin@emc.com.tw>");
+MODULE_DESCRIPTION("Elan I2C/SMBus Touchpad driver");
+MODULE_LICENSE("GPL");
--
1.7.10.4
--
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
> -----Original Message-----
> From: Dmitry Torokhov [mailto:dmitry.torokhov@gmail.com]
> Sent: Tuesday, December 17, 2013 5:00 AM
> To: Duson Lin
> Cc: linux-input@vger.kernel.org; linux-kernel@vger.kernel.org;
> agnescheng@google.com; phoenix@emc.com.tw
> Subject: Re: [PATCH] Input: add i2c/smbus driver for elan touchpad
>
> Hi Duson,
>
> On Fri, Nov 22, 2013 at 01:56:55PM +0800, Duson Lin wrote:
> > This driver adds support for elan i2c/smbus touchpad found on some laptops PC
> > ---
> > drivers/input/mouse/Kconfig | 10 +
> > drivers/input/mouse/Makefile | 1 +
> > drivers/input/mouse/elan_i2c.c | 1846
> ++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 1857 insertions(+)
> > create mode 100644 drivers/input/mouse/elan_i2c.c
> >
> > diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> > index effa9c5..8ad4b38 100644
> > --- a/drivers/input/mouse/Kconfig
> > +++ b/drivers/input/mouse/Kconfig
> > @@ -215,6 +215,16 @@ config MOUSE_CYAPA
> > To compile this driver as a module, choose M here: the module will be
> > called cyapa.
> >
> > +config MOUSE_ELAN_I2C
> > + tristate "ELAN I2C Touchpad support"
> > + depends on I2C
> > + help
> > + This driver adds support for Elan I2C Trackpads.
> > + Say y here if you have a ELAN I2C Touchpad.
> > +
> > + To compile this driver as a module, choose M here: the module will be
> > + called elan_i2c.
> > +
> > config MOUSE_INPORT
> > tristate "InPort/MS/ATIXL busmouse"
> > depends on ISA
> > diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
> > index c25efdb..24a12a6 100644
> > --- a/drivers/input/mouse/Makefile
> > +++ b/drivers/input/mouse/Makefile
> > @@ -9,6 +9,7 @@ obj-$(CONFIG_MOUSE_APPLETOUCH) +=
> appletouch.o
> > obj-$(CONFIG_MOUSE_ATARI) += atarimouse.o
> > obj-$(CONFIG_MOUSE_BCM5974) += bcm5974.o
> > obj-$(CONFIG_MOUSE_CYAPA) += cyapa.o
> > +obj-$(CONFIG_MOUSE_ELAN_I2C) += elan_i2c.o
> > obj-$(CONFIG_MOUSE_GPIO) += gpio_mouse.o
> > obj-$(CONFIG_MOUSE_INPORT) += inport.o
> > obj-$(CONFIG_MOUSE_LOGIBM) += logibm.o
> > diff --git a/drivers/input/mouse/elan_i2c.c b/drivers/input/mouse/elan_i2c.c
> > new file mode 100644
> > index 0000000..9892ee1
> > --- /dev/null
> > +++ b/drivers/input/mouse/elan_i2c.c
> > @@ -0,0 +1,1846 @@
> > +/*
> > + * Elan I2C/SMBus Touchpad driver
> > + *
> > + * Copyright (c) 2013 ELAN Microelectronics Corp.
> > + *
> > + * Author: 林政維 (Duson Lin) <dusonlin@emc.com.tw>
> > + * Version: 1.4.6
> > + *
> > + * 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.
> > + *
> > + * Trademarks are the property of their respective owners.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/firmware.h>
> > +#include <linux/i2c.h>
> > +#include <linux/init.h>
> > +#include <linux/input/mt.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +#include <linux/debugfs.h>
> > +#include <linux/cdev.h>
> > +#include <linux/kernel.h>
> > +#include <linux/major.h>
> > +#include <linux/sched.h>
> > +#include <linux/input.h>
> > +#include <linux/uaccess.h>
> > +#include <linux/jiffies.h>
> > +
> > +#define DRIVER_NAME "elan_i2c"
> > +#define ELAN_DRIVER_VERSION "1.4.6"
> > +#define ETP_PRESSURE_OFFSET 25
> > +#define ETP_MAX_PRESSURE 255
> > +#define ETP_FWIDTH_REDUCE 90
> > +#define ETP_FINGER_WIDTH 15
> > +
> > +#define ELAN_ADAPTER_FUNC_NONE 0
> > +#define ELAN_ADAPTER_FUNC_I2C 1
> > +#define ELAN_ADAPTER_FUNC_SMBUS 2
> > +#define ELAN_ADAPTER_FUNC_BOTH 3
> > +
> > +/* Length of Elan touchpad information */
> > +#define ETP_INF_LENGTH 2
> > +#define ETP_MAX_FINGERS 5
> > +#define ETP_FINGER_DATA_LEN 5
> > +#define ETP_REPORT_ID 0x5D
> > +#define ETP_MAX_REPORT_LEN 34
> > +#define ETP_ENABLE_ABS 0x0001
> > +#define ETP_ENABLE_CALIBRATE 0x0002
> > +#define ETP_DISABLE_CALIBRATE 0x0000
> > +
> > +/* Elan smbus command */
> > +#define ETP_SMBUS_IAP_CMD 0x00
> > +#define ETP_SMBUS_ENABLE_TP 0x20
> > +#define ETP_SMBUS_DISABLE_TP 0x21
> > +#define ETP_SMBUS_IAP_PASSWORD_WRITE 0x29
> > +#define ETP_SMBUS_IAP_PASSWORD_READ 0x80
> > +#define ETP_SMBUS_WRITE_FW_BLOCK 0x2A
> > +#define ETP_SMBUS_IAP_RESET_CMD 0x2B
> > +#define ETP_SMBUS_RANGE_CMD 0xA0
> > +#define ETP_SMBUS_FW_VERSION_CMD 0xA1
> > +#define ETP_SMBUS_XY_TRACENUM_CMD 0xA2
> > +#define ETP_SMBUS_SM_VERSION_CMD 0xA3
> > +#define ETP_SMBUS_UNIQUEID_CMD 0xA3
> > +#define ETP_SMBUS_RESOLUTION_CMD 0xA4
> > +#define ETP_SMBUS_HELLOPACKET_CMD 0xA7
> > +#define ETP_SMBUS_PACKET_QUERY 0xA8
> > +#define ETP_SMBUS_IAP_VERSION_CMD 0xAC
> > +#define ETP_SMBUS_IAP_CTRL_CMD 0xAD
> > +#define ETP_SMBUS_IAP_CHECKSUM_CMD 0xAE
> > +#define ETP_SMBUS_FW_CHECKSUM_CMD 0xAF
> > +#define ETP_SMBUS_MAX_BASELINE_CMD 0xC3
> > +#define ETP_SMBUS_MIN_BASELINE_CMD 0xC4
> > +#define ETP_SMBUS_CALIBRATE_QUERY 0xC5
> > +#define ETP_SMBUS_REPORT_LEN 32
> > +#define ETP_SMBUS_FINGER_DATA_OFFSET 2
> > +#define ETP_SMBUS_HELLOPACKET_LEN 5
> > +#define ETP_SMBUS_IAP_PASSWORD 0x1234
> > +#define ETP_SMBUS_IAP_MODE_ON (1<<6)
> > +
> > +/* Elan i2c command */
> > +#define ETP_I2C_RESET 0x0100
> > +#define ETP_I2C_WAKE_UP 0x0800
> > +#define ETP_I2C_SLEEP 0x0801
> > +#define ETP_I2C_DESC_CMD 0x0001
> > +#define ETP_I2C_REPORT_DESC_CMD 0x0002
> > +#define ETP_I2C_STAND_CMD 0x0005
> > +#define ETP_I2C_UNIQUEID_CMD 0x0101
> > +#define ETP_I2C_FW_VERSION_CMD 0x0102
> > +#define ETP_I2C_SM_VERSION_CMD 0x0103
> > +#define ETP_I2C_XY_TRACENUM_CMD 0x0105
> > +#define ETP_I2C_MAX_X_AXIS_CMD 0x0106
> > +#define ETP_I2C_MAX_Y_AXIS_CMD 0x0107
> > +#define ETP_I2C_RESOLUTION_CMD 0x0108
> > +#define ETP_I2C_IAP_VERSION_CMD 0x0110
> > +#define ETP_I2C_SET_CMD 0x0300
> > +#define ETP_I2C_MAX_BASELINE_CMD 0x0306
> > +#define ETP_I2C_MIN_BASELINE_CMD 0x0307
> > +#define ETP_I2C_FW_CHECKSUM_CMD 0x030F
> > +#define ETP_I2C_IAP_CTRL_CMD 0x0310
> > +#define ETP_I2C_IAP_CMD 0x0311
> > +#define ETP_I2C_IAP_RESET_CMD 0x0314
> > +#define ETP_I2C_IAP_CHECKSUM_CMD 0x0315
> > +#define ETP_I2C_CALIBRATE_CMD 0x0316
> > +#define ETP_I2C_REPORT_LEN 34
> > +#define ETP_I2C_FINGER_DATA_OFFSET 4
> > +#define ETP_I2C_REPORT_ID_OFFSET 2
> > +#define ETP_I2C_DESC_LENGTH 30
> > +#define ETP_I2C_REPORT_DESC_LENGTH 158
> > +#define ETP_I2C_IAP_PASSWORD 0x1EA5
> > +#define ETP_I2C_IAP_RESET 0xF0F0
> > +#define ETP_I2C_MAIN_MODE_ON (1<<9)
> > +#define ETP_I2C_IAP_REG_L 0x01
> > +#define ETP_I2C_IAP_REG_H 0x06
> > +
> > +/* IAP F/W updater */
> > +#define ETP_FW_NAME "elan_i2c.bin"
> > +#define ETP_IAP_VERSION_ADDR 0x0082
> > +#define ETP_IAP_START_ADDR 0x0083
> > +#define ETP_FW_IAP_PAGE_ERR (1<<5)
> > +#define ETP_FW_IAP_INTERFACE_ERR (1<<4)
> > +#define ETP_FW_PAGE_SIZE 64
> > +#define ETP_FW_PAGE_COUNT 768
> > +#define ETP_FW_SIZE (ETP_FW_PAGE_SIZE *
> ETP_FW_PAGE_COUNT)
> > +enum {UNKNOWN_MODE, IAP_MODE, MAIN_MODE};
> > +
> > +struct dbfs_data {
> > + bool bfetch;
> > + u8 buffer[ETP_MAX_REPORT_LEN];
> > +};
> > +
> > +/* The main device structure */
> > +struct elan_tp_data {
> > + struct i2c_client *client;
> > + struct input_dev *input;
> > + unsigned int max_x;
> > + unsigned int max_y;
> > + unsigned int width_x;
> > + unsigned int width_y;
> > + unsigned int irq;
> > +
> > + /* fields required for IAP firmware updater */
> > + u16 unique_id;
> > + u16 fw_version;
> > + u16 sm_version;
> > + u16 iap_version;
> > + bool updated_fw;
> > + u16 iap_start_addr;
> > +
> > + /* irq wake is enabled */
> > + bool irq_wake;
> > + bool smbus;
> > + bool enable_detail_info;
> > +
> > + /* fields required for debug fs */
> > + struct mutex dbfs_mutex;
> > + struct dentry *dbfs_root;
> > + struct dbfs_data dbfs_buffer;
> > +};
> > +
> > +u8 val[256];
> > +static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8 *val);
> > +static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16 cmd);
> > +static int elan_initialize(struct elan_tp_data *data);
> > +
> > +/*
> > + **************************************************************
> > + * debugfs interface
> > + **************************************************************
> > +*/
> > +static int elan_dbfs_open(struct inode *inode, struct file *file)
> > +{
> > + int retval;
> > + struct elan_tp_data *data = inode->i_private;
> > +
> > + if (!data)
> > + return -ENODEV;
> > +
> > + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> > + if (retval)
> > + return retval;
> > +
> > + if (!kobject_get(&data->client->dev.kobj)) {
>
> Why are you pinning I2C device kobject? It will not prevent unbinding
> the driver if that's what you are aiming for.
>
> > + retval = -ENODEV;
> > + goto dbfs_out;
> > + }
> > +
> > + file->private_data = data;
> > +dbfs_out:
> > + mutex_unlock(&data->dbfs_mutex);
> > + return 0;
> > +}
> > +
> > +static int elan_dbfs_release(struct inode *inode, struct file *file)
> > +{
> > + struct elan_tp_data *data = file->private_data;
> > + int retval;
> > + if (!data)
> > + return -ENODEV;
> > +
> > + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> > + if (retval)
> > + return retval;
> > + file->private_data = NULL;
> > + kobject_put(&data->client->dev.kobj);
> > + mutex_unlock(&data->dbfs_mutex);
> > + return 0;
> > +}
> > +
> > +
> > +static ssize_t elan_dbfs_read(struct file *file,
> > + char __user *buffer, size_t count, loff_t *ppos)
> > +{
> > + struct elan_tp_data *data = file->private_data;
> > + int retval;
> > + if (!data)
> > + return -ENODEV;
> > +
> > + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> > + if (retval)
> > + return -EFAULT;
> > + if (data->dbfs_buffer.bfetch == false) {
> > + if (!copy_to_user(buffer, data->dbfs_buffer.buffer, count)) {
> > + data->dbfs_buffer.bfetch = true;
> > + retval = count;
> > + } else {
> > + retval = -2;
>
> -ENOENT? Why?
>
> > + }
> > + } else {
> > + retval = -4;
>
> -EINTR???
>
> > + }
> > + mutex_unlock(&data->dbfs_mutex);
> > + return retval;
> > +}
> > +
> > +static ssize_t elan_dbfs_write(struct file *file,
> > + const char __user *buffer, size_t count, loff_t *ppos)
> > +{
> > + struct elan_tp_data *data = file->private_data;
> > + int retval;
> > + if (!data)
> > + return -ENODEV;
> > +
> > + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> > + if (retval)
> > + return -EFAULT;
> > + retval = count;
> > + mutex_unlock(&data->dbfs_mutex);
> > + return retval;
> > +}
>
> This looks like an unneeded stub.
>
> > +
> > +static long elan_dbfs_ioctrl(struct file *file,
> > + unsigned int cmd, unsigned long arg)
> > +{
> > + int retval = 0;
> > + struct elan_tp_data *data = file->private_data;
> > +
> > + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> > + if (retval)
> > + return retval;
> > + mutex_unlock(&data->dbfs_mutex);
> > + return retval;
> > +}
>
> Same here.
>
> > +
> > +static const struct file_operations elan_debug_fops = {
> > + .open = elan_dbfs_open,
> > + .release = elan_dbfs_release,
> > + .read = elan_dbfs_read,
> > + .write = elan_dbfs_write,
> > + .unlocked_ioctl = elan_dbfs_ioctrl
> > +};
> > +
> > +static int elan_dbfs_init(struct elan_tp_data *data)
> > +{
> > + /* Create a global debugfs root for all elan devices */
> > + /* sys/kernel/debug/elan */
> > + data->dbfs_root = debugfs_create_dir("elan", NULL);
> > + if (!data->dbfs_root) {
> > + dev_err(&data->client->dev, "cannot create dbfs_root.\n");
> > + return -ENODEV;
> > + }
>
> That should not be done in per-device code. That said, do you really
> need to dump your packets - looks like you are done with the driver
> development?
>
> > + mutex_init(&data->dbfs_mutex);
> > +
> > + debugfs_create_file(DRIVER_NAME, 0777,
> > + data->dbfs_root, data, &elan_debug_fops);
> > + data->dbfs_buffer.bfetch = false;
> > + return 0;
> > +}
> > +
> > +/**********************************************************
> > + * IAP firmware updater related routines *
> > + **********************************************************
> > +*/
> > +
> > +static int elan_iap_getmode(struct elan_tp_data *data)
> > +{
> > + u16 constant;
> > + int retval;
> > + struct i2c_client *client = data->client;
> > +
> > + if (data->smbus) {
> > + retval = i2c_smbus_read_block_data(client,
> > + ETP_SMBUS_IAP_CTRL_CMD, val);
> > + if (retval < 0) {
> > + dev_err(&client->dev, "read iap ctrol fail.\n");
> > + return UNKNOWN_MODE;
> > + }
> > + constant = be16_to_cpup((__be16 *)val);
> > + dev_dbg(&client->dev, "smbus iap control reg: 0x%04x.\n",
> > + constant);
> > + if ((constant & ETP_SMBUS_IAP_MODE_ON) == 0x00)
> > + return MAIN_MODE;
> > + } else {
> > + retval = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val);
> > + if (retval < 0) {
> > + dev_err(&client->dev, "read iap ctrol fail.\n");
> > + return UNKNOWN_MODE;
> > + }
> > + constant = le16_to_cpup((__le16 *)val);
> > + dev_dbg(&client->dev, "i2c iap control reg: 0x%04x.\n",
> > + constant);
> > + if (constant & ETP_I2C_MAIN_MODE_ON)
> > + return MAIN_MODE;
> > + }
>
> Instead of doing
>
> if (smbus)
> do_this();
> else
> do_that();
>
> everywhere maybe you could define transport functions and invoke them
> through a pointer to transport ops?
>
>
> > +
> > + return IAP_MODE;
> > +}
> > +
> > +static int elan_iap_checksum(struct elan_tp_data *data)
> > +{
> > + int retval = 0;
> > + u16 checksum = -1;
> > + struct i2c_client *client = data->client;
> > +
> > + if (data->smbus) {
> > + retval = i2c_smbus_read_block_data(client,
> > + ETP_SMBUS_IAP_CHECKSUM_CMD, val);
> > + if (retval < 0) {
> > + dev_err(&client->dev, "Read checksum fail, %d\n",
> > + retval);
> > + return -1;
> > + }
> > + checksum = be16_to_cpup((__be16 *)val);
> > + } else {
> > + retval = elan_i2c_read_cmd(client,
> > + ETP_I2C_IAP_CHECKSUM_CMD, val);
> > + if (retval < 0) {
> > + dev_err(&client->dev, "Read checksum fail, %d\n",
> > + retval);
> > + return -1;
> > + }
> > + checksum = le16_to_cpup((__le16 *)val);
> > + }
> > + return checksum;
> > +}
> > +
> > +static bool elan_iap_reset(struct elan_tp_data *data)
> > +{
> > + int retval = 0;
> > + struct i2c_client *client = data->client;
> > +
> > + if (data->smbus)
> > + retval = i2c_smbus_write_byte(client,
> > + ETP_SMBUS_IAP_RESET_CMD);
> > + else
> > + retval = elan_i2c_write_cmd(client, ETP_I2C_IAP_RESET_CMD,
> > + ETP_I2C_IAP_RESET);
> > + if (retval < 0) {
> > + dev_err(&client->dev, "cannot reset IC, %d\n", retval);
> > + return false;
> > + }
> > + return true;
> > +}
> > +
> > +static bool elan_iap_setflashkey(struct elan_tp_data *data)
> > +{
> > + int retval = 0;
> > + struct i2c_client *client = data->client;
> > + u8 smbus_cmd[4] = {0x00, 0x0B, 0x00, 0x5A};
> > +
> > + if (data->smbus)
> > + retval = i2c_smbus_write_block_data(client,
> > + ETP_SMBUS_IAP_CMD, 4, smbus_cmd);
> > + else
> > + retval = elan_i2c_write_cmd(client, ETP_I2C_IAP_CMD,
> > + ETP_I2C_IAP_PASSWORD);
> > + if (retval < 0) {
> > + dev_err(&client->dev, "cannot set flash key, %d\n", retval);
> > + return false;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +static int elan_check_fw(struct elan_tp_data *data,
> > + const struct firmware *fw)
> > +{
> > + struct device *dev = &data->client->dev;
> > +
> > + /* Firmware must match exact PAGE_NUM * PAGE_SIZE bytes */
> > + if (fw->size != ETP_FW_SIZE) {
> > + dev_err(dev, "invalid firmware size = %zu, expected %d.\n",
> > + fw->size, ETP_FW_SIZE);
> > + return -EBADF;
> > + }
> > +
> > + /* Get IAP Start Address*/
> > + memcpy(val, &fw->data[ETP_IAP_START_ADDR * 2], 2);
> > + data->iap_start_addr = le16_to_cpup((__le16 *)val);
> > + return 0;
> > +}
> > +
> > +
> > +static int elan_smbus_prepare_fw_update(struct elan_tp_data *data)
> > +{
> > + struct i2c_client *client = data->client;
> > + struct device *dev = &data->client->dev;
> > + u16 password;
> > + u8 cmd[4] = {0x0F, 0x78, 0x00, 0x06};
> > +
> > + /* Get FW in which mode (IAP_MODE/MAIN_MODE) */
> > + int mode = elan_iap_getmode(data);
> > + if (mode == UNKNOWN_MODE)
> > + return -1;
> > +
> > + if (mode == MAIN_MODE) {
> > +
> > + /* set flash key*/
> > + if (elan_iap_setflashkey(data) == false) {
> > + dev_err(dev, "cannot set flash key\n");
> > + return -1;
> > + }
> > +
> > + /* write iap password */
> > + if (i2c_smbus_write_byte(client,
> > + ETP_SMBUS_IAP_PASSWORD_WRITE) < 0) {
> > + dev_err(dev, "cannot write iap password\n");
> > + return -1;
>
> Would be nice using appropriate error codes. EIO?
>
> > + }
> > +
> > + if (i2c_smbus_write_block_data(client,
> > + ETP_SMBUS_IAP_CMD, 4, cmd) < 0) {
> > + dev_err(dev, "cannot write cmd\n");
> > + return -1;
> > + }
> > +
> > + /* read password to check we enabled successfully. */
> > + if (i2c_smbus_read_block_data(client,
> > + ETP_SMBUS_IAP_PASSWORD_READ, val) < 0) {
> > + dev_err(dev, "cannot get iap password\n");
> > + return -1;
> > + }
> > + password = be16_to_cpup((__be16 *)val);
> > +
> > + if (password != ETP_SMBUS_IAP_PASSWORD) {
> > + dev_err(dev, "wrong iap password = 0x%X\n", password);
> > + return -1;
> > + }
> > + /* wait 30ms, from MAIN_MODE change to IAP_MODE*/
> > + msleep(30);
> > + }
> > +
> > + /* set flash key*/
> > + if (elan_iap_setflashkey(data) == false) {
> > + dev_err(dev, "cannot set flash key\n");
> > + return -1;
> > + }
> > +
> > + /* Reset IC */
> > + if (elan_iap_reset(data) == false) {
> > + dev_err(dev, "iap reset fail.\n");
> > + return -1;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int elan_i2c_prepare_fw_update(struct elan_tp_data *data)
> > +{
> > + struct i2c_client *client = data->client;
> > + struct device *dev = &data->client->dev;
> > + u16 password;
> > +
> > + /* Get FW in which mode (IAP_MODE/MAIN_MODE) */
> > + int mode = elan_iap_getmode(data);
> > + if (mode == UNKNOWN_MODE)
> > + return -1;
> > +
> > + if (mode == IAP_MODE) {
> > + /* Reset IC */
> > + if (elan_iap_reset(data) == false)
> > + return -1;
> > + msleep(30);
> > + }
> > +
> > + /* set flash key*/
> > + if (elan_iap_setflashkey(data) == false) {
> > + dev_err(dev, "cannot set flash key\n");
> > + return -1;
> > + }
> > +
> > + /* Wait for F/W IAP initialization */
> > + if (mode == MAIN_MODE)
> > + msleep(100);
> > + else
> > + msleep(30);
> > +
> > + /* check is in iap mode or not*/
> > + if (elan_iap_getmode(data) == MAIN_MODE) {
> > + dev_err(dev, "status wrong.\n");
> > + return -1;
> > + }
> > +
> > + /* set flash key again */
> > + if (elan_iap_setflashkey(data) == false) {
> > + dev_err(dev, "cannot set flash key\n");
> > + return -1;
> > + }
> > +
> > + /* Wait for F/W IAP initialization */
> > + if (mode == MAIN_MODE)
> > + msleep(100);
> > + else
> > + msleep(30);
> > +
> > + /* read back to check we actually enabled successfully. */
> > + if (elan_i2c_read_cmd(client, ETP_I2C_IAP_CMD, val) < 0) {
> > + dev_err(dev, "cannot get iap register\n");
> > + return -1;
> > + }
> > + password = le16_to_cpup((__le16 *)val);
> > +
> > + if (password != ETP_I2C_IAP_PASSWORD) {
> > + dev_err(dev, "wrong iap password = 0x%X\n", password);
> > + return -1;
> > + }
> > + return 0;
> > +}
> > +
> > +static bool elan_iap_page_write_ok(struct elan_tp_data *data)
> > +{
> > + u16 constant;
> > + int retval = 0;
> > + struct i2c_client *client = data->client;
> > +
> > +
> > + if (data->smbus) {
> > + retval = i2c_smbus_read_block_data(client,
> > + ETP_SMBUS_IAP_CTRL_CMD, val);
> > + if (retval < 0)
> > + return false;
> > + constant = be16_to_cpup((__be16 *)val);
> > + } else {
> > + retval = elan_i2c_read_cmd(client,
> > + ETP_I2C_IAP_CTRL_CMD, val);
> > + if (retval < 0)
> > + return false;
> > + constant = le16_to_cpup((__le16 *)val);
> > + }
> > +
> > + if (constant & ETP_FW_IAP_PAGE_ERR)
> > + return false;
> > +
> > + if (constant & ETP_FW_IAP_INTERFACE_ERR)
> > + return false;
> > + return true;
> > +}
> > +
> > +static int elan_smbus_write_fw_block(struct elan_tp_data *data,
> > + const u8 *page, u16 checksum, int idx)
> > +{
> > + struct device *dev = &data->client->dev;
> > + int half_page_size = ETP_FW_PAGE_SIZE / 2;
> > + int repeat = 3;
> > +
> > + do {
> > + /* due to smbus can write 32 bytes one time,
> > + so, we must write data 2 times.
> > + */
> > + i2c_smbus_write_block_data(data->client,
> > + ETP_SMBUS_WRITE_FW_BLOCK,
> > + half_page_size,
> > + page);
> > + i2c_smbus_write_block_data(data->client,
> > + ETP_SMBUS_WRITE_FW_BLOCK,
> > + half_page_size,
> > + (page + half_page_size));
> > + /* Wait for F/W to update one page ROM data. */
> > + usleep_range(8000, 10000);
> > + if (elan_iap_page_write_ok(data))
> > + break;
> > + dev_info(dev, "IAP retry this page! [%d]\n", idx);
> > + repeat--;
> > + } while (repeat == 0);
> > +
> > + if (repeat > 0)
> > + return 0;
> > + return -1;
> > +
> > +}
> > +
> > +static int elan_i2c_write_fw_block(struct elan_tp_data *data,
> > + const u8 *page, u16 checksum, int idx)
> > +{
> > + struct device *dev = &data->client->dev;
> > + int ret;
> > + int repeat = 3;
> > + u8 page_store[ETP_FW_PAGE_SIZE + 4];
> > +
> > + page_store[0] = ETP_I2C_IAP_REG_L;
> > + page_store[1] = ETP_I2C_IAP_REG_H;
> > + memcpy(&page_store[2], page, ETP_FW_PAGE_SIZE);
> > +
> > + /* recode checksum at last two bytes */
> > + page_store[ETP_FW_PAGE_SIZE+2] = (u8)(checksum & 0xFF);
> > + page_store[ETP_FW_PAGE_SIZE+3] = (u8)((checksum >> 8)&0xFF);
> > +
> > + do {
> > + ret = i2c_master_send(data->client, page_store,
> > + ETP_FW_PAGE_SIZE + 4);
> > +
> > + /* Wait for F/W to update one page ROM data. */
> > + msleep(20);
> > +
> > + if (ret == (ETP_FW_PAGE_SIZE + 4)) {
> > + if (elan_iap_page_write_ok(data))
> > + break;
> > + }
> > + dev_dbg(dev, "IAP retry this page! [%d]\n", idx);
> > + repeat--;
> > + } while (repeat == 0);
> > +
> > + if (repeat > 0)
> > + return 0;
> > + return -1;
> > +}
> > +
> > +static int elan_write_fw_block(struct elan_tp_data *data,
> > + const u8 *page, u16 checksum, int idx)
> > +{
> > + int ret;
> > + if (data->smbus)
> > + ret = elan_smbus_write_fw_block(data, page, checksum, idx);
> > + else
> > + ret = elan_i2c_write_fw_block(data, page, checksum, idx);
> > + return ret;
> > +}
> > +
> > +static int elan_prepare_fw_update(struct elan_tp_data *data)
> > +{
> > + int ret = 0;
> > + if (data->smbus)
> > + ret = elan_smbus_prepare_fw_update(data);
> > + else
> > + ret = elan_i2c_prepare_fw_update(data);
> > + return ret;
> > +}
> > +
> > +static int elan_firmware(struct elan_tp_data *data)
> > +{
> > + struct device *dev = &data->client->dev;
> > + const struct firmware *fw;
> > + const char *fw_name = ETP_FW_NAME;
> > + int i, j, ret;
> > + u16 boot_page_count;
> > + u16 sw_checksum, fw_checksum;
> > + data->updated_fw = true;
> > +
> > + dev_info(dev, "Start firmware update....\n");
> > +
> > + ret = request_firmware(&fw, ETP_FW_NAME, dev);
> > + if (ret) {
> > + dev_err(dev, "cannot load firmware from %s, %d\n",
> > + fw_name, ret);
> > + goto done;
> > + }
> > + /* check fw data match current iap version */
> > + ret = elan_check_fw(data, fw);
> > + if (ret) {
> > + dev_err(dev, "Invalid Elan firmware from %s, %d\n",
> > + fw_name, ret);
> > + goto done;
> > + }
> > + /* setup IAP status */
> > + ret = elan_prepare_fw_update(data);
> > + if (ret)
> > + goto done;
> > + sw_checksum = 0;
> > + fw_checksum = 0;
> > + boot_page_count = (data->iap_start_addr * 2) / ETP_FW_PAGE_SIZE;
> > + for (i = boot_page_count; i < ETP_FW_PAGE_COUNT; i++) {
> > + u16 checksum = 0;
> > + const u8 *page = &fw->data[i * ETP_FW_PAGE_SIZE];
> > +
> > + for (j = 0; j < ETP_FW_PAGE_SIZE; j += 2)
> > + checksum += ((page[j + 1] << 8) | page[j]);
> > +
> > + ret = elan_write_fw_block(data, page, checksum, i);
> > + if (ret) {
> > + dev_err(dev, "write page %d fail\n", i);
> > + goto done;
> > + }
> > + sw_checksum += checksum;
> > + }
> > +
> > + /* Wait WDT reset and power on reset */
> > + msleep(600);
> > +
> > + /* check checksum */
> > + fw_checksum = elan_iap_checksum(data);
> > + if (sw_checksum != fw_checksum) {
> > + dev_err(dev, "checksum diff sw=[%04X], fw=[%04X]\n",
> > + sw_checksum, fw_checksum);
> > + ret = -1;
> > + goto done;
> > + }
> > + ret = 0;
> > +done:
> > + if (ret != 0) {
> > + elan_iap_reset(data);
> > + data->updated_fw = false;
> > + } else {
> > + if (data->smbus) {
> > + data->updated_fw = false;
> > + elan_initialize(data);
> > + }
> > + }
> > + release_firmware(fw);
> > + return ret;
> > +}
> > +
> > +/******************************************************************
> > +* Elan smbus interface
> > +*******************************************************************
> > +*/
> > +static int elan_smbus_initialize(struct i2c_client *client)
> > +{
> > + u8 check[ETP_SMBUS_HELLOPACKET_LEN] = {0x55, 0x55, 0x55, 0x55,
> 0x55};
> > + u8 values[ETP_SMBUS_HELLOPACKET_LEN] = {0, 0, 0, 0, 0};
> > + int ret;
> > +
> > + /* Get hello packet */
> > + ret = i2c_smbus_read_block_data(client,
> > + ETP_SMBUS_HELLOPACKET_CMD, values);
> > + if (ret != ETP_SMBUS_HELLOPACKET_LEN) {
> > + dev_err(&client->dev, "hello packet length fail\n");
> > + return -1;
> > + }
> > +
> > + /* compare hello packet */
> > + if (memcmp(values, check, ETP_SMBUS_HELLOPACKET_LEN)) {
> > + dev_err(&client->dev, "hello packet fail [%x %x %x %x %x]\n",
> > + values[0], values[1], values[2], values[3], values[4]);
> > + return -1;
> > + }
> > +
> > + /* enable tp */
> > + ret = i2c_smbus_write_byte(client, ETP_SMBUS_ENABLE_TP);
> > + return ret;
> > +}
> > +
> > +static int elan_smbus_enable_calibrate(struct i2c_client *client)
> > +{
> > + u8 cmd[4] = {0x00, 0x07, 0x00,
> ETP_ENABLE_ABS|ETP_ENABLE_CALIBRATE};
> > +
> > + return i2c_smbus_write_block_data(client,
> > + ETP_SMBUS_IAP_CMD, 4, cmd);
> > +}
> > +
> > +static int elan_smbus_disable_calibrate(struct i2c_client *client)
> > +{
> > + u8 cmd[4] = {0x00, 0x07, 0x00,
> ETP_ENABLE_ABS|ETP_DISABLE_CALIBRATE};
> > +
> > + return i2c_smbus_write_block_data(client,
> > + ETP_SMBUS_IAP_CMD, 4, cmd);
> > +}
> > +
> > +static int elan_smbus_enable_absolute_mode(struct i2c_client *client)
> > +{
> > + u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS};
> > +
> > + return i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, 4, cmd);
> > +}
> > +
> > +/*****************************************************************
> > +* Elan i2c interface
> > +******************************************************************
> > +*/
> > +static int elan_i2c_read_block(struct i2c_client *client,
> > + u16 reg, u8 *val, u16 len)
> > +{
> > + struct i2c_msg msgs[2];
> > + u8 buf[2];
> > + int ret;
> > +
> > + buf[0] = reg & 0xff;
> > + buf[1] = (reg >> 8) & 0xff;
> > +
> > + msgs[0].addr = client->addr;
> > + msgs[0].flags = client->flags & I2C_M_TEN;
> > + msgs[0].len = 2;
> > + msgs[0].buf = buf;
> > +
> > + msgs[1].addr = client->addr;
> > + msgs[1].flags = client->flags & I2C_M_TEN;
> > + msgs[1].flags |= I2C_M_RD;
> > + msgs[1].len = len;
> > + msgs[1].buf = val;
> > +
> > + ret = i2c_transfer(client->adapter, msgs, 2);
> > + return ret != 2 ? -EIO : 0;
> > +}
> > +
> > +static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8 *val)
> > +{
> > + int retval;
> > +
> > + retval = elan_i2c_read_block(client, reg, val, ETP_INF_LENGTH);
> > + if (retval < 0) {
> > + dev_err(&client->dev, "reading cmd (0x%04x) fail.\n", reg);
> > + return retval;
> > + }
> > + return 0;
> > +}
> > +
> > +static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16 cmd)
> > +{
> > + struct i2c_msg msg;
> > + u8 buf[4];
> > + int ret;
> > +
> > + buf[0] = reg & 0xff;
> > + buf[1] = (reg >> 8) & 0xff;
> > + buf[2] = cmd & 0xff;
> > + buf[3] = (cmd >> 8) & 0xff;
> > +
> > + msg.addr = client->addr;
> > + msg.flags = client->flags & I2C_M_TEN;
> > + msg.len = 4;
> > + msg.buf = buf;
> > +
> > + ret = i2c_transfer(client->adapter, &msg, 1);
> > + return ret != 1 ? -EIO : 0;
> > +}
> > +
> > +static int elan_i2c_reset(struct i2c_client *client)
> > +{
> > + return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
> > + ETP_I2C_RESET);
> > +}
> > +
> > +static int elan_i2c_wake_up(struct i2c_client *client)
> > +{
> > + return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
> > + ETP_I2C_WAKE_UP);
> > +}
> > +
> > +static int elan_i2c_sleep(struct i2c_client *client)
> > +{
> > + return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
> > + ETP_I2C_SLEEP);
> > +}
> > +
> > +static int elan_i2c_enable_absolute_mode(struct i2c_client *client)
> > +{
> > + return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
> > + ETP_ENABLE_ABS);
> > +}
> > +
> > +static int elan_i2c_enable_calibrate(struct i2c_client *client)
> > +{
> > + return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
> > + ETP_ENABLE_ABS|ETP_ENABLE_CALIBRATE);
> > +}
> > +
> > +static int elan_i2c_disable_calibrate(struct i2c_client *client)
> > +{
> > + return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
> > + ETP_ENABLE_ABS|ETP_DISABLE_CALIBRATE);
> > +}
> > +
> > +static int elan_i2c_get_desc(struct i2c_client *client, u8 *val)
> > +{
> > + return elan_i2c_read_block(client, ETP_I2C_DESC_CMD, val,
> > + ETP_I2C_DESC_LENGTH);
> > +}
> > +
> > +static int elan_i2c_get_report_desc(struct i2c_client *client, u8 *val)
> > +{
> > + return elan_i2c_read_block(client, ETP_I2C_REPORT_DESC_CMD,
> > + val, ETP_I2C_REPORT_DESC_LENGTH);
> > +}
> > +
> > +static int elan_i2c_initialize(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + int rc;
> > +
> > + rc = elan_i2c_reset(client);
> > + if (rc < 0) {
> > + dev_err(dev, "device reset failed.\n");
> > + return -1;
> > + }
> > +
> > + /* wait for get reset return flag */
> > + msleep(100);
> > + /* get reset return flag 0000 */
> > + rc = i2c_master_recv(client, val, ETP_INF_LENGTH);
> > + if (rc < 0) {
> > + dev_err(dev, "get device reset return value failed.\n");
> > + return -1;
> > + }
> > +
> > + rc = elan_i2c_get_desc(client, val);
> > + if (rc < 0) {
> > + dev_err(dev, "cannot get device descriptor.\n");
> > + return -1;
> > + }
> > +
> > + rc = elan_i2c_get_report_desc(client, val);
> > + if (rc < 0) {
> > + dev_err(dev, "fetching report descriptor failed.\n");
> > + return -1;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/**************************************************************************
> > +* Genernal functions
> > +***************************************************************************
> > +*/
> > +
> > +/*
> > + * (value from firmware) * 10 + 790 = dpi
> > + * we also have to convert dpi to dots/mm (*10/254 to avoid floating point)
> > + */
> > +static unsigned int elan_convert_res(char val)
> > +{
> > + int res;
> > + if (val & 0x80) {
> > + val = ~val + 1;
> > + res = (790 - val * 10) * 10 / 254;
> > + } else
> > + res = (val * 10 + 790) * 10 / 254;
> > + return res;
> > +}
> > +
> > +static int elan_get_iap_version(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_IAP_VERSION_CMD, val);
> > + ret = val[2];
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_IAP_VERSION_CMD, val);
> > + ret = val[0];
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_x_max(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_RANGE_CMD, val);
> > + ret = (0x0f & val[0]) << 8 | val[1];
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_MAX_X_AXIS_CMD, val);
> > + ret = (0x0f & val[1]) << 8 | val[0];
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_y_max(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_RANGE_CMD, val);
> > + ret = (0xf0 & val[0]) << 4 | val[2];
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_MAX_Y_AXIS_CMD, val);
> > + ret = (0x0f & val[1]) << 8 | val[0];
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_x_tracenum(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_XY_TRACENUM_CMD, val);
> > + ret = (val[1] - 1);
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_XY_TRACENUM_CMD, val);
> > + ret = (val[0] - 1);
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_y_tracenum(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_XY_TRACENUM_CMD, val);
> > + ret = (val[2] - 1);
> > + } else {
> > + ret = elan_i2c_read_cmd(data->client,
> > + ETP_I2C_XY_TRACENUM_CMD, val);
> > + ret = (val[1] - 1);
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_fw_version(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_FW_VERSION_CMD, val);
> > + ret = val[2];
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_FW_VERSION_CMD, val);
> > + ret = val[0];
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_sm_version(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus)
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_SM_VERSION_CMD, val);
> > + else
> > + elan_i2c_read_block(data->client,
> > + ETP_I2C_SM_VERSION_CMD, val, 1);
> > + ret = val[0];
> > + return ret;
> > +}
> > +
> > +static int elan_get_unique_id(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_UNIQUEID_CMD, val);
> > + ret = val[1];
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_UNIQUEID_CMD, val);
> > + ret = val[0];
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_x_resolution(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_RESOLUTION_CMD, val);
> > + ret = elan_convert_res(val[1] & 0x0F);
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_RESOLUTION_CMD, val);
> > + ret = elan_convert_res(val[0]);
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_y_resolution(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_RESOLUTION_CMD, val);
> > + ret = elan_convert_res((val[1] & 0xF0) >> 4);
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_RESOLUTION_CMD, val);
> > + ret = elan_convert_res(val[1]);
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_fw_checksum(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_FW_CHECKSUM_CMD, val);
> > + ret = be16_to_cpup((__be16 *)val);
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_FW_CHECKSUM_CMD, val);
> > + ret = le16_to_cpup((__le16 *)val);
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_max_baseline(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_MAX_BASELINE_CMD, val);
> > + ret = be16_to_cpup((__be16 *)val);
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_MAX_BASELINE_CMD, val);
> > + ret = le16_to_cpup((__le16 *)val);
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_get_min_baseline(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_MIN_BASELINE_CMD, val);
> > + ret = be16_to_cpup((__be16 *)val);
> > + } else {
> > + elan_i2c_read_cmd(data->client,
> > + ETP_I2C_MIN_BASELINE_CMD, val);
> > + ret = le16_to_cpup((__le16 *)val);
> > + }
> > + return ret;
> > +}
> > +
> > +static int elan_enable_calibrate(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus)
> > + ret = elan_smbus_enable_calibrate(data->client);
> > + else
> > + ret = elan_i2c_enable_calibrate(data->client);
> > + return ret;
> > +}
> > +
> > +static int elan_disable_calibrate(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus)
> > + ret = elan_smbus_disable_calibrate(data->client);
> > + else
> > + ret = elan_i2c_disable_calibrate(data->client);
> > + return ret;
> > +}
> > +
> > +static int elan_initialize(struct elan_tp_data *data)
> > +{
> > + int ret;
> > + if (data->smbus) {
> > + ret = elan_smbus_initialize(data->client);
> > + if (ret < 0) {
> > + dev_err(&data->client->dev,
> > + "device initialize failed.\n");
> > + goto err_initialize;
> > + }
> > +
> > + ret = elan_smbus_enable_absolute_mode(data->client);
> > + if (ret < 0)
> > + dev_err(&data->client->dev,
> > + "cannot switch to absolute mode.\n");
> > + } else {
> > + ret = elan_i2c_initialize(data->client);
> > + if (ret < 0) {
> > + dev_err(&data->client->dev,
> > + "device initialize failed.\n");
> > + goto err_initialize;
> > + }
> > +
> > + ret = elan_i2c_enable_absolute_mode(data->client);
> > + if (ret < 0) {
> > + dev_err(&data->client->dev,
> > + "cannot switch to absolute mode.\n");
> > + goto err_initialize;
> > + }
> > +
> > + ret = elan_i2c_wake_up(data->client);
> > + if (ret < 0)
> > + dev_err(&data->client->dev,
> > + "device wake up failed.\n");
> > + }
> > +err_initialize:
> > + return ret;
> > +}
> > +
> > +/********************************************************************
> > + * below routines export interfaces to sysfs file system.
> > + * so user can get firmware/driver/hardware information using cat command.
> > + * e.g.: use below command to get firmware version
> > + * cat /sys/bus/i2c/drivers/elan_i2c/1-0015/firmware_version
> > + *******************************************************************
> > + */
> > +static ssize_t elan_sysfs_enable_detailinfo(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + data->enable_detail_info = true;
>
> What is this for?
>
> > + return sprintf(buf, "enable\n");
> > +}
> > +
> > +static ssize_t elan_sysfs_read_fw_checksum(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + unsigned int checksum = 0;
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + if (data->enable_detail_info == true) {
> > + checksum = elan_get_fw_checksum(data);
> > + data->enable_detail_info = false;
> > + }
> > + return sprintf(buf, "0x%04x\n", checksum);
> > +}
> > +
> > +static ssize_t elan_sysfs_read_unique_id(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + data->unique_id = elan_get_unique_id(data);
> > + return sprintf(buf, "0x%04x\n", data->unique_id);
> > +}
> > +
> > +static ssize_t elan_sysfs_read_driver_ver(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + return sprintf(buf, "%s\n", ELAN_DRIVER_VERSION);
>
> This should be reported in MODULE_VERSION if you need it.
>
> > +}
> > +
> > +static ssize_t elan_sysfs_read_fw_ver(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + data->fw_version = elan_get_fw_version(data);
> > + return sprintf(buf, "0x%04x\n", data->fw_version);
> > +}
> > +
> > +static ssize_t elan_sysfs_read_sm_ver(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + data->sm_version = elan_get_sm_version(data);
> > + return sprintf(buf, "0x%04x\n", data->sm_version);
> > +}
> > +
> > +static ssize_t elan_sysfs_read_iap_ver(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + data->iap_version = elan_get_iap_version(data);
> > + return sprintf(buf, "0x%04x\n", data->iap_version);
> > +}
> > +
> > +
> > +static ssize_t elan_sysfs_update_fw(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t count)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + int ret;
> > + ret = elan_firmware(data);
> > + if (ret)
> > + dev_err(dev, "firmware update failed.\n");
> > + else
> > + dev_info(dev, "firmware update succeeded.\n");
> > + return ret ? ret : count;
> > +}
> > +
> > +static ssize_t elan_sysfs_calibrate(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + /* start calibarate cmd */
> > + u8 smbus_cmd[4] = {0x00, 0x08, 0x00, 0x01};
> > + int tries = 20;
> > + int ret = 0;
> > + val[0] = 0;
> > +
> > + disable_irq(data->irq);
> > + elan_enable_calibrate(data);
> > + if (data->smbus)
> > + i2c_smbus_write_block_data(data->client,
> > + ETP_SMBUS_IAP_CMD, 4, smbus_cmd);
> > + else
> > + elan_i2c_write_cmd(data->client,
> > + ETP_I2C_CALIBRATE_CMD, 1);
> > +
> > + do {
> > + /* wait 250ms and check finish or not */
> > + msleep(250);
> > +
> > + if (data->smbus)
> > + i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_CALIBRATE_QUERY, val);
> > + else
> > + elan_i2c_read_block(data->client,
> > + ETP_I2C_CALIBRATE_CMD, val, 1);
> > +
> > + /* calibrate finish */
> > + if (val[0] == 0)
> > + break;
> > + } while (--tries);
> > +
> > + elan_disable_calibrate(data);
> > + enable_irq(data->irq);
> > +
> > + if (tries == 0) {
> > + dev_err(dev, "Failed to calibrate. Timeout.\n");
> > + ret = -ETIMEDOUT;
> > + }
> > + return sprintf(buf, "calibration finish\n");
> > +}
> > +
> > +
> > +static ssize_t elan_sysfs_read_baseline(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + int max_baseline, min_baseline;
> > +
> > + disable_irq(data->irq);
> > + elan_enable_calibrate(data);
> > + msleep(250);
> > + max_baseline = elan_get_max_baseline(data);
> > + min_baseline = elan_get_min_baseline(data);
> > + elan_disable_calibrate(data);
> > + enable_irq(data->irq);
> > + return sprintf(buf, "max:%d min:%d\n", max_baseline, min_baseline);
> > +}
> > +
> > +static ssize_t elan_sysfs_reinitialize(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + disable_irq(data->irq);
> > + ret = elan_initialize(data);
> > + enable_irq(data->irq);
> > +
> > + if (ret < 0)
> > + return sprintf(buf, "reinitialize fail\n");
> > +
> > + return sprintf(buf, "reinitialize success\n");
>
> This actually should be a write method and instead of returning strings
> you should use return code to indicate success/failure.
>
> > +}
> > +
> > +static DEVICE_ATTR(unique_id, S_IRUGO, elan_sysfs_read_unique_id, NULL);
> > +static DEVICE_ATTR(firmware_version, S_IRUGO, elan_sysfs_read_fw_ver,
> NULL);
> > +static DEVICE_ATTR(sample_version, S_IRUGO, elan_sysfs_read_sm_ver,
> NULL);
> > +static DEVICE_ATTR(driver_version, S_IRUGO, elan_sysfs_read_driver_ver,
> NULL);
> > +static DEVICE_ATTR(iap_version, S_IRUGO, elan_sysfs_read_iap_ver, NULL);
> > +static DEVICE_ATTR(fw_checksum, S_IRUGO, elan_sysfs_read_fw_checksum,
> NULL);
> > +static DEVICE_ATTR(open_info, S_IRUGO, elan_sysfs_enable_detailinfo,
> NULL);
> > +static DEVICE_ATTR(baseline, S_IRUGO, elan_sysfs_read_baseline, NULL);
> > +static DEVICE_ATTR(reinitialize, S_IRUGO, elan_sysfs_reinitialize, NULL);
> > +static DEVICE_ATTR(calibrate, S_IRUGO, elan_sysfs_calibrate, NULL);
> > +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, elan_sysfs_update_fw);
> > +
> > +static struct attribute *elan_sysfs_entries[] = {
> > + &dev_attr_unique_id.attr,
> > + &dev_attr_firmware_version.attr,
> > + &dev_attr_sample_version.attr,
> > + &dev_attr_driver_version.attr,
> > + &dev_attr_iap_version.attr,
> > + &dev_attr_fw_checksum.attr,
> > + &dev_attr_open_info.attr,
> > + &dev_attr_baseline.attr,
> > + &dev_attr_reinitialize.attr,
> > + &dev_attr_calibrate.attr,
> > + &dev_attr_update_fw.attr,
> > + NULL,
>
> Please add documentation for sysfs entries to Documentation/...
>
> > +};
> > +
> > +static const struct attribute_group elan_sysfs_group = {
> > + .attrs = elan_sysfs_entries,
> > +};
> > +
> > +/*****************************************************************
> > +* Elan isr functions
> > +******************************************************************
> > +*/
> > +
> > +static int elan_check_packet(struct elan_tp_data *data, u8 *packet)
> > +{
> > + u8 rid;
> > +
> > + if (data->smbus)
> > + rid = packet[0];
> > + else
> > + rid = packet[ETP_I2C_REPORT_ID_OFFSET];
> > +
> > + /* check report id */
> > + if (rid != ETP_REPORT_ID) {
> > + dev_err(&data->client->dev, "report id [%x] fail.\n", rid);
> > + return -1;
> > + }
> > + return 0;
> > +}
> > +
> > +static void elan_report_absolute(struct elan_tp_data *data, u8 *packet)
> > +{
> > + struct input_dev *input = data->input;
> > + u8 *finger_data;
> > + bool finger_on;
> > + int pos_x, pos_y;
> > + int pressure, mk_x, mk_y;
> > + int i, area_x, area_y, major, minor, new_pressure;
> > + int finger_count = 0;
> > + int btn_click;
> > + u8 tp_info;
> > +
> > + if (data->smbus) {
> > + finger_data = &packet[ETP_SMBUS_FINGER_DATA_OFFSET];
> > + tp_info = packet[1];
> > + } else {
> > + finger_data = &packet[ETP_I2C_FINGER_DATA_OFFSET];
> > + tp_info = packet[3];
> > + }
> > +
> > + btn_click = (tp_info & 0x01);
> > + for (i = 0; i < ETP_MAX_FINGERS; i++) {
> > + finger_on = (tp_info >> (3 + i)) & 0x01;
> > +
> > + /* analyze touched finger raw data*/
> > + if (finger_on) {
> > + pos_x = ((finger_data[0] & 0xf0) << 4) |
> > + finger_data[1];
> > + pos_y = ((finger_data[0] & 0x0f) << 8) |
> > + finger_data[2];
> > + pos_y = data->max_y - pos_y;
> > + mk_x = (finger_data[3] & 0x0f);
> > + mk_y = (finger_data[3] >> 4);
> > + pressure = finger_data[4];
> > +
> > + /*
> > + to avoid fat finger be as palm, so reduce the
> > + width x and y per trace
> > + */
> > + area_x = mk_x * (data->width_x - ETP_FWIDTH_REDUCE);
> > + area_y = mk_y * (data->width_y - ETP_FWIDTH_REDUCE);
> > +
> > + major = max(area_x, area_y);
> > + minor = min(area_x, area_y);
> > +
> > + new_pressure = pressure + ETP_PRESSURE_OFFSET;
> > + if (new_pressure > ETP_MAX_PRESSURE)
> > + new_pressure = ETP_MAX_PRESSURE;
> > +
> > + input_mt_slot(input, i);
> > + input_mt_report_slot_state(input, MT_TOOL_FINGER,
> > + true);
> > + input_report_abs(input, ABS_MT_POSITION_X, pos_x);
> > + input_report_abs(input, ABS_MT_POSITION_Y, pos_y);
> > + input_report_abs(input, ABS_MT_PRESSURE, new_pressure);
> > + input_report_abs(input, ABS_TOOL_WIDTH, mk_x);
> > + input_report_abs(input, ABS_MT_TOUCH_MAJOR, major);
> > + input_report_abs(input, ABS_MT_TOUCH_MINOR, minor);
> > + finger_data += ETP_FINGER_DATA_LEN;
> > + finger_count++;
> > + } else {
> > + input_mt_slot(input, i);
> > + input_mt_report_slot_state(input,
> > + MT_TOOL_FINGER, false);
> > + }
> > + }
> > +
> > + input_report_key(input, BTN_LEFT, (btn_click == 1));
> > + input_mt_report_pointer_emulation(input, true);
> > + input_sync(input);
> > +}
> > +
> > +static irqreturn_t elan_isr(int irq, void *dev_id)
> > +{
> > + struct elan_tp_data *data = dev_id;
> > + u8 raw[ETP_MAX_REPORT_LEN];
> > + int retval;
> > + int report_len;
> > +
> > + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> > + if (retval)
> > + return IRQ_HANDLED;
> > +
> > + /*
> > + Only in I2C protocol, when IAP all page wrote finish, driver will
> > + get one INT signal from high to low, and driver must get 0000
> > + to confirm IAP is finished.
> > + */
> > + if (data->updated_fw) {
> > + retval = i2c_master_recv(data->client, raw,
> > + ETP_INF_LENGTH);
> > + if (retval == 2 && !le16_to_cpup((__le16 *)raw)) {
> > + dev_info(&data->client->dev,
> > + "reinitializing after F/W update...");
> > + elan_initialize(data);
> > + }
> > + data->updated_fw = false;
> > + goto elan_isr_end;
> > + }
> > +
> > + if (data->smbus) {
> > + report_len = ETP_SMBUS_REPORT_LEN;
> > + retval = i2c_smbus_read_block_data(data->client,
> > + ETP_SMBUS_PACKET_QUERY, raw);
> > + } else {
> > + report_len = ETP_I2C_REPORT_LEN;
> > + retval = i2c_master_recv(data->client, raw, report_len);
> > + }
> > +
> > + if (retval != report_len) {
> > + dev_err(&data->client->dev, "wrong packet len(%d)", retval);
> > + goto elan_isr_end;
> > + }
> > +
> > + if (elan_check_packet(data, raw) < 0) {
> > + dev_err(&data->client->dev, "wrong packet format.");
> > + goto elan_isr_end;
> > + }
> > + elan_report_absolute(data, raw);
> > + data->dbfs_buffer.bfetch = false;
> > + memcpy(data->dbfs_buffer.buffer, raw, report_len);
> > +
> > +elan_isr_end:
> > + mutex_unlock(&data->dbfs_mutex);
> > + return IRQ_HANDLED;
> > +}
> > +
> > +
> > +static int elan_input_dev_create(struct elan_tp_data *data)
> > +{
> > + struct i2c_client *client = data->client;
> > + struct input_dev *input;
> > + unsigned int x_res, y_res;
> > + int ret;
> > +
> > + data->input = input = input_allocate_device();
> > + if (!input)
> > + return -ENOMEM;
> > + input->name = "Elan Touchpad";
> > + input->id.bustype = BUS_I2C;
> > + input->dev.parent = &data->client->dev;
> > +
> > + __set_bit(INPUT_PROP_POINTER, input->propbit);
> > + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> > + __set_bit(EV_KEY, input->evbit);
> > + __set_bit(EV_ABS, input->evbit);
> > +
> > + __set_bit(BTN_LEFT, input->keybit);
> > + __set_bit(BTN_TOUCH, input->keybit);
> > + __set_bit(BTN_TOOL_FINGER, input->keybit);
> > + __set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
> > + __set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
> > + __set_bit(BTN_TOOL_QUADTAP, input->keybit);
> > + __set_bit(BTN_TOOL_QUINTTAP, input->keybit);
> > +
> > + __set_bit(ABS_MT_TOUCH_MAJOR, input->absbit);
> > + __set_bit(ABS_MT_TOUCH_MINOR, input->absbit);
> > + __set_bit(ABS_MT_POSITION_X, input->absbit);
> > + __set_bit(ABS_MT_POSITION_Y, input->absbit);
> > +
> > + data->unique_id = elan_get_unique_id(data);
> > + data->fw_version = elan_get_fw_version(data);
> > + data->sm_version = elan_get_sm_version(data);
> > + data->iap_version = elan_get_iap_version(data);
> > + data->max_x = elan_get_x_max(data);
> > + data->max_y = elan_get_y_max(data);
> > + data->width_x = data->max_x / elan_get_x_tracenum(data);
> > + data->width_y = data->max_y / elan_get_y_tracenum(data);
> > + x_res = elan_get_x_resolution(data);
> > + y_res = elan_get_y_resolution(data);
> > +
> > + dev_info(&client->dev,
> > + "Elan Touchpad Information:\n"
> > + " Module unique ID: 0x%04x\n"
> > + " Firmware Version: 0x%04x\n"
> > + " Sample Version: 0x%04x\n"
> > + " IAP Version: 0x%04x\n"
> > + " Max ABS X,Y: %d,%d\n"
> > + " Width X,Y: %d,%d\n"
> > + " Resolution X,Y: %d,%d (dots/mm)\n",
> > + data->unique_id,
> > + data->fw_version,
> > + data->sm_version,
> > + data->iap_version,
> > + data->max_x, data->max_y,
> > + data->width_x, data->width_y,
> > + (char)x_res, (char)y_res);
>
> Why cast to char?
>
> > +
> > + input_set_abs_params(input, ABS_X, 0, data->max_x, 0, 0);
> > + input_set_abs_params(input, ABS_Y, 0, data->max_y, 0, 0);
> > + input_abs_set_res(input, ABS_X, x_res);
> > + input_abs_set_res(input, ABS_Y, y_res);
> > + input_set_abs_params(input, ABS_PRESSURE, 0, ETP_MAX_PRESSURE,
> 0, 0);
> > + input_set_abs_params(input, ABS_TOOL_WIDTH, 0, ETP_FINGER_WIDTH,
> 0, 0);
> > +
> > + /* handle pointer emulation and unused slots in core */
> > + ret = input_mt_init_slots(input, ETP_MAX_FINGERS,
> > + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED);
> > + if (ret) {
> > + dev_info(&client->dev, "allocate MT slots failed, %d\n", ret);
> > + goto err_free_device;
> > + }
> > + input_set_abs_params(input, ABS_MT_POSITION_X, 0, data->max_x, 0, 0);
> > + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, data->max_y, 0, 0);
> > + input_abs_set_res(input, ABS_MT_POSITION_X, x_res);
> > + input_abs_set_res(input, ABS_MT_POSITION_Y, y_res);
> > + input_set_abs_params(input, ABS_MT_PRESSURE, 0,
> > + ETP_MAX_PRESSURE, 0, 0);
> > + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0,
> > + ETP_FINGER_WIDTH * max(data->width_x, data->width_y), 0, 0);
> > + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0,
> > + ETP_FINGER_WIDTH * min(data->width_x, data->width_y), 0, 0);
> > +
> > + /* Register the device in input subsystem */
> > + ret = input_register_device(input);
> > + if (ret) {
> > + dev_err(&client->dev, "input device register failed, %d\n",
> > + ret);
> > + goto err_free_device;
> > + }
> > +
> > + return 0;
> > +
> > +err_free_device:
> > + input_free_device(input);
> > + return ret;
> > +}
> > +
> > +static u8 elan_check_adapter_functionality(struct i2c_client *client)
> > +{
> > + u8 ret = ELAN_ADAPTER_FUNC_NONE;
> > +
> > + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
> > + ret |= ELAN_ADAPTER_FUNC_I2C;
> > + if (i2c_check_functionality(client->adapter,
> > + I2C_FUNC_SMBUS_BYTE_DATA |
> > + I2C_FUNC_SMBUS_BLOCK_DATA |
> > + I2C_FUNC_SMBUS_I2C_BLOCK))
> > + ret |= ELAN_ADAPTER_FUNC_SMBUS;
> > + return ret;
> > +}
> > +
> > +static int elan_probe(struct i2c_client *client,
> > + const struct i2c_device_id *dev_id)
> > +{
> > + struct elan_tp_data *data;
> > + int ret;
> > + u8 adapter_func;
> > + union i2c_smbus_data dummy;
> > + struct device *dev = &client->dev;
> > +
> > + adapter_func = elan_check_adapter_functionality(client);
> > + if (adapter_func == ELAN_ADAPTER_FUNC_NONE) {
> > + dev_err(dev, "not a supported I2C/SMBus adapter\n");
> > + return -EIO;
> > + }
> > +
> > + /* Make sure there is something at this address */
> > + if (dev->of_node && i2c_smbus_xfer(client->adapter, client->addr, 0,
> > + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0)
> > + return -ENODEV;
> > +
> > + data = kzalloc(sizeof(struct elan_tp_data), GFP_KERNEL);
> > + if (!data)
> > + return -ENOMEM;
> > +
> > + /* check protocol type */
> > + if (adapter_func == ELAN_ADAPTER_FUNC_SMBUS)
> > + data->smbus = true;
> > + else
> > + data->smbus = false;
> > + data->client = client;
> > + data->updated_fw = false;
> > + data->enable_detail_info = false;
> > + data->irq = client->irq;
> > +
> > + ret = elan_dbfs_init(data);
> > + if (ret < 0) {
> > + dev_err(&client->dev, "error create elan debugfs.\n");
> > + goto err_dbfs_init;
> > + }
> > + ret = request_threaded_irq(client->irq, NULL, elan_isr,
> > + IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> > + client->name, data);
> > + if (ret < 0) {
> > + dev_err(&client->dev, "cannot register irq=%d\n",
> > + client->irq);
> > + goto err_irq;
> > + }
> > +
> > + /* initial elan touch pad */
> > + ret = elan_initialize(data);
> > + if (ret < 0)
> > + goto err_init;
> > +
> > + /* create input device */
> > + ret = elan_input_dev_create(data);
> > + if (ret < 0)
> > + goto err_input_dev;
> > +
> > + device_init_wakeup(&client->dev, 1);
> > + ret = sysfs_create_group(&client->dev.kobj, &elan_sysfs_group);
> > + if (ret < 0) {
> > + dev_err(&client->dev, "cannot register dev attribute %d", ret);
> > + goto err_create_group;
> > + }
> > + i2c_set_clientdata(client, data);
> > + return 0;
> > +
> > +err_create_group:
> > + input_free_device(data->input);
>
> The device is registered here so you need to call unregister, not free.
>
> > +err_input_dev:
> > +err_init:
> > + free_irq(data->irq, data);
> > +err_irq:
> > + debugfs_remove_recursive(data->dbfs_root);
> > + mutex_destroy(&data->dbfs_mutex);
> > +err_dbfs_init:
> > + kfree(data);
> > + dev_err(&client->dev, "Elan Trackpad probe fail!\n");
> > + return ret;
> > +}
> > +
> > +static int elan_remove(struct i2c_client *client)
> > +{
> > + struct elan_tp_data *data = i2c_get_clientdata(client);
> > +
> > + free_irq(data->irq, data);
> > + debugfs_remove_recursive(data->dbfs_root);
> > + mutex_destroy(&data->dbfs_mutex);
> > +
> > + input_free_device(data->input);
>
> Not needed (and harmful here).
>
> > + input_unregister_device(data->input);
> > + kfree(data);
> > + sysfs_remove_group(&client->dev.kobj, &elan_sysfs_group);
>
> I'd start with removing sysfs attributes as first thing in remove().
>
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +static int elan_suspend(struct device *dev)
> > +{
> > + int ret;
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > +
> > + disable_irq(data->irq);
> > + if (data->smbus)
> > + ret = i2c_smbus_write_byte(data->client,
> > + ETP_SMBUS_DISABLE_TP);
> > + else
> > + ret = elan_i2c_sleep(data->client);
> > +
> > + if (ret < 0) {
> > + dev_err(dev, "suspend mode failed, %d\n", ret);
> > + } else {
> > + if (device_may_wakeup(dev))
> > + data->irq_wake = (enable_irq_wake(data->irq) == 0);
> > + }
> > + return 0;
> > +}
> > +
> > +static int elan_resume(struct device *dev)
> > +{
> > + int ret = 0;
> > + struct elan_tp_data *data = dev_get_drvdata(dev);
> > +
> > + if (device_may_wakeup(dev) && data->irq_wake)
> > + disable_irq_wake(data->irq);
> > +
> > + ret = elan_initialize(data);
> > + if (ret < 0)
> > + dev_err(dev, "resume active power failed, %d\n", ret);
> > +
> > + enable_irq(data->irq);
> > + return 0;
> > +}
> > +#endif
> > +
> > +static SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume);
> > +
> > +static const struct i2c_device_id elan_id[] = {
> > + { DRIVER_NAME, 0 },
> > + { },
> > +};
> > +MODULE_DEVICE_TABLE(i2c, elan_id);
> > +
> > +static struct i2c_driver elan_driver = {
> > + .driver = {
> > + .name = DRIVER_NAME,
> > + .owner = THIS_MODULE,
> > + .pm = &elan_pm_ops,
> > + },
> > + .probe = elan_probe,
> > + .remove = elan_remove,
> > + .id_table = elan_id,
> > +};
> > +
> > +
> > +static int __init elan_init(void)
> > +{
> > + int ret;
> > + ret = i2c_add_driver(&elan_driver);
> > + if (ret) {
> > + pr_err("elan driver register FAILED.\n");
> > + return ret;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void __exit elan_exit(void)
> > +{
> > + i2c_del_driver(&elan_driver);
> > +}
> > +
> > +module_init(elan_init);
> > +module_exit(elan_exit);
>
> module_i2c_driver().
>
> > +
> > +MODULE_AUTHOR("Duson Lin <dusonlin@emc.com.tw>");
> > +MODULE_DESCRIPTION("Elan I2C/SMBus Touchpad driver");
> > +MODULE_LICENSE("GPL");
> > --
> > 1.7.10.4
> >
>
> Thanks.
>
> --
> Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH] input synaptics-rmi4: Bug fixes to ATTN GPIO handling.
From: Christopher Heiny @ 2013-12-17 3:45 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Linux Input, Christopher Heiny, Andrew Duggan, Vincent Huang,
Vivian Ly, Daniel Rosenberg, Jean Delvare, Joerie de Gram,
Linus Walleij, Benjamin Tissoires
This patch fixes two bugs in handling of the RMI4 attention line GPIO.
1) in enable_sensor(), make sure the attn_gpio is defined before attempting to
get its value.
2) in rmi_driver_probe(), declare the name of the attn_gpio, then
request the attn_gpio before attempting to export it. As an added bonus,
the code relating to the export is tidied up.
Signed-off-by: Christopher Heiny <cheiny@synaptics.com>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
This patch implements changes to the synaptics-rmi4 branch of
Dmitry's input tree. The base for the patch is commit
e0c5aec5e6144ae8391d164e2dc659f8ef2b2ba7.
drivers/input/rmi4/rmi_driver.c | 37 ++++++++++++++++++++++---------------
1 file changed, 22 insertions(+), 15 deletions(-)
diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
index a30c7d3..030e8d5 100644
--- a/drivers/input/rmi4/rmi_driver.c
+++ b/drivers/input/rmi4/rmi_driver.c
@@ -169,7 +169,7 @@ static int enable_sensor(struct rmi_device *rmi_dev)
data->enabled = true;
- if (!pdata->level_triggered &&
+ if (pdata->attn_gpio && !pdata->level_triggered &&
gpio_get_value(pdata->attn_gpio) == pdata->attn_polarity)
retval = process_interrupt_requests(rmi_dev);
@@ -807,6 +807,9 @@ static int rmi_driver_remove(struct device *dev)
return 0;
}
+
+static const char *GPIO_LABEL = "attn";
+
static int rmi_driver_probe(struct device *dev)
{
struct rmi_driver *rmi_driver;
@@ -959,20 +962,24 @@ static int rmi_driver_probe(struct device *dev)
}
if (IS_ENABLED(CONFIG_RMI4_DEV) && pdata->attn_gpio) {
- retval = gpio_export(pdata->attn_gpio, false);
- if (retval) {
- dev_warn(dev, "WARNING: Failed to export ATTN gpio!\n");
- retval = 0;
- } else {
- retval = gpio_export_link(dev,
- "attn", pdata->attn_gpio);
- if (retval) {
- dev_warn(dev,
- "WARNING: Failed to symlink ATTN gpio!\n");
- retval = 0;
- } else {
- dev_info(dev, "Exported ATTN GPIO %d.",
- pdata->attn_gpio);
+ retval = gpio_request(pdata->attn_gpio, GPIO_LABEL);
+ if (retval)
+ dev_warn(dev, "WARNING: Failed to request ATTN gpio %d, code: %d.\n",
+ pdata->attn_gpio, retval);
+ else {
+ retval = gpio_export(pdata->attn_gpio, false);
+ if (retval)
+ dev_warn(dev, "WARNING: Failed to export ATTN %d, code: %d.\n",
+ pdata->attn_gpio, retval);
+ else {
+ retval = gpio_export_link(dev, "attn",
+ pdata->attn_gpio);
+ if (retval)
+ dev_warn(dev,
+ "WARNING: Failed to symlink ATTN gpio!\n");
+ else
+ dev_info(dev, "Exported ATTN GPIO %d.",
+ pdata->attn_gpio);
}
}
}
^ permalink raw reply related
* [PATCH -next] Input: zforce - fix error return code in zforce_start()
From: Wei Yongjun @ 2013-12-17 3:27 UTC (permalink / raw)
To: dmitry.torokhov, rydberg, heiko; +Cc: yongjun_wei, linux-input
From: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
The error code was not set if unable to set config, so the error
condition wasn't reflected in the return value. Fix to return a
negative error code from the error handling case instead of 0.
Signed-off-by: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
---
drivers/input/touchscreen/zforce_ts.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c
index aa127ba..2175f34 100644
--- a/drivers/input/touchscreen/zforce_ts.c
+++ b/drivers/input/touchscreen/zforce_ts.c
@@ -279,7 +279,8 @@ static int zforce_start(struct zforce_ts *ts)
goto error;
}
- if (zforce_setconfig(ts, SETCONFIG_DUALTOUCH)) {
+ ret = zforce_setconfig(ts, SETCONFIG_DUALTOUCH);
+ if (ret) {
dev_err(&client->dev, "Unable to set config\n");
goto error;
}
^ permalink raw reply related
* [PATCH] input synaptics-rmi4 trivial: Eliminate unused local variable.
From: Christopher Heiny @ 2013-12-17 2:25 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Linux Input, Christopher Heiny, Andrew Duggan, Vincent Huang,
Vivian Ly, Daniel Rosenberg, Benjamin Tissoires
One-line change to eliminate an unused local variable warning in rmi_f11.c.
Signed-off-by: Christopher Heiny <cheiny@synaptics.com>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
This patch implements changes to the synaptics-rmi4 branch of
Dmitry's input tree. The base for the patch is commit
e0c5aec5e6144ae8391d164e2dc659f8ef2b2ba7.
drivers/input/rmi4/rmi_f11.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/input/rmi4/rmi_f11.c b/drivers/input/rmi4/rmi_f11.c
index d63ef31..c2be9de 100644
--- a/drivers/input/rmi4/rmi_f11.c
+++ b/drivers/input/rmi4/rmi_f11.c
@@ -1289,7 +1289,6 @@ static int rmi_f11_register_devices(struct rmi_function *fn)
struct f11_data *f11 = fn->data;
struct input_dev *input_dev;
struct input_dev *input_dev_mouse;
- struct rmi_driver_data *driver_data = dev_get_drvdata(&rmi_dev->dev);
struct rmi_driver *driver = rmi_dev->driver;
struct f11_2d_sensor *sensor = &f11->sensor;
int rc;
^ permalink raw reply related
* Re: [PATCH 6/7] Input: pmic8xxx-keypad - Migrate to devm_* APIs
From: spamassassin system account @ 2013-12-17 2:01 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-kernel, linux-arm-msm, linux-arm-kernel, linux-input
In-Reply-To: <20131216153726.GB8884@core.coreip.homeip.net>
On 12/16, Dmitry Torokhov wrote:
> On Tue, Dec 10, 2013 at 03:43:15PM -0800, Stephen Boyd wrote:
> > -
> > -static int pmic8xxx_kp_remove(struct platform_device *pdev)
> > -{
> > - struct pmic8xxx_kp *kp = platform_get_drvdata(pdev);
> > -
> > - device_init_wakeup(&pdev->dev, 0);
>
> Why are we removing restoring wakeup capable state?
>
It's the only thing blocking removal of the remove callback and
as the driver is being unbound it didn't seem like we cared about
the state of the wakeup of the device. I greped the kernel tree
and I couldn't see a consistent pattern where driver probe was
setting the flag and driver remove was clearing it. Do we need to
keep it?
> > - free_irq(kp->key_stuck_irq, kp);
> > - free_irq(kp->key_sense_irq, kp);
> > - input_unregister_device(kp->input);
> > - kfree(kp);
> > -
> > - return 0;
> > }
> >
> > #ifdef CONFIG_PM_SLEEP
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
^ permalink raw reply
* Re: [PATCH] Input: add i2c/smbus driver for elan touchpad
From: Dmitry Torokhov @ 2013-12-16 21:00 UTC (permalink / raw)
To: Duson Lin; +Cc: linux-input, linux-kernel, agnescheng, phoenix
In-Reply-To: <1385099816-2873-1-git-send-email-dusonlin@emc.com.tw>
Hi Duson,
On Fri, Nov 22, 2013 at 01:56:55PM +0800, Duson Lin wrote:
> This driver adds support for elan i2c/smbus touchpad found on some laptops PC
> ---
> drivers/input/mouse/Kconfig | 10 +
> drivers/input/mouse/Makefile | 1 +
> drivers/input/mouse/elan_i2c.c | 1846 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1857 insertions(+)
> create mode 100644 drivers/input/mouse/elan_i2c.c
>
> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> index effa9c5..8ad4b38 100644
> --- a/drivers/input/mouse/Kconfig
> +++ b/drivers/input/mouse/Kconfig
> @@ -215,6 +215,16 @@ config MOUSE_CYAPA
> To compile this driver as a module, choose M here: the module will be
> called cyapa.
>
> +config MOUSE_ELAN_I2C
> + tristate "ELAN I2C Touchpad support"
> + depends on I2C
> + help
> + This driver adds support for Elan I2C Trackpads.
> + Say y here if you have a ELAN I2C Touchpad.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called elan_i2c.
> +
> config MOUSE_INPORT
> tristate "InPort/MS/ATIXL busmouse"
> depends on ISA
> diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
> index c25efdb..24a12a6 100644
> --- a/drivers/input/mouse/Makefile
> +++ b/drivers/input/mouse/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_MOUSE_APPLETOUCH) += appletouch.o
> obj-$(CONFIG_MOUSE_ATARI) += atarimouse.o
> obj-$(CONFIG_MOUSE_BCM5974) += bcm5974.o
> obj-$(CONFIG_MOUSE_CYAPA) += cyapa.o
> +obj-$(CONFIG_MOUSE_ELAN_I2C) += elan_i2c.o
> obj-$(CONFIG_MOUSE_GPIO) += gpio_mouse.o
> obj-$(CONFIG_MOUSE_INPORT) += inport.o
> obj-$(CONFIG_MOUSE_LOGIBM) += logibm.o
> diff --git a/drivers/input/mouse/elan_i2c.c b/drivers/input/mouse/elan_i2c.c
> new file mode 100644
> index 0000000..9892ee1
> --- /dev/null
> +++ b/drivers/input/mouse/elan_i2c.c
> @@ -0,0 +1,1846 @@
> +/*
> + * Elan I2C/SMBus Touchpad driver
> + *
> + * Copyright (c) 2013 ELAN Microelectronics Corp.
> + *
> + * Author: 林政維 (Duson Lin) <dusonlin@emc.com.tw>
> + * Version: 1.4.6
> + *
> + * 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.
> + *
> + * Trademarks are the property of their respective owners.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/firmware.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/input/mt.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/debugfs.h>
> +#include <linux/cdev.h>
> +#include <linux/kernel.h>
> +#include <linux/major.h>
> +#include <linux/sched.h>
> +#include <linux/input.h>
> +#include <linux/uaccess.h>
> +#include <linux/jiffies.h>
> +
> +#define DRIVER_NAME "elan_i2c"
> +#define ELAN_DRIVER_VERSION "1.4.6"
> +#define ETP_PRESSURE_OFFSET 25
> +#define ETP_MAX_PRESSURE 255
> +#define ETP_FWIDTH_REDUCE 90
> +#define ETP_FINGER_WIDTH 15
> +
> +#define ELAN_ADAPTER_FUNC_NONE 0
> +#define ELAN_ADAPTER_FUNC_I2C 1
> +#define ELAN_ADAPTER_FUNC_SMBUS 2
> +#define ELAN_ADAPTER_FUNC_BOTH 3
> +
> +/* Length of Elan touchpad information */
> +#define ETP_INF_LENGTH 2
> +#define ETP_MAX_FINGERS 5
> +#define ETP_FINGER_DATA_LEN 5
> +#define ETP_REPORT_ID 0x5D
> +#define ETP_MAX_REPORT_LEN 34
> +#define ETP_ENABLE_ABS 0x0001
> +#define ETP_ENABLE_CALIBRATE 0x0002
> +#define ETP_DISABLE_CALIBRATE 0x0000
> +
> +/* Elan smbus command */
> +#define ETP_SMBUS_IAP_CMD 0x00
> +#define ETP_SMBUS_ENABLE_TP 0x20
> +#define ETP_SMBUS_DISABLE_TP 0x21
> +#define ETP_SMBUS_IAP_PASSWORD_WRITE 0x29
> +#define ETP_SMBUS_IAP_PASSWORD_READ 0x80
> +#define ETP_SMBUS_WRITE_FW_BLOCK 0x2A
> +#define ETP_SMBUS_IAP_RESET_CMD 0x2B
> +#define ETP_SMBUS_RANGE_CMD 0xA0
> +#define ETP_SMBUS_FW_VERSION_CMD 0xA1
> +#define ETP_SMBUS_XY_TRACENUM_CMD 0xA2
> +#define ETP_SMBUS_SM_VERSION_CMD 0xA3
> +#define ETP_SMBUS_UNIQUEID_CMD 0xA3
> +#define ETP_SMBUS_RESOLUTION_CMD 0xA4
> +#define ETP_SMBUS_HELLOPACKET_CMD 0xA7
> +#define ETP_SMBUS_PACKET_QUERY 0xA8
> +#define ETP_SMBUS_IAP_VERSION_CMD 0xAC
> +#define ETP_SMBUS_IAP_CTRL_CMD 0xAD
> +#define ETP_SMBUS_IAP_CHECKSUM_CMD 0xAE
> +#define ETP_SMBUS_FW_CHECKSUM_CMD 0xAF
> +#define ETP_SMBUS_MAX_BASELINE_CMD 0xC3
> +#define ETP_SMBUS_MIN_BASELINE_CMD 0xC4
> +#define ETP_SMBUS_CALIBRATE_QUERY 0xC5
> +#define ETP_SMBUS_REPORT_LEN 32
> +#define ETP_SMBUS_FINGER_DATA_OFFSET 2
> +#define ETP_SMBUS_HELLOPACKET_LEN 5
> +#define ETP_SMBUS_IAP_PASSWORD 0x1234
> +#define ETP_SMBUS_IAP_MODE_ON (1<<6)
> +
> +/* Elan i2c command */
> +#define ETP_I2C_RESET 0x0100
> +#define ETP_I2C_WAKE_UP 0x0800
> +#define ETP_I2C_SLEEP 0x0801
> +#define ETP_I2C_DESC_CMD 0x0001
> +#define ETP_I2C_REPORT_DESC_CMD 0x0002
> +#define ETP_I2C_STAND_CMD 0x0005
> +#define ETP_I2C_UNIQUEID_CMD 0x0101
> +#define ETP_I2C_FW_VERSION_CMD 0x0102
> +#define ETP_I2C_SM_VERSION_CMD 0x0103
> +#define ETP_I2C_XY_TRACENUM_CMD 0x0105
> +#define ETP_I2C_MAX_X_AXIS_CMD 0x0106
> +#define ETP_I2C_MAX_Y_AXIS_CMD 0x0107
> +#define ETP_I2C_RESOLUTION_CMD 0x0108
> +#define ETP_I2C_IAP_VERSION_CMD 0x0110
> +#define ETP_I2C_SET_CMD 0x0300
> +#define ETP_I2C_MAX_BASELINE_CMD 0x0306
> +#define ETP_I2C_MIN_BASELINE_CMD 0x0307
> +#define ETP_I2C_FW_CHECKSUM_CMD 0x030F
> +#define ETP_I2C_IAP_CTRL_CMD 0x0310
> +#define ETP_I2C_IAP_CMD 0x0311
> +#define ETP_I2C_IAP_RESET_CMD 0x0314
> +#define ETP_I2C_IAP_CHECKSUM_CMD 0x0315
> +#define ETP_I2C_CALIBRATE_CMD 0x0316
> +#define ETP_I2C_REPORT_LEN 34
> +#define ETP_I2C_FINGER_DATA_OFFSET 4
> +#define ETP_I2C_REPORT_ID_OFFSET 2
> +#define ETP_I2C_DESC_LENGTH 30
> +#define ETP_I2C_REPORT_DESC_LENGTH 158
> +#define ETP_I2C_IAP_PASSWORD 0x1EA5
> +#define ETP_I2C_IAP_RESET 0xF0F0
> +#define ETP_I2C_MAIN_MODE_ON (1<<9)
> +#define ETP_I2C_IAP_REG_L 0x01
> +#define ETP_I2C_IAP_REG_H 0x06
> +
> +/* IAP F/W updater */
> +#define ETP_FW_NAME "elan_i2c.bin"
> +#define ETP_IAP_VERSION_ADDR 0x0082
> +#define ETP_IAP_START_ADDR 0x0083
> +#define ETP_FW_IAP_PAGE_ERR (1<<5)
> +#define ETP_FW_IAP_INTERFACE_ERR (1<<4)
> +#define ETP_FW_PAGE_SIZE 64
> +#define ETP_FW_PAGE_COUNT 768
> +#define ETP_FW_SIZE (ETP_FW_PAGE_SIZE * ETP_FW_PAGE_COUNT)
> +enum {UNKNOWN_MODE, IAP_MODE, MAIN_MODE};
> +
> +struct dbfs_data {
> + bool bfetch;
> + u8 buffer[ETP_MAX_REPORT_LEN];
> +};
> +
> +/* The main device structure */
> +struct elan_tp_data {
> + struct i2c_client *client;
> + struct input_dev *input;
> + unsigned int max_x;
> + unsigned int max_y;
> + unsigned int width_x;
> + unsigned int width_y;
> + unsigned int irq;
> +
> + /* fields required for IAP firmware updater */
> + u16 unique_id;
> + u16 fw_version;
> + u16 sm_version;
> + u16 iap_version;
> + bool updated_fw;
> + u16 iap_start_addr;
> +
> + /* irq wake is enabled */
> + bool irq_wake;
> + bool smbus;
> + bool enable_detail_info;
> +
> + /* fields required for debug fs */
> + struct mutex dbfs_mutex;
> + struct dentry *dbfs_root;
> + struct dbfs_data dbfs_buffer;
> +};
> +
> +u8 val[256];
> +static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8 *val);
> +static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16 cmd);
> +static int elan_initialize(struct elan_tp_data *data);
> +
> +/*
> + **************************************************************
> + * debugfs interface
> + **************************************************************
> +*/
> +static int elan_dbfs_open(struct inode *inode, struct file *file)
> +{
> + int retval;
> + struct elan_tp_data *data = inode->i_private;
> +
> + if (!data)
> + return -ENODEV;
> +
> + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> + if (retval)
> + return retval;
> +
> + if (!kobject_get(&data->client->dev.kobj)) {
Why are you pinning I2C device kobject? It will not prevent unbinding
the driver if that's what you are aiming for.
> + retval = -ENODEV;
> + goto dbfs_out;
> + }
> +
> + file->private_data = data;
> +dbfs_out:
> + mutex_unlock(&data->dbfs_mutex);
> + return 0;
> +}
> +
> +static int elan_dbfs_release(struct inode *inode, struct file *file)
> +{
> + struct elan_tp_data *data = file->private_data;
> + int retval;
> + if (!data)
> + return -ENODEV;
> +
> + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> + if (retval)
> + return retval;
> + file->private_data = NULL;
> + kobject_put(&data->client->dev.kobj);
> + mutex_unlock(&data->dbfs_mutex);
> + return 0;
> +}
> +
> +
> +static ssize_t elan_dbfs_read(struct file *file,
> + char __user *buffer, size_t count, loff_t *ppos)
> +{
> + struct elan_tp_data *data = file->private_data;
> + int retval;
> + if (!data)
> + return -ENODEV;
> +
> + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> + if (retval)
> + return -EFAULT;
> + if (data->dbfs_buffer.bfetch == false) {
> + if (!copy_to_user(buffer, data->dbfs_buffer.buffer, count)) {
> + data->dbfs_buffer.bfetch = true;
> + retval = count;
> + } else {
> + retval = -2;
-ENOENT? Why?
> + }
> + } else {
> + retval = -4;
-EINTR???
> + }
> + mutex_unlock(&data->dbfs_mutex);
> + return retval;
> +}
> +
> +static ssize_t elan_dbfs_write(struct file *file,
> + const char __user *buffer, size_t count, loff_t *ppos)
> +{
> + struct elan_tp_data *data = file->private_data;
> + int retval;
> + if (!data)
> + return -ENODEV;
> +
> + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> + if (retval)
> + return -EFAULT;
> + retval = count;
> + mutex_unlock(&data->dbfs_mutex);
> + return retval;
> +}
This looks like an unneeded stub.
> +
> +static long elan_dbfs_ioctrl(struct file *file,
> + unsigned int cmd, unsigned long arg)
> +{
> + int retval = 0;
> + struct elan_tp_data *data = file->private_data;
> +
> + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> + if (retval)
> + return retval;
> + mutex_unlock(&data->dbfs_mutex);
> + return retval;
> +}
Same here.
> +
> +static const struct file_operations elan_debug_fops = {
> + .open = elan_dbfs_open,
> + .release = elan_dbfs_release,
> + .read = elan_dbfs_read,
> + .write = elan_dbfs_write,
> + .unlocked_ioctl = elan_dbfs_ioctrl
> +};
> +
> +static int elan_dbfs_init(struct elan_tp_data *data)
> +{
> + /* Create a global debugfs root for all elan devices */
> + /* sys/kernel/debug/elan */
> + data->dbfs_root = debugfs_create_dir("elan", NULL);
> + if (!data->dbfs_root) {
> + dev_err(&data->client->dev, "cannot create dbfs_root.\n");
> + return -ENODEV;
> + }
That should not be done in per-device code. That said, do you really
need to dump your packets - looks like you are done with the driver
development?
> + mutex_init(&data->dbfs_mutex);
> +
> + debugfs_create_file(DRIVER_NAME, 0777,
> + data->dbfs_root, data, &elan_debug_fops);
> + data->dbfs_buffer.bfetch = false;
> + return 0;
> +}
> +
> +/**********************************************************
> + * IAP firmware updater related routines *
> + **********************************************************
> +*/
> +
> +static int elan_iap_getmode(struct elan_tp_data *data)
> +{
> + u16 constant;
> + int retval;
> + struct i2c_client *client = data->client;
> +
> + if (data->smbus) {
> + retval = i2c_smbus_read_block_data(client,
> + ETP_SMBUS_IAP_CTRL_CMD, val);
> + if (retval < 0) {
> + dev_err(&client->dev, "read iap ctrol fail.\n");
> + return UNKNOWN_MODE;
> + }
> + constant = be16_to_cpup((__be16 *)val);
> + dev_dbg(&client->dev, "smbus iap control reg: 0x%04x.\n",
> + constant);
> + if ((constant & ETP_SMBUS_IAP_MODE_ON) == 0x00)
> + return MAIN_MODE;
> + } else {
> + retval = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val);
> + if (retval < 0) {
> + dev_err(&client->dev, "read iap ctrol fail.\n");
> + return UNKNOWN_MODE;
> + }
> + constant = le16_to_cpup((__le16 *)val);
> + dev_dbg(&client->dev, "i2c iap control reg: 0x%04x.\n",
> + constant);
> + if (constant & ETP_I2C_MAIN_MODE_ON)
> + return MAIN_MODE;
> + }
Instead of doing
if (smbus)
do_this();
else
do_that();
everywhere maybe you could define transport functions and invoke them
through a pointer to transport ops?
> +
> + return IAP_MODE;
> +}
> +
> +static int elan_iap_checksum(struct elan_tp_data *data)
> +{
> + int retval = 0;
> + u16 checksum = -1;
> + struct i2c_client *client = data->client;
> +
> + if (data->smbus) {
> + retval = i2c_smbus_read_block_data(client,
> + ETP_SMBUS_IAP_CHECKSUM_CMD, val);
> + if (retval < 0) {
> + dev_err(&client->dev, "Read checksum fail, %d\n",
> + retval);
> + return -1;
> + }
> + checksum = be16_to_cpup((__be16 *)val);
> + } else {
> + retval = elan_i2c_read_cmd(client,
> + ETP_I2C_IAP_CHECKSUM_CMD, val);
> + if (retval < 0) {
> + dev_err(&client->dev, "Read checksum fail, %d\n",
> + retval);
> + return -1;
> + }
> + checksum = le16_to_cpup((__le16 *)val);
> + }
> + return checksum;
> +}
> +
> +static bool elan_iap_reset(struct elan_tp_data *data)
> +{
> + int retval = 0;
> + struct i2c_client *client = data->client;
> +
> + if (data->smbus)
> + retval = i2c_smbus_write_byte(client,
> + ETP_SMBUS_IAP_RESET_CMD);
> + else
> + retval = elan_i2c_write_cmd(client, ETP_I2C_IAP_RESET_CMD,
> + ETP_I2C_IAP_RESET);
> + if (retval < 0) {
> + dev_err(&client->dev, "cannot reset IC, %d\n", retval);
> + return false;
> + }
> + return true;
> +}
> +
> +static bool elan_iap_setflashkey(struct elan_tp_data *data)
> +{
> + int retval = 0;
> + struct i2c_client *client = data->client;
> + u8 smbus_cmd[4] = {0x00, 0x0B, 0x00, 0x5A};
> +
> + if (data->smbus)
> + retval = i2c_smbus_write_block_data(client,
> + ETP_SMBUS_IAP_CMD, 4, smbus_cmd);
> + else
> + retval = elan_i2c_write_cmd(client, ETP_I2C_IAP_CMD,
> + ETP_I2C_IAP_PASSWORD);
> + if (retval < 0) {
> + dev_err(&client->dev, "cannot set flash key, %d\n", retval);
> + return false;
> + }
> +
> + return true;
> +}
> +
> +static int elan_check_fw(struct elan_tp_data *data,
> + const struct firmware *fw)
> +{
> + struct device *dev = &data->client->dev;
> +
> + /* Firmware must match exact PAGE_NUM * PAGE_SIZE bytes */
> + if (fw->size != ETP_FW_SIZE) {
> + dev_err(dev, "invalid firmware size = %zu, expected %d.\n",
> + fw->size, ETP_FW_SIZE);
> + return -EBADF;
> + }
> +
> + /* Get IAP Start Address*/
> + memcpy(val, &fw->data[ETP_IAP_START_ADDR * 2], 2);
> + data->iap_start_addr = le16_to_cpup((__le16 *)val);
> + return 0;
> +}
> +
> +
> +static int elan_smbus_prepare_fw_update(struct elan_tp_data *data)
> +{
> + struct i2c_client *client = data->client;
> + struct device *dev = &data->client->dev;
> + u16 password;
> + u8 cmd[4] = {0x0F, 0x78, 0x00, 0x06};
> +
> + /* Get FW in which mode (IAP_MODE/MAIN_MODE) */
> + int mode = elan_iap_getmode(data);
> + if (mode == UNKNOWN_MODE)
> + return -1;
> +
> + if (mode == MAIN_MODE) {
> +
> + /* set flash key*/
> + if (elan_iap_setflashkey(data) == false) {
> + dev_err(dev, "cannot set flash key\n");
> + return -1;
> + }
> +
> + /* write iap password */
> + if (i2c_smbus_write_byte(client,
> + ETP_SMBUS_IAP_PASSWORD_WRITE) < 0) {
> + dev_err(dev, "cannot write iap password\n");
> + return -1;
Would be nice using appropriate error codes. EIO?
> + }
> +
> + if (i2c_smbus_write_block_data(client,
> + ETP_SMBUS_IAP_CMD, 4, cmd) < 0) {
> + dev_err(dev, "cannot write cmd\n");
> + return -1;
> + }
> +
> + /* read password to check we enabled successfully. */
> + if (i2c_smbus_read_block_data(client,
> + ETP_SMBUS_IAP_PASSWORD_READ, val) < 0) {
> + dev_err(dev, "cannot get iap password\n");
> + return -1;
> + }
> + password = be16_to_cpup((__be16 *)val);
> +
> + if (password != ETP_SMBUS_IAP_PASSWORD) {
> + dev_err(dev, "wrong iap password = 0x%X\n", password);
> + return -1;
> + }
> + /* wait 30ms, from MAIN_MODE change to IAP_MODE*/
> + msleep(30);
> + }
> +
> + /* set flash key*/
> + if (elan_iap_setflashkey(data) == false) {
> + dev_err(dev, "cannot set flash key\n");
> + return -1;
> + }
> +
> + /* Reset IC */
> + if (elan_iap_reset(data) == false) {
> + dev_err(dev, "iap reset fail.\n");
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int elan_i2c_prepare_fw_update(struct elan_tp_data *data)
> +{
> + struct i2c_client *client = data->client;
> + struct device *dev = &data->client->dev;
> + u16 password;
> +
> + /* Get FW in which mode (IAP_MODE/MAIN_MODE) */
> + int mode = elan_iap_getmode(data);
> + if (mode == UNKNOWN_MODE)
> + return -1;
> +
> + if (mode == IAP_MODE) {
> + /* Reset IC */
> + if (elan_iap_reset(data) == false)
> + return -1;
> + msleep(30);
> + }
> +
> + /* set flash key*/
> + if (elan_iap_setflashkey(data) == false) {
> + dev_err(dev, "cannot set flash key\n");
> + return -1;
> + }
> +
> + /* Wait for F/W IAP initialization */
> + if (mode == MAIN_MODE)
> + msleep(100);
> + else
> + msleep(30);
> +
> + /* check is in iap mode or not*/
> + if (elan_iap_getmode(data) == MAIN_MODE) {
> + dev_err(dev, "status wrong.\n");
> + return -1;
> + }
> +
> + /* set flash key again */
> + if (elan_iap_setflashkey(data) == false) {
> + dev_err(dev, "cannot set flash key\n");
> + return -1;
> + }
> +
> + /* Wait for F/W IAP initialization */
> + if (mode == MAIN_MODE)
> + msleep(100);
> + else
> + msleep(30);
> +
> + /* read back to check we actually enabled successfully. */
> + if (elan_i2c_read_cmd(client, ETP_I2C_IAP_CMD, val) < 0) {
> + dev_err(dev, "cannot get iap register\n");
> + return -1;
> + }
> + password = le16_to_cpup((__le16 *)val);
> +
> + if (password != ETP_I2C_IAP_PASSWORD) {
> + dev_err(dev, "wrong iap password = 0x%X\n", password);
> + return -1;
> + }
> + return 0;
> +}
> +
> +static bool elan_iap_page_write_ok(struct elan_tp_data *data)
> +{
> + u16 constant;
> + int retval = 0;
> + struct i2c_client *client = data->client;
> +
> +
> + if (data->smbus) {
> + retval = i2c_smbus_read_block_data(client,
> + ETP_SMBUS_IAP_CTRL_CMD, val);
> + if (retval < 0)
> + return false;
> + constant = be16_to_cpup((__be16 *)val);
> + } else {
> + retval = elan_i2c_read_cmd(client,
> + ETP_I2C_IAP_CTRL_CMD, val);
> + if (retval < 0)
> + return false;
> + constant = le16_to_cpup((__le16 *)val);
> + }
> +
> + if (constant & ETP_FW_IAP_PAGE_ERR)
> + return false;
> +
> + if (constant & ETP_FW_IAP_INTERFACE_ERR)
> + return false;
> + return true;
> +}
> +
> +static int elan_smbus_write_fw_block(struct elan_tp_data *data,
> + const u8 *page, u16 checksum, int idx)
> +{
> + struct device *dev = &data->client->dev;
> + int half_page_size = ETP_FW_PAGE_SIZE / 2;
> + int repeat = 3;
> +
> + do {
> + /* due to smbus can write 32 bytes one time,
> + so, we must write data 2 times.
> + */
> + i2c_smbus_write_block_data(data->client,
> + ETP_SMBUS_WRITE_FW_BLOCK,
> + half_page_size,
> + page);
> + i2c_smbus_write_block_data(data->client,
> + ETP_SMBUS_WRITE_FW_BLOCK,
> + half_page_size,
> + (page + half_page_size));
> + /* Wait for F/W to update one page ROM data. */
> + usleep_range(8000, 10000);
> + if (elan_iap_page_write_ok(data))
> + break;
> + dev_info(dev, "IAP retry this page! [%d]\n", idx);
> + repeat--;
> + } while (repeat == 0);
> +
> + if (repeat > 0)
> + return 0;
> + return -1;
> +
> +}
> +
> +static int elan_i2c_write_fw_block(struct elan_tp_data *data,
> + const u8 *page, u16 checksum, int idx)
> +{
> + struct device *dev = &data->client->dev;
> + int ret;
> + int repeat = 3;
> + u8 page_store[ETP_FW_PAGE_SIZE + 4];
> +
> + page_store[0] = ETP_I2C_IAP_REG_L;
> + page_store[1] = ETP_I2C_IAP_REG_H;
> + memcpy(&page_store[2], page, ETP_FW_PAGE_SIZE);
> +
> + /* recode checksum at last two bytes */
> + page_store[ETP_FW_PAGE_SIZE+2] = (u8)(checksum & 0xFF);
> + page_store[ETP_FW_PAGE_SIZE+3] = (u8)((checksum >> 8)&0xFF);
> +
> + do {
> + ret = i2c_master_send(data->client, page_store,
> + ETP_FW_PAGE_SIZE + 4);
> +
> + /* Wait for F/W to update one page ROM data. */
> + msleep(20);
> +
> + if (ret == (ETP_FW_PAGE_SIZE + 4)) {
> + if (elan_iap_page_write_ok(data))
> + break;
> + }
> + dev_dbg(dev, "IAP retry this page! [%d]\n", idx);
> + repeat--;
> + } while (repeat == 0);
> +
> + if (repeat > 0)
> + return 0;
> + return -1;
> +}
> +
> +static int elan_write_fw_block(struct elan_tp_data *data,
> + const u8 *page, u16 checksum, int idx)
> +{
> + int ret;
> + if (data->smbus)
> + ret = elan_smbus_write_fw_block(data, page, checksum, idx);
> + else
> + ret = elan_i2c_write_fw_block(data, page, checksum, idx);
> + return ret;
> +}
> +
> +static int elan_prepare_fw_update(struct elan_tp_data *data)
> +{
> + int ret = 0;
> + if (data->smbus)
> + ret = elan_smbus_prepare_fw_update(data);
> + else
> + ret = elan_i2c_prepare_fw_update(data);
> + return ret;
> +}
> +
> +static int elan_firmware(struct elan_tp_data *data)
> +{
> + struct device *dev = &data->client->dev;
> + const struct firmware *fw;
> + const char *fw_name = ETP_FW_NAME;
> + int i, j, ret;
> + u16 boot_page_count;
> + u16 sw_checksum, fw_checksum;
> + data->updated_fw = true;
> +
> + dev_info(dev, "Start firmware update....\n");
> +
> + ret = request_firmware(&fw, ETP_FW_NAME, dev);
> + if (ret) {
> + dev_err(dev, "cannot load firmware from %s, %d\n",
> + fw_name, ret);
> + goto done;
> + }
> + /* check fw data match current iap version */
> + ret = elan_check_fw(data, fw);
> + if (ret) {
> + dev_err(dev, "Invalid Elan firmware from %s, %d\n",
> + fw_name, ret);
> + goto done;
> + }
> + /* setup IAP status */
> + ret = elan_prepare_fw_update(data);
> + if (ret)
> + goto done;
> + sw_checksum = 0;
> + fw_checksum = 0;
> + boot_page_count = (data->iap_start_addr * 2) / ETP_FW_PAGE_SIZE;
> + for (i = boot_page_count; i < ETP_FW_PAGE_COUNT; i++) {
> + u16 checksum = 0;
> + const u8 *page = &fw->data[i * ETP_FW_PAGE_SIZE];
> +
> + for (j = 0; j < ETP_FW_PAGE_SIZE; j += 2)
> + checksum += ((page[j + 1] << 8) | page[j]);
> +
> + ret = elan_write_fw_block(data, page, checksum, i);
> + if (ret) {
> + dev_err(dev, "write page %d fail\n", i);
> + goto done;
> + }
> + sw_checksum += checksum;
> + }
> +
> + /* Wait WDT reset and power on reset */
> + msleep(600);
> +
> + /* check checksum */
> + fw_checksum = elan_iap_checksum(data);
> + if (sw_checksum != fw_checksum) {
> + dev_err(dev, "checksum diff sw=[%04X], fw=[%04X]\n",
> + sw_checksum, fw_checksum);
> + ret = -1;
> + goto done;
> + }
> + ret = 0;
> +done:
> + if (ret != 0) {
> + elan_iap_reset(data);
> + data->updated_fw = false;
> + } else {
> + if (data->smbus) {
> + data->updated_fw = false;
> + elan_initialize(data);
> + }
> + }
> + release_firmware(fw);
> + return ret;
> +}
> +
> +/******************************************************************
> +* Elan smbus interface
> +*******************************************************************
> +*/
> +static int elan_smbus_initialize(struct i2c_client *client)
> +{
> + u8 check[ETP_SMBUS_HELLOPACKET_LEN] = {0x55, 0x55, 0x55, 0x55, 0x55};
> + u8 values[ETP_SMBUS_HELLOPACKET_LEN] = {0, 0, 0, 0, 0};
> + int ret;
> +
> + /* Get hello packet */
> + ret = i2c_smbus_read_block_data(client,
> + ETP_SMBUS_HELLOPACKET_CMD, values);
> + if (ret != ETP_SMBUS_HELLOPACKET_LEN) {
> + dev_err(&client->dev, "hello packet length fail\n");
> + return -1;
> + }
> +
> + /* compare hello packet */
> + if (memcmp(values, check, ETP_SMBUS_HELLOPACKET_LEN)) {
> + dev_err(&client->dev, "hello packet fail [%x %x %x %x %x]\n",
> + values[0], values[1], values[2], values[3], values[4]);
> + return -1;
> + }
> +
> + /* enable tp */
> + ret = i2c_smbus_write_byte(client, ETP_SMBUS_ENABLE_TP);
> + return ret;
> +}
> +
> +static int elan_smbus_enable_calibrate(struct i2c_client *client)
> +{
> + u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS|ETP_ENABLE_CALIBRATE};
> +
> + return i2c_smbus_write_block_data(client,
> + ETP_SMBUS_IAP_CMD, 4, cmd);
> +}
> +
> +static int elan_smbus_disable_calibrate(struct i2c_client *client)
> +{
> + u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS|ETP_DISABLE_CALIBRATE};
> +
> + return i2c_smbus_write_block_data(client,
> + ETP_SMBUS_IAP_CMD, 4, cmd);
> +}
> +
> +static int elan_smbus_enable_absolute_mode(struct i2c_client *client)
> +{
> + u8 cmd[4] = {0x00, 0x07, 0x00, ETP_ENABLE_ABS};
> +
> + return i2c_smbus_write_block_data(client, ETP_SMBUS_IAP_CMD, 4, cmd);
> +}
> +
> +/*****************************************************************
> +* Elan i2c interface
> +******************************************************************
> +*/
> +static int elan_i2c_read_block(struct i2c_client *client,
> + u16 reg, u8 *val, u16 len)
> +{
> + struct i2c_msg msgs[2];
> + u8 buf[2];
> + int ret;
> +
> + buf[0] = reg & 0xff;
> + buf[1] = (reg >> 8) & 0xff;
> +
> + msgs[0].addr = client->addr;
> + msgs[0].flags = client->flags & I2C_M_TEN;
> + msgs[0].len = 2;
> + msgs[0].buf = buf;
> +
> + msgs[1].addr = client->addr;
> + msgs[1].flags = client->flags & I2C_M_TEN;
> + msgs[1].flags |= I2C_M_RD;
> + msgs[1].len = len;
> + msgs[1].buf = val;
> +
> + ret = i2c_transfer(client->adapter, msgs, 2);
> + return ret != 2 ? -EIO : 0;
> +}
> +
> +static int elan_i2c_read_cmd(struct i2c_client *client, u16 reg, u8 *val)
> +{
> + int retval;
> +
> + retval = elan_i2c_read_block(client, reg, val, ETP_INF_LENGTH);
> + if (retval < 0) {
> + dev_err(&client->dev, "reading cmd (0x%04x) fail.\n", reg);
> + return retval;
> + }
> + return 0;
> +}
> +
> +static int elan_i2c_write_cmd(struct i2c_client *client, u16 reg, u16 cmd)
> +{
> + struct i2c_msg msg;
> + u8 buf[4];
> + int ret;
> +
> + buf[0] = reg & 0xff;
> + buf[1] = (reg >> 8) & 0xff;
> + buf[2] = cmd & 0xff;
> + buf[3] = (cmd >> 8) & 0xff;
> +
> + msg.addr = client->addr;
> + msg.flags = client->flags & I2C_M_TEN;
> + msg.len = 4;
> + msg.buf = buf;
> +
> + ret = i2c_transfer(client->adapter, &msg, 1);
> + return ret != 1 ? -EIO : 0;
> +}
> +
> +static int elan_i2c_reset(struct i2c_client *client)
> +{
> + return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
> + ETP_I2C_RESET);
> +}
> +
> +static int elan_i2c_wake_up(struct i2c_client *client)
> +{
> + return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
> + ETP_I2C_WAKE_UP);
> +}
> +
> +static int elan_i2c_sleep(struct i2c_client *client)
> +{
> + return elan_i2c_write_cmd(client, ETP_I2C_STAND_CMD,
> + ETP_I2C_SLEEP);
> +}
> +
> +static int elan_i2c_enable_absolute_mode(struct i2c_client *client)
> +{
> + return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
> + ETP_ENABLE_ABS);
> +}
> +
> +static int elan_i2c_enable_calibrate(struct i2c_client *client)
> +{
> + return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
> + ETP_ENABLE_ABS|ETP_ENABLE_CALIBRATE);
> +}
> +
> +static int elan_i2c_disable_calibrate(struct i2c_client *client)
> +{
> + return elan_i2c_write_cmd(client, ETP_I2C_SET_CMD,
> + ETP_ENABLE_ABS|ETP_DISABLE_CALIBRATE);
> +}
> +
> +static int elan_i2c_get_desc(struct i2c_client *client, u8 *val)
> +{
> + return elan_i2c_read_block(client, ETP_I2C_DESC_CMD, val,
> + ETP_I2C_DESC_LENGTH);
> +}
> +
> +static int elan_i2c_get_report_desc(struct i2c_client *client, u8 *val)
> +{
> + return elan_i2c_read_block(client, ETP_I2C_REPORT_DESC_CMD,
> + val, ETP_I2C_REPORT_DESC_LENGTH);
> +}
> +
> +static int elan_i2c_initialize(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + int rc;
> +
> + rc = elan_i2c_reset(client);
> + if (rc < 0) {
> + dev_err(dev, "device reset failed.\n");
> + return -1;
> + }
> +
> + /* wait for get reset return flag */
> + msleep(100);
> + /* get reset return flag 0000 */
> + rc = i2c_master_recv(client, val, ETP_INF_LENGTH);
> + if (rc < 0) {
> + dev_err(dev, "get device reset return value failed.\n");
> + return -1;
> + }
> +
> + rc = elan_i2c_get_desc(client, val);
> + if (rc < 0) {
> + dev_err(dev, "cannot get device descriptor.\n");
> + return -1;
> + }
> +
> + rc = elan_i2c_get_report_desc(client, val);
> + if (rc < 0) {
> + dev_err(dev, "fetching report descriptor failed.\n");
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +/**************************************************************************
> +* Genernal functions
> +***************************************************************************
> +*/
> +
> +/*
> + * (value from firmware) * 10 + 790 = dpi
> + * we also have to convert dpi to dots/mm (*10/254 to avoid floating point)
> + */
> +static unsigned int elan_convert_res(char val)
> +{
> + int res;
> + if (val & 0x80) {
> + val = ~val + 1;
> + res = (790 - val * 10) * 10 / 254;
> + } else
> + res = (val * 10 + 790) * 10 / 254;
> + return res;
> +}
> +
> +static int elan_get_iap_version(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_IAP_VERSION_CMD, val);
> + ret = val[2];
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_IAP_VERSION_CMD, val);
> + ret = val[0];
> + }
> + return ret;
> +}
> +
> +static int elan_get_x_max(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_RANGE_CMD, val);
> + ret = (0x0f & val[0]) << 8 | val[1];
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_MAX_X_AXIS_CMD, val);
> + ret = (0x0f & val[1]) << 8 | val[0];
> + }
> + return ret;
> +}
> +
> +static int elan_get_y_max(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_RANGE_CMD, val);
> + ret = (0xf0 & val[0]) << 4 | val[2];
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_MAX_Y_AXIS_CMD, val);
> + ret = (0x0f & val[1]) << 8 | val[0];
> + }
> + return ret;
> +}
> +
> +static int elan_get_x_tracenum(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_XY_TRACENUM_CMD, val);
> + ret = (val[1] - 1);
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_XY_TRACENUM_CMD, val);
> + ret = (val[0] - 1);
> + }
> + return ret;
> +}
> +
> +static int elan_get_y_tracenum(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_XY_TRACENUM_CMD, val);
> + ret = (val[2] - 1);
> + } else {
> + ret = elan_i2c_read_cmd(data->client,
> + ETP_I2C_XY_TRACENUM_CMD, val);
> + ret = (val[1] - 1);
> + }
> + return ret;
> +}
> +
> +static int elan_get_fw_version(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_FW_VERSION_CMD, val);
> + ret = val[2];
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_FW_VERSION_CMD, val);
> + ret = val[0];
> + }
> + return ret;
> +}
> +
> +static int elan_get_sm_version(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus)
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_SM_VERSION_CMD, val);
> + else
> + elan_i2c_read_block(data->client,
> + ETP_I2C_SM_VERSION_CMD, val, 1);
> + ret = val[0];
> + return ret;
> +}
> +
> +static int elan_get_unique_id(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_UNIQUEID_CMD, val);
> + ret = val[1];
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_UNIQUEID_CMD, val);
> + ret = val[0];
> + }
> + return ret;
> +}
> +
> +static int elan_get_x_resolution(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_RESOLUTION_CMD, val);
> + ret = elan_convert_res(val[1] & 0x0F);
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_RESOLUTION_CMD, val);
> + ret = elan_convert_res(val[0]);
> + }
> + return ret;
> +}
> +
> +static int elan_get_y_resolution(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_RESOLUTION_CMD, val);
> + ret = elan_convert_res((val[1] & 0xF0) >> 4);
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_RESOLUTION_CMD, val);
> + ret = elan_convert_res(val[1]);
> + }
> + return ret;
> +}
> +
> +static int elan_get_fw_checksum(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_FW_CHECKSUM_CMD, val);
> + ret = be16_to_cpup((__be16 *)val);
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_FW_CHECKSUM_CMD, val);
> + ret = le16_to_cpup((__le16 *)val);
> + }
> + return ret;
> +}
> +
> +static int elan_get_max_baseline(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_MAX_BASELINE_CMD, val);
> + ret = be16_to_cpup((__be16 *)val);
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_MAX_BASELINE_CMD, val);
> + ret = le16_to_cpup((__le16 *)val);
> + }
> + return ret;
> +}
> +
> +static int elan_get_min_baseline(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_MIN_BASELINE_CMD, val);
> + ret = be16_to_cpup((__be16 *)val);
> + } else {
> + elan_i2c_read_cmd(data->client,
> + ETP_I2C_MIN_BASELINE_CMD, val);
> + ret = le16_to_cpup((__le16 *)val);
> + }
> + return ret;
> +}
> +
> +static int elan_enable_calibrate(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus)
> + ret = elan_smbus_enable_calibrate(data->client);
> + else
> + ret = elan_i2c_enable_calibrate(data->client);
> + return ret;
> +}
> +
> +static int elan_disable_calibrate(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus)
> + ret = elan_smbus_disable_calibrate(data->client);
> + else
> + ret = elan_i2c_disable_calibrate(data->client);
> + return ret;
> +}
> +
> +static int elan_initialize(struct elan_tp_data *data)
> +{
> + int ret;
> + if (data->smbus) {
> + ret = elan_smbus_initialize(data->client);
> + if (ret < 0) {
> + dev_err(&data->client->dev,
> + "device initialize failed.\n");
> + goto err_initialize;
> + }
> +
> + ret = elan_smbus_enable_absolute_mode(data->client);
> + if (ret < 0)
> + dev_err(&data->client->dev,
> + "cannot switch to absolute mode.\n");
> + } else {
> + ret = elan_i2c_initialize(data->client);
> + if (ret < 0) {
> + dev_err(&data->client->dev,
> + "device initialize failed.\n");
> + goto err_initialize;
> + }
> +
> + ret = elan_i2c_enable_absolute_mode(data->client);
> + if (ret < 0) {
> + dev_err(&data->client->dev,
> + "cannot switch to absolute mode.\n");
> + goto err_initialize;
> + }
> +
> + ret = elan_i2c_wake_up(data->client);
> + if (ret < 0)
> + dev_err(&data->client->dev,
> + "device wake up failed.\n");
> + }
> +err_initialize:
> + return ret;
> +}
> +
> +/********************************************************************
> + * below routines export interfaces to sysfs file system.
> + * so user can get firmware/driver/hardware information using cat command.
> + * e.g.: use below command to get firmware version
> + * cat /sys/bus/i2c/drivers/elan_i2c/1-0015/firmware_version
> + *******************************************************************
> + */
> +static ssize_t elan_sysfs_enable_detailinfo(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + data->enable_detail_info = true;
What is this for?
> + return sprintf(buf, "enable\n");
> +}
> +
> +static ssize_t elan_sysfs_read_fw_checksum(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + unsigned int checksum = 0;
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + if (data->enable_detail_info == true) {
> + checksum = elan_get_fw_checksum(data);
> + data->enable_detail_info = false;
> + }
> + return sprintf(buf, "0x%04x\n", checksum);
> +}
> +
> +static ssize_t elan_sysfs_read_unique_id(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + data->unique_id = elan_get_unique_id(data);
> + return sprintf(buf, "0x%04x\n", data->unique_id);
> +}
> +
> +static ssize_t elan_sysfs_read_driver_ver(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sprintf(buf, "%s\n", ELAN_DRIVER_VERSION);
This should be reported in MODULE_VERSION if you need it.
> +}
> +
> +static ssize_t elan_sysfs_read_fw_ver(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + data->fw_version = elan_get_fw_version(data);
> + return sprintf(buf, "0x%04x\n", data->fw_version);
> +}
> +
> +static ssize_t elan_sysfs_read_sm_ver(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + data->sm_version = elan_get_sm_version(data);
> + return sprintf(buf, "0x%04x\n", data->sm_version);
> +}
> +
> +static ssize_t elan_sysfs_read_iap_ver(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + data->iap_version = elan_get_iap_version(data);
> + return sprintf(buf, "0x%04x\n", data->iap_version);
> +}
> +
> +
> +static ssize_t elan_sysfs_update_fw(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + int ret;
> + ret = elan_firmware(data);
> + if (ret)
> + dev_err(dev, "firmware update failed.\n");
> + else
> + dev_info(dev, "firmware update succeeded.\n");
> + return ret ? ret : count;
> +}
> +
> +static ssize_t elan_sysfs_calibrate(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + /* start calibarate cmd */
> + u8 smbus_cmd[4] = {0x00, 0x08, 0x00, 0x01};
> + int tries = 20;
> + int ret = 0;
> + val[0] = 0;
> +
> + disable_irq(data->irq);
> + elan_enable_calibrate(data);
> + if (data->smbus)
> + i2c_smbus_write_block_data(data->client,
> + ETP_SMBUS_IAP_CMD, 4, smbus_cmd);
> + else
> + elan_i2c_write_cmd(data->client,
> + ETP_I2C_CALIBRATE_CMD, 1);
> +
> + do {
> + /* wait 250ms and check finish or not */
> + msleep(250);
> +
> + if (data->smbus)
> + i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_CALIBRATE_QUERY, val);
> + else
> + elan_i2c_read_block(data->client,
> + ETP_I2C_CALIBRATE_CMD, val, 1);
> +
> + /* calibrate finish */
> + if (val[0] == 0)
> + break;
> + } while (--tries);
> +
> + elan_disable_calibrate(data);
> + enable_irq(data->irq);
> +
> + if (tries == 0) {
> + dev_err(dev, "Failed to calibrate. Timeout.\n");
> + ret = -ETIMEDOUT;
> + }
> + return sprintf(buf, "calibration finish\n");
> +}
> +
> +
> +static ssize_t elan_sysfs_read_baseline(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + int max_baseline, min_baseline;
> +
> + disable_irq(data->irq);
> + elan_enable_calibrate(data);
> + msleep(250);
> + max_baseline = elan_get_max_baseline(data);
> + min_baseline = elan_get_min_baseline(data);
> + elan_disable_calibrate(data);
> + enable_irq(data->irq);
> + return sprintf(buf, "max:%d min:%d\n", max_baseline, min_baseline);
> +}
> +
> +static ssize_t elan_sysfs_reinitialize(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> + int ret;
> +
> + disable_irq(data->irq);
> + ret = elan_initialize(data);
> + enable_irq(data->irq);
> +
> + if (ret < 0)
> + return sprintf(buf, "reinitialize fail\n");
> +
> + return sprintf(buf, "reinitialize success\n");
This actually should be a write method and instead of returning strings
you should use return code to indicate success/failure.
> +}
> +
> +static DEVICE_ATTR(unique_id, S_IRUGO, elan_sysfs_read_unique_id, NULL);
> +static DEVICE_ATTR(firmware_version, S_IRUGO, elan_sysfs_read_fw_ver, NULL);
> +static DEVICE_ATTR(sample_version, S_IRUGO, elan_sysfs_read_sm_ver, NULL);
> +static DEVICE_ATTR(driver_version, S_IRUGO, elan_sysfs_read_driver_ver, NULL);
> +static DEVICE_ATTR(iap_version, S_IRUGO, elan_sysfs_read_iap_ver, NULL);
> +static DEVICE_ATTR(fw_checksum, S_IRUGO, elan_sysfs_read_fw_checksum, NULL);
> +static DEVICE_ATTR(open_info, S_IRUGO, elan_sysfs_enable_detailinfo, NULL);
> +static DEVICE_ATTR(baseline, S_IRUGO, elan_sysfs_read_baseline, NULL);
> +static DEVICE_ATTR(reinitialize, S_IRUGO, elan_sysfs_reinitialize, NULL);
> +static DEVICE_ATTR(calibrate, S_IRUGO, elan_sysfs_calibrate, NULL);
> +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, elan_sysfs_update_fw);
> +
> +static struct attribute *elan_sysfs_entries[] = {
> + &dev_attr_unique_id.attr,
> + &dev_attr_firmware_version.attr,
> + &dev_attr_sample_version.attr,
> + &dev_attr_driver_version.attr,
> + &dev_attr_iap_version.attr,
> + &dev_attr_fw_checksum.attr,
> + &dev_attr_open_info.attr,
> + &dev_attr_baseline.attr,
> + &dev_attr_reinitialize.attr,
> + &dev_attr_calibrate.attr,
> + &dev_attr_update_fw.attr,
> + NULL,
Please add documentation for sysfs entries to Documentation/...
> +};
> +
> +static const struct attribute_group elan_sysfs_group = {
> + .attrs = elan_sysfs_entries,
> +};
> +
> +/*****************************************************************
> +* Elan isr functions
> +******************************************************************
> +*/
> +
> +static int elan_check_packet(struct elan_tp_data *data, u8 *packet)
> +{
> + u8 rid;
> +
> + if (data->smbus)
> + rid = packet[0];
> + else
> + rid = packet[ETP_I2C_REPORT_ID_OFFSET];
> +
> + /* check report id */
> + if (rid != ETP_REPORT_ID) {
> + dev_err(&data->client->dev, "report id [%x] fail.\n", rid);
> + return -1;
> + }
> + return 0;
> +}
> +
> +static void elan_report_absolute(struct elan_tp_data *data, u8 *packet)
> +{
> + struct input_dev *input = data->input;
> + u8 *finger_data;
> + bool finger_on;
> + int pos_x, pos_y;
> + int pressure, mk_x, mk_y;
> + int i, area_x, area_y, major, minor, new_pressure;
> + int finger_count = 0;
> + int btn_click;
> + u8 tp_info;
> +
> + if (data->smbus) {
> + finger_data = &packet[ETP_SMBUS_FINGER_DATA_OFFSET];
> + tp_info = packet[1];
> + } else {
> + finger_data = &packet[ETP_I2C_FINGER_DATA_OFFSET];
> + tp_info = packet[3];
> + }
> +
> + btn_click = (tp_info & 0x01);
> + for (i = 0; i < ETP_MAX_FINGERS; i++) {
> + finger_on = (tp_info >> (3 + i)) & 0x01;
> +
> + /* analyze touched finger raw data*/
> + if (finger_on) {
> + pos_x = ((finger_data[0] & 0xf0) << 4) |
> + finger_data[1];
> + pos_y = ((finger_data[0] & 0x0f) << 8) |
> + finger_data[2];
> + pos_y = data->max_y - pos_y;
> + mk_x = (finger_data[3] & 0x0f);
> + mk_y = (finger_data[3] >> 4);
> + pressure = finger_data[4];
> +
> + /*
> + to avoid fat finger be as palm, so reduce the
> + width x and y per trace
> + */
> + area_x = mk_x * (data->width_x - ETP_FWIDTH_REDUCE);
> + area_y = mk_y * (data->width_y - ETP_FWIDTH_REDUCE);
> +
> + major = max(area_x, area_y);
> + minor = min(area_x, area_y);
> +
> + new_pressure = pressure + ETP_PRESSURE_OFFSET;
> + if (new_pressure > ETP_MAX_PRESSURE)
> + new_pressure = ETP_MAX_PRESSURE;
> +
> + input_mt_slot(input, i);
> + input_mt_report_slot_state(input, MT_TOOL_FINGER,
> + true);
> + input_report_abs(input, ABS_MT_POSITION_X, pos_x);
> + input_report_abs(input, ABS_MT_POSITION_Y, pos_y);
> + input_report_abs(input, ABS_MT_PRESSURE, new_pressure);
> + input_report_abs(input, ABS_TOOL_WIDTH, mk_x);
> + input_report_abs(input, ABS_MT_TOUCH_MAJOR, major);
> + input_report_abs(input, ABS_MT_TOUCH_MINOR, minor);
> + finger_data += ETP_FINGER_DATA_LEN;
> + finger_count++;
> + } else {
> + input_mt_slot(input, i);
> + input_mt_report_slot_state(input,
> + MT_TOOL_FINGER, false);
> + }
> + }
> +
> + input_report_key(input, BTN_LEFT, (btn_click == 1));
> + input_mt_report_pointer_emulation(input, true);
> + input_sync(input);
> +}
> +
> +static irqreturn_t elan_isr(int irq, void *dev_id)
> +{
> + struct elan_tp_data *data = dev_id;
> + u8 raw[ETP_MAX_REPORT_LEN];
> + int retval;
> + int report_len;
> +
> + retval = mutex_lock_interruptible(&data->dbfs_mutex);
> + if (retval)
> + return IRQ_HANDLED;
> +
> + /*
> + Only in I2C protocol, when IAP all page wrote finish, driver will
> + get one INT signal from high to low, and driver must get 0000
> + to confirm IAP is finished.
> + */
> + if (data->updated_fw) {
> + retval = i2c_master_recv(data->client, raw,
> + ETP_INF_LENGTH);
> + if (retval == 2 && !le16_to_cpup((__le16 *)raw)) {
> + dev_info(&data->client->dev,
> + "reinitializing after F/W update...");
> + elan_initialize(data);
> + }
> + data->updated_fw = false;
> + goto elan_isr_end;
> + }
> +
> + if (data->smbus) {
> + report_len = ETP_SMBUS_REPORT_LEN;
> + retval = i2c_smbus_read_block_data(data->client,
> + ETP_SMBUS_PACKET_QUERY, raw);
> + } else {
> + report_len = ETP_I2C_REPORT_LEN;
> + retval = i2c_master_recv(data->client, raw, report_len);
> + }
> +
> + if (retval != report_len) {
> + dev_err(&data->client->dev, "wrong packet len(%d)", retval);
> + goto elan_isr_end;
> + }
> +
> + if (elan_check_packet(data, raw) < 0) {
> + dev_err(&data->client->dev, "wrong packet format.");
> + goto elan_isr_end;
> + }
> + elan_report_absolute(data, raw);
> + data->dbfs_buffer.bfetch = false;
> + memcpy(data->dbfs_buffer.buffer, raw, report_len);
> +
> +elan_isr_end:
> + mutex_unlock(&data->dbfs_mutex);
> + return IRQ_HANDLED;
> +}
> +
> +
> +static int elan_input_dev_create(struct elan_tp_data *data)
> +{
> + struct i2c_client *client = data->client;
> + struct input_dev *input;
> + unsigned int x_res, y_res;
> + int ret;
> +
> + data->input = input = input_allocate_device();
> + if (!input)
> + return -ENOMEM;
> + input->name = "Elan Touchpad";
> + input->id.bustype = BUS_I2C;
> + input->dev.parent = &data->client->dev;
> +
> + __set_bit(INPUT_PROP_POINTER, input->propbit);
> + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> + __set_bit(EV_KEY, input->evbit);
> + __set_bit(EV_ABS, input->evbit);
> +
> + __set_bit(BTN_LEFT, input->keybit);
> + __set_bit(BTN_TOUCH, input->keybit);
> + __set_bit(BTN_TOOL_FINGER, input->keybit);
> + __set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
> + __set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
> + __set_bit(BTN_TOOL_QUADTAP, input->keybit);
> + __set_bit(BTN_TOOL_QUINTTAP, input->keybit);
> +
> + __set_bit(ABS_MT_TOUCH_MAJOR, input->absbit);
> + __set_bit(ABS_MT_TOUCH_MINOR, input->absbit);
> + __set_bit(ABS_MT_POSITION_X, input->absbit);
> + __set_bit(ABS_MT_POSITION_Y, input->absbit);
> +
> + data->unique_id = elan_get_unique_id(data);
> + data->fw_version = elan_get_fw_version(data);
> + data->sm_version = elan_get_sm_version(data);
> + data->iap_version = elan_get_iap_version(data);
> + data->max_x = elan_get_x_max(data);
> + data->max_y = elan_get_y_max(data);
> + data->width_x = data->max_x / elan_get_x_tracenum(data);
> + data->width_y = data->max_y / elan_get_y_tracenum(data);
> + x_res = elan_get_x_resolution(data);
> + y_res = elan_get_y_resolution(data);
> +
> + dev_info(&client->dev,
> + "Elan Touchpad Information:\n"
> + " Module unique ID: 0x%04x\n"
> + " Firmware Version: 0x%04x\n"
> + " Sample Version: 0x%04x\n"
> + " IAP Version: 0x%04x\n"
> + " Max ABS X,Y: %d,%d\n"
> + " Width X,Y: %d,%d\n"
> + " Resolution X,Y: %d,%d (dots/mm)\n",
> + data->unique_id,
> + data->fw_version,
> + data->sm_version,
> + data->iap_version,
> + data->max_x, data->max_y,
> + data->width_x, data->width_y,
> + (char)x_res, (char)y_res);
Why cast to char?
> +
> + input_set_abs_params(input, ABS_X, 0, data->max_x, 0, 0);
> + input_set_abs_params(input, ABS_Y, 0, data->max_y, 0, 0);
> + input_abs_set_res(input, ABS_X, x_res);
> + input_abs_set_res(input, ABS_Y, y_res);
> + input_set_abs_params(input, ABS_PRESSURE, 0, ETP_MAX_PRESSURE, 0, 0);
> + input_set_abs_params(input, ABS_TOOL_WIDTH, 0, ETP_FINGER_WIDTH, 0, 0);
> +
> + /* handle pointer emulation and unused slots in core */
> + ret = input_mt_init_slots(input, ETP_MAX_FINGERS,
> + INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED);
> + if (ret) {
> + dev_info(&client->dev, "allocate MT slots failed, %d\n", ret);
> + goto err_free_device;
> + }
> + input_set_abs_params(input, ABS_MT_POSITION_X, 0, data->max_x, 0, 0);
> + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, data->max_y, 0, 0);
> + input_abs_set_res(input, ABS_MT_POSITION_X, x_res);
> + input_abs_set_res(input, ABS_MT_POSITION_Y, y_res);
> + input_set_abs_params(input, ABS_MT_PRESSURE, 0,
> + ETP_MAX_PRESSURE, 0, 0);
> + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0,
> + ETP_FINGER_WIDTH * max(data->width_x, data->width_y), 0, 0);
> + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0,
> + ETP_FINGER_WIDTH * min(data->width_x, data->width_y), 0, 0);
> +
> + /* Register the device in input subsystem */
> + ret = input_register_device(input);
> + if (ret) {
> + dev_err(&client->dev, "input device register failed, %d\n",
> + ret);
> + goto err_free_device;
> + }
> +
> + return 0;
> +
> +err_free_device:
> + input_free_device(input);
> + return ret;
> +}
> +
> +static u8 elan_check_adapter_functionality(struct i2c_client *client)
> +{
> + u8 ret = ELAN_ADAPTER_FUNC_NONE;
> +
> + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
> + ret |= ELAN_ADAPTER_FUNC_I2C;
> + if (i2c_check_functionality(client->adapter,
> + I2C_FUNC_SMBUS_BYTE_DATA |
> + I2C_FUNC_SMBUS_BLOCK_DATA |
> + I2C_FUNC_SMBUS_I2C_BLOCK))
> + ret |= ELAN_ADAPTER_FUNC_SMBUS;
> + return ret;
> +}
> +
> +static int elan_probe(struct i2c_client *client,
> + const struct i2c_device_id *dev_id)
> +{
> + struct elan_tp_data *data;
> + int ret;
> + u8 adapter_func;
> + union i2c_smbus_data dummy;
> + struct device *dev = &client->dev;
> +
> + adapter_func = elan_check_adapter_functionality(client);
> + if (adapter_func == ELAN_ADAPTER_FUNC_NONE) {
> + dev_err(dev, "not a supported I2C/SMBus adapter\n");
> + return -EIO;
> + }
> +
> + /* Make sure there is something at this address */
> + if (dev->of_node && i2c_smbus_xfer(client->adapter, client->addr, 0,
> + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0)
> + return -ENODEV;
> +
> + data = kzalloc(sizeof(struct elan_tp_data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + /* check protocol type */
> + if (adapter_func == ELAN_ADAPTER_FUNC_SMBUS)
> + data->smbus = true;
> + else
> + data->smbus = false;
> + data->client = client;
> + data->updated_fw = false;
> + data->enable_detail_info = false;
> + data->irq = client->irq;
> +
> + ret = elan_dbfs_init(data);
> + if (ret < 0) {
> + dev_err(&client->dev, "error create elan debugfs.\n");
> + goto err_dbfs_init;
> + }
> + ret = request_threaded_irq(client->irq, NULL, elan_isr,
> + IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> + client->name, data);
> + if (ret < 0) {
> + dev_err(&client->dev, "cannot register irq=%d\n",
> + client->irq);
> + goto err_irq;
> + }
> +
> + /* initial elan touch pad */
> + ret = elan_initialize(data);
> + if (ret < 0)
> + goto err_init;
> +
> + /* create input device */
> + ret = elan_input_dev_create(data);
> + if (ret < 0)
> + goto err_input_dev;
> +
> + device_init_wakeup(&client->dev, 1);
> + ret = sysfs_create_group(&client->dev.kobj, &elan_sysfs_group);
> + if (ret < 0) {
> + dev_err(&client->dev, "cannot register dev attribute %d", ret);
> + goto err_create_group;
> + }
> + i2c_set_clientdata(client, data);
> + return 0;
> +
> +err_create_group:
> + input_free_device(data->input);
The device is registered here so you need to call unregister, not free.
> +err_input_dev:
> +err_init:
> + free_irq(data->irq, data);
> +err_irq:
> + debugfs_remove_recursive(data->dbfs_root);
> + mutex_destroy(&data->dbfs_mutex);
> +err_dbfs_init:
> + kfree(data);
> + dev_err(&client->dev, "Elan Trackpad probe fail!\n");
> + return ret;
> +}
> +
> +static int elan_remove(struct i2c_client *client)
> +{
> + struct elan_tp_data *data = i2c_get_clientdata(client);
> +
> + free_irq(data->irq, data);
> + debugfs_remove_recursive(data->dbfs_root);
> + mutex_destroy(&data->dbfs_mutex);
> +
> + input_free_device(data->input);
Not needed (and harmful here).
> + input_unregister_device(data->input);
> + kfree(data);
> + sysfs_remove_group(&client->dev.kobj, &elan_sysfs_group);
I'd start with removing sysfs attributes as first thing in remove().
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int elan_suspend(struct device *dev)
> +{
> + int ret;
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> +
> + disable_irq(data->irq);
> + if (data->smbus)
> + ret = i2c_smbus_write_byte(data->client,
> + ETP_SMBUS_DISABLE_TP);
> + else
> + ret = elan_i2c_sleep(data->client);
> +
> + if (ret < 0) {
> + dev_err(dev, "suspend mode failed, %d\n", ret);
> + } else {
> + if (device_may_wakeup(dev))
> + data->irq_wake = (enable_irq_wake(data->irq) == 0);
> + }
> + return 0;
> +}
> +
> +static int elan_resume(struct device *dev)
> +{
> + int ret = 0;
> + struct elan_tp_data *data = dev_get_drvdata(dev);
> +
> + if (device_may_wakeup(dev) && data->irq_wake)
> + disable_irq_wake(data->irq);
> +
> + ret = elan_initialize(data);
> + if (ret < 0)
> + dev_err(dev, "resume active power failed, %d\n", ret);
> +
> + enable_irq(data->irq);
> + return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume);
> +
> +static const struct i2c_device_id elan_id[] = {
> + { DRIVER_NAME, 0 },
> + { },
> +};
> +MODULE_DEVICE_TABLE(i2c, elan_id);
> +
> +static struct i2c_driver elan_driver = {
> + .driver = {
> + .name = DRIVER_NAME,
> + .owner = THIS_MODULE,
> + .pm = &elan_pm_ops,
> + },
> + .probe = elan_probe,
> + .remove = elan_remove,
> + .id_table = elan_id,
> +};
> +
> +
> +static int __init elan_init(void)
> +{
> + int ret;
> + ret = i2c_add_driver(&elan_driver);
> + if (ret) {
> + pr_err("elan driver register FAILED.\n");
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static void __exit elan_exit(void)
> +{
> + i2c_del_driver(&elan_driver);
> +}
> +
> +module_init(elan_init);
> +module_exit(elan_exit);
module_i2c_driver().
> +
> +MODULE_AUTHOR("Duson Lin <dusonlin@emc.com.tw>");
> +MODULE_DESCRIPTION("Elan I2C/SMBus Touchpad driver");
> +MODULE_LICENSE("GPL");
> --
> 1.7.10.4
>
Thanks.
--
Dmitry
--
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 v2] elantech: Properly differentiate between clickpads and normal touchpads
From: Dmitry Torokhov @ 2013-12-16 15:12 UTC (permalink / raw)
To: Hans de Goede; +Cc: linux-input, Peter Hutterer
In-Reply-To: <1386667874-6669-1-git-send-email-hdegoede@redhat.com>
On Tue, Dec 10, 2013 at 10:31:14AM +0100, Hans de Goede wrote:
> The current assumption in the elantech driver that hw version 3 touchpads are
> never clickpads and hw version 4 touchpads are always clickpads is wrong.
>
> There are several bug reports for this, ie:
> https://bugzilla.redhat.com/show_bug.cgi?id=1030802
> http://superuser.com/questions/619582/right-elantech-touchpad-button-not-working-in-linux
>
> I've spend a couple of hours wading through various bugzillas,
> launchpads and forum posts to create a list of fw-versions and capabilities
> for different laptop models to find a good method to differentiate between
> clickpads and versions with separate hardware buttons.
>
> Which shows that a device being a clickpad is reliable indicated by bit 12
> being set in the fw_version. I've included the gathered list inside the driver,
> so that we've this info at hand if we need to revisit this later.
>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Applied, thank you.
> ---
> drivers/input/mouse/elantech.c | 45 +++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 42 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
> index 597e9b8..ef1cf52 100644
> --- a/drivers/input/mouse/elantech.c
> +++ b/drivers/input/mouse/elantech.c
> @@ -486,6 +486,7 @@ static void elantech_input_sync_v4(struct psmouse *psmouse)
> unsigned char *packet = psmouse->packet;
>
> input_report_key(dev, BTN_LEFT, packet[0] & 0x01);
> + input_report_key(dev, BTN_RIGHT, packet[0] & 0x02);
> input_mt_report_pointer_emulation(dev, true);
> input_sync(dev);
> }
> @@ -984,6 +985,44 @@ static int elantech_get_resolution_v4(struct psmouse *psmouse,
> }
>
> /*
> + * Advertise INPUT_PROP_BUTTONPAD for clickpads. The testing of bit 12 in
> + * fw_version for this is based on the following fw_version & caps table:
> + *
> + * Laptop-model: fw_version: caps: buttons:
> + * Acer S3 0x461f00 10, 13, 0e clickpad
> + * Acer S7-392 0x581f01 50, 17, 0d clickpad
> + * Acer V5-131 0x461f02 01, 16, 0c clickpad
> + * Acer V5-551 0x461f00 ? clickpad
> + * Asus K53SV 0x450f01 78, 15, 0c 2 hw buttons
> + * Asus G46VW 0x460f02 00, 18, 0c 2 hw buttons
> + * Asus G750JX 0x360f00 00, 16, 0c 2 hw buttons
> + * Asus UX31 0x361f00 20, 15, 0e clickpad
> + * Asus UX32VD 0x361f02 00, 15, 0e clickpad
> + * Avatar AVIU-145A2 0x361f00 ? clickpad
> + * Gigabyte U2442 0x450f01 58, 17, 0c 2 hw buttons
> + * Lenovo L430 0x350f02 b9, 15, 0c 2 hw buttons (*)
> + * Samsung NF210 0x150b00 78, 14, 0a 2 hw buttons
> + * Samsung NP770Z5E 0x575f01 10, 15, 0f clickpad
> + * Samsung NP700Z5B 0x361f06 21, 15, 0f clickpad
> + * Samsung NP900X3E-A02 0x575f03 ? clickpad
> + * Samsung NP-QX410 0x851b00 19, 14, 0c clickpad
> + * Samsung RC512 0x450f00 08, 15, 0c 2 hw buttons
> + * Samsung RF710 0x450f00 ? 2 hw buttons
> + * System76 Pangolin 0x250f01 ? 2 hw buttons
> + * (*) + 3 trackpoint buttons
> + */
> +static void elantech_set_buttonpad_prop(struct psmouse *psmouse)
> +{
> + struct input_dev *dev = psmouse->dev;
> + struct elantech_data *etd = psmouse->private;
> +
> + if (etd->fw_version & 0x001000) {
> + __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit);
> + __clear_bit(BTN_RIGHT, dev->keybit);
> + }
> +}
> +
> +/*
> * Set the appropriate event bits for the input subsystem
> */
> static int elantech_set_input_params(struct psmouse *psmouse)
> @@ -1026,6 +1065,8 @@ static int elantech_set_input_params(struct psmouse *psmouse)
> __set_bit(INPUT_PROP_SEMI_MT, dev->propbit);
> /* fall through */
> case 3:
> + if (etd->hw_version == 3)
> + elantech_set_buttonpad_prop(psmouse);
> input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0);
> input_set_abs_params(dev, ABS_Y, y_min, y_max, 0, 0);
> if (etd->reports_pressure) {
> @@ -1047,9 +1088,7 @@ static int elantech_set_input_params(struct psmouse *psmouse)
> */
> psmouse_warn(psmouse, "couldn't query resolution data.\n");
> }
> - /* v4 is clickpad, with only one button. */
> - __set_bit(INPUT_PROP_BUTTONPAD, dev->propbit);
> - __clear_bit(BTN_RIGHT, dev->keybit);
> + elantech_set_buttonpad_prop(psmouse);
> __set_bit(BTN_TOOL_QUADTAP, dev->keybit);
> /* For X to recognize me as touchpad. */
> input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0);
> --
> 1.8.4.2
>
--
Dmitry
^ permalink raw reply
* Re: [PATCH -next] Input: pmic8xxx-pwrkey - fix to pass correct device identity to free_irq()
From: Stephen Boyd @ 2013-12-17 1:23 UTC (permalink / raw)
To: Wei Yongjun; +Cc: dmitry.torokhov, lars, sachin.kamat, yongjun_wei, linux-input
In-Reply-To: <CAPgLHd87FxZ6ur_WLC9Q9bjOfNbOTu6yzNWNcN-ar0=o1y48Kg@mail.gmail.com>
On 12/17, Wei Yongjun wrote:
> From: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
>
> free_irq() in the error handling case is missing when change
> pass input device directly to interrupt.
>
> Fixes: b27f8fee4965('Input: pmic8xxx-pwrkey - pass input device directly to interrupt')
> Signed-off-by: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
This driver was supposed to be converted to devm before
that patch. I forgot to send it and then Dmitry seems to have
fixed up the patch to make it apply. We should just use the devm
conversion patch to avoid needing to apply this one.
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
^ permalink raw reply
* [PATCH -next] Input: pmic8xxx-pwrkey - fix to pass correct device identity to free_irq()
From: Wei Yongjun @ 2013-12-17 1:16 UTC (permalink / raw)
To: dmitry.torokhov, sboyd, lars, sachin.kamat; +Cc: yongjun_wei, linux-input
From: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
free_irq() in the error handling case is missing when change
pass input device directly to interrupt.
Fixes: b27f8fee4965('Input: pmic8xxx-pwrkey - pass input device directly to interrupt')
Signed-off-by: Wei Yongjun <yongjun_wei@trendmicro.com.cn>
---
drivers/input/misc/pmic8xxx-pwrkey.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/misc/pmic8xxx-pwrkey.c b/drivers/input/misc/pmic8xxx-pwrkey.c
index ef93840..3914e5b 100644
--- a/drivers/input/misc/pmic8xxx-pwrkey.c
+++ b/drivers/input/misc/pmic8xxx-pwrkey.c
@@ -182,7 +182,7 @@ static int pmic8xxx_pwrkey_probe(struct platform_device *pdev)
return 0;
free_press_irq:
- free_irq(key_press_irq, pwrkey);
+ free_irq(key_press_irq, pwr);
unreg_input_dev:
input_unregister_device(pwr);
pwr = NULL;
^ permalink raw reply related
* Re: [PATCH v3] if xen_platform_pci=0 is set don't blow up.
From: Bjorn Helgaas @ 2013-12-17 1:00 UTC (permalink / raw)
To: Konrad Rzeszutek Wilk
Cc: Jens Axboe, Stefano Stabellini, ian.campbell, xen-devel,
linux-kernel@vger.kernel.org, boris.ostrovsky, David Vrabel,
leosilva, ashley, Peter Hüwe, mail, tpmdd, Dmitry Torokhov,
plagnioj, tomi.valkeinen, tpmdd-devel,
linux-input@vger.kernel.org, netdev, linux-pci@vger.kernel.org,
linux-fbdev
In-Reply-To: <1387206250-13963-1-git-send-email-konrad.wilk@oracle.com>
On Mon, Dec 16, 2013 at 8:04 AM, Konrad Rzeszutek Wilk
<konrad.wilk@oracle.com> wrote:
> The first patch:
> [PATCH v3 1/2] xen/pvhvm: If xen_platform_pci=0 is set don't blow up
>
> I would like to commit to stable as it is fixing an eggregious bug -
> where we blow up if the guest config has: "xen_platform_pci=0" setup.
> This bug has been in existence for years and it is time to stamp it out.
>
> The second patch is a cleanup - not a stable candidate.
>
> It touches all of the Xen frontend drivers and adds the logic of:
> "if user disabled us, don't init" - with variations. As you can
> specify exactly which ones you want to init and which ones
> not (Linux runtime parameter 'xen_emul_unplug'). But for the majority
> of drivers - it is just an on/off switch.
>
> Since it touches a lot of maintainers I figured I would send it
> to Linus on Wednesday or Thursday.
>
> Thank you!
>
> arch/x86/xen/platform-pci-unplug.c | 79 ++++++++++++++++++++++++++++--
> drivers/block/xen-blkfront.c | 4 +-
> drivers/char/tpm/xen-tpmfront.c | 4 ++
> drivers/input/misc/xen-kbdfront.c | 4 ++
> drivers/net/xen-netfront.c | 2 +-
> drivers/pci/xen-pcifront.c | 4 ++
> drivers/video/xen-fbfront.c | 4 ++
> drivers/xen/xenbus/xenbus_probe_frontend.c | 2 +-
> include/xen/platform_pci.h | 25 +++++++++-
> 9 files changed, 119 insertions(+), 9 deletions(-)
If you want it:
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
for the PCI parts.
>
> Konrad Rzeszutek Wilk (2):
> xen/pvhvm: If xen_platform_pci=0 is set don't blow up (v3).
> xen/pvhvm: Remove the xen_platform_pci int.
>
^ 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