From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-io1-f74.google.com (mail-io1-f74.google.com [209.85.166.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DFB7927BF85 for ; Mon, 18 Aug 2025 23:10:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.166.74 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755558643; cv=none; b=K8++NfedeSYyZSXqIalrZUcvnN3FSbHeCLAtLLxl50sehMLVRO0qolPn5fQUlDM5ZHu1mGz6eyUeWDEJjylwoJjS9yB4fjfhzFzKEAijuzcT5WcOkcqE3gy7rXk3aX38Ie1CTRcwZ0vohbLwCwu/idaaHJdScuuvtvfekqiTDR8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755558643; c=relaxed/simple; bh=96ckk3w4BE3eFejRPR7cCz/hnXvvKjoIJJBFTE9EyWA=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=iXZOFBoNga5a3rw8KzXGwz9+xToXfRF40Pe4r+f10L/s606DZmFGR9+z9uz4Ure5dQg9Ka5RfxwhqWoQcvy44Jzv5/XFl1qKEmDDHU3d71xy5BGLdn/Ue/TGMYZlhtW/tp7nTP4onEHbGHkJe8PoQ+fWbbu0czGaM0G95Id1wHg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--jdenose.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=xT29vCG0; arc=none smtp.client-ip=209.85.166.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--jdenose.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="xT29vCG0" Received: by mail-io1-f74.google.com with SMTP id ca18e2360f4ac-88432d8ddb1so547811239f.1 for ; Mon, 18 Aug 2025 16:10:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1755558640; x=1756163440; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=IFFdFO6bExoyO3S6sABBHDDzfLD5xTf6iI9L+uYHnwA=; b=xT29vCG0q4hIw8hVppOnJ/p6s505eN8PI1tgZOq+KsAPe47nmvksG3FOdlOtJ02cwc FWbohmmAYr1KYcg0e9mArjeIZIgvWYFHKNLoOwY/PWK/YIcVXWgjz+8VbXFf2ZMflCGo zG1JJNRveBLhP99PC1v/Oim/W4FsSEn9/OyjI2+YIgKG2251bdBdUzibcQl49uiSG+23 RWyUmEIoVNbKRpdo7txJS1f1ggx53k1sipZ/WyBS3e3Id8J7QGykWqB0XnGc2o2sZcYo 2y8Sz70Ttv+Soq6ANqH0w/4wsYihmRn0DKKMBIeA4smPyRCMIs9rt+1vMXswgGh3byY9 Z6XQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755558640; x=1756163440; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=IFFdFO6bExoyO3S6sABBHDDzfLD5xTf6iI9L+uYHnwA=; b=Mlm9x+yHoFt7tqsdzuvW3okUchdAblCHBrmml6nPNTvgxX+83aFr8AfEzhomoRRpX+ m9cWq6JnzhKm+O/nzIX0IlvECQwp/hAIf9BwuXIbdzGdKp5cYCgKkei+wGa366CztUDw lEB9ttMqvlfIq71IrQnUJnOhCko9YdiuSCvF+g086ykW4/BB3s5xKbwNT1NetXpCKHNb zpH4OjaEveI1NRvC4BKgMd+oJQbHSBjBj9EBT0q2I+2F2aCX1fWLuOyGzGjpO8TD7Ddo EIIMS+cD/hAc2uUUeEoEV/x4wyfZVidQKoHndmRvx9Sp+iyUG4xLYnbz1eRsK9iqNB6t MWQQ== X-Forwarded-Encrypted: i=1; AJvYcCUttkkRduESXUNCImwbJJknmStpsh2qcvcMqKJhRJIzWU0BsyEEyvX4WU/Lse4FnjqYto6ku1W/TXg=@vger.kernel.org X-Gm-Message-State: AOJu0Yz96Lj3TZPZJ8DTpXmWLoU5omgUcRBVsdp858118pgfiz6dZkdz 5g0sKRUMQD8HPlf6kjoseGzc+7hL6AsW7w92SGJIsBl9ID/7paiyVe6XJge5c5B3mt7Ys7K+rGC gf060JLT9Tg== X-Google-Smtp-Source: AGHT+IFh/CHWQFRkkC0rMB4wTcUHbxuLC22Xi/iQOnekBPr75+nxd9UeL/0LYZeJ8jx0aiDR9sbxghIqkd1D X-Received: from iobfb19.prod.google.com ([2002:a05:6602:3f93:b0:884:1a59:5308]) (user=jdenose job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6602:3fc3:b0:884:476b:352b with SMTP id ca18e2360f4ac-88467f641a9mr95739539f.9.1755558640071; Mon, 18 Aug 2025 16:10:40 -0700 (PDT) Date: Mon, 18 Aug 2025 23:08:47 +0000 In-Reply-To: <20250818-support-forcepads-v3-0-e4f9ab0add84@google.com> Precedence: bulk X-Mailing-List: linux-doc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250818-support-forcepads-v3-0-e4f9ab0add84@google.com> X-Mailer: b4 0.14.2 Message-ID: <20250818-support-forcepads-v3-6-e4f9ab0add84@google.com> Subject: [PATCH v3 06/11] HID: haptic: initialize haptic device From: Jonathan Denose To: Jiri Kosina , Benjamin Tissoires , Dmitry Torokhov , Jonathan Corbet , Henrik Rydberg Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, Angela Czubak , "Sean O'Brien" , Jonathan Denose Content-Type: text/plain; charset="utf-8" From: Angela Czubak Add hid_haptic_init(). Parse autotrigger report to retrieve ordinals for press and release waveforms. Implement force feedback functions. Signed-off-by: Angela Czubak Co-developed-by: Jonathan Denose Signed-off-by: Jonathan Denose --- drivers/hid/hid-haptic.c | 438 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-haptic.h | 5 + 2 files changed, 443 insertions(+) diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index d659a430c1a6b06ded31d49efe4bded909671cb6..ef09b4039f33f15d7220e69fbed10bd8b0362bb4 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -5,12 +5,16 @@ * Copyright (c) 2021 Angela Czubak */ +#include + #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, @@ -25,6 +29,20 @@ void hid_haptic_feature_mapping(struct hid_device *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); @@ -70,3 +88,423 @@ int hid_haptic_input_configured(struct hid_device *hdev, 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 int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect, + struct ff_effect *old) +{ + 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.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); + + 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 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; + 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; + 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; + + 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); diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h index 2e89addf5ec280d5b9a59d06088cc08bd4f445c1..25e0e2cef9399199796d8679d66209381b6a59f2 100644 --- a/drivers/hid/hid-haptic.h +++ b/drivers/hid/hid-haptic.h @@ -69,6 +69,7 @@ int hid_haptic_input_mapping(struct hid_device *hdev, 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); #else static inline void hid_haptic_feature_mapping(struct hid_device *hdev, @@ -98,4 +99,8 @@ int hid_haptic_input_configured(struct hid_device *hdev, { return 0; } +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr) +{ + return 0; +} #endif -- 2.51.0.rc1.193.gad69d77794-goog