Skip to content

Commit 8df3109

Browse files
committed
Input: add support for Flipper One haptic LRA
Flipper One has a haptic LRA (linear resonant actuator) exposed via its MCU interconnect interface. Add a driver to expose the haptic actuator as a force feedback device. Signed-off-by: Alexey Charkov <alchark@flipper.net>
1 parent c2ca0a7 commit 8df3109

3 files changed

Lines changed: 174 additions & 1 deletion

File tree

drivers/input/misc/Kconfig

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,23 @@ config INPUT_FLIPPER_ONE
184184
help
185185
Say Y here to enable support for human-machine interfacing
186186
functions on the Flipper One networking multitool, including its
187-
builtin buttons, touchpad, haptic feedback and headset inputs.
187+
builtin buttons, touchpad and headset inputs.
188188

189189
To compile this driver as a module, choose M here: the
190190
module will be called flipper-one-input.
191191

192+
config INPUT_FLIPPER_ONE_HAPTIC
193+
tristate "Flipper One haptic feedback support"
194+
depends on MFD_FLIPPER_ONE
195+
help
196+
Say Y here to enable support for the haptic feedback actuator
197+
on the Flipper One networking multitool. Effects are played from
198+
the controller's built-in waveform library through the force
199+
feedback interface.
200+
201+
To compile this driver as a module, choose M here: the
202+
module will be called flipper-one-haptic.
203+
192204
config INPUT_PCSPKR
193205
tristate "PC Speaker support"
194206
depends on PCSPKR_PLATFORM

drivers/input/misc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
3737
obj-$(CONFIG_INPUT_DA9063_ONKEY) += da9063_onkey.o
3838
obj-$(CONFIG_INPUT_E3X0_BUTTON) += e3x0-button.o
3939
obj-$(CONFIG_INPUT_FLIPPER_ONE) += flipper-one-input.o
40+
obj-$(CONFIG_INPUT_FLIPPER_ONE_HAPTIC) += flipper-one-haptic.o
4041
obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.o
4142
obj-$(CONFIG_INPUT_DRV2665_HAPTICS) += drv2665.o
4243
obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Flipper One haptic feedback driver
4+
* Copyright (C) 2026 Flipper FZCO
5+
*/
6+
7+
#include <linux/bitfield.h>
8+
#include <linux/input.h>
9+
#include <linux/minmax.h>
10+
#include <linux/module.h>
11+
#include <linux/platform_device.h>
12+
#include <linux/regmap.h>
13+
#include <linux/uaccess.h>
14+
#include <linux/workqueue.h>
15+
16+
#include <linux/mfd/flipper-one-mcu.h>
17+
18+
/* Highest effect index in the controller's built-in waveform library */
19+
#define FO_HAPTIC_EFFECT_MAX 123
20+
21+
/* Number of effects userspace may keep uploaded at once */
22+
#define FO_HAPTIC_MAX_EFFECTS 16
23+
24+
struct fo_haptic {
25+
struct fomcu_device *fomcu;
26+
struct device *dev;
27+
struct work_struct play_work;
28+
u16 effect_val[FO_HAPTIC_MAX_EFFECTS];
29+
u16 play_val;
30+
};
31+
32+
static void fo_haptic_play_work(struct work_struct *work)
33+
{
34+
struct fo_haptic *haptic = container_of(work, struct fo_haptic,
35+
play_work);
36+
int err;
37+
38+
err = regmap_write(haptic->fomcu->regmap, FOMCU_REG_HAPTIC,
39+
READ_ONCE(haptic->play_val));
40+
if (err)
41+
dev_err(haptic->dev, "Failed to write haptic register: %d\n",
42+
err);
43+
}
44+
45+
static int fo_haptic_upload(struct input_dev *idev, struct ff_effect *effect,
46+
struct ff_effect *old)
47+
{
48+
struct fo_haptic *haptic = input_get_drvdata(idev);
49+
unsigned int duration;
50+
s16 id;
51+
52+
if (effect->type != FF_PERIODIC ||
53+
effect->u.periodic.waveform != FF_CUSTOM)
54+
return -EINVAL;
55+
56+
/* custom_data holds a single s16: the library effect index */
57+
if (effect->u.periodic.custom_len != 1)
58+
return -EINVAL;
59+
60+
if (copy_from_user(&id, effect->u.periodic.custom_data, sizeof(id)))
61+
return -EFAULT;
62+
63+
if (id < 0 || id > FO_HAPTIC_EFFECT_MAX)
64+
return -EINVAL;
65+
66+
/*
67+
* Durations 0 and 1 are reserved to mean "play the full
68+
* library waveform", other values are in milliseconds
69+
*/
70+
duration = min_t(unsigned int, effect->replay.length,
71+
FIELD_MAX(FOMCU_HAPTIC_DURATION));
72+
73+
haptic->effect_val[effect->id] = FOMCU_HAPTIC_PLAY |
74+
FIELD_PREP(FOMCU_HAPTIC_EFFECT, id) |
75+
FIELD_PREP(FOMCU_HAPTIC_DURATION, duration);
76+
77+
return 0;
78+
}
79+
80+
static int fo_haptic_playback(struct input_dev *idev, int effect_id, int value)
81+
{
82+
struct fo_haptic *haptic = input_get_drvdata(idev);
83+
84+
WRITE_ONCE(haptic->play_val,
85+
value ? haptic->effect_val[effect_id] : 0);
86+
schedule_work(&haptic->play_work);
87+
88+
return 0;
89+
}
90+
91+
static void fo_haptic_close(struct input_dev *idev)
92+
{
93+
struct fo_haptic *haptic = input_get_drvdata(idev);
94+
95+
cancel_work_sync(&haptic->play_work);
96+
regmap_write(haptic->fomcu->regmap, FOMCU_REG_HAPTIC, 0);
97+
}
98+
99+
static int fo_haptic_probe(struct platform_device *pdev)
100+
{
101+
struct fomcu_device *fomcu = dev_get_drvdata(pdev->dev.parent);
102+
struct device *dev = &pdev->dev;
103+
struct fo_haptic *haptic;
104+
struct input_dev *idev;
105+
int err;
106+
107+
haptic = devm_kzalloc(dev, sizeof(*haptic), GFP_KERNEL);
108+
if (!haptic)
109+
return -ENOMEM;
110+
111+
haptic->fomcu = fomcu;
112+
haptic->dev = dev;
113+
INIT_WORK(&haptic->play_work, fo_haptic_play_work);
114+
115+
idev = devm_input_allocate_device(dev);
116+
if (!idev)
117+
return -ENOMEM;
118+
119+
idev->name = "Flipper One Haptic";
120+
idev->phys = "flipper-one-haptic/input0";
121+
idev->id.bustype = BUS_I2C;
122+
idev->close = fo_haptic_close;
123+
input_set_drvdata(idev, haptic);
124+
125+
input_set_capability(idev, EV_FF, FF_PERIODIC);
126+
input_set_capability(idev, EV_FF, FF_CUSTOM);
127+
128+
err = input_ff_create(idev, FO_HAPTIC_MAX_EFFECTS);
129+
if (err)
130+
return dev_err_probe(dev, err, "Failed to create FF device\n");
131+
132+
idev->ff->upload = fo_haptic_upload;
133+
idev->ff->playback = fo_haptic_playback;
134+
135+
err = input_register_device(idev);
136+
if (err)
137+
return dev_err_probe(dev, err,
138+
"Failed to register input device\n");
139+
140+
return 0;
141+
}
142+
143+
static const struct platform_device_id fo_haptic_id_table[] = {
144+
{ "flipper-one-haptic", },
145+
{ }
146+
};
147+
MODULE_DEVICE_TABLE(platform, fo_haptic_id_table);
148+
149+
static struct platform_driver fo_haptic_driver = {
150+
.driver = {
151+
.name = "flipper-one-haptic",
152+
},
153+
.probe = fo_haptic_probe,
154+
.id_table = fo_haptic_id_table,
155+
};
156+
module_platform_driver(fo_haptic_driver);
157+
158+
MODULE_DESCRIPTION("Flipper One haptic feedback driver");
159+
MODULE_AUTHOR("Alexey Charkov <alchark@flipper.net>");
160+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)