blob: df43d11a7438050083e04a3fedd6797c06a73078 [file] [log] [blame]
// 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/mm.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_orig = usage_hid;
haptic->press_ordinal_cur = usage_hid;
} else if (field->value[n] ==
(HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
haptic->release_ordinal_orig = usage_hid;
haptic->release_ordinal_cur = 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_hid_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_KERNEL)
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 void haptic_reset_work_handler(struct work_struct *work)
{
struct hid_haptic_device *haptic = container_of(work,
struct hid_haptic_device,
reset_work);
struct hid_device *hdev = haptic->hdev;
if (haptic->press_ordinal_cur && haptic->release_ordinal_cur)
switch_mode(hdev, haptic, HID_HAPTIC_MODE_KERNEL);
}
void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
{
if (haptic->wq)
queue_work(haptic->wq, &haptic->reset_work);
}
EXPORT_SYMBOL_GPL(hid_haptic_reset);
#ifdef CONFIG_PM
void hid_haptic_resume(struct hid_device *hdev, struct hid_haptic_device *haptic)
{
if (haptic->press_ordinal_cur && haptic->release_ordinal_cur)
switch_mode(hdev, haptic, HID_HAPTIC_MODE_KERNEL);
}
EXPORT_SYMBOL_GPL(hid_haptic_resume);
void hid_haptic_suspend(struct hid_device *hdev, struct hid_haptic_device *haptic)
{
if (haptic->press_ordinal_cur && haptic->release_ordinal_cur)
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
}
EXPORT_SYMBOL_GPL(hid_haptic_suspend);
#endif
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;
/* If vendor range, check vendor id and page */
if (effect->u.hid.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
effect->u.hid.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
(effect->u.hid.vendor_id != haptic->vendor_id ||
effect->u.hid.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.hid.hid_usage) {
ordinal = i;
break;
}
}
if (ordinal < 1)
return -EINVAL;
/* Fill the buffer for the efect id */
fill_effect_buf(haptic, &effect->u.hid, &haptic->effect[effect->id],
ordinal);
if (effect->id == HID_HAPTIC_RELEASE_EFFECT_ID) {
if (haptic->press_ordinal_cur &&
haptic->mode == HID_HAPTIC_MODE_DEVICE) {
switch_mode(hdev, haptic, HID_HAPTIC_MODE_KERNEL);
}
haptic->release_ordinal_cur = ordinal;
} else if (effect->id == HID_HAPTIC_PRESS_EFFECT_ID) {
if (haptic->release_ordinal_cur &&
haptic->mode == HID_HAPTIC_MODE_DEVICE) {
switch_mode(hdev, haptic, HID_HAPTIC_MODE_KERNEL);
}
haptic->press_ordinal_cur = ordinal;
}
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 int hid_haptic_change_control(struct input_dev *dev, int effect_id,
struct file *file, int take)
{
struct hid_haptic_device *haptic = dev->ff->private;
struct hid_haptic_effect_node *effect_node;
struct hid_haptic_effect *effect;
bool found = false;
int ret = 0;
effect = &haptic->effect[effect_id];
mutex_lock(&effect->control_mutex);
list_for_each_entry(effect_node, &effect->control, node) {
if (effect_node->file == file) {
found = true;
break;
}
}
if (take) {
if (!found) {
effect_node = kvzalloc(sizeof(struct hid_haptic_effect),
GFP_KERNEL);
if (!effect_node) {
ret = -ENOMEM;
goto exit;
}
effect_node->file = file;
}
list_add(&effect_node->node, &effect->control);
} else {
if (found) {
list_del(&effect_node->node);
kvfree(effect_node);
}
}
exit:
mutex_unlock(&effect->control_mutex);
return ret;
}
static void effect_set_default(struct ff_effect *effect)
{
effect->type = FF_HID;
effect->id = -1;
effect->u.hid.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
effect->u.hid.intensity = 100;
effect->u.hid.retrigger_period = 0;
effect->u.hid.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);
switch (effect_id) {
case HID_HAPTIC_RELEASE_EFFECT_ID:
ordinal = haptic->release_ordinal_orig;
haptic->release_ordinal_cur = ordinal;
if (!ordinal) {
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
if (haptic->mode == HID_HAPTIC_MODE_KERNEL)
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
} else {
effect.u.hid.hid_usage = HID_HP_WAVEFORMRELEASE &
HID_USAGE;
}
fill_effect_buf(haptic, &effect.u.hid, &haptic->effect[effect_id],
ordinal);
break;
case HID_HAPTIC_PRESS_EFFECT_ID:
ordinal = haptic->press_ordinal_orig;
haptic->press_ordinal_cur = ordinal;
if (!ordinal) {
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
if (haptic->mode == HID_HAPTIC_MODE_KERNEL)
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
} else {
effect.u.hid.hid_usage = HID_HP_WAVEFORMPRESS &
HID_USAGE;
}
fill_effect_buf(haptic, &effect.u.hid, &haptic->effect[effect_id],
ordinal);
break;
default:
break;
}
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);
}
static u32 convert_force_to_logical(struct hid_haptic_device *haptic, u32 value)
{
return (value - haptic->force_physical_minimum) *
haptic->force_resolution + haptic->force_logical_minimum;
}
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_hid_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);
struct ff_effect release_effect, press_effect;
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;
INIT_WORK(&haptic->reset_work, haptic_reset_work_handler);
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);
INIT_LIST_HEAD(&haptic->effect[r].control);
mutex_init(&haptic->effect[r].control_mutex);
}
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);
haptic->mode = HID_HAPTIC_MODE_DEVICE;
haptic->press_threshold = convert_force_to_logical(haptic,
HID_HAPTIC_PRESS_THRESH);
haptic->release_threshold = convert_force_to_logical(haptic,
HID_HAPTIC_RELEASE_THRESH);
input_set_capability(dev, EV_FF, FF_HID);
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->change_control = hid_haptic_change_control;
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;
}
effect_set_default(&release_effect);
if (haptic->release_ordinal_orig)
release_effect.u.hid.hid_usage = HID_HP_WAVEFORMRELEASE &
HID_USAGE;
ret = input_ff_upload(dev, &release_effect, (struct file *)UINTPTR_MAX);
if (ret || release_effect.id != HID_HAPTIC_RELEASE_EFFECT_ID) {
if (!ret) {
ret = -EBUSY;
input_ff_erase(dev, release_effect.id,
(struct file *)UINTPTR_MAX);
}
dev_err(&hdev->dev,
"Failed to allocate id 0 for release effect.\n");
goto input_free;
}
effect_set_default(&press_effect);
if (haptic->press_ordinal_orig)
press_effect.u.hid.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
ret = input_ff_upload(dev, &press_effect, (struct file *)UINTPTR_MAX);
if (ret || press_effect.id != HID_HAPTIC_PRESS_EFFECT_ID) {
if (!ret) {
ret = -EBUSY;
input_ff_erase(dev, press_effect.id,
(struct file *)UINTPTR_MAX);
}
dev_err(&hdev->dev,
"Failed to allocate id 1 for press effect.\n");
goto release_free;
}
return 0;
release_free:
input_ff_erase(dev, release_effect.id, (struct file *)UINTPTR_MAX);
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_handle_press_release(struct hid_haptic_device *haptic)
{
int prev_pressed_state = haptic->pressed_state;
struct input_dev *input = haptic->input_dev;
unsigned long flags;
if (haptic->pressure_sum > haptic->press_threshold)
haptic->pressed_state = 1;
else if (haptic->pressure_sum < haptic->release_threshold)
haptic->pressed_state = 0;
if (!prev_pressed_state && haptic->pressed_state &&
haptic->mode == HID_HAPTIC_MODE_KERNEL &&
list_empty(&haptic->effect[HID_HAPTIC_PRESS_EFFECT_ID].control)) {
spin_lock_irqsave(&input->event_lock, flags);
input->ff->playback(input, HID_HAPTIC_PRESS_EFFECT_ID, 1);
spin_unlock_irqrestore(&input->event_lock, flags);
}
if (prev_pressed_state && !haptic->pressed_state &&
haptic->mode == HID_HAPTIC_MODE_KERNEL &&
list_empty(&haptic->effect[HID_HAPTIC_RELEASE_EFFECT_ID].control)) {
spin_lock_irqsave(&input->event_lock, flags);
input->ff->playback(input, HID_HAPTIC_RELEASE_EFFECT_ID, 1);
spin_unlock_irqrestore(&input->event_lock, flags);
}
}
EXPORT_SYMBOL_GPL(hid_haptic_handle_press_release);
bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
{
if (haptic->mode == HID_HAPTIC_MODE_KERNEL) {
input_event(haptic->input_dev, EV_KEY, BTN_LEFT,
haptic->pressed_state);
return true;
}
return false;
}
EXPORT_SYMBOL_GPL(hid_haptic_handle_input);
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);