* [PATCH v4 19/24] hid: Port hid-zpff to ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
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
* [PATCH v4 20/24] input: Port gamecon to ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
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
* [PATCH v4 21/24] input: Port xpad to ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
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
* [PATCH v4 22/24] hid: Port hid-lg2ff to ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
- 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
* [PATCH v4 23/24] hid: Port hid-lg4ff to ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
Port hid-lg4ff to ff-memless-next
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
Tested-by: Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>
---
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
* [PATCH v4 24/24] input: Replace ff-memless with ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
Replace ff-memless with ff-memless-next
ff-memless-next is a module for handling force feedback devices that
have no FFB effect memory of their own. It is based on the ff-memless
module but it has been largerly extended. Notable changes include:
- Support for all force feedback effects currently defined by Microsoft
DirectInput.
- Improved emulation of periodic and rumble effects in case either of
those is not supported by a device.
- New kernel API to interface with HW-specific drivers.
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
* [PATCH v4 00/24] input: Introduce ff-memless-next as an improved replacement for ff-memless
From: Michal Malý @ 2014-04-26 15:01 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
ff-memless-next (MLNX) is a largely improved version of the current ff-memless
(FFML) driver. MLNX supports all force feedback effects currently available in
the Linux force feedback userspace API. All effects are handled in accordance
with Microsoft's DirectInput/XInput. Most notable changes include support for
conditional effects, proper handling of all periodic waveforms and improved
emulation of rumble effects through periodic effects. MLNX also uses its own
kernel API to pass processed effects to hardware-specific drivers instead of
abusing "ff_effect" struct. The API is documented in the respective header
file.
MLNX has been expanded to be a direct replacement for FFML.
Support for FF_PERIODIC and FF_RAMP has been added to all devices that
support FF_CONSTANT as a part of the port to the new API.
This patch series:
1) Adds "ff-memless-next" module [1]
2) Ports all hardware-specific drivers to MLNX's API [2-23]
3) Removes FFML and replaces it with MLNX [24]
Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net>
v4:
- Add a summary of changes between MLNX and FFML to the last patch
- Remove a stale empty line in hid-sony.c
- Add "Tested-by: Elias Vanderstuyft <elias.vds@gmail.com>" to hid-lg4ff patch.
v3:
- Rebase against latest linux-next. Fixes conflict in hid-sony.c and
max8997_haptic.c
- Updated documentation in ff-memless-next.h. The documentation now describes
parameters of the callback function and specifically mentions that
HW-specific drivers must not keep a reference to mlnx_effect_command struct
to which a pointer is passed in the callback function.
- Fix a minor brace inconsistency in hid-lgff
I believe that all concerns regarding v2 have been resolved as false alarms.
v2:
- Add missing msecs to jiffies conversion in ff-memless-next
- lgff: Properly convert force on Y axis from MLNX to device range
Support periodic effects for "joystick_ac" device class
- lg3ff: Properly convert forces from MLNX to device range
- Very minor coding style issues fixed
Michal Malý (24):
Add ff-memless-next module
Port arizona-haptics to ff-memless-next
Port twl4030-vibra to ff-memless-next
Port twl6040-vibra to ff-memless-next
Port max8997_haptic to ff-memless-next
Port pm8xxx-vibrator to ff-memless-next
Port hid-axff to ff-memless-next
Port hid-emsff to ff-memless-next
Port hid-dr to ff-memless-next
Port hid-gaff to ff-memless-next
Port hid-holtekff to ff-memless-next
Port hid-lgff to ff-memless-next
Port hid-lg3ff to ff-memless-next
Port hid-pl to ff-memless-next
Port hid-sjoy to ff-memless-next
Port hid-sony to ff-memless-next
Port hid-tmff to ff-memless-next
Port hid-wiimote-modules to ff-memless-next
Port hid-zpff to ff-memless-next
Port gamecon to ff-memless-next
Port xpad to ff-memless-next
Port hid-lg2ff to ff-memless-next
Port hid-lg4ff to ff-memless-next
Replace ff-memless with ff-memless-next
drivers/hid/Kconfig | 30 +-
drivers/hid/hid-axff.c | 32 +-
drivers/hid/hid-dr.c | 59 +-
drivers/hid/hid-emsff.c | 38 +-
drivers/hid/hid-gaff.c | 32 +-
drivers/hid/hid-holtekff.c | 47 +-
drivers/hid/hid-lg2ff.c | 65 ++-
drivers/hid/hid-lg3ff.c | 60 +-
drivers/hid/hid-lg4ff.c | 93 +--
drivers/hid/hid-lgff.c | 70 ++-
drivers/hid/hid-pl.c | 38 +-
drivers/hid/hid-sjoy.c | 35 +-
drivers/hid/hid-sony.c | 23 +-
drivers/hid/hid-tmff.c | 83 +--
drivers/hid/hid-wiimote-modules.c | 74 ++-
drivers/hid/hid-zpff.c | 30 +-
drivers/input/Kconfig | 4 +-
drivers/input/Makefile | 2 +-
drivers/input/ff-memless-next.c | 1037 +++++++++++++++++++++++++++++++++
drivers/input/ff-memless.c | 547 -----------------
drivers/input/joystick/Kconfig | 4 +-
drivers/input/joystick/gamecon.c | 57 +-
drivers/input/joystick/xpad.c | 125 ++--
drivers/input/misc/Kconfig | 10 +-
drivers/input/misc/arizona-haptics.c | 39 +-
drivers/input/misc/max8997_haptic.c | 25 +-
drivers/input/misc/pm8xxx-vibrator.c | 28 +-
drivers/input/misc/twl4030-vibra.c | 31 +-
drivers/input/misc/twl6040-vibra.c | 27 +-
include/linux/input.h | 3 -
include/linux/input/ff-memless-next.h | 162 +++++
31 files changed, 1951 insertions(+), 959 deletions(-)
create mode 100644 drivers/input/ff-memless-next.c
delete mode 100644 drivers/input/ff-memless.c
create mode 100644 include/linux/input/ff-memless-next.h
--
1.9.2
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH v4 01/24] input: Add ff-memless-next module
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
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
* [PATCH v4 07/24] hid: Port hid-axff to ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
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
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v4 09/24] hid: Port hid-dr to ff-memless-next
From: Michal Malý @ 2014-04-26 15:02 UTC (permalink / raw)
To: linux-input, linux-kernel
Cc: dmitry.torokhov, jkosina, elias.vds, anssi.hannula, simon,
Michal Malý
In-Reply-To: <1398524543-15012-1-git-send-email-madcatxster@devoid-pointer.net>
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
--
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
* Re: [PATCH v3 01/24] input: Add ff-memless-next module
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
In-Reply-To: <20140426150701.8dc9dde837757b966d6c0e27@ao2.it>
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
* Re: [PATCH v3 16/24] hid: Port hid-sony to ff-memless-next
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
In-Reply-To: <20140426150944.b119fd7f8c4334353b77d3d2@ao2.it>
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
* Re: [PATCH v2 09/24] input: Port hid-dr to ff-memless-next
From: Oliver Neukum @ 2014-04-27 8:22 UTC (permalink / raw)
To: Michal Malý
Cc: linux-input, linux-kernel, dmitry.torokhov, jkosina, elias.vds,
anssi.hannula, simon
In-Reply-To: <2156054.MZkXeC543k@sigyn>
On Thu, 2014-04-24 at 12:32 +0200, Michal Malý wrote:
> On Wednesday 23 of April 2014 15:41:03 Oliver Neukum wrote:
> > On Tue, 2014-04-22 at 15:59 +0200, Michal Malý wrote:
> > > 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;
> >
> > This looks like an endianness bug.
>
> I don't have a big endian machine to check but why would this be an endianness
> issue? We're dealing with values all the time here, not addresses so I'd
> expect the 'weak' and 'strong' values to be truncated if they won't fit into
> byte. Division done beforehand makes sure that the values are within <0; 255>
> range. As far as I can see this is quite common in the HID and Input code. Am
> I missing something here?
Sorry, I thought you were writing to 16bit variables.
Regards
Oliver
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v3 2/3] Input: gpio_keys - convert to use devm_*
From: Andy Shevchenko @ 2014-04-28 9:40 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Linus Walleij, linux-input, linux-kernel
In-Reply-To: <20140426044346.GB27425@core.coreip.homeip.net>
On Fri, 2014-04-25 at 21:43 -0700, Dmitry Torokhov wrote:
> On Fri, Apr 25, 2014 at 07:22:16PM +0300, Andy Shevchenko wrote:
> > This makes the error handling much more simpler than open-coding everything and
> > in addition makes the probe function smaller an tidier.
[]
> > - pdata = kzalloc(sizeof(*pdata) + nbuttons * (sizeof *button),
> > - GFP_KERNEL);
> > - if (!pdata) {
> > - error = -ENOMEM;
> > - goto err_out;
> > - }
> > + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
> > + if (!pdata)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + pdata->buttons = devm_kcalloc(dev, nbuttons, sizeof (*button),
> > + GFP_KERNEL);
> > + if (!pdata->buttons)
> > + return ERR_PTR(-ENOMEM);
>
> Why are we splitting the allocation in 2?
Just for logical distinguishing, but I'm okay with one allocation.
--
Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Intel Finland Oy
^ permalink raw reply
* Re: [PATCH v3 1/3] Input: gpio_keys - use dev instead of pdev in gpio_keys_setup_key()
From: Andy Shevchenko @ 2014-04-28 9:42 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Linus Walleij, linux-input, linux-kernel
In-Reply-To: <20140425212112.GA16783@core.coreip.homeip.net>
On Fri, 2014-04-25 at 14:21 -0700, Dmitry Torokhov wrote:
> On Fri, Apr 25, 2014 at 07:22:15PM +0300, Andy Shevchenko wrote:
> > The platform device is not used in gpio_keys_setup_key(). This patch
> > substitutes it by struct device.
>
> And the benefit of this is...?
Probably no benefit for now. Let's drop it for good.
--
Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Intel Finland Oy
^ permalink raw reply
* Re: [PATCH v4] HID: add missing hid usages
From: Jiri Kosina @ 2014-04-28 15:00 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Olivier Gay, linux-input, Nestor Lopez Casado, Mathieu Meisser
In-Reply-To: <20140425183043.GC12355@core.coreip.homeip.net>
On Fri, 25 Apr 2014, Dmitry Torokhov wrote:
> > Add some missing hid usages from consumer page, add
> > some display brightness control usages from approved hid usage
> > table request HUTTR41:
> > http://www.usb.org/developers/hidpage/HUTRR41.pdf
> > and add voice command usage from approved request HUTTR45:
> > http://www.usb.org/developers/hidpage/Voice_Command_Usage.pdf
> >
> > Signed-off-by: Olivier Gay <ogay@logitech.com>
> > Signed-off-by: Mathieu Meisser <mmeisser@logitech.com>
>
> Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
>
> with a small nit below. Probably Jiri can fix it up without resubmitting.
Absolutely; applied now.
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v4] HID: add missing hid usages
From: Olivier Gay @ 2014-04-28 15:51 UTC (permalink / raw)
To: Jiri Kosina
Cc: Dmitry Torokhov, linux-input, Nestor Lopez Casado,
Mathieu Meisser
In-Reply-To: <alpine.LNX.2.00.1404281659140.8903@pobox.suse.cz>
On Mon, Apr 28, 2014 at 5:00 PM, Jiri Kosina <jkosina@suse.cz> wrote:
> > with a small nit below. Probably Jiri can fix it up without resubmitting.
>
> Absolutely; applied now.
Great, thank you both!
Olivier
^ permalink raw reply
* Re: [PATCH v2] input: gpio-beeper: Simplify GPIO handling
From: Dmitry Torokhov @ 2014-04-29 2:21 UTC (permalink / raw)
To: Alexander Shiyan; +Cc: linux-input
In-Reply-To: <1398491214-14936-1-git-send-email-shc_work@mail.ru>
Hi Alexander,
On Sat, Apr 26, 2014 at 09:46:54AM +0400, Alexander Shiyan wrote:
> + beep->desc = devm_gpiod_get(&pdev->dev, NULL);
> + if (!beep->desc)
> + return -EINVAL;
As far as I can see devm_gpiod_get() never returns NULL, but either a
valid pointer or ERR_PTR-encoded error. Why do we check for NULL?
I can drop the check myself, no need to resubmit.
>
> -static struct of_device_id gpio_beeper_of_match[] = {
> +static struct of_device_id __maybe_unused gpio_beeper_of_match[] = {
> { .compatible = BEEPER_MODNAME, },
> { }
> };
Instead of marking it as __maybe_unused we should wrap it in
#ifdef CONFIG_OF. I'll do it as well.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH RESEND 2/2] input: gpio_keys: Convert to devm-* API
From: Dmitry Torokhov @ 2014-04-29 2:30 UTC (permalink / raw)
To: Alexander Shiyan; +Cc: linux-input
In-Reply-To: <1398491594-2004-2-git-send-email-shc_work@mail.ru>
Hi Alexander,
On Sat, Apr 26, 2014 at 09:53:14AM +0400, Alexander Shiyan wrote:
> Replace existing resource handling in the driver with managed
> device resource, this ensures more consistent error values and
> simplifies error paths.
> kzalloc -> devm_kzalloc
> gpio_request_one -> devm_gpio_request_one
> input_allocate_device -> devm_input_allocate_device
>
> Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
> ---
> drivers/input/keyboard/gpio_keys.c | 96 +++++++++++---------------------------
> 1 file changed, 28 insertions(+), 68 deletions(-)
>
> diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
> index 2db1324..c4bc6e4 100644
> --- a/drivers/input/keyboard/gpio_keys.c
> +++ b/drivers/input/keyboard/gpio_keys.c
> @@ -433,7 +433,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
> struct device *dev = &pdev->dev;
> irq_handler_t isr;
> unsigned long irqflags;
> - int irq, error;
> + int error;
>
> bdata->input = input;
> bdata->button = button;
> @@ -441,7 +441,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
>
> if (gpio_is_valid(button->gpio)) {
>
> - error = gpio_request_one(button->gpio, GPIOF_IN, desc);
> + error = devm_gpio_request_one(&pdev->dev, button->gpio,
> + GPIOF_IN, desc);
> if (error < 0) {
> dev_err(dev, "Failed to request GPIO %d, error %d\n",
> button->gpio, error);
> @@ -457,15 +458,13 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
> button->debounce_interval;
> }
>
> - irq = gpio_to_irq(button->gpio);
> - if (irq < 0) {
> - error = irq;
> + bdata->irq = gpio_to_irq(button->gpio);
> + if (bdata->irq < 0) {
This is wrong, bdata->irq is unsigned.
Also I already applied the earlier version of the patch dealing with
non-gpio resources. How about the patch below?
Thanks.
--
Dmitry
Input: gpio_keys - more conversions to devm-* API
From: Alexander Shiyan <shc_work@mail.ru>
Replace existing gpio resource handling in the driver with managed
resources, this ensures more consistent error values and simplifies error
paths.
Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/keyboard/gpio_keys.c | 69 ++++++++++++++++++------------------
1 file changed, 34 insertions(+), 35 deletions(-)
diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index 52dc872..8d7d748 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -424,6 +424,16 @@ out:
return IRQ_HANDLED;
}
+static void gpio_keys_quiesce_key(void *data)
+{
+ struct gpio_button_data *bdata = data;
+
+ if (bdata->timer_debounce)
+ del_timer_sync(&bdata->timer);
+
+ cancel_work_sync(&bdata->work);
+}
+
static int gpio_keys_setup_key(struct platform_device *pdev,
struct input_dev *input,
struct gpio_button_data *bdata,
@@ -433,7 +443,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
struct device *dev = &pdev->dev;
irq_handler_t isr;
unsigned long irqflags;
- int irq, error;
+ int irq;
+ int error;
bdata->input = input;
bdata->button = button;
@@ -441,7 +452,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
if (gpio_is_valid(button->gpio)) {
- error = gpio_request_one(button->gpio, GPIOF_IN, desc);
+ error = devm_gpio_request_one(&pdev->dev, button->gpio,
+ GPIOF_IN, desc);
if (error < 0) {
dev_err(dev, "Failed to request GPIO %d, error %d\n",
button->gpio, error);
@@ -463,7 +475,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
dev_err(dev,
"Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
- goto fail;
+ return error;
}
bdata->irq = irq;
@@ -497,26 +509,33 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
input_set_capability(input, button->type ?: EV_KEY, button->code);
/*
+ * Install custom action to cancel debounce timer and
+ * workqueue item.
+ */
+ error = devm_add_action(&pdev->dev, gpio_keys_quiesce_key, bdata);
+ if (error) {
+ dev_err(&pdev->dev,
+ "failed to register quiesce action, error: %d\n",
+ error);
+ return error;
+ }
+
+ /*
* If platform has specified that the button can be disabled,
* we don't want it to share the interrupt line.
*/
if (!button->can_disable)
irqflags |= IRQF_SHARED;
- error = request_any_context_irq(bdata->irq, isr, irqflags, desc, bdata);
+ error = devm_request_any_context_irq(&pdev->dev, bdata->irq,
+ isr, irqflags, desc, bdata);
if (error < 0) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
bdata->irq, error);
- goto fail;
+ return error;
}
return 0;
-
-fail:
- if (gpio_is_valid(button->gpio))
- gpio_free(button->gpio);
-
- return error;
}
static void gpio_keys_report_state(struct gpio_keys_drvdata *ddata)
@@ -662,16 +681,6 @@ gpio_keys_get_devtree_pdata(struct device *dev)
#endif
-static void gpio_remove_key(struct gpio_button_data *bdata)
-{
- free_irq(bdata->irq, bdata);
- if (bdata->timer_debounce)
- del_timer_sync(&bdata->timer);
- cancel_work_sync(&bdata->work);
- if (gpio_is_valid(bdata->button->gpio))
- gpio_free(bdata->button->gpio);
-}
-
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -730,7 +739,7 @@ static int gpio_keys_probe(struct platform_device *pdev)
error = gpio_keys_setup_key(pdev, input, bdata, button);
if (error)
- goto fail2;
+ return error;
if (button->wakeup)
wakeup = 1;
@@ -740,41 +749,31 @@ static int gpio_keys_probe(struct platform_device *pdev)
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
- goto fail2;
+ return error;
}
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
- goto fail3;
+ goto err_remove_group;
}
device_init_wakeup(&pdev->dev, wakeup);
return 0;
- fail3:
+err_remove_group:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
- fail2:
- while (--i >= 0)
- gpio_remove_key(&ddata->data[i]);
-
return error;
}
static int gpio_keys_remove(struct platform_device *pdev)
{
- struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
- int i;
-
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
device_init_wakeup(&pdev->dev, 0);
- for (i = 0; i < ddata->pdata->nbuttons; i++)
- gpio_remove_key(&ddata->data[i]);
-
return 0;
}
^ permalink raw reply related
* [PATCH] Input: remove race when instantiating polled device attributes
From: Dmitry Torokhov @ 2014-04-29 2:34 UTC (permalink / raw)
To: linux-input; +Cc: linux-kernel, Alexander Shiyan
Polled device's attributes controlling polling rate and whether polling is
enabled are attached to input device. We should have device core
instantiate them for us, so that they are created by the time new device
notification is sent to userspace, instead of doing it ourselves
afterwards.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/input-polldev.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/drivers/input/input-polldev.c b/drivers/input/input-polldev.c
index 7f161d9..4b19190 100644
--- a/drivers/input/input-polldev.c
+++ b/drivers/input/input-polldev.c
@@ -147,6 +147,11 @@ static struct attribute_group input_polldev_attribute_group = {
.attrs = sysfs_attrs
};
+static const struct attribute_group *input_polldev_attribute_groups[] = {
+ &input_polldev_attribute_group,
+ NULL
+};
+
/**
* input_allocate_polled_device - allocate memory for polled device
*
@@ -204,24 +209,21 @@ int input_register_polled_device(struct input_polled_dev *dev)
input_set_drvdata(input, dev);
INIT_DELAYED_WORK(&dev->work, input_polled_device_work);
+
if (!dev->poll_interval)
dev->poll_interval = 500;
if (!dev->poll_interval_max)
dev->poll_interval_max = dev->poll_interval;
+
input->open = input_open_polled_device;
input->close = input_close_polled_device;
+ input->dev.groups = input_polldev_attribute_groups;
+
error = input_register_device(input);
if (error)
return error;
- error = sysfs_create_group(&input->dev.kobj,
- &input_polldev_attribute_group);
- if (error) {
- input_unregister_device(input);
- return error;
- }
-
/*
* Take extra reference to the underlying input device so
* that it survives call to input_unregister_polled_device()
@@ -245,9 +247,6 @@ EXPORT_SYMBOL(input_register_polled_device);
*/
void input_unregister_polled_device(struct input_polled_dev *dev)
{
- sysfs_remove_group(&dev->input->dev.kobj,
- &input_polldev_attribute_group);
-
input_unregister_device(dev->input);
}
EXPORT_SYMBOL(input_unregister_polled_device);
--
1.9.0
--
Dmitry
^ permalink raw reply related
* Re: [PATCH v2] input: gpio-beeper: Simplify GPIO handling
From: Alexander Shiyan @ 2014-04-29 2:55 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
In-Reply-To: <20140429022104.GA7672@core.coreip.homeip.net>
Mon, 28 Apr 2014 19:21:04 -0700 от Dmitry Torokhov <dmitry.torokhov@gmail.com>:
> Hi Alexander,
>
> On Sat, Apr 26, 2014 at 09:46:54AM +0400, Alexander Shiyan wrote:
> > + beep->desc = devm_gpiod_get(&pdev->dev, NULL);
> > + if (!beep->desc)
> > + return -EINVAL;
>
> As far as I can see devm_gpiod_get() never returns NULL, but either a
> valid pointer or ERR_PTR-encoded error. Why do we check for NULL?
>
> I can drop the check myself, no need to resubmit.
OK.
...
One issue you keep value argument for gpio_beeper_toggle() as bool.
It should be "int" as in original patch, as well as "beeping" value in private struct.
---
^ permalink raw reply
* Re: [PATCH v2] input: gpio-beeper: Simplify GPIO handling
From: Dmitry Torokhov @ 2014-04-29 3:22 UTC (permalink / raw)
To: Alexander Shiyan; +Cc: linux-input
In-Reply-To: <1398740149.14014937@f348.i.mail.ru>
On Tue, Apr 29, 2014 at 06:55:49AM +0400, Alexander Shiyan wrote:
> Mon, 28 Apr 2014 19:21:04 -0700 от Dmitry Torokhov <dmitry.torokhov@gmail.com>:
> > Hi Alexander,
> >
> > On Sat, Apr 26, 2014 at 09:46:54AM +0400, Alexander Shiyan wrote:
> > > + beep->desc = devm_gpiod_get(&pdev->dev, NULL);
> > > + if (!beep->desc)
> > > + return -EINVAL;
> >
> > As far as I can see devm_gpiod_get() never returns NULL, but either a
> > valid pointer or ERR_PTR-encoded error. Why do we check for NULL?
> >
> > I can drop the check myself, no need to resubmit.
>
> OK.
>
> ...
>
> One issue you keep value argument for gpio_beeper_toggle() as bool.
> It should be "int" as in original patch, as well as "beeping" value in
> private struct.
Not really, value of gpio_set_value_cansleep() is a boolean, even though
the type is int. I guess using 'int' is historical. One can't really
set gpio value to let's say 5.
Thanks.
--
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH] Input: implement managed polled input devices
From: Dmitry Torokhov @ 2014-04-29 3:23 UTC (permalink / raw)
To: linux-input; +Cc: linux-kernel, Alexander Shiyan
Managed resources are becoming more and more popular in drivers. Let's
implement managed polled input devices, to complement managed regular input
devices.
Similarly to managed regular input devices only one new call
devm_input_allocate_polled_device() is added and the rest of APIs is
modified to work with both managed and non-managed devices.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/input-polldev.c | 113 +++++++++++++++++++++++++++++++++++++++++-
include/linux/input-polldev.h | 3 ++
2 files changed, 115 insertions(+), 1 deletion(-)
diff --git a/drivers/input/input-polldev.c b/drivers/input/input-polldev.c
index 4b19190..27961fc 100644
--- a/drivers/input/input-polldev.c
+++ b/drivers/input/input-polldev.c
@@ -176,6 +176,90 @@ struct input_polled_dev *input_allocate_polled_device(void)
}
EXPORT_SYMBOL(input_allocate_polled_device);
+struct input_polled_devres {
+ struct input_polled_dev *polldev;
+};
+
+static int devm_input_polldev_match(struct device *dev, void *res, void *data)
+{
+ struct input_polled_devres *devres = res;
+
+ return devres->polldev == data;
+}
+
+static void devm_input_polldev_release(struct device *dev, void *res)
+{
+ struct input_polled_devres *devres = res;
+ struct input_polled_dev *polldev = devres->polldev;
+
+ dev_dbg(dev, "%s: dropping reference/freeing %s\n",
+ __func__, dev_name(&polldev->input->dev));
+
+ input_put_device(polldev->input);
+ kfree(polldev);
+}
+
+static void devm_input_polldev_unregister(struct device *dev, void *res)
+{
+ struct input_polled_devres *devres = res;
+ struct input_polled_dev *polldev = devres->polldev;
+
+ dev_dbg(dev, "%s: unregistering device %s\n",
+ __func__, dev_name(&polldev->input->dev));
+ input_unregister_device(polldev->input);
+
+ /*
+ * Note that we are still holding extra reference to the input
+ * device so it will stick around until devm_input_polldev_release()
+ * is called.
+ */
+}
+
+/**
+ * devm_input_allocate_polled_device - allocate managed polled device
+ * @dev: device owning the polled device being created
+ *
+ * Returns prepared &struct input_polled_dev or %NULL.
+ *
+ * Managed polled input devices do not need to be explicitly unregistered
+ * or freed as it will be done automatically when owner device unbinds
+ * from * its driver (or binding fails). Once such managed polled device
+ * is allocated, it is ready to be set up and registered in the same
+ * fashion as regular polled input devices (using
+ * input_register_polled_device() function).
+ *
+ * If you want to manually unregister and free such managed polled devices,
+ * it can be still done by calling input_unregister_polled_device() and
+ * input_free_polled_device(), although it is rarely needed.
+ *
+ * NOTE: the owner device is set up as parent of input device and users
+ * should not override it.
+ */
+struct input_polled_dev *devm_input_allocate_polled_device(struct device *dev)
+{
+ struct input_polled_dev *polldev;
+ struct input_polled_devres *devres;
+
+ devres = devres_alloc(devm_input_polldev_release, sizeof(*devres),
+ GFP_KERNEL);
+ if (!devres)
+ return NULL;
+
+ polldev = input_allocate_polled_device();
+ if (!polldev) {
+ devres_free(devres);
+ return NULL;
+ }
+
+ polldev->input->dev.parent = dev;
+ polldev->devres_managed = true;
+
+ devres->polldev = polldev;
+ devres_add(dev, devres);
+
+ return polldev;
+}
+
/**
* input_free_polled_device - free memory allocated for polled device
* @dev: device to free
@@ -186,7 +270,12 @@ EXPORT_SYMBOL(input_allocate_polled_device);
void input_free_polled_device(struct input_polled_dev *dev)
{
if (dev) {
- input_free_device(dev->input);
+ if (dev->devres_managed)
+ WARN_ON(devres_destroy(dev->input->dev.parent,
+ devm_input_polldev_release,
+ devm_input_polldev_match,
+ dev));
+ input_put_device(dev->input);
kfree(dev);
}
}
@@ -204,9 +293,19 @@ EXPORT_SYMBOL(input_free_polled_device);
*/
int input_register_polled_device(struct input_polled_dev *dev)
{
+ struct input_polled_devres *devres = NULL;
struct input_dev *input = dev->input;
int error;
+ if (dev->devres_managed) {
+ devres = devres_alloc(devm_input_polldev_unregister,
+ sizeof(*devres), GFP_KERNEL);
+ if (!devres)
+ return -ENOMEM;
+
+ devres->polldev = dev;
+ }
+
input_set_drvdata(input, dev);
INIT_DELAYED_WORK(&dev->work, input_polled_device_work);
@@ -233,6 +332,12 @@ int input_register_polled_device(struct input_polled_dev *dev)
*/
input_get_device(input);
+ if (dev->devres_managed) {
+ dev_dbg(input->dev.parent, "%s: registering %s with devres.\n",
+ __func__, dev_name(&input->dev));
+ devres_add(input->dev.parent, devres);
+ }
+
return 0;
}
EXPORT_SYMBOL(input_register_polled_device);
@@ -247,6 +352,12 @@ EXPORT_SYMBOL(input_register_polled_device);
*/
void input_unregister_polled_device(struct input_polled_dev *dev)
{
+ if (dev->devres_managed)
+ WARN_ON(devres_destroy(dev->input->dev.parent,
+ devm_input_polldev_unregister,
+ devm_input_polldev_match,
+ dev));
+
input_unregister_device(dev->input);
}
EXPORT_SYMBOL(input_unregister_polled_device);
diff --git a/include/linux/input-polldev.h b/include/linux/input-polldev.h
index ce0b724..2465182 100644
--- a/include/linux/input-polldev.h
+++ b/include/linux/input-polldev.h
@@ -48,9 +48,12 @@ struct input_polled_dev {
/* private: */
struct delayed_work work;
+
+ bool devres_managed;
};
struct input_polled_dev *input_allocate_polled_device(void);
+struct input_polled_dev *devm_input_allocate_polled_device(struct device *dev);
void input_free_polled_device(struct input_polled_dev *dev);
int input_register_polled_device(struct input_polled_dev *dev);
void input_unregister_polled_device(struct input_polled_dev *dev);
--
1.9.0
--
Dmitry
^ permalink raw reply related
* Re: [PATCH RESEND 1/2] input: gpio_keys_polled: Convert to devm-* API
From: Dmitry Torokhov @ 2014-04-29 3:26 UTC (permalink / raw)
To: Alexander Shiyan; +Cc: linux-input
In-Reply-To: <1398491594-2004-1-git-send-email-shc_work@mail.ru>
Hi Alexander,
On Sat, Apr 26, 2014 at 09:53:13AM +0400, Alexander Shiyan wrote:
> Replace existing resource handling in the driver with managed
> device resource, this ensures more consistent error values and
> simplifies error paths.
> kzalloc -> devm_kzalloc
> gpio_request_one -> devm_gpio_request_one
>
If we are doing the conversion can we go all the Alexanderway (needs the
other 2 patches I just posted and CCed you)?
Thanks.
--
Dmitry
Input: gpio_keys_polled - convert to devm-* API
From: Alexander Shiyan <shc_work@mail.ru>
Replace existing resource handling in the driver with managed device
resources, this ensures more consistent error values and simplifies error
handling paths:
kzalloc -> devm_kzalloc
gpio_request_one -> devm_gpio_request_one
input_allocate_polled_device -> devm_input_allocate_polled_device
Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/keyboard/gpio_keys_polled.c | 98 ++++++-----------------------
1 file changed, 21 insertions(+), 77 deletions(-)
diff --git a/drivers/input/keyboard/gpio_keys_polled.c b/drivers/input/keyboard/gpio_keys_polled.c
index e571e19..196b5ec 100644
--- a/drivers/input/keyboard/gpio_keys_polled.c
+++ b/drivers/input/keyboard/gpio_keys_polled.c
@@ -120,12 +120,10 @@ static struct gpio_keys_platform_data *gpio_keys_polled_get_devtree_pdata(struct
if (nbuttons == 0)
return NULL;
- pdata = kzalloc(sizeof(*pdata) + nbuttons * (sizeof *button),
- GFP_KERNEL);
- if (!pdata) {
- error = -ENOMEM;
- goto err_out;
- }
+ pdata = devm_kzalloc(dev, sizeof(*pdata) + nbuttons * sizeof(*button),
+ GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
pdata->buttons = (struct gpio_keys_button *)(pdata + 1);
pdata->nbuttons = nbuttons;
@@ -151,7 +149,7 @@ static struct gpio_keys_platform_data *gpio_keys_polled_get_devtree_pdata(struct
dev_err(dev,
"Failed to get gpio flags, error: %d\n",
error);
- goto err_free_pdata;
+ return ERR_PTR(error);
}
button = &pdata->buttons[i++];
@@ -162,8 +160,7 @@ static struct gpio_keys_platform_data *gpio_keys_polled_get_devtree_pdata(struct
if (of_property_read_u32(pp, "linux,code", &button->code)) {
dev_err(dev, "Button without keycode: 0x%x\n",
button->gpio);
- error = -EINVAL;
- goto err_free_pdata;
+ return ERR_PTR(-EINVAL);
}
button->desc = of_get_property(pp, "label", NULL);
@@ -178,17 +175,10 @@ static struct gpio_keys_platform_data *gpio_keys_polled_get_devtree_pdata(struct
button->debounce_interval = 5;
}
- if (pdata->nbuttons == 0) {
- error = -EINVAL;
- goto err_free_pdata;
- }
+ if (pdata->nbuttons == 0)
+ return ERR_PTR(-EINVAL);
return pdata;
-
-err_free_pdata:
- kfree(pdata);
-err_out:
- return ERR_PTR(error);
}
static struct of_device_id gpio_keys_polled_of_match[] = {
@@ -213,6 +203,7 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
struct gpio_keys_polled_dev *bdev;
struct input_polled_dev *poll_dev;
struct input_dev *input;
+ size_t size;
int error;
int i;
@@ -228,24 +219,21 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
if (!pdata->poll_interval) {
dev_err(dev, "missing poll_interval value\n");
- error = -EINVAL;
- goto err_free_pdata;
+ return -EINVAL;
}
- bdev = kzalloc(sizeof(struct gpio_keys_polled_dev) +
- pdata->nbuttons * sizeof(struct gpio_keys_button_data),
- GFP_KERNEL);
+ size = sizeof(struct gpio_keys_polled_dev) +
+ pdata->nbuttons * sizeof(struct gpio_keys_button_data);
+ bdev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
if (!bdev) {
dev_err(dev, "no memory for private data\n");
- error = -ENOMEM;
- goto err_free_pdata;
+ return -ENOMEM;
}
- poll_dev = input_allocate_polled_device();
+ poll_dev = devm_input_allocate_polled_device(&pdev->dev);
if (!poll_dev) {
dev_err(dev, "no memory for polled device\n");
- error = -ENOMEM;
- goto err_free_bdev;
+ return -ENOMEM;
}
poll_dev->private = bdev;
@@ -258,7 +246,6 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
input->name = pdev->name;
input->phys = DRV_NAME"/input0";
- input->dev.parent = &pdev->dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
@@ -277,16 +264,15 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
if (button->wakeup) {
dev_err(dev, DRV_NAME " does not support wakeup\n");
- error = -EINVAL;
- goto err_free_gpio;
+ return -EINVAL;
}
- error = gpio_request_one(gpio, GPIOF_IN,
- button->desc ?: DRV_NAME);
+ error = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_IN,
+ button->desc ? : DRV_NAME);
if (error) {
dev_err(dev, "unable to claim gpio %u, err=%d\n",
gpio, error);
- goto err_free_gpio;
+ return error;
}
bdata->can_sleep = gpio_cansleep(gpio);
@@ -306,7 +292,7 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
if (error) {
dev_err(dev, "unable to register polled device, err=%d\n",
error);
- goto err_free_gpio;
+ return error;
}
/* report initial state of the buttons */
@@ -315,52 +301,10 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
&bdev->data[i]);
return 0;
-
-err_free_gpio:
- while (--i >= 0)
- gpio_free(pdata->buttons[i].gpio);
-
- input_free_polled_device(poll_dev);
-
-err_free_bdev:
- kfree(bdev);
-
-err_free_pdata:
- /* If we have no platform_data, we allocated pdata dynamically. */
- if (!dev_get_platdata(&pdev->dev))
- kfree(pdata);
-
- return error;
-}
-
-static int gpio_keys_polled_remove(struct platform_device *pdev)
-{
- struct gpio_keys_polled_dev *bdev = platform_get_drvdata(pdev);
- const struct gpio_keys_platform_data *pdata = bdev->pdata;
- int i;
-
- input_unregister_polled_device(bdev->poll_dev);
-
- for (i = 0; i < pdata->nbuttons; i++)
- gpio_free(pdata->buttons[i].gpio);
-
- input_free_polled_device(bdev->poll_dev);
-
- /*
- * If we had no platform_data, we allocated pdata dynamically and
- * must free it here.
- */
- if (!dev_get_platdata(&pdev->dev))
- kfree(pdata);
-
- kfree(bdev);
-
- return 0;
}
static struct platform_driver gpio_keys_polled_driver = {
.probe = gpio_keys_polled_probe,
- .remove = gpio_keys_polled_remove,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
^ permalink raw reply related
* Re: [PATCH RESEND 1/2] input: gpio_keys_polled: Convert to devm-* API
From: Alexander Shiyan @ 2014-04-29 4:43 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
In-Reply-To: <20140429032600.GD7672@core.coreip.homeip.net>
Mon, 28 Apr 2014 20:26:00 -0700 от Dmitry Torokhov <dmitry.torokhov@gmail.com>:
> Hi Alexander,
>
> On Sat, Apr 26, 2014 at 09:53:13AM +0400, Alexander Shiyan wrote:
> > Replace existing resource handling in the driver with managed
> > device resource, this ensures more consistent error values and
> > simplifies error paths.
> > kzalloc -> devm_kzalloc
> > gpio_request_one -> devm_gpio_request_one
> >
>
> If we are doing the conversion can we go all the Alexanderway (needs the
> other 2 patches I just posted and CCed you)?
>
> Thanks.
>
> --
> Dmitry
>
> Input: gpio_keys_polled - convert to devm-* API
>
> From: Alexander Shiyan <shc_work@mail.ru>
>
> Replace existing resource handling in the driver with managed device
> resources, this ensures more consistent error values and simplifies error
> handling paths:
>
> kzalloc -> devm_kzalloc
> gpio_request_one -> devm_gpio_request_one
> input_allocate_polled_device -> devm_input_allocate_polled_device
>
> Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> ---
...
> @@ -162,8 +160,7 @@ static struct gpio_keys_platform_data *gpio_keys_polled_get_devtree_pdata(struct
> if (of_property_read_u32(pp, "linux,code", &button->code)) {
> dev_err(dev, "Button without keycode: 0x%x\n",
> button->gpio);
> - error = -EINVAL;
> - goto err_free_pdata;
> + return ERR_PTR(-EINVAL);
> }
We can even use return value from of_property_read_u32() on error.
All other looks OK.
---
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox