linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless
@ 2014-04-22 13:45 Michal Malý
  2014-04-22 13:45 ` [PATCH v2 01/24] input: Add ff-memless-next module Michal Malý
                   ` (12 more replies)
  0 siblings, 13 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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

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

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

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>

v2:
 - Add missing msecs to jiffies conversion in ff-memless-next
 - lgff: Properly convert force on Y axis from MLNX to device range
         Support periodic effects for "joystick_ac" device class
 - lg3ff: Properly convert forces from MLNX to device range
 - Very minor coding style issues fixed

Michal Malý (24):
  Add ff-memless-next module
  Port arizona-haptics to ff-memless-next
  Port twl4030-vibra to ff-memless-next
  Port twl6040-vibra to ff-memless-next
  Port max8997_haptic to ff-memless-next
  Port pm8xxx-vibrator to ff-memless-next
  Port hid-axff to ff-memless-next
  Port hid-emsff to ff-memless-next
  Port hid-dr to ff-memless-next
  Port hid-gaff to ff-memless-next
  Port hid-holtekff to ff-memless-next
  Port hid-lgff to ff-memless-next
  Port hid-lg3ff to ff-memless-next
  Port hid-pl to ff-memless-next
  Port hid-sjoy to ff-memless-next
  Port hid-sony to ff-memless-next
  Port hid-tmff to ff-memless-next
  Port hid-wiimote-modules to ff-memless-next
  Port hid-zpff to ff-memless-next
  Port gamecon to ff-memless-next
  Port xpad to ff-memless-next
  Port hid-lg4ff to ff-memless-next
  Replace ff-memless with ff-memless-next

 drivers/hid/Kconfig                   |   30 +-
 drivers/hid/hid-axff.c                |   32 +-
 drivers/hid/hid-dr.c                  |   59 +-
 drivers/hid/hid-emsff.c               |   38 +-
 drivers/hid/hid-gaff.c                |   32 +-
 drivers/hid/hid-holtekff.c            |   47 +-
 drivers/hid/hid-lg2ff.c               |   65 ++-
 drivers/hid/hid-lg3ff.c               |   60 +-
 drivers/hid/hid-lg4ff.c               |   93 +--
 drivers/hid/hid-lgff.c                |   70 ++-
 drivers/hid/hid-pl.c                  |   38 +-
 drivers/hid/hid-sjoy.c                |   35 +-
 drivers/hid/hid-sony.c                |   24 +-
 drivers/hid/hid-tmff.c                |   83 +--
 drivers/hid/hid-wiimote-modules.c     |   74 ++-
 drivers/hid/hid-zpff.c                |   30 +-
 drivers/input/Kconfig                 |    4 +-
 drivers/input/Makefile                |    2 +-
 drivers/input/ff-memless-next.c       | 1037 +++++++++++++++++++++++++++++++++
 drivers/input/ff-memless.c            |  547 -----------------
 drivers/input/joystick/Kconfig        |    4 +-
 drivers/input/joystick/gamecon.c      |   57 +-
 drivers/input/joystick/xpad.c         |  125 ++--
 drivers/input/misc/Kconfig            |   10 +-
 drivers/input/misc/arizona-haptics.c  |   39 +-
 drivers/input/misc/max8997_haptic.c   |   25 +-
 drivers/input/misc/pm8xxx-vibrator.c  |   28 +-
 drivers/input/misc/twl4030-vibra.c    |   31 +-
 drivers/input/misc/twl6040-vibra.c    |   27 +-
 include/linux/input.h                 |    3 -
 include/linux/input/ff-memless-next.h |  149 +++++
 31 files changed, 1939 insertions(+), 959 deletions(-)
 create mode 100644 drivers/input/ff-memless-next.c
 delete mode 100644 drivers/input/ff-memless.c
 create mode 100644 include/linux/input/ff-memless-next.h

-- 
1.9.2


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 01/24] input: Add ff-memless-next module
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 02/24] Port arizona-haptics to ff-memless-next Michal Malý
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
	Michal Malý

From: Michal Malý <madcatxster@prifuk.cz>

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

diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index a11ff74..3780962 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -38,6 +38,17 @@ config INPUT_FF_MEMLESS
 	  To compile this driver as a module, choose M here: the
 	  module will be called ff-memless.
 
+config INPUT_FF_MEMLESS_NEXT
+	tristate "New version of support for memoryless force-feedback devices"
+	help
+	  Say Y here to enable a new version of support for memoryless force
+	  feedback devices.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ff-memless-next.
+
 config INPUT_POLLDEV
 	tristate "Polled input device skeleton"
 	help
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 5ca3f63..b4f11f5 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_INPUT)		+= input-core.o
 input-core-y := input.o input-compat.o input-mt.o ff-core.o
 
 obj-$(CONFIG_INPUT_FF_MEMLESS)	+= ff-memless.o
+obj-$(CONFIG_INPUT_FF_MEMLESS_NEXT)	+= ff-memless-next.o
 obj-$(CONFIG_INPUT_POLLDEV)	+= input-polldev.o
 obj-$(CONFIG_INPUT_SPARSEKMAP)	+= sparse-keymap.o
 obj-$(CONFIG_INPUT_MATRIXKMAP)	+= matrix-keymap.o
diff --git a/drivers/input/ff-memless-next.c b/drivers/input/ff-memless-next.c
new file mode 100644
index 0000000..24619e9
--- /dev/null
+++ b/drivers/input/ff-memless-next.c
@@ -0,0 +1,1037 @@
+/*
+ * Force feedback support for memoryless devices
+ *
+ * This module is based on "ff-memless" orignally written by Anssi Hannula.
+ * It is extended to support all force feedback effects currently supported
+ * by the Linux input stack.
+ * Logic of emulation of FF_RUMBLE through FF_PERIODIC provided by
+ * Elias Vanderstuyft <elias.vds@gmail.com>
+ *
+ * Copyright(c) 2014 Michal "MadCatX" Maly <madcatxster@devoid-pointer.net>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/jiffies.h>
+#include <linux/fixp-arith.h>
+#include <linux/input/ff-memless-next.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal \"MadCatX\" Maly");
+MODULE_DESCRIPTION("Force feedback support for memoryless force feedback devices");
+
+#define FF_MAX_EFFECTS 16
+#define FF_MIN_EFFECT_LENGTH ((MSEC_PER_SEC / CONFIG_HZ) + 1)
+#define FF_EFFECT_STARTED 1
+#define FF_EFFECT_PLAYING 2
+#define FF_EFFECT_EMULATED 3
+
+enum mlnx_emulate {
+	EMUL_NOTHING,	/* Do not emulate anything */
+	EMUL_RUMBLE,	/* Emulate FF_RUMBLE with FF_PERIODIC */
+	EMUL_PERIODIC	/* Emulate FF_PERIODIC with FF_RUMBLE */
+};
+
+struct mlnx_effect {
+	struct ff_effect effect;
+	unsigned long flags;
+	unsigned long begin_at;
+	unsigned long stop_at;
+	unsigned long updated_at;
+	unsigned long attack_stop;
+	unsigned long fade_begin;
+	int repeat;
+};
+
+struct mlnx_device {
+	u8 combinable_playing;
+	u8 rumble_playing;
+	unsigned long update_rate_jiffies;
+	void *private;
+	struct mlnx_effect effects[FF_MAX_EFFECTS];
+	u16 gain;
+	struct timer_list timer;
+	struct input_dev *dev;
+	enum mlnx_emulate emul;
+
+	int (*control_effect)(struct input_dev *, void *,
+			      const struct mlnx_effect_command *);
+};
+
+static s32 mlnx_calculate_x_force(const s32 level,
+						  const u16 direction)
+{
+	s32 new = (level * -fixp_sin(direction)) >> FRAC_N;
+	pr_debug("x force: %d\n", new);
+	return new;
+}
+
+static s32 mlnx_calculate_y_force(const s32 level,
+						  const u16 direction)
+{
+	s32 new = (level * fixp_cos(direction)) >> FRAC_N;
+	pr_debug("y force: %d\n", new);
+	return new;
+}
+
+static bool mlnx_is_combinable(const struct ff_effect *effect)
+{
+	switch (effect->type) {
+	case FF_CONSTANT:
+	case FF_PERIODIC:
+	case FF_RAMP:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool mlnx_is_conditional(const struct ff_effect *effect)
+{
+	switch (effect->type) {
+	case FF_DAMPER:
+	case FF_FRICTION:
+	case FF_INERTIA:
+	case FF_SPRING:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void mlnx_clr_emulated(struct mlnx_effect *mlnxeff)
+{
+	__clear_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static void mlnx_clr_playing(struct mlnx_effect *mlnxeff)
+{
+	__clear_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static void mlnx_clr_started(struct mlnx_effect *mlnxeff)
+{
+	__clear_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_emulated(const struct mlnx_effect *mlnxeff)
+{
+	return test_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_rumble(const struct ff_effect *effect)
+{
+	return effect->type == FF_RUMBLE;
+}
+
+static bool mlnx_is_playing(const struct mlnx_effect *mlnxeff)
+{
+	return test_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static bool mlnx_is_started(const struct mlnx_effect *mlnxeff)
+{
+	return test_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_test_set_playing(struct mlnx_effect *mlnxeff)
+{
+	return test_and_set_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static const struct ff_envelope *mlnx_get_envelope(const struct ff_effect *effect)
+{
+	static const struct ff_envelope empty;
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		return &effect->u.constant.envelope;
+	case FF_PERIODIC:
+		return &effect->u.periodic.envelope;
+	case FF_RAMP:
+		return &effect->u.ramp.envelope;
+	default:
+		return &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;
+	const u16 min_update_rate = update_rate < FF_MIN_EFFECT_LENGTH ?
+				    FF_MIN_EFFECT_LENGTH : update_rate;
+
+	mlnxdev = kzalloc(sizeof(*mlnxdev), GFP_KERNEL);
+	if (!mlnxdev)
+		return -ENOMEM;
+
+	mlnxdev->dev = dev;
+	mlnxdev->private = data;
+	mlnxdev->control_effect = control_effect;
+	mlnxdev->gain = 0xffff;
+	mlnxdev->update_rate_jiffies = msecs_to_jiffies(min_update_rate);
+	input_set_capability(dev, EV_FF, FF_GAIN);
+	setup_timer(&mlnxdev->timer, mlnx_timer_fired, (unsigned long)dev);
+
+	/* Set up effect emulation if needed */
+	if (test_bit(FF_PERIODIC, dev->ffbit) &&
+	    !test_bit(FF_RUMBLE, dev->ffbit)) {
+		set_bit(FF_RUMBLE, dev->ffbit);
+		mlnxdev->emul = EMUL_RUMBLE;
+		pr_debug("Emulating RUMBLE with PERIODIC\n");
+	} else if (test_bit(FF_RUMBLE, dev->ffbit) &&
+		   !test_bit(FF_PERIODIC, dev->ffbit)) {
+		set_bit(FF_PERIODIC, dev->ffbit);
+		set_bit(FF_SINE, dev->ffbit);
+		set_bit(FF_SQUARE, dev->ffbit);
+		set_bit(FF_TRIANGLE, dev->ffbit);
+		set_bit(FF_SAW_DOWN, dev->ffbit);
+		set_bit(FF_SAW_UP, dev->ffbit);
+		mlnxdev->emul = EMUL_PERIODIC;
+		pr_debug("Emulating PERIODIC with RUMBLE\n");
+	} else {
+		mlnxdev->emul = EMUL_NOTHING;
+		pr_debug("No effect emulation is necessary\n");
+	}
+
+	ret = input_ff_create(dev, FF_MAX_EFFECTS);
+	if (ret) {
+		kfree(mlnxdev);
+		return ret;
+	}
+
+
+	dev->ff->private = mlnxdev;
+	dev->ff->upload = mlnx_upload;
+	dev->ff->set_gain = mlnx_set_gain;
+	dev->ff->destroy = mlnx_destroy;
+	dev->ff->playback = mlnx_startstop;
+
+	pr_debug("Device successfully registered.\n");
+	return 0;
+}
+EXPORT_SYMBOL_GPL(input_ff_create_mlnx);
diff --git a/include/linux/input/ff-memless-next.h b/include/linux/input/ff-memless-next.h
new file mode 100644
index 0000000..2f15897
--- /dev/null
+++ b/include/linux/input/ff-memless-next.h
@@ -0,0 +1,149 @@
+#include <linux/input.h>
+
+/** DEFINITION OF TERMS
+ *
+ * Combined effect - An effect whose force is a superposition of forces
+ *                   generated by all effects that can be added together.
+ *                   Only one combined effect can be playing at a time.
+ *                   Effects that can be added together to create a combined
+ *                   effect are FF_CONSTANT, FF_PERIODIC and FF_RAMP.
+ * Uncombinable effect - An effect that cannot be combined with another effect.
+ *                       All conditional effects - FF_DAMPER, FF_FRICTION,
+ *                       FF_INERTIA and FF_SPRING are uncombinable.
+ *                       Number of uncombinable effects playing simultaneously
+ *                       depends on the capabilities of the hardware.
+ * Rumble effect - An effect generated by device's rumble motors instead of
+ *                 force feedback actuators.
+ *
+ *
+ * HANDLING OF UNCOMBINABLE EFFECTS
+ *
+ * Uncombinable effects cannot be combined together into just one effect, at
+ * least not in a clear and obvious manner. Therefore these effects have to
+ * be handled individually by ff-memless-next. Handling of these effects is
+ * left entirely to the hardware-specific driver, ff-memless-next merely
+ * passes these effects to the hardware-specific driver at appropriate time.
+ * ff-memless-next provides the UPLOAD command to notify the hardware-specific
+ * driver that the userspace is about to request playback of an uncombinable
+ * effect. The hardware-specific driver shall take all steps needed to make
+ * the device ready to play the effect when it receives the UPLOAD command.
+ * The actual playback shall commence when START_UNCOMB command is received.
+ * Opposite to the UPLOAD command is the ERASE command which tells
+ * the hardware-specific driver that the playback has finished and that
+ * the effect will not be restarted. STOP_UNCOMB command tells
+ * the hardware-specific driver that the playback shall stop but the device
+ * shall still be ready to resume the playback immediately.
+ *
+ * In case it is not possible to make the device ready to play an uncombinable
+ * effect (all hardware effect slots are occupied), the hardware-specific
+ * driver may return an error when it receives an UPLOAD command. If the
+ * hardware-specific driver returns 0, the upload is considered successful.
+ * START_UNCOMB and STOP_UNCOMB commands cannot fail and the device must always
+ * start the playback of the requested effect if the UPLOAD command of the
+ * respective effect has been successful. ff-memless-next will never send
+ * a START/STOP_UNCOMB command for an effect that has not been uploaded
+ * successfully, nor will it send an ERASE command for an effect that is
+ * playing (= has been started with START_UNCOMB command).
+ */
+
+enum mlnx_commands {
+	/* Start or update a combined effect. This command is sent whenever
+	 * a FF_CONSTANT, FF_PERIODIC or FF_RAMP is started, stopped or
+	 * updated by userspace, when the applied envelopes are recalculated
+	 * or when periodic effects are recalculated. */
+	MLNX_START_COMBINED,
+	/* Stop combined effect. This command is sent when all combinable
+	 * effects are stopped. */
+	MLNX_STOP_COMBINED,
+	/* Start or update a rumble effect. This command is sent whenever
+	 * a FF_RUMBLE effect is started or when its magnitudes or directions
+	 * change. */
+	MLNX_START_RUMBLE,
+	/* Stop a rumble effect. This command is sent when all FF_RUMBLE
+	 * effects are stopped. */
+	MLNX_STOP_RUMBLE,
+	/* Start or update an uncombinable effect. This command is sent
+	 * whenever an uncombinable effect is started or updated. */
+	MLNX_START_UNCOMB,
+	/* Stop uncombinable effect. This command is sent when an uncombinable
+	 * effect is stopped. */
+	MLNX_STOP_UNCOMB,
+	/* Upload uncombinable effect to device. This command is sent when the
+	 * effect is started from userspace. It is up to the hardware-specific
+	 * driver to handle this situation.
+	 */
+	MLNX_UPLOAD_UNCOMB,
+	/* Remove uncombinable effect from device, This command is sent when
+	 * and uncombinable effect has finished playing and will not be
+	 * restarted.
+	 */
+	MLNX_ERASE_UNCOMB
+};
+
+/** struct mlnx_simple_force - holds constant forces along X and Y axis
+ * @x: Force along X axis. Negative value denotes force pulling to the left,
+ *     positive value denotes force pulling to the right.
+ * @y: Force along Y axis. Negative value denotes force denotes force pulling
+ *     away from the user, positive value denotes force pulling towards
+ *     the user.
+ */
+struct mlnx_simple_force {
+	const s32 x;
+	const s32 y;
+};
+
+/** struct mlnx_rumble_force - holds information about rumble effect
+ * @strong: Magnitude of the strong vibration.
+ * @weak: Magnitude of the weak vibration.
+ * @strong_dir: Direction of the strong vibration expressed in the same way
+ *              as the direction of force feedback effect in struct ff_effect.
+ * @weak_dir: Direction of the weak vibration, same as above applies.
+ */
+struct mlnx_rumble_force {
+	const u32 strong;
+	const u32 weak;
+	const u16 strong_dir;
+	const u16 weak_dir;
+};
+
+/** struct mlnx_uncomb_effect - holds information about uncombinable effect
+ * @id: Id of the effect assigned by ff-core.
+ * @effect: Pointer to the uncombinable effect stored in ff-memless-next module
+ *          Hardware-specific driver must not alter this.
+ */
+struct mlnx_uncomb_effect {
+	const int id;
+	const struct ff_effect *effect;
+};
+
+/** struct mlnx_commands - describes what action shall the force feedback
+ *                         device perform
+ * @cmd: Type of the action.
+ * @u: Data associated with the action.
+ */
+struct mlnx_effect_command {
+	const enum mlnx_commands cmd;
+	union {
+		const struct mlnx_simple_force simple_force;
+		const struct mlnx_rumble_force rumble_force;
+		const struct mlnx_uncomb_effect uncomb;
+	} u;
+};
+
+/** input_ff_create_mlnx() - Register a device within ff-memless-next and
+ *                           the kernel force feedback system
+ * @dev: Pointer to the struct input_dev associated with the device.
+ * @data: Any device-specific data that shall be passed to the callback.
+ *        function called by ff-memless-next when a force feedback action
+ *        shall be performed.
+ * @control_effect: Pointer to the callback function.
+ * @update_date: Delay in milliseconds between two recalculations of periodic
+ *               effects, ramp effects and envelopes. Note that this value will
+ *               never be lower than (CONFIG_HZ / 1000) + 1 regardless of the
+ *               value specified here. This is not a "hard" rate limiter.
+ *               Userspace still can submit effects at a rate faster than
+ *               this value.
+ */
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+			 int (*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+			 const u16 update_rate);
-- 
1.9.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 02/24] Port arizona-haptics to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
  2014-04-22 13:45 ` [PATCH v2 01/24] input: Add ff-memless-next module Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 03/24] Port twl4030-vibra " Michal Malý
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 03/24] Port twl4030-vibra to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
  2014-04-22 13:45 ` [PATCH v2 01/24] input: Add ff-memless-next module Michal Malý
  2014-04-22 13:45 ` [PATCH 02/24] Port arizona-haptics to ff-memless-next Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 04/24] Port twl6040-vibra " Michal Malý
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 04/24] Port twl6040-vibra to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (2 preceding siblings ...)
  2014-04-22 13:45 ` [PATCH 03/24] Port twl4030-vibra " Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 05/24] Port max8997_haptic " Michal Malý
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 05/24] Port max8997_haptic to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (3 preceding siblings ...)
  2014-04-22 13:45 ` [PATCH 04/24] Port twl6040-vibra " Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 06/24] Port pm8xxx-vibrator " Michal Malý
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 06/24] Port pm8xxx-vibrator to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (4 preceding siblings ...)
  2014-04-22 13:45 ` [PATCH 05/24] Port max8997_haptic " Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 07/24] Port hid-axff " Michal Malý
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 07/24] Port hid-axff to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (5 preceding siblings ...)
  2014-04-22 13:45 ` [PATCH 06/24] Port pm8xxx-vibrator " Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 08/24] Port hid-emsff " Michal Malý
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 08/24] Port hid-emsff to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (6 preceding siblings ...)
  2014-04-22 13:45 ` [PATCH 07/24] Port hid-axff " Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:45 ` [PATCH 09/24] Port hid-dr " Michal Malý
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 09/24] Port hid-dr to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (7 preceding siblings ...)
  2014-04-22 13:45 ` [PATCH 08/24] Port hid-emsff " Michal Malý
@ 2014-04-22 13:45 ` Michal Malý
  2014-04-22 13:46 ` [PATCH 10/24] Port hid-gaff " Michal Malý
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:45 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 10/24] Port hid-gaff to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (8 preceding siblings ...)
  2014-04-22 13:45 ` [PATCH 09/24] Port hid-dr " Michal Malý
@ 2014-04-22 13:46 ` Michal Malý
  2014-04-22 13:46 ` [PATCH 11/24] Port hid-holtekff " Michal Malý
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:46 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 0ba1962..24b336e 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -656,7 +656,7 @@ config HID_GREENASIA
 config GREENASIA_FF
 	bool "GreenAsia (Product ID 0x12) force feedback support"
 	depends on HID_GREENASIA
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	---help---
 	Say Y here if you have a GreenAsia (Product ID 0x12) based game controller
 	(like MANTA Warrior MM816 and SpeedLink Strike2 SL-6635) or adapter
diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c
index 2d8cead..f2f70c1 100644
--- a/drivers/hid/hid-gaff.c
+++ b/drivers/hid/hid-gaff.c
@@ -31,8 +31,11 @@
 #include <linux/slab.h>
 #include <linux/hid.h>
 #include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
 #include "hid-ids.h"
 
+#define FF_UPDATE_RATE 50
+
 #ifdef CONFIG_GREENASIA_FF
 
 struct gaff_device {
@@ -40,19 +43,30 @@ struct gaff_device {
 };
 
 static int hid_gaff_play(struct input_dev *dev, void *data,
-			 struct ff_effect *effect)
+			 const struct mlnx_effect_command *command)
 {
 	struct hid_device *hid = input_get_drvdata(dev);
 	struct gaff_device *gaff = data;
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 	int left, right;
 
-	left = effect->u.rumble.strong_magnitude;
-	right = effect->u.rumble.weak_magnitude;
-
-	dbg_hid("called with 0x%04x 0x%04x", left, right);
-
-	left = left * 0xfe / 0xffff;
-	right = right * 0xfe / 0xffff;
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		left = rumble_force->strong;
+		right = rumble_force->weak;
+
+		dbg_hid("called with 0x%04x 0x%04x", left, right);
+
+		left = left * 0xfe / 0xffff;
+		right = right * 0xfe / 0xffff;
+		break;
+	case MLNX_STOP_RUMBLE:
+		left = 0;
+		right = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
 
 	gaff->report->field[0]->value[0] = 0x51;
 	gaff->report->field[0]->value[1] = 0x0;
@@ -109,7 +123,7 @@ static int gaff_init(struct hid_device *hid)
 
 	set_bit(FF_RUMBLE, dev->ffbit);
 
-	error = input_ff_create_memless(dev, gaff, hid_gaff_play);
+	error = input_ff_create_mlnx(dev, gaff, hid_gaff_play, FF_UPDATE_RATE);
 	if (error) {
 		kfree(gaff);
 		return error;
-- 
1.9.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 11/24] Port hid-holtekff to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (9 preceding siblings ...)
  2014-04-22 13:46 ` [PATCH 10/24] Port hid-gaff " Michal Malý
@ 2014-04-22 13:46 ` Michal Malý
  2014-04-22 13:46 ` [PATCH 12/24] Port hid-lgff " Michal Malý
  2014-04-22 13:46 ` [PATCH 13/24] Port hid-lg3ff " Michal Malý
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:46 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/hid/Kconfig        |  2 +-
 drivers/hid/hid-holtekff.c | 47 +++++++++++++++++++++++++++++-----------------
 2 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 24b336e..d1d4e77 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -247,7 +247,7 @@ config HID_HOLTEK
 config HOLTEK_FF
 	bool "Holtek On Line Grip force feedback support"
 	depends on HID_HOLTEK
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	---help---
 	  Say Y here if you have a Holtek On Line Grip based game controller
 	  and want to have force feedback support for it.
diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c
index 9325545..9c6064d 100644
--- a/drivers/hid/hid-holtekff.c
+++ b/drivers/hid/hid-holtekff.c
@@ -27,9 +27,12 @@
 #include <linux/input.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/input/ff-memless-next.h>
 
 #include "hid-ids.h"
 
+#define FF_UPDATE_RATE 50
+
 #ifdef CONFIG_HOLTEK_FF
 
 MODULE_LICENSE("GPL");
@@ -104,34 +107,44 @@ static void holtekff_send(struct holtekff_device *holtekff,
 }
 
 static int holtekff_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 holtekff_device *holtekff = data;
+	const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
 	int left, right;
 	/* effect type 1, length 65535 msec */
 	u8 buf[HOLTEKFF_MSG_LENGTH] =
 		{ 0x01, 0x01, 0xff, 0xff, 0x10, 0xe0, 0x00 };
 
-	left = effect->u.rumble.strong_magnitude;
-	right = effect->u.rumble.weak_magnitude;
-	dbg_hid("called with 0x%04x 0x%04x\n", left, right);
+	switch (command->cmd) {
+	case MLNX_START_RUMBLE:
+		left = rumble_force->strong;
+		right = rumble_force->weak;
+		dbg_hid("called with 0x%04x 0x%04x\n", left, right);
 
-	if (!left && !right) {
-		holtekff_send(holtekff, hid, stop_all6);
-		return 0;
-	}
+		if (!left && !right) {
+			holtekff_send(holtekff, hid, stop_all6);
+			return 0;
+		}
 
-	if (left)
-		buf[1] |= 0x80;
-	if (right)
-		buf[1] |= 0x40;
+		if (left)
+			buf[1] |= 0x80;
+		if (right)
+			buf[1] |= 0x40;
 
-	/* The device takes a single magnitude, so we just sum them up. */
-	buf[6] = min(0xf, (left >> 12) + (right >> 12));
+		/* The device takes a single magnitude, so we just sum them up. */
+		buf[6] = min(0xf, (left >> 12) + (right >> 12));
 
-	holtekff_send(holtekff, hid, buf);
-	holtekff_send(holtekff, hid, start_effect_1);
+		holtekff_send(holtekff, hid, buf);
+		holtekff_send(holtekff, hid, start_effect_1);
+		return 0;
+	case MLNX_STOP_RUMBLE:
+		holtekff_send(holtekff, hid, stop_all6);
+		return 0;
+	default:
+		return -EINVAL;
+	}
 
 	return 0;
 }
@@ -171,7 +184,7 @@ static int holtekff_init(struct hid_device *hid)
 	holtekff_send(holtekff, hid, stop_all4);
 	holtekff_send(holtekff, hid, stop_all6);
 
-	error = input_ff_create_memless(dev, holtekff, holtekff_play);
+	error = input_ff_create_mlnx(dev, holtekff, holtekff_play, FF_UPDATE_RATE);
 	if (error) {
 		kfree(holtekff);
 		return error;
-- 
1.9.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 12/24] Port hid-lgff to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (10 preceding siblings ...)
  2014-04-22 13:46 ` [PATCH 11/24] Port hid-holtekff " Michal Malý
@ 2014-04-22 13:46 ` Michal Malý
  2014-04-22 13:46 ` [PATCH 13/24] Port hid-lg3ff " Michal Malý
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:46 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/hid/Kconfig    |  2 +-
 drivers/hid/hid-lgff.c | 70 +++++++++++++++++++++++++++++++++++---------------
 2 files changed, 51 insertions(+), 21 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index d1d4e77..c7794ae 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -356,7 +356,7 @@ config HID_LOGITECH_DJ
 config LOGITECH_FF
 	bool "Logitech force feedback support"
 	depends on HID_LOGITECH
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	help
 	  Say Y here if you have one of these devices:
 	  - Logitech WingMan Cordless RumblePad
diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c
index e1394af..96d418c 100644
--- a/drivers/hid/hid-lgff.c
+++ b/drivers/hid/hid-lgff.c
@@ -31,9 +31,12 @@
 
 #include <linux/input.h>
 #include <linux/hid.h>
+#include <linux/input/ff-memless-next.h>
 
 #include "hid-lg.h"
 
+#define FF_UPDATE_RATE 50
+
 struct dev_type {
 	u16 idVendor;
 	u16 idProduct;
@@ -47,11 +50,25 @@ static const signed short ff_rumble[] = {
 
 static const signed short ff_joystick[] = {
 	FF_CONSTANT,
+	FF_RAMP,
+	FF_PERIODIC,
+	FF_SQUARE,
+	FF_TRIANGLE,
+	FF_SINE,
+	FF_SAW_UP,
+	FF_SAW_DOWN,
 	-1
 };
 
 static const signed short ff_joystick_ac[] = {
 	FF_CONSTANT,
+	FF_RAMP,
+	FF_PERIODIC,
+	FF_SQUARE,
+	FF_TRIANGLE,
+	FF_SINE,
+	FF_SAW_UP,
+	FF_SAW_DOWN,
 	FF_AUTOCENTER,
 	-1
 };
@@ -66,45 +83,58 @@ static const struct dev_type devices[] = {
 	{ 0x046d, 0xc295, ff_joystick },
 };
 
-static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+static int hid_lgff_play(struct input_dev *dev, void *data,
+			 const struct mlnx_effect_command *command)
 {
 	struct hid_device *hid = input_get_drvdata(dev);
 	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
 	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
-	int x, y;
-	unsigned int left, right;
 
-#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
+	switch (command->cmd) {
+	case MLNX_START_COMBINED: {
+		const struct mlnx_simple_force *simple_force = &command->u.simple_force;
+		/* Scale down from MLNX range */
+		const int x = 0x80 - (simple_force->x * 0xff / 0xffff);
+		const int y = 0x80 - simple_force->y * 0xff / 0xffff;
 
-	switch (effect->type) {
-	case FF_CONSTANT:
-		x = effect->u.ramp.start_level + 0x7f;	/* 0x7f is center */
-		y = effect->u.ramp.end_level + 0x7f;
-		CLAMP(x);
-		CLAMP(y);
 		report->field[0]->value[0] = 0x51;
 		report->field[0]->value[1] = 0x08;
 		report->field[0]->value[2] = x;
 		report->field[0]->value[3] = y;
 		dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
-		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
 		break;
+	}
+	case MLNX_STOP_COMBINED:
+		report->field[0]->value[0] = 0x51;
+		report->field[0]->value[1] = 0x08;
+		report->field[0]->value[2] = 0x80;
+		report->field[0]->value[3] = 0x80;
+		break;
+	case MLNX_START_RUMBLE: {
+		const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+		/* Scale down from MLNX range */
+		const unsigned int right = rumble_force->weak * 0xff / 0xffff;
+		const unsigned int left = rumble_force->strong * 0xff / 0xffff;
 
-	case FF_RUMBLE:
-		right = effect->u.rumble.strong_magnitude;
-		left = effect->u.rumble.weak_magnitude;
-		right = right * 0xff / 0xffff;
-		left = left * 0xff / 0xffff;
-		CLAMP(left);
-		CLAMP(right);
 		report->field[0]->value[0] = 0x42;
 		report->field[0]->value[1] = 0x00;
 		report->field[0]->value[2] = left;
 		report->field[0]->value[3] = right;
 		dbg_hid("(left, right)=(%04x, %04x)\n", left, right);
-		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
 		break;
 	}
+	case MLNX_STOP_RUMBLE:
+		report->field[0]->value[0] = 0x42;
+		report->field[0]->value[1] = 0x00;
+		report->field[0]->value[2] = 0;
+		report->field[0]->value[3] = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
 	return 0;
 }
 
@@ -148,7 +178,7 @@ int lgff_init(struct hid_device* hid)
 	for (i = 0; ff_bits[i] >= 0; i++)
 		set_bit(ff_bits[i], dev->ffbit);
 
-	error = input_ff_create_memless(dev, NULL, hid_lgff_play);
+	error = input_ff_create_mlnx(dev, NULL, hid_lgff_play, FF_UPDATE_RATE);
 	if (error)
 		return error;
 
-- 
1.9.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 13/24] Port hid-lg3ff to ff-memless-next
  2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
                   ` (11 preceding siblings ...)
  2014-04-22 13:46 ` [PATCH 12/24] Port hid-lgff " Michal Malý
@ 2014-04-22 13:46 ` Michal Malý
  12 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:46 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
 drivers/hid/Kconfig     |  2 +-
 drivers/hid/hid-lg3ff.c | 60 +++++++++++++++++++++++++++++++------------------
 2 files changed, 39 insertions(+), 23 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index c7794ae..6f2941a 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -382,7 +382,7 @@ config LOGIRUMBLEPAD2_FF
 config LOGIG940_FF
 	bool "Logitech Flight System G940 force feedback support"
 	depends on HID_LOGITECH
-	select INPUT_FF_MEMLESS
+	select INPUT_FF_MEMLESS_NEXT
 	help
 	  Say Y here if you want to enable force feedback support for Logitech
 	  Flight System G940 devices.
diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c
index 8c2da18..c49b374 100644
--- a/drivers/hid/hid-lg3ff.c
+++ b/drivers/hid/hid-lg3ff.c
@@ -23,9 +23,12 @@
 
 #include <linux/input.h>
 #include <linux/hid.h>
+#include <linux/input/ff-memless-next.h>
 
 #include "hid-lg.h"
 
+#define FF_UPDATE_RATE 50
+
 /*
  * G940 Theory of Operation (from experimentation)
  *
@@ -58,12 +61,11 @@ struct lg3ff_device {
 };
 
 static int hid_lg3ff_play(struct input_dev *dev, void *data,
-			 struct ff_effect *effect)
+			 const struct mlnx_effect_command *command)
 {
 	struct hid_device *hid = input_get_drvdata(dev);
 	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
 	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
-	int x, y;
 
 /*
  * Available values in the field should always be 63, but we only use up to
@@ -72,30 +74,37 @@ static int hid_lg3ff_play(struct input_dev *dev, void *data,
 	memset(report->field[0]->value, 0,
 	       sizeof(__s32) * report->field[0]->report_count);
 
-	switch (effect->type) {
-	case FF_CONSTANT:
-/*
- * Already clamped in ff_memless
- * 0 is center (different then other logitech)
- */
-		x = effect->u.ramp.start_level;
-		y = effect->u.ramp.end_level;
-
-		/* send command byte */
-		report->field[0]->value[0] = 0x51;
-
-/*
- * Sign backwards from other Force3d pro
- * which get recast here in two's complement 8 bits
- */
-		report->field[0]->value[1] = (unsigned char)(-x);
-		report->field[0]->value[31] = (unsigned char)(-y);
+	/* send command byte */
+	report->field[0]->value[0] = 0x51;
 
-		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+	switch (command->cmd) {
+	case MLNX_START_COMBINED: {
+		const struct mlnx_simple_force *simple_force = &command->u.simple_force;
+		/* Scale down from MLNX range */
+		const int x = simple_force->x * 0xff / 0xffff;
+		const int y = simple_force->y * 0xff / 0xffff;
+
+		/*
+		 * Sign backwards from other Force3d pro
+		 * which get recast here in two's complement 8 bits
+		*/
+		report->field[0]->value[1] = (unsigned char)x;
+		report->field[0]->value[31] = (unsigned char)y;
+		break;
+		}
+	case MLNX_STOP_COMBINED:
+		report->field[0]->value[1] = 0;
+		report->field[0]->value[31] = 0;
 		break;
+	default:
+		return -EINVAL;
 	}
+
+	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
 	return 0;
 }
+
 static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude)
 {
 	struct hid_device *hid = input_get_drvdata(dev);
@@ -123,6 +132,13 @@ static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude)
 
 static const signed short ff3_joystick_ac[] = {
 	FF_CONSTANT,
+	FF_RAMP,
+	FF_PERIODIC,
+	FF_SQUARE,
+	FF_TRIANGLE,
+	FF_SINE,
+	FF_SAW_UP,
+	FF_SAW_DOWN,
 	FF_AUTOCENTER,
 	-1
 };
@@ -143,7 +159,7 @@ int lg3ff_init(struct hid_device *hid)
 	for (i = 0; ff_bits[i] >= 0; i++)
 		set_bit(ff_bits[i], dev->ffbit);
 
-	error = input_ff_create_memless(dev, NULL, hid_lg3ff_play);
+	error = input_ff_create_mlnx(dev, NULL, hid_lg3ff_play, FF_UPDATE_RATE);
 	if (error)
 		return error;
 
-- 
1.9.2


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless
@ 2014-04-22 13:59 Michal Malý
  0 siblings, 0 replies; 15+ messages in thread
From: Michal Malý @ 2014-04-22 13:59 UTC (permalink / raw)
  To: linux-input, linux-kernel
  Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon

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

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

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

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

Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>

v2:
 - Add missing msecs to jiffies conversion in ff-memless-next
 - lgff: Properly convert force on Y axis from MLNX to device range
         Support periodic effects for "joystick_ac" device class
 - lg3ff: Properly convert forces from MLNX to device range
 - Very minor coding style issues fixed

Michal Malý (24):
  Add ff-memless-next module
  Port arizona-haptics to ff-memless-next
  Port twl4030-vibra to ff-memless-next
  Port twl6040-vibra to ff-memless-next
  Port max8997_haptic to ff-memless-next
  Port pm8xxx-vibrator to ff-memless-next
  Port hid-axff to ff-memless-next
  Port hid-emsff to ff-memless-next
  Port hid-dr to ff-memless-next
  Port hid-gaff to ff-memless-next
  Port hid-holtekff to ff-memless-next
  Port hid-lgff to ff-memless-next
  Port hid-lg3ff to ff-memless-next
  Port hid-pl to ff-memless-next
  Port hid-sjoy to ff-memless-next
  Port hid-sony to ff-memless-next
  Port hid-tmff to ff-memless-next
  Port hid-wiimote-modules to ff-memless-next
  Port hid-zpff to ff-memless-next
  Port gamecon to ff-memless-next
  Port xpad to ff-memless-next
  - Port hid-lg2ff to ff-memless-next     - Clamp vibration magnitude to
    range <0x02; 0xfd> to prevent irregular       shaking of the
    vibration motors.
  Port hid-lg4ff to ff-memless-next
  Replace ff-memless with ff-memless-next

 drivers/hid/Kconfig                   |   30 +-
 drivers/hid/hid-axff.c                |   32 +-
 drivers/hid/hid-dr.c                  |   59 +-
 drivers/hid/hid-emsff.c               |   38 +-
 drivers/hid/hid-gaff.c                |   32 +-
 drivers/hid/hid-holtekff.c            |   47 +-
 drivers/hid/hid-lg2ff.c               |   65 ++-
 drivers/hid/hid-lg3ff.c               |   60 +-
 drivers/hid/hid-lg4ff.c               |   93 +--
 drivers/hid/hid-lgff.c                |   70 ++-
 drivers/hid/hid-pl.c                  |   38 +-
 drivers/hid/hid-sjoy.c                |   35 +-
 drivers/hid/hid-sony.c                |   24 +-
 drivers/hid/hid-tmff.c                |   83 +--
 drivers/hid/hid-wiimote-modules.c     |   74 ++-
 drivers/hid/hid-zpff.c                |   30 +-
 drivers/input/Kconfig                 |    4 +-
 drivers/input/Makefile                |    2 +-
 drivers/input/ff-memless-next.c       | 1037 +++++++++++++++++++++++++++++++++
 drivers/input/ff-memless.c            |  547 -----------------
 drivers/input/joystick/Kconfig        |    4 +-
 drivers/input/joystick/gamecon.c      |   57 +-
 drivers/input/joystick/xpad.c         |  125 ++--
 drivers/input/misc/Kconfig            |   10 +-
 drivers/input/misc/arizona-haptics.c  |   39 +-
 drivers/input/misc/max8997_haptic.c   |   25 +-
 drivers/input/misc/pm8xxx-vibrator.c  |   28 +-
 drivers/input/misc/twl4030-vibra.c    |   31 +-
 drivers/input/misc/twl6040-vibra.c    |   27 +-
 include/linux/input.h                 |    3 -
 include/linux/input/ff-memless-next.h |  149 +++++
 31 files changed, 1939 insertions(+), 959 deletions(-)
 create mode 100644 drivers/input/ff-memless-next.c
 delete mode 100644 drivers/input/ff-memless.c
 create mode 100644 include/linux/input/ff-memless-next.h

-- 
1.9.2


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2014-04-22 14:13 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-04-22 13:45 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
2014-04-22 13:45 ` [PATCH v2 01/24] input: Add ff-memless-next module Michal Malý
2014-04-22 13:45 ` [PATCH 02/24] Port arizona-haptics to ff-memless-next Michal Malý
2014-04-22 13:45 ` [PATCH 03/24] Port twl4030-vibra " Michal Malý
2014-04-22 13:45 ` [PATCH 04/24] Port twl6040-vibra " Michal Malý
2014-04-22 13:45 ` [PATCH 05/24] Port max8997_haptic " Michal Malý
2014-04-22 13:45 ` [PATCH 06/24] Port pm8xxx-vibrator " Michal Malý
2014-04-22 13:45 ` [PATCH 07/24] Port hid-axff " Michal Malý
2014-04-22 13:45 ` [PATCH 08/24] Port hid-emsff " Michal Malý
2014-04-22 13:45 ` [PATCH 09/24] Port hid-dr " Michal Malý
2014-04-22 13:46 ` [PATCH 10/24] Port hid-gaff " Michal Malý
2014-04-22 13:46 ` [PATCH 11/24] Port hid-holtekff " Michal Malý
2014-04-22 13:46 ` [PATCH 12/24] Port hid-lgff " Michal Malý
2014-04-22 13:46 ` [PATCH 13/24] Port hid-lg3ff " Michal Malý
2014-04-22 13:59 [PATCH v2 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).