* [PATCH v3 01/24] input: Add ff-memless-next module
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 13:07 ` Antonio Ospite
2014-04-26 11:57 ` [PATCH v3 02/24] input: Port arizona-haptics to ff-memless-next Michal Malý
` (22 subsequent siblings)
23 siblings, 1 reply; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Add ff-memless-next module
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
---
drivers/input/Kconfig | 11 +
drivers/input/Makefile | 1 +
drivers/input/ff-memless-next.c | 1037 +++++++++++++++++++++++++++++++++
include/linux/input/ff-memless-next.h | 162 +++++
4 files changed, 1211 insertions(+)
create mode 100644 drivers/input/ff-memless-next.c
create mode 100644 include/linux/input/ff-memless-next.h
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index a11ff74..3780962 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -38,6 +38,17 @@ config INPUT_FF_MEMLESS
To compile this driver as a module, choose M here: the
module will be called ff-memless.
+config INPUT_FF_MEMLESS_NEXT
+ tristate "New version of support for memoryless force-feedback devices"
+ help
+ Say Y here to enable a new version of support for memoryless force
+ feedback devices.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ff-memless-next.
+
config INPUT_POLLDEV
tristate "Polled input device skeleton"
help
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 5ca3f63..b4f11f5 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_INPUT) += input-core.o
input-core-y := input.o input-compat.o input-mt.o ff-core.o
obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o
+obj-$(CONFIG_INPUT_FF_MEMLESS_NEXT) += ff-memless-next.o
obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o
obj-$(CONFIG_INPUT_SPARSEKMAP) += sparse-keymap.o
obj-$(CONFIG_INPUT_MATRIXKMAP) += matrix-keymap.o
diff --git a/drivers/input/ff-memless-next.c b/drivers/input/ff-memless-next.c
new file mode 100644
index 0000000..24619e9
--- /dev/null
+++ b/drivers/input/ff-memless-next.c
@@ -0,0 +1,1037 @@
+/*
+ * Force feedback support for memoryless devices
+ *
+ * This module is based on "ff-memless" orignally written by Anssi Hannula.
+ * It is extended to support all force feedback effects currently supported
+ * by the Linux input stack.
+ * Logic of emulation of FF_RUMBLE through FF_PERIODIC provided by
+ * Elias Vanderstuyft <elias.vds@gmail.com>
+ *
+ * Copyright(c) 2014 Michal "MadCatX" Maly <madcatxster@devoid-pointer.net>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/jiffies.h>
+#include <linux/fixp-arith.h>
+#include <linux/input/ff-memless-next.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal \"MadCatX\" Maly");
+MODULE_DESCRIPTION("Force feedback support for memoryless force feedback devices");
+
+#define FF_MAX_EFFECTS 16
+#define FF_MIN_EFFECT_LENGTH ((MSEC_PER_SEC / CONFIG_HZ) + 1)
+#define FF_EFFECT_STARTED 1
+#define FF_EFFECT_PLAYING 2
+#define FF_EFFECT_EMULATED 3
+
+enum mlnx_emulate {
+ EMUL_NOTHING, /* Do not emulate anything */
+ EMUL_RUMBLE, /* Emulate FF_RUMBLE with FF_PERIODIC */
+ EMUL_PERIODIC /* Emulate FF_PERIODIC with FF_RUMBLE */
+};
+
+struct mlnx_effect {
+ struct ff_effect effect;
+ unsigned long flags;
+ unsigned long begin_at;
+ unsigned long stop_at;
+ unsigned long updated_at;
+ unsigned long attack_stop;
+ unsigned long fade_begin;
+ int repeat;
+};
+
+struct mlnx_device {
+ u8 combinable_playing;
+ u8 rumble_playing;
+ unsigned long update_rate_jiffies;
+ void *private;
+ struct mlnx_effect effects[FF_MAX_EFFECTS];
+ u16 gain;
+ struct timer_list timer;
+ struct input_dev *dev;
+ enum mlnx_emulate emul;
+
+ int (*control_effect)(struct input_dev *, void *,
+ const struct mlnx_effect_command *);
+};
+
+static s32 mlnx_calculate_x_force(const s32 level,
+ const u16 direction)
+{
+ s32 new = (level * -fixp_sin(direction)) >> FRAC_N;
+ pr_debug("x force: %d\n", new);
+ return new;
+}
+
+static s32 mlnx_calculate_y_force(const s32 level,
+ const u16 direction)
+{
+ s32 new = (level * fixp_cos(direction)) >> FRAC_N;
+ pr_debug("y force: %d\n", new);
+ return new;
+}
+
+static bool mlnx_is_combinable(const struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_CONSTANT:
+ case FF_PERIODIC:
+ case FF_RAMP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mlnx_is_conditional(const struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void mlnx_clr_emulated(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static void mlnx_clr_playing(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static void mlnx_clr_started(struct mlnx_effect *mlnxeff)
+{
+ __clear_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_emulated(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+}
+
+static bool mlnx_is_rumble(const struct ff_effect *effect)
+{
+ return effect->type == FF_RUMBLE;
+}
+
+static bool mlnx_is_playing(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static bool mlnx_is_started(const struct mlnx_effect *mlnxeff)
+{
+ return test_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static bool mlnx_test_set_playing(struct mlnx_effect *mlnxeff)
+{
+ return test_and_set_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
+}
+
+static const struct ff_envelope *mlnx_get_envelope(const struct ff_effect *effect)
+{
+ static const struct ff_envelope empty;
+
+ switch (effect->type) {
+ case FF_CONSTANT:
+ return &effect->u.constant.envelope;
+ case FF_PERIODIC:
+ return &effect->u.periodic.envelope;
+ case FF_RAMP:
+ return &effect->u.ramp.envelope;
+ default:
+ return ∅
+ }
+}
+
+/* Some devices might have a limit on how many uncombinable effects
+ * can be played at once */
+static int mlnx_upload_conditional(struct mlnx_device *mlnxdev,
+ const struct ff_effect *effect)
+{
+ struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_UPLOAD_UNCOMB,
+ .u.uncomb.id = effect->id,
+ .u.uncomb.effect = effect
+ };
+ return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+}
+
+static int mlnx_erase_conditional(struct mlnx_device *mlnxdev,
+ const struct ff_effect *effect)
+{
+ struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_ERASE_UNCOMB,
+ .u.uncomb.id = effect->id,
+ .u.uncomb.effect = effect
+ };
+ return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+}
+
+static void mlnx_set_envelope_times(struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+ if (envelope->attack_length) {
+ unsigned long j = msecs_to_jiffies(envelope->attack_length);
+ mlnxeff->attack_stop = mlnxeff->begin_at + j;
+ }
+ if (effect->replay.length && envelope->fade_length) {
+ unsigned long j = msecs_to_jiffies(envelope->fade_length);
+ mlnxeff->fade_begin = mlnxeff->stop_at - j;
+ }
+}
+
+static void mlnx_set_trip_times(struct mlnx_effect *mlnxeff,
+ const unsigned long now)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const u16 replay_delay = effect->replay.delay;
+ const u16 replay_length = effect->replay.length;
+
+ mlnxeff->begin_at = now + msecs_to_jiffies(replay_delay);
+ mlnxeff->stop_at = mlnxeff->begin_at + msecs_to_jiffies(replay_length);
+ mlnxeff->updated_at = mlnxeff->begin_at;
+}
+
+static void mlnx_start_effect(struct mlnx_effect *mlnxeff)
+{
+ const unsigned long now = jiffies;
+
+ mlnx_set_trip_times(mlnxeff, now);
+ mlnx_set_envelope_times(mlnxeff);
+ __set_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
+}
+
+static void mlnx_stop_effect(struct mlnx_device *mlnxdev,
+ const struct mlnx_effect *mlnxeff)
+{
+ switch (mlnxeff->effect.type) {
+ case FF_PERIODIC:
+ if (mlnx_is_emulated(mlnxeff)) {
+ if (--mlnxdev->rumble_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_RUMBLE
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &c);
+ }
+ return;
+ }
+ case FF_CONSTANT:
+ case FF_RAMP:
+ if (--mlnxdev->combinable_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_COMBINED
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private,
+ &c);
+ }
+ return;
+ case FF_RUMBLE:
+ if (mlnx_is_emulated(mlnxeff)) {
+ if (--mlnxdev->combinable_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_COMBINED
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &c);
+ }
+ } else {
+ if (--mlnxdev->rumble_playing == 0) {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_RUMBLE
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &c);
+ }
+ }
+ return;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ {
+ const struct mlnx_effect_command c = {
+ .cmd = MLNX_STOP_UNCOMB,
+ .u.uncomb.id = mlnxeff->effect.id,
+ .u.uncomb.effect = &mlnxeff->effect
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &c);
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+static int mlnx_restart_effect(struct mlnx_device *mlnxdev,
+ struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const unsigned long now = jiffies;
+
+ if (mlnx_is_combinable(effect)) {
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ else {
+ if (mlnx_is_emulated(mlnxeff))
+ mlnxdev->rumble_playing--;
+ else
+ mlnxdev->combinable_playing--;
+ }
+ } else if (mlnx_is_rumble(effect)) {
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ else {
+ if (mlnx_is_emulated(mlnxeff))
+ mlnxdev->combinable_playing--;
+ else
+ mlnxdev->rumble_playing--;
+ }
+ } else if (mlnx_is_conditional(effect)) {
+ int ret;
+ if (effect->replay.delay)
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+
+ ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+ if (ret)
+ return ret;
+ }
+
+ mlnx_set_trip_times(mlnxeff, now);
+ mlnx_set_envelope_times(mlnxeff);
+
+ return 0;
+}
+
+static s32 mlnx_apply_envelope(const struct mlnx_effect *mlnxeff,
+ const s32 level)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ const s32 alevel = abs(level);
+
+ /* Effect has an envelope with nonzero attack time */
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+ const s32 alength = envelope->attack_length;
+ const s32 dlevel = (alevel - envelope->attack_level)
+ * clength / alength;
+ return level < 0 ? -(dlevel + envelope->attack_level) :
+ (dlevel + envelope->attack_level);
+ } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+ const s32 flength = envelope->fade_length;
+ const s32 dlevel = (envelope->fade_level - alevel)
+ * clength / flength;
+ return level < 0 ? -(dlevel + alevel) : (dlevel + alevel);
+ }
+
+ return level;
+}
+
+static s32 mlnx_calculate_periodic(struct mlnx_effect *mlnxeff, const s32 level)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const unsigned long now = jiffies;
+ const u16 period = effect->u.periodic.period;
+ const u16 phase = effect->u.periodic.phase;
+ const s16 offset = effect->u.periodic.offset;
+ s32 new = level;
+ u16 t = (jiffies_to_msecs(now - mlnxeff->begin_at) + phase) % period;
+
+ switch (effect->u.periodic.waveform) {
+ case FF_SINE:
+ {
+ u16 degrees = (360 * t) / period;
+ new = ((level * fixp_sin(degrees)) >> FRAC_N) + offset;
+ break;
+ }
+ case FF_SQUARE:
+ {
+ u16 degrees = (360 * t) / period;
+ new = level * (degrees < 180 ? 1 : -1) + offset;
+ break;
+ }
+ case FF_SAW_UP:
+ new = 2 * level * t / period - level + offset;
+ break;
+ case FF_SAW_DOWN:
+ new = level - 2 * level * t / period + offset;
+ break;
+ case FF_TRIANGLE:
+ {
+ new = (2 * abs(level - (2 * level * t) / period));
+ new = new - abs(level) + offset;
+ break;
+ }
+ case FF_CUSTOM:
+ pr_debug("Custom waveform is not handled by this driver\n");
+ return level;
+ default:
+ pr_err("Invalid waveform\n");
+ return level;
+ }
+
+ /* Ensure that the offset did not make the value exceed s16 range */
+ new = clamp(new, -0x7fff, 0x7fff);
+ pr_debug("level: %d, t: %u\n", new, t);
+ return new;
+}
+
+static s32 mlnx_calculate_ramp(const struct mlnx_effect *mlnxeff)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ const u16 length = effect->replay.length;
+ const s16 mean = (effect->u.ramp.start_level + effect->u.ramp.end_level) / 2;
+ const u16 t = jiffies_to_msecs(now - mlnxeff->begin_at);
+ s32 start = effect->u.ramp.start_level;
+ s32 end = effect->u.ramp.end_level;
+ s32 new;
+
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
+ const s32 alength = envelope->attack_length;
+ s32 attack_level;
+ if (end > start)
+ attack_level = mean - envelope->attack_level;
+ else
+ attack_level = mean + envelope->attack_level;
+ start = (start - attack_level) * clength / alength
+ + attack_level;
+ } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
+ const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
+ const s32 flength = envelope->fade_length;
+ s32 fade_level;
+ if (end > start)
+ fade_level = mean + envelope->fade_level;
+ else
+ fade_level = mean - envelope->fade_level;
+ end = (fade_level - end) * clength / flength + end;
+ }
+
+ new = ((end - start) * t) / length + start;
+ new = clamp(new, -0x7fff, 0x7fff);
+ pr_debug("RAMP level: %d, t: %u\n", new, t);
+ return new;
+}
+
+static void mlnx_destroy(struct ff_device *dev)
+{
+ struct mlnx_device *mlnxdev = dev->private;
+ del_timer_sync(&mlnxdev->timer);
+
+ kfree(mlnxdev->private);
+}
+
+static unsigned long mlnx_get_envelope_update_time(const struct mlnx_effect *mlnxeff,
+ const unsigned long update_rate_jiffies)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+ const unsigned long now = jiffies;
+ unsigned long fade_next;
+
+ /* Effect has an envelope with nonzero attack time */
+ if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
+ if (time_before(mlnxeff->updated_at + update_rate_jiffies, mlnxeff->attack_stop))
+ return mlnxeff->updated_at + update_rate_jiffies;
+
+ return mlnxeff->attack_stop;
+ }
+
+ /* Effect has an envelope with nonzero fade time */
+ if (mlnxeff->effect.replay.length) {
+ if (!envelope->fade_length)
+ return mlnxeff->stop_at;
+
+ /* Schedule the next update when the fade begins */
+ if (time_before(mlnxeff->updated_at, mlnxeff->fade_begin))
+ return mlnxeff->fade_begin;
+
+ /* Already fading */
+ fade_next = mlnxeff->updated_at + update_rate_jiffies;
+
+ /* Schedule update when the effect stops */
+ if (time_after(fade_next, mlnxeff->stop_at))
+ return mlnxeff->stop_at;
+ /* Schedule update at the next checkpoint */
+ return fade_next;
+ }
+
+ /* There is no envelope */
+
+ /* Prevent the effect from being started twice */
+ if (mlnxeff->begin_at == now && mlnx_is_playing(mlnxeff))
+ return now - 1;
+
+ return mlnxeff->begin_at;
+}
+
+static unsigned long mlnx_get_update_time(struct mlnx_effect *mlnxeff,
+ const unsigned long update_rate_jiffies)
+{
+ const unsigned long now = jiffies;
+ unsigned long time, update_periodic;
+
+ switch (mlnxeff->effect.type) {
+ /* Constant effect does not change with time, but it can have
+ * an envelope and a duration */
+ case FF_CONSTANT:
+ return mlnx_get_envelope_update_time(mlnxeff,
+ update_rate_jiffies);
+ /* Periodic and ramp effects have to be periodically updated */
+ case FF_PERIODIC:
+ case FF_RAMP:
+ time = mlnx_get_envelope_update_time(mlnxeff,
+ update_rate_jiffies);
+ if (mlnx_is_emulated(mlnxeff))
+ update_periodic = mlnxeff->stop_at;
+ else
+ update_periodic = mlnxeff->updated_at +
+ update_rate_jiffies;
+
+ /* Periodic effect has to be updated earlier than envelope
+ * or envelope update time is in the past */
+ if (time_before(update_periodic, time) || time_before(time, now))
+ return update_periodic;
+ /* Envelope needs to be updated */
+ return time;
+ case FF_RUMBLE:
+ if (mlnx_is_emulated(mlnxeff))
+ return mlnxeff->updated_at + update_rate_jiffies;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ default:
+ if (time_after_eq(mlnxeff->begin_at, now))
+ return mlnxeff->begin_at;
+
+ return mlnxeff->stop_at;
+ }
+}
+
+static void mlnx_schedule_playback(struct mlnx_device *mlnxdev)
+{
+ struct mlnx_effect *mlnxeff;
+ const unsigned long now = jiffies;
+ int events = 0;
+ int i;
+ unsigned long earliest = 0;
+ unsigned long time;
+
+ /* Iterate over all effects and determine the earliest
+ * time when we have to attend to any */
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ mlnxeff = &mlnxdev->effects[i];
+
+ if (!mlnx_is_started(mlnxeff))
+ continue; /* Effect is not started, skip it */
+
+ if (mlnx_is_playing(mlnxeff))
+ time = mlnx_get_update_time(mlnxeff,
+ mlnxdev->update_rate_jiffies);
+ else
+ time = mlnxeff->begin_at;
+
+ pr_debug("Update time for effect %d: %lu\n", i, time);
+
+ /* Scheduled time is in the future and is either
+ * before the current earliest time or it is
+ * the first valid time value in this pass */
+ if (time_before_eq(now, time) &&
+ (++events == 1 || time_before(time, earliest)))
+ earliest = time;
+ }
+
+ if (events) {
+ pr_debug("Events: %d, earliest: %lu\n", events, earliest);
+ mod_timer(&mlnxdev->timer, earliest);
+ } else {
+ pr_debug("No events, deactivating timer\n");
+ del_timer(&mlnxdev->timer);
+ }
+}
+
+static u16 mlnx_calculate_rumble_direction(const u32 total_mag, const u16 total_dir,
+ const u32 new_mag, const u16 new_dir)
+{
+ if (!new_mag)
+ return total_dir;
+ if (!total_mag)
+ return new_dir;
+ return (((total_dir >> 1) * total_mag +
+ (new_dir >> 1) * new_mag) /
+ (total_mag + new_mag)) << 1;
+}
+
+static void mlnx_add_force(struct mlnx_effect *mlnxeff, s32 *cfx, s32 *cfy,
+ const u16 gain)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ u16 direction;
+ s32 level;
+
+ pr_debug("Processing effect type %d, ID %d\n",
+ mlnxeff->effect.type, mlnxeff->effect.id);
+
+ direction = mlnxeff->effect.direction * 360 / 0xffff;
+ pr_debug("Direction deg: %u\n", direction);
+
+ switch (mlnxeff->effect.type) {
+ case FF_CONSTANT:
+ level = mlnx_apply_envelope(mlnxeff, effect->u.constant.level);
+ break;
+ case FF_PERIODIC:
+ level = mlnx_apply_envelope(mlnxeff,
+ effect->u.periodic.magnitude);
+ level = mlnx_calculate_periodic(mlnxeff, level);
+ break;
+ case FF_RAMP:
+ level = mlnx_calculate_ramp(mlnxeff);
+ break;
+ default:
+ pr_err("Effect %d not handled by mlnx_add_force\n",
+ mlnxeff->effect.type);
+ return;
+ }
+
+ *cfx += mlnx_calculate_x_force(level, direction) * gain / 0xffff;
+ *cfy += mlnx_calculate_y_force(level, direction) * gain / 0xffff;
+}
+
+static void mlnx_add_rumble(const struct mlnx_effect *mlnxeff, u32 *strong_mag,
+ u32 *weak_mag, u16 *strong_dir,
+ u16 *weak_dir, const u16 gain)
+{
+ const struct ff_effect *eff = &mlnxeff->effect;
+ const struct ff_rumble_effect *reff = &mlnxeff->effect.u.rumble;
+ const u32 new_strong_mag = (u32)reff->strong_magnitude * gain / 0xffffU;
+ const u32 new_weak_mag = (u32)reff->weak_magnitude * gain / 0xffffU;
+
+ *strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
+ new_strong_mag,
+ eff->direction);
+ *weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
+ new_weak_mag,
+ eff->direction);
+ *strong_mag += new_strong_mag;
+ *weak_mag += new_weak_mag;
+}
+
+static void mlnx_add_emul_periodic(const struct mlnx_effect *mlnxeff,
+ u32 *strong_mag, u32 *weak_mag,
+ u16 *strong_dir, u16 *weak_dir,
+ const u16 gain)
+{
+ const struct ff_effect *eff = &mlnxeff->effect;
+ const u32 level = (u32)abs(mlnx_apply_envelope(mlnxeff,
+ eff->u.periodic.magnitude)) * gain / 0x7fffU;
+
+ *strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
+ level, eff->direction);
+ *weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
+ level, eff->direction);
+
+ *strong_mag += level;
+ *weak_mag += level;
+}
+
+static void mlnx_add_emul_rumble(const struct mlnx_effect *mlnxeff, s32 *cfx,
+ s32 *cfy, const u16 gain,
+ const unsigned long now,
+ const unsigned long update_rate_jiffies)
+{
+ const struct ff_effect *effect = &mlnxeff->effect;
+ const u16 strong = effect->u.rumble.strong_magnitude;
+ const u16 weak = effect->u.rumble.weak_magnitude;
+ /* To calculate 't', we pretend that mlnxeff->begin_at == 0, thus t == now. */
+ /* This will synchronise all simultaneously playing emul rumble effects, */
+ /* otherwise non-deterministic phase-inversions could occur depending on */
+ /* upload time, which could lead to undesired cancellation of these effects. */
+ const unsigned long t = now % (4UL * update_rate_jiffies);
+ s32 level = 0;
+ bool direction_up;
+ bool direction_left;
+
+ if (strong)
+ level += (strong / 4) * (t < 2UL * update_rate_jiffies ? 1 : -1);
+ if (weak)
+ level += (weak / 4) * (t < 2UL * update_rate_jiffies ?
+ (t < 1UL * update_rate_jiffies ? 1 : -1) :
+ (t < 3UL * update_rate_jiffies ? 1 : -1));
+ direction_up = (effect->direction > 0x3fffU && effect->direction <= 0xbfffU);
+ direction_left = (effect->direction <= 0x7fffU);
+
+ pr_debug("Emulated cf: %d, t: %lu, n: %lu, begin: %lu, diff: %lu j: %lu\n",
+ level, t, now, mlnxeff->begin_at, now - mlnxeff->begin_at,
+ update_rate_jiffies);
+ level = (level * gain) / 0xffff;
+ *cfx += direction_left ? -level : level;
+ *cfy += direction_up ? -level : level;
+}
+
+static void mlnx_play_effects(struct mlnx_device *mlnxdev)
+{
+ const u16 gain = mlnxdev->gain;
+ const unsigned long now = jiffies;
+ int i;
+ s32 cfx = 0;
+ s32 cfy = 0;
+ u32 strong_mag = 0;
+ u32 weak_mag = 0;
+ u16 strong_dir = 0;
+ u16 weak_dir = 0;
+
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[i];
+
+ if (!mlnx_is_started(mlnxeff)) {
+ pr_debug("Effect %hd/%d not started\n",
+ mlnxeff->effect.id, i);
+ continue;
+ }
+
+ if (time_before(now, mlnxeff->begin_at)) {
+ pr_debug("Effect %hd/%d begins at a later time\n",
+ mlnxeff->effect.id, i);
+ continue;
+ }
+
+ if (time_before_eq(mlnxeff->stop_at, now) && mlnxeff->effect.replay.length) {
+ pr_debug("Effect %hd/%d has to be stopped\n",
+ mlnxeff->effect.id, i);
+
+ mlnx_clr_playing(mlnxeff);
+ if (--mlnxeff->repeat > 0)
+ mlnx_restart_effect(mlnxdev, mlnxeff);
+ else {
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ mlnx_clr_started(mlnxeff);
+ mlnx_clr_emulated(mlnxeff);
+ if (mlnx_is_conditional(&mlnxeff->effect))
+ mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
+ }
+
+ continue;
+ }
+
+ switch (mlnxeff->effect.type) {
+ case FF_PERIODIC:
+ if (mlnxdev->emul == EMUL_PERIODIC) {
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->rumble_playing++;
+ pr_debug("Starting emul periodic, total rumble %u\n",
+ mlnxdev->rumble_playing);
+ }
+ __set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+ mlnx_add_emul_periodic(mlnxeff, &strong_mag, &weak_mag,
+ &strong_dir, &weak_dir, gain);
+ break;
+ }
+ case FF_CONSTANT:
+ case FF_RAMP:
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->combinable_playing++;
+ pr_debug("Starting combinable effect, total %u\n",
+ mlnxdev->combinable_playing);
+ }
+ mlnx_add_force(mlnxeff, &cfx, &cfy, gain);
+ break;
+ case FF_RUMBLE:
+ if (mlnxdev->emul == EMUL_RUMBLE) {
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->combinable_playing++;
+ pr_debug("Starting emul rumble, total comb %u\n",
+ mlnxdev->combinable_playing);
+ }
+ __set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
+ mlnx_add_emul_rumble(mlnxeff, &cfx, &cfy, gain, now,
+ mlnxdev->update_rate_jiffies);
+ } else {
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ mlnxdev->rumble_playing++;
+ pr_debug("Starting rumble effect, total %u\n",
+ mlnxdev->rumble_playing);
+ }
+ mlnx_add_rumble(mlnxeff, &strong_mag, &weak_mag,
+ &strong_dir, &weak_dir, gain);
+ }
+ break;
+ case FF_DAMPER:
+ case FF_FRICTION:
+ case FF_INERTIA:
+ case FF_SPRING:
+ if (!mlnx_test_set_playing(mlnxeff)) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_UNCOMB,
+ .u.uncomb.id = i,
+ .u.uncomb.effect = &mlnxeff->effect
+ };
+ mlnxdev->control_effect(mlnxdev->dev,
+ mlnxdev->private, &ecmd);
+ }
+ break;
+ default:
+ pr_debug("Unhandled type of effect\n");
+ }
+ mlnxeff->updated_at = now;
+ }
+
+ if (mlnxdev->combinable_playing) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_COMBINED,
+ .u.simple_force = {
+ .x = clamp(cfx, -0x7fff, 0x7fff),
+ .y = clamp(cfy, -0x7fff, 0x7fff)
+ }
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+ }
+ if (mlnxdev->rumble_playing) {
+ const struct mlnx_effect_command ecmd = {
+ .cmd = MLNX_START_RUMBLE,
+ .u.rumble_force = {
+ .strong = clamp(strong_mag, (u32)0, (u32)0xffffU),
+ .weak = clamp(weak_mag, (u32)0, (u32)0xffffU),
+ .strong_dir = clamp(strong_dir, (u16)0, (u16)0xffffU),
+ .weak_dir = clamp(weak_dir, (u16)0, (u16)0xffffU)
+ }
+ };
+ mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
+ }
+
+ mlnx_schedule_playback(mlnxdev);
+}
+
+static void mlnx_set_gain(struct input_dev *dev, u16 gain)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ int i;
+
+ mlnxdev->gain = gain;
+
+ for (i = 0; i < FF_MAX_EFFECTS; i++) {
+ struct mlnx_effect *eff = &mlnxdev->effects[i];
+ if (eff == NULL)
+ continue;
+ if (mlnx_is_playing(eff)) {
+ if (mlnx_is_combinable(&eff->effect)) {
+ mlnx_clr_playing(eff);
+ if (mlnx_is_emulated(eff))
+ --mlnxdev->rumble_playing;
+ else
+ --mlnxdev->combinable_playing;
+ } else if (mlnx_is_rumble(&eff->effect)) {
+ mlnx_clr_playing(eff);
+ if (mlnx_is_emulated(eff))
+ --mlnxdev->combinable_playing;
+ else
+ --mlnxdev->rumble_playing;
+ }
+ }
+ }
+
+ mlnx_play_effects(mlnxdev);
+}
+
+static int mlnx_startstop(struct input_dev *dev, int effect_id, int repeat)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect_id];
+ int ret;
+
+ if (repeat > 0) {
+ pr_debug("Starting effect ID %d\n", effect_id);
+ mlnxeff->repeat = repeat;
+
+ if (!mlnx_is_started(mlnxeff)) {
+ /* Check that device has a free effect slot */
+ if (mlnx_is_conditional(&mlnxeff->effect)) {
+ ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
+ if (ret) {
+ /* Device effect slots are all occupied */
+ pr_debug("No free effect slot for EID %d\n", effect_id);
+ return ret;
+ }
+ }
+ mlnx_start_effect(mlnxeff);
+ }
+ } else {
+ pr_debug("Stopping effect ID %d\n", effect_id);
+ if (mlnx_is_started(mlnxeff)) {
+ if (mlnx_is_playing(mlnxeff)) {
+ mlnx_clr_playing(mlnxeff);
+ mlnx_stop_effect(mlnxdev, mlnxeff);
+ }
+ mlnx_clr_started(mlnxeff);
+ mlnx_clr_emulated(mlnxeff);
+
+ if (mlnx_is_conditional(&mlnxeff->effect))
+ return mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
+ } else {
+ pr_debug("Effect ID %d already stopped\n", effect_id);
+ return 0;
+ }
+ }
+ mlnx_play_effects(mlnxdev);
+
+ return 0;
+}
+
+static void mlnx_timer_fired(unsigned long data)
+{
+ struct input_dev *dev = (struct input_dev *)data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+ mlnx_play_effects(dev->ff->private);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static int mlnx_upload(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct mlnx_device *mlnxdev = dev->ff->private;
+ struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect->id];
+ const u16 length = effect->replay.length;
+ const u16 delay = effect->replay.delay;
+ int ret, fade_from;
+
+ /* Effect's timing is below kernel timer resolution */
+ if (length && length < FF_MIN_EFFECT_LENGTH)
+ effect->replay.length = FF_MIN_EFFECT_LENGTH;
+ if (delay && delay < FF_MIN_EFFECT_LENGTH)
+ effect->replay.delay = FF_MIN_EFFECT_LENGTH;
+
+ /* Periodic effects must have a non-zero period */
+ if (effect->type == FF_PERIODIC) {
+ if (!effect->u.periodic.period)
+ return -EINVAL;
+ }
+ /* Ramp effects cannot be infinite */
+ if (effect->type == FF_RAMP && !length)
+ return -EINVAL;
+
+ if (mlnx_is_combinable(effect)) {
+ const struct ff_envelope *envelope = mlnx_get_envelope(effect);
+
+ /* Infinite effects cannot fade */
+ if (!length && envelope->fade_length > 0)
+ return -EINVAL;
+ /* Fade length cannot be greater than effect duration */
+ fade_from = (int)length - (int)envelope->fade_length;
+ if (fade_from < 0)
+ return -EINVAL;
+ /* Envelope cannot start fading before it finishes attacking */
+ if (fade_from < envelope->attack_length && fade_from > 0)
+ return -EINVAL;
+ }
+
+
+ spin_lock_irq(&dev->event_lock);
+ mlnxeff->effect = *effect; /* Keep internal copy of the effect */
+ /* Check if the effect being modified is playing */
+ if (mlnx_is_started(mlnxeff)) {
+ if (mlnx_is_playing(mlnxeff)) {
+ mlnx_clr_playing(mlnxeff);
+ ret = mlnx_restart_effect(mlnxdev, mlnxeff);
+
+ if (ret) {
+ /* Restore the original effect */
+ if (old)
+ mlnxeff->effect = *old;
+ spin_unlock_irq(&dev->event_lock);
+ return ret;
+ }
+ }
+
+ mlnx_schedule_playback(mlnxdev);
+ }
+
+ spin_unlock_irq(&dev->event_lock);
+
+ return 0;
+}
+
+int input_ff_create_mlnx(struct input_dev *dev, void *data,
+ int (*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
+ const u16 update_rate)
+{
+ struct mlnx_device *mlnxdev;
+ int ret;
+ const u16 min_update_rate = update_rate < FF_MIN_EFFECT_LENGTH ?
+ FF_MIN_EFFECT_LENGTH : update_rate;
+
+ mlnxdev = kzalloc(sizeof(*mlnxdev), GFP_KERNEL);
+ if (!mlnxdev)
+ return -ENOMEM;
+
+ mlnxdev->dev = dev;
+ mlnxdev->private = data;
+ mlnxdev->control_effect = control_effect;
+ mlnxdev->gain = 0xffff;
+ mlnxdev->update_rate_jiffies = msecs_to_jiffies(min_update_rate);
+ input_set_capability(dev, EV_FF, FF_GAIN);
+ setup_timer(&mlnxdev->timer, mlnx_timer_fired, (unsigned long)dev);
+
+ /* Set up effect emulation if needed */
+ if (test_bit(FF_PERIODIC, dev->ffbit) &&
+ !test_bit(FF_RUMBLE, dev->ffbit)) {
+ set_bit(FF_RUMBLE, dev->ffbit);
+ mlnxdev->emul = EMUL_RUMBLE;
+ pr_debug("Emulating RUMBLE with PERIODIC\n");
+ } else if (test_bit(FF_RUMBLE, dev->ffbit) &&
+ !test_bit(FF_PERIODIC, dev->ffbit)) {
+ set_bit(FF_PERIODIC, dev->ffbit);
+ set_bit(FF_SINE, dev->ffbit);
+ set_bit(FF_SQUARE, dev->ffbit);
+ set_bit(FF_TRIANGLE, dev->ffbit);
+ set_bit(FF_SAW_DOWN, dev->ffbit);
+ set_bit(FF_SAW_UP, dev->ffbit);
+ mlnxdev->emul = EMUL_PERIODIC;
+ pr_debug("Emulating PERIODIC with RUMBLE\n");
+ } else {
+ mlnxdev->emul = EMUL_NOTHING;
+ pr_debug("No effect emulation is necessary\n");
+ }
+
+ ret = input_ff_create(dev, FF_MAX_EFFECTS);
+ if (ret) {
+ kfree(mlnxdev);
+ return ret;
+ }
+
+
+ dev->ff->private = mlnxdev;
+ dev->ff->upload = mlnx_upload;
+ dev->ff->set_gain = mlnx_set_gain;
+ dev->ff->destroy = mlnx_destroy;
+ dev->ff->playback = mlnx_startstop;
+
+ pr_debug("Device successfully registered.\n");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(input_ff_create_mlnx);
diff --git a/include/linux/input/ff-memless-next.h b/include/linux/input/ff-memless-next.h
new file mode 100644
index 0000000..7522451
--- /dev/null
+++ b/include/linux/input/ff-memless-next.h
@@ -0,0 +1,162 @@
+#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);
+
+/** int control_effect() - Callback to the HW-specific driver.
+ * @struct input_dev *: Pointer to the struct input_dev of the device that is
+ * is being controlled.
+ * @void *: Pointer to any device-specific data set by the HW-specific driver.
+ * This data will be free'd automatically by ff-memless-next when the
+ * device is destroyed.
+ * @const struct mlnx_effect_command *:
+ * Action the device shall perform. Note that this pointer is valid
+ * only within the context of the callback function. If the HW-specific
+ * driver needs any data from this structure after the callback
+ * function returns, it must copy it.
+ */
--
1.9.2
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/24] input: Add ff-memless-next module
2014-04-26 11:57 ` [PATCH v3 01/24] input: Add ff-memless-next module Michal Malý
@ 2014-04-26 13:07 ` Antonio Ospite
2014-04-26 15:04 ` Michal Malý
0 siblings, 1 reply; 30+ messages in thread
From: Antonio Ospite @ 2014-04-26 13:07 UTC (permalink / raw)
To: Michal Malý
Cc: linux-input, linux-kernel, dmitry.torokhov, jkosina, elias.vds,
anssi.hannula, simon
On Sat, 26 Apr 2014 13:57:38 +0200
Michal Malý <madcatxster@devoid-pointer.net> wrote:
> Add ff-memless-next module
>
Hi Michal, what about adding the notes from 0/24 to this commit
message? This is the one which will actually get into the project
history. And perhaps hint briefly about the improvements also in the
commit message of 24/24?
> 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 | 162 +++++
> 4 files changed, 1211 insertions(+)
> create mode 100644 drivers/input/ff-memless-next.c
> create mode 100644 include/linux/input/ff-memless-next.h
>
> diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
> index a11ff74..3780962 100644
> --- a/drivers/input/Kconfig
> +++ b/drivers/input/Kconfig
> @@ -38,6 +38,17 @@ config INPUT_FF_MEMLESS
> To compile this driver as a module, choose M here: the
> module will be called ff-memless.
>
> +config INPUT_FF_MEMLESS_NEXT
> + tristate "New version of support for memoryless force-feedback devices"
> + help
> + Say Y here to enable a new version of support for memoryless force
> + feedback devices.
> +
> + If unsure, say N.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called ff-memless-next.
> +
> config INPUT_POLLDEV
> tristate "Polled input device skeleton"
> help
> diff --git a/drivers/input/Makefile b/drivers/input/Makefile
> index 5ca3f63..b4f11f5 100644
> --- a/drivers/input/Makefile
> +++ b/drivers/input/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_INPUT) += input-core.o
> input-core-y := input.o input-compat.o input-mt.o ff-core.o
>
> obj-$(CONFIG_INPUT_FF_MEMLESS) += ff-memless.o
> +obj-$(CONFIG_INPUT_FF_MEMLESS_NEXT) += ff-memless-next.o
> obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o
> obj-$(CONFIG_INPUT_SPARSEKMAP) += sparse-keymap.o
> obj-$(CONFIG_INPUT_MATRIXKMAP) += matrix-keymap.o
> diff --git a/drivers/input/ff-memless-next.c b/drivers/input/ff-memless-next.c
> new file mode 100644
> index 0000000..24619e9
> --- /dev/null
> +++ b/drivers/input/ff-memless-next.c
> @@ -0,0 +1,1037 @@
> +/*
> + * Force feedback support for memoryless devices
> + *
> + * This module is based on "ff-memless" orignally written by Anssi Hannula.
> + * It is extended to support all force feedback effects currently supported
> + * by the Linux input stack.
> + * Logic of emulation of FF_RUMBLE through FF_PERIODIC provided by
> + * Elias Vanderstuyft <elias.vds@gmail.com>
> + *
> + * Copyright(c) 2014 Michal "MadCatX" Maly <madcatxster@devoid-pointer.net>
> + *
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +#include <linux/jiffies.h>
> +#include <linux/fixp-arith.h>
> +#include <linux/input/ff-memless-next.h>
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Michal \"MadCatX\" Maly");
> +MODULE_DESCRIPTION("Force feedback support for memoryless force feedback devices");
> +
> +#define FF_MAX_EFFECTS 16
> +#define FF_MIN_EFFECT_LENGTH ((MSEC_PER_SEC / CONFIG_HZ) + 1)
> +#define FF_EFFECT_STARTED 1
> +#define FF_EFFECT_PLAYING 2
> +#define FF_EFFECT_EMULATED 3
> +
> +enum mlnx_emulate {
> + EMUL_NOTHING, /* Do not emulate anything */
> + EMUL_RUMBLE, /* Emulate FF_RUMBLE with FF_PERIODIC */
> + EMUL_PERIODIC /* Emulate FF_PERIODIC with FF_RUMBLE */
> +};
> +
> +struct mlnx_effect {
> + struct ff_effect effect;
> + unsigned long flags;
> + unsigned long begin_at;
> + unsigned long stop_at;
> + unsigned long updated_at;
> + unsigned long attack_stop;
> + unsigned long fade_begin;
> + int repeat;
> +};
> +
> +struct mlnx_device {
> + u8 combinable_playing;
> + u8 rumble_playing;
> + unsigned long update_rate_jiffies;
> + void *private;
> + struct mlnx_effect effects[FF_MAX_EFFECTS];
> + u16 gain;
> + struct timer_list timer;
> + struct input_dev *dev;
> + enum mlnx_emulate emul;
> +
> + int (*control_effect)(struct input_dev *, void *,
> + const struct mlnx_effect_command *);
> +};
> +
> +static s32 mlnx_calculate_x_force(const s32 level,
> + const u16 direction)
> +{
> + s32 new = (level * -fixp_sin(direction)) >> FRAC_N;
> + pr_debug("x force: %d\n", new);
> + return new;
> +}
> +
> +static s32 mlnx_calculate_y_force(const s32 level,
> + const u16 direction)
> +{
> + s32 new = (level * fixp_cos(direction)) >> FRAC_N;
> + pr_debug("y force: %d\n", new);
> + return new;
> +}
> +
> +static bool mlnx_is_combinable(const struct ff_effect *effect)
> +{
> + switch (effect->type) {
> + case FF_CONSTANT:
> + case FF_PERIODIC:
> + case FF_RAMP:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool mlnx_is_conditional(const struct ff_effect *effect)
> +{
> + switch (effect->type) {
> + case FF_DAMPER:
> + case FF_FRICTION:
> + case FF_INERTIA:
> + case FF_SPRING:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static void mlnx_clr_emulated(struct mlnx_effect *mlnxeff)
> +{
> + __clear_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
> +}
> +
> +static void mlnx_clr_playing(struct mlnx_effect *mlnxeff)
> +{
> + __clear_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
> +}
> +
> +static void mlnx_clr_started(struct mlnx_effect *mlnxeff)
> +{
> + __clear_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
> +}
> +
> +static bool mlnx_is_emulated(const struct mlnx_effect *mlnxeff)
> +{
> + return test_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
> +}
> +
> +static bool mlnx_is_rumble(const struct ff_effect *effect)
> +{
> + return effect->type == FF_RUMBLE;
> +}
> +
> +static bool mlnx_is_playing(const struct mlnx_effect *mlnxeff)
> +{
> + return test_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
> +}
> +
> +static bool mlnx_is_started(const struct mlnx_effect *mlnxeff)
> +{
> + return test_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
> +}
> +
> +static bool mlnx_test_set_playing(struct mlnx_effect *mlnxeff)
> +{
> + return test_and_set_bit(FF_EFFECT_PLAYING, &mlnxeff->flags);
> +}
> +
> +static const struct ff_envelope *mlnx_get_envelope(const struct ff_effect *effect)
> +{
> + static const struct ff_envelope empty;
> +
> + switch (effect->type) {
> + case FF_CONSTANT:
> + return &effect->u.constant.envelope;
> + case FF_PERIODIC:
> + return &effect->u.periodic.envelope;
> + case FF_RAMP:
> + return &effect->u.ramp.envelope;
> + default:
> + return ∅
> + }
> +}
> +
> +/* Some devices might have a limit on how many uncombinable effects
> + * can be played at once */
> +static int mlnx_upload_conditional(struct mlnx_device *mlnxdev,
> + const struct ff_effect *effect)
> +{
> + struct mlnx_effect_command ecmd = {
> + .cmd = MLNX_UPLOAD_UNCOMB,
> + .u.uncomb.id = effect->id,
> + .u.uncomb.effect = effect
> + };
> + return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
> +}
> +
> +static int mlnx_erase_conditional(struct mlnx_device *mlnxdev,
> + const struct ff_effect *effect)
> +{
> + struct mlnx_effect_command ecmd = {
> + .cmd = MLNX_ERASE_UNCOMB,
> + .u.uncomb.id = effect->id,
> + .u.uncomb.effect = effect
> + };
> + return mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
> +}
> +
> +static void mlnx_set_envelope_times(struct mlnx_effect *mlnxeff)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const struct ff_envelope *envelope = mlnx_get_envelope(effect);
> +
> + if (envelope->attack_length) {
> + unsigned long j = msecs_to_jiffies(envelope->attack_length);
> + mlnxeff->attack_stop = mlnxeff->begin_at + j;
> + }
> + if (effect->replay.length && envelope->fade_length) {
> + unsigned long j = msecs_to_jiffies(envelope->fade_length);
> + mlnxeff->fade_begin = mlnxeff->stop_at - j;
> + }
> +}
> +
> +static void mlnx_set_trip_times(struct mlnx_effect *mlnxeff,
> + const unsigned long now)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const u16 replay_delay = effect->replay.delay;
> + const u16 replay_length = effect->replay.length;
> +
> + mlnxeff->begin_at = now + msecs_to_jiffies(replay_delay);
> + mlnxeff->stop_at = mlnxeff->begin_at + msecs_to_jiffies(replay_length);
> + mlnxeff->updated_at = mlnxeff->begin_at;
> +}
> +
> +static void mlnx_start_effect(struct mlnx_effect *mlnxeff)
> +{
> + const unsigned long now = jiffies;
> +
> + mlnx_set_trip_times(mlnxeff, now);
> + mlnx_set_envelope_times(mlnxeff);
> + __set_bit(FF_EFFECT_STARTED, &mlnxeff->flags);
> +}
> +
> +static void mlnx_stop_effect(struct mlnx_device *mlnxdev,
> + const struct mlnx_effect *mlnxeff)
> +{
> + switch (mlnxeff->effect.type) {
> + case FF_PERIODIC:
> + if (mlnx_is_emulated(mlnxeff)) {
> + if (--mlnxdev->rumble_playing == 0) {
> + const struct mlnx_effect_command c = {
> + .cmd = MLNX_STOP_RUMBLE
> + };
> + mlnxdev->control_effect(mlnxdev->dev,
> + mlnxdev->private, &c);
> + }
> + return;
> + }
> + case FF_CONSTANT:
> + case FF_RAMP:
> + if (--mlnxdev->combinable_playing == 0) {
> + const struct mlnx_effect_command c = {
> + .cmd = MLNX_STOP_COMBINED
> + };
> + mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private,
> + &c);
> + }
> + return;
> + case FF_RUMBLE:
> + if (mlnx_is_emulated(mlnxeff)) {
> + if (--mlnxdev->combinable_playing == 0) {
> + const struct mlnx_effect_command c = {
> + .cmd = MLNX_STOP_COMBINED
> + };
> + mlnxdev->control_effect(mlnxdev->dev,
> + mlnxdev->private, &c);
> + }
> + } else {
> + if (--mlnxdev->rumble_playing == 0) {
> + const struct mlnx_effect_command c = {
> + .cmd = MLNX_STOP_RUMBLE
> + };
> + mlnxdev->control_effect(mlnxdev->dev,
> + mlnxdev->private, &c);
> + }
> + }
> + return;
> + case FF_DAMPER:
> + case FF_FRICTION:
> + case FF_INERTIA:
> + case FF_SPRING:
> + {
> + const struct mlnx_effect_command c = {
> + .cmd = MLNX_STOP_UNCOMB,
> + .u.uncomb.id = mlnxeff->effect.id,
> + .u.uncomb.effect = &mlnxeff->effect
> + };
> + mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &c);
> + return;
> + }
> + default:
> + return;
> + }
> +}
> +
> +static int mlnx_restart_effect(struct mlnx_device *mlnxdev,
> + struct mlnx_effect *mlnxeff)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const unsigned long now = jiffies;
> +
> + if (mlnx_is_combinable(effect)) {
> + if (effect->replay.delay)
> + mlnx_stop_effect(mlnxdev, mlnxeff);
> + else {
> + if (mlnx_is_emulated(mlnxeff))
> + mlnxdev->rumble_playing--;
> + else
> + mlnxdev->combinable_playing--;
> + }
> + } else if (mlnx_is_rumble(effect)) {
> + if (effect->replay.delay)
> + mlnx_stop_effect(mlnxdev, mlnxeff);
> + else {
> + if (mlnx_is_emulated(mlnxeff))
> + mlnxdev->combinable_playing--;
> + else
> + mlnxdev->rumble_playing--;
> + }
> + } else if (mlnx_is_conditional(effect)) {
> + int ret;
> + if (effect->replay.delay)
> + mlnx_stop_effect(mlnxdev, mlnxeff);
> +
> + ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
> + if (ret)
> + return ret;
> + }
> +
> + mlnx_set_trip_times(mlnxeff, now);
> + mlnx_set_envelope_times(mlnxeff);
> +
> + return 0;
> +}
> +
> +static s32 mlnx_apply_envelope(const struct mlnx_effect *mlnxeff,
> + const s32 level)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const struct ff_envelope *envelope = mlnx_get_envelope(effect);
> + const unsigned long now = jiffies;
> + const s32 alevel = abs(level);
> +
> + /* Effect has an envelope with nonzero attack time */
> + if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
> + const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
> + const s32 alength = envelope->attack_length;
> + const s32 dlevel = (alevel - envelope->attack_level)
> + * clength / alength;
> + return level < 0 ? -(dlevel + envelope->attack_level) :
> + (dlevel + envelope->attack_level);
> + } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
> + const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
> + const s32 flength = envelope->fade_length;
> + const s32 dlevel = (envelope->fade_level - alevel)
> + * clength / flength;
> + return level < 0 ? -(dlevel + alevel) : (dlevel + alevel);
> + }
> +
> + return level;
> +}
> +
> +static s32 mlnx_calculate_periodic(struct mlnx_effect *mlnxeff, const s32 level)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const unsigned long now = jiffies;
> + const u16 period = effect->u.periodic.period;
> + const u16 phase = effect->u.periodic.phase;
> + const s16 offset = effect->u.periodic.offset;
> + s32 new = level;
> + u16 t = (jiffies_to_msecs(now - mlnxeff->begin_at) + phase) % period;
> +
> + switch (effect->u.periodic.waveform) {
> + case FF_SINE:
> + {
> + u16 degrees = (360 * t) / period;
> + new = ((level * fixp_sin(degrees)) >> FRAC_N) + offset;
> + break;
> + }
> + case FF_SQUARE:
> + {
> + u16 degrees = (360 * t) / period;
> + new = level * (degrees < 180 ? 1 : -1) + offset;
> + break;
> + }
> + case FF_SAW_UP:
> + new = 2 * level * t / period - level + offset;
> + break;
> + case FF_SAW_DOWN:
> + new = level - 2 * level * t / period + offset;
> + break;
> + case FF_TRIANGLE:
> + {
> + new = (2 * abs(level - (2 * level * t) / period));
> + new = new - abs(level) + offset;
> + break;
> + }
> + case FF_CUSTOM:
> + pr_debug("Custom waveform is not handled by this driver\n");
> + return level;
> + default:
> + pr_err("Invalid waveform\n");
> + return level;
> + }
> +
> + /* Ensure that the offset did not make the value exceed s16 range */
> + new = clamp(new, -0x7fff, 0x7fff);
> + pr_debug("level: %d, t: %u\n", new, t);
> + return new;
> +}
> +
> +static s32 mlnx_calculate_ramp(const struct mlnx_effect *mlnxeff)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const struct ff_envelope *envelope = mlnx_get_envelope(effect);
> + const unsigned long now = jiffies;
> + const u16 length = effect->replay.length;
> + const s16 mean = (effect->u.ramp.start_level + effect->u.ramp.end_level) / 2;
> + const u16 t = jiffies_to_msecs(now - mlnxeff->begin_at);
> + s32 start = effect->u.ramp.start_level;
> + s32 end = effect->u.ramp.end_level;
> + s32 new;
> +
> + if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
> + const s32 clength = jiffies_to_msecs(now - mlnxeff->begin_at);
> + const s32 alength = envelope->attack_length;
> + s32 attack_level;
> + if (end > start)
> + attack_level = mean - envelope->attack_level;
> + else
> + attack_level = mean + envelope->attack_level;
> + start = (start - attack_level) * clength / alength
> + + attack_level;
> + } else if (envelope->fade_length && time_before_eq(mlnxeff->fade_begin, now)) {
> + const s32 clength = jiffies_to_msecs(now - mlnxeff->fade_begin);
> + const s32 flength = envelope->fade_length;
> + s32 fade_level;
> + if (end > start)
> + fade_level = mean + envelope->fade_level;
> + else
> + fade_level = mean - envelope->fade_level;
> + end = (fade_level - end) * clength / flength + end;
> + }
> +
> + new = ((end - start) * t) / length + start;
> + new = clamp(new, -0x7fff, 0x7fff);
> + pr_debug("RAMP level: %d, t: %u\n", new, t);
> + return new;
> +}
> +
> +static void mlnx_destroy(struct ff_device *dev)
> +{
> + struct mlnx_device *mlnxdev = dev->private;
> + del_timer_sync(&mlnxdev->timer);
> +
> + kfree(mlnxdev->private);
> +}
> +
> +static unsigned long mlnx_get_envelope_update_time(const struct mlnx_effect *mlnxeff,
> + const unsigned long update_rate_jiffies)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const struct ff_envelope *envelope = mlnx_get_envelope(effect);
> + const unsigned long now = jiffies;
> + unsigned long fade_next;
> +
> + /* Effect has an envelope with nonzero attack time */
> + if (envelope->attack_length && time_before(now, mlnxeff->attack_stop)) {
> + if (time_before(mlnxeff->updated_at + update_rate_jiffies, mlnxeff->attack_stop))
> + return mlnxeff->updated_at + update_rate_jiffies;
> +
> + return mlnxeff->attack_stop;
> + }
> +
> + /* Effect has an envelope with nonzero fade time */
> + if (mlnxeff->effect.replay.length) {
> + if (!envelope->fade_length)
> + return mlnxeff->stop_at;
> +
> + /* Schedule the next update when the fade begins */
> + if (time_before(mlnxeff->updated_at, mlnxeff->fade_begin))
> + return mlnxeff->fade_begin;
> +
> + /* Already fading */
> + fade_next = mlnxeff->updated_at + update_rate_jiffies;
> +
> + /* Schedule update when the effect stops */
> + if (time_after(fade_next, mlnxeff->stop_at))
> + return mlnxeff->stop_at;
> + /* Schedule update at the next checkpoint */
> + return fade_next;
> + }
> +
> + /* There is no envelope */
> +
> + /* Prevent the effect from being started twice */
> + if (mlnxeff->begin_at == now && mlnx_is_playing(mlnxeff))
> + return now - 1;
> +
> + return mlnxeff->begin_at;
> +}
> +
> +static unsigned long mlnx_get_update_time(struct mlnx_effect *mlnxeff,
> + const unsigned long update_rate_jiffies)
> +{
> + const unsigned long now = jiffies;
> + unsigned long time, update_periodic;
> +
> + switch (mlnxeff->effect.type) {
> + /* Constant effect does not change with time, but it can have
> + * an envelope and a duration */
> + case FF_CONSTANT:
> + return mlnx_get_envelope_update_time(mlnxeff,
> + update_rate_jiffies);
> + /* Periodic and ramp effects have to be periodically updated */
> + case FF_PERIODIC:
> + case FF_RAMP:
> + time = mlnx_get_envelope_update_time(mlnxeff,
> + update_rate_jiffies);
> + if (mlnx_is_emulated(mlnxeff))
> + update_periodic = mlnxeff->stop_at;
> + else
> + update_periodic = mlnxeff->updated_at +
> + update_rate_jiffies;
> +
> + /* Periodic effect has to be updated earlier than envelope
> + * or envelope update time is in the past */
> + if (time_before(update_periodic, time) || time_before(time, now))
> + return update_periodic;
> + /* Envelope needs to be updated */
> + return time;
> + case FF_RUMBLE:
> + if (mlnx_is_emulated(mlnxeff))
> + return mlnxeff->updated_at + update_rate_jiffies;
> + case FF_DAMPER:
> + case FF_FRICTION:
> + case FF_INERTIA:
> + case FF_SPRING:
> + default:
> + if (time_after_eq(mlnxeff->begin_at, now))
> + return mlnxeff->begin_at;
> +
> + return mlnxeff->stop_at;
> + }
> +}
> +
> +static void mlnx_schedule_playback(struct mlnx_device *mlnxdev)
> +{
> + struct mlnx_effect *mlnxeff;
> + const unsigned long now = jiffies;
> + int events = 0;
> + int i;
> + unsigned long earliest = 0;
> + unsigned long time;
> +
> + /* Iterate over all effects and determine the earliest
> + * time when we have to attend to any */
> + for (i = 0; i < FF_MAX_EFFECTS; i++) {
> + mlnxeff = &mlnxdev->effects[i];
> +
> + if (!mlnx_is_started(mlnxeff))
> + continue; /* Effect is not started, skip it */
> +
> + if (mlnx_is_playing(mlnxeff))
> + time = mlnx_get_update_time(mlnxeff,
> + mlnxdev->update_rate_jiffies);
> + else
> + time = mlnxeff->begin_at;
> +
> + pr_debug("Update time for effect %d: %lu\n", i, time);
> +
> + /* Scheduled time is in the future and is either
> + * before the current earliest time or it is
> + * the first valid time value in this pass */
> + if (time_before_eq(now, time) &&
> + (++events == 1 || time_before(time, earliest)))
> + earliest = time;
> + }
> +
> + if (events) {
> + pr_debug("Events: %d, earliest: %lu\n", events, earliest);
> + mod_timer(&mlnxdev->timer, earliest);
> + } else {
> + pr_debug("No events, deactivating timer\n");
> + del_timer(&mlnxdev->timer);
> + }
> +}
> +
> +static u16 mlnx_calculate_rumble_direction(const u32 total_mag, const u16 total_dir,
> + const u32 new_mag, const u16 new_dir)
> +{
> + if (!new_mag)
> + return total_dir;
> + if (!total_mag)
> + return new_dir;
> + return (((total_dir >> 1) * total_mag +
> + (new_dir >> 1) * new_mag) /
> + (total_mag + new_mag)) << 1;
> +}
> +
> +static void mlnx_add_force(struct mlnx_effect *mlnxeff, s32 *cfx, s32 *cfy,
> + const u16 gain)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + u16 direction;
> + s32 level;
> +
> + pr_debug("Processing effect type %d, ID %d\n",
> + mlnxeff->effect.type, mlnxeff->effect.id);
> +
> + direction = mlnxeff->effect.direction * 360 / 0xffff;
> + pr_debug("Direction deg: %u\n", direction);
> +
> + switch (mlnxeff->effect.type) {
> + case FF_CONSTANT:
> + level = mlnx_apply_envelope(mlnxeff, effect->u.constant.level);
> + break;
> + case FF_PERIODIC:
> + level = mlnx_apply_envelope(mlnxeff,
> + effect->u.periodic.magnitude);
> + level = mlnx_calculate_periodic(mlnxeff, level);
> + break;
> + case FF_RAMP:
> + level = mlnx_calculate_ramp(mlnxeff);
> + break;
> + default:
> + pr_err("Effect %d not handled by mlnx_add_force\n",
> + mlnxeff->effect.type);
> + return;
> + }
> +
> + *cfx += mlnx_calculate_x_force(level, direction) * gain / 0xffff;
> + *cfy += mlnx_calculate_y_force(level, direction) * gain / 0xffff;
> +}
> +
> +static void mlnx_add_rumble(const struct mlnx_effect *mlnxeff, u32 *strong_mag,
> + u32 *weak_mag, u16 *strong_dir,
> + u16 *weak_dir, const u16 gain)
> +{
> + const struct ff_effect *eff = &mlnxeff->effect;
> + const struct ff_rumble_effect *reff = &mlnxeff->effect.u.rumble;
> + const u32 new_strong_mag = (u32)reff->strong_magnitude * gain / 0xffffU;
> + const u32 new_weak_mag = (u32)reff->weak_magnitude * gain / 0xffffU;
> +
> + *strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
> + new_strong_mag,
> + eff->direction);
> + *weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
> + new_weak_mag,
> + eff->direction);
> + *strong_mag += new_strong_mag;
> + *weak_mag += new_weak_mag;
> +}
> +
> +static void mlnx_add_emul_periodic(const struct mlnx_effect *mlnxeff,
> + u32 *strong_mag, u32 *weak_mag,
> + u16 *strong_dir, u16 *weak_dir,
> + const u16 gain)
> +{
> + const struct ff_effect *eff = &mlnxeff->effect;
> + const u32 level = (u32)abs(mlnx_apply_envelope(mlnxeff,
> + eff->u.periodic.magnitude)) * gain / 0x7fffU;
> +
> + *strong_dir = mlnx_calculate_rumble_direction(*strong_mag, *strong_dir,
> + level, eff->direction);
> + *weak_dir = mlnx_calculate_rumble_direction(*weak_mag, *weak_dir,
> + level, eff->direction);
> +
> + *strong_mag += level;
> + *weak_mag += level;
> +}
> +
> +static void mlnx_add_emul_rumble(const struct mlnx_effect *mlnxeff, s32 *cfx,
> + s32 *cfy, const u16 gain,
> + const unsigned long now,
> + const unsigned long update_rate_jiffies)
> +{
> + const struct ff_effect *effect = &mlnxeff->effect;
> + const u16 strong = effect->u.rumble.strong_magnitude;
> + const u16 weak = effect->u.rumble.weak_magnitude;
> + /* To calculate 't', we pretend that mlnxeff->begin_at == 0, thus t == now. */
> + /* This will synchronise all simultaneously playing emul rumble effects, */
> + /* otherwise non-deterministic phase-inversions could occur depending on */
> + /* upload time, which could lead to undesired cancellation of these effects. */
> + const unsigned long t = now % (4UL * update_rate_jiffies);
> + s32 level = 0;
> + bool direction_up;
> + bool direction_left;
> +
> + if (strong)
> + level += (strong / 4) * (t < 2UL * update_rate_jiffies ? 1 : -1);
> + if (weak)
> + level += (weak / 4) * (t < 2UL * update_rate_jiffies ?
> + (t < 1UL * update_rate_jiffies ? 1 : -1) :
> + (t < 3UL * update_rate_jiffies ? 1 : -1));
> + direction_up = (effect->direction > 0x3fffU && effect->direction <= 0xbfffU);
> + direction_left = (effect->direction <= 0x7fffU);
> +
> + pr_debug("Emulated cf: %d, t: %lu, n: %lu, begin: %lu, diff: %lu j: %lu\n",
> + level, t, now, mlnxeff->begin_at, now - mlnxeff->begin_at,
> + update_rate_jiffies);
> + level = (level * gain) / 0xffff;
> + *cfx += direction_left ? -level : level;
> + *cfy += direction_up ? -level : level;
> +}
> +
> +static void mlnx_play_effects(struct mlnx_device *mlnxdev)
> +{
> + const u16 gain = mlnxdev->gain;
> + const unsigned long now = jiffies;
> + int i;
> + s32 cfx = 0;
> + s32 cfy = 0;
> + u32 strong_mag = 0;
> + u32 weak_mag = 0;
> + u16 strong_dir = 0;
> + u16 weak_dir = 0;
> +
> + for (i = 0; i < FF_MAX_EFFECTS; i++) {
> + struct mlnx_effect *mlnxeff = &mlnxdev->effects[i];
> +
> + if (!mlnx_is_started(mlnxeff)) {
> + pr_debug("Effect %hd/%d not started\n",
> + mlnxeff->effect.id, i);
> + continue;
> + }
> +
> + if (time_before(now, mlnxeff->begin_at)) {
> + pr_debug("Effect %hd/%d begins at a later time\n",
> + mlnxeff->effect.id, i);
> + continue;
> + }
> +
> + if (time_before_eq(mlnxeff->stop_at, now) && mlnxeff->effect.replay.length) {
> + pr_debug("Effect %hd/%d has to be stopped\n",
> + mlnxeff->effect.id, i);
> +
> + mlnx_clr_playing(mlnxeff);
> + if (--mlnxeff->repeat > 0)
> + mlnx_restart_effect(mlnxdev, mlnxeff);
> + else {
> + mlnx_stop_effect(mlnxdev, mlnxeff);
> + mlnx_clr_started(mlnxeff);
> + mlnx_clr_emulated(mlnxeff);
> + if (mlnx_is_conditional(&mlnxeff->effect))
> + mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
> + }
> +
> + continue;
> + }
> +
> + switch (mlnxeff->effect.type) {
> + case FF_PERIODIC:
> + if (mlnxdev->emul == EMUL_PERIODIC) {
> + if (!mlnx_test_set_playing(mlnxeff)) {
> + mlnxdev->rumble_playing++;
> + pr_debug("Starting emul periodic, total rumble %u\n",
> + mlnxdev->rumble_playing);
> + }
> + __set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
> + mlnx_add_emul_periodic(mlnxeff, &strong_mag, &weak_mag,
> + &strong_dir, &weak_dir, gain);
> + break;
> + }
> + case FF_CONSTANT:
> + case FF_RAMP:
> + if (!mlnx_test_set_playing(mlnxeff)) {
> + mlnxdev->combinable_playing++;
> + pr_debug("Starting combinable effect, total %u\n",
> + mlnxdev->combinable_playing);
> + }
> + mlnx_add_force(mlnxeff, &cfx, &cfy, gain);
> + break;
> + case FF_RUMBLE:
> + if (mlnxdev->emul == EMUL_RUMBLE) {
> + if (!mlnx_test_set_playing(mlnxeff)) {
> + mlnxdev->combinable_playing++;
> + pr_debug("Starting emul rumble, total comb %u\n",
> + mlnxdev->combinable_playing);
> + }
> + __set_bit(FF_EFFECT_EMULATED, &mlnxeff->flags);
> + mlnx_add_emul_rumble(mlnxeff, &cfx, &cfy, gain, now,
> + mlnxdev->update_rate_jiffies);
> + } else {
> + if (!mlnx_test_set_playing(mlnxeff)) {
> + mlnxdev->rumble_playing++;
> + pr_debug("Starting rumble effect, total %u\n",
> + mlnxdev->rumble_playing);
> + }
> + mlnx_add_rumble(mlnxeff, &strong_mag, &weak_mag,
> + &strong_dir, &weak_dir, gain);
> + }
> + break;
> + case FF_DAMPER:
> + case FF_FRICTION:
> + case FF_INERTIA:
> + case FF_SPRING:
> + if (!mlnx_test_set_playing(mlnxeff)) {
> + const struct mlnx_effect_command ecmd = {
> + .cmd = MLNX_START_UNCOMB,
> + .u.uncomb.id = i,
> + .u.uncomb.effect = &mlnxeff->effect
> + };
> + mlnxdev->control_effect(mlnxdev->dev,
> + mlnxdev->private, &ecmd);
> + }
> + break;
> + default:
> + pr_debug("Unhandled type of effect\n");
> + }
> + mlnxeff->updated_at = now;
> + }
> +
> + if (mlnxdev->combinable_playing) {
> + const struct mlnx_effect_command ecmd = {
> + .cmd = MLNX_START_COMBINED,
> + .u.simple_force = {
> + .x = clamp(cfx, -0x7fff, 0x7fff),
> + .y = clamp(cfy, -0x7fff, 0x7fff)
> + }
> + };
> + mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
> + }
> + if (mlnxdev->rumble_playing) {
> + const struct mlnx_effect_command ecmd = {
> + .cmd = MLNX_START_RUMBLE,
> + .u.rumble_force = {
> + .strong = clamp(strong_mag, (u32)0, (u32)0xffffU),
> + .weak = clamp(weak_mag, (u32)0, (u32)0xffffU),
> + .strong_dir = clamp(strong_dir, (u16)0, (u16)0xffffU),
> + .weak_dir = clamp(weak_dir, (u16)0, (u16)0xffffU)
> + }
> + };
> + mlnxdev->control_effect(mlnxdev->dev, mlnxdev->private, &ecmd);
> + }
> +
> + mlnx_schedule_playback(mlnxdev);
> +}
> +
> +static void mlnx_set_gain(struct input_dev *dev, u16 gain)
> +{
> + struct mlnx_device *mlnxdev = dev->ff->private;
> + int i;
> +
> + mlnxdev->gain = gain;
> +
> + for (i = 0; i < FF_MAX_EFFECTS; i++) {
> + struct mlnx_effect *eff = &mlnxdev->effects[i];
> + if (eff == NULL)
> + continue;
> + if (mlnx_is_playing(eff)) {
> + if (mlnx_is_combinable(&eff->effect)) {
> + mlnx_clr_playing(eff);
> + if (mlnx_is_emulated(eff))
> + --mlnxdev->rumble_playing;
> + else
> + --mlnxdev->combinable_playing;
> + } else if (mlnx_is_rumble(&eff->effect)) {
> + mlnx_clr_playing(eff);
> + if (mlnx_is_emulated(eff))
> + --mlnxdev->combinable_playing;
> + else
> + --mlnxdev->rumble_playing;
> + }
> + }
> + }
> +
> + mlnx_play_effects(mlnxdev);
> +}
> +
> +static int mlnx_startstop(struct input_dev *dev, int effect_id, int repeat)
> +{
> + struct mlnx_device *mlnxdev = dev->ff->private;
> + struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect_id];
> + int ret;
> +
> + if (repeat > 0) {
> + pr_debug("Starting effect ID %d\n", effect_id);
> + mlnxeff->repeat = repeat;
> +
> + if (!mlnx_is_started(mlnxeff)) {
> + /* Check that device has a free effect slot */
> + if (mlnx_is_conditional(&mlnxeff->effect)) {
> + ret = mlnx_upload_conditional(mlnxdev, &mlnxeff->effect);
> + if (ret) {
> + /* Device effect slots are all occupied */
> + pr_debug("No free effect slot for EID %d\n", effect_id);
> + return ret;
> + }
> + }
> + mlnx_start_effect(mlnxeff);
> + }
> + } else {
> + pr_debug("Stopping effect ID %d\n", effect_id);
> + if (mlnx_is_started(mlnxeff)) {
> + if (mlnx_is_playing(mlnxeff)) {
> + mlnx_clr_playing(mlnxeff);
> + mlnx_stop_effect(mlnxdev, mlnxeff);
> + }
> + mlnx_clr_started(mlnxeff);
> + mlnx_clr_emulated(mlnxeff);
> +
> + if (mlnx_is_conditional(&mlnxeff->effect))
> + return mlnx_erase_conditional(mlnxdev, &mlnxeff->effect);
> + } else {
> + pr_debug("Effect ID %d already stopped\n", effect_id);
> + return 0;
> + }
> + }
> + mlnx_play_effects(mlnxdev);
> +
> + return 0;
> +}
> +
> +static void mlnx_timer_fired(unsigned long data)
> +{
> + struct input_dev *dev = (struct input_dev *)data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&dev->event_lock, flags);
> + mlnx_play_effects(dev->ff->private);
> + spin_unlock_irqrestore(&dev->event_lock, flags);
> +}
> +
> +static int mlnx_upload(struct input_dev *dev, struct ff_effect *effect,
> + struct ff_effect *old)
> +{
> + struct mlnx_device *mlnxdev = dev->ff->private;
> + struct mlnx_effect *mlnxeff = &mlnxdev->effects[effect->id];
> + const u16 length = effect->replay.length;
> + const u16 delay = effect->replay.delay;
> + int ret, fade_from;
> +
> + /* Effect's timing is below kernel timer resolution */
> + if (length && length < FF_MIN_EFFECT_LENGTH)
> + effect->replay.length = FF_MIN_EFFECT_LENGTH;
> + if (delay && delay < FF_MIN_EFFECT_LENGTH)
> + effect->replay.delay = FF_MIN_EFFECT_LENGTH;
> +
> + /* Periodic effects must have a non-zero period */
> + if (effect->type == FF_PERIODIC) {
> + if (!effect->u.periodic.period)
> + return -EINVAL;
> + }
> + /* Ramp effects cannot be infinite */
> + if (effect->type == FF_RAMP && !length)
> + return -EINVAL;
> +
> + if (mlnx_is_combinable(effect)) {
> + const struct ff_envelope *envelope = mlnx_get_envelope(effect);
> +
> + /* Infinite effects cannot fade */
> + if (!length && envelope->fade_length > 0)
> + return -EINVAL;
> + /* Fade length cannot be greater than effect duration */
> + fade_from = (int)length - (int)envelope->fade_length;
> + if (fade_from < 0)
> + return -EINVAL;
> + /* Envelope cannot start fading before it finishes attacking */
> + if (fade_from < envelope->attack_length && fade_from > 0)
> + return -EINVAL;
> + }
> +
> +
> + spin_lock_irq(&dev->event_lock);
> + mlnxeff->effect = *effect; /* Keep internal copy of the effect */
> + /* Check if the effect being modified is playing */
> + if (mlnx_is_started(mlnxeff)) {
> + if (mlnx_is_playing(mlnxeff)) {
> + mlnx_clr_playing(mlnxeff);
> + ret = mlnx_restart_effect(mlnxdev, mlnxeff);
> +
> + if (ret) {
> + /* Restore the original effect */
> + if (old)
> + mlnxeff->effect = *old;
> + spin_unlock_irq(&dev->event_lock);
> + return ret;
> + }
> + }
> +
> + mlnx_schedule_playback(mlnxdev);
> + }
> +
> + spin_unlock_irq(&dev->event_lock);
> +
> + return 0;
> +}
> +
> +int input_ff_create_mlnx(struct input_dev *dev, void *data,
> + int (*control_effect)(struct input_dev *, void *, const struct mlnx_effect_command *),
> + const u16 update_rate)
> +{
> + struct mlnx_device *mlnxdev;
> + int ret;
> + const u16 min_update_rate = update_rate < FF_MIN_EFFECT_LENGTH ?
> + FF_MIN_EFFECT_LENGTH : update_rate;
> +
> + mlnxdev = kzalloc(sizeof(*mlnxdev), GFP_KERNEL);
> + if (!mlnxdev)
> + return -ENOMEM;
> +
> + mlnxdev->dev = dev;
> + mlnxdev->private = data;
> + mlnxdev->control_effect = control_effect;
> + mlnxdev->gain = 0xffff;
> + mlnxdev->update_rate_jiffies = msecs_to_jiffies(min_update_rate);
> + input_set_capability(dev, EV_FF, FF_GAIN);
> + setup_timer(&mlnxdev->timer, mlnx_timer_fired, (unsigned long)dev);
> +
> + /* Set up effect emulation if needed */
> + if (test_bit(FF_PERIODIC, dev->ffbit) &&
> + !test_bit(FF_RUMBLE, dev->ffbit)) {
> + set_bit(FF_RUMBLE, dev->ffbit);
> + mlnxdev->emul = EMUL_RUMBLE;
> + pr_debug("Emulating RUMBLE with PERIODIC\n");
> + } else if (test_bit(FF_RUMBLE, dev->ffbit) &&
> + !test_bit(FF_PERIODIC, dev->ffbit)) {
> + set_bit(FF_PERIODIC, dev->ffbit);
> + set_bit(FF_SINE, dev->ffbit);
> + set_bit(FF_SQUARE, dev->ffbit);
> + set_bit(FF_TRIANGLE, dev->ffbit);
> + set_bit(FF_SAW_DOWN, dev->ffbit);
> + set_bit(FF_SAW_UP, dev->ffbit);
> + mlnxdev->emul = EMUL_PERIODIC;
> + pr_debug("Emulating PERIODIC with RUMBLE\n");
> + } else {
> + mlnxdev->emul = EMUL_NOTHING;
> + pr_debug("No effect emulation is necessary\n");
> + }
> +
> + ret = input_ff_create(dev, FF_MAX_EFFECTS);
> + if (ret) {
> + kfree(mlnxdev);
> + return ret;
> + }
> +
> +
> + dev->ff->private = mlnxdev;
> + dev->ff->upload = mlnx_upload;
> + dev->ff->set_gain = mlnx_set_gain;
> + dev->ff->destroy = mlnx_destroy;
> + dev->ff->playback = mlnx_startstop;
> +
> + pr_debug("Device successfully registered.\n");
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(input_ff_create_mlnx);
> diff --git a/include/linux/input/ff-memless-next.h b/include/linux/input/ff-memless-next.h
> new file mode 100644
> index 0000000..7522451
> --- /dev/null
> +++ b/include/linux/input/ff-memless-next.h
> @@ -0,0 +1,162 @@
> +#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);
> +
> +/** int control_effect() - Callback to the HW-specific driver.
> + * @struct input_dev *: Pointer to the struct input_dev of the device that is
> + * is being controlled.
> + * @void *: Pointer to any device-specific data set by the HW-specific driver.
> + * This data will be free'd automatically by ff-memless-next when the
> + * device is destroyed.
> + * @const struct mlnx_effect_command *:
> + * Action the device shall perform. Note that this pointer is valid
> + * only within the context of the callback function. If the HW-specific
> + * driver needs any data from this structure after the callback
> + * function returns, it must copy it.
> + */
> --
> 1.9.2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Antonio Ospite
http://ao2.it
A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 01/24] input: Add ff-memless-next module
2014-04-26 13:07 ` Antonio Ospite
@ 2014-04-26 15:04 ` Michal Malý
0 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 15:04 UTC (permalink / raw)
To: Antonio Ospite
Cc: linux-input, linux-kernel, dmitry.torokhov, jkosina, elias.vds,
anssi.hannula, simon
On Saturday 26 of April 2014 15:07:01 Antonio Ospite wrote:
> On Sat, 26 Apr 2014 13:57:38 +0200
>
> Michal Malý <madcatxster@devoid-pointer.net> wrote:
> > Add ff-memless-next module
>
> Hi Michal, what about adding the notes from 0/24 to this commit
> message? This is the one which will actually get into the project
> history. And perhaps hint briefly about the improvements also in the
> commit message of 24/24?
Okay. I added a summary of the changes to the commit message in "v4".
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v3 02/24] input: Port arizona-haptics to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
2014-04-26 11:57 ` [PATCH v3 01/24] input: Add ff-memless-next module Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 03/24] input: Port twl4030-vibra " Michal Malý
` (21 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port arizona-haptics to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/arizona-haptics.c | 39 +++++++++++++++++++++++-------------
2 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 5928ea7..f669cc7 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] 30+ messages in thread
* [PATCH v3 03/24] input: Port twl4030-vibra to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
2014-04-26 11:57 ` [PATCH v3 01/24] input: Add ff-memless-next module Michal Malý
2014-04-26 11:57 ` [PATCH v3 02/24] input: Port arizona-haptics to ff-memless-next Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v4 04/24] input: Port twl6040-vibra " Michal Malý
` (20 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port twl4030-vibra to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/twl4030-vibra.c | 31 +++++++++++++++++++++++++------
2 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index f669cc7..817156f 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] 30+ messages in thread
* [PATCH v4 04/24] input: Port twl6040-vibra to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (2 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 03/24] input: Port twl4030-vibra " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 05/24] input: Port max8997_haptic " Michal Malý
` (19 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port twl6040-vibra to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/twl6040-vibra.c | 27 ++++++++++++++++++++++-----
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 817156f..b9cbb91 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] 30+ messages in thread
* [PATCH v3 05/24] input: Port max8997_haptic to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (3 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v4 04/24] input: Port twl6040-vibra " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 06/24] input: Port pm8xxx-vibrator " Michal Malý
` (18 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port max8997_haptic to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/max8997_haptic.c | 25 +++++++++++++++++++------
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index b9cbb91..a89dc22 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 && 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] 30+ messages in thread
* [PATCH v3 06/24] input: Port pm8xxx-vibrator to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (4 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 05/24] input: Port max8997_haptic " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 07/24] hid: Port hid-axff " Michal Malý
` (17 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port pm8xxx-vibrator to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/misc/Kconfig | 2 +-
drivers/input/misc/pm8xxx-vibrator.c | 28 +++++++++++++++++++---------
2 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index a89dc22..7b962e4 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 6a915ba..1796f4f 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;
@@ -181,8 +191,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] 30+ messages in thread
* [PATCH v3 07/24] hid: Port hid-axff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (5 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 06/24] input: Port pm8xxx-vibrator " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 08/24] hid: Port hid-emsff " Michal Malý
` (16 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-axff to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-axff.c | 32 +++++++++++++++++++++++---------
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 7af9d0b..e076627 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] 30+ messages in thread
* [PATCH v3 08/24] hid: Port hid-emsff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (6 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 07/24] hid: Port hid-axff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 09/24] hid: Port hid-dr " Michal Malý
` (15 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-emsff to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-emsff.c | 38 ++++++++++++++++++++++++++------------
2 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index e076627..a78b5d8 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -213,7 +213,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] 30+ messages in thread
* [PATCH v3 09/24] hid: Port hid-dr to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (7 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 08/24] hid: Port hid-emsff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 10/24] hid: Port hid-gaff " Michal Malý
` (14 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-dr to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-dr.c | 59 ++++++++++++++++++++++++++++++++--------------------
2 files changed, 37 insertions(+), 24 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index a78b5d8..1d4180c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -205,7 +205,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] 30+ messages in thread
* [PATCH v3 10/24] hid: Port hid-gaff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (8 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 09/24] hid: Port hid-dr " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 11/24] hid: Port hid-holtekff " Michal Malý
` (13 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-gaff to ff-memless-next
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 1d4180c..4c59a88 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -667,7 +667,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
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 11/24] hid: Port hid-holtekff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (9 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 10/24] hid: Port hid-gaff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 12/24] hid: Port hid-lgff " Michal Malý
` (12 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-holtekff to ff-memless-next
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 4c59a88..1749a4a 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -256,7 +256,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] 30+ messages in thread
* [PATCH v3 12/24] hid: Port hid-lgff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (10 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 11/24] hid: Port hid-holtekff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 13/24] hid: Port hid-lg3ff " Michal Malý
` (11 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-lgff to ff-memless-next
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 1749a4a..c4b0cbb 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -365,7 +365,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..40ad68a 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] 30+ messages in thread
* [PATCH v3 13/24] hid: Port hid-lg3ff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (11 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 12/24] hid: Port hid-lgff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 14/24] hid: Port hid-pl " Michal Malý
` (10 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-lg3ff to ff-memless-next
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 c4b0cbb..eb0c7f1 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -391,7 +391,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] 30+ messages in thread
* [PATCH v3 14/24] hid: Port hid-pl to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (12 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 13/24] hid: Port hid-lg3ff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 15/24] hid: Port hid-sjoy " Michal Malý
` (9 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-pl to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-pl.c | 38 ++++++++++++++++++++++++++------------
2 files changed, 27 insertions(+), 13 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index eb0c7f1..42904e4 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -509,7 +509,7 @@ config HID_PANTHERLORD
config PANTHERLORD_FF
bool "Pantherlord force feedback support"
depends on HID_PANTHERLORD
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you have a PantherLord/GreenAsia based game controller
or adapter and want to enable force feedback support for it.
diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c
index 2dcd7d9..9b539d5 100644
--- a/drivers/hid/hid-pl.c
+++ b/drivers/hid/hid-pl.c
@@ -44,9 +44,12 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/hid.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-ids.h"
+#define FF_UPDATE_RATE 50
+
#ifdef CONFIG_PANTHERLORD_FF
struct plff_device {
@@ -57,24 +60,35 @@ struct plff_device {
};
static int hid_plff_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 plff_device *plff = 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;
- debug("called with 0x%04x 0x%04x", left, right);
-
- left = left * plff->maxval / 0xffff;
- right = right * plff->maxval / 0xffff;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ left = rumble_force->strong;
+ right = rumble_force->weak;
+ debug("called with 0x%04x 0x%04x", left, right);
+
+ left = left * plff->maxval / 0xffff;
+ right = right * plff->maxval / 0xffff;
+
+ *plff->strong = left;
+ *plff->weak = right;
+ debug("running with 0x%02x 0x%02x", left, right);
+ break;
+ case MLNX_STOP_RUMBLE:
+ *plff->strong = 0;
+ *plff->weak = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
- *plff->strong = left;
- *plff->weak = right;
- debug("running with 0x%02x 0x%02x", left, right);
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
-
return 0;
}
@@ -160,7 +174,7 @@ static int plff_init(struct hid_device *hid)
set_bit(FF_RUMBLE, dev->ffbit);
- error = input_ff_create_memless(dev, plff, hid_plff_play);
+ error = input_ff_create_mlnx(dev, plff, hid_plff_play, FF_UPDATE_RATE);
if (error) {
kfree(plff);
return error;
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 15/24] hid: Port hid-sjoy to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (13 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 14/24] hid: Port hid-pl " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 16/24] hid: Port hid-sony " Michal Malý
` (8 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-sjoy to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-sjoy.c | 35 +++++++++++++++++++++++++----------
2 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 42904e4..9260d14 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -692,7 +692,7 @@ config HID_SMARTJOYPLUS
config SMARTJOYPLUS_FF
bool "SmartJoy PLUS PS2/USB adapter force feedback support"
depends on HID_SMARTJOYPLUS
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you have a SmartJoy PLUS PS2/USB adapter and want to
enable force feedback support for it.
diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c
index 37845ec..a6f8cfe 100644
--- a/drivers/hid/hid-sjoy.c
+++ b/drivers/hid/hid-sjoy.c
@@ -30,8 +30,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_SMARTJOYPLUS_FF
struct sjoyff_device {
@@ -39,21 +42,33 @@ struct sjoyff_device {
};
static int hid_sjoyff_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 sjoyff_device *sjoyff = data;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
u32 left, right;
- left = effect->u.rumble.strong_magnitude;
- right = effect->u.rumble.weak_magnitude;
- dev_dbg(&dev->dev, "called with 0x%08x 0x%08x\n", left, right);
-
- left = left * 0xff / 0xffff;
- right = (right != 0); /* on/off only */
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ left = rumble_force->strong;
+ right = rumble_force->weak;
+ dev_dbg(&dev->dev, "called with 0x%08x 0x%08x\n", left, right);
+
+ left = left * 0xff / 0xffff;
+ right = (right != 0); /* on/off only */
+
+ sjoyff->report->field[0]->value[1] = right;
+ sjoyff->report->field[0]->value[2] = left;
+ break;
+ case MLNX_STOP_RUMBLE:
+ left = 0;
+ right = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
- sjoyff->report->field[0]->value[1] = right;
- sjoyff->report->field[0]->value[2] = left;
dev_dbg(&dev->dev, "running with 0x%02x 0x%02x\n", left, right);
hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
@@ -103,7 +118,7 @@ static int sjoyff_init(struct hid_device *hid)
set_bit(FF_RUMBLE, dev->ffbit);
- error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
+ error = input_ff_create_mlnx(dev, sjoyff, hid_sjoyff_play, FF_UPDATE_RATE);
if (error) {
kfree(sjoyff);
return error;
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 16/24] hid: Port hid-sony to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (14 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 15/24] hid: Port hid-sjoy " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 13:09 ` Antonio Ospite
2014-04-26 11:57 ` [PATCH v3 17/24] hid: Port hid-tmff " Michal Malý
` (7 subsequent siblings)
23 siblings, 1 reply; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-sony to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-sony.c | 24 ++++++++++++++++++------
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 9260d14..e97c382 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -634,7 +634,7 @@ config HID_SONY
config SONY_FF
bool "Sony PS2/3/4 accessories force feedback support"
depends on HID_SONY
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you have a Sony PS2/3/4 accessory and want to enable
force feedback support for it.
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 908de27..95bb2e1 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -34,6 +34,7 @@
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/input/mt.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-ids.h"
@@ -53,6 +54,7 @@
#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER)
#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER)
+#define FF_UPDATE_RATE 50
#define MAX_LEDS 4
static const u8 sixaxis_rdesc_fixup[] = {
@@ -1308,16 +1310,25 @@ static void dualshock4_state_worker(struct work_struct *work)
#ifdef CONFIG_SONY_FF
static int sony_play_effect(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 sony_sc *sc = hid_get_drvdata(hid);
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
- if (effect->type != FF_RUMBLE)
- return 0;
- sc->left = effect->u.rumble.strong_magnitude / 256;
- sc->right = effect->u.rumble.weak_magnitude / 256;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ sc->left = rumble_force->strong / 256;
+ sc->right = rumble_force->weak / 256;
+ break;
+ case MLNX_STOP_RUMBLE:
+ sc->left = 0;
+ sc->right = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
schedule_work(&sc->state_worker);
return 0;
@@ -1330,7 +1341,7 @@ static int sony_init_ff(struct hid_device *hdev)
struct input_dev *input_dev = hidinput->input;
input_set_capability(input_dev, EV_FF, FF_RUMBLE);
- return input_ff_create_memless(input_dev, NULL, sony_play_effect);
+ return input_ff_create_mlnx(input_dev, NULL, sony_play_effect, FF_UPDATE_RATE);
}
#else
@@ -1762,6 +1773,7 @@ static const struct hid_device_id sony_devices[] = {
.driver_data = DUALSHOCK4_CONTROLLER_USB },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
.driver_data = DUALSHOCK4_CONTROLLER_BT },
+
{ }
};
MODULE_DEVICE_TABLE(hid, sony_devices);
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH v3 16/24] hid: Port hid-sony to ff-memless-next
2014-04-26 11:57 ` [PATCH v3 16/24] hid: Port hid-sony " Michal Malý
@ 2014-04-26 13:09 ` Antonio Ospite
2014-04-26 15:05 ` Michal Malý
0 siblings, 1 reply; 30+ messages in thread
From: Antonio Ospite @ 2014-04-26 13:09 UTC (permalink / raw)
To: Michal Malý
Cc: linux-input, linux-kernel, dmitry.torokhov, jkosina, elias.vds,
anssi.hannula, simon
On Sat, 26 Apr 2014 13:57:53 +0200
Michal Malý <madcatxster@devoid-pointer.net> wrote:
> Port hid-sony to ff-memless-next
>
> Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
Not a big deal, but note that the patch adds an unneeded
blank line. Pointed out below.
> ---
> drivers/hid/Kconfig | 2 +-
> drivers/hid/hid-sony.c | 24 ++++++++++++++++++------
> 2 files changed, 19 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 9260d14..e97c382 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -634,7 +634,7 @@ config HID_SONY
> config SONY_FF
> bool "Sony PS2/3/4 accessories force feedback support"
> depends on HID_SONY
> - select INPUT_FF_MEMLESS
> + select INPUT_FF_MEMLESS_NEXT
> ---help---
> Say Y here if you have a Sony PS2/3/4 accessory and want to enable
> force feedback support for it.
> diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
> index 908de27..95bb2e1 100644
> --- a/drivers/hid/hid-sony.c
> +++ b/drivers/hid/hid-sony.c
> @@ -34,6 +34,7 @@
> #include <linux/spinlock.h>
> #include <linux/list.h>
> #include <linux/input/mt.h>
> +#include <linux/input/ff-memless-next.h>
>
> #include "hid-ids.h"
>
> @@ -53,6 +54,7 @@
> #define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER)
> #define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER)
>
> +#define FF_UPDATE_RATE 50
> #define MAX_LEDS 4
>
> static const u8 sixaxis_rdesc_fixup[] = {
> @@ -1308,16 +1310,25 @@ static void dualshock4_state_worker(struct work_struct *work)
>
> #ifdef CONFIG_SONY_FF
> static int sony_play_effect(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 sony_sc *sc = hid_get_drvdata(hid);
> + const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
>
> - if (effect->type != FF_RUMBLE)
> - return 0;
>
> - sc->left = effect->u.rumble.strong_magnitude / 256;
> - sc->right = effect->u.rumble.weak_magnitude / 256;
> + switch (command->cmd) {
> + case MLNX_START_RUMBLE:
> + sc->left = rumble_force->strong / 256;
> + sc->right = rumble_force->weak / 256;
> + break;
> + case MLNX_STOP_RUMBLE:
> + sc->left = 0;
> + sc->right = 0;
> + break;
> + default:
> + return -EINVAL;
> + }
>
> schedule_work(&sc->state_worker);
> return 0;
> @@ -1330,7 +1341,7 @@ static int sony_init_ff(struct hid_device *hdev)
> struct input_dev *input_dev = hidinput->input;
>
> input_set_capability(input_dev, EV_FF, FF_RUMBLE);
> - return input_ff_create_memless(input_dev, NULL, sony_play_effect);
> + return input_ff_create_mlnx(input_dev, NULL, sony_play_effect, FF_UPDATE_RATE);
> }
>
> #else
> @@ -1762,6 +1773,7 @@ static const struct hid_device_id sony_devices[] = {
> .driver_data = DUALSHOCK4_CONTROLLER_USB },
> { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
> .driver_data = DUALSHOCK4_CONTROLLER_BT },
> +
Extra blank line here.
> { }
> };
> MODULE_DEVICE_TABLE(hid, sony_devices);
> --
> 1.9.2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Antonio Ospite
http://ao2.it
A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v3 16/24] hid: Port hid-sony to ff-memless-next
2014-04-26 13:09 ` Antonio Ospite
@ 2014-04-26 15:05 ` Michal Malý
0 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 15:05 UTC (permalink / raw)
To: Antonio Ospite
Cc: linux-input, linux-kernel, dmitry.torokhov, jkosina, elias.vds,
anssi.hannula, simon
On Saturday 26 of April 2014 15:09:44 Antonio Ospite wrote:
> On Sat, 26 Apr 2014 13:57:53 +0200
>
> Michal Malý <madcatxster@devoid-pointer.net> wrote:
> > Port hid-sony to ff-memless-next
> >
> > Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
>
> Not a big deal, but note that the patch adds an unneeded
> blank line. Pointed out below.
Oh, thanks. Corrected in "v4".
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v3 17/24] hid: Port hid-tmff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (15 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 16/24] hid: Port hid-sony " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 18/24] hid: Port hid-wiimote-modules " Michal Malý
` (6 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-tmff to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-tmff.c | 83 ++++++++++++++++++++++++++++++--------------------
2 files changed, 51 insertions(+), 34 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index e97c382..17ed5cf 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -730,7 +730,7 @@ config HID_THRUSTMASTER
config THRUSTMASTER_FF
bool "ThrustMaster devices force feedback support"
depends on HID_THRUSTMASTER
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or 3,
a THRUSTMASTER Dual Trigger 3-in-1 or a THRUSTMASTER Ferrari GT
diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c
index b833760..3df000c 100644
--- a/drivers/hid/hid-tmff.c
+++ b/drivers/hid/hid-tmff.c
@@ -31,9 +31,12 @@
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-ids.h"
+#define FF_UPDATE_RATE 50
+
static const signed short ff_rumble[] = {
FF_RUMBLE,
-1
@@ -41,6 +44,13 @@ static const signed short ff_rumble[] = {
static const signed short ff_joystick[] = {
FF_CONSTANT,
+ FF_RAMP,
+ FF_PERIODIC,
+ FF_SINE,
+ FF_SQUARE,
+ FF_TRIANGLE,
+ FF_SAW_DOWN,
+ FF_SAW_UP,
-1
};
@@ -67,12 +77,12 @@ static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum)
return ret;
}
-/* Changes values from -0x80 to 0x7f into values from minimum to maximum */
-static inline int tmff_scale_s8(int in, int minimum, int maximum)
+/* Changes values from -0x7fff to 0x7fff into values from minimum to maximum */
+static inline int tmff_scale_s32(int in, int minimum, int maximum)
{
int ret;
- ret = (((in + 0x80) * (maximum - minimum)) / 0xff) + minimum;
+ ret = (((in + 0x7fff) * (maximum - minimum)) / 0xffff) + minimum;
if (ret < minimum)
return minimum;
if (ret > maximum)
@@ -81,43 +91,50 @@ static inline int tmff_scale_s8(int in, int minimum, int maximum)
}
static int tmff_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 tmff_device *tmff = data;
struct hid_field *ff_field = tmff->ff_field;
int x, y;
- int left, right; /* Rumbling */
-
- switch (effect->type) {
- case FF_CONSTANT:
- x = tmff_scale_s8(effect->u.ramp.start_level,
- ff_field->logical_minimum,
- ff_field->logical_maximum);
- y = tmff_scale_s8(effect->u.ramp.end_level,
- ff_field->logical_minimum,
- ff_field->logical_maximum);
-
- dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
- ff_field->value[0] = x;
- ff_field->value[1] = y;
- hid_hw_request(hid, tmff->report, HID_REQ_SET_REPORT);
- break;
- case FF_RUMBLE:
- left = tmff_scale_u16(effect->u.rumble.weak_magnitude,
- ff_field->logical_minimum,
- ff_field->logical_maximum);
- right = tmff_scale_u16(effect->u.rumble.strong_magnitude,
- ff_field->logical_minimum,
- ff_field->logical_maximum);
-
- dbg_hid("(left,right)=(%08x, %08x)\n", left, right);
- ff_field->value[0] = left;
- ff_field->value[1] = right;
- hid_hw_request(hid, tmff->report, HID_REQ_SET_REPORT);
+ switch (command->cmd) {
+ case MLNX_START_COMBINED: {
+ const struct mlnx_simple_force *sf = &command->u.simple_force;
+ x = tmff_scale_s32(sf->x,
+ ff_field->logical_minimum,
+ ff_field->logical_maximum);
+ y = tmff_scale_s32(sf->y,
+ ff_field->logical_minimum,
+ ff_field->logical_maximum);
+ break;
+ }
+ case MLNX_STOP_COMBINED:
+ x = 0;
+ y = 0;
+ break;
+ case MLNX_START_RUMBLE: {
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+ x = tmff_scale_u16(rumble_force->weak,
+ ff_field->logical_minimum,
+ ff_field->logical_maximum);
+ y = tmff_scale_u16(rumble_force->strong,
+ ff_field->logical_minimum,
+ ff_field->logical_maximum);
break;
+ }
+ case MLNX_STOP_RUMBLE:
+ x = 0;
+ y = 0;
+ break;
+ default:
+ return -EINVAL;
}
+
+ ff_field->value[0] = x;
+ ff_field->value[1] = y;
+
+ hid_hw_request(hid, tmff->report, HID_REQ_SET_REPORT);
return 0;
}
@@ -192,7 +209,7 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
goto fail;
}
- error = input_ff_create_memless(input_dev, tmff, tmff_play);
+ error = input_ff_create_mlnx(input_dev, tmff, tmff_play, FF_UPDATE_RATE);
if (error)
goto fail;
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 18/24] hid: Port hid-wiimote-modules to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (16 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 17/24] hid: Port hid-tmff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 19/24] hid: Port hid-zpff " Michal Malý
` (5 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-wiimote-modules to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-wiimote-modules.c | 74 ++++++++++++++++++++++++---------------
2 files changed, 46 insertions(+), 30 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 17ed5cf..23d9776 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -749,7 +749,7 @@ config HID_WIIMOTE
depends on HID
depends on LEDS_CLASS
select POWER_SUPPLY
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Support for Nintendo Wii and Wii U Bluetooth peripherals. Supported
devices are the Wii Remote and its extension devices, but also devices
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c
index 6b61f01..95b20ea 100644
--- a/drivers/hid/hid-wiimote-modules.c
+++ b/drivers/hid/hid-wiimote-modules.c
@@ -37,8 +37,11 @@
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/spinlock.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-wiimote.h"
+#define FF_UPDATE_RATE 50
+
/*
* Keys
* The initial Wii Remote provided a bunch of buttons that are reported as
@@ -131,21 +134,27 @@ static void wiimod_rumble_worker(struct work_struct *work)
}
static int wiimod_rumble_play(struct input_dev *dev, void *data,
- struct ff_effect *eff)
+ const struct mlnx_effect_command *command)
{
struct wiimote_data *wdata = input_get_drvdata(dev);
- __u8 value;
-
- /*
- * The wiimote supports only a single rumble motor so if any magnitude
- * is set to non-zero then we start the rumble motor. If both are set to
- * zero, we stop the rumble motor.
- */
-
- if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
- value = 1;
- else
- value = 0;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+ __u8 value = 0;
+
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ /*
+ * The wiimote supports only a single rumble motor so if any magnitude
+ * is set to non-zero then we start the rumble motor. If both are set to
+ * zero, we stop the rumble motor.
+ */
+ if (rumble_force->strong || rumble_force->weak)
+ value = 1;
+ break;
+ case MLNX_STOP_RUMBLE:
+ break;
+ default:
+ return -EINVAL;
+ }
/* Locking state.lock here might deadlock with input_event() calls.
* schedule_work acts as barrier. Merging multiple changes is fine. */
@@ -161,7 +170,7 @@ static int wiimod_rumble_probe(const struct wiimod_ops *ops,
INIT_WORK(&wdata->rumble_worker, wiimod_rumble_worker);
set_bit(FF_RUMBLE, wdata->input->ffbit);
- if (input_ff_create_memless(wdata->input, NULL, wiimod_rumble_play))
+ if (input_ff_create_mlnx(wdata->input, NULL, wiimod_rumble_play, FF_UPDATE_RATE))
return -ENOMEM;
return 0;
@@ -1771,21 +1780,28 @@ static void wiimod_pro_close(struct input_dev *dev)
}
static int wiimod_pro_play(struct input_dev *dev, void *data,
- struct ff_effect *eff)
+ const struct mlnx_effect_command *command)
{
struct wiimote_data *wdata = input_get_drvdata(dev);
- __u8 value;
-
- /*
- * The wiimote supports only a single rumble motor so if any magnitude
- * is set to non-zero then we start the rumble motor. If both are set to
- * zero, we stop the rumble motor.
- */
-
- if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
- value = 1;
- else
- value = 0;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+ __u8 value = 0;
+
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ /*
+ * The wiimote supports only a single rumble motor so if any magnitude
+ * is set to non-zero then we start the rumble motor. If both are set to
+ * zero, we stop the rumble motor.
+ */
+
+ if (rumble_force->strong || rumble_force->weak)
+ value = 1;
+ break;
+ case MLNX_STOP_RUMBLE:
+ break;
+ default:
+ return -EINVAL;
+ }
/* Locking state.lock here might deadlock with input_event() calls.
* schedule_work acts as barrier. Merging multiple changes is fine. */
@@ -1867,8 +1883,8 @@ static int wiimod_pro_probe(const struct wiimod_ops *ops,
set_bit(FF_RUMBLE, wdata->extension.input->ffbit);
input_set_drvdata(wdata->extension.input, wdata);
- if (input_ff_create_memless(wdata->extension.input, NULL,
- wiimod_pro_play)) {
+ if (input_ff_create_mlnx(wdata->extension.input, NULL,
+ wiimod_pro_play, FF_UPDATE_RATE)) {
ret = -ENOMEM;
goto err_free;
}
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 19/24] hid: Port hid-zpff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (17 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 18/24] hid: Port hid-wiimote-modules " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 20/24] input: Port gamecon " Michal Malý
` (4 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-zpff to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-zpff.c | 30 ++++++++++++++++++++++--------
2 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 23d9776..97d2d8f 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -786,7 +786,7 @@ config HID_ZEROPLUS
config ZEROPLUS_FF
bool "Zeroplus based game controller force feedback support"
depends on HID_ZEROPLUS
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you have a Zeroplus based game controller and want
to have force feedback support for it.
diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c
index a29756c..6912500 100644
--- a/drivers/hid/hid-zpff.c
+++ b/drivers/hid/hid-zpff.c
@@ -25,9 +25,12 @@
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/input/ff-memless-next.h>
#include "hid-ids.h"
+#define FF_UPDATE_RATE 50
+
#ifdef CONFIG_ZEROPLUS_FF
struct zpff_device {
@@ -35,10 +38,11 @@ struct zpff_device {
};
static int zpff_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 zpff_device *zpff = data;
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
int left, right;
/*
@@ -47,12 +51,22 @@ static int zpff_play(struct input_dev *dev, void *data,
* however it is possible that the XFX Executioner is an exception
*/
- left = effect->u.rumble.strong_magnitude;
- right = effect->u.rumble.weak_magnitude;
- dbg_hid("called with 0x%04x 0x%04x\n", left, right);
-
- left = left * 0x7f / 0xffff;
- right = right * 0x7f / 0xffff;
+ 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);
+
+ left = left * 0x7f / 0xffff;
+ right = right * 0x7f / 0xffff;
+ break;
+ case MLNX_STOP_RUMBLE:
+ left = 0;
+ right = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
zpff->report->field[2]->value[0] = left;
zpff->report->field[3]->value[0] = right;
@@ -83,7 +97,7 @@ static int zpff_init(struct hid_device *hid)
set_bit(FF_RUMBLE, dev->ffbit);
- error = input_ff_create_memless(dev, zpff, zpff_play);
+ error = input_ff_create_mlnx(dev, zpff, zpff_play, FF_UPDATE_RATE);
if (error) {
kfree(zpff);
return error;
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 20/24] input: Port gamecon to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (18 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 19/24] hid: Port hid-zpff " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 21/24] input: Port xpad " Michal Malý
` (3 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port gamecon to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/joystick/Kconfig | 2 +-
drivers/input/joystick/gamecon.c | 57 ++++++++++++++++++++++------------------
2 files changed, 33 insertions(+), 26 deletions(-)
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 56eb471..2dd3ba1 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -221,7 +221,7 @@ config JOYSTICK_DB9
config JOYSTICK_GAMECON
tristate "Multisystem, NES, SNES, N64, PSX joysticks and gamepads"
depends on PARPORT
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you have a Nintendo Entertainment System gamepad,
Super Nintendo Entertainment System gamepad, Nintendo 64 gamepad,
diff --git a/drivers/input/joystick/gamecon.c b/drivers/input/joystick/gamecon.c
index e68e497..209d0fb 100644
--- a/drivers/input/joystick/gamecon.c
+++ b/drivers/input/joystick/gamecon.c
@@ -40,6 +40,7 @@
#include <linux/input.h>
#include <linux/mutex.h>
#include <linux/slab.h>
+#include <linux/input/ff-memless-next.h>
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION("NES, SNES, N64, MultiSystem, PSX gamepad driver");
@@ -47,6 +48,7 @@ MODULE_LICENSE("GPL");
#define GC_MAX_PORTS 3
#define GC_MAX_DEVICES 5
+#define FF_UPDATE_RATE 50
struct gc_config {
int args[GC_MAX_DEVICES + 1];
@@ -263,43 +265,48 @@ static void gc_n64_process_packet(struct gc *gc)
}
static int gc_n64_play_effect(struct input_dev *dev, void *data,
- struct ff_effect *effect)
+ const struct mlnx_effect_command *command)
{
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
int i;
unsigned long flags;
struct gc *gc = input_get_drvdata(dev);
struct gc_subdev *sdev = data;
unsigned char target = 1 << sdev->idx; /* select desired pin */
+ unsigned int cmd;
- if (effect->type == FF_RUMBLE) {
- struct ff_rumble_effect *rumble = &effect->u.rumble;
- unsigned int cmd =
- rumble->strong_magnitude || rumble->weak_magnitude ?
- GC_N64_CMD_01 : GC_N64_CMD_00;
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ cmd = (rumble_force->strong || rumble_force->weak) ? GC_N64_CMD_01 : GC_N64_CMD_00;
+ break;
+ case MLNX_STOP_RUMBLE:
+ cmd = GC_N64_CMD_00;
+ break;
+ default:
+ return -EINVAL;
+ }
- local_irq_save(flags);
+ local_irq_save(flags);
- /* Init Rumble - 0x03, 0x80, 0x01, (34)0x80 */
- gc_n64_send_command(gc, GC_N64_CMD_03, target);
+ /* Init Rumble - 0x03, 0x80, 0x01, (34)0x80 */
+ gc_n64_send_command(gc, GC_N64_CMD_03, target);
+ gc_n64_send_command(gc, GC_N64_CMD_80, target);
+ gc_n64_send_command(gc, GC_N64_CMD_01, target);
+ for (i = 0; i < 32; i++)
gc_n64_send_command(gc, GC_N64_CMD_80, target);
- gc_n64_send_command(gc, GC_N64_CMD_01, target);
- for (i = 0; i < 32; i++)
- gc_n64_send_command(gc, GC_N64_CMD_80, target);
- gc_n64_send_stop_bit(gc, target);
+ gc_n64_send_stop_bit(gc, target);
- udelay(GC_N64_DELAY);
-
- /* Now start or stop it - 0x03, 0xc0, 0zx1b, (32)0x01/0x00 */
- gc_n64_send_command(gc, GC_N64_CMD_03, target);
- gc_n64_send_command(gc, GC_N64_CMD_c0, target);
- gc_n64_send_command(gc, GC_N64_CMD_1b, target);
- for (i = 0; i < 32; i++)
- gc_n64_send_command(gc, cmd, target);
- gc_n64_send_stop_bit(gc, target);
+ udelay(GC_N64_DELAY);
- local_irq_restore(flags);
+ /* Now start or stop it - 0x03, 0xc0, 0zx1b, (32)0x01/0x00 */
+ gc_n64_send_command(gc, GC_N64_CMD_03, target);
+ gc_n64_send_command(gc, GC_N64_CMD_c0, target);
+ gc_n64_send_command(gc, GC_N64_CMD_1b, target);
+ for (i = 0; i < 32; i++)
+ gc_n64_send_command(gc, cmd, target);
+ gc_n64_send_stop_bit(gc, target);
- }
+ local_irq_restore(flags);
return 0;
}
@@ -317,7 +324,7 @@ static int __init gc_n64_init_ff(struct input_dev *dev, int i)
input_set_capability(dev, EV_FF, FF_RUMBLE);
- err = input_ff_create_memless(dev, sdev, gc_n64_play_effect);
+ err = input_ff_create_mlnx(dev, sdev, gc_n64_play_effect, FF_UPDATE_RATE);
if (err) {
kfree(sdev);
return err;
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 21/24] input: Port xpad to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (19 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 20/24] input: Port gamecon " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:57 ` [PATCH v3 22/24] hid: Port hid-lg2ff " Michal Malý
` (2 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port xpad to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/joystick/Kconfig | 2 +-
drivers/input/joystick/xpad.c | 125 +++++++++++++++++++++++------------------
2 files changed, 71 insertions(+), 56 deletions(-)
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 2dd3ba1..9f26e48 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -294,7 +294,7 @@ config JOYSTICK_XPAD
config JOYSTICK_XPAD_FF
bool "X-Box gamepad rumble support"
depends on JOYSTICK_XPAD && INPUT
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
---help---
Say Y here if you want to take advantage of xbox 360 rumble features.
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index 603fe0d..5d9c8a2 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -78,6 +78,7 @@
#include <linux/stat.h>
#include <linux/module.h>
#include <linux/usb/input.h>
+#include <linux/input/ff-memless-next.h>
#define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>"
#define DRIVER_DESC "X-Box pad driver"
@@ -97,6 +98,8 @@
#define XTYPE_XBOX360W 2
#define XTYPE_UNKNOWN 3
+#define FF_UPDATE_RATE 50
+
static bool dpad_to_buttons;
module_param(dpad_to_buttons, bool, S_IRUGO);
MODULE_PARM_DESC(dpad_to_buttons, "Map D-PAD to buttons rather than axes for unknown pads");
@@ -628,63 +631,75 @@ static void xpad_stop_output(struct usb_xpad *xpad) {}
#endif
#ifdef CONFIG_JOYSTICK_XPAD_FF
-static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+static int xpad_play_effect(struct input_dev *dev, void *data,
+ const struct mlnx_effect_command *command)
{
struct usb_xpad *xpad = input_get_drvdata(dev);
+ const struct mlnx_rumble_force *rumble_force = &command->u.rumble_force;
+ __u16 strong, weak;
- if (effect->type == FF_RUMBLE) {
- __u16 strong = effect->u.rumble.strong_magnitude;
- __u16 weak = effect->u.rumble.weak_magnitude;
-
- switch (xpad->xtype) {
-
- case XTYPE_XBOX:
- xpad->odata[0] = 0x00;
- xpad->odata[1] = 0x06;
- xpad->odata[2] = 0x00;
- xpad->odata[3] = strong / 256; /* left actuator */
- xpad->odata[4] = 0x00;
- xpad->odata[5] = weak / 256; /* right actuator */
- xpad->irq_out->transfer_buffer_length = 6;
-
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
-
- case XTYPE_XBOX360:
- xpad->odata[0] = 0x00;
- xpad->odata[1] = 0x08;
- xpad->odata[2] = 0x00;
- xpad->odata[3] = strong / 256; /* left actuator? */
- xpad->odata[4] = weak / 256; /* right actuator? */
- xpad->odata[5] = 0x00;
- xpad->odata[6] = 0x00;
- xpad->odata[7] = 0x00;
- xpad->irq_out->transfer_buffer_length = 8;
-
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
-
- case XTYPE_XBOX360W:
- xpad->odata[0] = 0x00;
- xpad->odata[1] = 0x01;
- xpad->odata[2] = 0x0F;
- xpad->odata[3] = 0xC0;
- xpad->odata[4] = 0x00;
- xpad->odata[5] = strong / 256;
- xpad->odata[6] = weak / 256;
- xpad->odata[7] = 0x00;
- xpad->odata[8] = 0x00;
- xpad->odata[9] = 0x00;
- xpad->odata[10] = 0x00;
- xpad->odata[11] = 0x00;
- xpad->irq_out->transfer_buffer_length = 12;
-
- return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
-
- default:
- dev_dbg(&xpad->dev->dev,
- "%s - rumble command sent to unsupported xpad type: %d\n",
- __func__, xpad->xtype);
- return -1;
- }
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ strong = rumble_force->strong;
+ weak = rumble_force->weak;
+ break;
+ case MLNX_STOP_RUMBLE:
+ strong = 0;
+ weak = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+
+ switch (xpad->xtype) {
+
+ case XTYPE_XBOX:
+ xpad->odata[0] = 0x00;
+ xpad->odata[1] = 0x06;
+ xpad->odata[2] = 0x00;
+ xpad->odata[3] = strong / 256; /* left actuator */
+ xpad->odata[4] = 0x00;
+ xpad->odata[5] = weak / 256; /* right actuator */
+ xpad->irq_out->transfer_buffer_length = 6;
+
+ return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+
+ case XTYPE_XBOX360:
+ xpad->odata[0] = 0x00;
+ xpad->odata[1] = 0x08;
+ xpad->odata[2] = 0x00;
+ xpad->odata[3] = strong / 256; /* left actuator? */
+ xpad->odata[4] = weak / 256; /* right actuator? */
+ xpad->odata[5] = 0x00;
+ xpad->odata[6] = 0x00;
+ xpad->odata[7] = 0x00;
+ xpad->irq_out->transfer_buffer_length = 8;
+
+ return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+
+ case XTYPE_XBOX360W:
+ xpad->odata[0] = 0x00;
+ xpad->odata[1] = 0x01;
+ xpad->odata[2] = 0x0F;
+ xpad->odata[3] = 0xC0;
+ xpad->odata[4] = 0x00;
+ xpad->odata[5] = strong / 256;
+ xpad->odata[6] = weak / 256;
+ xpad->odata[7] = 0x00;
+ xpad->odata[8] = 0x00;
+ xpad->odata[9] = 0x00;
+ xpad->odata[10] = 0x00;
+ xpad->odata[11] = 0x00;
+ xpad->irq_out->transfer_buffer_length = 12;
+
+ return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+
+ default:
+ dev_dbg(&xpad->dev->dev,
+ "%s - rumble command sent to unsupported xpad type: %d\n",
+ __func__, xpad->xtype);
+ return -1;
}
return 0;
@@ -697,7 +712,7 @@ static int xpad_init_ff(struct usb_xpad *xpad)
input_set_capability(xpad->dev, EV_FF, FF_RUMBLE);
- return input_ff_create_memless(xpad->dev, NULL, xpad_play_effect);
+ return input_ff_create_mlnx(xpad->dev, NULL, xpad_play_effect, FF_UPDATE_RATE);
}
#else
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 22/24] hid: Port hid-lg2ff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (20 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 21/24] input: Port xpad " Michal Malý
@ 2014-04-26 11:57 ` Michal Malý
2014-04-26 11:58 ` [PATCH v3 23/24] hid: Port hid-lg4ff " Michal Malý
2014-04-26 11:58 ` [PATCH v3 24/24] input: Replace ff-memless with ff-memless-next Michal Malý
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:57 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-lg2ff to ff-memless-next.
Clamp vibration magnitude to range <0x02; 0xfd> to prevent irregular
shaking of the vibration motors.
Signed-off-by: Elias Vanderstuyft <elias.vds@gmail.com>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-lg2ff.c | 65 ++++++++++++++++++++++++++++++++++---------------
2 files changed, 47 insertions(+), 20 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 97d2d8f..5e70519 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -381,7 +381,7 @@ config LOGITECH_FF
config LOGIRUMBLEPAD2_FF
bool "Logitech force feedback support (variant 2)"
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 RumblePad
diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c
index 0e3fb1a..6ab5327 100644
--- a/drivers/hid/hid-lg2ff.c
+++ b/drivers/hid/hid-lg2ff.c
@@ -22,42 +22,69 @@
#include <linux/input.h>
+#include <linux/input/ff-memless-next.h>
#include <linux/slab.h>
#include <linux/hid.h>
#include "hid-lg.h"
+#define FF_UPDATE_RATE 8
+
struct lg2ff_device {
struct hid_report *report;
};
-static int play_effect(struct input_dev *dev, void *data,
- struct ff_effect *effect)
+static int hid_lg2ff_start_rumble(struct hid_device *hid, struct hid_report *report,
+ const struct mlnx_rumble_force *rumble)
{
- struct hid_device *hid = input_get_drvdata(dev);
- struct lg2ff_device *lg2ff = data;
int weak, strong;
- strong = effect->u.rumble.strong_magnitude;
- weak = effect->u.rumble.weak_magnitude;
+#define CLAMP_QUIRK(x) do { if (x < 2) x = 2; else if (x > 0xfd) x = 0xfd; } \
+ while (0)
- if (weak || strong) {
- weak = weak * 0xff / 0xffff;
- strong = strong * 0xff / 0xffff;
+ /* Scale down from MLNX range */
+ strong = rumble->strong * 0xff / 0xffff;
+ weak = rumble->weak * 0xff / 0xffff;
+ CLAMP_QUIRK(weak);
+ CLAMP_QUIRK(strong);
- lg2ff->report->field[0]->value[0] = 0x51;
- lg2ff->report->field[0]->value[2] = weak;
- lg2ff->report->field[0]->value[4] = strong;
- } else {
- lg2ff->report->field[0]->value[0] = 0xf3;
- lg2ff->report->field[0]->value[2] = 0x00;
- lg2ff->report->field[0]->value[4] = 0x00;
- }
+ report->field[0]->value[0] = 0x51;
+ report->field[0]->value[2] = weak;
+ report->field[0]->value[4] = strong;
- hid_hw_request(hid, lg2ff->report, HID_REQ_SET_REPORT);
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
return 0;
}
+static int hid_lg2ff_stop_rumble(struct hid_device *hid, struct hid_report *report)
+{
+ report->field[0]->value[0] = 0xf3;
+ report->field[0]->value[2] = 0x00;
+ report->field[0]->value[4] = 0x00;
+
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+ return 0;
+}
+
+static int hid_lg2ff_control(struct input_dev *dev, void *data,
+ const struct mlnx_effect_command *command)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct lg2ff_device *lg2ff = data;
+
+ switch (command->cmd) {
+ case MLNX_START_RUMBLE:
+ return hid_lg2ff_start_rumble(hid, lg2ff->report, &command->u.rumble_force);
+ break;
+ case MLNX_STOP_RUMBLE:
+ return hid_lg2ff_stop_rumble(hid, lg2ff->report);
+ break;
+ default:
+ dbg_hid("Unsupported effect command");
+ return -EINVAL;
+ }
+}
+
int lg2ff_init(struct hid_device *hid)
{
struct lg2ff_device *lg2ff;
@@ -78,7 +105,7 @@ int lg2ff_init(struct hid_device *hid)
set_bit(FF_RUMBLE, dev->ffbit);
- error = input_ff_create_memless(dev, lg2ff, play_effect);
+ error = input_ff_create_mlnx(dev, lg2ff, hid_lg2ff_control, FF_UPDATE_RATE);
if (error) {
kfree(lg2ff);
return error;
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v3 23/24] hid: Port hid-lg4ff to ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (21 preceding siblings ...)
2014-04-26 11:57 ` [PATCH v3 22/24] hid: Port hid-lg2ff " Michal Malý
@ 2014-04-26 11:58 ` Michal Malý
2014-04-26 13:50 ` Elias Vanderstuyft
2014-04-26 11:58 ` [PATCH v3 24/24] input: Replace ff-memless with ff-memless-next Michal Malý
23 siblings, 1 reply; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:58 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Port hid-lg4ff to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/hid/Kconfig | 2 +-
drivers/hid/hid-lg4ff.c | 93 ++++++++++++++++++++++++++++++-------------------
2 files changed, 59 insertions(+), 36 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 5e70519..546ac4a 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -399,7 +399,7 @@ config LOGIG940_FF
config LOGIWHEELS_FF
bool "Logitech wheels configuration and force feedback support"
depends on HID_LOGITECH
- select INPUT_FF_MEMLESS
+ select INPUT_FF_MEMLESS_NEXT
default LOGITECH_FF
help
Say Y here if you want to enable force feedback and range setting
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index 24883b4..d629093 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -25,6 +25,7 @@
#include <linux/input.h>
+#include <linux/input/ff-memless-next.h>
#include <linux/usb.h>
#include <linux/hid.h>
@@ -45,6 +46,8 @@
#define G27_REV_MIN 0x38
#define G27_2_REV_MIN 0x39
+#define FF_UPDATE_RATE 8
+
#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
@@ -69,6 +72,13 @@ struct lg4ff_device_entry {
static const signed short lg4ff_wheel_effects[] = {
FF_CONSTANT,
+ FF_RAMP,
+ FF_PERIODIC,
+ FF_SQUARE,
+ FF_TRIANGLE,
+ FF_SINE,
+ FF_SAW_UP,
+ FF_SAW_DOWN,
FF_AUTOCENTER,
-1
};
@@ -184,47 +194,60 @@ int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
}
}
-static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+static int hid_lg4ff_start_combined(struct hid_device *hid, struct hid_report *report,
+ const struct mlnx_simple_force *force)
+{
+ __s32 *value = report->field[0]->value;
+ int scaled_x;
+
+ /* Scale down from MLNX range */
+ scaled_x = 0x80 - (force->x * 0xff / 0xffff);
+
+ value[0] = 0x11; /* Slot 1 */
+ value[1] = 0x08;
+ value[2] = scaled_x;
+ value[3] = 0x80;
+ value[4] = 0x00;
+ value[5] = 0x00;
+ value[6] = 0x00;
+
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+ return 0;
+}
+
+static int hid_lg4ff_stop_combined(struct hid_device *hid, struct hid_report *report)
+{
+ __s32 *value = report->field[0]->value;
+
+ value[0] = 0x13; /* Slot 1 */
+ value[1] = 0x00;
+ value[2] = 0x00;
+ value[3] = 0x00;
+ value[4] = 0x00;
+ value[5] = 0x00;
+ value[6] = 0x00;
+
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+ return 0;
+}
+
+static int hid_lg4ff_control(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);
- __s32 *value = report->field[0]->value;
- int x;
-
-#define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
-
- switch (effect->type) {
- case FF_CONSTANT:
- x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */
- CLAMP(x);
-
- if (x == 0x80) {
- /* De-activate force in slot-1*/
- value[0] = 0x13;
- value[1] = 0x00;
- value[2] = 0x00;
- value[3] = 0x00;
- value[4] = 0x00;
- value[5] = 0x00;
- value[6] = 0x00;
-
- hid_hw_request(hid, report, HID_REQ_SET_REPORT);
- return 0;
- }
-
- value[0] = 0x11; /* Slot 1 */
- value[1] = 0x08;
- value[2] = x;
- value[3] = 0x80;
- value[4] = 0x00;
- value[5] = 0x00;
- value[6] = 0x00;
- hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+ switch (command->cmd) {
+ case MLNX_START_COMBINED:
+ return hid_lg4ff_start_combined(hid, report, &command->u.simple_force);
+ break;
+ case MLNX_STOP_COMBINED:
+ return hid_lg4ff_stop_combined(hid, report);
break;
+ default:
+ dbg_hid("Unsupported effect command");
+ return -EINVAL;
}
- return 0;
}
/* Sends default autocentering command compatible with
@@ -610,7 +633,7 @@ int lg4ff_init(struct hid_device *hid)
for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
- error = input_ff_create_memless(dev, NULL, hid_lg4ff_play);
+ error = input_ff_create_mlnx(dev, (void *)NULL, hid_lg4ff_control, FF_UPDATE_RATE);
if (error)
return error;
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH v3 23/24] hid: Port hid-lg4ff to ff-memless-next
2014-04-26 11:58 ` [PATCH v3 23/24] hid: Port hid-lg4ff " Michal Malý
@ 2014-04-26 13:50 ` Elias Vanderstuyft
0 siblings, 0 replies; 30+ messages in thread
From: Elias Vanderstuyft @ 2014-04-26 13:50 UTC (permalink / raw)
To: Michal Malý
Cc: linux-input, linux-kernel, Dmitry Torokhov, Jiri Kosina,
Anssi Hannula, Simon Wood
On Sat, Apr 26, 2014 at 1:58 PM, Michal Malý
<madcatxster@devoid-pointer.net> wrote:
> Port hid-lg4ff to ff-memless-next
>
> Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
> ---
> drivers/hid/Kconfig | 2 +-
> drivers/hid/hid-lg4ff.c | 93 ++++++++++++++++++++++++++++++-------------------
> 2 files changed, 59 insertions(+), 36 deletions(-)
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 5e70519..546ac4a 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -399,7 +399,7 @@ config LOGIG940_FF
> config LOGIWHEELS_FF
> bool "Logitech wheels configuration and force feedback support"
> depends on HID_LOGITECH
> - select INPUT_FF_MEMLESS
> + select INPUT_FF_MEMLESS_NEXT
> default LOGITECH_FF
> help
> Say Y here if you want to enable force feedback and range setting
> diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
> index 24883b4..d629093 100644
> --- a/drivers/hid/hid-lg4ff.c
> +++ b/drivers/hid/hid-lg4ff.c
> @@ -25,6 +25,7 @@
>
>
> #include <linux/input.h>
> +#include <linux/input/ff-memless-next.h>
> #include <linux/usb.h>
> #include <linux/hid.h>
>
> @@ -45,6 +46,8 @@
> #define G27_REV_MIN 0x38
> #define G27_2_REV_MIN 0x39
>
> +#define FF_UPDATE_RATE 8
> +
> #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
>
> static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
> @@ -69,6 +72,13 @@ struct lg4ff_device_entry {
>
> static const signed short lg4ff_wheel_effects[] = {
> FF_CONSTANT,
> + FF_RAMP,
> + FF_PERIODIC,
> + FF_SQUARE,
> + FF_TRIANGLE,
> + FF_SINE,
> + FF_SAW_UP,
> + FF_SAW_DOWN,
> FF_AUTOCENTER,
> -1
> };
> @@ -184,47 +194,60 @@ int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
> }
> }
>
> -static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
> +static int hid_lg4ff_start_combined(struct hid_device *hid, struct hid_report *report,
> + const struct mlnx_simple_force *force)
> +{
> + __s32 *value = report->field[0]->value;
> + int scaled_x;
> +
> + /* Scale down from MLNX range */
> + scaled_x = 0x80 - (force->x * 0xff / 0xffff);
> +
> + value[0] = 0x11; /* Slot 1 */
> + value[1] = 0x08;
> + value[2] = scaled_x;
> + value[3] = 0x80;
> + value[4] = 0x00;
> + value[5] = 0x00;
> + value[6] = 0x00;
> +
> + hid_hw_request(hid, report, HID_REQ_SET_REPORT);
> + return 0;
> +}
> +
> +static int hid_lg4ff_stop_combined(struct hid_device *hid, struct hid_report *report)
> +{
> + __s32 *value = report->field[0]->value;
> +
> + value[0] = 0x13; /* Slot 1 */
> + value[1] = 0x00;
> + value[2] = 0x00;
> + value[3] = 0x00;
> + value[4] = 0x00;
> + value[5] = 0x00;
> + value[6] = 0x00;
> +
> + hid_hw_request(hid, report, HID_REQ_SET_REPORT);
> + return 0;
> +}
> +
> +static int hid_lg4ff_control(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);
> - __s32 *value = report->field[0]->value;
> - int x;
> -
> -#define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
> -
> - switch (effect->type) {
> - case FF_CONSTANT:
> - x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */
> - CLAMP(x);
> -
> - if (x == 0x80) {
> - /* De-activate force in slot-1*/
> - value[0] = 0x13;
> - value[1] = 0x00;
> - value[2] = 0x00;
> - value[3] = 0x00;
> - value[4] = 0x00;
> - value[5] = 0x00;
> - value[6] = 0x00;
> -
> - hid_hw_request(hid, report, HID_REQ_SET_REPORT);
> - return 0;
> - }
> -
> - value[0] = 0x11; /* Slot 1 */
> - value[1] = 0x08;
> - value[2] = x;
> - value[3] = 0x80;
> - value[4] = 0x00;
> - value[5] = 0x00;
> - value[6] = 0x00;
>
> - hid_hw_request(hid, report, HID_REQ_SET_REPORT);
> + switch (command->cmd) {
> + case MLNX_START_COMBINED:
> + return hid_lg4ff_start_combined(hid, report, &command->u.simple_force);
> + break;
> + case MLNX_STOP_COMBINED:
> + return hid_lg4ff_stop_combined(hid, report);
> break;
> + default:
> + dbg_hid("Unsupported effect command");
> + return -EINVAL;
> }
> - return 0;
> }
>
> /* Sends default autocentering command compatible with
> @@ -610,7 +633,7 @@ int lg4ff_init(struct hid_device *hid)
> for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
> set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
>
> - error = input_ff_create_memless(dev, NULL, hid_lg4ff_play);
> + error = input_ff_create_mlnx(dev, (void *)NULL, hid_lg4ff_control, FF_UPDATE_RATE);
>
> if (error)
> return error;
> --
> 1.9.2
>
You can add Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v3 24/24] input: Replace ff-memless with ff-memless-next
2014-04-26 11:57 [PATCH v3 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless Michal Malý
` (22 preceding siblings ...)
2014-04-26 11:58 ` [PATCH v3 23/24] hid: Port hid-lg4ff " Michal Malý
@ 2014-04-26 11:58 ` Michal Malý
23 siblings, 0 replies; 30+ messages in thread
From: Michal Malý @ 2014-04-26 11:58 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
Replace ff-memless with ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
---
drivers/input/Kconfig | 13 +-
drivers/input/Makefile | 1 -
drivers/input/ff-memless.c | 547 ---------------------------------------------
include/linux/input.h | 3 -
4 files changed, 1 insertion(+), 563 deletions(-)
delete mode 100644 drivers/input/ff-memless.c
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index 3780962..061ae3c 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -25,7 +25,7 @@ config INPUT
if INPUT
-config INPUT_FF_MEMLESS
+config INPUT_FF_MEMLESS_NEXT
tristate "Support for memoryless force-feedback devices"
help
Say Y here if you have memoryless force-feedback input device
@@ -36,17 +36,6 @@ config INPUT_FF_MEMLESS
If unsure, say N.
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
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index b4f11f5..f7ae055 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -7,7 +7,6 @@
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
diff --git a/drivers/input/ff-memless.c b/drivers/input/ff-memless.c
deleted file mode 100644
index 74c0d8c..0000000
--- a/drivers/input/ff-memless.c
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Force feedback support for memoryless devices
- *
- * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com>
- * Copyright (c) 2006 Dmitry Torokhov <dtor@mail.ru>
- */
-
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-/* #define DEBUG */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/slab.h>
-#include <linux/input.h>
-#include <linux/module.h>
-#include <linux/mutex.h>
-#include <linux/spinlock.h>
-#include <linux/jiffies.h>
-#include <linux/fixp-arith.h>
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Anssi Hannula <anssi.hannula@gmail.com>");
-MODULE_DESCRIPTION("Force feedback support for memoryless devices");
-
-/* Number of effects handled with memoryless devices */
-#define FF_MEMLESS_EFFECTS 16
-
-/* Envelope update interval in ms */
-#define FF_ENVELOPE_INTERVAL 50
-
-#define FF_EFFECT_STARTED 0
-#define FF_EFFECT_PLAYING 1
-#define FF_EFFECT_ABORTING 2
-
-struct ml_effect_state {
- struct ff_effect *effect;
- unsigned long flags; /* effect state (STARTED, PLAYING, etc) */
- int count; /* loop count of the effect */
- unsigned long play_at; /* start time */
- unsigned long stop_at; /* stop time */
- unsigned long adj_at; /* last time the effect was sent */
-};
-
-struct ml_device {
- void *private;
- struct ml_effect_state states[FF_MEMLESS_EFFECTS];
- int gain;
- struct timer_list timer;
- struct input_dev *dev;
-
- int (*play_effect)(struct input_dev *dev, void *data,
- struct ff_effect *effect);
-};
-
-static const struct ff_envelope *get_envelope(const struct ff_effect *effect)
-{
- static const struct ff_envelope empty_envelope;
-
- switch (effect->type) {
- case FF_PERIODIC:
- return &effect->u.periodic.envelope;
-
- case FF_CONSTANT:
- return &effect->u.constant.envelope;
-
- default:
- return &empty_envelope;
- }
-}
-
-/*
- * Check for the next time envelope requires an update on memoryless devices
- */
-static unsigned long calculate_next_time(struct ml_effect_state *state)
-{
- const struct ff_envelope *envelope = get_envelope(state->effect);
- unsigned long attack_stop, fade_start, next_fade;
-
- if (envelope->attack_length) {
- attack_stop = state->play_at +
- msecs_to_jiffies(envelope->attack_length);
- if (time_before(state->adj_at, attack_stop))
- return state->adj_at +
- msecs_to_jiffies(FF_ENVELOPE_INTERVAL);
- }
-
- if (state->effect->replay.length) {
- if (envelope->fade_length) {
- /* check when fading should start */
- fade_start = state->stop_at -
- msecs_to_jiffies(envelope->fade_length);
-
- if (time_before(state->adj_at, fade_start))
- return fade_start;
-
- /* already fading, advance to next checkpoint */
- next_fade = state->adj_at +
- msecs_to_jiffies(FF_ENVELOPE_INTERVAL);
- if (time_before(next_fade, state->stop_at))
- return next_fade;
- }
-
- return state->stop_at;
- }
-
- return state->play_at;
-}
-
-static void ml_schedule_timer(struct ml_device *ml)
-{
- struct ml_effect_state *state;
- unsigned long now = jiffies;
- unsigned long earliest = 0;
- unsigned long next_at;
- int events = 0;
- int i;
-
- pr_debug("calculating next timer\n");
-
- for (i = 0; i < FF_MEMLESS_EFFECTS; i++) {
-
- state = &ml->states[i];
-
- if (!test_bit(FF_EFFECT_STARTED, &state->flags))
- continue;
-
- if (test_bit(FF_EFFECT_PLAYING, &state->flags))
- next_at = calculate_next_time(state);
- else
- next_at = state->play_at;
-
- if (time_before_eq(now, next_at) &&
- (++events == 1 || time_before(next_at, earliest)))
- earliest = next_at;
- }
-
- if (!events) {
- pr_debug("no actions\n");
- del_timer(&ml->timer);
- } else {
- pr_debug("timer set\n");
- mod_timer(&ml->timer, earliest);
- }
-}
-
-/*
- * Apply an envelope to a value
- */
-static int apply_envelope(struct ml_effect_state *state, int value,
- struct ff_envelope *envelope)
-{
- struct ff_effect *effect = state->effect;
- unsigned long now = jiffies;
- int time_from_level;
- int time_of_envelope;
- int envelope_level;
- int difference;
-
- if (envelope->attack_length &&
- time_before(now,
- state->play_at + msecs_to_jiffies(envelope->attack_length))) {
- pr_debug("value = 0x%x, attack_level = 0x%x\n",
- value, envelope->attack_level);
- time_from_level = jiffies_to_msecs(now - state->play_at);
- time_of_envelope = envelope->attack_length;
- envelope_level = min_t(u16, envelope->attack_level, 0x7fff);
-
- } else if (envelope->fade_length && effect->replay.length &&
- time_after(now,
- state->stop_at - msecs_to_jiffies(envelope->fade_length)) &&
- time_before(now, state->stop_at)) {
- time_from_level = jiffies_to_msecs(state->stop_at - now);
- time_of_envelope = envelope->fade_length;
- envelope_level = min_t(u16, envelope->fade_level, 0x7fff);
- } else
- return value;
-
- difference = abs(value) - envelope_level;
-
- pr_debug("difference = %d\n", difference);
- pr_debug("time_from_level = 0x%x\n", time_from_level);
- pr_debug("time_of_envelope = 0x%x\n", time_of_envelope);
-
- difference = difference * time_from_level / time_of_envelope;
-
- pr_debug("difference = %d\n", difference);
-
- return value < 0 ?
- -(difference + envelope_level) : (difference + envelope_level);
-}
-
-/*
- * Return the type the effect has to be converted into (memless devices)
- */
-static int get_compatible_type(struct ff_device *ff, int effect_type)
-{
-
- if (test_bit(effect_type, ff->ffbit))
- return effect_type;
-
- if (effect_type == FF_PERIODIC && test_bit(FF_RUMBLE, ff->ffbit))
- return FF_RUMBLE;
-
- pr_err("invalid type in get_compatible_type()\n");
-
- return 0;
-}
-
-/*
- * Only left/right direction should be used (under/over 0x8000) for
- * forward/reverse motor direction (to keep calculation fast & simple).
- */
-static u16 ml_calculate_direction(u16 direction, u16 force,
- u16 new_direction, u16 new_force)
-{
- if (!force)
- return new_direction;
- if (!new_force)
- return direction;
- return (((u32)(direction >> 1) * force +
- (new_direction >> 1) * new_force) /
- (force + new_force)) << 1;
-}
-
-/*
- * Combine two effects and apply gain.
- */
-static void ml_combine_effects(struct ff_effect *effect,
- struct ml_effect_state *state,
- int gain)
-{
- struct ff_effect *new = state->effect;
- unsigned int strong, weak, i;
- int x, y;
- fixp_t level;
-
- switch (new->type) {
- case FF_CONSTANT:
- i = new->direction * 360 / 0xffff;
- level = fixp_new16(apply_envelope(state,
- new->u.constant.level,
- &new->u.constant.envelope));
- x = fixp_mult(fixp_sin(i), level) * gain / 0xffff;
- y = fixp_mult(-fixp_cos(i), level) * gain / 0xffff;
- /*
- * here we abuse ff_ramp to hold x and y of constant force
- * If in future any driver wants something else than x and y
- * in s8, this should be changed to something more generic
- */
- effect->u.ramp.start_level =
- clamp_val(effect->u.ramp.start_level + x, -0x80, 0x7f);
- effect->u.ramp.end_level =
- clamp_val(effect->u.ramp.end_level + y, -0x80, 0x7f);
- break;
-
- case FF_RUMBLE:
- strong = (u32)new->u.rumble.strong_magnitude * gain / 0xffff;
- weak = (u32)new->u.rumble.weak_magnitude * gain / 0xffff;
-
- if (effect->u.rumble.strong_magnitude + strong)
- effect->direction = ml_calculate_direction(
- effect->direction,
- effect->u.rumble.strong_magnitude,
- new->direction, strong);
- else if (effect->u.rumble.weak_magnitude + weak)
- effect->direction = ml_calculate_direction(
- effect->direction,
- effect->u.rumble.weak_magnitude,
- new->direction, weak);
- else
- effect->direction = 0;
- effect->u.rumble.strong_magnitude =
- min(strong + effect->u.rumble.strong_magnitude,
- 0xffffU);
- effect->u.rumble.weak_magnitude =
- min(weak + effect->u.rumble.weak_magnitude, 0xffffU);
- break;
-
- case FF_PERIODIC:
- i = apply_envelope(state, abs(new->u.periodic.magnitude),
- &new->u.periodic.envelope);
-
- /* here we also scale it 0x7fff => 0xffff */
- i = i * gain / 0x7fff;
-
- if (effect->u.rumble.strong_magnitude + i)
- effect->direction = ml_calculate_direction(
- effect->direction,
- effect->u.rumble.strong_magnitude,
- new->direction, i);
- else
- effect->direction = 0;
- effect->u.rumble.strong_magnitude =
- min(i + effect->u.rumble.strong_magnitude, 0xffffU);
- effect->u.rumble.weak_magnitude =
- min(i + effect->u.rumble.weak_magnitude, 0xffffU);
- break;
-
- default:
- pr_err("invalid type in ml_combine_effects()\n");
- break;
- }
-
-}
-
-
-/*
- * Because memoryless devices have only one effect per effect type active
- * at one time we have to combine multiple effects into one
- */
-static int ml_get_combo_effect(struct ml_device *ml,
- unsigned long *effect_handled,
- struct ff_effect *combo_effect)
-{
- struct ff_effect *effect;
- struct ml_effect_state *state;
- int effect_type;
- int i;
-
- memset(combo_effect, 0, sizeof(struct ff_effect));
-
- for (i = 0; i < FF_MEMLESS_EFFECTS; i++) {
- if (__test_and_set_bit(i, effect_handled))
- continue;
-
- state = &ml->states[i];
- effect = state->effect;
-
- if (!test_bit(FF_EFFECT_STARTED, &state->flags))
- continue;
-
- if (time_before(jiffies, state->play_at))
- continue;
-
- /*
- * here we have started effects that are either
- * currently playing (and may need be aborted)
- * or need to start playing.
- */
- effect_type = get_compatible_type(ml->dev->ff, effect->type);
- if (combo_effect->type != effect_type) {
- if (combo_effect->type != 0) {
- __clear_bit(i, effect_handled);
- continue;
- }
- combo_effect->type = effect_type;
- }
-
- if (__test_and_clear_bit(FF_EFFECT_ABORTING, &state->flags)) {
- __clear_bit(FF_EFFECT_PLAYING, &state->flags);
- __clear_bit(FF_EFFECT_STARTED, &state->flags);
- } else if (effect->replay.length &&
- time_after_eq(jiffies, state->stop_at)) {
-
- __clear_bit(FF_EFFECT_PLAYING, &state->flags);
-
- if (--state->count <= 0) {
- __clear_bit(FF_EFFECT_STARTED, &state->flags);
- } else {
- state->play_at = jiffies +
- msecs_to_jiffies(effect->replay.delay);
- state->stop_at = state->play_at +
- msecs_to_jiffies(effect->replay.length);
- }
- } else {
- __set_bit(FF_EFFECT_PLAYING, &state->flags);
- state->adj_at = jiffies;
- ml_combine_effects(combo_effect, state, ml->gain);
- }
- }
-
- return combo_effect->type != 0;
-}
-
-static void ml_play_effects(struct ml_device *ml)
-{
- struct ff_effect effect;
- DECLARE_BITMAP(handled_bm, FF_MEMLESS_EFFECTS);
-
- memset(handled_bm, 0, sizeof(handled_bm));
-
- while (ml_get_combo_effect(ml, handled_bm, &effect))
- ml->play_effect(ml->dev, ml->private, &effect);
-
- ml_schedule_timer(ml);
-}
-
-static void ml_effect_timer(unsigned long timer_data)
-{
- struct input_dev *dev = (struct input_dev *)timer_data;
- struct ml_device *ml = dev->ff->private;
- unsigned long flags;
-
- pr_debug("timer: updating effects\n");
-
- spin_lock_irqsave(&dev->event_lock, flags);
- ml_play_effects(ml);
- spin_unlock_irqrestore(&dev->event_lock, flags);
-}
-
-/*
- * Sets requested gain for FF effects. Called with dev->event_lock held.
- */
-static void ml_ff_set_gain(struct input_dev *dev, u16 gain)
-{
- struct ml_device *ml = dev->ff->private;
- int i;
-
- ml->gain = gain;
-
- for (i = 0; i < FF_MEMLESS_EFFECTS; i++)
- __clear_bit(FF_EFFECT_PLAYING, &ml->states[i].flags);
-
- ml_play_effects(ml);
-}
-
-/*
- * Start/stop specified FF effect. Called with dev->event_lock held.
- */
-static int ml_ff_playback(struct input_dev *dev, int effect_id, int value)
-{
- struct ml_device *ml = dev->ff->private;
- struct ml_effect_state *state = &ml->states[effect_id];
-
- if (value > 0) {
- pr_debug("initiated play\n");
-
- __set_bit(FF_EFFECT_STARTED, &state->flags);
- state->count = value;
- state->play_at = jiffies +
- msecs_to_jiffies(state->effect->replay.delay);
- state->stop_at = state->play_at +
- msecs_to_jiffies(state->effect->replay.length);
- state->adj_at = state->play_at;
-
- } else {
- pr_debug("initiated stop\n");
-
- if (test_bit(FF_EFFECT_PLAYING, &state->flags))
- __set_bit(FF_EFFECT_ABORTING, &state->flags);
- else
- __clear_bit(FF_EFFECT_STARTED, &state->flags);
- }
-
- ml_play_effects(ml);
-
- return 0;
-}
-
-static int ml_ff_upload(struct input_dev *dev,
- struct ff_effect *effect, struct ff_effect *old)
-{
- struct ml_device *ml = dev->ff->private;
- struct ml_effect_state *state = &ml->states[effect->id];
-
- spin_lock_irq(&dev->event_lock);
-
- if (test_bit(FF_EFFECT_STARTED, &state->flags)) {
- __clear_bit(FF_EFFECT_PLAYING, &state->flags);
- state->play_at = jiffies +
- msecs_to_jiffies(state->effect->replay.delay);
- state->stop_at = state->play_at +
- msecs_to_jiffies(state->effect->replay.length);
- state->adj_at = state->play_at;
- ml_schedule_timer(ml);
- }
-
- spin_unlock_irq(&dev->event_lock);
-
- return 0;
-}
-
-static void ml_ff_destroy(struct ff_device *ff)
-{
- struct ml_device *ml = ff->private;
-
- kfree(ml->private);
-}
-
-/**
- * input_ff_create_memless() - create memoryless force-feedback device
- * @dev: input device supporting force-feedback
- * @data: driver-specific data to be passed into @play_effect
- * @play_effect: driver-specific method for playing FF effect
- */
-int input_ff_create_memless(struct input_dev *dev, void *data,
- int (*play_effect)(struct input_dev *, void *, struct ff_effect *))
-{
- struct ml_device *ml;
- struct ff_device *ff;
- int error;
- int i;
-
- ml = kzalloc(sizeof(struct ml_device), GFP_KERNEL);
- if (!ml)
- return -ENOMEM;
-
- ml->dev = dev;
- ml->private = data;
- ml->play_effect = play_effect;
- ml->gain = 0xffff;
- setup_timer(&ml->timer, ml_effect_timer, (unsigned long)dev);
-
- set_bit(FF_GAIN, dev->ffbit);
-
- error = input_ff_create(dev, FF_MEMLESS_EFFECTS);
- if (error) {
- kfree(ml);
- return error;
- }
-
- ff = dev->ff;
- ff->private = ml;
- ff->upload = ml_ff_upload;
- ff->playback = ml_ff_playback;
- ff->set_gain = ml_ff_set_gain;
- ff->destroy = ml_ff_destroy;
-
- /* we can emulate periodic effects with RUMBLE */
- if (test_bit(FF_RUMBLE, ff->ffbit)) {
- set_bit(FF_PERIODIC, dev->ffbit);
- set_bit(FF_SINE, dev->ffbit);
- set_bit(FF_TRIANGLE, dev->ffbit);
- set_bit(FF_SQUARE, dev->ffbit);
- }
-
- for (i = 0; i < FF_MEMLESS_EFFECTS; i++)
- ml->states[i].effect = &ff->effects[i];
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(input_ff_create_memless);
diff --git a/include/linux/input.h b/include/linux/input.h
index 82ce323..0c6c3f1 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -528,7 +528,4 @@ int input_ff_event(struct input_dev *dev, unsigned int type, unsigned int code,
int input_ff_upload(struct input_dev *dev, struct ff_effect *effect, struct file *file);
int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file);
-int input_ff_create_memless(struct input_dev *dev, void *data,
- int (*play_effect)(struct input_dev *, void *, struct ff_effect *));
-
#endif
--
1.9.2
^ permalink raw reply related [flat|nested] 30+ messages in thread