Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH 09/24] Port hid-dr to ff-memless-next
From: Michal Malý @ 2014-04-09 11:21 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port hid-dr to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/hid/Kconfig  |  2 +-
 drivers/hid/hid-dr.c | 59 ++++++++++++++++++++++++++++++++--------------------
 2 files changed, 37 insertions(+), 24 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 6e233d2..0ba1962 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -196,7 +196,7 @@ config HID_DRAGONRISE
 config DRAGONRISE_FF
 	bool "DragonRise Inc. force feedback"
 	depends on HID_DRAGONRISE
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	---help---
 	Say Y here if you want to enable force feedback support for DragonRise Inc.
 	game controllers.
diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c
index ce06444..b95c676 100644
--- a/drivers/hid/hid-dr.c
+++ b/drivers/hid/hid-dr.c
@@ -31,8 +31,10 @@
 #include <linux/slab.h>
 #include <linux/hid.h>
 #include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
 
 #include "hid-ids.h"
+#define FF_UPDATE_RATE 50
 
 #ifdef CONFIG_DRAGONRISE_FF
 
@@ -41,38 +43,49 @@ struct drff_device {
 };
 
 static int drff_play(struct input_dev *dev, void *data,
-				 struct ff_effect *effect)
+			const struct mlnx_effect_command *command)
 {
 	struct hid_device *hid = input_get_drvdata(dev);
 	struct drff_device *drff = data;
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 	int strong, weak;
 
-	strong = effect->u.rumble.strong_magnitude;
-	weak = effect->u.rumble.weak_magnitude;
+	strong = rumble_force->strong;
+	weak = rumble_force->weak;
 
 	dbg_hid("called with 0x%04x 0x%04x", strong, weak);
 
-	if (strong || weak) {
-		strong = strong * 0xff / 0xffff;
-		weak = weak * 0xff / 0xffff;
-
-		/* While reverse engineering this device, I found that when
-		   this value is set, it causes the strong rumble to function
-		   at a near maximum speed, so we'll bypass it. */
-		if (weak == 0x0a)
-			weak = 0x0b;
-
-		drff->report->field[0]->value[0] = 0x51;
-		drff->report->field[0]->value[1] = 0x00;
-		drff->report->field[0]->value[2] = weak;
-		drff->report->field[0]->value[4] = strong;
-		hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
-
-		drff->report->field[0]->value[0] = 0xfa;
-		drff->report->field[0]->value[1] = 0xfe;
-	} else {
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		if (strong || weak) {
+			strong = strong * 0xff / 0xffff;
+			weak = weak * 0xff / 0xffff;
+
+			/* While reverse engineering this device, I found that when
+			this value is set, it causes the strong rumble to function
+			at a near maximum speed, so we'll bypass it. */
+			if (weak == 0x0a)
+				weak = 0x0b;
+
+			drff->report->field[0]->value[0] = 0x51;
+			drff->report->field[0]->value[1] = 0x00;
+			drff->report->field[0]->value[2] = weak;
+			drff->report->field[0]->value[4] = strong;
+			hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
+
+			drff->report->field[0]->value[0] = 0xfa;
+			drff->report->field[0]->value[1] = 0xfe;
+		} else {
+			drff->report->field[0]->value[0] = 0xf3;
+			drff->report->field[0]->value[1] = 0x00;
+		}
+		break;
+	case MLNX_STOP_RUMBLE:
 		drff->report->field[0]->value[0] = 0xf3;
 		drff->report->field[0]->value[1] = 0x00;
+		break;
+	default:
+		return -EINVAL;
 	}
 
 	drff->report->field[0]->value[2] = 0x00;
@@ -116,7 +129,7 @@ static int drff_init(struct hid_device *hid)
 
 	set_bit(FF_RUMBLE, dev->ffbit);
 
-	error = input_ff_create_memless(dev, drff, drff_play);
+	error = input_ff_create_mlnx(dev, drff, drff_play, FF_UPDATE_RATE);
 	if (error) {
 		kfree(drff);
 		return error;
-- 
1.9.1


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

^ permalink raw reply related

* [PATCH 01/24] Add ff-memless-next module
From: Michal Malý @ 2014-04-09 11:13 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Add ff-memless-next module

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
---
 drivers/input/Kconfig                 |   11 +
 drivers/input/Makefile                |    1 +
 drivers/input/ff-memless-next.c       | 1036 +++++++++++++++++++++++++++++++++
 include/linux/input/ff-memless-next.h |  149 +++++
 4 files changed, 1197 insertions(+)
 create mode 100644 drivers/input/ff-memless-next.c
 create mode 100644 include/linux/input/ff-memless-next.h

diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index a11ff74..3780962 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -38,6 +38,17 @@ config INPUT_FF_MEMLESS
 	  To compile this driver as a module, choose M here: the
 	  module will be called ff-memless.
 
+config INPUT_FF_MEMLESS_NEXT
+	tristate "New version of support for memoryless force-feedback devices"
+	help
+	  Say Y here to enable a new version of support for memoryless force
+	  feedback devices.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ff-memless-next.
+
 config INPUT_POLLDEV
 	tristate "Polled input device skeleton"
 	help
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 5ca3f63..b4f11f5 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_INPUT)		+= input-core.o
 input-core-y := input.o input-compat.o input-mt.o ff-core.o
 
 obj-$(CONFIG_INPUT_FF_MEMLESS)	+= ff-memless.o
+obj-$(CONFIG_INPUT_FF_MEMLESS_NEXT)	+= ff-memless-next.o
 obj-$(CONFIG_INPUT_POLLDEV)	+= input-polldev.o
 obj-$(CONFIG_INPUT_SPARSEKMAP)	+= sparse-keymap.o
 obj-$(CONFIG_INPUT_MATRIXKMAP)	+= matrix-keymap.o
diff --git a/drivers/input/ff-memless-next.c b/drivers/input/ff-memless-next.c
new file mode 100644
index 0000000..f343865
--- /dev/null
+++ b/drivers/input/ff-memless-next.c
@@ -0,0 +1,1036 @@
+/*
+ * Force feedback support for memoryless devices
+ *
+ * This module is based on "ff-memless" orignally written by Anssi Hannula.
+ * It is extended to support all force feedback effects currently supported
+ * by the Linux input stack.
+ * Logic of emulation of FF_RUMBLE through FF_PERIODIC provided by
+ * Elias Vanderstuyft <elias.vds@gmail.com>
+ *
+ * Copyright(c) 2014 Michal "MadCatX" Maly <madcatxster@devoid-pointer.net>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/jiffies.h>
+#include <linux/fixp-arith.h>
+#include <linux/input/ff-memless-next.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal \"MadCatX\" Maly");
+MODULE_DESCRIPTION("Force feedback support for memoryless force feedback devices");
+
+#define FF_MAX_EFFECTS 16
+#define FF_MIN_EFFECT_LENGTH ((1000 / CONFIG_HZ) + 1)
+#define FF_EFFECT_STARTED 1
+#define FF_EFFECT_PLAYING 2
+#define FF_EFFECT_EMULATED 3
+
+enum mlnx_emulate {
+	EMUL_NOTHING,	/* Do not emulate anything */
+	EMUL_RUMBLE,	/* Emulate FF_RUMBLE with FF_PERIODIC */
+	EMUL_PERIODIC	/* Emulate FF_PERIODIC with FF_RUMBLE */
+};
+
+struct mlnx_effect {
+	struct ff_effect effect;
+	unsigned long flags;
+	unsigned long begin_at;
+	unsigned long stop_at;
+	unsigned long updated_at;
+	unsigned long attack_stop;
+	unsigned long fade_begin;
+	int repeat;
+};
+
+struct mlnx_device {
+	u8 combinable_playing;
+	u8 rumble_playing;
+	unsigned long update_rate_jiffies;
+	void *private;
+	struct mlnx_effect effects[FF_MAX_EFFECTS];
+	u16 gain;
+	struct timer_list timer;
+	struct input_dev *dev;
+	enum mlnx_emulate emul;
+
+	int (*control_effect)(struct input_dev *, void *,
+			      const struct mlnx_effect_command *);
+};
+
+static s32 mlnx_calculate_x_force(const s32 level,
+						  const u16 direction)
+{
+	s32 new = (level * -fixp_sin(direction)) >> FRAC_N;
+	pr_debug("x force: %d\n", new);
+	return new;
+}
+
+static s32 mlnx_calculate_y_force(const s32 level,
+						  const u16 direction)
+{
+	s32 new = (level * fixp_cos(direction)) >> FRAC_N;
+	pr_debug("y force: %d\n", new);
+	return new;
+}
+
+static bool mlnx_is_combinable(const struct ff_effect *effect)
+{
+	switch (effect->type) {
+	case FF_CONSTANT:
+	case FF_PERIODIC:
+	case FF_RAMP:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool mlnx_is_conditional(const struct ff_effect *effect)
+{
+	switch (effect->type) {
+	case FF_DAMPER:
+	case FF_FRICTION:
+	case FF_INERTIA:
+	case FF_SPRING:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void mlnx_clr_emulated(struct mlnx_effect *mlnxeff)
+{
+	__clear_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static void mlnx_clr_playing(struct mlnx_effect *mlnxeff)
+{
+	__clear_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static void mlnx_clr_started(struct mlnx_effect *mlnxeff)
+{
+	__clear_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_emulated(const struct mlnx_effect *mlnxeff)
+{
+	return test_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_rumble(const struct ff_effect *effect)
+{
+	return effect->type == FF_RUMBLE;
+}
+
+static bool mlnx_is_playing(const struct mlnx_effect *mlnxeff)
+{
+	return test_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static bool mlnx_is_started(const struct mlnx_effect *mlnxeff)
+{
+	return test_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_test_set_playing(struct mlnx_effect *mlnxeff)
+{
+	return test_and_set_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static const struct ff_envelope *mlnx_get_envelope(const struct ff_effect *effect)
+{
+	static const struct ff_envelope empty;
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		return &effect->u.constant.envelope;
+	case FF_PERIODIC:
+		return &effect->u.periodic.envelope;
+	case FF_RAMP:
+		return &effect->u.ramp.envelope;
+	default:
+		return &empty;
+	}
+}
+
+/* Some devices might have a limit on how many uncombinable effects
+ * can be played at once */
+static int mlnx_upload_conditional(struct mlnx_device *mlnxdev,
+				   const struct ff_effect *effect)
+{
+	struct mlnx_effect_command ecmd = {
+		.cmd = MLNX_UPLOAD_UNCOMB,
+		.u.uncomb.id = effect->id,
+		.u.uncomb.effect = effect
+	};
+	return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+}
+
+static int mlnx_erase_conditional(struct mlnx_device *mlnxdev,
+				  const struct ff_effect *effect)
+{
+	struct mlnx_effect_command ecmd = {
+		.cmd = MLNX_ERASE_UNCOMB,
+		.u.uncomb.id = effect->id,
+		.u.uncomb.effect = effect
+	};
+	return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+}
+
+static void mlnx_set_envelope_times(struct mlnx_effect *mlnxeff)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+	if (envelope->attack_length) {
+		unsigned long j = msecs_to_jiffies(envelope->attack_length);
+		mlnxeff->attack_stop = mlnxeff->begin_at + j;
+	}
+	if (effect->replay.length && envelope->fade_length) {
+		unsigned long j = msecs_to_jiffies(envelope->fade_length);
+		mlnxeff->fade_begin = mlnxeff->stop_at - j;
+	}
+}
+
+static void mlnx_set_trip_times(struct mlnx_effect *mlnxeff,
+				const unsigned long now)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const u16 replay_delay = effect->replay.delay;
+	const u16 replay_length = effect->replay.length;
+
+	mlnxeff->begin_at = now + msecs_to_jiffies(replay_delay);
+	mlnxeff->stop_at = mlnxeff->begin_at + msecs_to_jiffies(replay_length);
+	mlnxeff->updated_at = mlnxeff->begin_at;
+}
+
+static void mlnx_start_effect(struct mlnx_effect *mlnxeff)
+{
+	const unsigned long now = jiffies;
+
+	mlnx_set_trip_times(mlnxeff, now);
+	mlnx_set_envelope_times(mlnxeff);
+	__set_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static void mlnx_stop_effect(struct mlnx_device *mlnxdev,
+			     const struct mlnx_effect *mlnxeff)
+{
+	switch (mlnxeff->effect.type) {
+	case FF_PERIODIC:
+		if (mlnx_is_emulated(mlnxeff)) {
+			if (--mlnxdev->rumble_playing == 0) {
+				const struct mlnx_effect_command c = {
+					.cmd = MLNX_STOP_RUMBLE
+				};
+				mlnxdev->control_effect(mlnxdev->dev,
+							mlnxdev->private, &c);
+			}
+			return;
+		}
+	case FF_CONSTANT:
+	case FF_RAMP:
+		if (--mlnxdev->combinable_playing == 0) {
+			const struct mlnx_effect_command c = {
+				.cmd = MLNX_STOP_COMBINED
+			};
+			mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private,
+						&c);
+		}
+		return;
+	case FF_RUMBLE:
+		if (mlnx_is_emulated(mlnxeff)) {
+			if (--mlnxdev->combinable_playing == 0) {
+				const struct mlnx_effect_command c = {
+					.cmd = MLNX_STOP_COMBINED
+				};
+				mlnxdev->control_effect(mlnxdev->dev,
+							mlnxdev->private, &c);
+			}
+		} else {
+			if (--mlnxdev->rumble_playing == 0) {
+				const struct mlnx_effect_command c = {
+					.cmd = MLNX_STOP_RUMBLE
+				};
+				mlnxdev->control_effect(mlnxdev->dev,
+							mlnxdev->private, &c);
+			}
+		}
+		return;
+	case FF_DAMPER:
+	case FF_FRICTION:
+	case FF_INERTIA:
+	case FF_SPRING:
+	{
+		const struct mlnx_effect_command c = {
+			.cmd = MLNX_STOP_UNCOMB,
+			.u.uncomb.id = mlnxeff->effect.id,
+			.u.uncomb.effect = &mlnxeff->effect
+		};
+		mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &c);
+		return;
+	}
+	default:
+		return;
+	}
+}
+
+static int mlnx_restart_effect(struct mlnx_device *mlnxdev,
+			       struct mlnx_effect *mlnxeff)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const unsigned long now = jiffies;
+
+	if (mlnx_is_combinable(effect)) {
+		if (effect->replay.delay)
+			mlnx_stop_effect(mlnxdev, mlnxeff);
+		else {
+			if (mlnx_is_emulated(mlnxeff))
+				mlnxdev->rumble_playing--;
+			else
+				mlnxdev->combinable_playing--;
+		}
+	} else if (mlnx_is_rumble(effect)) {
+		if (effect->replay.delay)
+			mlnx_stop_effect(mlnxdev, mlnxeff);
+		else {
+			if (mlnx_is_emulated(mlnxeff))
+				mlnxdev->combinable_playing--;
+			else
+				mlnxdev->rumble_playing--;
+		}
+	} else if (mlnx_is_conditional(effect)) {
+		int ret;
+		if (effect->replay.delay)
+			mlnx_stop_effect(mlnxdev, mlnxeff);
+
+		ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+		if (ret)
+			return ret;
+	}
+
+	mlnx_set_trip_times(mlnxeff, now);
+	mlnx_set_envelope_times(mlnxeff);
+
+	return 0;
+}
+
+static s32 mlnx_apply_envelope(const struct mlnx_effect *mlnxeff,
+			       const s32 level)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+	const unsigned long now = jiffies;
+	const s32 alevel = abs(level);
+
+	/* Effect has an envelope with nonzero attack time */
+	if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+		const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+		const s32 alength = envelope->attack_length;
+		const s32 dlevel = (alevel - envelope->attack_level)
+				 * clength / alength;
+		return level < 0 ? -(dlevel + envelope->attack_level) :
+				    (dlevel + envelope->attack_level);
+	} else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+		const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+		const s32 flength = envelope->fade_length;
+		const s32 dlevel = (envelope->fade_level - alevel)
+				 * clength / flength;
+		return level < 0 ? -(dlevel + alevel) : (dlevel + alevel);
+	}
+
+	return level;
+}
+
+static s32 mlnx_calculate_periodic(struct mlnx_effect *mlnxeff, const s32 level)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const unsigned long now = jiffies;
+	const u16 period = effect->u.periodic.period;
+	const u16 phase = effect->u.periodic.phase;
+	const s16 offset = effect->u.periodic.offset;
+	s32 new = level;
+	u16 t = (jiffies_to_msecs(now - mlnxeff->begin_at) + phase) % period;
+
+	switch (effect->u.periodic.waveform) {
+	case FF_SINE:
+	{
+		u16 degrees = (360 * t) / period;
+		new = ((level * fixp_sin(degrees)) >> FRAC_N) + offset;
+		break;
+	}
+	case FF_SQUARE:
+	{
+		u16 degrees = (360 * t) / period;
+		new = level * (degrees < 180 ? 1 : -1) + offset;
+		break;
+	}
+	case FF_SAW_UP:
+		new = 2 * level * t / period - level + offset;
+		break;
+	case FF_SAW_DOWN:
+		new = level - 2 * level * t / period + offset;
+		break;
+	case FF_TRIANGLE:
+	{
+		new = (2 * abs(level - (2 * level * t) / period));
+		new = new - abs(level) + offset;
+		break;
+	}
+	case FF_CUSTOM:
+		pr_debug("Custom waveform is not handled by this driver\n");
+		return level;
+	default:
+		pr_err("Invalid waveform\n");
+		return level;
+	}
+
+	/* Ensure that the offset did not make the value exceed s16 range */
+	new = clamp(new, -0x7fff, 0x7fff);
+	pr_debug("level: %d, t: %u\n", new, t);
+	return new;
+}
+
+static s32 mlnx_calculate_ramp(const struct mlnx_effect *mlnxeff)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+	const unsigned long now = jiffies;
+	const u16 length = effect->replay.length;
+	const s16 mean = (effect->u.ramp.start_level + effect->u.ramp.end_level) / 2;
+	const u16 t = jiffies_to_msecs(now - mlnxeff->begin_at);
+	s32 start = effect->u.ramp.start_level;
+	s32 end = effect->u.ramp.end_level;
+	s32 new;
+
+	if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+		const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+		const s32 alength = envelope->attack_length;
+		s32 attack_level;
+		if (end > start)
+			attack_level = mean - envelope->attack_level;
+		else
+			attack_level = mean + envelope->attack_level;
+		start = (start - attack_level) * clength / alength
+		      + attack_level;
+	} else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+		const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+		const s32 flength = envelope->fade_length;
+		s32 fade_level;
+		if (end > start)
+			fade_level = mean + envelope->fade_level;
+		else
+			fade_level = mean - envelope->fade_level;
+		end = (fade_level - end) * clength / flength + end;
+	}
+
+	new = ((end - start) * t) / length + start;
+	new = clamp(new, -0x7fff, 0x7fff);
+	pr_debug("RAMP level: %d, t: %u\n", new, t);
+	return new;
+}
+
+static void mlnx_destroy(struct ff_device *dev)
+{
+	struct mlnx_device *mlnxdev = dev->private;
+	del_timer_sync(&mlnxdev->timer);
+
+	kfree(mlnxdev->private);
+}
+
+static unsigned long mlnx_get_envelope_update_time(const struct mlnx_effect *mlnxeff,
+						   const unsigned long update_rate_jiffies)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+	const unsigned long now = jiffies;
+	unsigned long fade_next;
+
+	/* Effect has an envelope with nonzero attack time */
+	if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+		if (time_before(mlnxeff->updated_at + update_rate_jiffies, mlnxeff->attack_stop))
+			return mlnxeff->updated_at + update_rate_jiffies;
+
+		return mlnxeff->attack_stop;
+	}
+
+	/* Effect has an envelope with nonzero fade time */
+	if (mlnxeff->effect.replay.length) {
+		if (!envelope->fade_length)
+			return mlnxeff->stop_at;
+
+		/* Schedule the next update when the fade begins */
+		if (time_before(mlnxeff->updated_at, mlnxeff->fade_begin))
+			return mlnxeff->fade_begin;
+
+		/* Already fading */
+		fade_next = mlnxeff->updated_at + update_rate_jiffies;
+
+		/* Schedule update when the effect stops */
+		if (time_after(fade_next, mlnxeff->stop_at))
+			return mlnxeff->stop_at;
+		/* Schedule update at the next checkpoint */
+			return fade_next;
+	}
+
+	/* There is no envelope */
+
+	/* Prevent the effect from being started twice */
+	if (mlnxeff->begin_at == now && mlnx_is_playing(mlnxeff))
+		return now - 1;
+
+	return mlnxeff->begin_at;
+}
+
+static unsigned long mlnx_get_update_time(struct mlnx_effect *mlnxeff,
+				const unsigned long update_rate_jiffies)
+{
+	const unsigned long now = jiffies;
+	unsigned long time, update_periodic;
+
+	switch (mlnxeff->effect.type) {
+	/* Constant effect does not change with time, but it can have
+	 * an envelope and a duration */
+	case FF_CONSTANT:
+		return mlnx_get_envelope_update_time(mlnxeff,
+						     update_rate_jiffies);
+	/* Periodic and ramp effects have to be periodically updated */
+	case FF_PERIODIC:
+	case FF_RAMP:
+		time = mlnx_get_envelope_update_time(mlnxeff,
+						     update_rate_jiffies);
+		if (mlnx_is_emulated(mlnxeff))
+			update_periodic = mlnxeff->stop_at;
+		else
+			update_periodic = mlnxeff->updated_at +
+					  update_rate_jiffies;
+
+		/* Periodic effect has to be updated earlier than envelope
+		 * or envelope update time is in the past */
+		if (time_before(update_periodic, time) || time_before(time, now))
+			return update_periodic;
+		/* Envelope needs to be updated */
+		return time;
+	case FF_RUMBLE:
+		if (mlnx_is_emulated(mlnxeff))
+			return mlnxeff->updated_at + update_rate_jiffies;
+	case FF_DAMPER:
+	case FF_FRICTION:
+	case FF_INERTIA:
+	case FF_SPRING:
+	default:
+		if (time_after_eq(mlnxeff->begin_at, now))
+			return mlnxeff->begin_at;
+
+		return mlnxeff->stop_at;
+	}
+}
+
+static void mlnx_schedule_playback(struct mlnx_device *mlnxdev)
+{
+	struct mlnx_effect *mlnxeff;
+	const unsigned long now = jiffies;
+	int events = 0;
+	int i;
+	unsigned long earliest = 0;
+	unsigned long time;
+
+	/* Iterate over all effects and determine the earliest
+	 * time when we have to attend to any */
+	for (i = 0; i < FF_MAX_EFFECTS; i++) {
+		mlnxeff = &mlnxdev->effects[i];
+
+		if (!mlnx_is_started(mlnxeff))
+			continue; /* Effect is not started, skip it */
+
+		if (mlnx_is_playing(mlnxeff))
+			time = mlnx_get_update_time(mlnxeff,
+						mlnxdev->update_rate_jiffies);
+		else
+			time = mlnxeff->begin_at;
+
+		pr_debug("Update time for effect %d: %lu\n", i, time);
+
+		/* Scheduled time is in the future and is either
+		 * before the current earliest time or it is
+		 * the first valid time value in this pass */
+		if (time_before_eq(now, time) &&
+		    (++events == 1 || time_before(time, earliest)))
+			earliest = time;
+	}
+
+	if (events) {
+		pr_debug("Events: %d, earliest: %lu\n", events, earliest);
+		mod_timer(&mlnxdev->timer, earliest);
+	} else {
+		pr_debug("No events, deactivating timer\n");
+		del_timer(&mlnxdev->timer);
+	}
+}
+
+static u16 mlnx_calculate_rumble_direction(const u32 total_mag, const u16 total_dir,
+					   const u32 new_mag, const u16 new_dir)
+{
+	if (!new_mag)
+		return total_dir;
+	if (!total_mag)
+		return new_dir;
+	return (((total_dir >> 1) * total_mag +
+		(new_dir >> 1) * new_mag) /
+		(total_mag + new_mag)) << 1;
+}
+
+static void mlnx_add_force(struct mlnx_effect *mlnxeff, s32 *cfx, s32 *cfy,
+			   const u16 gain)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	u16 direction;
+	s32 level;
+
+	pr_debug("Processing effect type %d, ID %d\n",
+		 mlnxeff->effect.type, mlnxeff->effect.id);
+
+	direction = mlnxeff->effect.direction * 360 / 0xffff;
+	pr_debug("Direction deg: %u\n", direction);
+
+	switch (mlnxeff->effect.type) {
+	case FF_CONSTANT:
+		level = mlnx_apply_envelope(mlnxeff, effect->u.constant.level);
+		break;
+	case FF_PERIODIC:
+		level = mlnx_apply_envelope(mlnxeff,
+					    effect->u.periodic.magnitude);
+		level = mlnx_calculate_periodic(mlnxeff, level);
+		break;
+	case FF_RAMP:
+		level = mlnx_calculate_ramp(mlnxeff);
+		break;
+	default:
+		pr_err("Effect %d not handled by mlnx_add_force\n",
+		       mlnxeff->effect.type);
+		return;
+	}
+
+	*cfx += mlnx_calculate_x_force(level, direction) * gain / 0xffff;
+	*cfy += mlnx_calculate_y_force(level, direction) * gain / 0xffff;
+}
+
+static void mlnx_add_rumble(const struct mlnx_effect *mlnxeff, u32 *strong_mag,
+			    u32 *weak_mag, u16 *strong_dir,
+			    u16 *weak_dir, const u16 gain)
+{
+	const struct ff_effect *eff = &mlnxeff->effect;
+	const struct ff_rumble_effect *reff = &mlnxeff->effect.u.rumble;
+	const u32 new_strong_mag = (u32)reff->strong_magnitude * gain / 0xffffU;
+	const u32 new_weak_mag = (u32)reff->weak_magnitude * gain / 0xffffU;
+
+	*strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
+						      new_strong_mag,
+						      eff->direction);
+	*weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
+						    new_weak_mag,
+						    eff->direction);
+	*strong_mag += new_strong_mag;
+	*weak_mag += new_weak_mag;
+}
+
+static void mlnx_add_emul_periodic(const struct mlnx_effect *mlnxeff,
+				   u32 *strong_mag, u32 *weak_mag,
+				   u16 *strong_dir, u16 *weak_dir,
+				   const u16 gain)
+{
+	const struct ff_effect *eff = &mlnxeff->effect;
+	const u32 level = (u32)abs(mlnx_apply_envelope(mlnxeff,
+						  eff->u.periodic.magnitude)) * gain / 0x7fffU;
+
+	*strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
+						      level, eff->direction);
+	*weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
+						    level, eff->direction);
+
+	*strong_mag += level;
+	*weak_mag += level;
+}
+
+static void mlnx_add_emul_rumble(const struct mlnx_effect *mlnxeff, s32 *cfx,
+				 s32 *cfy, const u16 gain,
+				 const unsigned long now,
+				 const unsigned long update_rate_jiffies)
+{
+	const struct ff_effect *effect = &mlnxeff->effect;
+	const u16 strong = effect->u.rumble.strong_magnitude;
+	const u16 weak = effect->u.rumble.weak_magnitude;
+	/* To calculate 't', we pretend that mlnxeff->begin_at == 0, thus t == now.  */
+	/* This will synchronise all simultaneously playing emul rumble effects,     */
+	/* otherwise non-deterministic phase-inversions could occur depending on     */
+	/* upload time, which could lead to undesired cancellation of these effects. */
+	const unsigned long t = now % (4UL * update_rate_jiffies);
+	s32 level = 0;
+	bool direction_up;
+	bool direction_left;
+
+	if (strong)
+		level += (strong / 4) * (t < 2UL * update_rate_jiffies ? 1 : -1);
+	if (weak)
+		level += (weak / 4) * (t < 2UL * update_rate_jiffies ?
+					(t < 1UL * update_rate_jiffies ? 1 : -1) :
+					(t < 3UL * update_rate_jiffies ? 1 : -1));
+	direction_up = (effect->direction > 0x3fffU && effect->direction <= 0xbfffU);
+	direction_left = (effect->direction <= 0x7fffU);
+
+	pr_debug("Emulated cf: %d, t: %lu, n: %lu, begin: %lu, diff: %lu j: %lu\n",
+		 level, t, now, mlnxeff->begin_at, now - mlnxeff->begin_at,
+		 update_rate_jiffies);
+	level = (level * gain) / 0xffff;
+	*cfx += direction_left ? -level : level;
+	*cfy += direction_up ? -level : level;
+}
+
+static void mlnx_play_effects(struct mlnx_device *mlnxdev)
+{
+	const u16 gain = mlnxdev->gain;
+	const unsigned long now = jiffies;
+	int i;
+	s32 cfx = 0;
+	s32 cfy = 0;
+	u32 strong_mag = 0;
+	u32 weak_mag = 0;
+	u16 strong_dir = 0;
+	u16 weak_dir = 0;
+
+	for (i = 0; i < FF_MAX_EFFECTS; i++) {
+		struct mlnx_effect *mlnxeff = &mlnxdev->effects[i];
+
+		if (!mlnx_is_started(mlnxeff)) {
+			pr_debug("Effect %hd/%d not started\n",
+				 mlnxeff->effect.id, i);
+			continue;
+		}
+
+		if (time_before(now, mlnxeff->begin_at)) {
+			pr_debug("Effect %hd/%d begins at a later time\n",
+				 mlnxeff->effect.id, i);
+			continue;
+		}
+
+		if (time_before_eq(mlnxeff->stop_at, now) && mlnxeff->effect.replay.length) {
+			pr_debug("Effect %hd/%d has to be stopped\n",
+				 mlnxeff->effect.id, i);
+
+			mlnx_clr_playing(mlnxeff);
+			if (--mlnxeff->repeat > 0)
+				mlnx_restart_effect(mlnxdev, mlnxeff);
+			else {
+				mlnx_stop_effect(mlnxdev, mlnxeff);
+				mlnx_clr_started(mlnxeff);
+				mlnx_clr_emulated(mlnxeff);
+				if (mlnx_is_conditional(&mlnxeff->effect))
+					mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
+			}
+
+			continue;
+		}
+
+		switch (mlnxeff->effect.type) {
+		case FF_PERIODIC:
+			if (mlnxdev->emul == EMUL_PERIODIC) {
+				if (!mlnx_test_set_playing(mlnxeff)) {
+					mlnxdev->rumble_playing++;
+					pr_debug("Starting emul periodic, total rumble %u\n",
+						 mlnxdev->rumble_playing);
+				}
+				__set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+				mlnx_add_emul_periodic(mlnxeff, &strong_mag, &weak_mag,
+						       &strong_dir, &weak_dir, gain);
+				break;
+			}
+		case FF_CONSTANT:
+		case FF_RAMP:
+			if (!mlnx_test_set_playing(mlnxeff)) {
+				mlnxdev->combinable_playing++;
+				pr_debug("Starting combinable effect, total %u\n",
+					 mlnxdev->combinable_playing);
+			}
+			mlnx_add_force(mlnxeff, &cfx, &cfy, gain);
+			break;
+		case FF_RUMBLE:
+			if (mlnxdev->emul == EMUL_RUMBLE) {
+				if (!mlnx_test_set_playing(mlnxeff)) {
+					mlnxdev->combinable_playing++;
+					pr_debug("Starting emul rumble, total comb %u\n",
+						 mlnxdev->combinable_playing);
+				}
+				__set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+				mlnx_add_emul_rumble(mlnxeff, &cfx, &cfy, gain, now,
+						     mlnxdev->update_rate_jiffies);
+			} else {
+				if (!mlnx_test_set_playing(mlnxeff)) {
+					mlnxdev->rumble_playing++;
+					pr_debug("Starting rumble effect, total %u\n",
+						 mlnxdev->rumble_playing);
+				}
+				mlnx_add_rumble(mlnxeff, &strong_mag, &weak_mag,
+						&strong_dir, &weak_dir, gain);
+			}
+			break;
+		case FF_DAMPER:
+		case FF_FRICTION:
+		case FF_INERTIA:
+		case FF_SPRING:
+			if (!mlnx_test_set_playing(mlnxeff)) {
+				const struct mlnx_effect_command ecmd = {
+					.cmd = MLNX_START_UNCOMB,
+					.u.uncomb.id = i,
+					.u.uncomb.effect = &mlnxeff->effect
+				};
+				mlnxdev->control_effect(mlnxdev->dev,
+						      mlnxdev->private, &ecmd);
+			}
+			break;
+		default:
+			pr_debug("Unhandled type of effect\n");
+		}
+		mlnxeff->updated_at = now;
+	}
+
+	if (mlnxdev->combinable_playing) {
+		const struct mlnx_effect_command ecmd = {
+			.cmd = MLNX_START_COMBINED,
+			.u.simple_force = {
+				.x = clamp(cfx, -0x7fff, 0x7fff),
+				.y = clamp(cfy, -0x7fff, 0x7fff)
+			}
+		};
+		mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+	}
+	if (mlnxdev->rumble_playing) {
+		const struct mlnx_effect_command ecmd = {
+			.cmd = MLNX_START_RUMBLE,
+			.u.rumble_force = {
+				.strong = clamp(strong_mag, (u32)0, (u32)0xffffU),
+				.weak = clamp(weak_mag, (u32)0, (u32)0xffffU),
+				.strong_dir = clamp(strong_dir, (u16)0, (u16)0xffffU),
+				.weak_dir = clamp(weak_dir, (u16)0, (u16)0xffffU)
+			}
+		};
+		mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+	}
+
+	mlnx_schedule_playback(mlnxdev);
+}
+
+static void mlnx_set_gain(struct input_dev *dev, u16 gain)
+{
+	struct mlnx_device *mlnxdev = dev->ff->private;
+	int i;
+
+	mlnxdev->gain = gain;
+
+	for (i = 0; i < FF_MAX_EFFECTS; i++) {
+		struct mlnx_effect *eff = &mlnxdev->effects[i];
+		if (eff == NULL)
+			continue;
+		if (mlnx_is_playing(eff)) {
+			if (mlnx_is_combinable(&eff->effect)) {
+				mlnx_clr_playing(eff);
+				if (mlnx_is_emulated(eff))
+					--mlnxdev->rumble_playing;
+				else
+					--mlnxdev->combinable_playing;
+			} else if (mlnx_is_rumble(&eff->effect)) {
+				mlnx_clr_playing(eff);
+				if (mlnx_is_emulated(eff))
+					--mlnxdev->combinable_playing;
+				else
+					--mlnxdev->rumble_playing;
+			}
+		}
+	}
+
+	mlnx_play_effects(mlnxdev);
+}
+
+static int mlnx_startstop(struct input_dev *dev, int effect_id, int repeat)
+{
+	struct mlnx_device *mlnxdev = dev->ff->private;
+	struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect_id];
+	int ret;
+
+	if (repeat > 0) {
+		pr_debug("Starting effect ID %d\n", effect_id);
+		mlnxeff->repeat = repeat;
+
+		if (!mlnx_is_started(mlnxeff)) {
+			/* Check that device has a free effect slot */
+			if (mlnx_is_conditional(&mlnxeff->effect)) {
+				ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+				if (ret) {
+					/* Device effect slots are all occupied */
+					pr_debug("No free effect slot for EID %d\n", effect_id);
+					return ret;
+				}
+			}
+			mlnx_start_effect(mlnxeff);
+		}
+	} else {
+		pr_debug("Stopping effect ID %d\n", effect_id);
+		if (mlnx_is_started(mlnxeff)) {
+			if (mlnx_is_playing(mlnxeff)) {
+				mlnx_clr_playing(mlnxeff);
+				mlnx_stop_effect(mlnxdev, mlnxeff);
+			}
+			mlnx_clr_started(mlnxeff);
+			mlnx_clr_emulated(mlnxeff);
+
+			if (mlnx_is_conditional(&mlnxeff->effect))
+				return mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
+		} else {
+			pr_debug("Effect ID %d already stopped\n", effect_id);
+			return 0;
+		}
+	}
+	mlnx_play_effects(mlnxdev);
+
+	return 0;
+}
+
+static void mlnx_timer_fired(unsigned long data)
+{
+	struct input_dev *dev = (struct input_dev *)data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	mlnx_play_effects(dev->ff->private);
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static int mlnx_upload(struct input_dev *dev, struct ff_effect *effect,
+		       struct ff_effect *old)
+{
+	struct mlnx_device *mlnxdev = dev->ff->private;
+	struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect->id];
+	const u16 length = effect->replay.length;
+	const u16 delay = effect->replay.delay;
+	int ret, fade_from;
+
+	/* Effect's timing is below kernel timer resolution */
+	if (length && length < FF_MIN_EFFECT_LENGTH)
+		effect->replay.length = FF_MIN_EFFECT_LENGTH;
+	if (delay && delay < FF_MIN_EFFECT_LENGTH)
+		effect->replay.delay = FF_MIN_EFFECT_LENGTH;
+
+	/* Periodic effects must have a non-zero period */
+	if (effect->type == FF_PERIODIC) {
+		if (!effect->u.periodic.period)
+			return -EINVAL;
+	}
+	/* Ramp effects cannot be infinite */
+	if (effect->type == FF_RAMP && !length)
+		return -EINVAL;
+
+	if (mlnx_is_combinable(effect)) {
+		const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+		/* Infinite effects cannot fade */
+		if (!length && envelope->fade_length > 0)
+			return -EINVAL;
+		/* Fade length cannot be greater than effect duration */
+		fade_from = (int)length - (int)envelope->fade_length;
+		if (fade_from < 0)
+			return -EINVAL;
+		/* Envelope cannot start fading before it finishes attacking */
+		if (fade_from < envelope->attack_length && fade_from > 0)
+			return -EINVAL;
+	}
+
+
+	spin_lock_irq(&dev->event_lock);
+	mlnxeff->effect = *effect; /* Keep internal copy of the effect */
+	/* Check if the effect being modified is playing */
+	if (mlnx_is_started(mlnxeff)) {
+		if (mlnx_is_playing(mlnxeff)) {
+			mlnx_clr_playing(mlnxeff);
+			ret = mlnx_restart_effect(mlnxdev, mlnxeff);
+
+			if (ret) {
+				/* Restore the original effect */
+				if (old)
+					mlnxeff->effect = *old;
+				spin_unlock_irq(&dev->event_lock);
+				return ret;
+			}
+		}
+
+		mlnx_schedule_playback(mlnxdev);
+	}
+
+	spin_unlock_irq(&dev->event_lock);
+
+	return 0;
+}
+
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+			 int (*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+			 const u16 update_rate)
+{
+	struct mlnx_device *mlnxdev;
+	int ret;
+
+	mlnxdev = kzalloc(sizeof(*mlnxdev), GFP_KERNEL);
+	if (!mlnxdev)
+		return -ENOMEM;
+
+	mlnxdev->dev = dev;
+	mlnxdev->private = data;
+	mlnxdev->control_effect = control_effect;
+	mlnxdev->gain = 0xffff;
+	mlnxdev->update_rate_jiffies = update_rate < FF_MIN_EFFECT_LENGTH ?
+				       FF_MIN_EFFECT_LENGTH : update_rate;
+	input_set_capability(dev, EV_FF, FF_GAIN);
+	setup_timer(&mlnxdev->timer, mlnx_timer_fired, (unsigned long)dev);
+
+	/* Set up effect emulation if needed */
+	if (test_bit(FF_PERIODIC, dev->ffbit) &&
+	    !test_bit(FF_RUMBLE, dev->ffbit)) {
+		set_bit(FF_RUMBLE, dev->ffbit);
+		mlnxdev->emul = EMUL_RUMBLE;
+		pr_debug("Emulating RUMBLE with PERIODIC\n");
+	} else if (test_bit(FF_RUMBLE, dev->ffbit) &&
+		   !test_bit(FF_PERIODIC, dev->ffbit)) {
+		set_bit(FF_PERIODIC, dev->ffbit);
+		set_bit(FF_SINE, dev->ffbit);
+		set_bit(FF_SQUARE, dev->ffbit);
+		set_bit(FF_TRIANGLE, dev->ffbit);
+		set_bit(FF_SAW_DOWN, dev->ffbit);
+		set_bit(FF_SAW_UP, dev->ffbit);
+		mlnxdev->emul = EMUL_PERIODIC;
+		pr_debug("Emulating PERIODIC with RUMBLE\n");
+	} else {
+		mlnxdev->emul = EMUL_NOTHING;
+		pr_debug("No effect emulation is necessary\n");
+	}
+
+	ret = input_ff_create(dev, FF_MAX_EFFECTS);
+	if (ret) {
+		kfree(mlnxdev);
+		return ret;
+	}
+
+
+	dev->ff->private = mlnxdev;
+	dev->ff->upload = mlnx_upload;
+	dev->ff->set_gain = mlnx_set_gain;
+	dev->ff->destroy = mlnx_destroy;
+	dev->ff->playback = mlnx_startstop;
+
+	pr_debug("Device successfully registered.\n");
+	return 0;
+}
+EXPORT_SYMBOL_GPL(input_ff_create_mlnx);
diff --git a/include/linux/input/ff-memless-next.h b/include/linux/input/ff-memless-next.h
new file mode 100644
index 0000000..2f15897
--- /dev/null
+++ b/include/linux/input/ff-memless-next.h
@@ -0,0 +1,149 @@
+#include <linux/input.h>
+
+/** DEFINITION OF TERMS
+ *
+ * Combined effect - An effect whose force is a superposition of forces
+ *                   generated by all effects that can be added together.
+ *                   Only one combined effect can be playing at a time.
+ *                   Effects that can be added together to create a combined
+ *                   effect are FF_CONSTANT, FF_PERIODIC and FF_RAMP.
+ * Uncombinable effect - An effect that cannot be combined with another effect.
+ *                       All conditional effects - FF_DAMPER, FF_FRICTION,
+ *                       FF_INERTIA and FF_SPRING are uncombinable.
+ *                       Number of uncombinable effects playing simultaneously
+ *                       depends on the capabilities of the hardware.
+ * Rumble effect - An effect generated by device's rumble motors instead of
+ *                 force feedback actuators.
+ *
+ *
+ * HANDLING OF UNCOMBINABLE EFFECTS
+ *
+ * Uncombinable effects cannot be combined together into just one effect, at
+ * least not in a clear and obvious manner. Therefore these effects have to
+ * be handled individually by ff-memless-next. Handling of these effects is
+ * left entirely to the hardware-specific driver, ff-memless-next merely
+ * passes these effects to the hardware-specific driver at appropriate time.
+ * ff-memless-next provides the UPLOAD command to notify the hardware-specific
+ * driver that the userspace is about to request playback of an uncombinable
+ * effect. The hardware-specific driver shall take all steps needed to make
+ * the device ready to play the effect when it receives the UPLOAD command.
+ * The actual playback shall commence when START_UNCOMB command is received.
+ * Opposite to the UPLOAD command is the ERASE command which tells
+ * the hardware-specific driver that the playback has finished and that
+ * the effect will not be restarted. STOP_UNCOMB command tells
+ * the hardware-specific driver that the playback shall stop but the device
+ * shall still be ready to resume the playback immediately.
+ *
+ * In case it is not possible to make the device ready to play an uncombinable
+ * effect (all hardware effect slots are occupied), the hardware-specific
+ * driver may return an error when it receives an UPLOAD command. If the
+ * hardware-specific driver returns 0, the upload is considered successful.
+ * START_UNCOMB and STOP_UNCOMB commands cannot fail and the device must always
+ * start the playback of the requested effect if the UPLOAD command of the
+ * respective effect has been successful. ff-memless-next will never send
+ * a START/STOP_UNCOMB command for an effect that has not been uploaded
+ * successfully, nor will it send an ERASE command for an effect that is
+ * playing (= has been started with START_UNCOMB command).
+ */
+
+enum mlnx_commands {
+	/* Start or update a combined effect. This command is sent whenever
+	 * a FF_CONSTANT, FF_PERIODIC or FF_RAMP is started, stopped or
+	 * updated by userspace, when the applied envelopes are recalculated
+	 * or when periodic effects are recalculated. */
+	MLNX_START_COMBINED,
+	/* Stop combined effect. This command is sent when all combinable
+	 * effects are stopped. */
+	MLNX_STOP_COMBINED,
+	/* Start or update a rumble effect. This command is sent whenever
+	 * a FF_RUMBLE effect is started or when its magnitudes or directions
+	 * change. */
+	MLNX_START_RUMBLE,
+	/* Stop a rumble effect. This command is sent when all FF_RUMBLE
+	 * effects are stopped. */
+	MLNX_STOP_RUMBLE,
+	/* Start or update an uncombinable effect. This command is sent
+	 * whenever an uncombinable effect is started or updated. */
+	MLNX_START_UNCOMB,
+	/* Stop uncombinable effect. This command is sent when an uncombinable
+	 * effect is stopped. */
+	MLNX_STOP_UNCOMB,
+	/* Upload uncombinable effect to device. This command is sent when the
+	 * effect is started from userspace. It is up to the hardware-specific
+	 * driver to handle this situation.
+	 */
+	MLNX_UPLOAD_UNCOMB,
+	/* Remove uncombinable effect from device, This command is sent when
+	 * and uncombinable effect has finished playing and will not be
+	 * restarted.
+	 */
+	MLNX_ERASE_UNCOMB
+};
+
+/** struct mlnx_simple_force - holds constant forces along X and Y axis
+ * @x: Force along X axis. Negative value denotes force pulling to the left,
+ *     positive value denotes force pulling to the right.
+ * @y: Force along Y axis. Negative value denotes force denotes force pulling
+ *     away from the user, positive value denotes force pulling towards
+ *     the user.
+ */
+struct mlnx_simple_force {
+	const s32 x;
+	const s32 y;
+};
+
+/** struct mlnx_rumble_force - holds information about rumble effect
+ * @strong: Magnitude of the strong vibration.
+ * @weak: Magnitude of the weak vibration.
+ * @strong_dir: Direction of the strong vibration expressed in the same way
+ *              as the direction of force feedback effect in struct ff_effect.
+ * @weak_dir: Direction of the weak vibration, same as above applies.
+ */
+struct mlnx_rumble_force {
+	const u32 strong;
+	const u32 weak;
+	const u16 strong_dir;
+	const u16 weak_dir;
+};
+
+/** struct mlnx_uncomb_effect - holds information about uncombinable effect
+ * @id: Id of the effect assigned by ff-core.
+ * @effect: Pointer to the uncombinable effect stored in ff-memless-next module
+ *          Hardware-specific driver must not alter this.
+ */
+struct mlnx_uncomb_effect {
+	const int id;
+	const struct ff_effect *effect;
+};
+
+/** struct mlnx_commands - describes what action shall the force feedback
+ *                         device perform
+ * @cmd: Type of the action.
+ * @u: Data associated with the action.
+ */
+struct mlnx_effect_command {
+	const enum mlnx_commands cmd;
+	union {
+		const struct mlnx_simple_force simple_force;
+		const struct mlnx_rumble_force rumble_force;
+		const struct mlnx_uncomb_effect uncomb;
+	} u;
+};
+
+/** input_ff_create_mlnx() - Register a device within ff-memless-next and
+ *                           the kernel force feedback system
+ * @dev: Pointer to the struct input_dev associated with the device.
+ * @data: Any device-specific data that shall be passed to the callback.
+ *        function called by ff-memless-next when a force feedback action
+ *        shall be performed.
+ * @control_effect: Pointer to the callback function.
+ * @update_date: Delay in milliseconds between two recalculations of periodic
+ *               effects, ramp effects and envelopes. Note that this value will
+ *               never be lower than (CONFIG_HZ / 1000) + 1 regardless of the
+ *               value specified here. This is not a "hard" rate limiter.
+ *               Userspace still can submit effects at a rate faster than
+ *               this value.
+ */
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+			 int (*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+			 const u16 update_rate);
-- 
1.9.1


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

^ permalink raw reply related

* [PATCH 0/24] Introduce ff-memless-next as an improved replacement for ff-memless
From: Michal Malý @ 2014-04-09 11:10 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft

ff-memless-next (MLNX) is a largely improved version of the current ff-memless
(FFML) driver. MLNX supports all force feedback effects currently available in
the Linux force feedback userspace API. All effects are handled in accordance
with Microsoft's DirectInput/XInput. Most notable changes include support for
conditional effects, proper handling of all periodic waveforms and improved
emulation of rumble effects through periodic effects. MLNX also uses its own
kernel API to pass processed effects to hardware-specific drivers instead of
abusing "ff_effect" struct. The API is documented in the respective header
file.

MLNX has been expanded to be a direct replacement for FFML.

Support for FF_PERIODIC and FF_RAMP has been added to all devices that
support FF_CONSTANT as a part of the port to the new API.

This patch series:
1) Adds "ff-memless-next" module [1]
2) Ports all hardware-specific drivers to MLNX's API [2-23]
3) Removes FFML and replaces it with MLNX [24]

Michal Malý
--
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 08/24] Port hid-emsff to ff-memless-next
From: Michal Malý @ 2014-04-09 11:19 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port hid-emsff to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/hid/Kconfig     |  2 +-
 drivers/hid/hid-emsff.c | 38 ++++++++++++++++++++++++++------------
 2 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 68c19a0..6e233d2 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -204,7 +204,7 @@ config DRAGONRISE_FF
 config HID_EMS_FF
 	tristate "EMS Production Inc. force feedback support"
 	depends on HID
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	---help---
 	Say Y here if you want to enable force feedback support for devices by
 	EMS Production Ltd.
diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c
index d82d75b..c0cbe50 100644
--- a/drivers/hid/hid-emsff.c
+++ b/drivers/hid/hid-emsff.c
@@ -24,30 +24,44 @@
 #include <linux/hid.h>
 #include <linux/input.h>
 #include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
 
 #include "hid-ids.h"
 
+#define FF_UPDATE_RATE 50
+
 struct emsff_device {
 	struct hid_report *report;
 };
 
 static int emsff_play(struct input_dev *dev, void *data,
-			 struct ff_effect *effect)
+			 const struct mlnx_effect_command *command)
 {
 	struct hid_device *hid = input_get_drvdata(dev);
 	struct emsff_device *emsff = data;
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 	int weak, strong;
 
-	weak = effect->u.rumble.weak_magnitude;
-	strong = effect->u.rumble.strong_magnitude;
-
-	dbg_hid("called with 0x%04x 0x%04x\n", strong, weak);
-
-	weak = weak * 0xff / 0xffff;
-	strong = strong * 0xff / 0xffff;
-
-	emsff->report->field[0]->value[1] = weak;
-	emsff->report->field[0]->value[2] = strong;
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		weak = rumble_force->weak;
+		strong = rumble_force->strong;
+
+		dbg_hid("called with 0x%04x 0x%04x\n", strong, weak);
+
+		weak = weak * 0xff / 0xffff;
+		strong = strong * 0xff / 0xffff;
+
+		emsff->report->field[0]->value[1] = weak;
+		emsff->report->field[0]->value[2] = strong;
+		break;
+	case MLNX_STOP_RUMBLE:
+		weak = 0;
+		strong = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
 
 	dbg_hid("running with 0x%02x 0x%02x\n", strong, weak);
 	hid_hw_request(hid, emsff->report, HID_REQ_SET_REPORT);
@@ -88,7 +102,7 @@ static int emsff_init(struct hid_device *hid)
 
 	set_bit(FF_RUMBLE, dev->ffbit);
 
-	error = input_ff_create_memless(dev, emsff, emsff_play);
+	error = input_ff_create_mlnx(dev, emsff, emsff_play, FF_UPDATE_RATE);
 	if (error) {
 		kfree(emsff);
 		return error;
-- 
1.9.1


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

^ permalink raw reply related

* [PATCH 07/24] Port hid-axff to ff-memless-next
From: Michal Malý @ 2014-04-09 11:18 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port hid-axff to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/hid/Kconfig    |  2 +-
 drivers/hid/hid-axff.c | 32 +++++++++++++++++++++++---------
 2 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index f722001..68c19a0 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -106,7 +106,7 @@ config HID_ACRUX
 config HID_ACRUX_FF
 	bool "ACRUX force feedback support"
 	depends on HID_ACRUX
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	---help---
 	Say Y here if you want to enable force feedback support for ACRUX
 	game controllers.
diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c
index a594e47..7fbfcbc 100644
--- a/drivers/hid/hid-axff.c
+++ b/drivers/hid/hid-axff.c
@@ -31,31 +31,45 @@
 #include <linux/slab.h>
 #include <linux/hid.h>
 #include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
 
 #include "hid-ids.h"
 
+#define FF_UPDATE_RATE 50
 #ifdef CONFIG_HID_ACRUX_FF
 
 struct axff_device {
 	struct hid_report *report;
 };
 
-static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+static int axff_play(struct input_dev *dev, void *data,
+		     const struct mlnx_effect_command *command)
 {
 	struct hid_device *hid = input_get_drvdata(dev);
 	struct axff_device *axff = data;
 	struct hid_report *report = axff->report;
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 	int field_count = 0;
 	int left, right;
 	int i, j;
 
-	left = effect->u.rumble.strong_magnitude;
-	right = effect->u.rumble.weak_magnitude;
-
-	dbg_hid("called with 0x%04x 0x%04x", left, right);
-
-	left = left * 0xff / 0xffff;
-	right = right * 0xff / 0xffff;
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		left = rumble_force->strong;
+		right = rumble_force->weak;
+
+		dbg_hid("called with 0x%04x 0x%04x", left, right);
+
+		left = left * 0xff / 0xffff;
+		right = right * 0xff / 0xffff;
+		break;
+	case MLNX_STOP_RUMBLE:
+		left = 0;
+		right = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
 
 	for (i = 0; i < report->maxfield; i++) {
 		for (j = 0; j < report->field[i]->report_count; j++) {
@@ -107,7 +121,7 @@ static int axff_init(struct hid_device *hid)
 
 	set_bit(FF_RUMBLE, dev->ffbit);
 
-	error = input_ff_create_memless(dev, axff, axff_play);
+	error = input_ff_create_mlnx(dev, axff, axff_play, FF_UPDATE_RATE);
 	if (error)
 		goto err_free_mem;
 
-- 
1.9.1


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

^ permalink raw reply related

* [PATCH 06/24] Port pm8xxx-vibrator to ff-memless-next
From: Michal Malý @ 2014-04-09 11:17 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port pm8xxx-vibrator to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/input/misc/Kconfig           |  2 +-
 drivers/input/misc/pm8xxx-vibrator.c | 28 +++++++++++++++++++---------
 2 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 6ba327f..300b5a8 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -108,7 +108,7 @@ config INPUT_PCSPKR
 config INPUT_PM8XXX_VIBRATOR
 	tristate "Qualcomm PM8XXX vibrator support"
 	depends on MFD_PM8XXX
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	help
 	  This option enables device driver support for the vibrator
 	  on Qualcomm PM8xxx chip. This driver supports ff-memless interface
diff --git a/drivers/input/misc/pm8xxx-vibrator.c b/drivers/input/misc/pm8xxx-vibrator.c
index b88b7cb..6b0e9eb 100644
--- a/drivers/input/misc/pm8xxx-vibrator.c
+++ b/drivers/input/misc/pm8xxx-vibrator.c
@@ -17,6 +17,7 @@
 #include <linux/input.h>
 #include <linux/slab.h>
 #include <linux/regmap.h>
+#include <linux/input/ff-memless-next.h>
 
 #define VIB_DRV			0x4A
 
@@ -29,7 +30,7 @@
 #define VIB_MAX_LEVELS		(VIB_MAX_LEVEL_mV - VIB_MIN_LEVEL_mV)
 
 #define MAX_FF_SPEED		0xff
-
+#define FF_UPDATE_RATE		50
 /**
  * struct pm8xxx_vib - structure to hold vibrator data
  * @vib_input_dev: input device supporting force feedback
@@ -128,14 +129,23 @@ static void pm8xxx_vib_close(struct input_dev *dev)
  * Currently this driver supports only rumble effects.
  */
 static int pm8xxx_vib_play_effect(struct input_dev *dev, void *data,
-				  struct ff_effect *effect)
+				  const struct mlnx_command *command)
 {
 	struct pm8xxx_vib *vib = input_get_drvdata(dev);
-
-	vib->speed = effect->u.rumble.strong_magnitude >> 8;
-	if (!vib->speed)
-		vib->speed = effect->u.rumble.weak_magnitude >> 9;
-
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		vib->speed = rumble_force->strong >> 8;
+		if (!vib->speed)
+			vib->speed = rumble_force->weak >> 9;
+		break;
+	case MLNX_STOP_RUMBLE:
+		vib->speed = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
 	schedule_work(&vib->work);
 
 	return 0;
@@ -182,8 +192,8 @@ static int pm8xxx_vib_probe(struct platform_device *pdev)
 	input_set_drvdata(input_dev, vib);
 	input_set_capability(vib->vib_input_dev, EV_FF, FF_RUMBLE);
 
-	error = input_ff_create_memless(input_dev, NULL,
-					pm8xxx_vib_play_effect);
+	error = input_ff_create_mlnx(input_dev, NULL,
+				     pm8xxx_vib_play_effect, FF_UPDATE_RATE);
 	if (error) {
 		dev_err(&pdev->dev,
 			"couldn't register vibrator as FF device\n");
-- 
1.9.1


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

^ permalink raw reply related

* [PATCH 05/24] Port max8997_haptic to ff-memless-next
From: Michal Malý @ 2014-04-09 11:17 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port max8997_haptic to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/input/misc/Kconfig          |  2 +-
 drivers/input/misc/max8997_haptic.c | 25 +++++++++++++++++++------
 2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 91dcae8..6ba327f 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -157,7 +157,7 @@ config INPUT_MAX8925_ONKEY
 config INPUT_MAX8997_HAPTIC
 	tristate "MAXIM MAX8997 haptic controller support"
 	depends on PWM && HAVE_PWM && MFD_MAX8997
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	help
 	  This option enables device driver support for the haptic controller
 	  on MAXIM MAX8997 chip. This driver supports ff-memless interface
diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c
index 1fea548..029ac8b 100644
--- a/drivers/input/misc/max8997_haptic.c
+++ b/drivers/input/misc/max8997_haptic.c
@@ -31,6 +31,7 @@
 #include <linux/mfd/max8997-private.h>
 #include <linux/mfd/max8997.h>
 #include <linux/regulator/consumer.h>
+#include <linux/input/ff-memless-next.h>
 
 /* Haptic configuration 2 register */
 #define MAX8997_MOTOR_TYPE_SHIFT	7
@@ -43,6 +44,8 @@
 #define MAX8997_SIG_DUTY_SHIFT		2
 #define MAX8997_PWM_DUTY_SHIFT		0
 
+#define FF_UPDATE_RATE 50
+
 struct max8997_haptic {
 	struct device *dev;
 	struct i2c_client *client;
@@ -219,13 +222,23 @@ static void max8997_haptic_play_effect_work(struct work_struct *work)
 }
 
 static int max8997_haptic_play_effect(struct input_dev *dev, void *data,
-				  struct ff_effect *effect)
+				  const struct mlnx_effect_command *command)
 {
 	struct max8997_haptic *chip = input_get_drvdata(dev);
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 
-	chip->level = effect->u.rumble.strong_magnitude;
-	if (!chip->level)
-		chip->level = effect->u.rumble.weak_magnitude;
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		chip->level = rumble_force->strong;
+		if (!chip->level)
+			chip->level = rumble_force->weak;
+		break;
+	case MLNX_STOP_RUMBLE:
+		chip->level = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
 
 	schedule_work(&chip->work);
 
@@ -319,8 +332,8 @@ static int max8997_haptic_probe(struct platform_device *pdev)
 	input_set_drvdata(input_dev, chip);
 	input_set_capability(input_dev, EV_FF, FF_RUMBLE);
 
-	error = input_ff_create_memless(input_dev, NULL,
-				max8997_haptic_play_effect);
+	error = input_ff_create_mlnx(input_dev, NULL,
+				max8997_haptic_play_effect, FF_UPDATE_RATE);
 	if (error) {
 		dev_err(&pdev->dev,
 			"unable to create FF device, error: %d\n",
-- 
1.9.1


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

^ permalink raw reply related

* [PATCH 04/24] Port twl6040-vibra to ff-memless-next
From: Michal Malý @ 2014-04-09 11:16 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port twl6040-vibra to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/input/misc/Kconfig         |  2 +-
 drivers/input/misc/twl6040-vibra.c | 27 ++++++++++++++++++++++-----
 2 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 1016a1b..91dcae8 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -417,7 +417,7 @@ config INPUT_TWL4030_VIBRA
 config INPUT_TWL6040_VIBRA
 	tristate "Support for TWL6040 Vibrator"
 	depends on TWL6040_CORE
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	help
 	  This option enables support for TWL6040 Vibrator Driver.
 
diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c
index 77dc23b..7440a74 100644
--- a/drivers/input/misc/twl6040-vibra.c
+++ b/drivers/input/misc/twl6040-vibra.c
@@ -30,12 +30,14 @@
 #include <linux/of.h>
 #include <linux/workqueue.h>
 #include <linux/input.h>
+#include <linux/input/ff-memless-next.h>
 #include <linux/mfd/twl6040.h>
 #include <linux/slab.h>
 #include <linux/delay.h>
 #include <linux/regulator/consumer.h>
 
 #define EFFECT_DIR_180_DEG	0x8000
+#define FF_UPDATE_RATE		50
 
 /* Recommended modulation index 85% */
 #define TWL6040_VIBRA_MOD	85
@@ -197,9 +199,10 @@ static void vibra_play_work(struct work_struct *work)
 }
 
 static int vibra_play(struct input_dev *input, void *data,
-		      struct ff_effect *effect)
+		      const struct mlnx_effect_command *command)
 {
 	struct vibra_info *info = input_get_drvdata(input);
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 	int ret;
 
 	/* Do not allow effect, while the routing is set to use audio */
@@ -209,9 +212,23 @@ static int vibra_play(struct input_dev *input, void *data,
 		return -EBUSY;
 	}
 
-	info->weak_speed = effect->u.rumble.weak_magnitude;
-	info->strong_speed = effect->u.rumble.strong_magnitude;
-	info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1;
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		info->weak_speed = rumble_force->weak;
+		info->strong_speed = rumble_force->strong;
+		if (info->strong_speed >= info->weak_speed)
+			info->direction = rumble_force->strong_dir < EFFECT_DIR_180_DEG ? 1 : -1;
+		else
+			info->direction = rumble_force->weak_dir < EFFECT_DIR_180_DEG ? 1 : -1;
+		break;
+	case MLNX_STOP_RUMBLE:
+		info->weak_speed = 0;
+		info->strong_speed = 0;
+		info->direction = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
 
 	ret = queue_work(info->workqueue, &info->play_work);
 	if (!ret) {
@@ -367,7 +384,7 @@ static int twl6040_vibra_probe(struct platform_device *pdev)
 	info->input_dev->close = twl6040_vibra_close;
 	__set_bit(FF_RUMBLE, info->input_dev->ffbit);
 
-	ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
+	ret = input_ff_create_mlnx(info->input_dev, NULL, vibra_play, FF_UPDATE_RATE);
 	if (ret < 0) {
 		dev_err(info->dev, "couldn't register vibrator to FF\n");
 		goto err_ialloc;
-- 
1.9.1


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

^ permalink raw reply related

* [PATCH 03/24] Port twl4030-vibra to ff-memless-next
From: Michal Malý @ 2014-04-09 11:15 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port twl4030-vibra to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/input/misc/Kconfig         |  2 +-
 drivers/input/misc/twl4030-vibra.c | 31 +++++++++++++++++++++++++------
 2 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 33e0f5d..1016a1b 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -407,7 +407,7 @@ config INPUT_TWL4030_VIBRA
 	tristate "Support for TWL4030 Vibrator"
 	depends on TWL4030_CORE
 	select MFD_TWL4030_AUDIO
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	help
 	  This option enables support for TWL4030 Vibrator Driver.
 
diff --git a/drivers/input/misc/twl4030-vibra.c b/drivers/input/misc/twl4030-vibra.c
index 960ef2a..5274ad2 100644
--- a/drivers/input/misc/twl4030-vibra.c
+++ b/drivers/input/misc/twl4030-vibra.c
@@ -32,9 +32,11 @@
 #include <linux/mfd/twl4030-audio.h>
 #include <linux/input.h>
 #include <linux/slab.h>
+#include <linux/input/ff-memless-next.h>
 
 /* MODULE ID2 */
 #define LEDEN		0x00
+#define FF_UPDATE_RATE	50
 
 /* ForceFeedback */
 #define EFFECT_DIR_180_DEG	0x8000 /* range is 0 - 0xFFFF */
@@ -134,14 +136,31 @@ static void vibra_play_work(struct work_struct *work)
 /*** Input/ForceFeedback ***/
 
 static int vibra_play(struct input_dev *input, void *data,
-		      struct ff_effect *effect)
+		      const struct mlnx_effect_command *command)
 {
 	struct vibra_info *info = input_get_drvdata(input);
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		if (command->u.rumble_force.strong) {
+			info->speed = rumble_force->strong >> 8;
+			info->direction = rumble_force->strong_dir < EFFECT_DIR_180_DEG ?
+					  0 : 1;
+		} else {
+			info->speed = rumble_force->weak >> 9;
+			info->direction = rumble_force->weak_dir < EFFECT_DIR_180_DEG ?
+					  0 : 1;
+		}
+		break;
+	case MLNX_STOP_RUMBLE:
+		info->speed = 0;
+		info->direction = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
 
-	info->speed = effect->u.rumble.strong_magnitude >> 8;
-	if (!info->speed)
-		info->speed = effect->u.rumble.weak_magnitude >> 9;
-	info->direction = effect->direction < EFFECT_DIR_180_DEG ? 0 : 1;
 	schedule_work(&info->play_work);
 	return 0;
 }
@@ -227,7 +246,7 @@ static int twl4030_vibra_probe(struct platform_device *pdev)
 	info->input_dev->close = twl4030_vibra_close;
 	__set_bit(FF_RUMBLE, info->input_dev->ffbit);
 
-	ret = input_ff_create_memless(info->input_dev, NULL, vibra_play);
+	ret = input_ff_create_mlnx(info->input_dev, NULL, vibra_play, FF_UPDATE_RATE);
 	if (ret < 0) {
 		dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n");
 		return ret;
-- 
1.9.1

^ permalink raw reply related

* [PATCH 02/24] Port arizona-haptics to ff-memless-next
From: Michal Malý @ 2014-04-09 11:14 UTC (permalink / raw)
  To: dmitry.torokhov, jkosina; +Cc: linux-input, linux-kernel, Elias Vanderstuyft
In-Reply-To: <1609685.QRL9N9sQvX@sigyn>

Port arizona-haptics to ff-memless-next

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/input/misc/Kconfig           |  2 +-
 drivers/input/misc/arizona-haptics.c | 39 +++++++++++++++++++++++-------------
 2 files changed, 26 insertions(+), 15 deletions(-)

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7904ab0..33e0f5d 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -75,7 +75,7 @@ config INPUT_AD714X_SPI
 config INPUT_ARIZONA_HAPTICS
 	tristate "Arizona haptics support"
 	depends on MFD_ARIZONA && SND_SOC
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	help
 	  Say Y to enable support for the haptics module in Arizona CODECs.
 
diff --git a/drivers/input/misc/arizona-haptics.c b/drivers/input/misc/arizona-haptics.c
index ef2e281..b4707cc 100644
--- a/drivers/input/misc/arizona-haptics.c
+++ b/drivers/input/misc/arizona-haptics.c
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/input.h>
+#include <linux/input/ff-memless-next.h>
 #include <linux/slab.h>
 
 #include <sound/soc.h>
@@ -22,6 +23,8 @@
 #include <linux/mfd/arizona/pdata.h>
 #include <linux/mfd/arizona/registers.h>
 
+#define FF_UPDATE_RATE 50
+
 struct arizona_haptics {
 	struct arizona *arizona;
 	struct input_dev *input_dev;
@@ -108,29 +111,37 @@ static void arizona_haptics_work(struct work_struct *work)
 }
 
 static int arizona_haptics_play(struct input_dev *input, void *data,
-				struct ff_effect *effect)
+				const struct mlnx_effect_command *command)
 {
 	struct arizona_haptics *haptics = input_get_drvdata(input);
 	struct arizona *arizona = haptics->arizona;
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 
 	if (!arizona->dapm) {
 		dev_err(arizona->dev, "No DAPM context\n");
 		return -EBUSY;
 	}
 
-	if (effect->u.rumble.strong_magnitude) {
-		/* Scale the magnitude into the range the device supports */
-		if (arizona->pdata.hap_act) {
-			haptics->intensity =
-				effect->u.rumble.strong_magnitude >> 9;
-			if (effect->direction < 0x8000)
-				haptics->intensity += 0x7f;
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		if (rumble_force->strong) {
+			/* Scale the magnitude into the range the device supports */
+			if (arizona->pdata.hap_act) {
+				haptics->intensity = rumble_force->strong >> 9;
+				if (rumble_force->strong_dir < 0x8000)
+					haptics->intensity += 0x7f;
+			} else {
+				haptics->intensity = rumble_force->strong >> 8;
+			}
 		} else {
-			haptics->intensity =
-				effect->u.rumble.strong_magnitude >> 8;
+			haptics->intensity = 0;
 		}
-	} else {
+		break;
+	case MLNX_STOP_RUMBLE:
 		haptics->intensity = 0;
+		break;
+	default:
+		return -EINVAL;
 	}
 
 	schedule_work(&haptics->work);
@@ -183,10 +194,10 @@ static int arizona_haptics_probe(struct platform_device *pdev)
 	haptics->input_dev->close = arizona_haptics_close;
 	__set_bit(FF_RUMBLE, haptics->input_dev->ffbit);
 
-	ret = input_ff_create_memless(haptics->input_dev, NULL,
-				      arizona_haptics_play);
+	ret = input_ff_create_mlnx(haptics->input_dev, NULL,
+				   arizona_haptics_play, FF_UPDATE_RATE);
 	if (ret < 0) {
-		dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n",
+		dev_err(arizona->dev, "input_ff_create_mlnx() failed: %d\n",
 			ret);
 		goto err_ialloc;
 	}
-- 
1.9.1

^ permalink raw reply related

* Re: [PATCH] HID: lg2ff: add rumble magnitude clamping quirk
From: Jiri Kosina @ 2014-04-09  9:24 UTC (permalink / raw)
  To: Elias Vanderstuyft
  Cc: Edgar Simo-Serra, Michal Malý, linux-input, linux-kernel
In-Reply-To: <CADbOyBS2xxaXDGuYd+Kr2i9uFPkXMH3VK1+WQBp+UaSyh41=Xw@mail.gmail.com>

On Wed, 9 Apr 2014, Elias Vanderstuyft wrote:

> Since this patch haven't been applied yet, I withdraw this patch for
> the following reason:
> It will get in as a part of the move to ff-memless-next.

Okay, I am ignoring it for now. Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH] HID: lg2ff: add rumble magnitude clamping quirk
From: Elias Vanderstuyft @ 2014-04-09  9:19 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Elias Vanderstuyft, Edgar Simo-Serra, Michal Malý,
	linux-input, linux-kernel
In-Reply-To: <1396962367-6469-1-git-send-email-elias.vds@gmail.com>

Since this patch haven't been applied yet, I withdraw this patch for
the following reason:
It will get in as a part of the move to ff-memless-next.

Elias

^ permalink raw reply

* [PATCH resend 2/2] input/serio/8042: Add firmware_id support
From: Hans de Goede @ 2014-04-09  8:47 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Matthew Garrett, Benjamin Tissoires, Peter Hutterer, linux-input,
	Hans de Goede
In-Reply-To: <1397033270-29597-1-git-send-email-hdegoede@redhat.com>

Fill in the new serio firmware_id sysfs attribute for pnp instantiated
8042 serio ports.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Peter Hutterer <peter.hutterer@who-t.net>
---
 drivers/input/serio/i8042-x86ia64io.h | 26 ++++++++++++++++++++++++++
 drivers/input/serio/i8042.c           |  6 ++++++
 2 files changed, 32 insertions(+)

diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h
index 0ec9abb..3f9da83 100644
--- a/drivers/input/serio/i8042-x86ia64io.h
+++ b/drivers/input/serio/i8042-x86ia64io.h
@@ -704,6 +704,8 @@ static char i8042_pnp_aux_name[32];
 
 static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
 {
+	struct pnp_id *id = dev->id;
+
 	if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
 		i8042_pnp_data_reg = pnp_port_start(dev,0);
 
@@ -719,6 +721,17 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *
 		strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name));
 	}
 
+	if (id) {
+		strlcpy(i8042_kbd_firmware_id, id->id,
+			sizeof(i8042_kbd_firmware_id));
+		for (id = id->next; id; id = id->next) {
+			strlcat(i8042_kbd_firmware_id, " ",
+				sizeof(i8042_kbd_firmware_id));
+			strlcat(i8042_kbd_firmware_id, id->id,
+				sizeof(i8042_kbd_firmware_id));
+		}
+	}
+
 	/* Keyboard ports are always supposed to be wakeup-enabled */
 	device_set_wakeup_enable(&dev->dev, true);
 
@@ -728,6 +741,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *
 
 static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did)
 {
+	struct pnp_id *id = dev->id;
+
 	if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
 		i8042_pnp_data_reg = pnp_port_start(dev,0);
 
@@ -743,6 +758,17 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *
 		strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name));
 	}
 
+	if (id) {
+		strlcpy(i8042_aux_firmware_id, id->id,
+			sizeof(i8042_aux_firmware_id));
+		for (id = id->next; id; id = id->next) {
+			strlcat(i8042_aux_firmware_id, " ",
+				sizeof(i8042_aux_firmware_id));
+			strlcat(i8042_aux_firmware_id, id->id,
+				sizeof(i8042_aux_firmware_id));
+		}
+	}
+
 	i8042_pnp_aux_devices++;
 	return 0;
 }
diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c
index 020053f..3807c3e 100644
--- a/drivers/input/serio/i8042.c
+++ b/drivers/input/serio/i8042.c
@@ -87,6 +87,8 @@ MODULE_PARM_DESC(debug, "Turn i8042 debugging mode on and off");
 #endif
 
 static bool i8042_bypass_aux_irq_test;
+static char i8042_kbd_firmware_id[128];
+static char i8042_aux_firmware_id[128];
 
 #include "i8042.h"
 
@@ -1218,6 +1220,8 @@ static int __init i8042_create_kbd_port(void)
 	serio->dev.parent	= &i8042_platform_device->dev;
 	strlcpy(serio->name, "i8042 KBD port", sizeof(serio->name));
 	strlcpy(serio->phys, I8042_KBD_PHYS_DESC, sizeof(serio->phys));
+	strlcpy(serio->firmware_id, i8042_kbd_firmware_id,
+		sizeof(serio->firmware_id));
 
 	port->serio = serio;
 	port->irq = I8042_KBD_IRQ;
@@ -1244,6 +1248,8 @@ static int __init i8042_create_aux_port(int idx)
 	if (idx < 0) {
 		strlcpy(serio->name, "i8042 AUX port", sizeof(serio->name));
 		strlcpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys));
+		strlcpy(serio->firmware_id, i8042_aux_firmware_id,
+			sizeof(serio->firmware_id));
 		serio->close = i8042_port_close;
 	} else {
 		snprintf(serio->name, sizeof(serio->name), "i8042 AUX%d port", idx);
-- 
1.9.0


^ permalink raw reply related

* [PATCH resend 1/2] input/serio: Add a firmware_id sysfs attribute
From: Hans de Goede @ 2014-04-09  8:47 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Matthew Garrett, Benjamin Tissoires, Peter Hutterer, linux-input,
	Hans de Goede
In-Reply-To: <1397033270-29597-1-git-send-email-hdegoede@redhat.com>

serio devices exposed via platform firmware interfaces such as ACPI
may provide additional identifying information of use to userspace.

We don't associate the serio devices with the firmware device (we don't
set it as parent), so there's no way for userspace to make use of this
information.

We cannot change the parent for serio devices instantiated though a firmware
interface as that would break suspend / resume ordering.

Therefor this patch adds a new firmware_id sysfs attribute so that userspace
can get a string from there with any additional identifying information the
firmware interface may provide.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Peter Hutterer <peter.hutterer@who-t.net>
---
 drivers/input/serio/serio.c | 12 ++++++++++++
 include/linux/serio.h       |  1 +
 2 files changed, 13 insertions(+)

diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c
index 8f4c4ab..1788a4d 100644
--- a/drivers/input/serio/serio.c
+++ b/drivers/input/serio/serio.c
@@ -451,6 +451,13 @@ static ssize_t serio_set_bind_mode(struct device *dev, struct device_attribute *
 	return retval;
 }
 
+static ssize_t firmware_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct serio *serio = to_serio_port(dev);
+
+	return sprintf(buf, "%s\n", serio->firmware_id);
+}
+
 static DEVICE_ATTR_RO(type);
 static DEVICE_ATTR_RO(proto);
 static DEVICE_ATTR_RO(id);
@@ -473,12 +480,14 @@ static DEVICE_ATTR_RO(modalias);
 static DEVICE_ATTR_WO(drvctl);
 static DEVICE_ATTR(description, S_IRUGO, serio_show_description, NULL);
 static DEVICE_ATTR(bind_mode, S_IWUSR | S_IRUGO, serio_show_bind_mode, serio_set_bind_mode);
+static DEVICE_ATTR_RO(firmware_id);
 
 static struct attribute *serio_device_attrs[] = {
 	&dev_attr_modalias.attr,
 	&dev_attr_description.attr,
 	&dev_attr_drvctl.attr,
 	&dev_attr_bind_mode.attr,
+	&dev_attr_firmware_id.attr,
 	NULL
 };
 
@@ -923,6 +932,9 @@ static int serio_uevent(struct device *dev, struct kobj_uevent_env *env)
 	SERIO_ADD_UEVENT_VAR("SERIO_EXTRA=%02x", serio->id.extra);
 	SERIO_ADD_UEVENT_VAR("MODALIAS=serio:ty%02Xpr%02Xid%02Xex%02X",
 				serio->id.type, serio->id.proto, serio->id.id, serio->id.extra);
+	if (serio->firmware_id[0])
+		SERIO_ADD_UEVENT_VAR("SERIO_FIRMWARE_ID=%s",
+				     serio->firmware_id);
 
 	return 0;
 }
diff --git a/include/linux/serio.h b/include/linux/serio.h
index 36aac73..9f779c7 100644
--- a/include/linux/serio.h
+++ b/include/linux/serio.h
@@ -23,6 +23,7 @@ struct serio {
 
 	char name[32];
 	char phys[32];
+	char firmware_id[128];
 
 	bool manual_bind;
 
-- 
1.9.0


^ permalink raw reply related

* [PATCH resend 0/2] input/serio: Add a firmware_id sysfs attribute
From: Hans de Goede @ 2014-04-09  8:47 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Matthew Garrett, Benjamin Tissoires, Peter Hutterer, linux-input

Hi All,

It seems that in our last discussion of this patch-set we ended up agreeing
that this really is something which we want to have since Windows drivers use
on PNP IDs to distguinish between various models and using the same mechanism
as windows is the best way to avoid surprises.

So here is a resend with the intent to get this merged now that we agree on
this.

Thanks & Regards,

Hans

^ permalink raw reply

* [RESEND: PATCH 2/2] input: tc3589x-keypad: support probing from device tree
From: Linus Walleij @ 2014-04-09  7:28 UTC (permalink / raw)
  To: devicetree, Dmitry Torokhov, linux-input, Samuel Ortiz, Lee Jones
  Cc: linux-kernel, linux-arm-kernel, Mark Rutland, Linus Walleij

Implement device tree probing for the tc3589x keypad driver.
This is modeled on the STMPE keypad driver and tested on the
Ux500 TVK1281618 UIB.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
ChangeLog v2->v3:
- Use two local u32 variables to avoid weirdness in u8 casting
  of the resulting values to the pointers.
ChangeLog v1->v2:
- Fix rows/columns binding to read two u32's insead of two
  u8 /bits/ as noted by Mark Rutland.
---
 drivers/input/keyboard/tc3589x-keypad.c | 66 ++++++++++++++++++++++++++++++++-
 1 file changed, 64 insertions(+), 2 deletions(-)

diff --git a/drivers/input/keyboard/tc3589x-keypad.c b/drivers/input/keyboard/tc3589x-keypad.c
index 74494a357522..ad7abae69078 100644
--- a/drivers/input/keyboard/tc3589x-keypad.c
+++ b/drivers/input/keyboard/tc3589x-keypad.c
@@ -296,6 +296,65 @@ static void tc3589x_keypad_close(struct input_dev *input)
 	tc3589x_keypad_disable(keypad);
 }
 
+#ifdef CONFIG_OF
+static const struct tc3589x_keypad_platform_data *
+tc3589x_keypad_of_probe(struct device *dev)
+{
+	struct device_node *np = dev->of_node;
+	struct tc3589x_keypad_platform_data *plat;
+	u32 cols, rows;
+	u32 debounce_ms;
+	int proplen;
+
+	if (!np)
+		return ERR_PTR(-ENODEV);
+
+	plat = devm_kzalloc(dev, sizeof(*plat), GFP_KERNEL);
+	if (!plat)
+		return ERR_PTR(-ENOMEM);
+
+	of_property_read_u32(np, "keypad,num-columns", &cols);
+	of_property_read_u32(np, "keypad,num-rows", &rows);
+	plat->kcol = (u8) cols;
+	plat->krow = (u8) rows;
+	if (!plat->krow || !plat->kcol ||
+	     plat->krow > TC_KPD_ROWS || plat->kcol > TC_KPD_COLUMNS) {
+		dev_err(dev,
+			"keypad columns/rows not properly specified (%ux%u)\n",
+			plat->kcol, plat->krow);
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (!of_get_property(np, "linux,keymap", &proplen)) {
+		dev_err(dev, "property linux,keymap not found\n");
+		return ERR_PTR(-ENOENT);
+	}
+
+	plat->no_autorepeat = of_property_read_bool(np, "linux,no-autorepeat");
+	plat->enable_wakeup = of_property_read_bool(np, "linux,wakeup");
+
+	/* The custom delay format is ms/16 */
+	of_property_read_u32(np, "debounce-delay-ms", &debounce_ms);
+	if (debounce_ms)
+		plat->debounce_period = debounce_ms * 16;
+	else
+		plat->debounce_period = TC_KPD_DEBOUNCE_PERIOD;
+
+	plat->settle_time = TC_KPD_SETTLE_TIME;
+	/* FIXME: should be property of the IRQ resource? */
+	plat->irqtype = IRQF_TRIGGER_FALLING;
+
+	return plat;
+}
+#else
+static inline const struct tc3589x_keypad_platform_data *
+tc3589x_keypad_of_probe(struct device *dev)
+{
+	return ERR_PTR(-ENODEV);
+}
+#endif
+
+
 static int tc3589x_keypad_probe(struct platform_device *pdev)
 {
 	struct tc3589x *tc3589x = dev_get_drvdata(pdev->dev.parent);
@@ -306,8 +365,11 @@ static int tc3589x_keypad_probe(struct platform_device *pdev)
 
 	plat = tc3589x->pdata->keypad;
 	if (!plat) {
-		dev_err(&pdev->dev, "invalid keypad platform data\n");
-		return -EINVAL;
+		plat = tc3589x_keypad_of_probe(&pdev->dev);
+		if (IS_ERR(plat)) {
+			dev_err(&pdev->dev, "invalid keypad platform data\n");
+			return PTR_ERR(plat);
+		}
 	}
 
 	irq = platform_get_irq(pdev, 0);
-- 
1.9.0

^ permalink raw reply related

* [RESEND: PATCH 1/2] mfd: tc3589x: Add device tree bindings
From: Linus Walleij @ 2014-04-09  7:28 UTC (permalink / raw)
  To: devicetree, Dmitry Torokhov, linux-input, Samuel Ortiz, Lee Jones
  Cc: linux-kernel, linux-arm-kernel, Mark Rutland, Linus Walleij

This defines the device tree bindings for the Toshiba TC3589x
series of multi-purpose expanders. Only the stuff I can test
is defined: GPIO and keypad. Others may implement more
subdevices further down the road.

This is to complement
commit a435ae1d51e2f18414f2a87219fdbe068231e692
"mfd: Enable the tc3589x for Device Tree" which left off
the definition of the device tree bindings.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
---
ChangeLog v2->v3:
- Fix the keys/rows bindings to be u32 rather than
  /bits/ 8, inconsistency noted by Mark Rutland.
ChangeLog v1->v2:
- Include a verbose example in the DT bindings.
- Explain why this is a stand-alone bindings patch.
---
 Documentation/devicetree/bindings/mfd/tc3589x.txt | 107 ++++++++++++++++++++++
 1 file changed, 107 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/tc3589x.txt

diff --git a/Documentation/devicetree/bindings/mfd/tc3589x.txt b/Documentation/devicetree/bindings/mfd/tc3589x.txt
new file mode 100644
index 000000000000..c6ac5bd2ce51
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/tc3589x.txt
@@ -0,0 +1,107 @@
+* Toshiba TC3589x multi-purpose expander
+
+The Toshiba TC3589x series are I2C-based MFD devices which may expose the
+following built-in devices: gpio, keypad, rotator (vibrator), PWM (for
+e.g. LEDs or vibrators) The included models are:
+
+- TC35890
+- TC35892
+- TC35893
+- TC35894
+- TC35895
+- TC35896
+
+Required properties:
+ - compatible : must be "toshiba,tc35890", "toshiba,tc35892", "toshiba,tc35893",
+   "toshiba,tc35894", "toshiba,tc35895" or "toshiba,tc35896"
+ - reg : I2C address of the device
+ - interrupt-parent : specifies which IRQ controller we're connected to
+ - interrupts : the interrupt on the parent the controller is connected to
+ - interrupt-controller : marks the device node as an interrupt controller
+ - #interrupt-cells : should be <1>, the first cell is the IRQ offset on this
+   TC3589x interrupt controller.
+
+Optional nodes:
+
+- GPIO
+  This GPIO module inside the TC3589x has 24 (TC35890, TC35892) or 20
+  (other models) GPIO lines.
+ - compatible : must be "toshiba,tc3589x-gpio"
+ - interrupts : interrupt on the parent, which must be the tc3589x MFD device
+ - interrupt-controller : marks the device node as an interrupt controller
+ - #interrupt-cells : should be <2>, the first cell is the IRQ offset on this
+   TC3589x GPIO interrupt controller, the second cell is the interrupt flags
+   in accordance with <dt-bindings/interrupt-controller/irq.h>. The following
+   flags are valid:
+   - IRQ_TYPE_LEVEL_LOW
+   - IRQ_TYPE_LEVEL_HIGH
+   - IRQ_TYPE_EDGE_RISING
+   - IRQ_TYPE_EDGE_FALLING
+   - IRQ_TYPE_EDGE_BOTH
+ - gpio-controller : marks the device node as a GPIO controller
+ - #gpio-cells : should be <2>, the first cell is the GPIO offset on this
+   GPIO controller, the second cell is the flags.
+
+- Keypad
+  This keypad is the same on all variants, supporting up to 96 different
+  keys. The linux-specific properties are modeled on those already existing
+  in other input drivers.
+ - compatible : must be "toshiba,tc3589x-keypad"
+ - debounce-delay-ms : debounce interval in milliseconds
+ - keypad,num-rows : number of rows in the matrix, see
+   bindings/input/matrix-keymap.txt
+ - keypad,num-columns : number of columns in the matrix, see
+   bindings/input/matrix-keymap.txt
+ - linux,keymap: the definition can be found in
+   bindings/input/matrix-keymap.txt
+ - linux,no-autorepeat: do no enable autorepeat feature.
+ - linux,wakeup: use any event on keypad as wakeup event.
+
+Example:
+
+tc35893@44 {
+	compatible = "toshiba,tc35893";
+	reg = <0x44>;
+	interrupt-parent = <&gpio6>;
+	interrupts = <26 IRQ_TYPE_EDGE_RISING>;
+
+	interrupt-controller;
+	#interrupt-cells = <1>;
+
+	tc3589x_gpio {
+		compatible = "toshiba,tc3589x-gpio";
+		interrupts = <0>;
+
+		interrupt-controller;
+		#interrupt-cells = <2>;
+		gpio-controller;
+		#gpio-cells = <2>;
+	};
+	tc3589x_keypad {
+		compatible = "toshiba,tc3589x-keypad";
+		interrupts = <6>;
+		debounce-delay-ms = <4>;
+		keypad,num-columns = <8>;
+		keypad,num-rows = <8>;
+		linux,no-autorepeat;
+		linux,wakeup;
+		linux,keymap = <0x0301006b
+			        0x04010066
+				0x06040072
+				0x040200d7
+				0x0303006a
+				0x0205000e
+				0x0607008b
+				0x0500001c
+				0x0403000b
+				0x03040034
+				0x05020067
+				0x0305006c
+				0x040500e7
+				0x0005009e
+				0x06020073
+				0x01030039
+				0x07060069
+				0x050500d9>;
+	};
+};
-- 
1.9.0

^ permalink raw reply related

* [RESEND: PATCH 0/2] TC3589x keypad device tree support
From: Linus Walleij @ 2014-04-09  7:27 UTC (permalink / raw)
  To: devicetree, Dmitry Torokhov, linux-input, Samuel Ortiz, Lee Jones
  Cc: linux-kernel, linux-arm-kernel, Mark Rutland, Linus Walleij

RESEND: can we merge this to the MFD+input trees now?

It seems no subsystem maintainers have any opinions and the DT
bindings people are silent on the matter since weeks.

Linus Walleij (2):
  mfd: tc3589x: Add device tree bindings
  input: tc3589x-keypad: support probing from device tree

 Documentation/devicetree/bindings/mfd/tc3589x.txt | 107 ++++++++++++++++++++++
 drivers/input/keyboard/tc3589x-keypad.c           |  66 ++++++++++++-
 2 files changed, 171 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mfd/tc3589x.txt

-- 
1.9.0


^ permalink raw reply

* Re: [PATCH v4 7/7] HID: sony: Add blink support to the Sixaxis and DualShock 4 LEDs
From: simon @ 2014-04-09  5:51 UTC (permalink / raw)
  Cc: HID CORE LAYER, Jiri Kosina, Frank Praznik
In-Reply-To: <533EBE48.5010100@oh.rr.com>

[-- Attachment #1: Type: text/plain, Size: 1569 bytes --]

Unfortunately I was unable to find the 'led chaining' code I mentioned. I
guess it has been lost to the sands of time....

>> 3rd Party Intec - Was unable to get any controlled blinking. As
>> previously
>> mentioned all leds flash (automatic, as if first plugged in) whenever
>> all
>> leds are turned off.
>> I can control all leds in a static on/off mode, but can't set any
>> blinking/timer behaviour
>
> It sounds like this controller just doesn't implement all of the
> behavior of the official controller.  I'm not sure how to fix it if it's
> not obeying the instructions in valid output reports and I don't have
> one to test personally.  Do the lights flash properly when the
> controller is used with a PS3?

I was able to patch the code so that I could turn all LEDs off my Intec
controller, see attached. Tested this against the Intec and the SixAxis.

I was not able to make the LEDs flash in a controlled fashion, so you
might be right about the controller not working properly - although my
comments in python script suggests I did have something going.... must be
missing a snippet of info.


I also found that on the SixAxis, when I reported that I had to set 1st
LED off before I could set it on.... this only applies when the controller
was off and then plugged into USB. At this point the LEDs are slow
flashing, and the 'brightness' reports as 1 (driver code only writes to
device if value is changed).

If the device was already on (LEDs flashing) then only the 1st LED is set
on plug in, and I can turn LEDs off/on straight away.

Cheers,
Simon

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-HID-hid-sony-allow-3rd-party-INTEC-controller-to-tur.patch --]
[-- Type: text/x-patch; name="0001-HID-hid-sony-allow-3rd-party-INTEC-controller-to-tur.patch", Size: 1122 bytes --]

>From a0597309d26ddecb5d4662d9e3bcb4d2689b7ed5 Mon Sep 17 00:00:00 2001
From: Simon Wood <simon@mungewell.org>
Date: Tue, 8 Apr 2014 21:39:59 -0600
Subject: [PATCH] HID: hid-sony - allow 3rd party INTEC controller to turn off
 all leds

Without this patch the 3rd party INTEC (PS3) controller will blink all
leds when user turns them off, it appears to require an extra flag set.

Signed-off-by: Simon Wood <simon@mungewell.org>
---
 drivers/hid/hid-sony.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index aa5ece5..45cb8b6 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -1423,6 +1423,10 @@ static void sixaxis_state_worker(struct work_struct *work)
 	report.data.leds_bitmap |= sc->led_state[2] << 3;
 	report.data.leds_bitmap |= sc->led_state[3] << 4;
 
+	/* Set flag for all leds off, required for 3rd party INTEC controller */
+	if ((report.data.leds_bitmap & 0x1E) == 0)
+		report.data.leds_bitmap |= 0x20;
+
 	/*
 	 * The LEDs in the report are indexed in reverse order to their
 	 * corresponding light on the controller.
-- 
1.8.1.2

^ permalink raw reply related

* Re: HID vendor access from user space
From: Nestor Lopez Casado @ 2014-04-08 15:53 UTC (permalink / raw)
  To: David Herrmann
  Cc: Jiri Kosina, Dmitry Torokhov, open list:HID CORE LAYER,
	Olivier Gay, Benjamin Tissoires, Mathieu Meisser
In-Reply-To: <CANq1E4TR34FG+vbAeHWv1JT5V0w1k3hq6Kmhx3ngO8+=arMsWA@mail.gmail.com>

On Mon, Apr 7, 2014 at 6:27 PM, David Herrmann <dh.herrmann@gmail.com> wrote:
> Hi
>
> On Mon, Apr 7, 2014 at 6:15 PM, Nestor Lopez Casado
> <nlopezcasad@logitech.com> wrote:
>> We consider this a potential security problem and we are looking into
>> a solution that would enable the currently logged local user to access
>> the vendor collections of a hid device without requiring any special
>> permissions.
>>
>> Maybe the solution is not within the kernel itself, but rather on
>> using a user mode component, maybe the X server or even udev. Do you
>> get my point now ?
>
> There is a lot of work going on to make Xorg (and also Wayland
> compositors) run as non-root. Many people, however, seem to ignore
> that this means the compositor runs with the _same_ privileges as the
> applications. At least that is the security model we are going for.
> Therefore, if a compositor can access input devices, all applications
> can do so, too. Of course, you can introduce new privilege-levels and
> or run applications with less privileges than normal user processes.
> But I guess you get the point.
>
> Therefore, I really doubt there is much need to split the
> access-rights here. What you wanna do is to provide this
> privilege-separation on the kernel level. However, I think this is the
> wrong way to do it. Imagine an HID device that has several
> vendor-commands, some of them rather trivial and un-privileged, others
> not. Do you expect the kernel to provide one device-node for each? How
> do you expect the kernel to know which vender-extensions are _safe_ to
> be grouped together?

I do not expect the kernel to know whether vendor extensions are safe
to be grouped together, I would simply provide them as a separate
-user accessible- interface whose access rights can be assigned
separately from the classic input collections.

Assignment of access rights would then be up to udev rules. Which each
manufacturer/user can define for each of its devices. By default
access rights would stay for root only.

>
> Yes, the kernel should provide different interfaces for different
> hw-features so user-space can apply fine-grained access-modes.
> However, if we don't know what hardware feature a specific command
> represents, I don't think we should apply some kind of random
> interface separation. This is why Jiri and I recommend writing an
> in-kernel driver. That driver can be small and all it does it provide
> a separate interface for your vendor-extensions. This can be as easy
> as setting HID_QUIRK_MULTI_INPUT for given devices (or an equivalent
> HID_QUIRK_MULTI_HIDRAW, which doesn't exist, yet). I still think it
> would be nicer if the kernel provides a proper interface-abstraction,
> but I'd be fine with such a quirk, too.

The problem I see with a specific kernel driver for a specific vendor
extension is two fold:
1) It creates some potential for code bloat/duplication in the kernel
across multiple drivers for different manufacturers, while the only
purpose of this driver would be to expose some raw vendor
functionality to user programs.

2) The interface provided by this driver to user programs would
probably diverge from driver to driver, making it impossible to have a
generic user component (think signal11's HidApi lib for instance) to
provide a consistent hid access library across operating systems.

>
> Maybe you can describe one specific example? Otherwise, it's hard to
> imagine hid devices that provide two totally separate interfaces that
> we had the need to split them. All examples I know already provide
> different virtual HID devices and thus don't suffer from this problem.

You can look at the hid device descriptor that I posted in a previous
message of this thread. This device descriptor actually comes from one
of our devices: The Logitech BT Illuminated kbd K810. Most of our
input devices follow this pattern:

- Top level "classic" collection types (mouse, keyboard, etc)
- Top level vendor collections.

The vendor collections are the method we use to access extended
functionality of the device, (button reprogrammability, dpi settings,
extended battery status, firmware update and many others)

> Furthermore, did you do some research how other platforms deal with
> it?

Neither Microsoft Windows nor Apple Mac OS provide a posix-style
filesystem based device access model for HID devices. Nevertheless,
Windows does provide "physical device objects" for each top level
collection found in a hid device. From msdn:

"A report descriptor can include more than one top-level collection.
The HID class driver enumerates the top-level collections of an input
device and creates a physical device object (PDO) for each top-level
collection. User-mode applications or kernel-mode drivers can access a
top-level collection by opening its PDO and using the HIDClass support
routines and the HID class driver IOCTLs"

Access rights to vendor collection PDOs is granted to any logged on user.

Apple's Mac OS HidManager client API, provides a mechanism to select
the parent device of any top level hid collection, then Set/Get
report-style APIs to address each of the vendor collections.

Access rights seem to be given to any logged on user.

>
> Thanks
> David

^ permalink raw reply

* [PATCH] HID: lg2ff: add rumble magnitude clamping quirk
From: Elias Vanderstuyft @ 2014-04-08 13:06 UTC (permalink / raw)
  To: Jiri Kosina
  Cc: Elias Vanderstuyft, Edgar Simo-Serra, Michal Malý,
	linux-input, linux-kernel

If a magnitude in the output report is lower than 2, i.e. 1 or 0,
the corresponding rumble motor shakes irregularly,
instead of being turned (almost) off like when magnitude 2 is used.
On the other hand, if a magnitude is higher than 0xfd, i.e 0xfe or 0xff,
the corresponding rumble motor shakes irregularly with a rotation speed
lower than when magnitude 0xfd is used.
From 0x02 to 0xfd, the device behaves well:
a monotonic increase of rotation speed.
This applies to both weak and strong rumble motor types.

This patch fixes this issue by clamping magnitudes from 0x02 to 0xfd.

This behaviour is observed on the Formula Vibration wheel (ca04).
However it's not present on the Wingman Rumblepad (c20a) device,
yet the clamping has no effect on the haptic side,
so that special case handling is not needed.
Discussion on http://www.spinics.net/lists/linux-input/msg30711.html

Note: The same thing appears to happen in the Windows Logitech driver,
      except the max clamping bound is not 0xfd, but 0xfe.
      Experimentally, I proved this to be wrong.

Tested-by: Hendrik Iben <hendrik_iben@web.de>
Signed-off-by: Elias Vanderstuyft <elias.vds@gmail.com>
Cc: Edgar Simo-Serra <bobbens@gmail.com>
Cc: Michal Malý <madcatxster@devoid-pointer.net>
Cc: linux-input@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
 drivers/hid/hid-lg2ff.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c
index 0e3fb1a..e180e1e 100644
--- a/drivers/hid/hid-lg2ff.c
+++ b/drivers/hid/hid-lg2ff.c
@@ -38,12 +38,17 @@ static int play_effect(struct input_dev *dev, void *data,
 	struct lg2ff_device *lg2ff = data;
 	int weak, strong;
 
+#define CLAMP_QUIRK(x) do { if (x < 2) x = 2; else if (x > 0xfd) x = 0xfd; } \
+			while (0)
+
 	strong = effect->u.rumble.strong_magnitude;
 	weak = effect->u.rumble.weak_magnitude;
 
 	if (weak || strong) {
 		weak = weak * 0xff / 0xffff;
 		strong = strong * 0xff / 0xffff;
+		CLAMP_QUIRK(weak);
+		CLAMP_QUIRK(strong);
 
 		lg2ff->report->field[0]->value[0] = 0x51;
 		lg2ff->report->field[0]->value[2] = weak;
-- 
1.8.3.1

^ permalink raw reply related

* Re: [PATCHv2 1/5] Input: add common DT binding for touchscreens
From: Rob Herring @ 2014-04-08  2:53 UTC (permalink / raw)
  To: Sebastian Reichel
  Cc: Dmitry Torokhov, Dmitry Torokhov, linux-input@vger.kernel.org,
	Tony Lindgren, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, devicetree@vger.kernel.org, linux-omap,
	linux-kernel@vger.kernel.org
In-Reply-To: <20140407200022.GA20023@earth.universe>

On Mon, Apr 7, 2014 at 3:00 PM, Sebastian Reichel <sre@kernel.org> wrote:
> On Mon, Apr 07, 2014 at 11:38:01AM -0500, Rob Herring wrote:
>> On Sat, Apr 5, 2014 at 5:26 PM, Sebastian Reichel <sre@kernel.org> wrote:
>> > Add common DT binding documentation for touchscreen devices and
>> > implement input_parse_touchscreen_of_params, which parses the common
>> > properties and configures the input device accordingly.
>>
>> Good.
>>
>> [...]
>> > +Optional properties for Touchscreens:
>> > + - touchscreen-size-x          : horizontal resolution of touchscreen
>> > + - touchscreen-size-y          : vertical resolution of touchscreen
>>
>> While I like the consistency, x-size and y-size are already commonly
>> used. Perhaps the common binding should have both and x-size/y-size be
>> marked deprecated.
>
> So you want me to add something like the following?
>
> - x-size : deprecated name for touchscreen-size-x
> - y-size : deprecated name for touchscreen-size-y

Yes.

>
>> > + - touchscreen-max-pressure    : maximum reported pressure
>> > + - touchscreen-fuzz-x          : horizontal noise value of the absolute input device
>> > + - touchscreen-fuzz-y          : vertical noise value of the absolute input device
>> > + - touchscreen-fuzz-pressure   : pressure noise value of the absolute input device
>>
>> What are the units or are they just an arbitrary range dependent on
>> the controller? Several existing bindings appear to be in pixels, but
>> that seems wrong to me.
>
> x/y related properties: pixels
> pressure related properties: arbitrary range dependent on the controller

Please make this clear in the binding description.

>> There's also these various properties that should have common versions created:
>>
>> ti,x-plate-resistance and ti,x-plate-ohms (tsc2007)
>
> I think this is ti specific. But I should probably name the tsc2005
> property "ti,x-plate-ohms", so that its in sync with tsc2007.
>
>> - rohm,flip-x             : Flip touch coordinates on the X axis
>> - rohm,flip-y             : Flip touch coordinates on the Y axis
>> - x-invert: invert X axis
>> - y-invert: invert Y axis
>
> like this?
>
> - touchscreen-inverted-x: X axis is inverted
> - touchscreen-inverted-y: Y axis is inverted
> - x-invert: deprecated name for touchscreen-inverted-x
> - y-invert: deprecated name for touchscreen-inverted-y
>
> Inverting is currently not supported by the input system, though. So
> adding support for it to input_parse_touchscreen_of_params() is not
> trivial :/

Does not matter. I'm only asking you to define the property. Linux
does not have to use it currently.

>> - contact-threshold:
>> - moving-threshold:
>
> I think those are hardware specific. The properties are currently
> used by one driver, which writes them directly into some registers.
> The DT binding does not give more information about them.

moving-threshold is probably the same as fuzz. contact-threshold is
sounds like min pressure. I remember having to tune the minimum
pressure on a touchscreen I worked on long ago. So think think is
definitely not an uncommon property.

Rob

^ permalink raw reply

* Re: [PATCH 1/3] Input: synaptics-rmi4 - add capabilities for touchpads
From: Christopher Heiny @ 2014-04-08  1:04 UTC (permalink / raw)
  To: Dmitry Torokhov, Benjamin Tissoires
  Cc: Linux Input, Andrew Duggan, Vincent Huang, Vivian Ly,
	Daniel Rosenberg, Linus Walleij, David Herrmann, Jiri Kosina
In-Reply-To: <20140328161505.GC22658@core.coreip.homeip.net>

On 03/28/2014 09:15 AM, Dmitry Torokhov wrote:
> On Wed, Mar 19, 2014 at 10:29:34AM -0400, Benjamin Tissoires wrote:
>> Hi Chris,
>>
>> On 03/18/2014 09:03 PM, Christopher Heiny wrote:
>>> When the device is a touchpad additional capabilities need to
>>> be set and reported.
>>>
>>
>> We have a problem here. While this patch would have been fine in the
>> pre-v3.8 era, it is not true anymore.
>> However, the current branch where synaptics-rmi4 is attached is v3.4.
>>
>> So if you use the right API (the current one), it will not compile :(
>>
>> Dmitry, would it be possible to update the branch to at least v3.8
>> to get the new input-mt API? (if the Synaptics guys are ok).
>
> If we are getting ready to pull it into mainline (and I think we are
> for F01 and F11 support) then I think I should simply uprev to the
> latest released kernel.

Looking at this further, we can rebase pretty easily to 3.9 or 3.10.

New kernels would be more work, as we don't currently have a dev 
platform available for those.  A quick nose around the intertubes shows 
some 3.14 implementations for at least one of our dev platforms 
(BeagleBone Black), so it's not like it would be painful - it'd just 
take soemwhat longer.

In either case, I'd like to get the current patch backlog addressed 
before rebasing.  That way the change set can be limited to those needed 
for the rebase.

Dmitry - how do you want to approach this?

					Chris

^ permalink raw reply

* Re: [PATCHv2 1/5] Input: add common DT binding for touchscreens
From: Sebastian Reichel @ 2014-04-07 20:00 UTC (permalink / raw)
  To: Rob Herring
  Cc: Dmitry Torokhov, Dmitry Torokhov, linux-input@vger.kernel.org,
	Tony Lindgren, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, devicetree@vger.kernel.org, linux-omap,
	linux-kernel@vger.kernel.org
In-Reply-To: <CAL_Jsq+46bPtgnQc5aAN8bcaw=te6MX=cvMMNP6+6xjG8Drkbg@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 2673 bytes --]

On Mon, Apr 07, 2014 at 11:38:01AM -0500, Rob Herring wrote:
> On Sat, Apr 5, 2014 at 5:26 PM, Sebastian Reichel <sre@kernel.org> wrote:
> > Add common DT binding documentation for touchscreen devices and
> > implement input_parse_touchscreen_of_params, which parses the common
> > properties and configures the input device accordingly.
> 
> Good.
> 
> [...]
> > +Optional properties for Touchscreens:
> > + - touchscreen-size-x          : horizontal resolution of touchscreen
> > + - touchscreen-size-y          : vertical resolution of touchscreen
> 
> While I like the consistency, x-size and y-size are already commonly
> used. Perhaps the common binding should have both and x-size/y-size be
> marked deprecated.

So you want me to add something like the following?

- x-size : deprecated name for touchscreen-size-x
- y-size : deprecated name for touchscreen-size-y

> > + - touchscreen-max-pressure    : maximum reported pressure
> > + - touchscreen-fuzz-x          : horizontal noise value of the absolute input device
> > + - touchscreen-fuzz-y          : vertical noise value of the absolute input device
> > + - touchscreen-fuzz-pressure   : pressure noise value of the absolute input device
> 
> What are the units or are they just an arbitrary range dependent on
> the controller? Several existing bindings appear to be in pixels, but
> that seems wrong to me.

x/y related properties: pixels
pressure related properties: arbitrary range dependent on the controller

> There's also these various properties that should have common versions created:
> 
> ti,x-plate-resistance and ti,x-plate-ohms (tsc2007)

I think this is ti specific. But I should probably name the tsc2005
property "ti,x-plate-ohms", so that its in sync with tsc2007.

> - rohm,flip-x             : Flip touch coordinates on the X axis
> - rohm,flip-y             : Flip touch coordinates on the Y axis
> - x-invert: invert X axis
> - y-invert: invert Y axis

like this?

- touchscreen-inverted-x: X axis is inverted
- touchscreen-inverted-y: Y axis is inverted
- x-invert: deprecated name for touchscreen-inverted-x
- y-invert: deprecated name for touchscreen-inverted-y

Inverting is currently not supported by the input system, though. So
adding support for it to input_parse_touchscreen_of_params() is not
trivial :/

> - contact-threshold:
> - moving-threshold:

I think those are hardware specific. The properties are currently
used by one driver, which writes them directly into some registers.
The DT binding does not give more information about them.

I think those should have been vendor-prefixed.

-- Sebastian

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply

* Re: [PATCH] HID: rmi: introduce RMI driver for Synaptics touchpads
From: Benjamin Tissoires @ 2014-04-07 17:43 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Andrew Duggan, Christopher Heiny, Jiri Kosina, linux-input,
	linux-kernel@vger.kernel.org
In-Reply-To: <1396892373-3330-1-git-send-email-benjamin.tissoires@redhat.com>

On Mon, Apr 7, 2014 at 1:39 PM, Benjamin Tissoires
<benjamin.tissoires@redhat.com> wrote:
> This driver add support for RMI4 over USB or I2C.
> The current state is that it uses its own RMI4 implementation, but once
> RMI4 is merged upstream, the driver will be a transport driver for the
> RMI4 library.
>
> Signed-off-by: Andrew Duggan <aduggan@synaptics.com>
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---

Of course, I did not triple checked things here, and the version I
sent was missing the comments:

Hi,

well, part of this driver should be considered as temporary. Most of the
RMI4 processing and input handling will be deleted at some point.

I based my work on Andrew's regarding its port of RMI4 over HID (see
https://github.com/mightybigcar/synaptics-rmi4/tree/rmihid )
This repo presents how the driver may looks like at the end:
https://github.com/mightybigcar/synaptics-rmi4/blob/rmihid/drivers/input/rmi4/rmi_hid.c

Andrew validated the changes, so I kept his S-o-b and the module author.

Without this temporary solution, the workaround we gave to users
is to disable i2c-hid, which leads to disabling the touchscreen on the
XPS 11 and 12 (Haswell generation).

Cheers,
Benjamin

Related bugs:
https://bugzilla.redhat.com/show_bug.cgi?id=1048314
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1218973



>  drivers/hid/Kconfig    |   8 +
>  drivers/hid/Makefile   |   1 +
>  drivers/hid/hid-core.c |   2 +
>  drivers/hid/hid-rmi.c  | 889 +++++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/hid.h    |   2 +
>  5 files changed, 902 insertions(+)
>  create mode 100644 drivers/hid/hid-rmi.c
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 7af9d0b..762f15d 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -657,6 +657,14 @@ config HID_SUNPLUS
>         ---help---
>         Support for Sunplus wireless desktop.
>
> +config HID_RMI
> +       tristate "Synaptics RMI4 device support"
> +       depends on HID
> +       ---help---
> +       Support for Synaptics RMI4 touchpads.
> +       Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
> +       and want support for its special functionalities.
> +
>  config HID_GREENASIA
>         tristate "GreenAsia (Product ID 0x12) game controller support"
>         depends on HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index fc712dd..a6fa6ba 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -97,6 +97,7 @@ obj-$(CONFIG_HID_ROCCAT)      += hid-roccat.o hid-roccat-common.o \
>         hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
>         hid-roccat-koneplus.o hid-roccat-konepure.o hid-roccat-kovaplus.o \
>         hid-roccat-lua.o hid-roccat-pyra.o hid-roccat-ryos.o hid-roccat-savu.o
> +obj-$(CONFIG_HID_RMI)          += hid-rmi.o
>  obj-$(CONFIG_HID_SAITEK)       += hid-saitek.o
>  obj-$(CONFIG_HID_SAMSUNG)      += hid-samsung.o
>  obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index abaa0a5..c97ece8 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -1879,6 +1879,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
>         { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
>         { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
>         { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
> +       { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> +       { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
>         { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
>         { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) },
>         { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) },
> diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
> new file mode 100644
> index 0000000..699d631
> --- /dev/null
> +++ b/drivers/hid/hid-rmi.c
> @@ -0,0 +1,889 @@
> +/*
> + *  Copyright (c) 2013 Andrew Duggan <aduggan@synaptics.com>
> + *  Copyright (c) 2013 Synaptics Incorporated
> + *  Copyright (c) 2014 Benjamin Tissoires <benjamin.tissoires@gmail.com>
> + *  Copyright (c) 2014 Red Hat, Inc
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the Free
> + * Software Foundation; either version 2 of the License, or (at your option)
> + * any later version.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/hid.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/module.h>
> +#include <linux/pm.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +#include <linux/sched.h>
> +#include "hid-ids.h"
> +
> +#define RMI_MOUSE_REPORT_ID            0x01 /* Mouse emulation Report */
> +#define RMI_WRITE_REPORT_ID            0x09 /* Output Report */
> +#define RMI_READ_ADDR_REPORT_ID                0x0a /* Output Report */
> +#define RMI_READ_DATA_REPORT_ID                0x0b /* Input Report */
> +#define RMI_ATTN_REPORT_ID             0x0c /* Input Report */
> +#define RMI_SET_RMI_MODE_REPORT_ID     0x0f /* Feature Report */
> +
> +/* flags */
> +#define RMI_READ_REQUEST_PENDING       BIT(0)
> +#define RMI_READ_DATA_PENDING          BIT(1)
> +#define RMI_STARTED                    BIT(2)
> +
> +enum rmi_mode_type {
> +       RMI_MODE_OFF                    = 0,
> +       RMI_MODE_ATTN_REPORTS           = 1,
> +       RMI_MODE_NO_PACKED_ATTN_REPORTS = 2,
> +};
> +
> +struct rmi_function {
> +       unsigned page;                  /* page of the function */
> +       u16 query_base_addr;            /* base address for queries */
> +       u16 command_base_addr;          /* base address for commands */
> +       u16 control_base_addr;          /* base address for controls */
> +       u16 data_base_addr;             /* base address for datas */
> +       unsigned int interrupt_base;    /* cross-function interrupt number
> +                                        * (uniq in the device)*/
> +       unsigned int interrupt_count;   /* number of interrupts */
> +       unsigned int report_size;       /* size of a report */
> +       unsigned long irq_mask;         /* mask of the interrupts
> +                                        * (to be applied against ATTN IRQ) */
> +};
> +
> +/**
> + * struct rmi_data - stores information for hid communication
> + *
> + * @page_mutex: Locks current page to avoid changing pages in unexpected ways.
> + * @page: Keeps track of the current virtual page
> + *
> + * @wait: Used for waiting for read data
> + *
> + * @writeReport: output buffer when writing RMI registers
> + * @readReport: input buffer when reading RMI registers
> + *
> + * @input_report_size: size of an input report (advertised by HID)
> + * @output_report_size: size of an output report (advertised by HID)
> + *
> + * @flags: flags for the current device (started, reading, etc...)
> + *
> + * @f11: placeholder of internal RMI function F11 description
> + * @f30: placeholder of internal RMI function F30 description
> + *
> + * @max_fingers: maximum finger count reported by the device
> + * @max_x: maximum x value reported by the device
> + * @max_y: maximum y value reported by the device
> + *
> + * @gpio_led_count: count of GPIOs + LEDs reported by F30
> + * @button_count: actual physical buttons count
> + * @button_mask: button mask used to decode GPIO ATTN reports
> + * @button_state_mask: pull state of the buttons
> + *
> + * @input: pointer to the kernel input device
> + *
> + * @reset_work: worker which will be called in case of a mouse report
> + * @hdev: pointer to the struct hid_device
> + */
> +struct rmi_data {
> +       struct mutex page_mutex;
> +       int page;
> +
> +       wait_queue_head_t wait;
> +
> +       u8 *writeReport;
> +       u8 *readReport;
> +
> +       int input_report_size;
> +       int output_report_size;
> +
> +       unsigned long flags;
> +
> +       struct rmi_function f11;
> +       struct rmi_function f30;
> +
> +       unsigned int max_fingers;
> +       unsigned int max_x;
> +       unsigned int max_y;
> +       unsigned int x_size_mm;
> +       unsigned int y_size_mm;
> +
> +       unsigned int gpio_led_count;
> +       unsigned int button_count;
> +       unsigned long button_mask;
> +       unsigned long button_state_mask;
> +
> +       struct input_dev *input;
> +
> +       struct work_struct reset_work;
> +       struct hid_device *hdev;
> +};
> +
> +#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
> +
> +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len);
> +
> +/**
> + * rmi_set_page - Set RMI page
> + * @hdev: The pointer to the hid_device struct
> + * @page: The new page address.
> + *
> + * RMI devices have 16-bit addressing, but some of the physical
> + * implementations (like SMBus) only have 8-bit addressing. So RMI implements
> + * a page address at 0xff of every page so we can reliable page addresses
> + * every 256 registers.
> + *
> + * The page_mutex lock must be held when this function is entered.
> + *
> + * Returns zero on success, non-zero on failure.
> + */
> +static int rmi_set_page(struct hid_device *hdev, u8 page)
> +{
> +       struct rmi_data *data = hid_get_drvdata(hdev);
> +       int retval;
> +
> +       data->writeReport[0] = RMI_WRITE_REPORT_ID;
> +       data->writeReport[1] = 1;
> +       data->writeReport[2] = 0xFF;
> +       data->writeReport[4] = page;
> +
> +       retval = rmi_write_report(hdev, data->writeReport,
> +                       data->output_report_size);
> +       if (retval != data->output_report_size) {
> +               dev_err(&hdev->dev,
> +                       "%s: set page failed: %d.", __func__, retval);
> +               return retval;
> +       }
> +
> +       data->page = page;
> +       return 0;
> +}
> +
> +static int rmi_set_mode(struct hid_device *hdev, u8 mode)
> +{
> +       int ret;
> +       u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode};
> +
> +       ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, txbuf,
> +                       sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> +       if (ret < 0) {
> +               dev_err(&hdev->dev, "unable to set rmi mode to %d (%d)\n", mode,
> +                       ret);
> +               return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int rmi_write_report(struct hid_device *hdev, u8 *report, int len)
> +{
> +       int ret;
> +
> +       ret = hid_hw_output_report(hdev, (void *)report, len);
> +       if (ret < 0) {
> +               dev_err(&hdev->dev, "failed to write hid report (%d)\n", ret);
> +               return ret;
> +       }
> +
> +       return ret;
> +}
> +
> +static int rmi_read_block(struct hid_device *hdev, u16 addr, void *buf,
> +               const int len)
> +{
> +       struct rmi_data *data = hid_get_drvdata(hdev);
> +       int ret;
> +       int bytes_read;
> +       int bytes_needed;
> +       int retries;
> +       int read_input_count;
> +
> +       mutex_lock(&data->page_mutex);
> +
> +       if (RMI_PAGE(addr) != data->page) {
> +               ret = rmi_set_page(hdev, RMI_PAGE(addr));
> +               if (ret < 0)
> +                       goto exit;
> +       }
> +
> +       for (retries = 5; retries > 0; retries--) {
> +               data->writeReport[0] = RMI_READ_ADDR_REPORT_ID;
> +               data->writeReport[1] = 0; /* old 1 byte read count */
> +               data->writeReport[2] = addr & 0xFF;
> +               data->writeReport[3] = (addr >> 8) & 0xFF;
> +               data->writeReport[4] = len  & 0xFF;
> +               data->writeReport[5] = (len >> 8) & 0xFF;
> +
> +               set_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> +
> +               ret = rmi_write_report(hdev, data->writeReport,
> +                                               data->output_report_size);
> +               if (ret != data->output_report_size) {
> +                       clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> +                       dev_err(&hdev->dev,
> +                               "failed to write request output report (%d)\n",
> +                               ret);
> +                       goto exit;
> +               }
> +
> +               bytes_read = 0;
> +               bytes_needed = len;
> +               while (bytes_read < len) {
> +                       if (!wait_event_timeout(data->wait,
> +                               test_bit(RMI_READ_DATA_PENDING, &data->flags),
> +                                       msecs_to_jiffies(1000))) {
> +                               hid_warn(hdev, "%s: timeout elapsed\n",
> +                                        __func__);
> +                               ret = -EAGAIN;
> +                               break;
> +                       }
> +
> +                       read_input_count = data->readReport[1];
> +                       memcpy(buf + bytes_read, &data->readReport[2],
> +                               read_input_count < bytes_needed ?
> +                                       read_input_count : bytes_needed);
> +
> +                       bytes_read += read_input_count;
> +                       bytes_needed -= read_input_count;
> +                       clear_bit(RMI_READ_DATA_PENDING, &data->flags);
> +               }
> +
> +               if (ret >= 0) {
> +                       ret = 0;
> +                       break;
> +               }
> +       }
> +
> +exit:
> +       clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
> +       mutex_unlock(&data->page_mutex);
> +       return ret;
> +}
> +
> +static inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
> +{
> +       return rmi_read_block(hdev, addr, buf, 1);
> +}
> +
> +static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
> +               u8 finger_state, u8 *touch_data)
> +{
> +       int x, y, wx, wy;
> +       int wide, major, minor;
> +       int z;
> +
> +       input_mt_slot(hdata->input, slot);
> +       input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER,
> +                       finger_state == 0x01);
> +       if (finger_state == 0x01) {
> +               x = (touch_data[0] << 4) | (touch_data[2] & 0x07);
> +               y = (touch_data[1] << 4) | (touch_data[2] >> 4);
> +               wx = touch_data[3] & 0x07;
> +               wy = touch_data[3] >> 4;
> +               wide = (wx > wy);
> +               major = max(wx, wy);
> +               minor = min(wx, wy);
> +               z = touch_data[4];
> +
> +               /* y is inverted */
> +               y = hdata->max_y - y;
> +
> +               input_event(hdata->input, EV_ABS, ABS_MT_POSITION_X, x);
> +               input_event(hdata->input, EV_ABS, ABS_MT_POSITION_Y, y);
> +               input_event(hdata->input, EV_ABS, ABS_MT_ORIENTATION, wide);
> +               input_event(hdata->input, EV_ABS, ABS_MT_PRESSURE, z);
> +               input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
> +               input_event(hdata->input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
> +       }
> +}
> +
> +static void rmi_reset_work(struct work_struct *work)
> +{
> +       struct rmi_data *hdata = container_of(work, struct rmi_data,
> +                                               reset_work);
> +
> +       /* switch the device to RMI if we receive a generic mouse report */
> +       rmi_set_mode(hdata->hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +static inline int rmi_schedule_reset(struct hid_device *hdev)
> +{
> +       struct rmi_data *hdata = hid_get_drvdata(hdev);
> +       return schedule_work(&hdata->reset_work);
> +}
> +
> +static int rmi_f11_input_event(struct hid_device *hdev, u8 irq, u8 *data,
> +               int size)
> +{
> +       struct rmi_data *hdata = hid_get_drvdata(hdev);
> +       int offset;
> +       int i;
> +
> +       if (size < hdata->f11.report_size)
> +               return 0;
> +
> +       if (!(irq & hdata->f11.irq_mask))
> +               return 0;
> +
> +       offset = (hdata->max_fingers >> 2) + 1;
> +       for (i = 0; i < hdata->max_fingers; i++) {
> +               int fs_byte_position = i >> 2;
> +               int fs_bit_position = (i & 0x3) << 1;
> +               int finger_state = (data[fs_byte_position] >> fs_bit_position) &
> +                                       0x03;
> +
> +               rmi_f11_process_touch(hdata, i, finger_state,
> +                               &data[offset + 5 * i]);
> +       }
> +       input_mt_sync_frame(hdata->input);
> +       input_sync(hdata->input);
> +       return hdata->f11.report_size;
> +}
> +
> +static int rmi_f30_input_event(struct hid_device *hdev, u8 irq, u8 *data,
> +               int size)
> +{
> +       struct rmi_data *hdata = hid_get_drvdata(hdev);
> +       int i;
> +       int button = 0;
> +       bool value;
> +
> +       if (!(irq & hdata->f30.irq_mask))
> +               return 0;
> +
> +       for (i = 0; i < hdata->gpio_led_count; i++) {
> +               if (test_bit(i, &hdata->button_mask)) {
> +                       value = (data[i / 8] >> (i & 0x07)) & BIT(0);
> +                       if (test_bit(i, &hdata->button_state_mask))
> +                               value = !value;
> +                       input_event(hdata->input, EV_KEY, BTN_LEFT + button++,
> +                                       value);
> +               }
> +       }
> +       return hdata->f30.report_size;
> +}
> +
> +static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
> +{
> +       struct rmi_data *hdata = hid_get_drvdata(hdev);
> +       unsigned long irq_mask = 0;
> +       unsigned index = 2;
> +
> +       if (!(test_bit(RMI_STARTED, &hdata->flags)))
> +               return 0;
> +
> +       irq_mask |= hdata->f11.irq_mask;
> +       irq_mask |= hdata->f30.irq_mask;
> +
> +       if (data[1] & ~irq_mask)
> +               hid_warn(hdev, "unknown intr source:%02lx %s:%d\n",
> +                       data[1] & ~irq_mask, __FILE__, __LINE__);
> +
> +       if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
> +               index += rmi_f11_input_event(hdev, data[1], &data[index],
> +                               size - index);
> +               index += rmi_f30_input_event(hdev, data[1], &data[index],
> +                               size - index);
> +       } else {
> +               index += rmi_f30_input_event(hdev, data[1], &data[index],
> +                               size - index);
> +               index += rmi_f11_input_event(hdev, data[1], &data[index],
> +                               size - index);
> +       }
> +
> +       return 1;
> +}
> +
> +static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
> +{
> +       struct rmi_data *hdata = hid_get_drvdata(hdev);
> +
> +       if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
> +               hid_err(hdev, "no read request pending\n");
> +               return 0;
> +       }
> +
> +       memcpy(hdata->readReport, data, size < hdata->input_report_size ?
> +                       size : hdata->input_report_size);
> +       set_bit(RMI_READ_DATA_PENDING, &hdata->flags);
> +       wake_up(&hdata->wait);
> +
> +       return 1;
> +}
> +
> +static int rmi_raw_event(struct hid_device *hdev,
> +               struct hid_report *report, u8 *data, int size)
> +{
> +       switch (data[0]) {
> +       case RMI_READ_DATA_REPORT_ID:
> +               return rmi_read_data_event(hdev, data, size);
> +       case RMI_ATTN_REPORT_ID:
> +               return rmi_input_event(hdev, data, size);
> +       case RMI_MOUSE_REPORT_ID:
> +               rmi_schedule_reset(hdev);
> +               break;
> +       }
> +
> +       return 0;
> +}
> +
> +static int rmi_post_reset(struct hid_device *hdev)
> +{
> +       return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +static int rmi_post_resume(struct hid_device *hdev)
> +{
> +       return rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> +}
> +
> +#define RMI4_MAX_PAGE 0xff
> +#define RMI4_PAGE_SIZE 0x0100
> +
> +#define PDT_START_SCAN_LOCATION 0x00e9
> +#define PDT_END_SCAN_LOCATION  0x0005
> +#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
> +
> +struct pdt_entry {
> +       u8 query_base_addr:8;
> +       u8 command_base_addr:8;
> +       u8 control_base_addr:8;
> +       u8 data_base_addr:8;
> +       u8 interrupt_source_count:3;
> +       u8 bits3and4:2;
> +       u8 function_version:2;
> +       u8 bit7:1;
> +       u8 function_number:8;
> +} __attribute__((__packed__));
> +
> +static inline unsigned long rmi_gen_mask(unsigned irq_base, unsigned irq_count)
> +{
> +       return GENMASK(irq_count + irq_base - 1, irq_base);
> +}
> +
> +static void rmi_register_function(struct rmi_data *data,
> +       struct pdt_entry *pdt_entry, int page, unsigned interrupt_count)
> +{
> +       struct rmi_function *f = NULL;
> +       u16 page_base = page << 8;
> +
> +       switch (pdt_entry->function_number) {
> +       case 0x11:
> +               f = &data->f11;
> +               break;
> +       case 0x30:
> +               f = &data->f30;
> +               break;
> +       }
> +
> +       if (f) {
> +               f->page = page;
> +               f->query_base_addr = page_base | pdt_entry->query_base_addr;
> +               f->command_base_addr = page_base | pdt_entry->command_base_addr;
> +               f->control_base_addr = page_base | pdt_entry->control_base_addr;
> +               f->data_base_addr = page_base | pdt_entry->data_base_addr;
> +               f->interrupt_base = interrupt_count;
> +               f->interrupt_count = pdt_entry->interrupt_source_count;
> +               f->irq_mask = rmi_gen_mask(f->interrupt_base,
> +                                               f->interrupt_count);
> +       }
> +}
> +
> +static int rmi_scan_pdt(struct hid_device *hdev)
> +{
> +       struct rmi_data *data = hid_get_drvdata(hdev);
> +       struct pdt_entry entry;
> +       int page;
> +       bool page_has_function;
> +       int i;
> +       int retval;
> +       int interrupt = 0;
> +       u16 page_start, pdt_start , pdt_end;
> +
> +       hid_info(hdev, "Scanning PDT...\n");
> +
> +       for (page = 0; (page <= RMI4_MAX_PAGE); page++) {
> +               page_start = RMI4_PAGE_SIZE * page;
> +               pdt_start = page_start + PDT_START_SCAN_LOCATION;
> +               pdt_end = page_start + PDT_END_SCAN_LOCATION;
> +
> +               page_has_function = false;
> +               for (i = pdt_start; i >= pdt_end; i -= sizeof(entry)) {
> +                       retval = rmi_read_block(hdev, i, &entry, sizeof(entry));
> +                       if (retval) {
> +                               hid_err(hdev,
> +                                       "Read of PDT entry at %#06x failed.\n",
> +                                       i);
> +                               goto error_exit;
> +                       }
> +
> +                       if (RMI4_END_OF_PDT(entry.function_number))
> +                               break;
> +
> +                       page_has_function = true;
> +
> +                       hid_info(hdev, "Found F%02X on page %#04x\n",
> +                                       entry.function_number, page);
> +
> +                       rmi_register_function(data, &entry, page, interrupt);
> +                       interrupt += entry.interrupt_source_count;
> +               }
> +
> +               if (!page_has_function)
> +                       break;
> +       }
> +
> +       hid_info(hdev, "%s: Done with PDT scan.\n", __func__);
> +       retval = 0;
> +
> +error_exit:
> +       return retval;
> +}
> +
> +static int rmi_populate_f11(struct hid_device *hdev)
> +{
> +       struct rmi_data *data = hid_get_drvdata(hdev);
> +       u8 buf[20];
> +       int ret;
> +       bool has_query12;
> +       bool has_physical_props;
> +       unsigned x_size, y_size;
> +
> +       if (!data->f11.query_base_addr) {
> +               hid_err(hdev, "No 2D sensor found, giving up.\n");
> +               return -ENODEV;
> +       }
> +
> +       /* query 0 contains some useful information */
> +       ret = rmi_read(hdev, data->f11.query_base_addr, buf);
> +       if (ret) {
> +               hid_err(hdev, "can not get query 0: %d.\n", ret);
> +               return ret;
> +       }
> +       has_query12 = !!(buf[0] & BIT(5));
> +
> +       /* query 1 to get the max number of fingers */
> +       ret = rmi_read(hdev, data->f11.query_base_addr + 1, buf);
> +       if (ret) {
> +               hid_err(hdev, "can not get NumberOfFingers: %d.\n", ret);
> +               return ret;
> +       }
> +       data->max_fingers = (buf[0] & 0x07) + 1;
> +       if (data->max_fingers > 5)
> +               data->max_fingers = 10;
> +
> +       data->f11.report_size = data->max_fingers * 5 +
> +                               DIV_ROUND_UP(data->max_fingers, 4);
> +
> +       if (!(buf[0] & BIT(4))) {
> +               hid_err(hdev, "No absolute events, giving up.\n");
> +               return -ENODEV;
> +       }
> +
> +       /*
> +        * query 12 to know if the physical properties are reported
> +        * (query 12 is at offset 10 for HID devices)
> +        */
> +       if (has_query12) {
> +               ret = rmi_read(hdev, data->f11.query_base_addr + 10, buf);
> +               if (ret) {
> +                       hid_err(hdev, "can not get query 12: %d.\n", ret);
> +                       return ret;
> +               }
> +               has_physical_props = !!(buf[0] & BIT(5));
> +
> +               if (has_physical_props) {
> +                       ret = rmi_read_block(hdev,
> +                                       data->f11.query_base_addr + 11, buf, 4);
> +                       if (ret) {
> +                               hid_err(hdev, "can not read query 15-18: %d.\n",
> +                                       ret);
> +                               return ret;
> +                       }
> +
> +                       x_size = buf[0] | (buf[1] << 8);
> +                       y_size = buf[2] | (buf[3] << 8);
> +
> +                       data->x_size_mm = DIV_ROUND_CLOSEST(x_size, 10);
> +                       data->y_size_mm = DIV_ROUND_CLOSEST(y_size, 10);
> +
> +                       hid_info(hdev, "%s: size in mm: %d x %d\n",
> +                                __func__, data->x_size_mm, data->y_size_mm);
> +               }
> +       }
> +
> +       /* retrieve the ctrl registers */
> +       ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 20);
> +       if (ret) {
> +               hid_err(hdev, "can not read ctrl block of size 20: %d.\n", ret);
> +               return ret;
> +       }
> +
> +       data->max_x = buf[6] | (buf[7] << 8);
> +       data->max_y = buf[8] | (buf[9] << 8);
> +
> +       return 0;
> +}
> +
> +static int rmi_populate_f30(struct hid_device *hdev)
> +{
> +       struct rmi_data *data = hid_get_drvdata(hdev);
> +       u8 buf[20];
> +       int ret;
> +       bool has_gpio, has_led;
> +       unsigned bytes_per_ctrl;
> +       u8 ctrl2_addr;
> +       int ctrl2_3_length;
> +       int i;
> +
> +       /* function F30 is for physical buttons */
> +       if (!data->f30.query_base_addr) {
> +               hid_err(hdev, "No GPIO/LEDs found, giving up.\n");
> +               return -ENODEV;
> +       }
> +
> +       ret = rmi_read_block(hdev, data->f30.query_base_addr, buf, 2);
> +       if (ret) {
> +               hid_err(hdev, "can not get F30 query registers: %d.\n", ret);
> +               return ret;
> +       }
> +
> +       has_gpio = !!(buf[0] & BIT(3));
> +       has_led = !!(buf[0] & BIT(2));
> +       data->gpio_led_count = buf[1] & 0x1f;
> +
> +       /* retrieve ctrl 2 & 3 registers */
> +       bytes_per_ctrl = (data->gpio_led_count + 7) / 8;
> +       /* Ctrl0 is present only if both has_gpio and has_led are set*/
> +       ctrl2_addr = (has_gpio && has_led) ? bytes_per_ctrl : 0;
> +       /* Ctrl1 is always be present */
> +       ctrl2_addr += bytes_per_ctrl;
> +       ctrl2_3_length = 2 * bytes_per_ctrl;
> +
> +       data->f30.report_size = bytes_per_ctrl;
> +
> +       ret = rmi_read_block(hdev, data->f30.control_base_addr + ctrl2_addr,
> +                               buf, ctrl2_3_length);
> +       if (ret) {
> +               hid_err(hdev, "can not read ctrl 2&3 block of size %d: %d.\n",
> +                       ctrl2_3_length, ret);
> +               return ret;
> +       }
> +
> +       for (i = 0; i < data->gpio_led_count; i++) {
> +               int byte_position = i >> 3;
> +               int bit_position = i & 0x07;
> +               u8 dir_byte = buf[byte_position];
> +               u8 data_byte = buf[byte_position + bytes_per_ctrl];
> +               bool dir = (dir_byte >> bit_position) & BIT(0);
> +               bool dat = (data_byte >> bit_position) & BIT(0);
> +
> +               if (dir == 0) {
> +                       /* input mode */
> +                       if (dat) {
> +                               /* actual buttons have pull up resistor */
> +                               data->button_count++;
> +                               set_bit(i, &data->button_mask);
> +                               set_bit(i, &data->button_state_mask);
> +                       }
> +               }
> +
> +       }
> +
> +       return 0;
> +}
> +
> +static int rmi_populate(struct hid_device *hdev)
> +{
> +       int ret;
> +
> +       ret = rmi_scan_pdt(hdev);
> +       if (ret) {
> +               hid_err(hdev, "PDT scan failed with code %d.\n", ret);
> +               return ret;
> +       }
> +
> +       ret = rmi_populate_f11(hdev);
> +       if (ret) {
> +               hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
> +               return ret;
> +       }
> +
> +       ret = rmi_populate_f30(hdev);
> +       if (ret)
> +               hid_warn(hdev, "Error while initializing F30 (%d).\n", ret);
> +
> +       return 0;
> +}
> +
> +static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
> +{
> +       struct rmi_data *data = hid_get_drvdata(hdev);
> +       struct input_dev *input = hi->input;
> +       int ret;
> +       int res_x, res_y, i;
> +
> +       data->input = input;
> +
> +       hid_dbg(hdev, "Opening low level driver\n");
> +       ret = hid_hw_open(hdev);
> +       if (ret)
> +               return;
> +
> +       /* Allow incoming hid reports */
> +       hid_device_io_start(hdev);
> +
> +       ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
> +       if (ret < 0) {
> +               dev_err(&hdev->dev, "failed to set rmi mode\n");
> +               goto exit;
> +       }
> +
> +       ret = rmi_set_page(hdev, 0);
> +       if (ret < 0) {
> +               dev_err(&hdev->dev, "failed to set page select to 0.\n");
> +               goto exit;
> +       }
> +
> +       ret = rmi_populate(hdev);
> +       if (ret)
> +               goto exit;
> +
> +       __set_bit(EV_ABS, input->evbit);
> +       input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
> +       input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
> +
> +       if (data->x_size_mm && data->x_size_mm) {
> +               res_x = (data->max_x - 1) / data->x_size_mm;
> +               res_y = (data->max_y - 1) / data->x_size_mm;
> +
> +               input_abs_set_res(input, ABS_MT_POSITION_X, res_x);
> +               input_abs_set_res(input, ABS_MT_POSITION_Y, res_y);
> +       }
> +
> +       input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0);
> +       input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xff, 0, 0);
> +       input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0x0f, 0, 0);
> +       input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 0x0f, 0, 0);
> +
> +       input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
> +
> +       if (data->button_count) {
> +               __set_bit(EV_KEY, input->evbit);
> +               for (i = 0; i < data->button_count; i++)
> +                       __set_bit(BTN_LEFT + i, input->keybit);
> +
> +               if (data->button_count == 1)
> +                       __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> +       }
> +
> +       set_bit(RMI_STARTED, &data->flags);
> +
> +exit:
> +       hid_device_io_stop(hdev);
> +       hid_hw_close(hdev);
> +}
> +
> +static int rmi_input_mapping(struct hid_device *hdev,
> +               struct hid_input *hi, struct hid_field *field,
> +               struct hid_usage *usage, unsigned long **bit, int *max)
> +{
> +       /* we want to make HID ignore the advertised HID collection */
> +       return -1;
> +}
> +
> +static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> +       struct rmi_data *data = NULL;
> +       int ret;
> +       size_t alloc_size;
> +
> +       data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
> +       if (!data)
> +               return -ENOMEM;
> +
> +       INIT_WORK(&data->reset_work, rmi_reset_work);
> +       data->hdev = hdev;
> +
> +       hid_set_drvdata(hdev, data);
> +
> +       hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
> +
> +       ret = hid_parse(hdev);
> +       if (ret) {
> +               hid_err(hdev, "parse failed\n");
> +               return ret;
> +       }
> +
> +       data->input_report_size = (hdev->report_enum[HID_INPUT_REPORT]
> +               .report_id_hash[RMI_ATTN_REPORT_ID]->size >> 3)
> +               + 1 /* report id */;
> +       data->output_report_size = (hdev->report_enum[HID_OUTPUT_REPORT]
> +               .report_id_hash[RMI_WRITE_REPORT_ID]->size >> 3)
> +               + 1 /* report id */;
> +
> +       alloc_size = data->output_report_size + data->input_report_size;
> +
> +       data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL);
> +       if (!data->writeReport) {
> +               ret = -ENOMEM;
> +               return ret;
> +       }
> +
> +       data->readReport = data->writeReport + data->output_report_size;
> +
> +       init_waitqueue_head(&data->wait);
> +
> +       mutex_init(&data->page_mutex);
> +
> +       ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> +       if (ret) {
> +               hid_err(hdev, "hw start failed\n");
> +               return ret;
> +       }
> +
> +       if (!test_bit(RMI_STARTED, &data->flags)) {
> +               hid_hw_stop(hdev);
> +               return -EIO;
> +       }
> +
> +       hid_hw_stop(hdev);
> +       return 0;
> +}
> +
> +static void rmi_remove(struct hid_device *hdev)
> +{
> +       struct rmi_data *hdata = hid_get_drvdata(hdev);
> +
> +       clear_bit(RMI_STARTED, &hdata->flags);
> +
> +       hid_hw_stop(hdev);
> +}
> +
> +static const struct hid_device_id rmi_id[] = {
> +       { HID_I2C_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> +       { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, HID_ANY_ID) },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(hid, rmi_id);
> +
> +static struct hid_driver rmi_driver = {
> +       .name = "hid-rmi",
> +       .id_table               = rmi_id,
> +       .probe                  = rmi_probe,
> +       .remove                 = rmi_remove,
> +       .raw_event              = rmi_raw_event,
> +       .input_mapping          = rmi_input_mapping,
> +       .input_configured       = rmi_input_configured,
> +#ifdef CONFIG_PM
> +       .resume                 = rmi_post_resume,
> +       .reset_resume           = rmi_post_reset,
> +#endif
> +};
> +
> +module_hid_driver(rmi_driver);
> +
> +MODULE_AUTHOR("Andrew Duggan <aduggan@synaptics.com>");
> +MODULE_DESCRIPTION("RMI HID driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index 720e3a1..54f855b 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -570,6 +570,8 @@ struct hid_descriptor {
>         .bus = BUS_USB, .vendor = (ven), .product = (prod)
>  #define HID_BLUETOOTH_DEVICE(ven, prod)                                        \
>         .bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
> +#define HID_I2C_DEVICE(ven, prod)                              \
> +       .bus = BUS_I2C, .vendor = (ven), .product = (prod)
>
>  #define HID_REPORT_ID(rep) \
>         .report_type = (rep)
> --
> 1.9.0
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox