mirror of
https://github.com/torvalds/linux.git
synced 2026-05-30 01:53:29 +02:00
Merge branch 'for-6.18/haptic' into for-linus
- Implement haptic touchpad support (Angela Czubak and Jonathan Denose)
This commit is contained in:
commit
b3f126f400
|
|
@ -400,6 +400,20 @@ can report through the rotational axes (absolute and/or relative rx, ry, rz).
|
|||
All other axes retain their meaning. A device must not mix
|
||||
regular directional axes and accelerometer axes on the same event node.
|
||||
|
||||
INPUT_PROP_HAPTIC_TOUCHPAD
|
||||
--------------------------
|
||||
|
||||
The INPUT_PROP_HAPTIC_TOUCHPAD property indicates that device:
|
||||
- supports simple haptic auto and manual triggering
|
||||
- can differentiate between at least 5 fingers
|
||||
- uses correct resolution for the X/Y (units and value)
|
||||
- reports correct force per touch, and correct units for them (newtons or grams)
|
||||
- follows the MT protocol type B
|
||||
|
||||
Summing up, such devices follow the MS spec for input devices in
|
||||
Win8 and Win8.1, and in addition support the Simple haptic controller HID table,
|
||||
and report correct units for the pressure.
|
||||
|
||||
Guidelines
|
||||
==========
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,17 @@ config HID_GENERIC
|
|||
|
||||
If unsure, say Y.
|
||||
|
||||
config HID_HAPTIC
|
||||
tristate "Haptic touchpad support"
|
||||
default n
|
||||
help
|
||||
Support for touchpads with force sensors and haptic actuators instead of a
|
||||
traditional button.
|
||||
Adds extra parsing and FF device for the hid multitouch driver.
|
||||
It can be used for Elan 2703 haptic touchpad.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
menu "Special HID drivers"
|
||||
|
||||
config HID_A4TECH
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
hid-y := hid-core.o hid-input.o hid-quirks.o
|
||||
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
|
||||
hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o
|
||||
|
||||
obj-$(CONFIG_HID_BPF) += bpf/
|
||||
|
||||
|
|
|
|||
580
drivers/hid/hid-haptic.c
Normal file
580
drivers/hid/hid-haptic.c
Normal file
|
|
@ -0,0 +1,580 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID Haptic support for Linux
|
||||
*
|
||||
* Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
|
||||
*/
|
||||
|
||||
#include <linux/input/mt.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "hid-haptic.h"
|
||||
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage *usage)
|
||||
{
|
||||
u16 usage_hid;
|
||||
|
||||
if (usage->hid == HID_HP_AUTOTRIGGER) {
|
||||
if (usage->usage_index >= field->report_count) {
|
||||
dev_err(&hdev->dev,
|
||||
"HID_HP_AUTOTRIGGER out of range\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hid_device_io_start(hdev);
|
||||
hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT);
|
||||
hid_hw_wait(hdev);
|
||||
hid_device_io_stop(hdev);
|
||||
haptic->default_auto_trigger =
|
||||
field->value[usage->usage_index];
|
||||
haptic->auto_trigger_report = field->report;
|
||||
} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) {
|
||||
usage_hid = usage->hid & HID_USAGE;
|
||||
switch (field->logical) {
|
||||
case HID_HP_WAVEFORMLIST:
|
||||
if (usage_hid > haptic->max_waveform_id)
|
||||
haptic->max_waveform_id = usage_hid;
|
||||
break;
|
||||
case HID_HP_DURATIONLIST:
|
||||
if (usage_hid > haptic->max_duration_id)
|
||||
haptic->max_duration_id = usage_hid;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
|
||||
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field)
|
||||
{
|
||||
if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) {
|
||||
haptic->force_logical_minimum = field->logical_minimum;
|
||||
haptic->force_physical_minimum = field->physical_minimum;
|
||||
haptic->force_resolution = input_abs_get_res(hi->input,
|
||||
ABS_MT_PRESSURE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit);
|
||||
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
if (usage->hid == HID_HP_MANUALTRIGGER) {
|
||||
haptic->manual_trigger_report = field->report;
|
||||
/* we don't really want to map these fields */
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_input_mapping);
|
||||
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
|
||||
if (hi->application == HID_DG_TOUCHPAD) {
|
||||
if (haptic->auto_trigger_report &&
|
||||
haptic->manual_trigger_report) {
|
||||
__set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
|
||||
|
||||
static void parse_auto_trigger_field(struct hid_haptic_device *haptic,
|
||||
struct hid_field *field)
|
||||
{
|
||||
int count = field->report_count;
|
||||
int n;
|
||||
u16 usage_hid;
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
switch (field->usage[n].hid & HID_USAGE_PAGE) {
|
||||
case HID_UP_ORDINAL:
|
||||
usage_hid = field->usage[n].hid & HID_USAGE;
|
||||
switch (field->logical) {
|
||||
case HID_HP_WAVEFORMLIST:
|
||||
haptic->hid_usage_map[usage_hid] = field->value[n];
|
||||
if (field->value[n] ==
|
||||
(HID_HP_WAVEFORMPRESS & HID_USAGE)) {
|
||||
haptic->press_ordinal = usage_hid;
|
||||
} else if (field->value[n] ==
|
||||
(HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
|
||||
haptic->release_ordinal = usage_hid;
|
||||
}
|
||||
break;
|
||||
case HID_HP_DURATIONLIST:
|
||||
haptic->duration_map[usage_hid] =
|
||||
field->value[n];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case HID_UP_HAPTIC:
|
||||
switch (field->usage[n].hid) {
|
||||
case HID_HP_WAVEFORMVENDORID:
|
||||
haptic->vendor_id = field->value[n];
|
||||
break;
|
||||
case HID_HP_WAVEFORMVENDORPAGE:
|
||||
haptic->vendor_page = field->value[n];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Should not really happen */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_effect_buf(struct hid_haptic_device *haptic,
|
||||
struct ff_haptic_effect *effect,
|
||||
struct hid_haptic_effect *haptic_effect,
|
||||
int waveform_ordinal)
|
||||
{
|
||||
struct hid_report *rep = haptic->manual_trigger_report;
|
||||
struct hid_usage *usage;
|
||||
struct hid_field *field;
|
||||
s32 value;
|
||||
int i, j;
|
||||
u8 *buf = haptic_effect->report_buf;
|
||||
|
||||
mutex_lock(&haptic->manual_trigger_mutex);
|
||||
for (i = 0; i < rep->maxfield; i++) {
|
||||
field = rep->field[i];
|
||||
/* Ignore if report count is out of bounds. */
|
||||
if (field->report_count < 1)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < field->maxusage; j++) {
|
||||
usage = &field->usage[j];
|
||||
|
||||
switch (usage->hid) {
|
||||
case HID_HP_INTENSITY:
|
||||
if (effect->intensity > 100) {
|
||||
value = field->logical_maximum;
|
||||
} else {
|
||||
value = field->logical_minimum +
|
||||
effect->intensity *
|
||||
(field->logical_maximum -
|
||||
field->logical_minimum) / 100;
|
||||
}
|
||||
break;
|
||||
case HID_HP_REPEATCOUNT:
|
||||
value = effect->repeat_count;
|
||||
break;
|
||||
case HID_HP_RETRIGGERPERIOD:
|
||||
value = effect->retrigger_period;
|
||||
break;
|
||||
case HID_HP_MANUALTRIGGER:
|
||||
value = waveform_ordinal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
field->value[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
hid_output_report(rep, buf);
|
||||
mutex_unlock(&haptic->manual_trigger_mutex);
|
||||
}
|
||||
|
||||
static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
int mode)
|
||||
{
|
||||
struct hid_report *rep = haptic->auto_trigger_report;
|
||||
struct hid_field *field;
|
||||
s32 value;
|
||||
int i, j;
|
||||
|
||||
if (mode == HID_HAPTIC_MODE_HOST)
|
||||
value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP;
|
||||
else
|
||||
value = haptic->default_auto_trigger;
|
||||
|
||||
mutex_lock(&haptic->auto_trigger_mutex);
|
||||
for (i = 0; i < rep->maxfield; i++) {
|
||||
field = rep->field[i];
|
||||
/* Ignore if report count is out of bounds. */
|
||||
if (field->report_count < 1)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < field->maxusage; j++) {
|
||||
if (field->usage[j].hid == HID_HP_AUTOTRIGGER)
|
||||
field->value[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* send the report */
|
||||
hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
|
||||
mutex_unlock(&haptic->auto_trigger_mutex);
|
||||
haptic->mode = mode;
|
||||
}
|
||||
|
||||
static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
|
||||
struct ff_effect *old)
|
||||
{
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct ff_device *ff = dev->ff;
|
||||
struct hid_haptic_device *haptic = ff->private;
|
||||
int i, ordinal = 0;
|
||||
bool switch_modes = false;
|
||||
|
||||
/* If vendor range, check vendor id and page */
|
||||
if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
|
||||
effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
|
||||
(effect->u.haptic.vendor_id != haptic->vendor_id ||
|
||||
effect->u.haptic.vendor_waveform_page != haptic->vendor_page))
|
||||
return -EINVAL;
|
||||
|
||||
/* Check hid_usage */
|
||||
for (i = 1; i <= haptic->max_waveform_id; i++) {
|
||||
if (haptic->hid_usage_map[i] == effect->u.haptic.hid_usage) {
|
||||
ordinal = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ordinal < 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* Fill the buffer for the effect id */
|
||||
fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id],
|
||||
ordinal);
|
||||
|
||||
if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) ||
|
||||
effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE))
|
||||
switch_modes = true;
|
||||
|
||||
/* If device is in autonomous mode, and the uploaded effect signals userspace
|
||||
* wants control of the device, change modes
|
||||
*/
|
||||
if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
struct hid_haptic_effect *effect)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_hw_output_report(hdev, effect->report_buf,
|
||||
haptic->manual_trigger_report_len);
|
||||
if (ret < 0) {
|
||||
ret = hid_hw_raw_request(hdev,
|
||||
haptic->manual_trigger_report->id,
|
||||
effect->report_buf,
|
||||
haptic->manual_trigger_report_len,
|
||||
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void haptic_work_handler(struct work_struct *work)
|
||||
{
|
||||
|
||||
struct hid_haptic_effect *effect = container_of(work,
|
||||
struct hid_haptic_effect,
|
||||
work);
|
||||
struct input_dev *dev = effect->input_dev;
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
|
||||
mutex_lock(&haptic->manual_trigger_mutex);
|
||||
if (effect != &haptic->stop_effect)
|
||||
play_effect(hdev, haptic, &haptic->stop_effect);
|
||||
|
||||
play_effect(hdev, haptic, effect);
|
||||
mutex_unlock(&haptic->manual_trigger_mutex);
|
||||
|
||||
}
|
||||
|
||||
static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value)
|
||||
{
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
|
||||
if (value)
|
||||
queue_work(haptic->wq, &haptic->effect[effect_id].work);
|
||||
else
|
||||
queue_work(haptic->wq, &haptic->stop_effect.work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void effect_set_default(struct ff_effect *effect)
|
||||
{
|
||||
effect->type = FF_HAPTIC;
|
||||
effect->id = -1;
|
||||
effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
|
||||
effect->u.haptic.intensity = 100;
|
||||
effect->u.haptic.retrigger_period = 0;
|
||||
effect->u.haptic.repeat_count = 0;
|
||||
}
|
||||
|
||||
static int hid_haptic_erase(struct input_dev *dev, int effect_id)
|
||||
{
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct ff_effect effect;
|
||||
int ordinal;
|
||||
|
||||
effect_set_default(&effect);
|
||||
|
||||
if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
|
||||
ordinal = haptic->release_ordinal;
|
||||
if (!ordinal) {
|
||||
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
|
||||
if (haptic->mode == HID_HAPTIC_MODE_HOST)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
|
||||
} else
|
||||
effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE;
|
||||
|
||||
fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
|
||||
ordinal);
|
||||
} else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
|
||||
ordinal = haptic->press_ordinal;
|
||||
if (!ordinal) {
|
||||
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
|
||||
if (haptic->mode == HID_HAPTIC_MODE_HOST)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
|
||||
}
|
||||
else
|
||||
effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
|
||||
|
||||
fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
|
||||
ordinal);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hid_haptic_destroy(struct ff_device *ff)
|
||||
{
|
||||
struct hid_haptic_device *haptic = ff->private;
|
||||
struct hid_device *hdev = haptic->hdev;
|
||||
int r;
|
||||
|
||||
if (hdev)
|
||||
put_device(&hdev->dev);
|
||||
|
||||
kfree(haptic->stop_effect.report_buf);
|
||||
haptic->stop_effect.report_buf = NULL;
|
||||
|
||||
if (haptic->effect) {
|
||||
for (r = 0; r < ff->max_effects; r++)
|
||||
kfree(haptic->effect[r].report_buf);
|
||||
kfree(haptic->effect);
|
||||
}
|
||||
haptic->effect = NULL;
|
||||
|
||||
destroy_workqueue(haptic->wq);
|
||||
haptic->wq = NULL;
|
||||
|
||||
kfree(haptic->duration_map);
|
||||
haptic->duration_map = NULL;
|
||||
|
||||
kfree(haptic->hid_usage_map);
|
||||
haptic->hid_usage_map = NULL;
|
||||
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
int hid_haptic_init(struct hid_device *hdev,
|
||||
struct hid_haptic_device **haptic_ptr)
|
||||
{
|
||||
struct hid_haptic_device *haptic = *haptic_ptr;
|
||||
struct input_dev *dev = NULL;
|
||||
struct hid_input *hidinput;
|
||||
struct ff_device *ff;
|
||||
int ret = 0, r;
|
||||
struct ff_haptic_effect stop_effect = {
|
||||
.hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE,
|
||||
};
|
||||
const char *prefix = "hid-haptic";
|
||||
char *name;
|
||||
int (*flush)(struct input_dev *dev, struct file *file);
|
||||
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
|
||||
|
||||
haptic->hdev = hdev;
|
||||
haptic->max_waveform_id = max(2u, haptic->max_waveform_id);
|
||||
haptic->max_duration_id = max(2u, haptic->max_duration_id);
|
||||
|
||||
haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1,
|
||||
sizeof(u16), GFP_KERNEL);
|
||||
if (!haptic->hid_usage_map) {
|
||||
ret = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
haptic->duration_map = kcalloc(haptic->max_duration_id + 1,
|
||||
sizeof(u32), GFP_KERNEL);
|
||||
if (!haptic->duration_map) {
|
||||
ret = -ENOMEM;
|
||||
goto usage_map;
|
||||
}
|
||||
|
||||
if (haptic->max_waveform_id != haptic->max_duration_id)
|
||||
dev_warn(&hdev->dev,
|
||||
"Haptic duration and waveform lists have different max id (%u and %u).\n",
|
||||
haptic->max_duration_id, haptic->max_waveform_id);
|
||||
|
||||
haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] =
|
||||
HID_HP_WAVEFORMNONE & HID_USAGE;
|
||||
haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
|
||||
HID_HP_WAVEFORMSTOP & HID_USAGE;
|
||||
|
||||
mutex_init(&haptic->auto_trigger_mutex);
|
||||
for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
|
||||
parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
|
||||
|
||||
list_for_each_entry(hidinput, &hdev->inputs, list) {
|
||||
if (hidinput->application == HID_DG_TOUCHPAD) {
|
||||
dev = hidinput->input;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev) {
|
||||
dev_err(&hdev->dev, "Failed to find the input device\n");
|
||||
ret = -ENODEV;
|
||||
goto duration_map;
|
||||
}
|
||||
|
||||
haptic->input_dev = dev;
|
||||
haptic->manual_trigger_report_len =
|
||||
hid_report_len(haptic->manual_trigger_report);
|
||||
mutex_init(&haptic->manual_trigger_mutex);
|
||||
name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL);
|
||||
if (name) {
|
||||
sprintf(name, "%s %s", prefix, hdev->name);
|
||||
haptic->wq = create_singlethread_workqueue(name);
|
||||
kfree(name);
|
||||
}
|
||||
if (!haptic->wq) {
|
||||
ret = -ENOMEM;
|
||||
goto duration_map;
|
||||
}
|
||||
haptic->effect = kcalloc(FF_MAX_EFFECTS,
|
||||
sizeof(struct hid_haptic_effect), GFP_KERNEL);
|
||||
if (!haptic->effect) {
|
||||
ret = -ENOMEM;
|
||||
goto output_queue;
|
||||
}
|
||||
for (r = 0; r < FF_MAX_EFFECTS; r++) {
|
||||
haptic->effect[r].report_buf =
|
||||
hid_alloc_report_buf(haptic->manual_trigger_report,
|
||||
GFP_KERNEL);
|
||||
if (!haptic->effect[r].report_buf) {
|
||||
dev_err(&hdev->dev,
|
||||
"Failed to allocate a buffer for an effect.\n");
|
||||
ret = -ENOMEM;
|
||||
goto buffer_free;
|
||||
}
|
||||
haptic->effect[r].input_dev = dev;
|
||||
INIT_WORK(&haptic->effect[r].work, haptic_work_handler);
|
||||
}
|
||||
haptic->stop_effect.report_buf =
|
||||
hid_alloc_report_buf(haptic->manual_trigger_report,
|
||||
GFP_KERNEL);
|
||||
if (!haptic->stop_effect.report_buf) {
|
||||
dev_err(&hdev->dev,
|
||||
"Failed to allocate a buffer for stop effect.\n");
|
||||
ret = -ENOMEM;
|
||||
goto buffer_free;
|
||||
}
|
||||
haptic->stop_effect.input_dev = dev;
|
||||
INIT_WORK(&haptic->stop_effect.work, haptic_work_handler);
|
||||
fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect,
|
||||
HID_HAPTIC_ORDINAL_WAVEFORMSTOP);
|
||||
|
||||
input_set_capability(dev, EV_FF, FF_HAPTIC);
|
||||
|
||||
flush = dev->flush;
|
||||
event = dev->event;
|
||||
ret = input_ff_create(dev, FF_MAX_EFFECTS);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "Failed to create ff device.\n");
|
||||
goto stop_buffer_free;
|
||||
}
|
||||
|
||||
ff = dev->ff;
|
||||
ff->private = haptic;
|
||||
ff->upload = hid_haptic_upload_effect;
|
||||
ff->playback = hid_haptic_playback;
|
||||
ff->erase = hid_haptic_erase;
|
||||
ff->destroy = hid_haptic_destroy;
|
||||
if (!try_module_get(THIS_MODULE)) {
|
||||
dev_err(&hdev->dev, "Failed to increase module count.\n");
|
||||
goto input_free;
|
||||
}
|
||||
if (!get_device(&hdev->dev)) {
|
||||
dev_err(&hdev->dev, "Failed to get hdev device.\n");
|
||||
module_put(THIS_MODULE);
|
||||
goto input_free;
|
||||
}
|
||||
return 0;
|
||||
|
||||
input_free:
|
||||
input_ff_destroy(dev);
|
||||
/* Do not let double free happen, input_ff_destroy will call
|
||||
* hid_haptic_destroy.
|
||||
*/
|
||||
*haptic_ptr = NULL;
|
||||
/* Restore dev flush and event */
|
||||
dev->flush = flush;
|
||||
dev->event = event;
|
||||
return ret;
|
||||
stop_buffer_free:
|
||||
kfree(haptic->stop_effect.report_buf);
|
||||
haptic->stop_effect.report_buf = NULL;
|
||||
buffer_free:
|
||||
while (--r >= 0)
|
||||
kfree(haptic->effect[r].report_buf);
|
||||
kfree(haptic->effect);
|
||||
haptic->effect = NULL;
|
||||
output_queue:
|
||||
destroy_workqueue(haptic->wq);
|
||||
haptic->wq = NULL;
|
||||
duration_map:
|
||||
kfree(haptic->duration_map);
|
||||
haptic->duration_map = NULL;
|
||||
usage_map:
|
||||
kfree(haptic->hid_usage_map);
|
||||
haptic->hid_usage_map = NULL;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_init);
|
||||
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic)
|
||||
{
|
||||
haptic->pressure_sum = 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset);
|
||||
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure)
|
||||
{
|
||||
haptic->pressure_sum += pressure;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase);
|
||||
127
drivers/hid/hid-haptic.h
Normal file
127
drivers/hid/hid-haptic.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* HID Haptic support for Linux
|
||||
*
|
||||
* Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
|
||||
*/
|
||||
|
||||
#include <linux/hid.h>
|
||||
|
||||
#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1
|
||||
#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2
|
||||
|
||||
#define HID_HAPTIC_MODE_DEVICE 0
|
||||
#define HID_HAPTIC_MODE_HOST 1
|
||||
|
||||
struct hid_haptic_effect {
|
||||
u8 *report_buf;
|
||||
struct input_dev *input_dev;
|
||||
struct work_struct work;
|
||||
struct list_head control;
|
||||
struct mutex control_mutex;
|
||||
};
|
||||
|
||||
struct hid_haptic_effect_node {
|
||||
struct list_head node;
|
||||
struct file *file;
|
||||
};
|
||||
|
||||
struct hid_haptic_device {
|
||||
struct input_dev *input_dev;
|
||||
struct hid_device *hdev;
|
||||
struct hid_report *auto_trigger_report;
|
||||
struct mutex auto_trigger_mutex;
|
||||
struct workqueue_struct *wq;
|
||||
struct hid_report *manual_trigger_report;
|
||||
struct mutex manual_trigger_mutex;
|
||||
size_t manual_trigger_report_len;
|
||||
int pressed_state;
|
||||
s32 pressure_sum;
|
||||
s32 force_logical_minimum;
|
||||
s32 force_physical_minimum;
|
||||
s32 force_resolution;
|
||||
u32 mode;
|
||||
u32 default_auto_trigger;
|
||||
u32 vendor_page;
|
||||
u32 vendor_id;
|
||||
u32 max_waveform_id;
|
||||
u32 max_duration_id;
|
||||
u16 *hid_usage_map;
|
||||
u32 *duration_map;
|
||||
u16 press_ordinal;
|
||||
u16 release_ordinal;
|
||||
struct hid_haptic_effect *effect;
|
||||
struct hid_haptic_effect stop_effect;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_HID_HAPTIC)
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage
|
||||
*usage);
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field);
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max);
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi);
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
|
||||
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure);
|
||||
#else
|
||||
static inline
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage
|
||||
*usage)
|
||||
{}
|
||||
static inline
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
|
||||
{}
|
||||
static inline
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
|
||||
static inline
|
||||
bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
|
||||
static inline
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure)
|
||||
{}
|
||||
#endif
|
||||
|
|
@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
|
|||
}
|
||||
break;
|
||||
|
||||
case ABS_PRESSURE:
|
||||
case ABS_MT_PRESSURE:
|
||||
if (field->unit == HID_UNIT_NEWTON) {
|
||||
/* Convert to grams, 1 newton is 101.97 grams */
|
||||
prev = physical_extents;
|
||||
physical_extents *= 10197;
|
||||
if (physical_extents < prev)
|
||||
return 0;
|
||||
unit_exponent -= 2;
|
||||
} else if (field->unit != HID_UNIT_GRAM) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -683,9 +696,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
|||
if (field->report_count < 1)
|
||||
goto ignore;
|
||||
|
||||
/* only LED usages are supported in output fields */
|
||||
/* only LED and HAPTIC usages are supported in output fields */
|
||||
if (field->report_type == HID_OUTPUT_REPORT &&
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED &&
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) {
|
||||
goto ignore;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ MODULE_LICENSE("GPL");
|
|||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#include "hid-haptic.h"
|
||||
|
||||
/* quirks to control the device */
|
||||
#define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0)
|
||||
#define MT_QUIRK_SLOT_IS_CONTACTID BIT(1)
|
||||
|
|
@ -168,11 +170,13 @@ struct mt_report_data {
|
|||
struct mt_device {
|
||||
struct mt_class mtclass; /* our mt device class */
|
||||
struct timer_list release_timer; /* to release sticky fingers */
|
||||
struct hid_haptic_device *haptic; /* haptic related configuration */
|
||||
struct hid_device *hdev; /* hid_device we're attached to */
|
||||
unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_*) */
|
||||
__u8 inputmode_value; /* InputMode HID feature value */
|
||||
__u8 maxcontacts;
|
||||
bool is_buttonpad; /* is this device a button pad? */
|
||||
bool is_haptic_touchpad; /* is this device a haptic touchpad? */
|
||||
bool serial_maybe; /* need to check for serial protocol */
|
||||
|
||||
struct list_head applications;
|
||||
|
|
@ -533,6 +537,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
|
|||
mt_get_feature(hdev, field->report);
|
||||
break;
|
||||
}
|
||||
|
||||
hid_haptic_feature_mapping(hdev, td->haptic, field, usage);
|
||||
}
|
||||
|
||||
static void set_abs(struct input_dev *input, unsigned int code,
|
||||
|
|
@ -888,6 +894,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
case HID_DG_TIPPRESSURE:
|
||||
set_abs(hi->input, ABS_MT_PRESSURE, field,
|
||||
cls->sn_pressure);
|
||||
td->is_haptic_touchpad =
|
||||
hid_haptic_check_pressure_unit(td->haptic,
|
||||
hi, field);
|
||||
MT_STORE_FIELD(p);
|
||||
return 1;
|
||||
case HID_DG_SCANTIME:
|
||||
|
|
@ -1008,6 +1017,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
|
|||
|
||||
app->num_received = 0;
|
||||
app->left_button_state = 0;
|
||||
if (td->is_haptic_touchpad)
|
||||
hid_haptic_pressure_reset(td->haptic);
|
||||
|
||||
if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
|
||||
set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
|
||||
|
|
@ -1165,6 +1176,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
|
|||
minor = minor >> 1;
|
||||
}
|
||||
|
||||
if (td->is_haptic_touchpad)
|
||||
hid_haptic_pressure_increase(td->haptic, *slot->p);
|
||||
|
||||
x = hdev->quirks & HID_QUIRK_X_INVERT ?
|
||||
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
|
||||
*slot->x;
|
||||
|
|
@ -1366,6 +1380,9 @@ static int mt_touch_input_configured(struct hid_device *hdev,
|
|||
if (cls->is_indirect)
|
||||
app->mt_flags |= INPUT_MT_POINTER;
|
||||
|
||||
if (td->is_haptic_touchpad)
|
||||
app->mt_flags |= INPUT_MT_TOTAL_FORCE;
|
||||
|
||||
if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
|
||||
app->mt_flags |= INPUT_MT_DROP_UNUSED;
|
||||
|
||||
|
|
@ -1401,6 +1418,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
struct mt_application *application;
|
||||
struct mt_report_data *rdata;
|
||||
int ret;
|
||||
|
||||
rdata = mt_find_report_data(td, field->report);
|
||||
if (!rdata) {
|
||||
|
|
@ -1463,6 +1481,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
if (field->physical == HID_DG_STYLUS)
|
||||
hi->application = HID_DG_STYLUS;
|
||||
|
||||
ret = hid_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
|
||||
max);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* let hid-core decide for the others */
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1685,6 +1708,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
|||
struct hid_report *report;
|
||||
int ret;
|
||||
|
||||
if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
|
||||
td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) {
|
||||
if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0)
|
||||
td->is_haptic_touchpad = false;
|
||||
} else {
|
||||
td->is_haptic_touchpad = false;
|
||||
}
|
||||
|
||||
list_for_each_entry(report, &hi->reports, hidinput_list) {
|
||||
rdata = mt_find_report_data(td, report);
|
||||
if (!rdata) {
|
||||
|
|
@ -1827,6 +1858,11 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
dev_err(&hdev->dev, "cannot allocate multitouch data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
td->haptic = devm_kzalloc(&hdev->dev, sizeof(*(td->haptic)), GFP_KERNEL);
|
||||
if (!td->haptic)
|
||||
return -ENOMEM;
|
||||
|
||||
td->haptic->hdev = hdev;
|
||||
td->hdev = hdev;
|
||||
td->mtclass = *mtclass;
|
||||
td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
|
||||
|
|
@ -1895,6 +1931,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
|
||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
|
||||
|
||||
if (td->is_haptic_touchpad) {
|
||||
if (hid_haptic_init(hdev, &td->haptic)) {
|
||||
dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
|
||||
hdev->name);
|
||||
td->is_haptic_touchpad = false;
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
} else {
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
|
|||
struct input_mt *mt = dev->mt;
|
||||
struct input_mt_slot *oldest;
|
||||
int oldid, count, i;
|
||||
int p, reported_p = 0;
|
||||
|
||||
if (!mt)
|
||||
return;
|
||||
|
|
@ -216,6 +217,13 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
|
|||
oldest = ps;
|
||||
oldid = id;
|
||||
}
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
|
||||
p = input_mt_get_value(ps, ABS_MT_PRESSURE);
|
||||
if (mt->flags & INPUT_MT_TOTAL_FORCE)
|
||||
reported_p += p;
|
||||
else if (oldid == id)
|
||||
reported_p = p;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
|
|
@ -245,10 +253,8 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
|
|||
input_event(dev, EV_ABS, ABS_X, x);
|
||||
input_event(dev, EV_ABS, ABS_Y, y);
|
||||
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
|
||||
int p = input_mt_get_value(oldest, ABS_MT_PRESSURE);
|
||||
input_event(dev, EV_ABS, ABS_PRESSURE, p);
|
||||
}
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit))
|
||||
input_event(dev, EV_ABS, ABS_PRESSURE, reported_p);
|
||||
} else {
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit))
|
||||
input_event(dev, EV_ABS, ABS_PRESSURE, 0);
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ struct hid_item {
|
|||
#define HID_UP_TELEPHONY 0x000b0000
|
||||
#define HID_UP_CONSUMER 0x000c0000
|
||||
#define HID_UP_DIGITIZER 0x000d0000
|
||||
#define HID_UP_HAPTIC 0x000e0000
|
||||
#define HID_UP_PID 0x000f0000
|
||||
#define HID_UP_BATTERY 0x00850000
|
||||
#define HID_UP_CAMERA 0x00900000
|
||||
|
|
@ -316,6 +317,28 @@ struct hid_item {
|
|||
#define HID_DG_TOOLSERIALNUMBER 0x000d005b
|
||||
#define HID_DG_LATENCYMODE 0x000d0060
|
||||
|
||||
#define HID_HP_SIMPLECONTROLLER 0x000e0001
|
||||
#define HID_HP_WAVEFORMLIST 0x000e0010
|
||||
#define HID_HP_DURATIONLIST 0x000e0011
|
||||
#define HID_HP_AUTOTRIGGER 0x000e0020
|
||||
#define HID_HP_MANUALTRIGGER 0x000e0021
|
||||
#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022
|
||||
#define HID_HP_INTENSITY 0x000e0023
|
||||
#define HID_HP_REPEATCOUNT 0x000e0024
|
||||
#define HID_HP_RETRIGGERPERIOD 0x000e0025
|
||||
#define HID_HP_WAVEFORMVENDORPAGE 0x000e0026
|
||||
#define HID_HP_WAVEFORMVENDORID 0x000e0027
|
||||
#define HID_HP_WAVEFORMCUTOFFTIME 0x000e0028
|
||||
#define HID_HP_WAVEFORMNONE 0x000e1001
|
||||
#define HID_HP_WAVEFORMSTOP 0x000e1002
|
||||
#define HID_HP_WAVEFORMCLICK 0x000e1003
|
||||
#define HID_HP_WAVEFORMBUZZCONTINUOUS 0x000e1004
|
||||
#define HID_HP_WAVEFORMRUMBLECONTINUOUS 0x000e1005
|
||||
#define HID_HP_WAVEFORMPRESS 0x000e1006
|
||||
#define HID_HP_WAVEFORMRELEASE 0x000e1007
|
||||
#define HID_HP_VENDORWAVEFORMMIN 0x000e2001
|
||||
#define HID_HP_VENDORWAVEFORMMAX 0x000e2fff
|
||||
|
||||
#define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065
|
||||
#define HID_BAT_CHARGING 0x00850044
|
||||
|
||||
|
|
@ -423,6 +446,12 @@ struct hid_item {
|
|||
#define HID_REPORT_PROTOCOL 1
|
||||
#define HID_BOOT_PROTOCOL 0
|
||||
|
||||
/*
|
||||
* HID units
|
||||
*/
|
||||
#define HID_UNIT_GRAM 0x0101
|
||||
#define HID_UNIT_NEWTON 0xe111
|
||||
|
||||
/*
|
||||
* This is the global environment of the parser. This information is
|
||||
* persistent for main-items. The global environment can be saved and
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#define INPUT_MT_DROP_UNUSED 0x0004 /* drop contacts not seen in frame */
|
||||
#define INPUT_MT_TRACK 0x0008 /* use in-kernel tracking */
|
||||
#define INPUT_MT_SEMI_MT 0x0010 /* semi-mt device, finger count handled manually */
|
||||
#define INPUT_MT_TOTAL_FORCE 0x0020 /* calculate total force from slots pressure */
|
||||
|
||||
/**
|
||||
* struct input_mt_slot - represents the state of an input MT slot
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */
|
||||
#define INPUT_PROP_POINTING_STICK 0x05 /* is a pointing stick */
|
||||
#define INPUT_PROP_ACCELEROMETER 0x06 /* has accelerometer */
|
||||
#define INPUT_PROP_HAPTIC_TOUCHPAD 0x07 /* is a haptic touchpad */
|
||||
|
||||
#define INPUT_PROP_MAX 0x1f
|
||||
#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1)
|
||||
|
|
|
|||
|
|
@ -429,6 +429,24 @@ struct ff_rumble_effect {
|
|||
__u16 weak_magnitude;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ff_haptic_effect
|
||||
* @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
|
||||
* @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range
|
||||
* @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range
|
||||
* @intensity: strength of the effect as percentage
|
||||
* @repeat_count: number of times to retrigger effect
|
||||
* @retrigger_period: time before effect is retriggered (in ms)
|
||||
*/
|
||||
struct ff_haptic_effect {
|
||||
__u16 hid_usage;
|
||||
__u16 vendor_id;
|
||||
__u8 vendor_waveform_page;
|
||||
__u16 intensity;
|
||||
__u16 repeat_count;
|
||||
__u16 retrigger_period;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ff_effect - defines force feedback effect
|
||||
* @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING,
|
||||
|
|
@ -465,6 +483,7 @@ struct ff_effect {
|
|||
struct ff_periodic_effect periodic;
|
||||
struct ff_condition_effect condition[2]; /* One for each axis */
|
||||
struct ff_rumble_effect rumble;
|
||||
struct ff_haptic_effect haptic;
|
||||
} u;
|
||||
};
|
||||
|
||||
|
|
@ -472,6 +491,7 @@ struct ff_effect {
|
|||
* Force feedback effect types
|
||||
*/
|
||||
|
||||
#define FF_HAPTIC 0x4f
|
||||
#define FF_RUMBLE 0x50
|
||||
#define FF_PERIODIC 0x51
|
||||
#define FF_CONSTANT 0x52
|
||||
|
|
@ -481,7 +501,7 @@ struct ff_effect {
|
|||
#define FF_INERTIA 0x56
|
||||
#define FF_RAMP 0x57
|
||||
|
||||
#define FF_EFFECT_MIN FF_RUMBLE
|
||||
#define FF_EFFECT_MIN FF_HAPTIC
|
||||
#define FF_EFFECT_MAX FF_RAMP
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user